add features
This commit is contained in:
159
frontend/src/components/vehicles/InspectionAlerts.tsx
Normal file
159
frontend/src/components/vehicles/InspectionAlerts.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
AlertTitle,
|
||||
Box,
|
||||
CircularProgress,
|
||||
Collapse,
|
||||
Link,
|
||||
Typography,
|
||||
} from '@mui/material';
|
||||
import { Link as RouterLink } from 'react-router-dom';
|
||||
import { vehiclesApi } from '../../services/vehicles';
|
||||
import { InspectionAlert, PruefungArtLabel, PruefungArt } from '../../types/vehicle.types';
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function formatDate(iso: string): string {
|
||||
return new Date(iso).toLocaleDateString('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric',
|
||||
});
|
||||
}
|
||||
|
||||
type Urgency = 'overdue' | 'urgent' | 'warning';
|
||||
|
||||
function getUrgency(tage: number): Urgency {
|
||||
if (tage < 0) return 'overdue';
|
||||
if (tage <= 14) return 'urgent';
|
||||
return 'warning';
|
||||
}
|
||||
|
||||
const URGENCY_CONFIG: Record<Urgency, { severity: 'error' | 'warning'; label: string }> = {
|
||||
overdue: { severity: 'error', label: 'Überfällig' },
|
||||
urgent: { severity: 'error', label: 'Dringend (≤ 14 Tage)' },
|
||||
warning: { severity: 'warning', label: 'Fällig in Kürze (≤ 30 Tage)' },
|
||||
};
|
||||
|
||||
// ── Component ─────────────────────────────────────────────────────────────────
|
||||
|
||||
interface InspectionAlertsProps {
|
||||
/** How many days ahead to fetch — default 30 */
|
||||
daysAhead?: number;
|
||||
/** Collapse into a single banner if no alerts */
|
||||
hideWhenEmpty?: boolean;
|
||||
}
|
||||
|
||||
const InspectionAlerts: React.FC<InspectionAlertsProps> = ({
|
||||
daysAhead = 30,
|
||||
hideWhenEmpty = true,
|
||||
}) => {
|
||||
const [alerts, setAlerts] = useState<InspectionAlert[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
|
||||
const fetchAlerts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
const data = await vehiclesApi.getAlerts(daysAhead);
|
||||
if (mounted) setAlerts(data);
|
||||
} catch {
|
||||
if (mounted) setError('Prüfungshinweise konnten nicht geladen werden.');
|
||||
} finally {
|
||||
if (mounted) setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAlerts();
|
||||
return () => { mounted = false; };
|
||||
}, [daysAhead]);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 1 }}>
|
||||
<CircularProgress size={16} />
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Prüfungsfristen werden geprüft…
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return <Alert severity="error">{error}</Alert>;
|
||||
}
|
||||
|
||||
if (alerts.length === 0) {
|
||||
if (hideWhenEmpty) return null;
|
||||
return (
|
||||
<Alert severity="success">
|
||||
Alle Prüfungsfristen sind aktuell. Keine Fälligkeiten in den nächsten {daysAhead} Tagen.
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
|
||||
// Group by urgency
|
||||
const overdue = alerts.filter((a) => a.tage < 0);
|
||||
const urgent = alerts.filter((a) => a.tage >= 0 && a.tage <= 14);
|
||||
const warning = alerts.filter((a) => a.tage > 14);
|
||||
|
||||
const groups: Array<{ urgency: Urgency; items: InspectionAlert[] }> = [
|
||||
{ urgency: 'overdue', items: overdue },
|
||||
{ urgency: 'urgent', items: urgent },
|
||||
{ urgency: 'warning', items: warning },
|
||||
].filter((g) => g.items.length > 0);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||||
{groups.map(({ urgency, items }) => {
|
||||
const { severity, label } = URGENCY_CONFIG[urgency];
|
||||
return (
|
||||
<Alert key={urgency} severity={severity} variant="outlined">
|
||||
<AlertTitle sx={{ fontWeight: 600 }}>{label}</AlertTitle>
|
||||
<Box component="ul" sx={{ m: 0, pl: 2 }}>
|
||||
{items.map((alert) => {
|
||||
const artLabel = PruefungArtLabel[alert.pruefungArt as PruefungArt] ?? alert.pruefungArt;
|
||||
const dateStr = formatDate(alert.faelligAm);
|
||||
const tageText = alert.tage < 0
|
||||
? `seit ${Math.abs(alert.tage)} Tag${Math.abs(alert.tage) === 1 ? '' : 'en'} überfällig`
|
||||
: alert.tage === 0
|
||||
? 'heute fällig'
|
||||
: `fällig in ${alert.tage} Tag${alert.tage === 1 ? '' : 'en'}`;
|
||||
|
||||
return (
|
||||
<Collapse key={alert.pruefungId} in timeout="auto">
|
||||
<Box component="li" sx={{ mb: 0.5 }}>
|
||||
<Link
|
||||
component={RouterLink}
|
||||
to={`/fahrzeuge/${alert.fahrzeugId}`}
|
||||
color="inherit"
|
||||
underline="hover"
|
||||
sx={{ fontWeight: 500 }}
|
||||
>
|
||||
{alert.bezeichnung}
|
||||
{alert.kurzname ? ` (${alert.kurzname})` : ''}
|
||||
</Link>
|
||||
{' — '}
|
||||
<strong>{artLabel}</strong>
|
||||
{' '}
|
||||
<Typography component="span" variant="body2">
|
||||
{tageText} ({dateStr})
|
||||
</Typography>
|
||||
</Box>
|
||||
</Collapse>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
</Alert>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default InspectionAlerts;
|
||||
Reference in New Issue
Block a user