refactor: move vehicle type assignment from detail page to settings page

This commit is contained in:
Matthias Hochmeister
2026-03-28 17:34:29 +01:00
parent 534a24edbf
commit bccb0745b8
2 changed files with 118 additions and 84 deletions

View File

@@ -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() {
</Button>
</DialogActions>
</Dialog>
<Divider sx={{ my: 4 }} />
<VehicleTypeAssignment allTypes={fahrzeugTypen} />
</Container>
</DashboardLayout>
);
}
// ── 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<FahrzeugTyp[]>([]);
const [saving, setSaving] = useState(false);
// cache of per-vehicle types: vehicleId → FahrzeugTyp[]
const [vehicleTypesMap, setVehicleTypesMap] = useState<Record<string, FahrzeugTyp[]>>({});
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 (
<>
<Typography variant="h6" sx={{ mb: 2 }}>Typzuweisung je Fahrzeug</Typography>
{isLoading ? (
<CircularProgress size={24} />
) : (
<TableContainer component={Paper}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Fahrzeug</TableCell>
<TableCell>Zugewiesene Typen</TableCell>
<TableCell align="right">Aktionen</TableCell>
</TableRow>
</TableHead>
<TableBody>
{vehicles.map((v) => {
const types = vehicleTypesMap[v.id];
return (
<TableRow key={v.id} hover>
<TableCell>{v.bezeichnung ?? v.kurzname}</TableCell>
<TableCell>
{types === undefined ? (
<Typography variant="body2" color="text.disabled"></Typography>
) : types.length === 0 ? (
<Typography variant="body2" color="text.disabled">Keine</Typography>
) : (
<Box sx={{ display: 'flex', gap: 0.5, flexWrap: 'wrap' }}>
{types.map((t) => <Chip key={t.id} label={t.name} size="small" variant="outlined" />)}
</Box>
)}
</TableCell>
<TableCell align="right">
<IconButton size="small" onClick={() => openAssign(v.id, v.bezeichnung ?? v.kurzname ?? v.id)}>
<EditIcon fontSize="small" />
</IconButton>
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</TableContainer>
)}
<Dialog open={!!assignDialog} onClose={() => setAssignDialog(null)} maxWidth="sm" fullWidth>
<DialogTitle>Typen für {assignDialog?.vehicleName}</DialogTitle>
<DialogContent sx={{ mt: 1 }}>
<Autocomplete
multiple
options={allTypes}
getOptionLabel={(o) => o.name}
value={selected}
onChange={(_e, val) => setSelected(val)}
isOptionEqualToValue={(a, b) => a.id === b.id}
renderInput={(params) => <TextField {...params} label="Fahrzeugtypen" />}
/>
</DialogContent>
<DialogActions>
<Button onClick={() => setAssignDialog(null)}>Abbrechen</Button>
<Button variant="contained" onClick={handleSave} disabled={saving}>
{saving ? <CircularProgress size={20} /> : 'Speichern'}
</Button>
</DialogActions>
</Dialog>
</>
);
}