import { useMemo, useState } from "react"; import CloseIcon from "@mui/icons-material/Close"; import { Alert, Box, Button, Checkbox, Chip, CircularProgress, Dialog, DialogContent, DialogTitle, FormControlLabel, IconButton, MenuItem, Stack, TextField, Tooltip, Typography, } from "@mui/material"; import { SignatureCapture } from "@/components/common/signature-capture"; import { useApproveUplift, useDenyUplift, useUpliftsForDispatch, } from "@/domain/uplifts/use-cases/use-uplift-actions"; import { DISPATCH_STATUS_OPTIONS } from "@/domain/work-orders/types/work-order"; import { useDispatchDetail } from "@/domain/work-orders/use-cases/use-work-order-detail"; import { useAddChecklistItem, useAddDispatchComment, useAddDispatchSignoff, useUpdateChecklistItem, useUpdateDispatch, useVerifyDispatch, } from "@/domain/work-orders/use-cases/use-work-order-mutations"; import { isDispatchLocked } from "@/domain/work-orders/utils/is-dispatch-locked"; import { useAuthContext } from "@/providers/auth-context"; import { formatDateTime, timeBetween, timeSince, toDateInputValue, waitTimeColor, } from "@/lib/time-utils"; interface DispatchDetailModalProps { dispatchId: string | number; workOrderId: string | number; onClose: () => void; onUpdated: () => void; } export function DispatchDetailModal({ dispatchId, workOrderId, onClose, onUpdated, }: DispatchDetailModalProps) { const { user } = useAuthContext(); const { data: dispatch, isLoading } = useDispatchDetail(dispatchId); const { data: uplifts = [] } = useUpliftsForDispatch(dispatchId); const [editing, setEditing] = useState(false); const [form, setForm] = useState({ status: "", nteAmount: "", scheduledDate: "", completedDate: "", description: "", }); const [newChecklistItem, setNewChecklistItem] = useState(""); const [commentText, setCommentText] = useState(""); const [sendEmail, setSendEmail] = useState(true); const [sigCaptureType, setSigCaptureType] = useState(null); const updateDispatchMutation = useUpdateDispatch(dispatchId, workOrderId); const addCommentMutation = useAddDispatchComment(dispatchId); const updateChecklistMutation = useUpdateChecklistItem(dispatchId); const addChecklistMutation = useAddChecklistItem(dispatchId); const addSignoffMutation = useAddDispatchSignoff(dispatchId); const verifyMutation = useVerifyDispatch(dispatchId, workOrderId); const approveUpliftMutation = useApproveUplift(dispatchId); const denyUpliftMutation = useDenyUplift(dispatchId); const userName = (typeof user?.name === "string" ? user.name : undefined) ?? (typeof user?.fullname === "string" ? user.fullname : undefined) ?? "User"; const beginEditing = () => { if (dispatch) { setForm({ status: dispatch.status || "", nteAmount: dispatch.nteAmount != null ? String(dispatch.nteAmount) : "", scheduledDate: toDateInputValue(dispatch.scheduledDate), completedDate: toDateInputValue(dispatch.completedDate), description: dispatch.description || "", }); } setEditing(true); }; const verifyState = useMemo(() => { if (!dispatch) { return { canVerify: false, missing: [] as string[] }; } const checklistTotal = dispatch.checklistItems.length; const checklistDone = dispatch.checklistItems.filter((item) => item.isCompleted).length; const signoffCount = dispatch.signoffs.length; const missing: string[] = []; if (checklistTotal > 0 && checklistDone < checklistTotal) { missing.push(`${checklistTotal - checklistDone} checklist item(s)`); } if (signoffCount < 2) { const types = dispatch.signoffs.map((signoff) => signoff.signoffType); if (!types.includes("Customer")) { missing.push("Customer sign-off"); } if (!types.includes("Vendor")) { missing.push("Vendor sign-off"); } } return { missing, canVerify: missing.length === 0 && (checklistTotal > 0 || signoffCount >= 2), }; }, [dispatch]); const handleSave = () => { updateDispatchMutation.mutate( { id: dispatchId, status: form.status, nteAmount: form.nteAmount ? parseFloat(form.nteAmount) : null, scheduledDate: form.scheduledDate || null, completedDate: form.completedDate || null, description: form.description, }, { onSuccess: () => { setEditing(false); onUpdated(); }, }, ); }; const handleComment = (event: React.FormEvent) => { event.preventDefault(); if (!commentText.trim()) { return; } addCommentMutation.mutate( { text: commentText.trim(), sendEmail }, { onSuccess: () => setCommentText(""), }, ); }; const handleApproveUplift = (upliftId: string | number, requestedNTE: number) => { const note = window.prompt("Optional approval note:", "") || ""; if (!window.confirm(`Approve uplift to $${requestedNTE.toFixed(2)}?`)) { return; } approveUpliftMutation.mutate({ id: upliftId, note }, { onSuccess: onUpdated }); }; const handleDenyUplift = (upliftId: string | number) => { const note = window.prompt("Denial reason (required):", ""); if (!note?.trim()) { return; } denyUpliftMutation.mutate({ id: upliftId, note: note.trim() }, { onSuccess: onUpdated }); }; if (isLoading) { return ( ); } if (!dispatch) { return null; } const isLocked = isDispatchLocked(dispatch); const waitColor = waitTimeColor(dispatch.dispatchedAt); return ( <> {dispatch.dispatchNumber || dispatch.poNumber} · {dispatch.vendorName} PO: {dispatch.poNumber} ·{" "} {dispatch.workOrders.length > 0 ? `${dispatch.workOrders.length} WO(s): ${dispatch.workOrders.map((wo) => wo.woNumber).join(", ")}` : `WO: ${dispatch.woNumber}`}{" "} · {dispatch.vendorEmail} {editing ? ( <> setForm((current) => ({ ...current, status: event.target.value })) } > {DISPATCH_STATUS_OPTIONS.map((status) => ( {status} ))} setForm((current) => ({ ...current, nteAmount: event.target.value })) } inputProps={{ step: "0.01" }} /> setForm((current) => ({ ...current, scheduledDate: event.target.value })) } slotProps={{ inputLabel: { shrink: true } }} /> setForm((current) => ({ ...current, completedDate: event.target.value })) } slotProps={{ inputLabel: { shrink: true } }} /> setForm((current) => ({ ...current, description: event.target.value })) } /> ) : ( <> {dispatch.description && ( )} {!isLocked && ( )} )} {(dispatch.checklistItems.length > 0 || !isLocked) && ( Checklist ({dispatch.checklistItems.filter((item) => item.isCompleted).length}/ {dispatch.checklistItems.length}) {dispatch.checklistItems.map((item) => ( updateChecklistMutation.mutate({ itemId: item.id, isCompleted: !item.isCompleted, completedBy: userName, }) } /> {item.itemText} {item.isCompleted && ( {item.completedBy} {item.completedAt ? ` · ${formatDateTime(item.completedAt)}` : ""} )} ))} {!isLocked && ( setNewChecklistItem(event.target.value)} onKeyDown={(event) => { if (event.key === "Enter" && newChecklistItem.trim()) { event.preventDefault(); addChecklistMutation.mutate(newChecklistItem.trim(), { onSuccess: () => setNewChecklistItem(""), }); } }} placeholder="Add checklist item..." fullWidth className="mt-2" /> )} )} Sign-offs {(["Customer", "Vendor"] as const).map((type) => { const signoff = dispatch.signoffs.find((item) => item.signoffType === type); return ( {type} {signoff ? ( {signoff.signatureMethod === "drawn" && signoff.signature && ( Signature )} {signoff.signatureMethod === "typed" && ( {signoff.signature} )} {signoff.name} {signoff.signedAt ? ` · ${formatDateTime(signoff.signedAt)}` : ""} ) : ( )} ); })} {dispatch.signoffs.length >= 2 && ( )} NTE Uplift Requests {uplifts.length === 0 ? ( No uplift requests. ) : ( {uplifts.map((uplift) => { const canDecide = uplift.canDecide && uplift.status === "Pending" && !isLocked; return ( {uplift.requiredTier === 2 && ( Tier 2 · Manager approval )} ${Number(uplift.currentNTE ?? 0).toFixed(2)} → $ {Number(uplift.requestedNTE).toFixed(2)} (+$ {Number(uplift.delta).toFixed(2)}) {uplift.vendorReason && ( Reason: {uplift.vendorReason} )} {(uplift.requestedByVendorName || uplift.requestedAt) && ( Requested by {uplift.requestedByVendorName || "vendor"} {uplift.requestedAt ? ` · ${formatDateTime(uplift.requestedAt)}` : ""} )} {uplift.status !== "Pending" && (uplift.decidedByName || uplift.decidedAt || uplift.decisionNote) && ( {(uplift.decidedByName || uplift.decidedAt) && ( {uplift.status} by {uplift.decidedByName || "—"} {uplift.decidedAt ? ` · ${formatDateTime(uplift.decidedAt)}` : ""} )} {uplift.decisionNote && ( Note: {uplift.decisionNote} )} )} {uplift.status === "Pending" && ( )} ); })} )} Vendor Communication {dispatch.comments.length === 0 ? ( No messages yet. ) : ( {dispatch.comments.map((comment) => ( {comment.userName || comment.commenter || "System"} {comment.createdDate ? formatDateTime(comment.createdDate) : ""} {comment.commenttext} ))} )} setCommentText(event.target.value)} placeholder="Message vendor..." fullWidth disabled={isLocked || addCommentMutation.isPending} /> setSendEmail(event.target.checked)} disabled={isLocked} /> } label="Send email" /> {dispatch.verifiedAt ? ( Verified by {dispatch.verifiedBy} on {formatDateTime(dispatch.verifiedAt)} ) : ( {verifyState.missing.length > 0 && ( Missing: {verifyState.missing.join(", ")} )} )} {sigCaptureType && ( setSigCaptureType(null)} onSave={(signature, method, name) => { addSignoffMutation.mutate( { signoffType: sigCaptureType, name, signature, signatureMethod: method, }, { onSuccess: () => setSigCaptureType(null) }, ); }} /> )} ); } function DetailField({ label, value, className, highlight, }: { label: string; value: string; className?: string; highlight?: "green" | "yellow" | "red" | ""; }) { const colorClass = highlight === "green" ? "text-green-600" : highlight === "yellow" ? "text-yellow-600" : highlight === "red" ? "text-red-600" : undefined; return ( {label} {value} ); }