Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 3x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 5x 5x 5x 5x 5x | /**
* @file StockPerSupplierDonut.tsx
* @module pages/analytics/blocks/StockPerSupplierDonut
*
* @summary
* Donut (pie) view of stock share per supplier (quantity-based).
* Uses /api/analytics/stock-per-supplier which returns totals by supplier.
*
* @enterprise
* - Purely presentational alternative to the bar version.
* - Gracefully handles empty datasets and long supplier names (legend).
*/
import * as React from 'react';
import { Card, CardContent, Typography, Skeleton, Box } from '@mui/material';
import { useTheme as useMuiTheme } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
import { useQuery } from '@tanstack/react-query';
import { ResponsiveContainer, PieChart, Pie, Tooltip, Legend, Cell } from 'recharts';
import { getStockPerSupplier, type StockPerSupplierPoint } from '../../../api/analytics';
import { useSettings } from '../../../hooks/useSettings';
import { formatNumber } from '../../../utils/formatters';
export default function StockPerSupplierDonut() {
const { t } = useTranslation(['analytics']);
const muiTheme = useMuiTheme();
const { userPreferences } = useSettings();
const q = useQuery<StockPerSupplierPoint[]>({
queryKey: ['analytics', 'stockPerSupplierDonut'],
queryFn: getStockPerSupplier,
staleTime: 60_000,
});
const data = React.useMemo(
() => (q.data ?? []).map(d => ({ name: d.supplierName, value: d.totalQuantity })),
[q.data]
);
const colors = [
muiTheme.palette.primary.main,
muiTheme.palette.success.main,
muiTheme.palette.info.main,
muiTheme.palette.warning.main,
muiTheme.palette.error.main,
muiTheme.palette.secondary.main,
];
return (
<Card>
<CardContent>
<Typography variant="subtitle1" sx={{ mb: 1 }}>
{t('analytics:stockPerSupplier.title')}
</Typography>
{q.isLoading ? (
<Skeleton variant="rounded" height={220} />
) : data.length === 0 ? (
<Box sx={{ height: 220, display: 'grid', placeItems: 'center', color: 'text.secondary' }}>
{t('analytics:stockPerSupplier.empty', 'No supplier data for the current filters.')}
</Box>
) : (
<Box sx={{ height: 260 }}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
dataKey="value"
nameKey="name"
innerRadius="55%"
outerRadius="80%"
paddingAngle={1}
isAnimationActive={false}
>
{data.map((_, i) => (
<Cell key={`seg-${i}`} fill={colors[i % colors.length]} />
))}
</Pie>
<Tooltip
formatter={(value: number | string) =>
typeof value === 'number'
? formatNumber(value, userPreferences.numberFormat, 0)
: value
}
/>
<Legend />
</PieChart>
</ResponsiveContainer>
</Box>
)}
</CardContent>
</Card>
);
}
|