new features

This commit is contained in:
Matthias Hochmeister
2026-03-23 16:09:42 +01:00
parent e9a9478aac
commit 8c66492b27
40 changed files with 2016 additions and 117 deletions

View File

@@ -65,8 +65,12 @@ import {
FahrzeugStatus,
FahrzeugStatusLabel,
CreateWartungslogPayload,
UpdateWartungslogPayload,
UpdateStatusPayload,
WartungslogArt,
WartungslogErgebnis,
WartungslogErgebnisLabel,
WartungslogErgebnisColor,
OverlappingBooking,
} from '../types/vehicle.types';
import type { AusruestungListItem } from '../types/equipment.types';
@@ -470,6 +474,7 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
const [dialogOpen, setDialogOpen] = useState(false);
const [saving, setSaving] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null);
const [editingWartungId, setEditingWartungId] = useState<string | null>(null);
const emptyForm: CreateWartungslogPayload = {
datum: '',
@@ -479,10 +484,36 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
kraftstoff_liter: undefined,
kosten: undefined,
externe_werkstatt: '',
ergebnis: undefined,
naechste_faelligkeit: '',
};
const [form, setForm] = useState<CreateWartungslogPayload>(emptyForm);
const openCreateDialog = () => {
setEditingWartungId(null);
setForm(emptyForm);
setSaveError(null);
setDialogOpen(true);
};
const openEditDialog = (entry: FahrzeugWartungslog) => {
setEditingWartungId(entry.id);
setForm({
datum: entry.datum ? new Date(entry.datum).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }) : '',
art: entry.art ?? undefined,
beschreibung: entry.beschreibung,
km_stand: entry.km_stand ?? undefined,
kraftstoff_liter: entry.kraftstoff_liter ?? undefined,
kosten: entry.kosten ?? undefined,
externe_werkstatt: entry.externe_werkstatt ?? '',
ergebnis: entry.ergebnis ?? undefined,
naechste_faelligkeit: entry.naechste_faelligkeit ? entry.naechste_faelligkeit.slice(0, 10) : '',
});
setSaveError(null);
setDialogOpen(true);
};
const handleSubmit = async () => {
if (!form.datum || !form.beschreibung.trim()) {
setSaveError('Datum und Beschreibung sind erforderlich.');
@@ -491,13 +522,29 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
try {
setSaving(true);
setSaveError(null);
await vehiclesApi.addWartungslog(fahrzeugId, {
...form,
datum: fromGermanDate(form.datum) || form.datum,
externe_werkstatt: form.externe_werkstatt || undefined,
});
const isoDate = fromGermanDate(form.datum) || form.datum;
if (editingWartungId) {
const payload: UpdateWartungslogPayload = {
datum: isoDate,
art: form.art,
beschreibung: form.beschreibung,
km_stand: form.km_stand,
externe_werkstatt: form.externe_werkstatt || undefined,
ergebnis: form.ergebnis,
naechste_faelligkeit: form.naechste_faelligkeit || undefined,
};
await vehiclesApi.updateWartungslog(fahrzeugId, editingWartungId, payload);
} else {
await vehiclesApi.addWartungslog(fahrzeugId, {
...form,
datum: isoDate,
externe_werkstatt: form.externe_werkstatt || undefined,
naechste_faelligkeit: form.naechste_faelligkeit || undefined,
});
}
setDialogOpen(false);
setForm(emptyForm);
setEditingWartungId(null);
onAdded();
} catch {
setSaveError('Wartungseintrag konnte nicht gespeichert werden.');
@@ -521,14 +568,20 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 0.25 }}>
<Typography variant="subtitle2">{fmtDate(entry.datum)}</Typography>
{entry.art && <Chip label={entry.art} size="small" variant="outlined" />}
{entry.ergebnis && (
<Chip
label={WartungslogErgebnisLabel[entry.ergebnis]}
size="small"
color={WartungslogErgebnisColor[entry.ergebnis]}
/>
)}
</Box>
<Typography variant="body2">{entry.beschreibung}</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 0.25 }}>
{[
entry.km_stand != null && `${entry.km_stand.toLocaleString('de-DE')} km`,
entry.kraftstoff_liter != null && `${Number(entry.kraftstoff_liter).toFixed(1)} L`,
entry.kosten != null && `${Number(entry.kosten).toFixed(2)}`,
entry.externe_werkstatt && entry.externe_werkstatt,
entry.naechste_faelligkeit && `Nächste Fälligkeit: ${fmtDate(entry.naechste_faelligkeit)}`,
].filter(Boolean).join(' · ')}
</Typography>
{entry.dokument_url ? (
@@ -568,6 +621,11 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
</Button>
) : null}
</Box>
{canWrite && (
<IconButton size="small" onClick={() => openEditDialog(entry)} aria-label="Bearbeiten">
<Edit fontSize="small" />
</IconButton>
)}
</Box>
);
})}
@@ -578,14 +636,14 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
<ChatAwareFab
size="small"
aria-label="Wartung eintragen"
onClick={() => { setForm(emptyForm); setDialogOpen(true); }}
onClick={openCreateDialog}
>
<Add />
</ChatAwareFab>
)}
<Dialog open={dialogOpen} onClose={() => setDialogOpen(false)} maxWidth="sm" fullWidth>
<DialogTitle>Wartung / Service eintragen</DialogTitle>
<DialogTitle>{editingWartungId ? 'Wartungseintrag bearbeiten' : 'Wartung / Service eintragen'}</DialogTitle>
<DialogContent>
{saveError && <Alert severity="error" sx={{ mb: 2 }}>{saveError}</Alert>}
<Grid container spacing={2} sx={{ mt: 0.5 }}>
@@ -624,7 +682,7 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
onChange={(e) => setForm((f) => ({ ...f, beschreibung: e.target.value }))}
/>
</Grid>
<Grid item xs={12} sm={4}>
<Grid item xs={12} sm={6}>
<TextField
label="km-Stand"
type="number"
@@ -634,27 +692,7 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
inputProps={{ min: 0 }}
/>
</Grid>
<Grid item xs={12} sm={4}>
<TextField
label="Kraftstoff (L)"
type="number"
fullWidth
value={form.kraftstoff_liter ?? ''}
onChange={(e) => setForm((f) => ({ ...f, kraftstoff_liter: e.target.value ? Number(e.target.value) : undefined }))}
inputProps={{ min: 0, step: 0.1 }}
/>
</Grid>
<Grid item xs={12} sm={4}>
<TextField
label="Kosten (€)"
type="number"
fullWidth
value={form.kosten ?? ''}
onChange={(e) => setForm((f) => ({ ...f, kosten: e.target.value ? Number(e.target.value) : undefined }))}
inputProps={{ min: 0, step: 0.01 }}
/>
</Grid>
<Grid item xs={12}>
<Grid item xs={12} sm={6}>
<TextField
label="Externe Werkstatt"
fullWidth
@@ -663,6 +701,31 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
placeholder="Name der Werkstatt (wenn extern)"
/>
</Grid>
<Grid item xs={12} sm={6}>
<FormControl fullWidth>
<InputLabel>Ergebnis</InputLabel>
<Select
label="Ergebnis"
value={form.ergebnis ?? ''}
onChange={(e) => setForm((f) => ({ ...f, ergebnis: (e.target.value || undefined) as WartungslogErgebnis | undefined }))}
>
<MenuItem value=""> Kein Ergebnis </MenuItem>
<MenuItem value="bestanden">Bestanden</MenuItem>
<MenuItem value="bestanden_mit_maengeln">Bestanden mit Mängeln</MenuItem>
<MenuItem value="nicht_bestanden">Nicht bestanden</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12} sm={6}>
<TextField
label="Nächste Fälligkeit"
type="date"
fullWidth
value={form.naechste_faelligkeit ?? ''}
onChange={(e) => setForm((f) => ({ ...f, naechste_faelligkeit: e.target.value }))}
InputLabelProps={{ shrink: true }}
/>
</Grid>
</Grid>
</DialogContent>
<DialogActions>