From dc81078df7d12fa423c7c96154bf964d3576e263 Mon Sep 17 00:00:00 2001 From: Adam Moussa <166072409+amoussa1229@users.noreply.github.com> Date: Fri, 17 Apr 2026 16:13:18 -0400 Subject: [PATCH] Add multi-WO selection to dispatch modal - Current WO shown as non-removable chip - Dropdown shows all WOs at same site, filtered by locationId server-side - Search bar filters the dropdown list - Click to add, X to remove additional WOs - Header shows count when multiple WOs selected - All selected WO IDs sent to backend --- .../workorders/view/WorkOrderViewPage.css | 54 +++++++++++++++++++ .../workorders/view/WorkOrderViewPage.js | 54 ++++++++++++++++++- 2 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/pages/workorders/view/WorkOrderViewPage.css b/src/pages/workorders/view/WorkOrderViewPage.css index 0a1f86b7..7747a645 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.css +++ b/src/pages/workorders/view/WorkOrderViewPage.css @@ -749,6 +749,60 @@ font-weight: 600; } +.dispatchWOChips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.dispatchWOChip { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 4px 10px; + background: #e9ecef; + border-radius: 4px; + font-size: 12px; + color: #333; +} + +.dispatchWOChip button { + background: none; + border: none; + color: #999; + cursor: pointer; + font-size: 14px; + padding: 0; + line-height: 1; +} + +.dispatchWOChip button:hover { + color: #dc3545; +} + +.dispatchWODropdown { + border: 1px solid #ddd; + border-radius: 4px; + max-height: 160px; + overflow-y: auto; + margin-top: 4px; +} + +.dispatchWOOption { + padding: 8px 12px; + font-size: 12px; + cursor: pointer; + border-bottom: 1px solid #f0f0f0; +} + +.dispatchWOOption:hover { + background: #f0f7ff; +} + +.dispatchWOOption:last-child { + border-bottom: none; +} + .dispatchVerifiedBanner { padding: 12px 20px; background: #d4edda; diff --git a/src/pages/workorders/view/WorkOrderViewPage.js b/src/pages/workorders/view/WorkOrderViewPage.js index 456b07ac..ac053183 100644 --- a/src/pages/workorders/view/WorkOrderViewPage.js +++ b/src/pages/workorders/view/WorkOrderViewPage.js @@ -529,6 +529,9 @@ function DispatchModal({ wo, onClose, onDispatched }) { const [taskTemplates, setTaskTemplates] = useState([]); const [selectedTemplateId, setSelectedTemplateId] = useState(""); const [loaded, setLoaded] = useState(false); + const [selectedWOs, setSelectedWOs] = useState([{ id: wo.id, number: wo.internalWONumber, title: wo.title }]); + const [siteWOs, setSiteWOs] = useState([]); + const [woSearch, setWoSearch] = useState(""); useEffect(() => { fetchVendorsDropdown(wo.trade, wo.locationZip).then((data) => { @@ -536,6 +539,13 @@ function DispatchModal({ wo, onClose, onDispatched }) { setLoaded(true); }).catch(() => setLoaded(true)); fetchTaskTemplates().then(setTaskTemplates).catch(() => {}); + if (wo.locationId) { + const { get: apiGet } = require("../../../apiUtil"); + const { API_URL } = require("../../../constants"); + apiGet(`${API_URL}/WorkOrder/GetWorkOrderList?search=&page=1&pageSize=500&sortBy=number&sortDir=asc&locationId=${wo.locationId}`).then((res) => { + setSiteWOs((res?.data || []).filter(w => w.id !== wo.id)); + }).catch(() => {}); + } }, [wo.trade]); const selectVendor = (id) => { @@ -552,7 +562,7 @@ function DispatchModal({ wo, onClose, onDispatched }) { setSending(true); try { await dispatchToVendor({ - WorkOrderIds: [wo.id], + WorkOrderIds: selectedWOs.map(w => w.id), VendorIds: [selectedId], NTEAmount: nteAmount ? parseFloat(nteAmount) : null, ScheduledDate: scheduledDate || null, @@ -573,10 +583,50 @@ function DispatchModal({ wo, onClose, onDispatched }) {
e.stopPropagation()}>
-

Dispatch Work Order {wo.internalWONumber}

+

Dispatch {selectedWOs.length > 1 ? `${selectedWOs.length} Work Orders` : `Work Order ${wo.internalWONumber}`}

+
+ +
+ {selectedWOs.map((w) => ( + + {w.number} — {w.title} + {w.id !== wo.id && } + + ))} +
+ {siteWOs.length > 0 && ( + <> + setWoSearch(e.target.value)} + placeholder="Search to filter..." + style={{ marginTop: 8 }} + /> +
+ {siteWOs + .filter(w => !selectedWOs.find(s => s.id === w.id)) + .filter(w => !woSearch || (w.number || "").includes(woSearch) || (w.title || "").toLowerCase().includes(woSearch.toLowerCase())) + .slice(0, 10) + .map(w => ( +
{ + setSelectedWOs(prev => [...prev, { id: w.id, number: w.number, title: w.title }]); + setWoSearch(""); + }}> + {w.number} — {w.title} +
+ )) + } + {siteWOs.filter(w => !selectedWOs.find(s => s.id === w.id)).length === 0 && ( +
All work orders at this site are selected
+ )} +
+ + )} +