widget icon rework, widget grouping rework

This commit is contained in:
Matthias Hochmeister
2026-04-14 10:53:03 +02:00
parent 4fbea8af81
commit 588d8e81db
22 changed files with 245 additions and 327 deletions

View File

@@ -3,16 +3,16 @@ import {
Alert,
AlertTitle,
Box,
Card,
CardContent,
CircularProgress,
Typography,
} from '@mui/material';
import AirIcon from '@mui/icons-material/Air';
import { useNavigate } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';
import { atemschutzApi } from '../../services/atemschutz';
import { useCountUp } from '../../hooks/useCountUp';
import type { AtemschutzStats } from '../../types/atemschutz.types';
import { WidgetCard } from '../templates/WidgetCard';
import { StatSkeleton } from '../templates/SkeletonPresets';
interface AtemschutzDashboardCardProps {
hideWhenEmpty?: boolean;
@@ -30,102 +30,74 @@ const AtemschutzDashboardCard: React.FC<AtemschutzDashboardCardProps> = ({
const animatedReady = useCountUp(stats?.einsatzbereit ?? 0);
const animatedTotal = useCountUp(stats?.total ?? 0);
if (isLoading) {
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>
);
}
const hasConcerns = stats
? stats.untersuchungAbgelaufen > 0 ||
stats.leistungstestAbgelaufen > 0 ||
stats.untersuchungBaldFaellig > 0 ||
stats.leistungstestBaldFaellig > 0
: false;
const allGood = stats ? stats.einsatzbereit === stats.total && !hasConcerns : false;
if (isError) {
return (
<Card>
<CardContent>
<Typography variant="body2" color="error">
Atemschutzstatus konnte nicht geladen werden.
</Typography>
</CardContent>
</Card>
);
}
if (!stats) return null;
const hasConcerns =
stats.untersuchungAbgelaufen > 0 ||
stats.leistungstestAbgelaufen > 0 ||
stats.untersuchungBaldFaellig > 0 ||
stats.leistungstestBaldFaellig > 0;
const allGood = stats.einsatzbereit === stats.total && !hasConcerns;
if (hideWhenEmpty && allGood) return null;
if (!isLoading && !isError && stats && hideWhenEmpty && allGood) return null;
return (
<Card sx={{ height: '100%', cursor: 'pointer' }} onClick={() => navigate('/atemschutz')}>
<CardContent>
<Typography variant="h6" gutterBottom>
Atemschutz
</Typography>
<WidgetCard
title="Atemschutz"
icon={<AirIcon />}
isLoading={isLoading}
isError={isError}
errorMessage="Atemschutzstatus konnte nicht geladen werden."
skeleton={<StatSkeleton />}
onClick={() => navigate('/atemschutz')}
>
<Typography variant="h4" fontWeight={700} color={allGood ? 'success.main' : 'text.primary'}>
{animatedReady}/{animatedTotal}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
einsatzbereit
</Typography>
{/* Main metric */}
<Typography variant="h4" fontWeight={700} color={allGood ? 'success.main' : 'text.primary'}>
{animatedReady}/{animatedTotal}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 1.5 }}>
einsatzbereit
</Typography>
{hasConcerns && stats && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 1 }}>
{(stats.untersuchungAbgelaufen > 0 || stats.leistungstestAbgelaufen > 0) && (
<Alert severity="error" variant="outlined" sx={{ py: 0.5 }}>
<AlertTitle sx={{ fontWeight: 600, mb: 0.5 }}>Abgelaufen</AlertTitle>
{stats.untersuchungAbgelaufen > 0 && (
<Typography variant="body2">
{stats.untersuchungAbgelaufen} Untersuchung{stats.untersuchungAbgelaufen !== 1 ? 'en' : ''} abgelaufen
</Typography>
)}
{stats.leistungstestAbgelaufen > 0 && (
<Typography variant="body2">
{stats.leistungstestAbgelaufen} Leistungstest{stats.leistungstestAbgelaufen !== 1 ? 's' : ''} abgelaufen
</Typography>
)}
</Alert>
)}
{(stats.untersuchungBaldFaellig > 0 || stats.leistungstestBaldFaellig > 0) && (
<Alert severity="warning" variant="outlined" sx={{ py: 0.5 }}>
<AlertTitle sx={{ fontWeight: 600, mb: 0.5 }}>Bald fällig</AlertTitle>
{stats.untersuchungBaldFaellig > 0 && (
<Typography variant="body2">
{stats.untersuchungBaldFaellig} Untersuchung{stats.untersuchungBaldFaellig !== 1 ? 'en' : ''} bald fällig
</Typography>
)}
{stats.leistungstestBaldFaellig > 0 && (
<Typography variant="body2">
{stats.leistungstestBaldFaellig} Leistungstest{stats.leistungstestBaldFaellig !== 1 ? 's' : ''} bald fällig
</Typography>
)}
</Alert>
)}
</Box>
)}
{/* Concerns list — using Alert components for consistent warning styling */}
{hasConcerns && (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1, mt: 1 }}>
{(stats.untersuchungAbgelaufen > 0 || stats.leistungstestAbgelaufen > 0) && (
<Alert severity="error" variant="outlined" sx={{ py: 0.5 }}>
<AlertTitle sx={{ fontWeight: 600, mb: 0.5 }}>Abgelaufen</AlertTitle>
{stats.untersuchungAbgelaufen > 0 && (
<Typography variant="body2">
{stats.untersuchungAbgelaufen} Untersuchung{stats.untersuchungAbgelaufen !== 1 ? 'en' : ''} abgelaufen
</Typography>
)}
{stats.leistungstestAbgelaufen > 0 && (
<Typography variant="body2">
{stats.leistungstestAbgelaufen} Leistungstest{stats.leistungstestAbgelaufen !== 1 ? 's' : ''} abgelaufen
</Typography>
)}
</Alert>
)}
{(stats.untersuchungBaldFaellig > 0 || stats.leistungstestBaldFaellig > 0) && (
<Alert severity="warning" variant="outlined" sx={{ py: 0.5 }}>
<AlertTitle sx={{ fontWeight: 600, mb: 0.5 }}>Bald fällig</AlertTitle>
{stats.untersuchungBaldFaellig > 0 && (
<Typography variant="body2">
{stats.untersuchungBaldFaellig} Untersuchung{stats.untersuchungBaldFaellig !== 1 ? 'en' : ''} bald fällig
</Typography>
)}
{stats.leistungstestBaldFaellig > 0 && (
<Typography variant="body2">
{stats.leistungstestBaldFaellig} Leistungstest{stats.leistungstestBaldFaellig !== 1 ? 's' : ''} bald fällig
</Typography>
)}
</Alert>
)}
</Box>
)}
{/* All good message */}
{allGood && (
<Typography variant="body2" color="success.main">
Alle Atemschutzträger einsatzbereit
</Typography>
)}
</CardContent>
</Card>
{allGood && (
<Typography variant="body2" color="success.main">
Alle Atemschutzträger einsatzbereit
</Typography>
)}
</WidgetCard>
);
};