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;
|
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(
|
export function useWizardPocAutofill(
|
||||||
open: boolean,
|
open: boolean,
|
||||||
locationId: string,
|
locationId: string,
|
||||||
|
|
@ -25,8 +29,12 @@ export function useWizardPocAutofill(
|
||||||
}
|
}
|
||||||
setDraft((current) => ({
|
setDraft((current) => ({
|
||||||
...current,
|
...current,
|
||||||
pocName: locationDetail.contact ?? current.pocName,
|
pocName: current.pocName.trim()
|
||||||
pocPhone: locationDetail.phone ?? current.pocPhone,
|
? current.pocName
|
||||||
|
: (locationDetail.contact ?? current.pocName),
|
||||||
|
pocPhone: current.pocPhone.trim()
|
||||||
|
? current.pocPhone
|
||||||
|
: (locationDetail.phone ?? current.pocPhone),
|
||||||
}));
|
}));
|
||||||
setPocAutoFilledFor(locationId);
|
setPocAutoFilledFor(locationId);
|
||||||
}, [locationId, locationDetail, pocAutoFilledFor, setDraft]);
|
}, [locationId, locationDetail, pocAutoFilledFor, setDraft]);
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,16 @@ export function WizardStepLocationServiceSelect({
|
||||||
}: WizardStepLocationServiceSelectProps) {
|
}: WizardStepLocationServiceSelectProps) {
|
||||||
const svcSel = [draft.pm, ...draft.extraServices].filter((p, i, a) => !!p && a.indexOf(p) === i);
|
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 toggleSvc = (p: string) => {
|
||||||
const next = svcSel.includes(p) ? svcSel.filter((x) => x !== p) : [...svcSel, p];
|
const next = svcSel.includes(p) ? svcSel.filter((x) => x !== p) : [...svcSel, p];
|
||||||
|
const nextPm = next[0] ?? "";
|
||||||
onPatch({
|
onPatch({
|
||||||
pm: next[0] ?? "",
|
pm: nextPm,
|
||||||
extraServices: next.slice(1),
|
extraServices: next.slice(1),
|
||||||
|
...clearVendorIfPrimaryChanged(nextPm),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -62,6 +67,7 @@ export function WizardStepLocationServiceSelect({
|
||||||
onPatch({
|
onPatch({
|
||||||
pm: p,
|
pm: p,
|
||||||
extraServices: draft.extraServices.filter((x) => x !== p),
|
extraServices: draft.extraServices.filter((x) => x !== p),
|
||||||
|
...clearVendorIfPrimaryChanged(p),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,14 @@ export function WizardStepLocationSiteSelect({
|
||||||
}))}
|
}))}
|
||||||
onPick={(id) => {
|
onPick={(id) => {
|
||||||
const loc = locations.find((l) => String(l.id) === 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({
|
onPatch({
|
||||||
locationId: id,
|
locationId: id,
|
||||||
locationName: loc?.name ?? "",
|
locationName: loc?.name ?? "",
|
||||||
siteCode: loc ? resolveSiteCode(loc) : "",
|
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 type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
|
||||||
import { mondayOf } from "@/domain/work-orders/utils/wizard-date-utils";
|
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(
|
export function assignWizardScheduleFields(
|
||||||
payload: WorkOrderBoardCreatePayload,
|
payload: WorkOrderBoardCreatePayload,
|
||||||
draft: WorkOrderWizardDraft,
|
draft: WorkOrderWizardDraft,
|
||||||
|
|
@ -21,7 +25,7 @@ export function assignWizardScheduleFields(
|
||||||
if (week) {
|
if (week) {
|
||||||
payload.targetWeek = week;
|
payload.targetWeek = week;
|
||||||
}
|
}
|
||||||
} else if (!isRange && draft.scheduledOn) {
|
} else if (draft.scheduledOn && (isRange || !draft.scheduleWeekOnly)) {
|
||||||
payload.scheduledDate = draft.scheduledOn;
|
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");
|
).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({
|
const payload = mapWizardDraftToBoardCreatePayload({
|
||||||
...EMPTY_WIZARD_DRAFT,
|
...EMPTY_WIZARD_DRAFT,
|
||||||
|
|
||||||
|
|
@ -273,7 +273,7 @@ describe("mapWizardDraftToBoardCreatePayload", () => {
|
||||||
vendorNotes: "Also not in description",
|
vendorNotes: "Also not in description",
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(payload.scheduledDate).toBeUndefined();
|
expect(payload.scheduledDate).toBe("2026-06-08");
|
||||||
|
|
||||||
expect(payload.scheduleWeekOnly).toBeUndefined();
|
expect(payload.scheduleWeekOnly).toBeUndefined();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue