Merge remote-tracking branch 'origin/feat/ab/sh-335-wo-site-dialog' into feat/ab/sh-312-site-map-preview

This commit is contained in:
Alexandre Brandizzi 2026-09-25 16:42:55 -03:00
commit 4f90e0dce1
4 changed files with 29 additions and 5 deletions

View file

@ -10,12 +10,23 @@ type SiteDialogPocSectionProps = {
primaryRequired?: boolean; primaryRequired?: boolean;
/** Edits go to the site record rather than only this work order. */ /** Edits go to the site record rather than only this work order. */
editsSiteRecord?: boolean; editsSiteRecord?: boolean;
/** The site record did not load, so Save writes this work order only. */
siteRecordUnavailable?: boolean;
onPocNameChange: (value: string) => void; onPocNameChange: (value: string) => void;
onPocPhoneChange: (value: string) => void; onPocPhoneChange: (value: string) => void;
onPocNotesChange: (value: string) => void; onPocNotesChange: (value: string) => void;
onAdditionalContactsChange: (contacts: WorkOrderAdditionalContact[]) => void; onAdditionalContactsChange: (contacts: WorkOrderAdditionalContact[]) => void;
}; };
function helperText(editsSiteRecord: boolean, siteRecordUnavailable: boolean): string {
if (!editsSiteRecord) {
return "Auto-filled from site records when empty. Additional contacts persist on save.";
}
return siteRecordUnavailable
? "Site record unavailable — changes apply to this work order only."
: "Contacts and notes are saved to the site record and apply to all its work orders.";
}
export function SiteDialogPocSection({ export function SiteDialogPocSection({
pocName, pocName,
pocPhone, pocPhone,
@ -24,6 +35,7 @@ export function SiteDialogPocSection({
showPrimaryErrors = false, showPrimaryErrors = false,
primaryRequired = false, primaryRequired = false,
editsSiteRecord = false, editsSiteRecord = false,
siteRecordUnavailable = false,
onPocNameChange, onPocNameChange,
onPocPhoneChange, onPocPhoneChange,
onPocNotesChange, onPocNotesChange,
@ -41,11 +53,7 @@ export function SiteDialogPocSection({
onPocPhoneChange={onPocPhoneChange} onPocPhoneChange={onPocPhoneChange}
onPocNotesChange={onPocNotesChange} onPocNotesChange={onPocNotesChange}
onAdditionalContactsChange={onAdditionalContactsChange} onAdditionalContactsChange={onAdditionalContactsChange}
helperText={ helperText={helperText(editsSiteRecord, siteRecordUnavailable)}
editsSiteRecord
? "Contacts and notes are saved to the site record and apply to all its work orders."
: "Auto-filled from site records when empty. Additional contacts persist on save."
}
/> />
); );
} }

View file

@ -139,6 +139,7 @@ export function SiteDialog({
showPrimaryErrors={s.requiresPoc && s.showErrors} showPrimaryErrors={s.requiresPoc && s.showErrors}
primaryRequired={s.requiresPoc} primaryRequired={s.requiresPoc}
editsSiteRecord={s.editsSiteRecord} editsSiteRecord={s.editsSiteRecord}
siteRecordUnavailable={s.siteRecordUnavailable}
onPocNameChange={s.setPn} onPocNameChange={s.setPn}
onPocPhoneChange={s.setPp} onPocPhoneChange={s.setPp}
onPocNotesChange={s.setNotes} onPocNotesChange={s.setNotes}

View file

@ -95,6 +95,9 @@ export function useSiteDialogState({
locationDetailError: liveLocationDetailError, locationDetailError: liveLocationDetailError,
fields, fields,
}); });
// The site failed to load (or the user typed before it did), so Save writes this work order only.
const siteRecordUnavailable =
editsSiteRecord && Boolean(locId) && !siteRecord.synced && !siteRecord.loading;
const updateSite = useUpdateSiteContactInfo(); const updateSite = useUpdateSiteContactInfo();
const pocMissing = !pn.trim() || !pp.trim(); const pocMissing = !pn.trim() || !pp.trim();
const requiresPoc = createMode || editsSiteRecord; const requiresPoc = createMode || editsSiteRecord;
@ -166,6 +169,7 @@ export function useSiteDialogState({
saving: updateSite.isPending, saving: updateSite.isPending,
siteRecordLoading: siteRecord.loading, siteRecordLoading: siteRecord.loading,
editsSiteRecord, editsSiteRecord,
siteRecordUnavailable,
requiresPoc, requiresPoc,
setPn: fields.setPnDirty, setPn: fields.setPnDirty,
setPp: fields.setPpDirty, setPp: fields.setPpDirty,

View file

@ -34,6 +34,10 @@ const dallas: Location = {
const sites = [{ id: "1", name: "DAL1" }]; const sites = [{ id: "1", name: "DAL1" }];
const SITE_RECORD_COPY =
"Contacts and notes are saved to the site record and apply to all its work orders.";
const SITE_UNAVAILABLE_COPY = "Site record unavailable — changes apply to this work order only.";
function renderDialog(overrides: Partial<Parameters<typeof SiteDialog>[0]> = {}) { function renderDialog(overrides: Partial<Parameters<typeof SiteDialog>[0]> = {}) {
const onSave = vi.fn(); const onSave = vi.fn();
const onOpenChange = vi.fn(); const onOpenChange = vi.fn();
@ -93,6 +97,8 @@ describe("Work order Site dialog edits the site record", () => {
expect(screen.queryByText("Enter an address to preview the location")).not.toBeInTheDocument(); expect(screen.queryByText("Enter an address to preview the location")).not.toBeInTheDocument();
expect(screen.getByText("(214) 555-0100")).toBeInTheDocument(); expect(screen.getByText("(214) 555-0100")).toBeInTheDocument();
expect(screen.getByText("dal1@acme.test")).toBeInTheDocument(); expect(screen.getByText("dal1@acme.test")).toBeInTheDocument();
expect(screen.getByText(SITE_RECORD_COPY)).toBeInTheDocument();
expect(screen.queryByText(SITE_UNAVAILABLE_COPY)).not.toBeInTheDocument();
}); });
it("locks the contact fields until the site loads, so typed input is never replaced", async () => { it("locks the contact fields until the site loads, so typed input is never replaced", async () => {
@ -124,12 +130,17 @@ describe("Work order Site dialog edits the site record", () => {
const { onSave, queryClient } = renderDialog(); const { onSave, queryClient } = renderDialog();
await waitFor(() => expect(primaryField("POC name")).toBeEnabled()); await waitFor(() => expect(primaryField("POC name")).toBeEnabled());
expect(screen.getByText(SITE_UNAVAILABLE_COPY)).toBeInTheDocument();
expect(screen.queryByText(SITE_RECORD_COPY)).not.toBeInTheDocument();
fireEvent.change(primaryField("POC name"), { target: { value: "John" } }); fireEvent.change(primaryField("POC name"), { target: { value: "John" } });
await queryClient.refetchQueries({ queryKey: queryKeys.locations.detail("1") }); await queryClient.refetchQueries({ queryKey: queryKeys.locations.detail("1") });
await waitFor(() => expect(getById).toHaveBeenCalledTimes(2)); await waitFor(() => expect(getById).toHaveBeenCalledTimes(2));
await screen.findByText("(214) 555-0100"); await screen.findByText("(214) 555-0100");
// The typed input stays on the work order, so the copy still says so after the refetch.
expect(screen.getByText(SITE_UNAVAILABLE_COPY)).toBeInTheDocument();
expect(screen.queryByText(SITE_RECORD_COPY)).not.toBeInTheDocument();
expect(primaryField("POC name")).toHaveValue("John"); expect(primaryField("POC name")).toHaveValue("John");
expect(primaryField("POC name")).toBeEnabled(); expect(primaryField("POC name")).toBeEnabled();
expect(primaryField("POC phone")).toHaveValue("(000) 000-0000"); expect(primaryField("POC phone")).toHaveValue("(000) 000-0000");