refactor(work-orders): keep technician assignment paths maintainable

This commit is contained in:
Alexandre Brandizzi 2026-09-29 19:26:34 -03:00
parent 262c93b41c
commit 55b5441d71
4 changed files with 307 additions and 99 deletions

View file

@ -31,6 +31,14 @@ type UseCompDocDialogArgs = {
onDateCompletedChange?: (date: string) => void | Promise<void>;
};
function completionDocTechnician(
assigned: boolean | null | undefined,
frozenAssigned: boolean | null | undefined,
contactName: string,
): string {
return assigned === false || frozenAssigned === false ? "" : contactName || "";
}
function notifyDraftSaved() {
toast(
<div
@ -116,10 +124,11 @@ export function useCompDocDialog({
amazonRep,
seaRep,
siteCode: site.code,
tech:
info.technicianAssigned === false || company.technicianAssigned === false
? ""
: company.contactName || "",
tech: completionDocTechnician(
info.technicianAssigned,
company.technicianAssigned,
company.contactName,
),
company: company.name || "",
};

View file

@ -1,4 +1,4 @@
import { useEffect, useMemo, useState } from "react";
import { useEffect, useMemo, useState, type Dispatch, type SetStateAction } from "react";
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
import type { VendorFormValues } from "@/domain/vendors/schemas/vendor-schema";
import { useAddTechniciansToCompany } from "@/domain/vendors/use-cases/use-add-technicians-to-company";
@ -31,6 +31,69 @@ type UseVendorDialogFormArgs = {
onSave: (patch: VendorPatch) => void;
};
type TechnicianKeyArgs = Pick<
UseVendorDialogFormArgs,
"technicianAssigned" | "vendorId" | "tech" | "company" | "vendors"
>;
function resolveInitialTechnicianKey(args: TechnicianKeyArgs): string {
if (args.technicianAssigned === false) return NO_TECHNICIAN_KEY;
return resolveTechnicianKey(args.vendorId, args.tech, args.company, args.vendors);
}
function buildSubmitBaseline(args: TechnicianKeyArgs & { techPhone: string }) {
const options = buildTechnicianOptions(args.vendors, args.company, args.tech, args.company);
return {
company: args.company,
selectedKey: resolveInitialTechnicianKey(args),
phone: args.techPhone || phoneForOption(args.tech, options),
notes: "",
pendingCreateCount: 0,
};
}
function createCompanyPickHandler(args: {
selectedKey: string;
technicianOptions: ReturnType<typeof buildTechnicianOptions>;
setSelectedCompany: (company: string) => void;
setAddedVendors: Dispatch<SetStateAction<VendorDropdownItem[]>>;
setPendingCreates: Dispatch<SetStateAction<Record<string, VendorFormValues>>>;
setSelectedKey: (key: string) => void;
setT: (value: string) => void;
setPhone: (value: string) => void;
}) {
return (nextCompany: string) => {
const selectedOption = args.technicianOptions.find((option) => option.key === args.selectedKey);
args.setSelectedCompany(nextCompany);
if (selectedOption?.company === nextCompany) return;
args.setAddedVendors([]);
args.setPendingCreates({});
args.setSelectedKey("");
args.setT("");
args.setPhone("");
};
}
function createTechnicianPickHandler(args: {
technicianOptions: ReturnType<typeof buildTechnicianOptions>;
setSelectedKey: (key: string) => void;
setT: (value: string) => void;
setPhone: (value: string) => void;
}) {
return (key: string) => {
if (key === NO_TECHNICIAN_KEY) {
args.setSelectedKey(key);
args.setT("");
args.setPhone("");
return;
}
const option = args.technicianOptions.find((item) => item.key === key);
args.setSelectedKey(key);
args.setT(option?.name ?? key);
args.setPhone(option?.phone || phoneForOption(option?.name ?? key, args.technicianOptions));
};
}
export function useVendorDialogForm({
open,
onOpenChange,
@ -44,10 +107,12 @@ export function useVendorDialogForm({
}: UseVendorDialogFormArgs) {
const addTechnicians = useAddTechniciansToCompany();
const [selectedCompany, setSelectedCompany] = useState(company);
const technicianKeyArgs = useMemo(
() => ({ technicianAssigned, vendorId, tech, company, vendors }),
[technicianAssigned, vendorId, tech, company, vendors],
);
const [selectedKey, setSelectedKey] = useState(() =>
technicianAssigned === false
? NO_TECHNICIAN_KEY
: resolveTechnicianKey(vendorId, tech, company, vendors),
resolveInitialTechnicianKey(technicianKeyArgs),
);
const [t, setT] = useState(tech);
const [phone, setPhone] = useState(techPhone);
@ -73,10 +138,7 @@ export function useVendorDialogForm({
useEffect(() => {
if (open) {
const key =
technicianAssigned === false
? NO_TECHNICIAN_KEY
: resolveTechnicianKey(vendorId, tech, company, vendors);
const key = resolveInitialTechnicianKey(technicianKeyArgs);
const initialOptions = buildTechnicianOptions(vendors, company, tech, company);
setSelectedCompany(company);
setSelectedKey(key);
@ -88,7 +150,7 @@ export function useVendorDialogForm({
setSaveError(null);
}
// invariant: rebuilding options after Add must not clear staged technician drafts.
}, [open, tech, techPhone, vendors, company, vendorId, technicianAssigned]);
}, [open, tech, techPhone, vendors, company, vendorId, technicianAssigned, technicianKeyArgs]);
const selected = technicianOptions.find((option) => option.key === selectedKey);
const resolvedCompany = selectedCompany;
@ -112,14 +174,7 @@ export function useVendorDialogForm({
},
baseline: {
company,
selectedKey:
technicianAssigned === false
? NO_TECHNICIAN_KEY
: resolveTechnicianKey(vendorId, tech, company, vendors),
phone:
techPhone || phoneForOption(tech, buildTechnicianOptions(vendors, company, tech, company)),
notes: "",
pendingCreateCount: 0,
...buildSubmitBaseline({ ...technicianKeyArgs, techPhone }),
},
});
@ -141,6 +196,23 @@ export function useVendorDialogForm({
canSubmit,
});
const handleCompanyPick = createCompanyPickHandler({
selectedKey,
technicianOptions,
setSelectedCompany,
setAddedVendors,
setPendingCreates,
setSelectedKey,
setT,
setPhone,
});
const handlePick = createTechnicianPickHandler({
technicianOptions,
setSelectedKey,
setT,
setPhone,
});
return {
companyOptions,
technicianOptions,
@ -158,29 +230,8 @@ export function useVendorDialogForm({
canSubmit,
isPending: addTechnicians.isPending,
handleSave,
handleCompanyPick: (nextCompany: string) => {
const selectedOption = technicianOptions.find((option) => option.key === selectedKey);
setSelectedCompany(nextCompany);
if (selectedOption?.company !== nextCompany) {
setAddedVendors([]);
setPendingCreates({});
setSelectedKey("");
setT("");
setPhone("");
}
},
handlePick: (key: string) => {
if (key === NO_TECHNICIAN_KEY) {
setSelectedKey(key);
setT("");
setPhone("");
return;
}
const option = technicianOptions.find((o) => o.key === key);
setSelectedKey(key);
setT(option?.name ?? key);
setPhone(option?.phone || phoneForOption(option?.name ?? key, technicianOptions));
},
handleCompanyPick,
handlePick,
handleTechnicianCreated: (
created: VendorDropdownItem,
createdPhone: string,

View file

@ -22,6 +22,138 @@ type UseVendorDialogSaveArgs = {
canSubmit: boolean;
};
type VendorResolutionArgs = Pick<
UseVendorDialogSaveArgs,
"allVendors" | "selectedKey" | "selectedVendor" | "resolvedCompany" | "anchorCompanyName" | "tech"
>;
function resolveSaveVendor(args: VendorResolutionArgs): VendorDropdownItem | undefined {
return (
(args.selectedKey
? args.allVendors.find((vendor) => String(vendor.id) === args.selectedKey)
: undefined) ??
args.allVendors.find(
(vendor) =>
vendor.contactName === args.tech &&
vendor.companyName === (args.resolvedCompany || args.anchorCompanyName),
) ??
args.selectedVendor
);
}
async function resolvePersistedVendor(args: {
vendor: VendorDropdownItem | undefined;
allVendors: VendorDropdownItem[];
pendingCreates: Record<string, VendorFormValues>;
phone: string;
addTechnicians: ReturnType<typeof useAddTechniciansToCompany>;
setSaveError: (error: string | null) => void;
}): Promise<VendorDropdownItem | null> {
const payload = args.vendor ? args.pendingCreates[String(args.vendor.id)] : undefined;
if (!args.vendor || !isDraftVendorId(args.vendor.id) || !payload) return args.vendor;
try {
return await persistDraftTechnician({
allVendors: args.allVendors,
pendingPayload: payload,
phone: args.phone,
addTechnicians: args.addTechnicians,
});
} catch (error) {
args.setSaveError(error instanceof Error ? error.message : "Failed to create technician");
return null;
}
}
function resolveVendorForSave(args: {
vendor: VendorDropdownItem | undefined;
allVendors: VendorDropdownItem[];
pendingCreates: Record<string, VendorFormValues>;
phone: string;
addTechnicians: ReturnType<typeof useAddTechniciansToCompany>;
setSaveError: (error: string | null) => void;
}): VendorDropdownItem | undefined | Promise<VendorDropdownItem | null> {
const payload = args.vendor ? args.pendingCreates[String(args.vendor.id)] : undefined;
if (!args.vendor || !isDraftVendorId(args.vendor.id) || !payload) return args.vendor;
return resolvePersistedVendor(args);
}
function isNoTechnician(key: string): boolean {
return key === "" || key === "__no_technician__";
}
function resolveAssignmentVendorId(args: {
noTechnician: boolean;
saveVendor: VendorDropdownItem | undefined;
vendorId: string | number;
resolvedCompany: string;
originalCompany: string;
}): string {
if (!args.noTechnician && args.saveVendor) return String(args.saveVendor.id);
if (
args.noTechnician &&
args.resolvedCompany === args.originalCompany &&
args.vendorId !== "" &&
args.vendorId != null
) {
return String(args.vendorId);
}
return args.noTechnician && args.saveVendor ? String(args.saveVendor.id) : "";
}
function buildVendorPatch(args: {
selectedKey: string;
saveVendor: VendorDropdownItem | undefined;
resolvedCompany: string;
anchorCompanyName: string;
tech: string;
phone: string;
vendorId: string | number;
originalCompany: string;
}): VendorPatch {
const assigned = args.selectedKey !== "" && !isNoTechnician(args.selectedKey);
const noTechnician = isNoTechnician(args.selectedKey);
return {
vendorId: resolveAssignmentVendorId({
noTechnician,
saveVendor: args.saveVendor,
vendorId: args.vendorId,
resolvedCompany: args.resolvedCompany,
originalCompany: args.originalCompany,
}),
company: args.resolvedCompany || args.anchorCompanyName,
tech: assigned ? (args.saveVendor?.contactName ?? args.tech) : "",
techPhone: assigned ? args.phone : "",
technicianAssigned: assigned,
};
}
function commitVendorPatch(args: {
saveVendor: VendorDropdownItem | undefined;
selectedKey: string;
resolvedCompany: string;
anchorCompanyName: string;
tech: string;
phone: string;
vendorId: string | number;
originalCompany: string;
onSave: (patch: VendorPatch) => void;
onOpenChange: (open: boolean) => void;
}): void {
args.onSave(
buildVendorPatch({
saveVendor: args.saveVendor,
selectedKey: args.selectedKey,
resolvedCompany: args.resolvedCompany,
anchorCompanyName: args.anchorCompanyName,
tech: args.tech,
phone: args.phone,
vendorId: args.vendorId,
originalCompany: args.originalCompany,
}),
);
args.onOpenChange(false);
}
/** Persists a staged draft technician under its existing company (additive roster PATCH). */
async function persistDraftTechnician({
allVendors,
@ -71,54 +203,54 @@ export function useVendorDialogSave({
setSaveError,
canSubmit,
}: UseVendorDialogSaveArgs) {
const resolveSaveVendor = () =>
(selectedKey ? allVendors.find((vendor) => String(vendor.id) === selectedKey) : undefined) ??
allVendors.find(
(vendor) =>
vendor.contactName === tech &&
vendor.companyName === (resolvedCompany || anchorCompanyName),
) ??
selectedVendor;
const handleSave = async () => {
if (!canSubmit) return;
setSaveError(null);
let saveVendor = resolveSaveVendor();
const pendingPayload = saveVendor ? pendingCreates[String(saveVendor.id)] : undefined;
if (saveVendor != null && isDraftVendorId(saveVendor.id) && pendingPayload) {
try {
saveVendor = await persistDraftTechnician({
allVendors,
pendingPayload,
phone,
addTechnicians,
});
} catch (error) {
const message = error instanceof Error ? error.message : "Failed to create technician";
setSaveError(message);
return;
}
}
const noTechnician = selectedKey === "" || selectedKey === "__no_technician__";
const preservedVendorId =
noTechnician && resolvedCompany === originalCompany && vendorId !== "" && vendorId != null
? String(vendorId)
: "";
onSave({
vendorId:
selectedKey && !noTechnician
? saveVendor
? String(saveVendor.id)
: ""
: preservedVendorId || (noTechnician && saveVendor ? String(saveVendor.id) : ""),
company: resolvedCompany || anchorCompanyName,
tech: selectedKey && !noTechnician ? (saveVendor?.contactName ?? tech) : "",
techPhone: selectedKey && !noTechnician ? phone : "",
technicianAssigned: Boolean(selectedKey && !noTechnician),
const vendor = resolveSaveVendor({
allVendors,
selectedKey,
selectedVendor,
resolvedCompany,
anchorCompanyName,
tech,
});
const resolvedVendor = resolveVendorForSave({
vendor,
allVendors,
pendingCreates,
phone,
addTechnicians,
setSaveError,
});
if (resolvedVendor instanceof Promise) {
const saveVendor = await resolvedVendor;
if (saveVendor === null) return;
commitVendorPatch({
saveVendor,
selectedKey,
resolvedCompany,
anchorCompanyName,
tech,
phone,
vendorId,
originalCompany,
onSave,
onOpenChange,
});
return;
}
commitVendorPatch({
saveVendor: resolvedVendor,
selectedKey,
resolvedCompany,
anchorCompanyName,
tech,
phone,
vendorId,
originalCompany,
onSave,
onOpenChange,
});
onOpenChange(false);
};
return { handleSave };

View file

@ -84,6 +84,26 @@ function readNullableBool(
return undefined;
}
function resolveTechnicianAssignment(
rowAssigned: boolean | null | undefined,
frozenAssigned: boolean | null | undefined,
): boolean | null | undefined {
if (rowAssigned === false || frozenAssigned === false) return false;
if (rowAssigned !== undefined) return rowAssigned;
return frozenAssigned;
}
function readEffectiveTechnicianFields(
item: Record<string, unknown>,
assigned: boolean | null | undefined,
): { tech: string; techPhone: string } {
if (assigned === false) return { tech: "", techPhone: "" };
return {
tech: readString(item, "techName", "TechName", "tech", "Tech", "technician", "Technician"),
techPhone: readString(item, "techPhone", "TechPhone"),
};
}
function readStringArray(record: Record<string, unknown>, ...keys: string[]): string[] {
for (const key of keys) {
const value = record[key];
@ -396,12 +416,11 @@ export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
const upliftSummary = boardUpliftSummary == null ? undefined : mapUpliftSummary(item);
const frozenCompany = readFrozenCompany(item);
const rowTechnicianAssigned = readNullableBool(item, "technicianAssigned", "TechnicianAssigned");
const technicianAssigned =
rowTechnicianAssigned === false || frozenCompany?.technicianAssigned === false
? false
: rowTechnicianAssigned !== undefined
? rowTechnicianAssigned
: frozenCompany?.technicianAssigned;
const technicianAssigned = resolveTechnicianAssignment(
rowTechnicianAssigned,
frozenCompany?.technicianAssigned,
);
const technician = readEffectiveTechnicianFields(item, technicianAssigned);
const row: WorkOrderTableRow = {
id: readId(item),
@ -428,11 +447,8 @@ export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
vendorId: vendorIdNum != null ? String(vendorIdNum) : "",
company: readString(item, "company", "Company", "vendorName", "VendorName"),
primaryDispatchStatus: readPrimaryDispatchStatus(item),
tech:
technicianAssigned === false
? ""
: readString(item, "techName", "TechName", "tech", "Tech", "technician", "Technician"),
techPhone: technicianAssigned === false ? "" : readString(item, "techPhone", "TechPhone"),
tech: technician.tech,
techPhone: technician.techPhone,
...(technicianAssigned !== undefined ? { technicianAssigned } : {}),
apptStart,
apptEnd,