mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(work-orders): preserve wizard POC edits, clear stale vendor, persist range start
This commit is contained in:
parent
162d47fe1a
commit
b3d8be571e
7 changed files with 134 additions and 8 deletions
|
|
@ -4,7 +4,11 @@ import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order
|
|||
|
||||
type SetDraft = (updater: (current: WorkOrderWizardDraft) => WorkOrderWizardDraft) => void;
|
||||
|
||||
/** Auto-fills POC name/phone from the fetched location, once per selected location. */
|
||||
/**
|
||||
* Auto-fills POC name/phone from the fetched location, once per selected location.
|
||||
* Never clobbers fields the dispatcher already typed while detail was loading
|
||||
* (prototype: autofill from site records, then edit if outdated).
|
||||
*/
|
||||
export function useWizardPocAutofill(
|
||||
open: boolean,
|
||||
locationId: string,
|
||||
|
|
@ -25,8 +29,12 @@ export function useWizardPocAutofill(
|
|||
}
|
||||
setDraft((current) => ({
|
||||
...current,
|
||||
pocName: locationDetail.contact ?? current.pocName,
|
||||
pocPhone: locationDetail.phone ?? current.pocPhone,
|
||||
pocName: current.pocName.trim()
|
||||
? current.pocName
|
||||
: (locationDetail.contact ?? current.pocName),
|
||||
pocPhone: current.pocPhone.trim()
|
||||
? current.pocPhone
|
||||
: (locationDetail.phone ?? current.pocPhone),
|
||||
}));
|
||||
setPocAutoFilledFor(locationId);
|
||||
}, [locationId, locationDetail, pocAutoFilledFor, setDraft]);
|
||||
|
|
|
|||
|
|
@ -30,11 +30,16 @@ export function WizardStepLocationServiceSelect({
|
|||
}: WizardStepLocationServiceSelectProps) {
|
||||
const svcSel = [draft.pm, ...draft.extraServices].filter((p, i, a) => !!p && a.indexOf(p) === i);
|
||||
|
||||
const clearVendorIfPrimaryChanged = (nextPm: string): Partial<WorkOrderWizardDraft> =>
|
||||
nextPm !== draft.pm ? { vendorId: "", vendorName: "", techPhone: "" } : {};
|
||||
|
||||
const toggleSvc = (p: string) => {
|
||||
const next = svcSel.includes(p) ? svcSel.filter((x) => x !== p) : [...svcSel, p];
|
||||
const nextPm = next[0] ?? "";
|
||||
onPatch({
|
||||
pm: next[0] ?? "",
|
||||
pm: nextPm,
|
||||
extraServices: next.slice(1),
|
||||
...clearVendorIfPrimaryChanged(nextPm),
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -62,6 +67,7 @@ export function WizardStepLocationServiceSelect({
|
|||
onPatch({
|
||||
pm: p,
|
||||
extraServices: draft.extraServices.filter((x) => x !== p),
|
||||
...clearVendorIfPrimaryChanged(p),
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -33,10 +33,14 @@ export function WizardStepLocationSiteSelect({
|
|||
}))}
|
||||
onPick={(id) => {
|
||||
const loc = locations.find((l) => String(l.id) === id);
|
||||
// Switching site invalidates the previous POC — clear so autofill can
|
||||
// seed from the new location without clobbering mid-load edits later.
|
||||
onPatch({
|
||||
locationId: id,
|
||||
locationName: loc?.name ?? "",
|
||||
siteCode: loc ? resolveSiteCode(loc) : "",
|
||||
pocName: "",
|
||||
pocPhone: "",
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import type { WorkOrderBoardCreatePayload } from "@/domain/work-orders/types/wor
|
|||
import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
|
||||
import { mondayOf } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
|
||||
/** Mutates `payload` with `dueDate` and day/week schedule fields (range mode has no schedule fields). */
|
||||
/**
|
||||
* Mutates `payload` with `dueDate` and schedule fields.
|
||||
* Range mode lands on the board using the range start as `scheduledDate`
|
||||
* (prototype `hasScheduleDate`: a range start qualifies as scheduled).
|
||||
*/
|
||||
export function assignWizardScheduleFields(
|
||||
payload: WorkOrderBoardCreatePayload,
|
||||
draft: WorkOrderWizardDraft,
|
||||
|
|
@ -21,7 +25,7 @@ export function assignWizardScheduleFields(
|
|||
if (week) {
|
||||
payload.targetWeek = week;
|
||||
}
|
||||
} else if (!isRange && draft.scheduledOn) {
|
||||
} else if (draft.scheduledOn && (isRange || !draft.scheduleWeekOnly)) {
|
||||
payload.scheduledDate = draft.scheduledOn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import { act, renderHook } from "@testing-library/react";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { useWizardPocAutofill } from "@/app/(protected)/workorders/_components/wizard/use-wizard-poc-autofill";
|
||||
import type { Location } from "@/domain/locations/types/location";
|
||||
import {
|
||||
EMPTY_WIZARD_DRAFT,
|
||||
type WorkOrderWizardDraft,
|
||||
} from "@/domain/work-orders/types/work-order-wizard";
|
||||
import { useState } from "react";
|
||||
|
||||
function baseLocation(overrides: Partial<Location> = {}): Location {
|
||||
return {
|
||||
id: "loc-1",
|
||||
name: "BK5",
|
||||
contact: "Janelle Pickett",
|
||||
phone: "214-555-0111",
|
||||
...overrides,
|
||||
} as Location;
|
||||
}
|
||||
|
||||
describe("useWizardPocAutofill", () => {
|
||||
it("fills empty POC fields from location detail once", () => {
|
||||
const { result } = renderHook(() => {
|
||||
const [draft, setDraft] = useState<WorkOrderWizardDraft>({ ...EMPTY_WIZARD_DRAFT });
|
||||
useWizardPocAutofill(true, "loc-1", baseLocation(), setDraft);
|
||||
return draft;
|
||||
});
|
||||
|
||||
expect(result.current.pocName).toBe("Janelle Pickett");
|
||||
expect(result.current.pocPhone).toBe("214-555-0111");
|
||||
});
|
||||
|
||||
it("does not overwrite POC edits made before location detail arrives", () => {
|
||||
const { result, rerender } = renderHook(
|
||||
({ detail }: { detail: Location | undefined }) => {
|
||||
const [draft, setDraft] = useState<WorkOrderWizardDraft>({
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
pocName: "Dispatcher Typed",
|
||||
pocPhone: "999-000-1111",
|
||||
});
|
||||
useWizardPocAutofill(true, "loc-1", detail, setDraft);
|
||||
return draft;
|
||||
},
|
||||
{ initialProps: { detail: undefined as Location | undefined } },
|
||||
);
|
||||
|
||||
expect(result.current.pocName).toBe("Dispatcher Typed");
|
||||
|
||||
act(() => {
|
||||
rerender({ detail: baseLocation() });
|
||||
});
|
||||
|
||||
expect(result.current.pocName).toBe("Dispatcher Typed");
|
||||
expect(result.current.pocPhone).toBe("999-000-1111");
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { WizardStepLocationServiceSelect } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-service-select";
|
||||
import {
|
||||
EMPTY_WIZARD_DRAFT,
|
||||
type WorkOrderWizardDraft,
|
||||
} from "@/domain/work-orders/types/work-order-wizard";
|
||||
|
||||
describe("WizardStepLocationServiceSelect — vendor clear on service change", () => {
|
||||
it("clears vendor fields when the primary service changes", () => {
|
||||
const onPatch = vi.fn();
|
||||
const draft: WorkOrderWizardDraft = {
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
pm: "HVAC",
|
||||
vendorId: "9",
|
||||
vendorName: "Old Vendor",
|
||||
techPhone: "555-0100",
|
||||
};
|
||||
|
||||
render(
|
||||
<WizardStepLocationServiceSelect
|
||||
draft={draft}
|
||||
services={[
|
||||
{ id: "1", value: "HVAC" },
|
||||
{ id: "2", value: "Plumbing" },
|
||||
]}
|
||||
svcOpen={false}
|
||||
onSvcOpenChange={vi.fn()}
|
||||
svcSearch=""
|
||||
onSvcSearchChange={vi.fn()}
|
||||
onUpdate={vi.fn()}
|
||||
onPatch={onPatch}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "HVAC" }));
|
||||
fireEvent.click(screen.getByRole("button", { name: "Plumbing" }));
|
||||
|
||||
expect(onPatch).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
pm: "Plumbing",
|
||||
vendorId: "",
|
||||
vendorName: "",
|
||||
techPhone: "",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -254,7 +254,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
|||
).toThrow("Site code is required");
|
||||
});
|
||||
|
||||
it("omits scheduledDate for range schedule and puts range in description only", () => {
|
||||
it("sets scheduledDate from range start and keeps range text in description", () => {
|
||||
const payload = mapWizardDraftToBoardCreatePayload({
|
||||
...EMPTY_WIZARD_DRAFT,
|
||||
|
||||
|
|
@ -273,7 +273,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
|||
vendorNotes: "Also not in description",
|
||||
});
|
||||
|
||||
expect(payload.scheduledDate).toBeUndefined();
|
||||
expect(payload.scheduledDate).toBe("2026-06-08");
|
||||
|
||||
expect(payload.scheduleWeekOnly).toBeUndefined();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue