shoc-frontend-new/src/app/(protected)/dashboard/_components/dispatcher-workload-card.tsx
Codex Review Integration 0279cfac64 feat(dashboard): paginate dispatcher tables and fix Aveta drilldown
Workload and Performance page server-side at 10 rows and now return Page,
PageSize, and TotalDispatchers on dev (backend#126/#127), so wire the pager
that SH-346 and SH-347 require: getWorkload/getPerformance send a 1-based
`page` and parse the paging envelope, the hooks and query keys carry the page,
and each dispatcher table renders a 10-row pager. Page state is per-table and
resets on a range change so switching range never lands on an out-of-range
empty page that reads as a false "no dispatchers" state.

Drop `docs=No` from the Aveta Pending drilldown. `docs` filters on
completion-document status, not the Aveta clearance document the tile counts
(SH-294 / backend#138), so it narrowed to the wrong rows. The board has no
missing-Aveta-document filter, so the drilldown is now the Aveta-required work
in the two-day open window — a deliberate superset of the count.
2026-09-17 00:36:55 -03:00

107 lines
3.1 KiB
TypeScript

import { useNavigate } from "react-router";
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
import { Text } from "@/components/ui/text";
import type { DashboardRangeKey } from "@/domain/dashboard/types/dashboard-range";
import type { DispatcherWorkloadRow } from "@/domain/dashboard/types/dashboard-workload";
import {
workOrderDispatcherDrilldownSearch,
workOrderDrilldownUrl,
} from "@/domain/dashboard/utils/work-order-drilldown-links";
import { DashboardCard } from "./dashboard-card";
import { DispatcherTablePager } from "./dispatcher-table-pager";
const EMPTY_MESSAGE = "No dispatchers are set up yet.";
interface DispatcherWorkloadCardProps {
rows: DispatcherWorkloadRow[];
range: DashboardRangeKey;
page: number;
pageSize: number;
totalDispatchers: number;
onPageChange: (page: number) => void;
isLoading?: boolean;
error?: unknown;
}
function DispatcherNameCell({
row,
range,
}: {
row: DispatcherWorkloadRow;
range: DashboardRangeKey;
}) {
const navigate = useNavigate();
const drillable = Boolean(row.dispatcherId) && row.totalWorkOrders > 0;
if (!drillable) {
return <Text variant="description">{row.dispatcherName}</Text>;
}
return (
<Text
variant="description"
as="button"
className="cursor-pointer text-left font-medium underline-offset-2 hover:underline"
onClick={() =>
navigate(workOrderDrilldownUrl(workOrderDispatcherDrilldownSearch(row.dispatcherId, range)))
}
>
{row.dispatcherName}
</Text>
);
}
export function DispatcherWorkloadCard({
rows,
range,
page,
pageSize,
totalDispatchers,
onPageChange,
isLoading = false,
error,
}: DispatcherWorkloadCardProps) {
return (
<DashboardCard
title="Dispatcher Workload"
isLoading={isLoading}
error={error}
isEmpty={!isLoading && !error && rows.length === 0}
emptyMessage={EMPTY_MESSAGE}
>
<TableContainer>
<Table size="small" aria-label="Dispatcher Workload">
<TableHead>
<TableRow>
<TableCell className="font-semibold">Dispatcher</TableCell>
<TableCell align="right" className="font-semibold">
Open
</TableCell>
<TableCell align="right" className="font-semibold">
Total
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.dispatcherId || row.dispatcherName}>
<TableCell>
<DispatcherNameCell row={row} range={range} />
</TableCell>
<TableCell align="right">{row.openWorkOrders.toLocaleString()}</TableCell>
<TableCell align="right">{row.totalWorkOrders.toLocaleString()}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</TableContainer>
<DispatcherTablePager
page={page}
pageSize={pageSize}
totalDispatchers={totalDispatchers}
onPageChange={onPageChange}
label="Dispatcher Workload pagination"
/>
</DashboardCard>
);
}