chore: vehicle groups editing

This commit is contained in:
2025-12-01 01:21:02 +01:00
parent f29678a811
commit 53ee601633
5 changed files with 378 additions and 125 deletions
@@ -1,14 +1,10 @@
<template>
<div class="modal-bg" @click="() => (vehiclesStore.selectedVehicleId = -1)"></div>
<div class="modal-bg" @click="closeModal()"></div>
<div class="modal">
<div class="modal" @keydown.esc="closeModal()" ref="modal" tabindex="0">
<div class="modal-content">
<h1 class="modal-header">
{{ mode == 'update' ? 'Edytuj' : 'Dodaj' }} pojazd
<button class="modal-exit" @click="closeModal">
<LucideX :size="40" />
</button>
<span>{{ mode == 'update' ? 'Edytuj' : 'Dodaj' }} pojazd</span>
</h1>
<div class="modal-details" v-if="vehicleRef">
@@ -40,20 +36,22 @@
</div>
<div>
<input
type="checkbox"
name="vehicle-team-only"
id="vehicle-team-only"
v-model="vehicleValues.restrictions!.teamOnly"
/>
<label for="vehicle-team-only">Tylko dla zespołu</label>
<span>
<input
type="checkbox"
name="vehicle-team-only"
id="vehicle-team-only"
v-model="vehicleValues.restrictions!.teamOnly"
/>
<label for="vehicle-team-only">Tylko dla zespołu</label>
</span>
&nbsp;
<input type="checkbox" name="vehicle-hidden" id="vehicle-hidden" v-model="vehicleValues.hidden" />
<label for="vehicle-hidden">Ukryty</label>
<span>
<input type="checkbox" name="vehicle-hidden" id="vehicle-hidden" v-model="vehicleValues.hidden" />
<label for="vehicle-hidden">Ukryty</label>
</span>
</div>
<div></div>
<div>
<b>Miniaturka:</b>
<div>
@@ -98,15 +96,15 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, PropType, reactive, ref, Ref, watch } from 'vue';
import { computed, onMounted, PropType, reactive, ref, Ref, useTemplateRef, watch } from 'vue';
import { useVehiclesStore } from '../../stores/vehicles.store';
import { LucideX } from 'lucide-vue-next';
import { IVehicle, RemoveVehicleGroupAPIResponse, UpdateVehicleAPIResponse } from '../../types/vehicles.types';
import client from '../../common/http';
import { AxiosError } from 'axios';
const vehiclesStore = useVehiclesStore();
const modalElementRef = useTemplateRef('modal');
const vehiclesStore = useVehiclesStore();
const currentVehicleRef: Ref<IVehicle | null> = ref(null);
let vehicleValues: Partial<IVehicle> = reactive({
@@ -121,8 +119,6 @@ let vehicleValues: Partial<IVehicle> = reactive({
hidden: false,
});
let currentChanges: Record<string, any> = reactive({});
const props = defineProps({
mode: {
type: String as PropType<'update' | 'create'>,
@@ -149,22 +145,10 @@ onMounted(() => {
teamOnly: currentVehicleRef.value.restrictions?.teamOnly ?? false,
};
}
modalElementRef.value?.focus();
});
watch(
vehicleValues,
(val) => {
Object.keys(val).forEach((k) => {
const newValue = vehicleValues[k as keyof IVehicle];
const currentValue = currentVehicleRef.value![k as keyof IVehicle];
if (newValue != currentValue) currentChanges[k] = newValue;
else currentChanges[k] = undefined;
});
},
{ deep: true },
);
function closeModal() {
vehiclesStore.selectedVehicleId = -1;
}
@@ -226,6 +210,10 @@ async function removeVehicle() {
if (!vehicle) return;
const removeConfirm = confirm('Czy na pewno chcesz usunąć ten pojazd?');
if (!removeConfirm) return;
try {
const removedData = (await client.delete<RemoveVehicleGroupAPIResponse>(`/manager/vehicles/${vehicle.id}`)).data;
vehicle.group._count.vehicles -= 1;
@@ -260,6 +248,10 @@ async function removeVehicle() {
padding: 1em;
}
h1.modal-header {
justify-content: center;
}
.modal-actions {
display: flex;
gap: 0.5em;
@@ -0,0 +1,326 @@
<template>
<div class="modal-bg" @click="closeModal()"></div>
<div class="modal" @keydown.esc="closeModal()" ref="modal" tabindex="0">
<div class="modal-content">
<h1 class="modal-header">
<span>{{ mode == 'update' ? 'Edytuj' : 'Dodaj' }} grupę #{{ vehicleGroupRef?.id || '???' }}</span>
</h1>
<div class="modal-details" v-if="vehicleGroupRef">
<div><b>ID:</b> {{ vehicleGroupRef.id }}</div>
<div><b>Przypisane pojazdy:</b> {{ vehicleGroupRef._count.vehicles }}</div>
<div>
<b>Rodzaj pojazdów grupy: </b>
<select name="group-vehicle-type" id="group-vehicle-type" v-model="currentVehicleGroupType">
<option value="unit">Jednostka trakcyjna</option>
<option value="wagon">Wagon / ZT</option>
</select>
</div>
<div><b>Nazwa: </b> <input type="text" v-model="vehicleGroupValues.name" /></div>
<div><b>Długość: </b> <input type="number" v-model="vehicleGroupValues.length" /> m</div>
<div><b>Masa: </b> <input type="number" v-model="vehicleGroupValues.weight" /> kg</div>
<div><b>Prędkość maks.:</b> <input type="number" v-model="vehicleGroupValues.speed" /> km/h</div>
<div>
<b>Prędkość luzem: </b>
<input type="number" v-model="vehicleGroupValues.speedLoco" :disabled="currentVehicleGroupType != 'unit'" />
km/h
</div>
<div>
<b>Prędkość z ładunkiem (wagon): </b>
<input
type="number"
v-model="vehicleGroupValues.speedLoaded"
:disabled="currentVehicleGroupType != 'wagon'"
/>
km/h
</div>
<div>
<span>
<input
type="checkbox"
name="cold-start"
id="cold-start"
v-model="vehicleGroupValues.locoProps!.coldStart"
:disabled="currentVehicleGroupType != 'unit'"
/>
<label for="cold-start">Zimny start</label>
</span>
&nbsp;
<span>
<input
type="checkbox"
name="double-manned"
id="double-manned"
v-model="vehicleGroupValues.locoProps!.doubleManned"
:disabled="currentVehicleGroupType != 'unit'"
/>
<label for="double-manned">Podwójna obsada</label>
</span>
</div>
<div class="details-cargo-types" v-if="currentVehicleGroupType == 'wagon'">
<input type="checkbox" id="include-cargo-types" v-model="includeCargoTypes" />
<label for="include-cargo-types">Ładunki (wagon):</label>
<br />
<textarea name="" id="">{{ JSON.stringify(vehicleGroupValues.cargoTypes) }}</textarea>
</div>
<div class="details-mass-speeds">
<input type="checkbox" id="include-mass-speeds" v-model="includeMassSpeeds" />
<label for="include-mass-speeds">Dopuszczalne masy:</label>
(JSON: {{ isMassSpeedsJsonValid ? 'poprawny' : 'niepoprawny' }})
<br />
<textarea name="" id="" v-model="massSpeedsJsonString">{{
JSON.stringify(vehicleGroupValues.massSpeeds)
}}</textarea>
</div>
</div>
<div v-else>Ups! Nie ma takiego elementu! :/</div>
<div class="modal-actions">
<button @click="updateVehicleGroup">Aktualizuj dane</button>
<button @click="removeVehicleGroup">Usuń grupę</button>
<button @click="closeModal">Nie zapisuj</button>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed, onMounted, PropType, reactive, ref, Ref, useTemplateRef, watch } from 'vue';
import { useVehiclesStore } from '../../stores/vehicles.store';
import client from '../../common/http';
import { AxiosError } from 'axios';
import {
IVehicleGroup,
RemoveVehicleGroupAPIResponse,
UpdateVehicleGroupAPIResponse,
} from '../../types/vehicles.types';
const props = defineProps({
mode: {
type: String as PropType<'update' | 'create'>,
required: true,
},
});
const modalElementRef = useTemplateRef('modal');
const vehiclesStore = useVehiclesStore();
const currentVehicleGroupType = ref<'unit' | 'wagon'>('unit');
const includeMassSpeeds = ref(false);
const includeCargoTypes = ref(false);
const massSpeedsJsonString = ref('');
const cargoTypesJsonString = ref('');
const isMassSpeedsJsonValid = ref(true);
const isCargoTypesJsonValid = ref(true);
let vehicleGroupValues: Partial<IVehicleGroup> = reactive({
name: '',
length: 0,
weight: 0,
speed: 0,
speedLoaded: 0,
speedLoco: 0,
locoProps: {
coldStart: false,
doubleManned: false,
},
massSpeeds: null,
cargoTypes: null,
});
const vehicleGroupRef = computed(
() =>
vehiclesStore.vehicleGroupsTable.find((g) => g.vehicleGroupRef.id == vehiclesStore.selectedVehicleGroupId)
?.vehicleGroupRef,
);
onMounted(() => {
if (vehicleGroupRef.value) {
populateVehicleGroupValues(vehicleGroupRef.value);
}
modalElementRef.value?.focus();
});
watch(massSpeedsJsonString, (val) => {
try {
JSON.parse(val);
isMassSpeedsJsonValid.value = true;
} catch (error) {
isMassSpeedsJsonValid.value = false;
}
});
function populateVehicleGroupValues(vehicleGroup: IVehicleGroup) {
if (!vehicleGroup.locoProps) currentVehicleGroupType.value = 'wagon';
vehicleGroupValues.name = vehicleGroup.name || '';
vehicleGroupValues.length = vehicleGroup.length || 0;
vehicleGroupValues.weight = vehicleGroup.weight || 0;
vehicleGroupValues.speed = vehicleGroup.speed || 0;
vehicleGroupValues.speedLoco = vehicleGroup.speedLoco || null;
vehicleGroupValues.speedLoaded = vehicleGroup.speedLoaded || null;
vehicleGroupValues.locoProps = {
coldStart: vehicleGroup.locoProps?.coldStart ?? false,
doubleManned: vehicleGroup.locoProps?.doubleManned ?? false,
};
vehicleGroupValues.cargoTypes = [];
vehicleGroupValues.massSpeeds = {
none: 0,
cargo: {},
passenger: {},
};
if (vehicleGroup.cargoTypes) {
includeCargoTypes.value = true;
vehicleGroupValues.cargoTypes = vehicleGroup.cargoTypes;
}
if (vehicleGroup.massSpeeds) {
includeMassSpeeds.value = true;
vehicleGroupValues.massSpeeds = vehicleGroup.massSpeeds;
}
massSpeedsJsonString.value = JSON.stringify(vehicleGroupValues.massSpeeds);
cargoTypesJsonString.value = JSON.stringify(vehicleGroupValues.cargoTypes);
}
function closeModal() {
vehiclesStore.selectedVehicleGroupId = -1;
}
function handleAPIErrors(error: unknown) {
console.error(error);
if (error instanceof AxiosError) {
return `Nie zaktualizowano grupy: ${error.response?.data.message}`;
} else {
return `Nie zaktualizowano grupy: ${error}`;
}
}
async function updateVehicleGroup() {
const vehicleGroup = vehicleGroupRef.value;
if (!vehicleGroup) return;
try {
const updatedData = (
await client.put<UpdateVehicleGroupAPIResponse>(`/manager/vehicleGroups/${vehicleGroup.id}`, {
name: vehicleGroupValues.name,
length: vehicleGroupValues.length,
weight: vehicleGroupValues.weight,
speed: vehicleGroupValues.speed,
speedLoaded: vehicleGroupValues.speedLoaded,
speedLoco: vehicleGroupValues.speedLoco,
locoProps: currentVehicleGroupType.value == 'unit' ? vehicleGroupValues.locoProps : null,
massSpeeds: includeMassSpeeds.value ? JSON.parse(massSpeedsJsonString.value) : null,
cargoTypes: includeCargoTypes.value ? JSON.parse(cargoTypesJsonString.value) : null,
} as Partial<IVehicleGroup>)
).data;
const tableObject = vehiclesStore.vehicleGroupsTable.find((g) => g.vehicleGroupRef.id == updatedData.id);
if (tableObject) {
tableObject.vehicleGroupRef = updatedData;
}
// alert('Zaktualizowano grupę: ' + updatedData.name);
closeModal();
} catch (error) {
alert(handleAPIErrors(error));
}
}
async function removeVehicleGroup() {
const vehicleGroup = vehicleGroupRef.value;
if (!vehicleGroup) return;
const removeConfirm = confirm('Czy na pewno chcesz usunąć ten pojazd?');
if (!removeConfirm) return;
try {
const removedData = (
await client.delete<RemoveVehicleGroupAPIResponse>(`/manager/vehicleGroups/${vehicleGroup.id}`)
).data;
vehiclesStore.vehicleGroupsTable = vehiclesStore.vehicleGroupsTable.filter(
(v) => v.vehicleGroupRef.id != vehicleGroup.id,
);
// alert('Usunięto pojazd: ' + removedData.name);
closeModal();
} catch (error) {
alert(handleAPIErrors(error));
}
}
</script>
<style lang="scss" scoped>
@use '../../styles/modal';
.modal-content {
display: grid;
gap: 0.5em;
grid-template-rows: auto 1fr auto;
height: 100%;
line-height: 2em;
}
h1.modal-header {
justify-content: center;
}
.modal-details {
display: flex;
flex-direction: column;
gap: 0.5em;
overflow: auto;
padding: 1em;
}
.details-cargo-types textarea,
.details-mass-speeds textarea {
width: 100%;
height: 200px;
}
.cargo-types-actions {
display: flex;
gap: 0.5em;
}
.modal-actions {
display: flex;
gap: 0.5em;
font-size: 1.1em;
padding: 1em;
button {
width: 100%;
}
}
</style>
@@ -18,24 +18,10 @@
wyników
</div>
<VehicleGroupEditModal v-if="vehiclesStore.selectedVehicleGroupId != -1" mode="update" />
<div class="table-wrapper">
<table class="vehicle-manager-table">
<!-- <thead>
<tr>
<td style="width: 50px">#</td>
<td style="width: 200px">Nazwa</td>
<td style="width: 80px">Długość</td>
<td style="width: 80px">Masa</td>
<td style="width: 100px">Prędkość</td>
<td style="width: 100px">Prędkość lok.</td>
<td style="width: 100px">Prędkość ładown.</td>
<td style="width: 100px">Pojazdy</td>
<td style="width: 80px">Zimny start</td>
<td style="width: 80px">Podwójna obsada</td>
<td style="width: 200px">Prędkości wg masy</td>
</tr>
</thead> -->
<thead>
<tr>
<td
@@ -55,52 +41,52 @@
</thead>
<tbody>
<tr v-for="row in vehicleGroupsTableComp" :key="row.vehicleGroupRef.id">
<tr
v-for="row in vehicleGroupsTableComp"
:key="row.vehicleGroupRef.id"
@click="openEditModal(row.vehicleGroupRef.id)"
>
<td>{{ row.vehicleGroupRef.id }}</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.name }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.length }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.weight }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.speed }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.speedLoco }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.speedLoaded }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef._count.vehicles }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.locoProps?.coldStart ? '✅' : '❌' }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.locoProps?.doubleManned ? '✅' : '❌' }}
</td>
<td class="editable">
<td>
{{ row.vehicleGroupRef.massSpeeds ? 'WPISANE' : 'BRAK' }}
</td>
<td class="editable" @click="removeVehicleGroupRow(row.vehicleGroupRef.id)">
<img src="/icon-trash.svg" alt="remove" />
</td>
</tr>
</tbody>
</table>
@@ -112,6 +98,7 @@ import { computed, onMounted, ref, watch } from 'vue';
import { useVehiclesStore } from '../../stores/vehicles.store';
import { LucideArrowDown, LucideArrowUp, LucidePlus, LucideX } from 'lucide-vue-next';
import { IVehicleGroup, IVehicleGroupTableRow, VehicleGroupEditRowKey } from '../../types/vehicles.types';
import VehicleGroupEditModal from './VehicleGroupEditModal.vue';
interface TableHeader {
id: string;
@@ -142,7 +129,6 @@ const headers: TableHeader[] = [
{ id: 'coldStart', elementWidth: 75, title: 'Zimny start', sortable: true },
{ id: 'doubleManned', elementWidth: 75, title: 'Podwójna obsada', sortable: true },
{ id: 'massSpeeds', elementWidth: 100, title: 'Prędkości wg masy', sortable: false },
{ id: 'remove', elementWidth: 75, title: 'Usuń', sortable: false },
];
const vehiclesStore = useVehiclesStore();
@@ -183,27 +169,8 @@ function sortTableBy(id: string) {
activeSortKey.value = id;
}
async function editRowPrimitive(row: IVehicleGroupTableRow, editKey: VehicleGroupEditRowKey) {
if (!(editKey in row.vehicleGroupRef)) return;
let rowValue = row.vehicleGroupRef[editKey];
if (typeof rowValue === 'string' || typeof rowValue === 'undefined' || rowValue == null) {
const promptValue = prompt('Zmień wartość:', rowValue ?? '');
if (promptValue == null) return;
const updatedData = await vehiclesStore.updateVehicleGroup(row.vehicleGroupRef.id, editKey, promptValue);
if (updatedData) {
(row.vehicleGroupRef[editKey] as any) = updatedData[editKey];
}
} else if (typeof rowValue == 'boolean') {
const updatedData = await vehiclesStore.updateVehicleGroup(row.vehicleGroupRef.id, editKey, !rowValue);
if (updatedData) {
(row.vehicleGroupRef[editKey] as any) = updatedData[editKey];
}
}
function openEditModal(vehicleId: number) {
vehiclesStore.selectedVehicleGroupId = vehicleId;
}
async function addVehicleGroupRow() {
@@ -227,18 +194,6 @@ async function addVehicleGroupRow() {
vehicleGroupSearchInput.value = data.name;
}
}
async function removeVehicleGroupRow(id: number) {
const confirmRemove = confirm('Czy na pewno chcesz usunąć ten pojazd?');
if (!confirmRemove) return;
const removedData = await vehiclesStore.removeVehicle(id);
if (removedData) {
vehiclesStore.vehicleGroupsTable = vehiclesStore.vehicleGroupsTable.filter((v) => v.vehicleGroupRef.id != id);
}
}
</script>
<style lang="scss" scoped></style>
@@ -57,22 +57,6 @@
</td>
<td>
<!-- <select
v-if="currentEditId == row.vehicleRef.id"
@blur="(e) => editVehicleGroup(e, row)"
:id="`select-group-${row.vehicleRef.id}`"
ref="select-group"
style="width: 100%"
>
<option
v-for="value in vehiclesStore.vehicleGroupsTable"
:value="value.vehicleGroupRef.id"
:selected="row.vehicleRef.group.id == value.vehicleGroupRef.id"
>
{{ value.vehicleGroupRef.name }}
</option>
</select> -->
<span>{{ row.vehicleRef.group.name }}</span>
</td>
@@ -95,10 +79,6 @@
<td>
{{ row.vehicleRef.hidden ? '✅' : '❌' }}
</td>
<td>
<img src="/icon-trash.svg" alt="remove" />
</td>
</tr>
</tbody>
</table>
@@ -106,9 +86,9 @@
</template>
<script lang="ts" setup>
import { computed, nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import { useVehiclesStore } from '../../stores/vehicles.store';
import { IVehicle, IVehicleTableRow, VehicleEditRestrictionKey, VehicleEditRowKey } from '../../types/vehicles.types';
import { IVehicle } from '../../types/vehicles.types';
import { LucideArrowDown, LucideArrowUp, LucidePlus, LucideX } from 'lucide-vue-next';
import VehicleEditModal from './VehicleEditModal.vue';
@@ -141,7 +121,6 @@ const headers: TableHeader[] = [
{ id: 'sponsorOnly', elementWidth: 130, title: 'Tylko sponsorzy do', sortable: true },
{ id: 'teamOnly', elementWidth: 100, title: 'Tylko zespół', sortable: true },
{ id: 'hidden', elementWidth: 75, title: 'Ukryty', sortable: true },
{ id: 'remove', elementWidth: 75, title: 'Usuń', sortable: false },
];
const vehiclesStore = useVehiclesStore();
+1
View File
@@ -24,6 +24,7 @@ export const useVehiclesStore = defineStore('vehiclesStore', {
dataState: LoadingState.INIT,
selectedVehicleId: -1,
selectedVehicleGroupId: -1,
vehiclesTable: [] as IVehicleTableRow[],
vehicleGroupsTable: [] as IVehicleGroupTableRow[],