shoc-frontend-new/src/app/(protected)/workorders/[id].tsx
Arthur Bassi d9d6334f57 fix(work-orders): lock completed status and legacy detail
Keep Flag, comments, and extra docs writable after completion, and hide the completed status menu.
2026-08-24 12:06:42 -03:00

155 lines
5.2 KiB
TypeScript

import { useParams } from "react-router";
import { useState } from "react";
import { Box } from "@mui/material";
import { QueryErrorPanel } from "@/components/ui/query-error-panel";
import { DetailPageSkeleton } from "./_components/loading/detail-page-skeleton";
import { DispatchCreateModal } from "./_components/dispatch-create-modal";
import { DispatchDetailModal } from "./_components/dispatch-detail-modal";
import { WorkOrderActivityPanel } from "./_components/view/work-order-activity-panel";
import { WorkOrderDispatchesAccordion } from "./_components/view/work-order-dispatches-accordion";
import { WorkOrderVendorEditorDialog } from "./_components/view/work-order-vendor-editor-dialog";
import { WorkOrderViewAccordions } from "./_components/view/work-order-view-accordions";
import { WorkOrderViewDetails } from "./_components/view/work-order-view-details";
import { WorkOrderViewHeader } from "./_components/view/work-order-view-header";
import { useWorkOrderViewPage } from "./_hooks/use-work-order-view-page";
import { WorkOrderCompleteDialog } from "./_components/list/work-order-complete-dialog";
import {
detailToClosabilityInput,
getClosabilityGaps,
isCompletionStatusLabel,
} from "@/domain/work-orders/utils/wo-closability";
import {
isWorkOrderCoreLocked,
isWorkOrderFullyLocked,
} from "@/domain/work-orders/utils/work-order-lock";
import type { WorkOrderStatus } from "@/domain/work-orders/types/work-order";
export default function WorkOrderViewPage() {
const { id } = useParams();
const workOrderId = id ?? "";
const view = useWorkOrderViewPage(workOrderId);
const [confirmComplete, setConfirmComplete] = useState(false);
if (view.isLoading && !view.wo) {
return <DetailPageSkeleton variant="view" />;
}
if (!view.wo) {
return (
<Box className="p-4">
<QueryErrorPanel
message={view.error instanceof Error ? view.error.message : "Work order not found."}
onRetry={() => void view.refetch()}
/>
</Box>
);
}
const wo = view.wo;
const isLocked = isWorkOrderCoreLocked(wo.status) || isWorkOrderFullyLocked(wo.status);
const handleStatusChange = (status: string) => {
if (isLocked) return;
const isCompletionStatus = isCompletionStatusLabel(status);
if (
isCompletionStatus &&
(!view.isClosabilityReady || getClosabilityGaps(detailToClosabilityInput(wo)).length > 0)
) {
return;
}
if (isCompletionStatus) {
setConfirmComplete(true);
return;
}
view.statusMutation.mutate(status as WorkOrderStatus);
};
return (
<Box className="flex flex-col gap-4 p-4">
<WorkOrderViewHeader
wo={wo}
workOrderId={workOrderId}
isClosabilityReady={view.isClosabilityReady}
statusAnchor={view.statusAnchor}
onStatusAnchorChange={view.setStatusAnchor}
onStatusChange={handleStatusChange}
onOpenDispatch={() => view.setDispatchOpen(true)}
readOnly={isLocked}
/>
<WorkOrderViewDetails
wo={view.wo}
users={view.users}
assignAnchor={view.assignAnchor}
onAssignAnchorChange={view.setAssignAnchor}
onAssign={(userId, userName) => {
if (isLocked) return;
view.assignmentMutation.mutate({ userId, userName });
}}
onUnassign={() => {
if (isLocked) return;
view.assignmentMutation.mutate({ userId: "", userName: "" });
}}
readOnly={isLocked}
/>
<WorkOrderViewAccordions wo={view.wo} />
<WorkOrderDispatchesAccordion
wo={view.wo}
vendorViewOnly={isLocked}
onOpenDispatchDetail={view.setDispatchDetailId}
onOpenVendorEditor={view.openVendorEditor}
/>
<WorkOrderActivityPanel
customerComments={view.customerComments}
internalComments={view.internalComments}
auditLog={view.auditLog}
mentionUsers={view.mentionUsers}
isSubmitting={view.commentMutation.isPending}
onSubmitComment={view.submitComment}
/>
{view.dispatchOpen && (
<DispatchCreateModal
workOrder={view.wo}
onClose={() => view.setDispatchOpen(false)}
onDispatched={() => void view.refetch()}
/>
)}
{view.dispatchDetailId != null && (
<DispatchDetailModal
dispatchId={view.dispatchDetailId}
workOrderId={workOrderId}
onClose={() => view.setDispatchDetailId(null)}
onUpdated={() => void view.refetch()}
/>
)}
<WorkOrderVendorEditorDialog
editor={view.vendorEditor}
loading={view.vendorEditorLoading}
error={view.vendorEditorError}
isSaving={view.vendorMutation.isPending}
viewOnly={isLocked}
onClose={() => {
if (!view.vendorMutation.isPending) view.setVendorEditor(null);
}}
onSave={view.saveVendorEditor}
onEditorChange={view.setVendorEditor}
/>
<WorkOrderCompleteDialog
open={confirmComplete}
isPending={view.statusMutation.isPending}
onDialogClose={() => setConfirmComplete(false)}
onKeepOpen={() => setConfirmComplete(false)}
onConfirm={() => {
view.statusMutation.mutate("Done");
setConfirmComplete(false);
}}
/>
</Box>
);
}