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 96 97 98 | 1x 1x 1x 1x 1x 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 3x 2x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 1x 1x 1x 1x 5x 5x 5x 5x 5x | /**
* @file StockUpdatesTable.tsx
* @module pages/analytics/blocks/StockUpdatesTable
*
* @summary
* Recent stock updates (auditable changes) for the current filters. Minimal table
* with resilient parsing; shows an empty-state when nothing matches.
*/
import { Card, CardContent, Typography, Skeleton, Box, Table, TableHead, TableRow, TableCell, TableBody } from '@mui/material';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { getStockUpdates, type StockUpdateRow } from '../../../api/analytics/updates';
import { useSettings } from '../../../hooks/useSettings';
import { formatDate, formatNumber } from '../../../utils/formatters';
// Map backend reason codes to friendly labels
const REASON_LABEL: Record<string, string> = {
INITIAL_STOCK: 'Purchase',
INITIAL_STC: 'Purchase', // seen variant
INITIAL_ST: 'Purchase', // extra safety
ADJUSTMENT: 'Adjustment',
SALE: 'Sale',
WRITE_OFF: 'Write-off',
};
const formatReason = (r?: string) => (r ? (REASON_LABEL[r] ?? r) : '—');
export type StockUpdatesTableProps = { from?: string; to?: string; supplierId?: string | null };
export default function StockUpdatesTable({ from, to, supplierId }: StockUpdatesTableProps) {
const { t } = useTranslation(['analytics']);
const { userPreferences } = useSettings();
const q = useQuery<StockUpdateRow[]>({
queryKey: ['analytics', 'stockUpdates', from, to, supplierId ?? null],
queryFn: () => getStockUpdates({ from, to, supplierId: supplierId ?? undefined, limit: 25 }),
});
return (
<Card>
<CardContent>
<Typography variant="subtitle1" sx={{ mb: 1 }}>{t('analytics:updates.title', 'Recent stock updates')}</Typography>
{q.isLoading ? (
<Skeleton variant="rounded" height={220} />
) : (q.data?.length ?? 0) === 0 ? (
<Box sx={{ color: 'text.secondary' }}>{t('analytics:updates.empty', 'No updates in this period.')}</Box>
) : (
<Box sx={{ overflowX: 'auto', pr: 1 }}>
<Table size="small" sx={{ minWidth: 680, tableLayout: 'fixed' }}>
<TableHead>
<TableRow>
<TableCell sx={{ width: 160 }}>{t('analytics:updates.when', 'When')}</TableCell>
<TableCell sx={{ width: 220, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{t('analytics:updates.item', 'Item')}
</TableCell>
<TableCell align="right" sx={{ width: 80 }}>{t('analytics:updates.delta', 'Δ Qty')}</TableCell>
<TableCell sx={{ width: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{t('analytics:updates.reason', 'Reason')}
</TableCell>
<TableCell sx={{ width: 140, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{t('analytics:updates.user', 'User')}
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{q.data!.map((r, idx) => {
const decimals = typeof r.delta === 'number' && !Number.isInteger(r.delta) ? 2 : 0;
const formattedDelta = typeof r.delta === 'number'
? formatNumber(Math.abs(r.delta), userPreferences.numberFormat, decimals)
: '—';
const deltaLabel = typeof r.delta === 'number'
? `${r.delta >= 0 ? '+' : '-'}${formattedDelta}`
: '—';
return (
<TableRow key={idx}>
<TableCell>
{r.timestamp ? formatDate(new Date(r.timestamp), userPreferences.dateFormat) : '—'}
</TableCell>
<TableCell sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{r.itemName}
</TableCell>
<TableCell align="right">{deltaLabel}</TableCell>
<TableCell sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{formatReason(r.reason)}
</TableCell>
<TableCell sx={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
{r.user ?? '—'}
</TableCell>
</TableRow>
);
})}
</TableBody>
</Table>
</Box>
)}
</CardContent>
</Card>
);
} |