diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx new file mode 100644 index 00000000..59de1a9f --- /dev/null +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx @@ -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) => void; +}; + +export function WoTableRowCells({ + row, + allRows, + users, + sites, + sitePlace, + search, + forceScheduleHighlight, + mutationCellStyle, + handlers, + onPatchRow, +}: WoTableRowCellsProps) { + return ( + <> + e.stopPropagation()} + > + + onPatchRow({ + site: p.site, + location: p.location, + locationId: p.locationId, + pocName: p.pocName, + pocPhone: p.pocPhone, + pocNotes: p.pocNotes, + }) + } + /> + + + onPatchRow({ woNumber: v })} + onDuplicateFound={handlers.onDuplicateFound} + /> + + e.stopPropagation()} + > + onPatchRow({ type: t })} /> + + e.stopPropagation()} + > + + handlers.onPatch(row.id, { dispatcherId: id, dispatcherName: name }) + } + /> + + e.stopPropagation()} + > + handlers.onScheduleDate(row.id, date)} + onSaveWeek={(week) => handlers.onScheduleWeek(row.id, week)} + /> + + e.stopPropagation()} + > + onPatchRow({ dueDate: date })} /> + + + ); +} diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx new file mode 100644 index 00000000..9167dec7 --- /dev/null +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx @@ -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) => void; +}; + +export function WoTableRowServiceCells({ + row, + vendors, + pmTypes, + search, + mutationCellStyle, + handlers, + onPatchRow, +}: WoTableRowServiceCellsProps) { + return ( + <> + e.stopPropagation()} + > + onPatchRow({ pm })} + onSaveDetails={(d) => + onPatchRow({ + pm: d.pm, + extraServices: d.extraServices, + serviceNotes: d.serviceNotes, + }) + } + /> + + e.stopPropagation()} + > + + onPatchRow({ + vendorId: p.vendorId, + company: p.company, + tech: p.tech, + techPhone: p.techPhone, + }) + } + /> + + e.stopPropagation()} + > + onPatchRow({ apptTime: v })} /> + + e.stopPropagation()} + > + onPatchRow({ status })} + onOpenSlideOver={() => handlers.onOpenSlideOver(row)} + onPastDueAttempt={() => handlers.onPastDueAttempt?.(row.id)} + /> + + e.stopPropagation()}> + handlers.onOpenCompDoc!(row) : undefined} + onOpenMedia={() => handlers.onOpenMedia(row)} + /> + + + ); +} diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-style.ts b/src/app/(protected)/workorders/_components/list/table/wo-table-row-style.ts new file mode 100644 index 00000000..4e2be089 --- /dev/null +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-style.ts @@ -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", + }; +} diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row.tsx index 388f583b..b3ba685f 100644 --- a/src/app/(protected)/workorders/_components/list/table/wo-table-row.tsx +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row.tsx @@ -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) => 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) => { - if (isCanceled) return; - const merged = { ...row, ...patch }; - const auto = maybeAutoSchedulePatch(merged); - handlers.onPatch(row.id, { ...patch, ...auto }); - }; - const patchRow = (patch: Partial) => { 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 ( { if (!isSelected && !isFlashing) { (e.currentTarget as HTMLTableRowElement).style.background = "var(--color-primary-tint)"; @@ -144,7 +119,11 @@ export function WoTableRow({ }} > e.stopPropagation()} > e.stopPropagation()} > handlers.setFlag(row.id, c)} - /> - - e.stopPropagation()} - > - - patchRow({ - site: p.site, - location: p.location, - locationId: p.locationId, - pocName: p.pocName, - pocPhone: p.pocPhone, - pocNotes: p.pocNotes, - }) - } - /> - - - patchRow({ woNumber: v })} - onDuplicateFound={handlers.onDuplicateFound} - /> - - e.stopPropagation()} - > - patchRow({ type: t })} /> - - e.stopPropagation()} - > - patchAndMaybeAuto({ dispatcherId: id, dispatcherName: name })} - /> - - e.stopPropagation()} - > - patchAndMaybeAuto(applySchedulePatch(date, false))} - onSaveWeek={(week) => - patchAndMaybeAuto({ - scheduledOn: "", - targetWeek: week, - scheduleWeekOnly: true, - dayGroup: "", - dayLabel: "", - }) - } - /> - - e.stopPropagation()} - > - patchRow({ dueDate: date })} /> - - e.stopPropagation()} - > - patchRow({ pm })} /> - - e.stopPropagation()} - > - - patchRow({ - company: p.company, - tech: p.tech, - techPhone: p.techPhone, - }) - } - /> - - e.stopPropagation()} - > - patchRow({ apptTime: v })} /> - - e.stopPropagation()} - > - patchRow({ status })} - onOpenSlideOver={() => handlers.onOpenSlideOver(row)} - onPastDueAttempt={() => handlers.onPastDueAttempt?.(row.id)} - /> - - e.stopPropagation()}> - handlers.onOpenCompDoc!(row) : undefined} - onOpenMedia={() => handlers.onOpenMedia(row)} + onChange={(c) => handlers.onPatch(row.id, { flagColor: c })} /> + + e.stopPropagation()}> handlers.onView(row)}