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());