mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 06:53:12 +00:00
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
This commit is contained in:
parent
199715d5b6
commit
dc81078df7
2 changed files with 106 additions and 2 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
|||
<div className="woDispatchOverlay" onClick={onClose}>
|
||||
<div className="woDispatchModal" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="woDispatchHeader">
|
||||
<h3>Dispatch Work Order {wo.internalWONumber}</h3>
|
||||
<h3>Dispatch {selectedWOs.length > 1 ? `${selectedWOs.length} Work Orders` : `Work Order ${wo.internalWONumber}`}</h3>
|
||||
<button className="woDispatchClose" onClick={onClose}>×</button>
|
||||
</div>
|
||||
<div className="woDispatchBody">
|
||||
<div className="woDispatchField">
|
||||
<label>Work Orders ({selectedWOs.length})</label>
|
||||
<div className="dispatchWOChips">
|
||||
{selectedWOs.map((w) => (
|
||||
<span key={w.id} className="dispatchWOChip">
|
||||
{w.number} — {w.title}
|
||||
{w.id !== wo.id && <button onClick={() => setSelectedWOs(prev => prev.filter(p => p.id !== w.id))}>×</button>}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
{siteWOs.length > 0 && (
|
||||
<>
|
||||
<input
|
||||
className="woDispatchSearch"
|
||||
value={woSearch}
|
||||
onChange={(e) => setWoSearch(e.target.value)}
|
||||
placeholder="Search to filter..."
|
||||
style={{ marginTop: 8 }}
|
||||
/>
|
||||
<div className="dispatchWODropdown">
|
||||
{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 => (
|
||||
<div key={w.id} className="dispatchWOOption" onClick={() => {
|
||||
setSelectedWOs(prev => [...prev, { id: w.id, number: w.number, title: w.title }]);
|
||||
setWoSearch("");
|
||||
}}>
|
||||
<strong>{w.number}</strong> — {w.title}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{siteWOs.filter(w => !selectedWOs.find(s => s.id === w.id)).length === 0 && (
|
||||
<div className="dispatchWOOption" style={{ color: "#999", cursor: "default" }}>All work orders at this site are selected</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="woDispatchField">
|
||||
<label>Select Vendors</label>
|
||||
<input
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue