fix(work-orders): resolve CompDoc/dev merge type and maintainability issues

This commit is contained in:
Arthur Bassi 2026-07-30 11:09:05 -03:00
parent e3aa69e577
commit b2d44944ab
8 changed files with 157 additions and 76 deletions

View file

@ -0,0 +1,91 @@
import { CompDocDialog } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog";
import { MediaUploadModal } from "@/app/(protected)/workorders/_components/media/media-upload-modal";
import { WorkOrdersAdvancedSearchSheet } from "@/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet";
import { NewWoWizard } from "@/app/(protected)/workorders/_components/wizard/new-wo-wizard";
import type { CompDocData, CompDocSaveStatus } from "@/domain/work-orders/types/completion-doc";
import type { WorkOrderAdvancedFilters } from "@/domain/work-orders/types/work-order-advanced-filters";
import type { LocationOption } from "@/domain/work-orders/types/work-order-common";
import type { WorkOrderUser } from "@/domain/work-orders/types/work-order-list";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
type WorkOrdersListOverlaysProps = {
wizardOpen: boolean;
onWizardOpenChange: (open: boolean) => void;
users: WorkOrderUser[];
sites: LocationOption[];
pmTypes: string[];
vendorTechs: string[];
advOpen: boolean;
onAdvOpenChange: (open: boolean) => void;
advInitial: WorkOrderAdvancedFilters;
onApplyAdvanced: (next: WorkOrderAdvancedFilters) => void;
onClearAdvanced: () => void;
activeDocRow: WorkOrderTableRow | null;
onDocOpenChange: (open: boolean) => void;
completionDocs: Record<string, CompDocData>;
onSaveCompDoc: (data: CompDocData, status: CompDocSaveStatus) => void;
onDateCompletedChange: (date: string) => void;
activeMediaRow: WorkOrderTableRow | null;
onMediaOpenChange: (open: boolean) => void;
};
export function WorkOrdersListOverlays({
wizardOpen,
onWizardOpenChange,
users,
sites,
pmTypes,
vendorTechs,
advOpen,
onAdvOpenChange,
advInitial,
onApplyAdvanced,
onClearAdvanced,
activeDocRow,
onDocOpenChange,
completionDocs,
onSaveCompDoc,
onDateCompletedChange,
activeMediaRow,
onMediaOpenChange,
}: WorkOrdersListOverlaysProps) {
return (
<>
<WorkOrdersAdvancedSearchSheet
open={advOpen}
onOpenChange={onAdvOpenChange}
initial={advInitial}
onApply={onApplyAdvanced}
onClearAll={onClearAdvanced}
sites={sites}
users={users}
pmTypes={pmTypes}
vendorTechs={vendorTechs}
/>
<NewWoWizard open={wizardOpen} onOpenChange={onWizardOpenChange} users={users} />
{activeDocRow != null && (
<CompDocDialog
row={activeDocRow}
open={!!activeDocRow}
onOpenChange={onDocOpenChange}
data={completionDocs[String(activeDocRow.id)]}
// docStatus persists via board PATCH (Yes|No|NotNeeded); completedDate via
// EditWorkorder. patchField serializes same-id saves so they never race.
// Notes/generatedAt stay client-local (no content endpoint on this branch).
onSave={onSaveCompDoc}
onDateCompletedChange={onDateCompletedChange}
/>
)}
{activeMediaRow != null && (
<MediaUploadModal
row={activeMediaRow}
open={!!activeMediaRow}
onOpenChange={onMediaOpenChange}
/>
)}
</>
);
}

View file

@ -2,15 +2,12 @@ import { useMemo, useState } from "react";
import { Box } from "@mui/material";
import { useNavigate } from "react-router";
import { PageHeader } from "@/components/ui/page-header";
import { CompDocDialog } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog";
import { QueryErrorPanel } from "@/components/ui/query-error-panel";
import { MediaUploadModal } from "@/app/(protected)/workorders/_components/media/media-upload-modal";
import { WorkOrdersAdvancedSearchSheet } from "@/app/(protected)/workorders/_components/list/advanced-search/work-orders-advanced-search-sheet";
import { WorkOrdersAdvancedFiltersBanner } from "@/app/(protected)/workorders/_components/list/work-orders-advanced-filters-banner";
import { WorkOrdersFilterBar } from "@/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar";
import { WorkOrdersListHeader } from "@/app/(protected)/workorders/_components/list/work-orders-list-header";
import { WorkOrdersListOverlays } from "@/app/(protected)/workorders/_components/list/work-orders-list-overlays";
import { WoTable } from "@/app/(protected)/workorders/_components/list/table/wo-table";
import { NewWoWizard } from "@/app/(protected)/workorders/_components/wizard/new-wo-wizard";
import { useWorkOrdersListFilters } from "@/app/(protected)/workorders/_hooks/use-work-orders-list-filters";
import { useWorkOrdersTableData } from "@/app/(protected)/workorders/_hooks/use-work-orders-table-data";
import { useDropdownOptionsByCategory } from "@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category";
@ -25,6 +22,14 @@ import { useWorkOrderTableMutations } from "@/domain/work-orders/use-cases/use-w
import { hasCompletionDocService } from "@/domain/work-orders/utils/completion-doc-service-gate";
import { useAuthContext } from "@/providers/auth-context";
function resolveActiveRow(
selected: WorkOrderTableRow | null,
rows: WorkOrderTableRow[],
): WorkOrderTableRow | null {
if (!selected) return null;
return rows.find((row) => row.id === selected.id) ?? selected;
}
export default function WorkOrdersListPage() {
const navigate = useNavigate();
const { user } = useAuthContext();
@ -39,35 +44,26 @@ export default function WorkOrdersListPage() {
clearPatch: tableData.clearRowPatch,
getRow: (id) => tableData.rows.find((row) => String(row.id) === String(id)),
});
const { data: users = [], isLoading: usersLoading } = useWorkOrderUsers();
const { data: locations = [] } = useWorkOrderLocations();
const { data: vendors = [] } = useWorkOrderVendors();
const { data: problemOptions = [] } = useDropdownOptionsByCategory("Problem");
const pmTypes = useMemo(
() => problemOptions.map((option) => option.value).filter(Boolean),
[problemOptions],
);
const vendorTechs = useMemo(
() => [...new Set(vendors.map((vendor) => vendor.companyName).filter(Boolean))],
[vendors],
);
const totalCount = tableData.totalCount;
const scopeTotalCount = tableData.scopeTotalCount;
const activeDocRow = useMemo(() => {
if (!docRow) return null;
return tableData.rows.find((row) => row.id === docRow.id) ?? docRow;
}, [docRow, tableData.rows]);
const activeMediaRow = useMemo(() => {
if (!mediaRow) return null;
return tableData.rows.find((row) => row.id === mediaRow.id) ?? mediaRow;
}, [mediaRow, tableData.rows]);
const activeDocRow = useMemo(
() => resolveActiveRow(docRow, tableData.rows),
[docRow, tableData.rows],
);
const activeMediaRow = useMemo(
() => resolveActiveRow(mediaRow, tableData.rows),
[mediaRow, tableData.rows],
);
return (
<Box className="flex h-full flex-col overflow-hidden p-2.5">
<PageHeader
@ -96,14 +92,14 @@ export default function WorkOrdersListPage() {
users={users}
usersLoading={usersLoading}
currentUserId={user?.id}
filteredCount={totalCount}
scopeTotalCount={scopeTotalCount}
filteredCount={tableData.totalCount}
scopeTotalCount={tableData.scopeTotalCount}
/>
{filters.advApplied != null && (
<WorkOrdersAdvancedFiltersBanner
applied={filters.advApplied}
totalCount={totalCount}
totalCount={tableData.totalCount}
sites={locations}
users={users}
onClearChip={filters.clearAdvancedFilterChip}
@ -146,48 +142,37 @@ export default function WorkOrdersListPage() {
onPatch={patchField}
/>
<WorkOrdersAdvancedSearchSheet
open={filters.advOpen}
onOpenChange={filters.setAdvOpen}
initial={filters.advInitial}
onApply={filters.applyAdvancedFilters}
onClearAll={filters.clearAdvancedFilters}
sites={locations}
<WorkOrdersListOverlays
wizardOpen={wizardOpen}
onWizardOpenChange={setWizardOpen}
users={users}
sites={locations}
pmTypes={pmTypes}
vendorTechs={vendorTechs}
advOpen={filters.advOpen}
onAdvOpenChange={filters.setAdvOpen}
advInitial={filters.advInitial}
onApplyAdvanced={filters.applyAdvancedFilters}
onClearAdvanced={filters.clearAdvancedFilters}
activeDocRow={activeDocRow}
onDocOpenChange={(open) => {
if (!open) setDocRow(null);
}}
completionDocs={completionDocs}
onSaveCompDoc={(data, status) => {
if (!activeDocRow) return;
setCompletionDocs((prev) => ({ ...prev, [String(activeDocRow.id)]: data }));
patchField(activeDocRow.id, { docStatus: status });
}}
onDateCompletedChange={(date) => {
if (!activeDocRow) return;
patchField(activeDocRow.id, { completedDate: date });
}}
activeMediaRow={activeMediaRow}
onMediaOpenChange={(open) => {
if (!open) setMediaRow(null);
}}
/>
<NewWoWizard open={wizardOpen} onOpenChange={setWizardOpen} users={users} />
{activeDocRow != null && (
<CompDocDialog
row={activeDocRow}
open={!!activeDocRow}
onOpenChange={(open) => {
if (!open) setDocRow(null);
}}
data={completionDocs[String(activeDocRow.id)]}
// docStatus persists via board PATCH (Yes|No|NotNeeded); completedDate via
// EditWorkorder. patchField serializes same-id saves so they never race.
// Notes/generatedAt stay client-local (no content endpoint on this branch).
onSave={(data, status) => {
setCompletionDocs((prev) => ({ ...prev, [String(activeDocRow.id)]: data }));
patchField(activeDocRow.id, { docStatus: status });
}}
onDateCompletedChange={(date) => patchField(activeDocRow.id, { completedDate: date })}
/>
)}
{activeMediaRow != null && (
<MediaUploadModal
row={activeMediaRow}
open={!!activeMediaRow}
onOpenChange={(open) => {
if (!open) setMediaRow(null);
}}
/>
)}
</Box>
);
}

View file

@ -237,6 +237,7 @@ export function mapWorkOrderTableRow(raw: unknown): WorkOrderTableRow {
dispatcherId,
dispatcherName,
scheduledOn,
completedDate: toDateInputValue(readString(item, "completedDate", "CompletedDate")),
targetWeek: toDateInputValue(readString(item, "targetWeek", "TargetWeek")),
dayGroup,
dayLabel,

View file

@ -47,17 +47,6 @@ export function useWorkOrderMedia(
});
}
export function useWorkOrderBoardDetail(
id: string | number | undefined,
enabled = true,
): UseQueryResult<WorkOrderBoardDetail, Error> {
return useQuery({
queryKey: queryKeys.workOrders.boardDetail(id ?? ""),
queryFn: () => workOrdersApi.getBoardDetail(requireQueryParam(id, "Work order id")),
enabled: enabled && id !== undefined && id !== "",
});
}
export function useWorkOrderLocations(): UseQueryResult<LocationOption[], Error> {
return useQuery({
queryKey: queryKeys.locations.dropdown(),

View file

@ -30,6 +30,9 @@ function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
location: "Seattle",
locationId: "mock-loc-1",
pm: "HVAC",
serviceNotes: "",
extraServices: [],
vendorId: "",
company: "Vendor Co",
tech: "Tech One",
techPhone: "",
@ -51,6 +54,10 @@ function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
mediaCount: 0,
title: "PM — SEA1",
priority: "Sev 3",
primaryDispatchId: null,
rowVersion: "",
dispatchRowVersion: "",
flagColor: null,
...overrides,
};
}

View file

@ -49,6 +49,9 @@ function baseRow(): WorkOrderTableRow {
location: "Seattle",
locationId: "loc-1",
pm: "",
serviceNotes: "",
extraServices: [],
vendorId: "",
company: "",
tech: "",
techPhone: "",
@ -70,6 +73,10 @@ function baseRow(): WorkOrderTableRow {
mediaCount: 1,
title: "Emergency",
priority: "Sev 1",
primaryDispatchId: null,
rowVersion: "",
dispatchRowVersion: "",
flagColor: null,
};
}

View file

@ -144,9 +144,9 @@ describe("mapWorkOrderFormToUpdateFormData", () => {
docStatus: "NN" as const,
};
const payload = mapWorkOrderFormToUpdatePayload(1, values);
const formData = mapWorkOrderFormToUpdateFormData(1, values);
expect(payload.CompletedDate).toBe("2026-07-20");
expect(payload).not.toHaveProperty("DocStatus");
expect(formData.get("CompletedDate")).toBe("2026-07-20");
expect(formData.has("DocStatus")).toBe(false);
});
});

View file

@ -15,6 +15,7 @@ function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow
dispatcherId: "10",
dispatcherName: "Alex",
scheduledOn: "2026-07-16",
completedDate: "",
targetWeek: "",
dayGroup: "2026-07-16",
dayLabel: "Thu",