diff --git a/src/app/(protected)/workorders/_components/detail/use-slide-over-edit-state.ts b/src/app/(protected)/workorders/_components/detail/use-slide-over-edit-state.ts index 0304111b..7d5deead 100644 --- a/src/app/(protected)/workorders/_components/detail/use-slide-over-edit-state.ts +++ b/src/app/(protected)/workorders/_components/detail/use-slide-over-edit-state.ts @@ -1,3 +1,4 @@ +import type { Dispatch, SetStateAction } from "react"; import { useEffect, useMemo, useState } from "react"; import type { WorkOrderTablePatch, @@ -19,6 +20,8 @@ function isSlideOverInfoLocked(status: string | null | undefined): boolean { return isWorkOrderFullyLocked(status) || isWorkOrderCoreLocked(status); } +type StateSetter = Dispatch>; + type UseSlideOverEditStateArgs = { row: WorkOrderTableRow | null; editMode: boolean; @@ -33,6 +36,47 @@ type UseSlideOverEditStateArgs = { openRequestKey?: number; }; +function useSlideOverInitialization({ + row, + editMode, + initialTab, + setTab, + setDraft, + setBaseline, + setEditing, + setShowUnsaved, + setUnsavedIntent, +}: { + row: WorkOrderTableRow | null; + editMode: boolean; + initialTab: SlideOverTab; + setTab: (tab: SlideOverTab) => void; + setDraft: StateSetter; + setBaseline: StateSetter; + setEditing: StateSetter; + setShowUnsaved: StateSetter; + setUnsavedIntent: StateSetter<"close" | "cancel-edit" | null>; +}) { + useEffect(() => { + if (!row) { + setDraft(null); + setBaseline(null); + setEditing(false); + setShowUnsaved(false); + setUnsavedIntent(null); + return; + } + setDraft(row); + setBaseline(row); + setEditing(editMode && !isSlideOverInfoLocked(row.status)); + setTab(editMode ? "info" : initialTab); + setShowUnsaved(false); + setUnsavedIntent(null); + // Intentionally identity-only: depending on `row` would absorb optimistic patches. + // eslint-disable-next-line react-hooks/exhaustive-deps -- row.id / editMode gate + }, [row?.id, editMode]); +} + export function useSlideOverEditState({ row, editMode, @@ -53,24 +97,17 @@ export function useSlideOverEditState({ // Reset draft/baseline only when the opened WO identity or editMode intent changes — // not on every optimistic parent-row field update during a failed/in-flight save. - useEffect(() => { - if (!row) { - setDraft(null); - setBaseline(null); - setEditing(false); - setShowUnsaved(false); - setUnsavedIntent(null); - return; - } - setDraft(row); - setBaseline(row); - setEditing(editMode && !isSlideOverInfoLocked(row.status)); - setTab(editMode ? "info" : initialTab); - setShowUnsaved(false); - setUnsavedIntent(null); - // Intentionally identity-only: depending on `row` would absorb optimistic patches. - // eslint-disable-next-line react-hooks/exhaustive-deps -- row.id / editMode gate - }, [row?.id, editMode]); + useSlideOverInitialization({ + row, + editMode, + initialTab, + setTab, + setDraft, + setBaseline, + setEditing, + setShowUnsaved, + setUnsavedIntent, + }); // Re-apply the requested tab on every open, including a second notification for the work order // already on screen (same row id) where the identity effect above does not re-run. Tab-only so @@ -110,8 +147,7 @@ export function useSlideOverEditState({ }); }, [infoSource, editing, baseline]); - const activeDraft = draft ?? infoSource; - const dirtyBaseline = baseline ?? infoSource; + const [activeDraft, dirtyBaseline] = [draft ?? infoSource, baseline ?? infoSource]; const isDirty = useMemo(() => { if (!dirtyBaseline || !activeDraft || !editing) return false; return isSlideOverDraftDirty(activeDraft, dirtyBaseline); diff --git a/src/app/(protected)/workorders/_hooks/use-work-orders-list-page.ts b/src/app/(protected)/workorders/_hooks/use-work-orders-list-page.ts index 252709cd..113dec56 100644 --- a/src/app/(protected)/workorders/_hooks/use-work-orders-list-page.ts +++ b/src/app/(protected)/workorders/_hooks/use-work-orders-list-page.ts @@ -19,40 +19,56 @@ import { guardDialogClose } from "@/lib/dialog-guard"; import { useWorkOrdersListFilters } from "./use-work-orders-list-filters"; import { useWorkOrdersTableData } from "./use-work-orders-table-data"; -function useSlideOverState() { - const [row, setRow] = useState(null); - const [edit, setEdit] = useState(false); - const [tab, setTab] = useState("info"); - const [highlight, setHighlight] = useState(null); +function useSlideOverOpenState() { + const [slideOverRow, setSlideOverRow] = useState(null); + const [slideOverEdit, setSlideOverEdit] = useState(false); + const [slideOverTab, setSlideOverTab] = useState("info"); + const [slideOverHighlight, setSlideOverHighlight] = useState(null); // Bumps on every open() so the slide-over re-applies tab/highlight even when a second // notification targets the work order already on screen (same row id). - const [openRequestKey, setOpenRequestKey] = useState(0); + const [slideOverOpenRequestKey, setSlideOverOpenRequestKey] = useState(0); - const open = ( - next: WorkOrderTableRow, - nextEdit = false, - nextTab: SlideOverTab = "info", - nextHighlight: CommentHighlight | null = null, + const handleOpenSlideOver = ( + row: WorkOrderTableRow, + edit = false, + tab: SlideOverTab = "info", + highlight: CommentHighlight | null = null, ) => { - setRow(next); - setEdit(nextEdit); - setTab(nextTab); - setHighlight(nextHighlight); - setOpenRequestKey((key) => key + 1); + setSlideOverRow(row); + setSlideOverEdit(edit); + setSlideOverTab(tab); + setSlideOverHighlight(highlight); + setSlideOverOpenRequestKey((key) => key + 1); }; - const close = () => { - setRow(null); - setEdit(false); - setHighlight(null); + const handleCloseSlideOver = () => { + setSlideOverRow(null); + setSlideOverEdit(false); + setSlideOverHighlight(null); }; - return { row, edit, tab, highlight, openRequestKey, open, close }; + return { + slideOverRow, + slideOverEdit, + slideOverTab, + slideOverHighlight, + slideOverOpenRequestKey, + handleOpenSlideOver, + handleCloseSlideOver, + }; } export function useWorkOrdersListPage() { const [wizardOpen, setWizardOpen] = useState(false); - const slideOver = useSlideOverState(); + const { + slideOverRow, + slideOverEdit, + slideOverTab, + slideOverHighlight, + slideOverOpenRequestKey, + handleOpenSlideOver, + handleCloseSlideOver: closeSlideOverState, + } = useSlideOverOpenState(); const [confirmCancel, setConfirmCancel] = useState(null); const [confirmComplete, setConfirmComplete] = useState(null); const [docRow, setDocRow] = useState(null); @@ -86,7 +102,6 @@ export function useWorkOrdersListPage() { [vendors], ); - const slideOverRow = slideOver.row; const activeSlideOverRow = useMemo(() => { if (!slideOverRow) return null; return tableData.rows.find((row) => row.id === slideOverRow.id) ?? slideOverRow; @@ -108,7 +123,7 @@ export function useWorkOrdersListPage() { }, [upliftsRow, tableData.rows]); const handleCloseSlideOver = () => { - slideOver.close(); + closeSlideOverState(); tableData.setSelectedId(null); }; @@ -156,10 +171,10 @@ export function useWorkOrdersListPage() { return { wizardOpen, setWizardOpen, - slideOverEdit: slideOver.edit, - slideOverTab: slideOver.tab, - slideOverHighlight: slideOver.highlight, - slideOverOpenRequestKey: slideOver.openRequestKey, + slideOverEdit, + slideOverTab, + slideOverHighlight, + slideOverOpenRequestKey, confirmCancel, setConfirmCancel, confirmComplete, @@ -185,7 +200,7 @@ export function useWorkOrdersListPage() { activeDocRow, activeMediaRow, activeUpliftsRow, - handleOpenSlideOver: slideOver.open, + handleOpenSlideOver, handleCloseSlideOver, handleConfirmCancel, handleConfirmComplete,