refactor(work-orders): extract WoTableRow under maintainability limits

This commit is contained in:
Arthur Bassi 2026-07-30 10:40:55 -03:00
parent 3004d039aa
commit d6179fcec5
4 changed files with 319 additions and 190 deletions

View file

@ -0,0 +1,123 @@
import type { CSSProperties } from "react";
import type { LocationOption, WorkOrderUser } from "@/domain/work-orders/types/work-order";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { AssignedToCell } from "./cells/assigned-to-cell";
import { DateCell } from "./cells/date-cell";
import { EditableWONumberCell } from "./cells/editable-wo-number-cell";
import { SiteCell } from "./cells/site-cell";
import { TypeCell } from "./cells/type-cell";
import type { WoTableRowHandlers } from "./wo-table-row";
type WoTableRowCellsProps = {
row: WorkOrderTableRow;
allRows: WorkOrderTableRow[];
users: WorkOrderUser[];
sites: LocationOption[];
sitePlace?: { city: string; state: string };
search?: string;
forceScheduleHighlight?: boolean;
mutationCellStyle?: CSSProperties;
handlers: WoTableRowHandlers;
onPatchRow: (patch: Partial<WorkOrderTableRow>) => void;
};
export function WoTableRowCells({
row,
allRows,
users,
sites,
sitePlace,
search,
forceScheduleHighlight,
mutationCellStyle,
handlers,
onPatchRow,
}: WoTableRowCellsProps) {
return (
<>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<SiteCell
value={row.site}
location={row.location}
locationId={row.locationId}
city={sitePlace?.city}
state={sitePlace?.state}
pocName={row.pocName}
pocPhone={row.pocPhone}
pocNotes={row.pocNotes}
sites={sites}
q={search}
onSave={(p) =>
onPatchRow({
site: p.site,
location: p.location,
locationId: p.locationId,
pocName: p.pocName,
pocPhone: p.pocPhone,
pocNotes: p.pocNotes,
})
}
/>
</td>
<td style={{ padding: "0 14px", whiteSpace: "nowrap", ...mutationCellStyle }}>
<EditableWONumberCell
value={row.woNumber}
rescheduleCount={row.rescheduleCount}
carriedOver={row.carriedOver}
q={search}
currentId={row.id}
allOrders={allRows}
onSave={(v) => onPatchRow({ woNumber: v })}
onDuplicateFound={handlers.onDuplicateFound}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<TypeCell value={row.type} q={search} onSave={(t) => onPatchRow({ type: t })} />
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<AssignedToCell
dispatcherId={row.dispatcherId}
users={users}
highlight={!row.dispatcherId}
onSave={(id, name) =>
handlers.onPatch(row.id, { dispatcherId: id, dispatcherName: name })
}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<DateCell
value={row.scheduledOn}
q={search}
highlight={forceScheduleHighlight || row.isPastDue}
allowWeekMode
targetWeek={row.targetWeek}
onSave={(date) => handlers.onScheduleDate(row.id, date)}
onSaveWeek={(week) => handlers.onScheduleWeek(row.id, week)}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<DateCell value={row.dueDate} q={search} onSave={(date) => onPatchRow({ dueDate: date })} />
</td>
</>
);
}

View file

@ -0,0 +1,106 @@
import type { CSSProperties } from "react";
import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { ApptTimeCell } from "./cells/appt-time-cell";
import { CompDocCell } from "./cells/comp-doc-cell";
import { PMTypeCell } from "./cells/pm-type-cell";
import { StatusCell } from "./cells/status-cell";
import { VendorCell } from "./cells/vendor-cell";
import type { WoTableRowHandlers } from "./wo-table-row";
type WoTableRowServiceCellsProps = {
row: WorkOrderTableRow;
vendors: VendorDropdownItem[];
pmTypes: string[];
search?: string;
mutationCellStyle?: CSSProperties;
handlers: WoTableRowHandlers;
onPatchRow: (patch: Partial<WorkOrderTableRow>) => void;
};
export function WoTableRowServiceCells({
row,
vendors,
pmTypes,
search,
mutationCellStyle,
handlers,
onPatchRow,
}: WoTableRowServiceCellsProps) {
return (
<>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<PMTypeCell
value={row.pm}
pmTypes={pmTypes}
q={search}
serviceNotes={row.serviceNotes}
extraServices={row.extraServices}
woNumber={row.woNumber}
onSave={(pm) => onPatchRow({ pm })}
onSaveDetails={(d) =>
onPatchRow({
pm: d.pm,
extraServices: d.extraServices,
serviceNotes: d.serviceNotes,
})
}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<VendorCell
company={row.company}
tech={row.tech}
techPhone={row.techPhone}
vendors={vendors}
q={search}
onSave={(p) =>
onPatchRow({
vendorId: p.vendorId,
company: p.company,
tech: p.tech,
techPhone: p.techPhone,
})
}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<ApptTimeCell apptTime={row.apptTime} onSave={(v) => onPatchRow({ apptTime: v })} />
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<StatusCell
wo={row}
onChangeStatus={(status) => onPatchRow({ status })}
onOpenSlideOver={() => handlers.onOpenSlideOver(row)}
onPastDueAttempt={() => handlers.onPastDueAttempt?.(row.id)}
/>
</td>
<td style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}>
<CompDocCell
status={row.docStatus}
woType={row.type}
pm={row.pm}
mediaCount={row.mediaCount}
onOpen={handlers.onOpenCompDoc ? () => handlers.onOpenCompDoc!(row) : undefined}
onOpenMedia={() => handlers.onOpenMedia(row)}
/>
</td>
</>
);
}

View file

@ -0,0 +1,43 @@
import type { CSSProperties } from "react";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { WO_TABLE_ROW_HEIGHT } from "./wo-table-cols";
export const MUTATION_CELL_STYLE: CSSProperties = { pointerEvents: "none" };
export function resolveFirstCellShadow(row: WorkOrderTableRow): string {
if (row.type === "Emergency") {
return "inset 3px 0 0 var(--destructive)";
}
if (row.isPastDue) {
return "inset 3px 0 0 var(--warning)";
}
return "none";
}
type WoTableRowStyleInput = {
isDragging: boolean;
isDropTarget: boolean;
isFlashing: boolean;
isSelected: boolean;
isCanceled: boolean;
};
export function resolveWoTableRowStyle({
isDragging,
isDropTarget,
isFlashing,
isSelected,
isCanceled,
}: WoTableRowStyleInput): CSSProperties {
return {
height: WO_TABLE_ROW_HEIGHT,
borderBottom: "1px solid var(--border)",
borderTop: isDropTarget ? "2px solid var(--color-primary)" : undefined,
opacity: isCanceled ? 0.45 : isDragging ? 0.4 : 1,
background: isFlashing || isSelected ? "var(--color-primary-tint)" : "var(--card)",
outline: isFlashing ? "2px solid var(--color-primary)" : "none",
outlineOffset: isFlashing ? "-2px" : undefined,
borderRadius: isFlashing ? 4 : undefined,
transition: "background 1.8s ease-out, outline 1.8s ease-out",
};
}

View file

@ -6,23 +6,15 @@ import type {
} from "@/domain/work-orders/types/work-order";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import type { DragVizState } from "@/app/(protected)/workorders/_hooks/use-work-orders-table-data";
import {
applySchedulePatch,
maybeAutoSchedulePatch,
} from "@/domain/work-orders/use-cases/use-work-order-table-mutations";
import { AssignedToCell } from "./cells/assigned-to-cell";
import { ApptTimeCell } from "./cells/appt-time-cell";
import { CompDocCell } from "./cells/comp-doc-cell";
import { DateCell } from "./cells/date-cell";
import { EditableWONumberCell } from "./cells/editable-wo-number-cell";
import { FlagCell } from "./cells/flag-cell";
import { PMTypeCell } from "./cells/pm-type-cell";
import { SiteCell } from "./cells/site-cell";
import { StatusCell } from "./cells/status-cell";
import { TypeCell } from "./cells/type-cell";
import { VendorCell } from "./cells/vendor-cell";
import { WO_TABLE_ROW_HEIGHT } from "./wo-table-cols";
import { WoTableRowActions } from "./wo-table-row-actions";
import { WoTableRowCells } from "./wo-table-row-cells";
import { WoTableRowServiceCells } from "./wo-table-row-service-cells";
import {
MUTATION_CELL_STYLE,
resolveFirstCellShadow,
resolveWoTableRowStyle,
} from "./wo-table-row-style";
export type WoTableRowHandlers = {
onRowClick: (row: WorkOrderTableRow) => void;
@ -31,6 +23,8 @@ export type WoTableRowHandlers = {
onOpenCompDoc?: (row: WorkOrderTableRow) => void;
onOpenMedia: (row: WorkOrderTableRow) => void;
onPatch: (id: string | number, patch: Partial<WorkOrderTableRow>) => void;
onScheduleDate: (id: string | number, date: string) => void;
onScheduleWeek: (id: string | number, week: string) => void;
onDuplicateFound?: (existing: WorkOrderTableRow) => void;
onOpenSlideOver: (row: WorkOrderTableRow) => void;
onPastDueAttempt?: (id: string | number) => void;
@ -39,7 +33,6 @@ export type WoTableRowHandlers = {
onDragOver: (id: string | number) => void;
onDrop: () => void;
onDragEnd: () => void;
setFlag: (id: string | number, color: string | null) => void;
};
type WoTableRowProps = {
@ -47,11 +40,11 @@ type WoTableRowProps = {
allRows: WorkOrderTableRow[];
users: WorkOrderUser[];
sites: LocationOption[];
sitePlace?: { city: string; state: string };
vendors: VendorDropdownItem[];
pmTypes: string[];
search?: string;
forceScheduleHighlight?: boolean;
flagColor?: string;
isSelected: boolean;
isFlashing: boolean;
dragViz: DragVizState;
@ -63,11 +56,11 @@ export function WoTableRow({
allRows,
users,
sites,
sitePlace,
vendors,
pmTypes,
search,
forceScheduleHighlight,
flagColor,
isSelected,
isFlashing,
dragViz,
@ -77,27 +70,13 @@ export function WoTableRow({
const isDropTarget = dragViz.overId === row.id && dragViz.dragId !== row.id;
const isCanceled = (row.status as string) === "Canceled";
let firstCellShadow = "none";
if (row.type === "Emergency") {
firstCellShadow = "inset 3px 0 0 var(--destructive)";
} else if (row.isPastDue) {
firstCellShadow = "inset 3px 0 0 var(--warning)";
}
const patchAndMaybeAuto = (patch: Partial<WorkOrderTableRow>) => {
if (isCanceled) return;
const merged = { ...row, ...patch };
const auto = maybeAutoSchedulePatch(merged);
handlers.onPatch(row.id, { ...patch, ...auto });
};
const patchRow = (patch: Partial<WorkOrderTableRow>) => {
if (isCanceled) return;
handlers.onPatch(row.id, patch);
};
/** Block mutation controls on canceled rows while keeping row/View clickable. */
const mutationCellStyle = isCanceled ? ({ pointerEvents: "none" } as const) : undefined;
const mutationCellStyle = isCanceled ? MUTATION_CELL_STYLE : undefined;
return (
<tr
@ -121,17 +100,13 @@ export function WoTableRow({
}}
onDragEnd={handlers.onDragEnd}
className="group/row cursor-pointer transition-colors"
style={{
height: WO_TABLE_ROW_HEIGHT,
borderBottom: "1px solid var(--border)",
borderTop: isDropTarget ? "2px solid var(--color-primary)" : undefined,
opacity: isCanceled ? 0.45 : isDragging ? 0.4 : 1,
background: isFlashing || isSelected ? "var(--color-primary-tint)" : "var(--card)",
outline: isFlashing ? "2px solid var(--color-primary)" : "none",
outlineOffset: isFlashing ? "-2px" : undefined,
borderRadius: isFlashing ? 4 : undefined,
transition: "background 1.8s ease-out, outline 1.8s ease-out",
}}
style={resolveWoTableRowStyle({
isDragging,
isDropTarget,
isFlashing,
isSelected,
isCanceled,
})}
onMouseEnter={(e) => {
if (!isSelected && !isFlashing) {
(e.currentTarget as HTMLTableRowElement).style.background = "var(--color-primary-tint)";
@ -144,7 +119,11 @@ export function WoTableRow({
}}
>
<td
style={{ padding: "0 0 0 6px", boxShadow: firstCellShadow, ...mutationCellStyle }}
style={{
padding: "0 0 0 6px",
boxShadow: resolveFirstCellShadow(row),
...mutationCellStyle,
}}
onClick={(e) => e.stopPropagation()}
>
<span
@ -162,154 +141,32 @@ export function WoTableRow({
onClick={(e) => e.stopPropagation()}
>
<FlagCell
color={flagColor}
color={row.flagColor}
disabled={isCanceled}
onChange={(c) => handlers.setFlag(row.id, c)}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<SiteCell
value={row.site}
location={row.location}
locationId={row.locationId}
pocName={row.pocName}
pocPhone={row.pocPhone}
pocNotes={row.pocNotes}
sites={sites}
q={search}
onSave={(p) =>
patchRow({
site: p.site,
location: p.location,
locationId: p.locationId,
pocName: p.pocName,
pocPhone: p.pocPhone,
pocNotes: p.pocNotes,
})
}
/>
</td>
<td style={{ padding: "0 14px", whiteSpace: "nowrap", ...mutationCellStyle }}>
<EditableWONumberCell
value={row.woNumber}
rescheduleCount={row.rescheduleCount}
carriedOver={row.carriedOver}
q={search}
currentId={row.id}
allOrders={allRows}
onSave={(v) => patchRow({ woNumber: v })}
onDuplicateFound={handlers.onDuplicateFound}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<TypeCell value={row.type} q={search} onSave={(t) => patchRow({ type: t })} />
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<AssignedToCell
dispatcherId={row.dispatcherId}
users={users}
highlight={!row.dispatcherId}
onSave={(id, name) => patchAndMaybeAuto({ dispatcherId: id, dispatcherName: name })}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<DateCell
value={row.scheduledOn}
q={search}
highlight={forceScheduleHighlight || row.isPastDue}
allowWeekMode
targetWeek={row.targetWeek}
onSave={(date) => patchAndMaybeAuto(applySchedulePatch(date, false))}
onSaveWeek={(week) =>
patchAndMaybeAuto({
scheduledOn: "",
targetWeek: week,
scheduleWeekOnly: true,
dayGroup: "",
dayLabel: "",
})
}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<DateCell value={row.dueDate} q={search} onSave={(date) => patchRow({ dueDate: date })} />
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<PMTypeCell value={row.pm} pmTypes={pmTypes} q={search} onSave={(pm) => patchRow({ pm })} />
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<VendorCell
company={row.company}
tech={row.tech}
techPhone={row.techPhone}
vendors={vendors}
q={search}
onSave={(p) =>
patchRow({
company: p.company,
tech: p.tech,
techPhone: p.techPhone,
})
}
/>
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<ApptTimeCell apptTime={row.apptTime} onSave={(v) => patchRow({ apptTime: v })} />
</td>
<td
className="group/cell"
style={{ padding: "0 14px", ...mutationCellStyle }}
onClick={(e) => e.stopPropagation()}
>
<StatusCell
wo={row}
onChangeStatus={(status) => patchRow({ status })}
onOpenSlideOver={() => handlers.onOpenSlideOver(row)}
onPastDueAttempt={() => handlers.onPastDueAttempt?.(row.id)}
/>
</td>
<td style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}>
<CompDocCell
status={row.docStatus}
woType={row.type}
pm={row.pm}
mediaCount={row.mediaCount}
onOpen={handlers.onOpenCompDoc ? () => handlers.onOpenCompDoc!(row) : undefined}
onOpenMedia={() => handlers.onOpenMedia(row)}
onChange={(c) => handlers.onPatch(row.id, { flagColor: c })}
/>
</td>
<WoTableRowCells
row={row}
allRows={allRows}
users={users}
sites={sites}
sitePlace={sitePlace}
search={search}
forceScheduleHighlight={forceScheduleHighlight}
mutationCellStyle={mutationCellStyle}
handlers={handlers}
onPatchRow={patchRow}
/>
<WoTableRowServiceCells
row={row}
vendors={vendors}
pmTypes={pmTypes}
search={search}
mutationCellStyle={mutationCellStyle}
handlers={handlers}
onPatchRow={patchRow}
/>
<td style={{ padding: "0 8px" }} onClick={(e) => e.stopPropagation()}>
<WoTableRowActions
onView={() => handlers.onView(row)}