import { useQuery } from '@tanstack/react-query'; import { useTranslation } from 'react-i18next'; import { Loader2 } from 'lucide-react'; import { api } from '../api/client'; // Consumption, cost and stock grouped by the internal material number // (#2870) — the identifier the business actually purchases and costs by, // unlike brand+material+colour. Data comes from the dedicated aggregate // endpoint so archived spools' recorded usage still counts. interface MaterialNumberStatsProps { currency: string; // Dashboard timeframe. Narrows the consumption/cost columns only — the // spool count and remaining weight are point-in-time stock. dateFrom?: string; dateTo?: string; } function formatGrams(g: number): string { if (Math.abs(g) >= 1000) return `${(g / 1000).toFixed(2)} kg`; return `${Math.round(g)} g`; } export function MaterialNumberStats({ currency, dateFrom, dateTo }: MaterialNumberStatsProps) { const { t } = useTranslation(); const { data, isLoading, isError } = useQuery({ queryKey: ['material-number-stats', dateFrom ?? null, dateTo ?? null], queryFn: () => api.getMaterialNumberStats({ dateFrom, dateTo }), }); if (isLoading) { return (
); } // A failed request is not an empty inventory: telling someone who has // numbered their spools to go and number them (because of a 403 from a // missing INVENTORY_READ, or a dropped connection) sends them looking for // a problem that isn't there. if (isError) { return

{t('stats.materialNumbers.loadFailed')}

; } if (!data || data.length === 0) { return

{t('stats.materialNumbers.empty')}

; } return (
{data.map((row) => ( ))}
{t('inventory.materialNumber')} {t('stats.materialNumbers.spools')} {t('stats.materialNumbers.remaining')} {t('stats.materialNumbers.consumed')} {t('stats.materialNumbers.cost')}
{row.material_number} {row.spool_count} {formatGrams(row.remaining_g)} {formatGrams(row.consumed_g)} {currency} {row.cost.toFixed(2)}
); }