- {source.tech || source.company}
+
+
+ {source.tech || source.company}
+
+ {Boolean(source.tech && source.company) && (
+
+ {source.company}
- {Boolean(source.tech && source.company) && (
-
- {source.company}
-
- )}
-
-
setVendorDlg(false)}
- />
- >
+ )}
+
);
}
diff --git a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab.tsx b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab.tsx
index c8d9e09b..5d109b07 100644
--- a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab.tsx
+++ b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab.tsx
@@ -58,7 +58,6 @@ export function SlideOverInfoTab({
source={source}
isCancelled={isCancelled}
dispatcher={dispatcher}
- vendors={vendors}
/>
);
}
diff --git a/src/app/(protected)/workorders/_components/dispatch-create-modal.tsx b/src/app/(protected)/workorders/_components/dispatch-create-modal.tsx
deleted file mode 100644
index b7ab376c..00000000
--- a/src/app/(protected)/workorders/_components/dispatch-create-modal.tsx
+++ /dev/null
@@ -1,377 +0,0 @@
-import { useMemo, useState } from "react";
-import {
- Box,
- Button,
- Chip,
- Dialog,
- DialogActions,
- DialogContent,
- DialogTitle,
- IconButton,
- MenuItem,
- Radio,
- Stack,
- TextField,
- Typography,
-} from "@mui/material";
-import CloseIcon from "@mui/icons-material/Close";
-import { useWorkOrdersList } from "@/domain/work-orders/use-cases/use-work-orders-list";
-import {
- useTaskListTemplates,
- useWorkOrderVendors,
-} from "@/domain/work-orders/use-cases/use-work-order-detail";
-import { useDispatchToVendor } from "@/domain/work-orders/use-cases/use-work-order-mutations";
-import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order";
-
-interface SelectedWorkOrder {
- id: string | number;
- number: string;
- title: string;
-}
-
-interface DispatchCreateModalProps {
- workOrder: WorkOrderDetail;
- onClose: () => void;
- onDispatched: () => void;
-}
-
-export function DispatchCreateModal({
- workOrder,
- onClose,
- onDispatched,
-}: DispatchCreateModalProps) {
- const [selectedVendorId, setSelectedVendorId] = useState
(null);
- const [nteAmount, setNteAmount] = useState(workOrder.vendorNTE || "");
- const [scheduledDate, setScheduledDate] = useState("");
- const [description, setDescription] = useState(workOrder.description || "");
- const [vendorSearch, setVendorSearch] = useState("");
- const [selectedTemplateId, setSelectedTemplateId] = useState("");
- const [customItems, setCustomItems] = useState([]);
- const [newCustomItem, setNewCustomItem] = useState("");
- const [selectedWOs, setSelectedWOs] = useState([
- {
- id: workOrder.id,
- number: workOrder.internalWONumber,
- title: workOrder.title,
- },
- ]);
- const [woSearch, setWoSearch] = useState("");
-
- const { data: vendors = [], isLoading: isLoadingVendors } = useWorkOrderVendors(
- workOrder.trade,
- workOrder.locationZip,
- );
- const { data: taskTemplates = [] } = useTaskListTemplates();
- const dispatchMutation = useDispatchToVendor(workOrder.id);
-
- const { data: siteWorkOrdersData } = useWorkOrdersList({
- search: "",
- page: 1,
- pageSize: 500,
- sortBy: "number",
- sortDir: "asc",
- locationId: workOrder.locationId,
- });
-
- const siteWorkOrders = useMemo(
- () =>
- (siteWorkOrdersData?.items ?? []).filter((item) => String(item.id) !== String(workOrder.id)),
- [siteWorkOrdersData?.items, workOrder.id],
- );
-
- const filteredVendors = useMemo(
- () =>
- vendors.filter(
- (vendor) =>
- vendor.companyName.toLowerCase().includes(vendorSearch.toLowerCase()) ||
- vendor.tradeSpecialties.toLowerCase().includes(vendorSearch.toLowerCase()),
- ),
- [vendors, vendorSearch],
- );
-
- const filteredSiteWOs = useMemo(
- () =>
- siteWorkOrders
- .filter((item) => !selectedWOs.some((selected) => String(selected.id) === String(item.id)))
- .filter(
- (item) =>
- !woSearch ||
- item.woNumber.includes(woSearch) ||
- item.title.toLowerCase().includes(woSearch.toLowerCase()),
- )
- .slice(0, 10),
- [siteWorkOrders, selectedWOs, woSearch],
- );
-
- const handleSubmit = () => {
- if (!selectedVendorId) {
- return;
- }
-
- dispatchMutation.mutate(
- {
- workOrderIds: selectedWOs.map((wo) => wo.id),
- vendorIds: [selectedVendorId],
- nteAmount: nteAmount ? parseFloat(nteAmount) : null,
- scheduledDate: scheduledDate || null,
- description,
- taskListTemplateId: selectedTemplateId ? parseInt(selectedTemplateId, 10) : null,
- customChecklistItems: customItems.length > 0 ? customItems : null,
- },
- {
- onSuccess: () => {
- onDispatched();
- onClose();
- },
- },
- );
- };
-
- return (
-
- );
-}
-
-function PaperList({ children }: { children: React.ReactNode }) {
- return (
- {children}
- );
-}
diff --git a/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-checklist-fields.tsx b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-checklist-fields.tsx
new file mode 100644
index 00000000..8b9aa5e8
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-checklist-fields.tsx
@@ -0,0 +1,84 @@
+import { Chip, MenuItem, Skeleton, Stack, TextField } from "@mui/material";
+import type { TaskListTemplate } from "@/domain/work-orders/types/work-order";
+
+interface DispatchCreateChecklistFieldsProps {
+ taskTemplates: TaskListTemplate[];
+ isLoadingTemplates: boolean;
+ selectedTemplateId: string;
+ onTemplateChange: (id: string) => void;
+ isPending: boolean;
+ customItems: string[];
+ onRemoveCustomItem: (index: number) => void;
+ newCustomItem: string;
+ onNewCustomItemChange: (value: string) => void;
+ onAddCustomItem: () => void;
+}
+
+export function DispatchCreateChecklistFields({
+ taskTemplates,
+ isLoadingTemplates,
+ selectedTemplateId,
+ onTemplateChange,
+ isPending,
+ customItems,
+ onRemoveCustomItem,
+ newCustomItem,
+ onNewCustomItemChange,
+ onAddCustomItem,
+}: DispatchCreateChecklistFieldsProps) {
+ return (
+ <>
+ {isLoadingTemplates ? (
+
+ ) : (
+ onTemplateChange(event.target.value)}
+ fullWidth
+ disabled={isPending}
+ >
+
+ {taskTemplates.map((template) => (
+
+ ))}
+
+ )}
+
+ {customItems.length > 0 && (
+
+ {customItems.map((item, index) => (
+ onRemoveCustomItem(index)}
+ />
+ ))}
+
+ )}
+
+ onNewCustomItemChange(event.target.value)}
+ onKeyDown={(event) => {
+ if (event.key === "Enter" && newCustomItem.trim()) {
+ event.preventDefault();
+ onAddCustomItem();
+ }
+ }}
+ placeholder="Add custom checklist item..."
+ fullWidth
+ size="small"
+ />
+ >
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-details-fields.tsx b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-details-fields.tsx
new file mode 100644
index 00000000..fa265c4f
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-details-fields.tsx
@@ -0,0 +1,38 @@
+import { Stack, TextField } from "@mui/material";
+
+interface DispatchCreateDetailsFieldsProps {
+ nteAmount: string;
+ onNteAmountChange: (value: string) => void;
+ scheduledDate: string;
+ onScheduledDateChange: (value: string) => void;
+}
+
+export function DispatchCreateDetailsFields({
+ nteAmount,
+ onNteAmountChange,
+ scheduledDate,
+ onScheduledDateChange,
+}: DispatchCreateDetailsFieldsProps) {
+ return (
+
+ onNteAmountChange(event.target.value)}
+ fullWidth
+ slotProps={{
+ htmlInput: { step: "0.01", min: 0 },
+ }}
+ />
+ onScheduledDateChange(event.target.value)}
+ slotProps={{ inputLabel: { shrink: true } }}
+ fullWidth
+ />
+
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-vendor-section.tsx b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-vendor-section.tsx
new file mode 100644
index 00000000..29133854
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-vendor-section.tsx
@@ -0,0 +1,110 @@
+import { Box, Radio, Skeleton, Stack, TextField, Typography } from "@mui/material";
+import type { VendorDropdownItem } from "@/domain/work-orders/types/work-order";
+
+interface DispatchCreateVendorSectionProps {
+ vendors: VendorDropdownItem[];
+ isLoading: boolean;
+ vendorSearch: string;
+ selectedVendorId: string | number | null;
+ onSearchChange: (value: string) => void;
+ onSelect: (vendorId: string | number) => void;
+}
+
+function VendorLine({ label }: { label: string }) {
+ return (
+
+ {label}
+
+ );
+}
+
+function VendorOption({
+ vendor,
+ isSelected,
+ onSelect,
+}: {
+ vendor: VendorDropdownItem;
+ isSelected: boolean;
+ onSelect: () => void;
+}) {
+ return (
+
+
+
+
+ {vendor.companyName}
+
+ {Boolean(vendor.contactName) && }
+ {Boolean(vendor.tradeSpecialties) && }
+ {Boolean(vendor.address) && }
+
+ {vendor.distanceMiles != null && (
+
+ {vendor.distanceMiles} miles
+
+ )}
+
+ );
+}
+
+export function DispatchCreateVendorSection({
+ vendors,
+ isLoading,
+ vendorSearch,
+ selectedVendorId,
+ onSearchChange,
+ onSelect,
+}: DispatchCreateVendorSectionProps) {
+ return (
+
+
+ Select Vendor
+
+ onSearchChange(event.target.value)}
+ placeholder="Search vendors..."
+ fullWidth
+ className="mb-2"
+ />
+ {isLoading ? (
+
+ {Array.from({ length: 3 }, (_, i) => (
+
+ ))}
+
+ ) : vendors.length === 0 ? (
+
+ No vendors found
+
+ ) : (
+
+ {vendors.map((vendor) => (
+ onSelect(vendor.id)}
+ />
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-work-orders-section.tsx b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-work-orders-section.tsx
new file mode 100644
index 00000000..c3026a26
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/dispatch-create-modal/dispatch-create-work-orders-section.tsx
@@ -0,0 +1,73 @@
+import { Box, Chip, Stack, TextField, Typography } from "@mui/material";
+import type { SelectedWorkOrderRef } from "@/domain/work-orders/types/work-order";
+import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+
+interface DispatchCreateWorkOrdersSectionProps {
+ workOrderId: string | number;
+ selectedWOs: SelectedWorkOrderRef[];
+ siteWorkOrders: WorkOrderTableRow[];
+ filteredSiteWOs: WorkOrderTableRow[];
+ woSearch: string;
+ onWoSearchChange: (value: string) => void;
+ onAddWO: (item: WorkOrderTableRow) => void;
+ onRemoveWO: (id: string | number) => void;
+}
+
+export function DispatchCreateWorkOrdersSection({
+ workOrderId,
+ selectedWOs,
+ siteWorkOrders,
+ filteredSiteWOs,
+ woSearch,
+ onWoSearchChange,
+ onAddWO,
+ onRemoveWO,
+}: DispatchCreateWorkOrdersSectionProps) {
+ return (
+
+
+ Work Orders ({selectedWOs.length})
+
+
+ {selectedWOs.map((wo) => (
+ onRemoveWO(wo.id) : undefined}
+ />
+ ))}
+
+ {siteWorkOrders.length > 0 && (
+
+ onWoSearchChange(event.target.value)}
+ placeholder="Search to filter..."
+ fullWidth
+ className="mb-2"
+ />
+
+ {filteredSiteWOs.map((item) => (
+ onAddWO(item)}
+ >
+
+ {item.woNumber} — {item.title}
+
+
+ ))}
+
+
+ )}
+
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/dispatch-create-modal/index.tsx b/src/app/(protected)/workorders/_components/dispatch-create-modal/index.tsx
new file mode 100644
index 00000000..08fe407d
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/dispatch-create-modal/index.tsx
@@ -0,0 +1,114 @@
+import {
+ Dialog,
+ DialogActions,
+ DialogContent,
+ DialogTitle,
+ IconButton,
+ TextField,
+} from "@mui/material";
+import CloseIcon from "@mui/icons-material/Close";
+import { Button } from "@/components/ui/button";
+import type { WorkOrderDetail } from "@/domain/work-orders/types/work-order";
+import { useDispatchCreateController } from "@/app/(protected)/workorders/_hooks/use-dispatch-create-controller";
+import { DispatchCreateWorkOrdersSection } from "./dispatch-create-work-orders-section";
+import { DispatchCreateVendorSection } from "./dispatch-create-vendor-section";
+import { DispatchCreateChecklistFields } from "./dispatch-create-checklist-fields";
+import { DispatchCreateDetailsFields } from "./dispatch-create-details-fields";
+
+interface DispatchCreateModalProps {
+ workOrder: WorkOrderDetail;
+ onClose: () => void;
+ onDispatched: () => void;
+}
+
+export function DispatchCreateModal({
+ workOrder,
+ onClose,
+ onDispatched,
+}: DispatchCreateModalProps) {
+ const controller = useDispatchCreateController(workOrder, onClose, onDispatched);
+
+ return (
+
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-checklist-section.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-checklist-section.tsx
index 03ff527a..0f54f805 100644
--- a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-checklist-section.tsx
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-checklist-section.tsx
@@ -6,6 +6,7 @@ type DispatchChecklistSectionProps = {
items: DispatchChecklistItem[];
isLocked: boolean;
isUpdating: boolean;
+ isAdding?: boolean;
newItem: string;
onNewItemChange: (value: string) => void;
onToggleItem: (itemId: string | number, isCompleted: boolean) => void;
@@ -16,6 +17,7 @@ export function DispatchChecklistSection({
items,
isLocked,
isUpdating,
+ isAdding = false,
newItem,
onNewItemChange,
onToggleItem,
@@ -58,14 +60,16 @@ export function DispatchChecklistSection({
value={newItem}
onChange={(event) => onNewItemChange(event.target.value)}
onKeyDown={(event) => {
- if (event.key === "Enter" && newItem.trim()) {
+ if (event.key === "Enter" && newItem.trim() && !isAdding) {
event.preventDefault();
onAddItem(newItem.trim());
}
}}
- placeholder="Add checklist item..."
+ placeholder={isAdding ? "Adding…" : "Add checklist item..."}
+ disabled={isAdding}
fullWidth
className="mt-2"
+ slotProps={{ htmlInput: { "aria-busy": isAdding || undefined } }}
/>
)}
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-detail-body.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-detail-body.tsx
new file mode 100644
index 00000000..637f3a47
--- /dev/null
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-detail-body.tsx
@@ -0,0 +1,100 @@
+import { DialogContent, Skeleton } from "@mui/material";
+import type { DispatchDetail } from "@/domain/work-orders/types/work-order";
+import type { DispatchDetailController } from "@/app/(protected)/workorders/_hooks/use-dispatch-detail-controller";
+import { DispatchChecklistSection } from "./dispatch-checklist-section";
+import { DispatchDetailsGrid, DispatchDetailsView, DispatchEditForm } from "./dispatch-edit-form";
+import { DispatchSignoffsSection } from "./dispatch-signoffs-section";
+import { DispatchUpliftsSection } from "./dispatch-uplifts-section";
+import { DispatchVendorCommunication } from "./dispatch-vendor-communication";
+import { DispatchVerifyFooter } from "./dispatch-verify-footer";
+
+interface DispatchDetailBodyProps {
+ dispatch: DispatchDetail;
+ controller: DispatchDetailController;
+}
+
+export function DispatchDetailBody({ dispatch, controller }: DispatchDetailBodyProps) {
+ return (
+
+
+ {controller.editing ? (
+ controller.setEditing(false)}
+ isSaving={controller.updateDispatchMutation.isPending}
+ />
+ ) : (
+ controller.handleCancelDispatch(dispatch)}
+ />
+ )}
+
+
+ {(dispatch.checklistItems.length > 0 || !controller.isLocked) && (
+
+ controller.updateChecklistMutation.mutate({
+ itemId,
+ isCompleted,
+ completedBy: controller.userName,
+ })
+ }
+ onAddItem={(text) =>
+ controller.addChecklistMutation.mutate(text, {
+ onSuccess: () => controller.setNewChecklistItem(""),
+ })
+ }
+ />
+ )}
+
+
+
+ {controller.isLoadingUplifts ? (
+
+ ) : (
+ controller.setUpliftAction({ type: "approve", uplift })}
+ onDeny={(uplift) => controller.setUpliftAction({ type: "deny", uplift })}
+ />
+ )}
+
+
+
+ controller.verifyMutation.mutate(undefined)}
+ />
+
+ );
+}
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-edit-form.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-edit-form.tsx
index 1a0a27f2..152eb807 100644
--- a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-edit-form.tsx
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-edit-form.tsx
@@ -1,5 +1,6 @@
-import { Box, Button, MenuItem, Stack, TextField } from "@mui/material";
+import { Box, MenuItem, Stack, TextField } from "@mui/material";
import { DetailField } from "@/components/common/detail-field";
+import { Button } from "@/components/ui/button";
import {
DISPATCH_STATUS_OPTIONS,
type DispatchDetail,
@@ -84,10 +85,12 @@ export function DispatchEditForm({
onChange={(event) => onChange({ ...form, description: event.target.value })}
/>
-
>
);
@@ -138,12 +141,10 @@ export function DispatchDetailsView({
)}
{!isLocked && (
-
+
Cancel Dispatch
-
- Edit
-
+ Edit
)}
>
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-signoffs-section.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-signoffs-section.tsx
index 00c5e177..c2526620 100644
--- a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-signoffs-section.tsx
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-signoffs-section.tsx
@@ -5,12 +5,14 @@ import { formatDateTime } from "@/lib/time-utils";
type DispatchSignoffsSectionProps = {
signoffs: DispatchSignoff[];
isLocked: boolean;
+ isSigningOff?: boolean;
onCollectSignature: (type: "Customer" | "Vendor") => void;
};
export function DispatchSignoffsSection({
signoffs,
isLocked,
+ isSigningOff = false,
onCollectSignature,
}: DispatchSignoffsSectionProps) {
return (
@@ -49,9 +51,9 @@ export function DispatchSignoffsSection({
variant="outlined"
className="mt-2"
onClick={() => onCollectSignature(type)}
- disabled={isLocked}
+ disabled={isLocked || isSigningOff}
>
- {isLocked ? "Not signed" : "Collect Signature"}
+ {isLocked ? "Not signed" : isSigningOff ? "Saving…" : "Collect Signature"}
)}
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-vendor-communication.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-vendor-communication.tsx
index 74cf4e1a..816b78a5 100644
--- a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-vendor-communication.tsx
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-vendor-communication.tsx
@@ -1,6 +1,7 @@
-import { Box, Button, Checkbox, FormControlLabel, Stack, TextField } from "@mui/material";
+import { Box, Checkbox, FormControlLabel, Stack, TextField } from "@mui/material";
import { CommentTimeline } from "@/components/common/comment-timeline";
import { mapDispatchCommentsToTimeline } from "@/components/common/comment-timeline-mappers";
+import { Button } from "@/components/ui/button";
import type { DispatchComment } from "@/domain/work-orders/types/work-order";
type DispatchVendorCommunicationProps = {
@@ -61,10 +62,11 @@ export function DispatchVendorCommunication({
/>
- {isPending ? "Sending..." : "Send"}
+ Send
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-verify-footer.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-verify-footer.tsx
index 7ee7cb1e..4c8cb8a7 100644
--- a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-verify-footer.tsx
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/dispatch-verify-footer.tsx
@@ -1,4 +1,5 @@
-import { Alert, Button, Stack, Typography } from "@mui/material";
+import { Alert, Stack, Typography } from "@mui/material";
+import { Button } from "@/components/ui/button";
import { formatDateTime } from "@/lib/time-utils";
type DispatchVerifyFooterProps = {
@@ -34,10 +35,15 @@ export function DispatchVerifyFooter({
)}
Verify & Close
diff --git a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/index.tsx b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/index.tsx
index 7189fe1e..b5d0562c 100644
--- a/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/index.tsx
+++ b/src/app/(protected)/workorders/_components/dispatch/dispatch-detail/index.tsx
@@ -1,13 +1,10 @@
-import { CircularProgress, Dialog, DialogContent, DialogTitle } from "@mui/material";
+import { Dialog, DialogTitle } from "@mui/material";
import { SignatureCapture } from "@/components/common/signature-capture";
import { useDispatchDetailController } from "@/app/(protected)/workorders/_hooks/use-dispatch-detail-controller";
-import { DispatchChecklistSection } from "./dispatch-checklist-section";
+import { DispatchDetailSkeleton } from "@/app/(protected)/workorders/_components/loading/dispatch-detail-skeleton";
+import { guardDialogClose } from "@/lib/dialog-guard";
+import { DispatchDetailBody } from "./dispatch-detail-body";
import { DispatchDetailHeader } from "./dispatch-detail-header";
-import { DispatchDetailsGrid, DispatchDetailsView, DispatchEditForm } from "./dispatch-edit-form";
-import { DispatchSignoffsSection } from "./dispatch-signoffs-section";
-import { DispatchUpliftsSection } from "./dispatch-uplifts-section";
-import { DispatchVendorCommunication } from "./dispatch-vendor-communication";
-import { DispatchVerifyFooter } from "./dispatch-verify-footer";
import { UpliftDecisionDialog } from "./uplift-decision-dialog";
interface DispatchDetailModalProps {
@@ -23,13 +20,12 @@ export function DispatchDetailModal({
onClose,
}: DispatchDetailModalProps) {
const controller = useDispatchDetailController(dispatchId, workOrderId);
+ const handleClose = guardDialogClose(controller.isMutating, onClose);
- if (controller.isLoading) {
+ if (controller.isLoading && !controller.dispatch) {
return (
);
}
@@ -42,91 +38,23 @@ export function DispatchDetailModal({
return (
<>
-
+
+