new features
This commit is contained in:
@@ -43,6 +43,7 @@ import {
|
||||
DirectionsCar,
|
||||
Edit,
|
||||
Error as ErrorIcon,
|
||||
History,
|
||||
LocalFireDepartment,
|
||||
MoreHoriz,
|
||||
PauseCircle,
|
||||
@@ -121,6 +122,58 @@ function fmtDatetime(iso: string | Date | null | undefined): string {
|
||||
return fmtDate(iso ? new Date(iso).toISOString() : null);
|
||||
}
|
||||
|
||||
// ── Status History Section ────────────────────────────────────────────────────
|
||||
|
||||
const StatusHistorySection: React.FC<{ vehicleId: string }> = ({ vehicleId }) => {
|
||||
const [history, setHistory] = useState<{ alter_status: string; neuer_status: string; bemerkung?: string; geaendert_von_name?: string; erstellt_am: string }[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
vehiclesApi.getStatusHistory(vehicleId)
|
||||
.then(setHistory)
|
||||
.catch(() => setHistory([]))
|
||||
.finally(() => setLoading(false));
|
||||
}, [vehicleId]);
|
||||
|
||||
if (loading || history.length === 0) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Typography variant="h6" sx={{ mt: 3, mb: 1.5, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<History fontSize="small" /> Status-Historie
|
||||
</Typography>
|
||||
<TableContainer component={Paper} variant="outlined">
|
||||
<Table size="small">
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Datum</TableCell>
|
||||
<TableCell>Von</TableCell>
|
||||
<TableCell>Nach</TableCell>
|
||||
<TableCell>Bemerkung</TableCell>
|
||||
<TableCell>Geändert von</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{history.map((h, idx) => (
|
||||
<TableRow key={idx}>
|
||||
<TableCell>{fmtDatetime(h.erstellt_am)}</TableCell>
|
||||
<TableCell>
|
||||
<Chip size="small" label={FahrzeugStatusLabel[h.alter_status as FahrzeugStatus] || h.alter_status} color={STATUS_CHIP_COLOR[h.alter_status as FahrzeugStatus] || 'default'} />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Chip size="small" label={FahrzeugStatusLabel[h.neuer_status as FahrzeugStatus] || h.neuer_status} color={STATUS_CHIP_COLOR[h.neuer_status as FahrzeugStatus] || 'default'} />
|
||||
</TableCell>
|
||||
<TableCell>{h.bemerkung || '—'}</TableCell>
|
||||
<TableCell>{h.geaendert_von_name || '—'}</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// ── Übersicht Tab ─────────────────────────────────────────────────────────────
|
||||
|
||||
interface UebersichtTabProps {
|
||||
@@ -148,7 +201,7 @@ const UebersichtTab: React.FC<UebersichtTabProps> = ({ vehicle, onStatusUpdated,
|
||||
|
||||
const openDialog = () => {
|
||||
setNewStatus(vehicle.status);
|
||||
setBemerkung(vehicle.status_bemerkung ?? '');
|
||||
setBemerkung('');
|
||||
setAusserDienstVon(
|
||||
vehicle.ausser_dienst_von ? new Date(vehicle.ausser_dienst_von).toISOString().slice(0, 16) : ''
|
||||
);
|
||||
@@ -323,6 +376,9 @@ const UebersichtTab: React.FC<UebersichtTabProps> = ({ vehicle, onStatusUpdated,
|
||||
})}
|
||||
</Grid>
|
||||
|
||||
{/* Status history */}
|
||||
<StatusHistorySection vehicleId={vehicle.id} />
|
||||
|
||||
{/* Status change dialog */}
|
||||
<Dialog open={statusDialogOpen} onClose={closeDialog} maxWidth="sm" fullWidth>
|
||||
<DialogTitle>Fahrzeugstatus ändern</DialogTitle>
|
||||
@@ -475,6 +531,42 @@ const WartungTab: React.FC<WartungTabProps> = ({ fahrzeugId, wartungslog, onAdde
|
||||
entry.externe_werkstatt && entry.externe_werkstatt,
|
||||
].filter(Boolean).join(' · ')}
|
||||
</Typography>
|
||||
{entry.dokument_url ? (
|
||||
<Chip
|
||||
label="Dokument"
|
||||
size="small"
|
||||
color="info"
|
||||
variant="outlined"
|
||||
component="a"
|
||||
href={`/api/uploads/${entry.dokument_url.split('/uploads/')[1] || entry.dokument_url}`}
|
||||
target="_blank"
|
||||
clickable
|
||||
sx={{ mt: 0.5 }}
|
||||
/>
|
||||
) : canWrite ? (
|
||||
<Button
|
||||
size="small"
|
||||
component="label"
|
||||
sx={{ mt: 0.5, textTransform: 'none', fontSize: '0.75rem' }}
|
||||
>
|
||||
Dokument hochladen
|
||||
<input
|
||||
type="file"
|
||||
hidden
|
||||
accept=".pdf,.doc,.docx,.jpg,.png"
|
||||
onChange={async (e) => {
|
||||
const file = e.target.files?.[0];
|
||||
if (!file) return;
|
||||
try {
|
||||
await vehiclesApi.uploadWartungFile(entry.id, file);
|
||||
onAdded();
|
||||
} catch {
|
||||
// silent fail — user can retry
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Button>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user