From 647c7a7b3d8e664238e90d5c1e0350cdbdcde822 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Tue, 11 Aug 2026 11:14:08 -0300 Subject: [PATCH] fix(work-orders): extract hooks to satisfy max-lines governance gate Co-authored-by: Cursor --- .../list/table/use-wo-table-inline-create.ts | 58 ++++++++++ .../_components/list/table/wo-table.tsx | 41 ++----- .../wizard/use-new-wo-wizard-state.ts | 90 +++------------- .../wizard/use-wizard-duplicate-actions.ts | 100 ++++++++++++++++++ 4 files changed, 181 insertions(+), 108 deletions(-) create mode 100644 src/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create.ts create mode 100644 src/app/(protected)/workorders/_components/wizard/use-wizard-duplicate-actions.ts diff --git a/src/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create.ts b/src/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create.ts new file mode 100644 index 00000000..d6097cb4 --- /dev/null +++ b/src/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create.ts @@ -0,0 +1,58 @@ +import { useState } from "react"; +import { toast } from "react-toastify"; +import type { LocationOption, WorkOrderUser } from "@/domain/work-orders/types/work-order"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api"; +import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard"; +import { findDuplicateWoNumberRow } from "@/domain/work-orders/utils/find-duplicate-wo-number-row"; +import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types"; +import { buildInlineDraftCreateInput } from "./build-inline-draft-create-input"; + +type UseWoTableInlineCreateArgs = { + tableData: ReturnTypeOfUseWorkOrdersTableData; + users: WorkOrderUser[]; + sites: LocationOption[]; + onDuplicateFound: (row: WorkOrderTableRow) => void; +}; + +export function useWoTableInlineCreate({ + tableData, + users, + sites, + onDuplicateFound, +}: UseWoTableInlineCreateArgs) { + const [isCheckingInlineDuplicate, setIsCheckingInlineDuplicate] = useState(false); + const createMutation = useCreateWorkOrderFromWizard(() => { + tableData.cancelDraft(); + }); + + const handleInlineSave = () => { + void (async () => { + const draftSnapshot = tableData.draft; + const woNumber = draftSnapshot.woNumber.trim(); + if (woNumber) { + setIsCheckingInlineDuplicate(true); + try { + const duplicate = + findDuplicateWoNumberRow(woNumber, tableData.rows, "draft") ?? + (await workOrdersApi.findByWoNumber(woNumber)); + if (duplicate && String(duplicate.id) !== "draft") { + onDuplicateFound(duplicate); + return; + } + } catch { + toast.error("Unable to verify the work order number. Please try again."); + return; + } finally { + setIsCheckingInlineDuplicate(false); + } + } + createMutation.mutate(buildInlineDraftCreateInput(tableData.draft, users, sites)); + })(); + }; + + return { + handleInlineSave, + saving: createMutation.isPending || isCheckingInlineDuplicate, + }; +} diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table.tsx index c055d4d7..f3bd99c7 100644 --- a/src/app/(protected)/workorders/_components/list/table/wo-table.tsx +++ b/src/app/(protected)/workorders/_components/list/table/wo-table.tsx @@ -1,5 +1,4 @@ import { useMemo, useState } from "react"; -import { toast } from "react-toastify"; import { useLocationSites } from "@/domain/locations/use-cases/use-location-sites"; import type { LocationOption, @@ -10,14 +9,11 @@ import type { WorkOrderTablePatch, WorkOrderTableRow, } from "@/domain/work-orders/types/work-order-table-row"; -import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api"; -import { useCreateWorkOrderFromWizard } from "@/domain/work-orders/use-cases/use-create-work-order-from-wizard"; -import { findDuplicateWoNumberRow } from "@/domain/work-orders/utils/find-duplicate-wo-number-row"; import { DuplicateWoNumberDialog } from "@/app/(protected)/workorders/_components/list/duplicate-wo-number-dialog"; import { WoTableSkeleton } from "@/app/(protected)/workorders/_components/loading/wo-table-skeleton"; import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types"; -import { buildInlineDraftCreateInput } from "./build-inline-draft-create-input"; import { buildSitePlaceLookups, resolveSitePlace } from "./site-place-lookup"; +import { useWoTableInlineCreate } from "./use-wo-table-inline-create"; import { useWoTableRowHandlers } from "./use-wo-table-row-handlers"; import { WoTableBody } from "./wo-table-body"; import { WoTableHead } from "./wo-table-head"; @@ -60,11 +56,13 @@ export function WoTable({ }: WoTableProps) { const [scheduleHighlightId, setScheduleHighlightId] = useState(null); const [duplicateRow, setDuplicateRow] = useState(null); - const [isCheckingInlineDuplicate, setIsCheckingInlineDuplicate] = useState(false); const { data: locationSites = [] } = useLocationSites(); const sitePlaceLookups = useMemo(() => buildSitePlaceLookups(locationSites), [locationSites]); - const createMutation = useCreateWorkOrderFromWizard(() => { - tableData.cancelDraft(); + const { handleInlineSave, saving } = useWoTableInlineCreate({ + tableData, + users, + sites, + onDuplicateFound: setDuplicateRow, }); const handlers = useWoTableRowHandlers({ @@ -79,31 +77,6 @@ export function WoTable({ onDuplicateFound: setDuplicateRow, }); - const handleInlineSave = () => { - void (async () => { - const draftSnapshot = tableData.draft; - const woNumber = draftSnapshot.woNumber.trim(); - if (woNumber) { - setIsCheckingInlineDuplicate(true); - try { - const duplicate = - findDuplicateWoNumberRow(woNumber, tableData.rows, "draft") ?? - (await workOrdersApi.findByWoNumber(woNumber)); - if (duplicate && String(duplicate.id) !== "draft") { - setDuplicateRow(duplicate); - return; - } - } catch { - toast.error("Unable to verify the work order number. Please try again."); - return; - } finally { - setIsCheckingInlineDuplicate(false); - } - } - createMutation.mutate(buildInlineDraftCreateInput(tableData.draft, users, sites)); - })(); - }; - const renderRow = (row: WorkOrderTableRow, forceScheduleHighlight = false) => ( (null); - const [isCheckingDuplicate, setIsCheckingDuplicate] = useState(false); - const openRef = useRef(open); - const draftRef = useRef(draft); - - useEffect(() => { - openRef.current = open; - }, [open]); - - useEffect(() => { - draftRef.current = draft; - }, [draft]); const { data: apiLocations = [] } = useWorkOrderLocations(); const { data: apiProblems = [] } = useDropdownOptionsByCategory("Problem"); @@ -72,6 +58,18 @@ export function useNewWoWizardState({ isMockLocation ? undefined : locationDetail?.zipCode, ); const createMutation = useCreateWorkOrderFromWizard(() => onOpenChange(false)); + const { + duplicateRow, + setDuplicateRow, + handleDuplicateFound, + handleCreate, + isCreating, + } = useWizardDuplicateActions({ + open, + draft, + existingRows, + createMutation, + }); const wizardUsers = useMemo(() => { if (users.length > 0) { @@ -92,7 +90,7 @@ export function useNewWoWizardState({ setPocAutoFilledFor(""); setDuplicateRow(null); } - }, [open]); + }, [open, setDuplicateRow]); useEffect(() => { if (!draft.locationId || !locationDetail) { @@ -123,62 +121,6 @@ export function useNewWoWizardState({ } }; - const resolveDuplicate = async (woNumber: string) => { - const localDuplicate = findDuplicateWoNumberRow(woNumber, existingRows); - if (localDuplicate) { - return localDuplicate; - } - - // The loaded board rows are only an immediate UX hint; the unfiltered API lookup is authoritative. - return workOrdersApi.findByWoNumber(woNumber); - }; - - const handleDuplicateFound = (woNumber: string) => { - void (async () => { - try { - const duplicate = await resolveDuplicate(woNumber); - // Ignore stale lookups after dismiss or after the user edits the WO#. - if (!openRef.current || woNumber !== draftRef.current.woNumber) { - return; - } - if (duplicate) { - setDuplicateRow(duplicate); - } - } catch { - if (!openRef.current) { - return; - } - toast.error("Unable to verify the work order number. Please try again."); - } - })(); - }; - - const handleCreate = () => { - const woNumberAtSubmit = draft.woNumber; - void (async () => { - setIsCheckingDuplicate(true); - try { - const duplicate = await resolveDuplicate(woNumberAtSubmit); - // Ignore after dismiss or if the WO# changed while the request was in flight. - if (!openRef.current || woNumberAtSubmit !== draftRef.current.woNumber) { - return; - } - if (duplicate) { - setDuplicateRow(duplicate); - return; - } - createMutation.mutate(draftRef.current); - } catch { - if (!openRef.current) { - return; - } - toast.error("Unable to verify the work order number. Please try again."); - } finally { - setIsCheckingDuplicate(false); - } - })(); - }; - return { step, setStep, @@ -194,6 +136,6 @@ export function useNewWoWizardState({ patchDraft, handleDuplicateFound, handleCreate, - isCreating: createMutation.isPending || isCheckingDuplicate, + isCreating, }; } diff --git a/src/app/(protected)/workorders/_components/wizard/use-wizard-duplicate-actions.ts b/src/app/(protected)/workorders/_components/wizard/use-wizard-duplicate-actions.ts new file mode 100644 index 00000000..0f015d2c --- /dev/null +++ b/src/app/(protected)/workorders/_components/wizard/use-wizard-duplicate-actions.ts @@ -0,0 +1,100 @@ +import { useEffect, useRef, useState } from "react"; +import { toast } from "react-toastify"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import type { WorkOrderWizardDraft } from "@/domain/work-orders/types/work-order-wizard"; +import { findDuplicateWoNumberRow } from "@/domain/work-orders/utils/find-duplicate-wo-number-row"; +import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api"; + +type UseWizardDuplicateActionsArgs = { + open: boolean; + draft: WorkOrderWizardDraft; + existingRows: WorkOrderTableRow[]; + createMutation: { + mutate: (draft: WorkOrderWizardDraft) => void; + isPending: boolean; + }; +}; + +export function useWizardDuplicateActions({ + open, + draft, + existingRows, + createMutation, +}: UseWizardDuplicateActionsArgs) { + const [duplicateRow, setDuplicateRow] = useState(null); + const [isCheckingDuplicate, setIsCheckingDuplicate] = useState(false); + const openRef = useRef(open); + const draftRef = useRef(draft); + + useEffect(() => { + openRef.current = open; + }, [open]); + + useEffect(() => { + draftRef.current = draft; + }, [draft]); + + const resolveDuplicate = async (woNumber: string) => { + const localDuplicate = findDuplicateWoNumberRow(woNumber, existingRows); + if (localDuplicate) { + return localDuplicate; + } + + // The loaded board rows are only an immediate UX hint; the unfiltered API lookup is authoritative. + return workOrdersApi.findByWoNumber(woNumber); + }; + + const handleDuplicateFound = (woNumber: string) => { + void (async () => { + try { + const duplicate = await resolveDuplicate(woNumber); + // Ignore stale lookups after dismiss or after the user edits the WO#. + if (!openRef.current || woNumber !== draftRef.current.woNumber) { + return; + } + if (duplicate) { + setDuplicateRow(duplicate); + } + } catch { + if (!openRef.current) { + return; + } + toast.error("Unable to verify the work order number. Please try again."); + } + })(); + }; + + const handleCreate = () => { + const woNumberAtSubmit = draft.woNumber; + void (async () => { + setIsCheckingDuplicate(true); + try { + const duplicate = await resolveDuplicate(woNumberAtSubmit); + // Ignore after dismiss or if the WO# changed while the request was in flight. + if (!openRef.current || woNumberAtSubmit !== draftRef.current.woNumber) { + return; + } + if (duplicate) { + setDuplicateRow(duplicate); + return; + } + createMutation.mutate(draftRef.current); + } catch { + if (!openRef.current) { + return; + } + toast.error("Unable to verify the work order number. Please try again."); + } finally { + setIsCheckingDuplicate(false); + } + })(); + }; + + return { + duplicateRow, + setDuplicateRow, + handleDuplicateFound, + handleCreate, + isCreating: createMutation.isPending || isCheckingDuplicate, + }; +}