Files
dashboard/frontend/src/components/atemschutz/AtemschutzDashboardCard.tsx
Matthias Hochmeister 5b8f40ab9a add now features
2026-03-01 14:41:45 +01:00

149 lines
4.4 KiB
TypeScript

import React, { useEffect, useState } from 'react';
import {
Box,
Card,
CardContent,
CircularProgress,
Link,
Typography,
} from '@mui/material';
import { Link as RouterLink } from 'react-router-dom';
import { atemschutzApi } from '../../services/atemschutz';
import type { AtemschutzStats } from '../../types/atemschutz.types';
interface AtemschutzDashboardCardProps {
hideWhenEmpty?: boolean;
}
const AtemschutzDashboardCard: React.FC<AtemschutzDashboardCardProps> = ({
hideWhenEmpty = false,
}) => {
const [stats, setStats] = useState<AtemschutzStats | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
let mounted = true;
const fetchStats = async () => {
try {
setLoading(true);
setError(null);
const data = await atemschutzApi.getStats();
if (mounted) setStats(data);
} catch {
if (mounted) setError('Atemschutzstatus konnte nicht geladen werden.');
} finally {
if (mounted) setLoading(false);
}
};
fetchStats();
return () => {
mounted = false;
};
}, []);
if (loading) {
return (
<Card>
<CardContent sx={{ display: 'flex', alignItems: 'center', gap: 1, py: 2 }}>
<CircularProgress size={16} />
<Typography variant="body2" color="text.secondary">
Atemschutzstatus wird geladen...
</Typography>
</CardContent>
</Card>
);
}
if (error) {
return (
<Card>
<CardContent>
<Typography variant="body2" color="error">
{error}
</Typography>
</CardContent>
</Card>
);
}
if (!stats) return null;
// Determine if there are any concerns
const hasConcerns =
stats.untersuchungAbgelaufen > 0 ||
stats.leistungstestAbgelaufen > 0 ||
stats.untersuchungBaldFaellig > 0 ||
stats.leistungstestBaldFaellig > 0;
const allGood = stats.einsatzbereit === stats.total && !hasConcerns;
// If hideWhenEmpty and everything is fine, render nothing
if (hideWhenEmpty && allGood) return null;
return (
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Atemschutz
</Typography>
{/* Main metric */}
<Typography variant="h4" fontWeight={700} color={allGood ? 'success.main' : 'text.primary'}>
{stats.einsatzbereit}/{stats.total}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
einsatzbereit
</Typography>
{/* Concerns list */}
{hasConcerns && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
{stats.untersuchungAbgelaufen > 0 && (
<Typography variant="body2" color="error.main">
{stats.untersuchungAbgelaufen} Untersuchung{stats.untersuchungAbgelaufen !== 1 ? 'en' : ''} abgelaufen
</Typography>
)}
{stats.leistungstestAbgelaufen > 0 && (
<Typography variant="body2" color="error.main">
{stats.leistungstestAbgelaufen} Leistungstest{stats.leistungstestAbgelaufen !== 1 ? 's' : ''} abgelaufen
</Typography>
)}
{stats.untersuchungBaldFaellig > 0 && (
<Typography variant="body2" color="warning.main">
{stats.untersuchungBaldFaellig} Untersuchung{stats.untersuchungBaldFaellig !== 1 ? 'en' : ''} bald fällig
</Typography>
)}
{stats.leistungstestBaldFaellig > 0 && (
<Typography variant="body2" color="warning.main">
{stats.leistungstestBaldFaellig} Leistungstest{stats.leistungstestBaldFaellig !== 1 ? 's' : ''} bald fällig
</Typography>
)}
</Box>
)}
{/* All good message */}
{allGood && (
<Typography variant="body2" color="success.main">
Alle Atemschutzträger einsatzbereit
</Typography>
)}
{/* Link to management page */}
<Box sx={{ mt: 2 }}>
<Link
component={RouterLink}
to="/atemschutz"
underline="hover"
variant="body2"
>
Zur Verwaltung
</Link>
</Box>
</CardContent>
</Card>
);
};
export default AtemschutzDashboardCard;