refactor(work-orders): extract wizard controller for maintainability gate

Move duplicate-WO wiring and lookup hooks out of NewWoWizard so the
changed-file function line cap passes after merging dev.
This commit is contained in:
arthur.bassi 2026-08-13 11:38:38 -03:00
parent 7d57319c91
commit 33026ab927
2 changed files with 135 additions and 109 deletions

View file

@ -1,21 +1,10 @@
import { useEffect } from "react";
import { Dialog } from "@mui/material";
import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop";
import { guardDialogClose } from "@/lib/dialog-guard";
import { DuplicateWoNumberDialog } from "@/app/(protected)/workorders/_components/list/duplicate-wo-number-dialog";
import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category";
import type { WorkOrderUser } from "@/domain/work-orders/types/work-order";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
import { useLocationDetail } from "@/domain/locations/use-cases/use-location-detail";
import { useLocationSites } from "@/domain/locations/use-cases/use-location-sites";
import { useWorkOrderVendors } from "@/domain/work-orders/use-cases/use-work-order-detail";
import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard";
import { WizardFooter } from "@/app/(protected)/workorders/_components/wizard/wizard-footer";
import { WizardSidebar } from "@/app/(protected)/workorders/_components/wizard/wizard-sidebar";
@ -23,9 +12,7 @@ import { WizardStepHeader } from "@/app/(protected)/workorders/_components/wizar
import { WizardStepLocationService } from "@/app/(protected)/workorders/_components/wizard/wizard-step-location-service";
import { WizardStepTypeSchedule } from "@/app/(protected)/workorders/_components/wizard/wizard-step-type-schedule";
import { WizardStepVendorTime } from "@/app/(protected)/workorders/_components/wizard/wizard-step-vendor-time";
import { useNewWoWizardDraft } from "@/app/(protected)/workorders/_components/wizard/use-new-wo-wizard-draft";
import { useWizardDuplicateActions } from "@/app/(protected)/workorders/_components/wizard/use-wizard-duplicate-actions";
import { useWizardPocAutofill } from "@/app/(protected)/workorders/_components/wizard/use-wizard-poc-autofill";
import { useNewWoWizardController } from "@/app/(protected)/workorders/_components/wizard/use-new-wo-wizard-controller";
type NewWoWizardProps = {
open: boolean;
@ -42,67 +29,13 @@ export function NewWoWizard({
existingRows = [],
onViewExistingWo,
}: NewWoWizardProps) {
const {
step,
setStep,
draft,
setDraft,
setField,
patchDraft: patchDraftOnly,
svcOpen,
setSvcOpen,
svcSearch,
setSvcSearch,
} = useNewWoWizardDraft(open);
const { data: locations = [] } = useLocationSites();
const { data: problems = [] } = useDropdownOptionsByCategory("Problem");
const {
data: locationDetail,
isLoading: locationDetailLoading,
isError: locationDetailError,
} = useLocationDetail(draft.locationId || undefined);
const { data: vendors = [] } = useWorkOrderVendors(
draft.pm.trim() || undefined,
locationDetail?.zipCode,
);
const { notifyLocationChanged } = useWizardPocAutofill(
open,
draft.locationId,
locationDetail,
setDraft,
);
const patchDraft = (patch: Partial<WorkOrderWizardDraft>) => {
patchDraftOnly(patch);
if (patch.locationId !== undefined) {
notifyLocationChanged(patch.locationId);
}
};
const createMutation = useCreateWorkOrderFromWizard(() => onOpenChange(false));
const { duplicateRow, setDuplicateRow, handleDuplicateFound, handleCreate, isCreating } =
useWizardDuplicateActions({
open,
draft,
existingRows,
createMutation,
});
useEffect(() => {
if (open) {
setDuplicateRow(null);
}
}, [open, setDuplicateRow]);
const handleClose = guardDialogClose(isCreating, () => onOpenChange(false));
const wizard = useNewWoWizardController({ open, onOpenChange, existingRows });
return (
<>
<Dialog
open={open}
onClose={handleClose}
onClose={wizard.handleClose}
maxWidth={false}
slotProps={{
...dialogBlurBackdropSlotProps,
@ -114,80 +47,80 @@ export function NewWoWizard({
maxWidth: 768,
height: 680,
maxHeight: 680,
pointerEvents: isCreating ? "none" : undefined,
opacity: isCreating ? 0.85 : 1,
pointerEvents: wizard.isCreating ? "none" : undefined,
opacity: wizard.isCreating ? 0.85 : 1,
},
},
}}
>
<span className="sr-only">Guided setup for creating a new work order</span>
<div className="flex min-h-0 flex-1" aria-busy={isCreating || undefined}>
<WizardSidebar step={step} draft={draft} />
<div className="flex min-h-0 flex-1" aria-busy={wizard.isCreating || undefined}>
<WizardSidebar step={wizard.step} draft={wizard.draft} />
<div
className="min-h-0 flex-1 overflow-y-auto"
style={{ padding: 32, background: "var(--color-bg-surface)" }}
>
<WizardStepHeader step={step} />
<WizardStepHeader step={wizard.step} />
{step === 1 && (
{wizard.step === 1 && (
<WizardStepTypeSchedule
draft={draft}
draft={wizard.draft}
users={users}
onUpdate={setField}
onPatch={patchDraft}
onDuplicateWoNumber={handleDuplicateFound}
onUpdate={wizard.setField}
onPatch={wizard.patchDraft}
onDuplicateWoNumber={wizard.handleDuplicateFound}
/>
)}
{step === 2 && (
{wizard.step === 2 && (
<WizardStepLocationService
draft={draft}
locations={locations}
locationDetail={locationDetail}
locationDetailLoading={locationDetailLoading}
locationDetailError={locationDetailError}
services={problems.map((p) => ({ id: p.id, value: p.value }))}
svcOpen={svcOpen}
onSvcOpenChange={setSvcOpen}
svcSearch={svcSearch}
onSvcSearchChange={setSvcSearch}
onUpdate={setField}
onPatch={patchDraft}
draft={wizard.draft}
locations={wizard.locations}
locationDetail={wizard.locationDetail}
locationDetailLoading={wizard.locationDetailLoading}
locationDetailError={wizard.locationDetailError}
services={wizard.problems.map((p) => ({ id: p.id, value: p.value }))}
svcOpen={wizard.svcOpen}
onSvcOpenChange={wizard.setSvcOpen}
svcSearch={wizard.svcSearch}
onSvcSearchChange={wizard.setSvcSearch}
onUpdate={wizard.setField}
onPatch={wizard.patchDraft}
/>
)}
{step === 3 && (
{wizard.step === 3 && (
<WizardStepVendorTime
draft={draft}
vendors={vendors}
onUpdate={setField}
onPatch={patchDraft}
draft={wizard.draft}
vendors={wizard.vendors}
onUpdate={wizard.setField}
onPatch={wizard.patchDraft}
/>
)}
</div>
</div>
<WizardFooter
step={step}
draft={draft}
step={wizard.step}
draft={wizard.draft}
onCancel={() => {
if (!isCreating) onOpenChange(false);
if (!wizard.isCreating) onOpenChange(false);
}}
onBack={() => setStep((s) => s - 1)}
onContinue={() => setStep((s) => s + 1)}
onCreate={handleCreate}
isCreating={isCreating}
onBack={() => wizard.setStep((s) => s - 1)}
onContinue={() => wizard.setStep((s) => s + 1)}
onCreate={wizard.handleCreate}
isCreating={wizard.isCreating}
/>
</Dialog>
<DuplicateWoNumberDialog
open={duplicateRow != null}
existing={duplicateRow}
onDismiss={() => setDuplicateRow(null)}
open={wizard.duplicateRow != null}
existing={wizard.duplicateRow}
onDismiss={() => wizard.setDuplicateRow(null)}
onViewExisting={(row) => {
setDuplicateRow(null);
wizard.setDuplicateRow(null);
onOpenChange(false);
onViewExistingWo?.(row);
}}

View file

@ -0,0 +1,93 @@
import { useEffect } from "react";
import { guardDialogClose } from "@/lib/dialog-guard";
import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard";
import { useLocationDetail } from "@/domain/locations/use-cases/use-location-detail";
import { useLocationSites } from "@/domain/locations/use-cases/use-location-sites";
import { useWorkOrderVendors } from "@/domain/work-orders/use-cases/use-work-order-detail";
import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard";
import { useNewWoWizardDraft } from "@/app/(protected)/workorders/_components/wizard/use-new-wo-wizard-draft";
import { useWizardDuplicateActions } from "@/app/(protected)/workorders/_components/wizard/use-wizard-duplicate-actions";
import { useWizardPocAutofill } from "@/app/(protected)/workorders/_components/wizard/use-wizard-poc-autofill";
type UseNewWoWizardControllerArgs = {
open: boolean;
onOpenChange: (open: boolean) => void;
existingRows: WorkOrderTableRow[];
};
/** Wires draft state, lookups, POC autofill, and duplicate-WO guards for the create wizard. */
export function useNewWoWizardController({
open,
onOpenChange,
existingRows,
}: UseNewWoWizardControllerArgs) {
const draftState = useNewWoWizardDraft(open);
const { data: locations = [] } = useLocationSites();
const { data: problems = [] } = useDropdownOptionsByCategory("Problem");
const {
data: locationDetail,
isLoading: locationDetailLoading,
isError: locationDetailError,
} = useLocationDetail(draftState.draft.locationId || undefined);
const { data: vendors = [] } = useWorkOrderVendors(
draftState.draft.pm.trim() || undefined,
locationDetail?.zipCode,
);
const { notifyLocationChanged } = useWizardPocAutofill(
open,
draftState.draft.locationId,
locationDetail,
draftState.setDraft,
);
const patchDraft = (patch: Partial<WorkOrderWizardDraft>) => {
draftState.patchDraft(patch);
if (patch.locationId !== undefined) {
notifyLocationChanged(patch.locationId);
}
};
const createMutation = useCreateWorkOrderFromWizard(() => onOpenChange(false));
const { duplicateRow, setDuplicateRow, handleDuplicateFound, handleCreate, isCreating } =
useWizardDuplicateActions({
open,
draft: draftState.draft,
existingRows,
createMutation,
});
useEffect(() => {
if (open) {
setDuplicateRow(null);
}
}, [open, setDuplicateRow]);
const handleClose = guardDialogClose(isCreating, () => onOpenChange(false));
return {
...draftState,
patchDraft,
locations,
problems,
locationDetail,
locationDetailLoading,
locationDetailError,
vendors,
duplicateRow,
setDuplicateRow,
handleDuplicateFound,
handleCreate,
isCreating,
handleClose,
};
}