diff --git a/frontend/src/pages/FahrzeugDetail.tsx b/frontend/src/pages/FahrzeugDetail.tsx index 8e73086..c7fd039 100644 --- a/frontend/src/pages/FahrzeugDetail.tsx +++ b/frontend/src/pages/FahrzeugDetail.tsx @@ -1,7 +1,6 @@ import React, { useEffect, useState, useCallback } from 'react'; import { Alert, - Autocomplete, Box, Button, Chip, @@ -61,8 +60,6 @@ import { vehiclesApi } from '../services/vehicles'; import GermanDateField from '../components/shared/GermanDateField'; import { fromGermanDate } from '../utils/dateInput'; import { equipmentApi } from '../services/equipment'; -import { fahrzeugTypenApi } from '../services/fahrzeugTypen'; -import type { FahrzeugTyp } from '../types/checklist.types'; import { FahrzeugDetail as FahrzeugDetailType, FahrzeugWartungslog, @@ -207,43 +204,6 @@ const UebersichtTab: React.FC = ({ vehicle, onStatusUpdated, const [saveError, setSaveError] = useState(null); const [overlappingBookings, setOverlappingBookings] = useState([]); - // ── Fahrzeugtypen ── - const [allTypes, setAllTypes] = useState([]); - const [vehicleTypes, setVehicleTypes] = useState([]); - const [typesLoading, setTypesLoading] = useState(true); - const [editingTypes, setEditingTypes] = useState(false); - const [selectedTypes, setSelectedTypes] = useState([]); - const [typesSaving, setTypesSaving] = useState(false); - - useEffect(() => { - let cancelled = false; - Promise.all([ - fahrzeugTypenApi.getAll(), - fahrzeugTypenApi.getTypesForVehicle(vehicle.id), - ]) - .then(([all, assigned]) => { - if (cancelled) return; - setAllTypes(all); - setVehicleTypes(assigned); - }) - .catch(() => {}) - .finally(() => { if (!cancelled) setTypesLoading(false); }); - return () => { cancelled = true; }; - }, [vehicle.id]); - - const handleSaveTypes = async () => { - try { - setTypesSaving(true); - await fahrzeugTypenApi.setTypesForVehicle(vehicle.id, selectedTypes.map((t) => t.id)); - setVehicleTypes(selectedTypes); - setEditingTypes(false); - } catch { - // silent — keep dialog open - } finally { - setTypesSaving(false); - } - }; - const isAusserDienst = (s: FahrzeugStatus) => s === FahrzeugStatus.AusserDienstWartung || s === FahrzeugStatus.AusserDienstSchaden; @@ -383,50 +343,6 @@ const UebersichtTab: React.FC = ({ vehicle, onStatusUpdated, ))} - {/* Fahrzeugtypen */} - - Fahrzeugtypen - - {typesLoading ? ( - - ) : editingTypes ? ( - - o.name} - value={selectedTypes} - onChange={(_e, val) => setSelectedTypes(val)} - isOptionEqualToValue={(a, b) => a.id === b.id} - renderInput={(params) => } - sx={{ minWidth: 300, flexGrow: 1 }} - /> - - - - ) : ( - - {vehicleTypes.length === 0 ? ( - Keine Typen zugewiesen - ) : ( - vehicleTypes.map((t) => ( - - )) - )} - {canEdit && ( - { setSelectedTypes(vehicleTypes); setEditingTypes(true); }} - aria-label="Fahrzeugtypen bearbeiten" - > - - - )} - - )} - {/* Inspection deadline quick view */} Prüf- und Wartungsfristen diff --git a/frontend/src/pages/FahrzeugEinstellungen.tsx b/frontend/src/pages/FahrzeugEinstellungen.tsx index ac14bbe..42bde11 100644 --- a/frontend/src/pages/FahrzeugEinstellungen.tsx +++ b/frontend/src/pages/FahrzeugEinstellungen.tsx @@ -1,14 +1,17 @@ import { useState } from 'react'; import { Alert, + Autocomplete, Box, Button, + Chip, CircularProgress, Container, Dialog, DialogActions, DialogContent, DialogTitle, + Divider, IconButton, Paper, Table, @@ -31,6 +34,7 @@ import DashboardLayout from '../components/dashboard/DashboardLayout'; import { usePermissionContext } from '../contexts/PermissionContext'; import { useNotification } from '../contexts/NotificationContext'; import { fahrzeugTypenApi } from '../services/fahrzeugTypen'; +import { vehiclesApi } from '../services/vehicles'; import type { FahrzeugTyp } from '../types/checklist.types'; export default function FahrzeugEinstellungen() { @@ -229,7 +233,121 @@ export default function FahrzeugEinstellungen() { + + + ); } + +// ── Per-vehicle type assignment ──────────────────────────────────────────────── + +function VehicleTypeAssignment({ allTypes }: { allTypes: FahrzeugTyp[] }) { + const { showSuccess, showError } = useNotification(); + const { data: vehicles = [], isLoading } = useQuery({ + queryKey: ['vehicles'], + queryFn: vehiclesApi.getAll, + }); + + const [assignDialog, setAssignDialog] = useState<{ vehicleId: string; vehicleName: string; current: FahrzeugTyp[] } | null>(null); + const [selected, setSelected] = useState([]); + const [saving, setSaving] = useState(false); + + // cache of per-vehicle types: vehicleId → FahrzeugTyp[] + const [vehicleTypesMap, setVehicleTypesMap] = useState>({}); + + const openAssign = async (vehicleId: string, vehicleName: string) => { + let current = vehicleTypesMap[vehicleId]; + if (!current) { + try { current = await fahrzeugTypenApi.getTypesForVehicle(vehicleId); } + catch { current = []; } + setVehicleTypesMap((m) => ({ ...m, [vehicleId]: current })); + } + setSelected(current); + setAssignDialog({ vehicleId, vehicleName, current }); + }; + + const handleSave = async () => { + if (!assignDialog) return; + try { + setSaving(true); + await fahrzeugTypenApi.setTypesForVehicle(assignDialog.vehicleId, selected.map((t) => t.id)); + setVehicleTypesMap((m) => ({ ...m, [assignDialog.vehicleId]: selected })); + setAssignDialog(null); + showSuccess('Typen gespeichert'); + } catch { + showError('Fehler beim Speichern'); + } finally { + setSaving(false); + } + }; + + return ( + <> + Typzuweisung je Fahrzeug + {isLoading ? ( + + ) : ( + + + + + Fahrzeug + Zugewiesene Typen + Aktionen + + + + {vehicles.map((v) => { + const types = vehicleTypesMap[v.id]; + return ( + + {v.bezeichnung ?? v.kurzname} + + {types === undefined ? ( + + ) : types.length === 0 ? ( + Keine + ) : ( + + {types.map((t) => )} + + )} + + + openAssign(v.id, v.bezeichnung ?? v.kurzname ?? v.id)}> + + + + + ); + })} + +
+
+ )} + + setAssignDialog(null)} maxWidth="sm" fullWidth> + Typen für {assignDialog?.vehicleName} + + o.name} + value={selected} + onChange={(_e, val) => setSelected(val)} + isOptionEqualToValue={(a, b) => a.id === b.id} + renderInput={(params) => } + /> + + + + + + + + ); +}