mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
266 lines
9.8 KiB
TypeScript
266 lines
9.8 KiB
TypeScript
import { useMemo, useState } from "react";
|
|
import {
|
|
Box,
|
|
Dialog,
|
|
DialogActions,
|
|
DialogContent,
|
|
DialogContentText,
|
|
DialogTitle,
|
|
} from "@mui/material";
|
|
import { PageHeader } from "@/components/ui/page-header";
|
|
import { Button } from "@/components/ui/button";
|
|
import { dialogBlurBackdropSlotProps } from "@/components/ui/dialog-blur-backdrop";
|
|
import { QueryErrorPanel } from "@/components/ui/query-error-panel";
|
|
import { CompDocDialog } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-dialog";
|
|
import { MediaUploadModal } from "@/app/(protected)/workorders/_components/media/media-upload-modal";
|
|
import { WorkOrderSlideOver } from "@/app/(protected)/workorders/_components/detail/work-order-slide-over";
|
|
import { NewWoWizard } from "@/app/(protected)/workorders/_components/wizard/new-wo-wizard";
|
|
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 { WoTable } from "@/app/(protected)/workorders/_components/list/table/wo-table";
|
|
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";
|
|
import {
|
|
useWorkOrderLocations,
|
|
useWorkOrderVendors,
|
|
} from "@/domain/work-orders/use-cases/use-work-order-detail";
|
|
import { useWorkOrderUsers } from "@/domain/work-orders/use-cases/use-work-orders-list";
|
|
import type { CompDocData } from "@/domain/work-orders/types/completion-doc";
|
|
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
|
import { useWorkOrderTableMutations } from "@/domain/work-orders/use-cases/use-work-order-table-mutations";
|
|
import { guardDialogClose } from "@/lib/dialog-guard";
|
|
import { useAuthContext } from "@/providers/auth-context";
|
|
|
|
export default function WorkOrdersListPage() {
|
|
const { user } = useAuthContext();
|
|
const [wizardOpen, setWizardOpen] = useState(false);
|
|
const [slideOverRow, setSlideOverRow] = useState<WorkOrderTableRow | null>(null);
|
|
const [slideOverEdit, setSlideOverEdit] = useState(false);
|
|
const [confirmCancel, setConfirmCancel] = useState<WorkOrderTableRow | null>(null);
|
|
const [docRow, setDocRow] = useState<WorkOrderTableRow | null>(null);
|
|
const [mediaRow, setMediaRow] = useState<WorkOrderTableRow | null>(null);
|
|
const [completionDocs, setCompletionDocs] = useState<Record<string, CompDocData>>({});
|
|
const filters = useWorkOrdersListFilters();
|
|
const tableData = useWorkOrdersTableData(filters);
|
|
const { patchField, isPending: isPatchPending } = useWorkOrderTableMutations(
|
|
tableData.patchRow,
|
|
(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 activeSlideOverRow = useMemo(() => {
|
|
if (!slideOverRow) return null;
|
|
return tableData.rows.find((row) => row.id === slideOverRow.id) ?? slideOverRow;
|
|
}, [slideOverRow, tableData.rows]);
|
|
|
|
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 handleOpenSlideOver = (row: WorkOrderTableRow, edit = false) => {
|
|
setSlideOverRow(row);
|
|
setSlideOverEdit(edit);
|
|
};
|
|
|
|
const handleCloseSlideOver = () => {
|
|
setSlideOverRow(null);
|
|
setSlideOverEdit(false);
|
|
tableData.setSelectedId(null);
|
|
};
|
|
|
|
const handleConfirmCancel = () => {
|
|
if (!confirmCancel) return;
|
|
patchField(confirmCancel.id, { status: "Canceled" });
|
|
setConfirmCancel(null);
|
|
handleCloseSlideOver();
|
|
};
|
|
|
|
const cancelDialogClose = guardDialogClose(isPatchPending, () => setConfirmCancel(null));
|
|
|
|
return (
|
|
<Box className="flex h-full flex-col overflow-hidden p-2.5">
|
|
<PageHeader
|
|
surface
|
|
eyebrow="OPERATIONS"
|
|
title="Work Orders"
|
|
actions={
|
|
<WorkOrdersListHeader
|
|
search={filters.search}
|
|
onSearchChange={filters.handleSearchChange}
|
|
onOpenAdvancedFilters={() => filters.setAdvOpen(true)}
|
|
onNewWorkOrder={() => setWizardOpen(true)}
|
|
activeFilterCount={filters.activeFilterCount}
|
|
isFetching={tableData.isFetching && !tableData.isLoading}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
<WorkOrdersFilterBar
|
|
weekMonday={filters.weekMonday}
|
|
onWeekChange={filters.setWeekMonday}
|
|
dispatcherIds={filters.dispatcherIds}
|
|
onDispatcherChange={filters.setDispatcherIds}
|
|
typeFilter={filters.typeFilter}
|
|
onTypeChange={filters.setTypeFilter}
|
|
users={users}
|
|
usersLoading={usersLoading}
|
|
currentUserId={user?.id}
|
|
filteredCount={totalCount}
|
|
scopeTotalCount={scopeTotalCount}
|
|
/>
|
|
|
|
{filters.advApplied && (
|
|
<WorkOrdersAdvancedFiltersBanner
|
|
applied={filters.advApplied}
|
|
totalCount={totalCount}
|
|
sites={locations}
|
|
users={users}
|
|
onClearChip={filters.clearAdvancedFilterChip}
|
|
onClearAll={filters.clearAdvancedFilters}
|
|
/>
|
|
)}
|
|
|
|
{tableData.error && (
|
|
<QueryErrorPanel
|
|
className="mb-2 shrink-0"
|
|
compact
|
|
message={
|
|
tableData.error instanceof Error
|
|
? tableData.error.message
|
|
: "Failed to load work orders."
|
|
}
|
|
onRetry={() => void tableData.refetch()}
|
|
/>
|
|
)}
|
|
|
|
<WoTable
|
|
tableData={tableData}
|
|
users={users}
|
|
sites={locations}
|
|
vendors={vendors}
|
|
pmTypes={pmTypes}
|
|
weekMonday={filters.weekMonday}
|
|
onWeekChange={filters.setWeekMonday}
|
|
onOpenSlideOver={handleOpenSlideOver}
|
|
onOpenCompDoc={setDocRow}
|
|
onOpenMedia={setMediaRow}
|
|
onClearSearch={() => filters.handleSearchChange("")}
|
|
onOpenAdvancedFilters={() => filters.setAdvOpen(true)}
|
|
onPatch={patchField}
|
|
/>
|
|
|
|
<NewWoWizard open={wizardOpen} onOpenChange={setWizardOpen} users={users} />
|
|
|
|
<WorkOrdersAdvancedSearchSheet
|
|
open={filters.advOpen}
|
|
onOpenChange={filters.setAdvOpen}
|
|
initial={filters.advInitial}
|
|
onApply={filters.applyAdvancedFilters}
|
|
onClearAll={filters.clearAdvancedFilters}
|
|
sites={locations}
|
|
users={users}
|
|
pmTypes={pmTypes}
|
|
vendorTechs={vendorTechs}
|
|
/>
|
|
|
|
<WorkOrderSlideOver
|
|
row={activeSlideOverRow}
|
|
editMode={slideOverEdit}
|
|
users={users}
|
|
sites={locations}
|
|
vendors={vendors}
|
|
pmTypes={pmTypes}
|
|
allOrders={tableData.rows}
|
|
onSave={(id, patch) => patchField(id, patch)}
|
|
onOpenCompletionDoc={setDocRow}
|
|
onCancelWO={setConfirmCancel}
|
|
onDuplicateFound={(existing) => handleOpenSlideOver(existing, false)}
|
|
saving={isPatchPending}
|
|
onClose={handleCloseSlideOver}
|
|
/>
|
|
|
|
<Dialog
|
|
open={Boolean(confirmCancel)}
|
|
onClose={cancelDialogClose}
|
|
slotProps={dialogBlurBackdropSlotProps}
|
|
>
|
|
<DialogTitle>Cancel work order?</DialogTitle>
|
|
<DialogContent>
|
|
<DialogContentText>
|
|
Cancel WO #{confirmCancel?.woNumber}? The record will be kept with status Canceled.
|
|
</DialogContentText>
|
|
</DialogContent>
|
|
<DialogActions>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setConfirmCancel(null)}
|
|
disabled={isPatchPending}
|
|
>
|
|
Keep WO
|
|
</Button>
|
|
<Button
|
|
variant="destructive"
|
|
onClick={handleConfirmCancel}
|
|
loading={isPatchPending}
|
|
loadingText="Canceling…"
|
|
>
|
|
Cancel WO
|
|
</Button>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
{activeMediaRow && (
|
|
<MediaUploadModal
|
|
row={activeMediaRow}
|
|
open={!!activeMediaRow}
|
|
onOpenChange={(open) => {
|
|
if (!open) setMediaRow(null);
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{activeDocRow && (
|
|
<CompDocDialog
|
|
row={activeDocRow}
|
|
open={!!activeDocRow}
|
|
onOpenChange={(open) => {
|
|
if (!open) setDocRow(null);
|
|
}}
|
|
data={completionDocs[String(activeDocRow.id)]}
|
|
onSave={(data, status) => {
|
|
setCompletionDocs((prev) => ({ ...prev, [String(activeDocRow.id)]: data }));
|
|
patchField(activeDocRow.id, { docStatus: status });
|
|
}}
|
|
onStatusChange={(status) => patchField(activeDocRow.id, { status })}
|
|
onDateCompletedChange={(date) => patchField(activeDocRow.id, { scheduledOn: date })}
|
|
/>
|
|
)}
|
|
</Box>
|
|
);
|
|
}
|