refine vehicle freatures

This commit is contained in:
Matthias Hochmeister
2026-02-28 17:19:18 +01:00
parent 0e81eabda6
commit e2be29c712
17 changed files with 4071 additions and 117 deletions

View File

@@ -31,6 +31,9 @@ import {
import { useNavigate } from 'react-router-dom';
import DashboardLayout from '../components/dashboard/DashboardLayout';
import { vehiclesApi } from '../services/vehicles';
import { equipmentApi } from '../services/equipment';
import type { VehicleEquipmentWarning } from '../types/equipment.types';
import { AusruestungStatus, AusruestungStatusLabel } from '../types/equipment.types';
import {
FahrzeugListItem,
FahrzeugStatus,
@@ -86,9 +89,10 @@ function inspTooltipTitle(fullLabel: string, tage: number | null, faelligAm: str
interface VehicleCardProps {
vehicle: FahrzeugListItem;
onClick: (id: string) => void;
warnings?: VehicleEquipmentWarning[];
}
const VehicleCard: React.FC<VehicleCardProps> = ({ vehicle, onClick }) => {
const VehicleCard: React.FC<VehicleCardProps> = ({ vehicle, onClick, warnings = [] }) => {
const status = vehicle.status as FahrzeugStatus;
const statusCfg = STATUS_CONFIG[status] ?? STATUS_CONFIG[FahrzeugStatus.Einsatzbereit];
const isSchaden = status === FahrzeugStatus.AusserDienstSchaden;
@@ -183,13 +187,6 @@ const VehicleCard: React.FC<VehicleCardProps> = ({ vehicle, onClick }) => {
/>
</Box>
{vehicle.besatzung_soll && (
<Typography variant="body2" color="text.secondary" sx={{ mb: 1 }}>
Besatzung: {vehicle.besatzung_soll}
{vehicle.baujahr && ` · Bj. ${vehicle.baujahr}`}
</Typography>
)}
{inspBadges.length > 0 && (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
{inspBadges.map((b) => {
@@ -214,6 +211,18 @@ const VehicleCard: React.FC<VehicleCardProps> = ({ vehicle, onClick }) => {
})}
</Box>
)}
{warnings.length > 0 && (
<Tooltip title={warnings.map(w => `${w.bezeichnung}: ${AusruestungStatusLabel[w.status]}`).join('\n')}>
<Chip
size="small"
icon={<Warning />}
label={`${warnings.length} Ausrüstung nicht bereit`}
color={warnings.some(w => w.status === AusruestungStatus.Beschaedigt) ? 'error' : 'warning'}
sx={{ mt: 0.5 }}
/>
</Tooltip>
)}
</CardContent>
</CardActionArea>
</Card>
@@ -229,6 +238,7 @@ function Fahrzeuge() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [search, setSearch] = useState('');
const [equipmentWarnings, setEquipmentWarnings] = useState<Map<string, VehicleEquipmentWarning[]>>(new Map());
const fetchVehicles = useCallback(async () => {
try {
@@ -245,6 +255,26 @@ function Fahrzeuge() {
useEffect(() => { fetchVehicles(); }, [fetchVehicles]);
// Fetch equipment warnings separately — must not block or delay vehicle list rendering
useEffect(() => {
async function fetchWarnings() {
try {
const warnings = await equipmentApi.getVehicleWarnings();
const warningsMap = new Map<string, VehicleEquipmentWarning[]>();
warnings.forEach(w => {
const existing = warningsMap.get(w.fahrzeug_id) || [];
existing.push(w);
warningsMap.set(w.fahrzeug_id, existing);
});
setEquipmentWarnings(warningsMap);
} catch {
// Silently fail — equipment warnings are non-critical
setEquipmentWarnings(new Map());
}
}
fetchWarnings();
}, []);
const filtered = vehicles.filter((v) => {
if (!search.trim()) return true;
const q = search.toLowerCase();
@@ -337,6 +367,7 @@ function Fahrzeuge() {
<VehicleCard
vehicle={vehicle}
onClick={(id) => navigate(`/fahrzeuge/${id}`)}
warnings={equipmentWarnings.get(vehicle.id) || []}
/>
</Grid>
))}