import React, { useEffect, useState, useCallback } from 'react';
import {
Alert,
Box,
Button,
Card,
CardActionArea,
CardContent,
CardMedia,
Chip,
CircularProgress,
Container,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
IconButton,
InputAdornment,
Paper,
Tab,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Tabs,
TextField,
Tooltip,
Typography,
} from '@mui/material';
import {
Add,
Add as AddIcon,
CheckCircle,
Delete as DeleteIcon,
DirectionsCar,
Edit as EditIcon,
Error as ErrorIcon,
FileDownload,
PauseCircle,
School,
Search,
Warning,
ReportProblem,
} from '@mui/icons-material';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import ChatAwareFab from '../components/shared/ChatAwareFab';
import { vehiclesApi } from '../services/vehicles';
import { equipmentApi } from '../services/equipment';
import { fahrzeugTypenApi } from '../services/fahrzeugTypen';
import type { VehicleEquipmentWarning } from '../types/equipment.types';
import { AusruestungStatus, AusruestungStatusLabel } from '../types/equipment.types';
import {
FahrzeugListItem,
FahrzeugStatus,
FahrzeugStatusLabel,
} from '../types/vehicle.types';
import type { FahrzeugTyp } from '../types/checklist.types';
import { usePermissions } from '../hooks/usePermissions';
import { usePermissionContext } from '../contexts/PermissionContext';
import { useNotification } from '../contexts/NotificationContext';
// ── Status chip config ────────────────────────────────────────────────────────
const STATUS_CONFIG: Record<
FahrzeugStatus,
{ color: 'success' | 'warning' | 'error' | 'info'; icon: React.ReactElement }
> = {
[FahrzeugStatus.Einsatzbereit]: { color: 'success', icon: },
[FahrzeugStatus.AusserDienstWartung]: { color: 'warning', icon: },
[FahrzeugStatus.AusserDienstSchaden]: { color: 'error', icon: },
};
// ── Inspection badge helpers ──────────────────────────────────────────────────
type InspBadgeColor = 'success' | 'warning' | 'error' | 'default';
function inspBadgeColor(tage: number | null): InspBadgeColor {
if (tage === null) return 'default';
if (tage < 0) return 'error';
if (tage <= 30) return 'warning';
return 'success';
}
function inspBadgeLabel(art: string, tage: number | null, faelligAm: string | null): string {
if (faelligAm === null) return '';
const date = new Date(faelligAm).toLocaleDateString('de-DE', {
day: '2-digit', month: '2-digit', year: '2-digit',
});
if (tage === null) return `${art}: ${date}`;
if (tage < 0) return `${art}: ÜBERFÄLLIG (${date})`;
if (tage === 0) return `${art}: heute (${date})`;
return `${art}: ${date}`;
}
function inspTooltipTitle(fullLabel: string, tage: number | null, faelligAm: string | null): string {
if (!faelligAm) return fullLabel;
const date = new Date(faelligAm).toLocaleDateString('de-DE');
if (tage !== null && tage < 0) {
return `${fullLabel}: Seit ${Math.abs(tage)} Tagen überfällig!`;
}
return `${fullLabel}: Fällig am ${date}`;
}
// ── Vehicle Card ──────────────────────────────────────────────────────────────
interface VehicleCardProps {
vehicle: FahrzeugListItem;
onClick: (id: string) => void;
warnings?: VehicleEquipmentWarning[];
}
const VehicleCard: React.FC = ({ vehicle, onClick, warnings = [] }) => {
const status = vehicle.status as FahrzeugStatus;
const statusCfg = STATUS_CONFIG[status] ?? STATUS_CONFIG[FahrzeugStatus.Einsatzbereit];
const isSchaden = status === FahrzeugStatus.AusserDienstSchaden;
const inspBadges = [
{
art: '§57a',
fullLabel: '§57a Periodische Prüfung',
tage: vehicle.paragraph57a_tage_bis_faelligkeit,
faelligAm: vehicle.paragraph57a_faellig_am,
},
{
art: 'Wartung',
fullLabel: 'Nächste Wartung / Service',
tage: vehicle.wartung_tage_bis_faelligkeit,
faelligAm: vehicle.naechste_wartung_am,
},
].filter((b) => b.faelligAm !== null);
return (
{isSchaden && (
)}
onClick(vehicle.id)}
sx={{ flexGrow: 1, display: 'flex', flexDirection: 'column', alignItems: 'stretch' }}
>
{vehicle.bild_url ? (
) : (
)}
{vehicle.bezeichnung}
{vehicle.kurzname && (
({vehicle.kurzname})
)}
{vehicle.amtliches_kennzeichen && (
{vehicle.amtliches_kennzeichen}
)}
{vehicle.aktiver_lehrgang && (
}
label="In Lehrgang"
color="info"
size="small"
variant="outlined"
/>
)}
{(status === FahrzeugStatus.AusserDienstWartung || status === FahrzeugStatus.AusserDienstSchaden) &&
vehicle.ausser_dienst_bis && (
Bis ca. {new Date(vehicle.ausser_dienst_bis).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' })}
)}
{inspBadges.length > 0 && (
{inspBadges.map((b) => {
const color = inspBadgeColor(b.tage);
const label = inspBadgeLabel(b.art, b.tage, b.faelligAm);
if (!label) return null;
return (
: undefined}
sx={{ fontSize: '0.7rem' }}
/>
);
})}
)}
{warnings.length > 0 && (
{warnings.slice(0, warnings.length > 3 ? 2 : 3).map((w) => {
const isError =
w.status === AusruestungStatus.Beschaedigt ||
w.status === AusruestungStatus.AusserDienst;
return (
}
label={w.bezeichnung}
color={isError ? 'error' : 'warning'}
sx={{ fontSize: '0.7rem', maxWidth: 160 }}
/>
);
})}
{warnings.length > 3 && (
`${w.bezeichnung}: ${AusruestungStatusLabel[w.status]}`)
.join('\n')}
>
}
label={`+${warnings.length - 2} weitere`}
color={
warnings
.slice(2)
.some(
(w) =>
w.status === AusruestungStatus.Beschaedigt ||
w.status === AusruestungStatus.AusserDienst
)
? 'error'
: 'warning'
}
sx={{ fontSize: '0.7rem' }}
/>
)}
)}
);
};
// ── Fahrzeugtypen-Verwaltung (Einstellungen Tab) ─────────────────────────────
function FahrzeugTypenSettings() {
const queryClient = useQueryClient();
const { showSuccess, showError } = useNotification();
const { data: fahrzeugTypen = [], isLoading } = useQuery({
queryKey: ['fahrzeug-typen'],
queryFn: fahrzeugTypenApi.getAll,
});
const [dialogOpen, setDialogOpen] = useState(false);
const [editing, setEditing] = useState(null);
const [form, setForm] = useState({ name: '', beschreibung: '', icon: '' });
const [deleteError, setDeleteError] = useState(null);
const createMutation = useMutation({
mutationFn: (data: Partial) => fahrzeugTypenApi.create(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fahrzeug-typen'] });
setDialogOpen(false);
showSuccess('Fahrzeugtyp erstellt');
},
onError: () => showError('Fehler beim Erstellen'),
});
const updateMutation = useMutation({
mutationFn: ({ id, data }: { id: number; data: Partial }) =>
fahrzeugTypenApi.update(id, data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fahrzeug-typen'] });
setDialogOpen(false);
showSuccess('Fahrzeugtyp aktualisiert');
},
onError: () => showError('Fehler beim Aktualisieren'),
});
const deleteMutation = useMutation({
mutationFn: (id: number) => fahrzeugTypenApi.delete(id),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['fahrzeug-typen'] });
setDeleteError(null);
showSuccess('Fahrzeugtyp gelöscht');
},
onError: (err: any) => {
const msg = err?.response?.data?.message || 'Fehler beim Löschen — Typ ist möglicherweise noch in Verwendung.';
setDeleteError(msg);
},
});
const openCreate = () => {
setEditing(null);
setForm({ name: '', beschreibung: '', icon: '' });
setDialogOpen(true);
};
const openEdit = (t: FahrzeugTyp) => {
setEditing(t);
setForm({ name: t.name, beschreibung: t.beschreibung ?? '', icon: t.icon ?? '' });
setDialogOpen(true);
};
const handleSubmit = () => {
if (!form.name.trim()) return;
if (editing) {
updateMutation.mutate({ id: editing.id, data: form });
} else {
createMutation.mutate(form);
}
};
const isSaving = createMutation.isPending || updateMutation.isPending;
return (
Fahrzeugtypen
{deleteError && (
setDeleteError(null)}>
{deleteError}
)}
{isLoading ? (
) : (
<>
} onClick={openCreate}>
Neuer Fahrzeugtyp
Name
Beschreibung
Icon
Aktionen
{fahrzeugTypen.length === 0 ? (
Keine Fahrzeugtypen vorhanden
) : (
fahrzeugTypen.map((t) => (
{t.name}
{t.beschreibung ?? '–'}
{t.icon ?? '–'}
openEdit(t)}>
deleteMutation.mutate(t.id)}
>
))
)}
>
)}
);
}
// ── Main Page ─────────────────────────────────────────────────────────────────
function Fahrzeuge() {
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const tab = parseInt(searchParams.get('tab') ?? '0', 10);
const { isAdmin } = usePermissions();
const { hasPermission } = usePermissionContext();
const [vehicles, setVehicles] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const [search, setSearch] = useState('');
const [equipmentWarnings, setEquipmentWarnings] = useState