mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
feat(work-orders): confirm dialog when duplicate wo number detected
This commit is contained in:
parent
6ac08c5851
commit
287517b08f
6 changed files with 197 additions and 17 deletions
|
|
@ -0,0 +1,45 @@
|
|||
import { Button, Dialog, DialogActions, DialogContent, DialogTitle } from "@mui/material";
|
||||
import { Text } from "@/components/ui/text";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
|
||||
type DuplicateWoNumberDialogProps = {
|
||||
open: boolean;
|
||||
existing: WorkOrderTableRow | null;
|
||||
onDismiss: () => void;
|
||||
onViewExisting: (row: WorkOrderTableRow) => void;
|
||||
};
|
||||
|
||||
export function DuplicateWoNumberDialog({
|
||||
open,
|
||||
existing,
|
||||
onDismiss,
|
||||
onViewExisting,
|
||||
}: DuplicateWoNumberDialogProps) {
|
||||
const woNumber = existing?.woNumber ?? "";
|
||||
|
||||
return (
|
||||
<Dialog open={open} onClose={onDismiss} aria-labelledby="duplicate-wo-number-dialog-title">
|
||||
<DialogTitle id="duplicate-wo-number-dialog-title">Duplicate Work Order Number</DialogTitle>
|
||||
<DialogContent>
|
||||
<Text variant="body">
|
||||
Work order number {woNumber} is already assigned to another work order. View the existing
|
||||
record or dismiss to keep editing.
|
||||
</Text>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={onDismiss}>Dismiss</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
disabled={existing == null}
|
||||
onClick={() => {
|
||||
if (existing != null) {
|
||||
onViewExisting(existing);
|
||||
}
|
||||
}}
|
||||
>
|
||||
View existing
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ type UseWoTableRowHandlersOptions = {
|
|||
onOpenMedia: (row: WorkOrderTableRow) => void;
|
||||
onPatch: (id: string | number, patch: WorkOrderTablePatch) => void;
|
||||
onScheduleHighlight: (id: string | number | null) => void;
|
||||
onDuplicateFound?: (existing: WorkOrderTableRow) => void;
|
||||
};
|
||||
|
||||
export function useWoTableRowHandlers({
|
||||
|
|
@ -30,6 +31,7 @@ export function useWoTableRowHandlers({
|
|||
onOpenMedia,
|
||||
onPatch,
|
||||
onScheduleHighlight,
|
||||
onDuplicateFound,
|
||||
}: UseWoTableRowHandlersOptions): WoTableRowHandlers {
|
||||
const {
|
||||
dragState: dragStateRef,
|
||||
|
|
@ -97,8 +99,7 @@ export function useWoTableRowHandlers({
|
|||
});
|
||||
},
|
||||
onDuplicateFound: (existing: WorkOrderTableRow) => {
|
||||
tableData.setSelectedId(existing.id);
|
||||
onOpenSlideOver(existing, false);
|
||||
onDuplicateFound?.(existing);
|
||||
},
|
||||
onOpenSlideOver: (row: WorkOrderTableRow) => onOpenSlideOver(row, false),
|
||||
onPastDueAttempt: (id: string | number) => onScheduleHighlight(id),
|
||||
|
|
@ -138,6 +139,7 @@ export function useWoTableRowHandlers({
|
|||
onOpenMedia,
|
||||
onPatch,
|
||||
onScheduleHighlight,
|
||||
onDuplicateFound,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type {
|
|||
WorkOrderTableRow,
|
||||
} from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard";
|
||||
import { DuplicateWoNumberDialog } from "@/app/(protected)/workorders/_components/list/duplicate-wo-number-dialog";
|
||||
import { WoTableSkeleton } from "@/app/(protected)/workorders/_components/loading/wo-table-skeleton";
|
||||
import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types";
|
||||
import { buildInlineDraftCreateInput } from "./build-inline-draft-create-input";
|
||||
|
|
@ -55,6 +56,7 @@ export function WoTable({
|
|||
onPatch,
|
||||
}: WoTableProps) {
|
||||
const [scheduleHighlightId, setScheduleHighlightId] = useState<string | number | null>(null);
|
||||
const [duplicateRow, setDuplicateRow] = useState<WorkOrderTableRow | null>(null);
|
||||
const { data: locationSites = [] } = useLocationSites();
|
||||
const sitePlaceLookups = useMemo(() => buildSitePlaceLookups(locationSites), [locationSites]);
|
||||
const createMutation = useCreateWorkOrderFromWizard(() => {
|
||||
|
|
@ -70,6 +72,7 @@ export function WoTable({
|
|||
onOpenMedia,
|
||||
onPatch,
|
||||
onScheduleHighlight: setScheduleHighlightId,
|
||||
onDuplicateFound: setDuplicateRow,
|
||||
});
|
||||
|
||||
const renderRow = (row: WorkOrderTableRow, forceScheduleHighlight = false) => (
|
||||
|
|
@ -150,21 +153,33 @@ export function WoTable({
|
|||
}
|
||||
|
||||
return (
|
||||
<WoTableShell>
|
||||
<table
|
||||
style={{
|
||||
width: "100%",
|
||||
minWidth: WO_TABLE_MIN_WIDTH,
|
||||
borderCollapse: "collapse",
|
||||
<>
|
||||
<WoTableShell>
|
||||
<table
|
||||
style={{
|
||||
width: "100%",
|
||||
minWidth: WO_TABLE_MIN_WIDTH,
|
||||
borderCollapse: "collapse",
|
||||
}}
|
||||
>
|
||||
<WoTableHead />
|
||||
<WoTableBody
|
||||
tableData={tableData}
|
||||
renderRow={renderRow}
|
||||
renderInlineRow={renderInlineRow}
|
||||
/>
|
||||
</table>
|
||||
</WoTableShell>
|
||||
|
||||
<DuplicateWoNumberDialog
|
||||
open={duplicateRow != null}
|
||||
existing={duplicateRow}
|
||||
onDismiss={() => setDuplicateRow(null)}
|
||||
onViewExisting={(row) => {
|
||||
setDuplicateRow(null);
|
||||
onOpenSlideOver(row, false);
|
||||
}}
|
||||
>
|
||||
<WoTableHead />
|
||||
<WoTableBody
|
||||
tableData={tableData}
|
||||
renderRow={renderRow}
|
||||
renderInlineRow={renderInlineRow}
|
||||
/>
|
||||
</table>
|
||||
</WoTableShell>
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,58 @@
|
|||
import { fireEvent, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { DuplicateWoNumberDialog } from "@/app/(protected)/workorders/_components/list/duplicate-wo-number-dialog";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { renderWithProviders } from "@/test/test-utils";
|
||||
|
||||
const existingRow = { id: "wo-99", woNumber: "12345678901" } as WorkOrderTableRow;
|
||||
|
||||
describe("DuplicateWoNumberDialog", () => {
|
||||
it("shows the duplicate title and work order number when open", () => {
|
||||
renderWithProviders(
|
||||
<DuplicateWoNumberDialog
|
||||
open
|
||||
existing={existingRow}
|
||||
onDismiss={vi.fn()}
|
||||
onViewExisting={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole("dialog")).toBeInTheDocument();
|
||||
expect(screen.getByText("Duplicate Work Order Number")).toBeInTheDocument();
|
||||
expect(screen.getByText(/12345678901/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calls onViewExisting when View existing is clicked", () => {
|
||||
const onViewExisting = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
<DuplicateWoNumberDialog
|
||||
open
|
||||
existing={existingRow}
|
||||
onDismiss={vi.fn()}
|
||||
onViewExisting={onViewExisting}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "View existing" }));
|
||||
|
||||
expect(onViewExisting).toHaveBeenCalledWith(existingRow);
|
||||
});
|
||||
|
||||
it("calls onDismiss when Dismiss is clicked", () => {
|
||||
const onDismiss = vi.fn();
|
||||
|
||||
renderWithProviders(
|
||||
<DuplicateWoNumberDialog
|
||||
open
|
||||
existing={existingRow}
|
||||
onDismiss={onDismiss}
|
||||
onViewExisting={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
|
||||
|
||||
expect(onDismiss).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import { renderHook } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { useWoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/use-wo-table-row-handlers";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
|
||||
const existingRow = { id: "wo-99", woNumber: "12345678901" } as WorkOrderTableRow;
|
||||
|
||||
function buildTableData() {
|
||||
return {
|
||||
dragState: { current: { dragId: null, overId: null } },
|
||||
setDragViz: vi.fn(),
|
||||
reorderRows: vi.fn(),
|
||||
endDrag: vi.fn(),
|
||||
setUnschedOpen: vi.fn(),
|
||||
triggerFlash: vi.fn(),
|
||||
setSelectedId: vi.fn(),
|
||||
} as never;
|
||||
}
|
||||
|
||||
describe("useWoTableRowHandlers onDuplicateFound", () => {
|
||||
it("delegates to onDuplicateFound instead of opening the slide-over", () => {
|
||||
const onDuplicateFound = vi.fn();
|
||||
const onOpenSlideOver = vi.fn();
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useWoTableRowHandlers({
|
||||
tableData: buildTableData(),
|
||||
weekMonday: "2026-07-20",
|
||||
onWeekChange: vi.fn(),
|
||||
onOpenSlideOver,
|
||||
onOpenMedia: vi.fn(),
|
||||
onPatch: vi.fn(),
|
||||
onScheduleHighlight: vi.fn(),
|
||||
onDuplicateFound,
|
||||
}),
|
||||
);
|
||||
|
||||
result.current.onDuplicateFound?.(existingRow);
|
||||
|
||||
expect(onDuplicateFound).toHaveBeenCalledWith(existingRow);
|
||||
expect(onOpenSlideOver).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
17
tmp/pr-descriptions/pr8-duplicate-dialog.md
Normal file
17
tmp/pr-descriptions/pr8-duplicate-dialog.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
## Summary
|
||||
|
||||
- Show a confirmation dialog when inline WO number editing detects a duplicate instead of navigating immediately to the existing work order.
|
||||
- Dialog title: **Duplicate Work Order Number**; actions: **View existing** (opens the existing WO slide-over) and **Dismiss** (closes the dialog and keeps the current row editable).
|
||||
- Duplicate scan logic in `editable-wo-number-cell.tsx` is unchanged; only the post-detection UX is updated.
|
||||
|
||||
## Test plan
|
||||
|
||||
- [ ] Edit a WO number in the table to match an existing row → dialog opens with the duplicate number in the message.
|
||||
- [ ] Click **Dismiss** → dialog closes; edited row keeps its original number.
|
||||
- [ ] Repeat duplicate entry → click **View existing** → navigates to `/workorders/{id}` for the existing row.
|
||||
- [ ] Confirm non-duplicate WO number edits still save normally.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Server-side uniqueness (422) handling
|
||||
- SH provisional numbering
|
||||
Loading…
Add table
Reference in a new issue