diff --git a/src/app/(protected)/locations/_components/site-delete-dialog.tsx b/src/app/(protected)/locations/_components/site-delete-dialog.tsx index 78f55716..5b7ba58b 100644 --- a/src/app/(protected)/locations/_components/site-delete-dialog.tsx +++ b/src/app/(protected)/locations/_components/site-delete-dialog.tsx @@ -51,8 +51,9 @@ export function SiteDeleteDialog({ refetch, } = useSiteOpenWorkOrders(siteId, open); const openCount = openWorkOrders?.count ?? 0; - // Without the count the warning cannot be shown, so Delete waits for a successful check. - const countUnknown = isLoading || isError; + // Without a current count the warning cannot be trusted, so Delete waits for a successful check. + // `isFetching` covers reopening: the count cached from the last open is stale until the refetch lands. + const countUnknown = isLoading || isFetching || isError; // The server caps the id list; when it doesn't cover the stated count, open the site's whole board. const openIds = openWorkOrders?.workOrderIds ?? []; const openWorkOrdersHref = @@ -73,7 +74,9 @@ export function SiteDeleteDialog({ > Delete this site? - {isLoading && } + {isFetching && !isError && ( + + )} {isError && {CHECK_FAILED_MESSAGE}} {!countUnknown && ( {describeDeletion(siteCode, openCount)} @@ -88,7 +91,7 @@ export function SiteDeleteDialog({ Try again )} - {openCount > 0 && ( + {!countUnknown && openCount > 0 && ( diff --git a/src/test/app/(protected)/locations/site-delete.test.tsx b/src/test/app/(protected)/locations/site-delete.test.tsx index a5395bb0..10b2c1d1 100644 --- a/src/test/app/(protected)/locations/site-delete.test.tsx +++ b/src/test/app/(protected)/locations/site-delete.test.tsx @@ -229,6 +229,42 @@ describe("Delete a site from the detail panel", () => { expect(deleteSite).not.toHaveBeenCalled(); }); + it("holds Delete on reopen until the fresh open work-order count replaces the cached one", async () => { + let resolveRecheck: (value: { count: number; workOrderIds: number[] }) => void = () => {}; + getOpenWorkOrders.mockResolvedValueOnce({ count: 0, workOrderIds: [] }); + getOpenWorkOrders.mockReturnValueOnce( + new Promise((resolve) => { + resolveRecheck = resolve; + }), + ); + renderAs("Admin"); + const panel = await openPanel(); + + fireEvent.click(within(panel).getByRole("button", { name: "Delete" })); + let dialog = await screen.findByRole("dialog", { name: "Delete this site?" }); + await within(dialog).findByText( + "This will permanently remove DAL1 from Sites. This cannot be undone.", + ); + fireEvent.click(within(dialog).getByRole("button", { name: "Cancel" })); + await waitFor(() => + expect(screen.queryByRole("dialog", { name: "Delete this site?" })).not.toBeInTheDocument(), + ); + + fireEvent.click(within(panel).getByRole("button", { name: "Delete" })); + dialog = await screen.findByRole("dialog", { name: "Delete this site?" }); + await waitFor(() => expect(getOpenWorkOrders).toHaveBeenCalledTimes(2)); + + expect(within(dialog).getByRole("button", { name: "Delete" })).toBeDisabled(); + expect(within(dialog).queryByText(/This cannot be undone/)).not.toBeInTheDocument(); + expect(within(dialog).getByLabelText("Checking open work orders")).toBeInTheDocument(); + + resolveRecheck({ count: 1, workOrderIds: [101] }); + + expect(await within(dialog).findByText(/It still has 1 open work order —/)).toBeInTheDocument(); + expect(within(dialog).getByRole("button", { name: "Delete" })).toBeEnabled(); + expect(deleteSite).not.toHaveBeenCalled(); + }); + it("keeps the site and shows the server's refusal when the API answers 403", async () => { getOpenWorkOrders.mockResolvedValue({ count: 0, workOrderIds: [] }); deleteSite.mockRejectedValue(forbidden());