update
This commit is contained in:
@@ -6,10 +6,9 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Link,
|
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { atemschutzApi } from '../../services/atemschutz';
|
import { atemschutzApi } from '../../services/atemschutz';
|
||||||
import { useCountUp } from '../../hooks/useCountUp';
|
import { useCountUp } from '../../hooks/useCountUp';
|
||||||
@@ -22,6 +21,7 @@ interface AtemschutzDashboardCardProps {
|
|||||||
const AtemschutzDashboardCard: React.FC<AtemschutzDashboardCardProps> = ({
|
const AtemschutzDashboardCard: React.FC<AtemschutzDashboardCardProps> = ({
|
||||||
hideWhenEmpty = false,
|
hideWhenEmpty = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { data: stats, isLoading, isError } = useQuery<AtemschutzStats>({
|
const { data: stats, isLoading, isError } = useQuery<AtemschutzStats>({
|
||||||
queryKey: ['atemschutz-stats'],
|
queryKey: ['atemschutz-stats'],
|
||||||
queryFn: () => atemschutzApi.getStats(),
|
queryFn: () => atemschutzApi.getStats(),
|
||||||
@@ -68,7 +68,7 @@ const AtemschutzDashboardCard: React.FC<AtemschutzDashboardCardProps> = ({
|
|||||||
if (hideWhenEmpty && allGood) return null;
|
if (hideWhenEmpty && allGood) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ height: '100%' }}>
|
<Card sx={{ height: '100%', cursor: 'pointer' }} onClick={() => navigate('/atemschutz')}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
Atemschutz
|
Atemschutz
|
||||||
@@ -124,18 +124,6 @@ const AtemschutzDashboardCard: React.FC<AtemschutzDashboardCardProps> = ({
|
|||||||
Alle Atemschutzträger einsatzbereit
|
Alle Atemschutzträger einsatzbereit
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Link to management page */}
|
|
||||||
<Box sx={{ mt: 2 }}>
|
|
||||||
<Link
|
|
||||||
component={RouterLink}
|
|
||||||
to="/atemschutz"
|
|
||||||
underline="hover"
|
|
||||||
variant="body2"
|
|
||||||
>
|
|
||||||
Zur Verwaltung
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -55,12 +55,15 @@ function AusruestungsanfrageWidget() {
|
|||||||
<Typography variant="body2" color="text.secondary">Keine Anfragen vorhanden.</Typography>
|
<Typography variant="body2" color="text.secondary">Keine Anfragen vorhanden.</Typography>
|
||||||
) : (
|
) : (
|
||||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
<Chip label={`${overview.pending_count} Offen`} size="small" color={overview.pending_count > 0 ? 'warning' : 'default'} variant="outlined" />
|
|
||||||
<Chip label={`${overview.approved_count} Genehmigt`} size="small" color={overview.approved_count > 0 ? 'info' : 'default'} variant="outlined" />
|
|
||||||
{overview.unhandled_count > 0 && (
|
{overview.unhandled_count > 0 && (
|
||||||
<Chip label={`${overview.unhandled_count} Neu`} size="small" color="error" variant="outlined" />
|
<Chip label={`${overview.unhandled_count} Neu`} size="small" color="error" variant="outlined" />
|
||||||
)}
|
)}
|
||||||
<Chip label={`${overview.total_count} Gesamt`} size="small" variant="outlined" />
|
{overview.pending_count > 0 && (
|
||||||
|
<Chip label={`${overview.pending_count} Offen`} size="small" color="warning" variant="outlined" />
|
||||||
|
)}
|
||||||
|
{overview.approved_count > 0 && (
|
||||||
|
<Chip label={`${overview.approved_count} Genehmigt`} size="small" color="info" variant="outlined" />
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Card, CardContent, Typography, Box, Chip, List, ListItem, ListItemText, Divider, Skeleton } from '@mui/material';
|
import { Card, CardContent, Typography, Box, Chip, Skeleton } from '@mui/material';
|
||||||
import { LocalShipping } from '@mui/icons-material';
|
import { LocalShipping } from '@mui/icons-material';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
@@ -20,13 +20,18 @@ function BestellungenWidget() {
|
|||||||
(o) => !['abgeschlossen'].includes(o.status)
|
(o) => !['abgeschlossen'].includes(o.status)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Group open orders by status, keep only non-zero counts
|
||||||
|
const statusCounts = (Object.keys(BESTELLUNG_STATUS_LABELS) as BestellungStatus[])
|
||||||
|
.filter((s) => s !== 'abgeschlossen')
|
||||||
|
.map((s) => ({ status: s, count: openOrders.filter((o) => o.status === s).length }))
|
||||||
|
.filter((s) => s.count > 0);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom>Bestellungen</Typography>
|
<Typography variant="h6" gutterBottom>Bestellungen</Typography>
|
||||||
<Skeleton variant="rectangular" height={60} />
|
<Skeleton variant="rectangular" height={40} />
|
||||||
<Skeleton variant="rectangular" height={60} sx={{ mt: 1 }} />
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
@@ -45,61 +50,27 @@ function BestellungenWidget() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (openOrders.length === 0) {
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card sx={{ cursor: 'pointer', '&:hover': { bgcolor: 'action.hover' } }} onClick={() => navigate('/bestellungen')}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom>Bestellungen</Typography>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1.5 }}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, color: 'text.secondary' }}>
|
|
||||||
<LocalShipping fontSize="small" />
|
|
||||||
<Typography variant="body2">Keine offenen Bestellungen</Typography>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<CardContent>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
|
||||||
<Typography variant="h6">Bestellungen</Typography>
|
<Typography variant="h6">Bestellungen</Typography>
|
||||||
<Chip label={`${openOrders.length} offen`} size="small" color="primary" />
|
<LocalShipping fontSize="small" color="action" />
|
||||||
</Box>
|
</Box>
|
||||||
<List dense disablePadding>
|
{statusCounts.length === 0 ? (
|
||||||
{openOrders.slice(0, 5).map((order, idx) => (
|
<Typography variant="body2" color="text.secondary">Keine offenen Bestellungen</Typography>
|
||||||
<Box key={order.id}>
|
) : (
|
||||||
{idx > 0 && <Divider />}
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||||
<ListItem
|
{statusCounts.map(({ status, count }) => (
|
||||||
disablePadding
|
|
||||||
sx={{ cursor: 'pointer', py: 0.5, '&:hover': { bgcolor: 'action.hover' } }}
|
|
||||||
onClick={() => navigate(`/bestellungen/${order.id}`)}
|
|
||||||
>
|
|
||||||
<ListItemText
|
|
||||||
primary={order.bezeichnung}
|
|
||||||
secondary={order.lieferant_name || 'Kein Lieferant'}
|
|
||||||
primaryTypographyProps={{ variant: 'body2', noWrap: true }}
|
|
||||||
secondaryTypographyProps={{ variant: 'caption' }}
|
|
||||||
/>
|
|
||||||
<Chip
|
<Chip
|
||||||
label={BESTELLUNG_STATUS_LABELS[order.status as BestellungStatus]}
|
key={status}
|
||||||
color={BESTELLUNG_STATUS_COLORS[order.status as BestellungStatus]}
|
label={`${count} ${BESTELLUNG_STATUS_LABELS[status]}`}
|
||||||
|
color={BESTELLUNG_STATUS_COLORS[status]}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ ml: 1 }}
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
</ListItem>
|
|
||||||
</Box>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</Box>
|
||||||
{openOrders.length > 5 && (
|
|
||||||
<Typography
|
|
||||||
variant="caption"
|
|
||||||
color="primary"
|
|
||||||
sx={{ cursor: 'pointer', mt: 1, display: 'block' }}
|
|
||||||
onClick={() => navigate('/bestellungen')}
|
|
||||||
>
|
|
||||||
Alle {openOrders.length} Bestellungen anzeigen
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Link,
|
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { equipmentApi } from '../../services/equipment';
|
import { equipmentApi } from '../../services/equipment';
|
||||||
import { useCountUp } from '../../hooks/useCountUp';
|
import { useCountUp } from '../../hooks/useCountUp';
|
||||||
@@ -22,6 +21,7 @@ interface EquipmentDashboardCardProps {
|
|||||||
const EquipmentDashboardCard: React.FC<EquipmentDashboardCardProps> = ({
|
const EquipmentDashboardCard: React.FC<EquipmentDashboardCardProps> = ({
|
||||||
hideWhenEmpty = false,
|
hideWhenEmpty = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { data: stats, isLoading, isError } = useQuery<EquipmentStats>({
|
const { data: stats, isLoading, isError } = useQuery<EquipmentStats>({
|
||||||
queryKey: ['equipment-stats'],
|
queryKey: ['equipment-stats'],
|
||||||
queryFn: () => equipmentApi.getStats(),
|
queryFn: () => equipmentApi.getStats(),
|
||||||
@@ -68,7 +68,7 @@ const EquipmentDashboardCard: React.FC<EquipmentDashboardCardProps> = ({
|
|||||||
if (hideWhenEmpty && allGood) return null;
|
if (hideWhenEmpty && allGood) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ height: '100%' }}>
|
<Card sx={{ height: '100%', cursor: 'pointer' }} onClick={() => navigate('/ausruestung')}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
Ausrüstung
|
Ausrüstung
|
||||||
@@ -134,18 +134,6 @@ const EquipmentDashboardCard: React.FC<EquipmentDashboardCardProps> = ({
|
|||||||
Alle Ausrüstung einsatzbereit
|
Alle Ausrüstung einsatzbereit
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Link to management page */}
|
|
||||||
<Box sx={{ mt: 2 }}>
|
|
||||||
<Link
|
|
||||||
component={RouterLink}
|
|
||||||
to="/ausruestung"
|
|
||||||
underline="hover"
|
|
||||||
variant="body2"
|
|
||||||
>
|
|
||||||
Zur Verwaltung
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,10 +6,9 @@ import {
|
|||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
CircularProgress,
|
CircularProgress,
|
||||||
Link,
|
|
||||||
Typography,
|
Typography,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useQuery } from '@tanstack/react-query';
|
||||||
import { vehiclesApi } from '../../services/vehicles';
|
import { vehiclesApi } from '../../services/vehicles';
|
||||||
import { equipmentApi } from '../../services/equipment';
|
import { equipmentApi } from '../../services/equipment';
|
||||||
@@ -24,6 +23,7 @@ interface VehicleDashboardCardProps {
|
|||||||
const VehicleDashboardCard: React.FC<VehicleDashboardCardProps> = ({
|
const VehicleDashboardCard: React.FC<VehicleDashboardCardProps> = ({
|
||||||
hideWhenEmpty = false,
|
hideWhenEmpty = false,
|
||||||
}) => {
|
}) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const { data: stats, isLoading: statsLoading, isError: statsError } = useQuery<VehicleStats>({
|
const { data: stats, isLoading: statsLoading, isError: statsError } = useQuery<VehicleStats>({
|
||||||
queryKey: ['vehicle-stats'],
|
queryKey: ['vehicle-stats'],
|
||||||
queryFn: () => vehiclesApi.getStats(),
|
queryFn: () => vehiclesApi.getStats(),
|
||||||
@@ -85,7 +85,7 @@ const VehicleDashboardCard: React.FC<VehicleDashboardCardProps> = ({
|
|||||||
if (hideWhenEmpty && allGood) return null;
|
if (hideWhenEmpty && allGood) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ height: '100%' }}>
|
<Card sx={{ height: '100%', cursor: 'pointer' }} onClick={() => navigate('/fahrzeuge')}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
Fahrzeuge
|
Fahrzeuge
|
||||||
@@ -143,18 +143,6 @@ const VehicleDashboardCard: React.FC<VehicleDashboardCardProps> = ({
|
|||||||
Alle Fahrzeuge einsatzbereit
|
Alle Fahrzeuge einsatzbereit
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Link to management page */}
|
|
||||||
<Box sx={{ mt: 2 }}>
|
|
||||||
<Link
|
|
||||||
component={RouterLink}
|
|
||||||
to="/fahrzeuge"
|
|
||||||
underline="hover"
|
|
||||||
variant="body2"
|
|
||||||
>
|
|
||||||
Zur Verwaltung
|
|
||||||
</Link>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user