mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 15:09:02 +00:00
fix(work-orders): distinguish loading from empty in Unassigned pin (SH-165)
The pinned Unassigned section folded the queue query's pending state into isFetching only, which the section never read. On first mount, once GET /board resolved while the queue was still paging GET /board/search, the pin announced "No unassigned work orders." instead of a loading state. Expose the queue query's own isLoading as unassignedLoading, thread it through the table data hook, and render an aria-busy loading row while it is true so the loading and empty states are separately observable.
This commit is contained in:
parent
dc9e146080
commit
ac9ce34101
4 changed files with 60 additions and 1 deletions
|
|
@ -23,8 +23,19 @@ export function WoTableBody({ tableData, renderRow, renderInlineRow }: WoTableBo
|
||||||
const unassignedRows = tableData.unassigned ?? [];
|
const unassignedRows = tableData.unassigned ?? [];
|
||||||
const showUnscheduled =
|
const showUnscheduled =
|
||||||
(tableData.unscheduled?.length ?? 0) > 0 || tableData.draftDayKey === UNSCHEDULED_DAY_KEY;
|
(tableData.unscheduled?.length ?? 0) > 0 || tableData.draftDayKey === UNSCHEDULED_DAY_KEY;
|
||||||
|
// SH-165: "still loading" and "zero unassigned" are distinct states. Without
|
||||||
|
// this split the pin announced "No unassigned work orders." while the queue
|
||||||
|
// was still paging board/search on first mount.
|
||||||
|
const showUnassignedLoading =
|
||||||
|
tableData.unassignedOpen &&
|
||||||
|
unassignedRows.length === 0 &&
|
||||||
|
!tableData.unassignedError &&
|
||||||
|
tableData.unassignedLoading;
|
||||||
const showUnassignedEmpty =
|
const showUnassignedEmpty =
|
||||||
tableData.unassignedOpen && unassignedRows.length === 0 && !tableData.unassignedError;
|
tableData.unassignedOpen &&
|
||||||
|
unassignedRows.length === 0 &&
|
||||||
|
!tableData.unassignedError &&
|
||||||
|
!tableData.unassignedLoading;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<tbody>
|
<tbody>
|
||||||
|
|
@ -47,6 +58,15 @@ export function WoTableBody({ tableData, renderRow, renderInlineRow }: WoTableBo
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
{showUnassignedLoading && (
|
||||||
|
<tr aria-busy="true">
|
||||||
|
<td colSpan={WO_TABLE_COLS.length} style={{ padding: "8px 14px" }}>
|
||||||
|
<Text variant="feedback" tone="muted">
|
||||||
|
Loading unassigned work orders…
|
||||||
|
</Text>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)}
|
||||||
{showUnassignedEmpty && (
|
{showUnassignedEmpty && (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={WO_TABLE_COLS.length} style={{ padding: "8px 14px" }}>
|
<td colSpan={WO_TABLE_COLS.length} style={{ padding: "8px 14px" }}>
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ export type WorkOrdersBoardSource = {
|
||||||
data: WorkOrderBoardListResult | undefined;
|
data: WorkOrderBoardListResult | undefined;
|
||||||
unassignedQueue: WorkOrderTableRow[];
|
unassignedQueue: WorkOrderTableRow[];
|
||||||
unassignedError: Error | null;
|
unassignedError: Error | null;
|
||||||
|
unassignedLoading: boolean;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
isFetching: boolean;
|
isFetching: boolean;
|
||||||
error: Error | null;
|
error: Error | null;
|
||||||
|
|
@ -75,6 +76,7 @@ export function useWorkOrdersBoardSource(filters: FiltersReturn): WorkOrdersBoar
|
||||||
} = useWorkOrdersBoardSearch(resolvedSearchParams ?? { datePreset: "ThisWeek" }, advActive);
|
} = useWorkOrdersBoardSearch(resolvedSearchParams ?? { datePreset: "ThisWeek" }, advActive);
|
||||||
const {
|
const {
|
||||||
data: queueData,
|
data: queueData,
|
||||||
|
isLoading: queueLoading,
|
||||||
isFetching: queueFetching,
|
isFetching: queueFetching,
|
||||||
error: queueError,
|
error: queueError,
|
||||||
refetch: refetchQueue,
|
refetch: refetchQueue,
|
||||||
|
|
@ -94,6 +96,10 @@ export function useWorkOrdersBoardSource(filters: FiltersReturn): WorkOrdersBoar
|
||||||
data: advActive ? searchData : boardData,
|
data: advActive ? searchData : boardData,
|
||||||
unassignedQueue: advActive ? EMPTY_UNASSIGNED_QUEUE : (queueData ?? EMPTY_UNASSIGNED_QUEUE),
|
unassignedQueue: advActive ? EMPTY_UNASSIGNED_QUEUE : (queueData ?? EMPTY_UNASSIGNED_QUEUE),
|
||||||
unassignedError: advActive ? null : (queueError ?? null),
|
unassignedError: advActive ? null : (queueError ?? null),
|
||||||
|
// SH-165: the pinned queue has its own first-load state. Folding it into
|
||||||
|
// isFetching only made "still paging board/search" indistinguishable from
|
||||||
|
// "zero unassigned", so the pin announced an empty queue while loading.
|
||||||
|
unassignedLoading: advActive ? false : queueLoading,
|
||||||
isLoading: advActive ? searchLoading : boardLoading,
|
isLoading: advActive ? searchLoading : boardLoading,
|
||||||
isFetching: advActive ? searchFetching : boardFetching || queueFetching,
|
isFetching: advActive ? searchFetching : boardFetching || queueFetching,
|
||||||
error: advActive ? searchError : boardError,
|
error: advActive ? searchError : boardError,
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export function useWorkOrdersTableData(filters: FiltersReturn) {
|
||||||
data,
|
data,
|
||||||
unassignedQueue,
|
unassignedQueue,
|
||||||
unassignedError,
|
unassignedError,
|
||||||
|
unassignedLoading,
|
||||||
isLoading,
|
isLoading,
|
||||||
isFetching,
|
isFetching,
|
||||||
error,
|
error,
|
||||||
|
|
@ -107,6 +108,7 @@ export function useWorkOrdersTableData(filters: FiltersReturn) {
|
||||||
advResults,
|
advResults,
|
||||||
unassigned,
|
unassigned,
|
||||||
unassignedError,
|
unassignedError,
|
||||||
|
unassignedLoading,
|
||||||
unscheduled,
|
unscheduled,
|
||||||
displayGroups,
|
displayGroups,
|
||||||
searchActive,
|
searchActive,
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ function baseTableData(
|
||||||
advResults: [],
|
advResults: [],
|
||||||
unassigned: [],
|
unassigned: [],
|
||||||
unassignedError: null,
|
unassignedError: null,
|
||||||
|
unassignedLoading: false,
|
||||||
unscheduled: [],
|
unscheduled: [],
|
||||||
displayGroups: [],
|
displayGroups: [],
|
||||||
searchActive: false,
|
searchActive: false,
|
||||||
|
|
@ -308,6 +309,36 @@ describe("WoTableBody Unassigned queue (SH-165)", () => {
|
||||||
expect(screen.queryByText("WED · 07/15")).not.toBeInTheDocument();
|
expect(screen.queryByText("WED · 07/15")).not.toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("distinguishes a still-loading queue from an empty one", () => {
|
||||||
|
render(
|
||||||
|
<table>
|
||||||
|
<WoTableBody
|
||||||
|
tableData={baseTableData({ unassignedLoading: true })}
|
||||||
|
renderRow={() => null}
|
||||||
|
renderInlineRow={() => null}
|
||||||
|
/>
|
||||||
|
</table>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText(/Loading unassigned work orders/)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText("No unassigned work orders.")).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("announces an empty queue only once loading has settled", () => {
|
||||||
|
render(
|
||||||
|
<table>
|
||||||
|
<WoTableBody
|
||||||
|
tableData={baseTableData({ unassignedLoading: false })}
|
||||||
|
renderRow={() => null}
|
||||||
|
renderInlineRow={() => null}
|
||||||
|
/>
|
||||||
|
</table>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText("No unassigned work orders.")).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/Loading unassigned work orders/)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps the Unassigned pin visible when the queue query fails", () => {
|
it("keeps the Unassigned pin visible when the queue query fails", () => {
|
||||||
render(
|
render(
|
||||||
<table>
|
<table>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue