Change dispatch to single vendor select per dispatch

- Switch from checkbox multi-select to radio button single select
- One vendor per dispatch, can dispatch multiple times for multiple vendors
- Selected vendor shown as chip below the list
This commit is contained in:
Adam Moussa 2026-04-17 12:22:37 -04:00
parent 766ad20774
commit 0455fdf2ec
2 changed files with 16 additions and 16 deletions

View file

@ -526,7 +526,7 @@
background: #e6f0ff; background: #e6f0ff;
} }
.woDispatchVendorItem input[type="checkbox"] { .woDispatchVendorItem input[type="radio"] {
flex-shrink: 0; flex-shrink: 0;
width: 16px; width: 16px;
height: 16px; height: 16px;

View file

@ -162,7 +162,7 @@ function AuditTimeline({ entries }) {
function DispatchModal({ wo, onClose, onDispatched }) { function DispatchModal({ wo, onClose, onDispatched }) {
const [vendors, setVendors] = useState([]); const [vendors, setVendors] = useState([]);
const [selectedIds, setSelectedIds] = useState([]); const [selectedId, setSelectedId] = useState(null);
const [nteAmount, setNteAmount] = useState(wo.vendorNTE || ""); const [nteAmount, setNteAmount] = useState(wo.vendorNTE || "");
const [scheduledDate, setScheduledDate] = useState(""); const [scheduledDate, setScheduledDate] = useState("");
const [description, setDescription] = useState(wo.description || ""); const [description, setDescription] = useState(wo.description || "");
@ -177,8 +177,8 @@ function DispatchModal({ wo, onClose, onDispatched }) {
}).catch(() => setLoaded(true)); }).catch(() => setLoaded(true));
}, [wo.trade]); }, [wo.trade]);
const toggleVendor = (id) => { const selectVendor = (id) => {
setSelectedIds(prev => prev.includes(id) ? prev.filter(v => v !== id) : [...prev, id]); setSelectedId(prev => prev === id ? null : id);
}; };
const filteredVendors = vendors.filter((v) => const filteredVendors = vendors.filter((v) =>
@ -187,16 +187,16 @@ function DispatchModal({ wo, onClose, onDispatched }) {
); );
const handleSubmit = async () => { const handleSubmit = async () => {
if (selectedIds.length === 0) { toast.error("Select at least one vendor"); return; } if (!selectedId) { toast.error("Select a vendor"); return; }
setSending(true); setSending(true);
try { try {
await dispatchToVendor(wo.id, { await dispatchToVendor(wo.id, {
VendorIds: selectedIds, VendorIds: [selectedId],
NTEAmount: nteAmount ? parseFloat(nteAmount) : null, NTEAmount: nteAmount ? parseFloat(nteAmount) : null,
ScheduledDate: scheduledDate || null, ScheduledDate: scheduledDate || null,
Description: description, Description: description,
}); });
toast.success(`Dispatched to ${selectedIds.length} vendor(s)`); toast.success("Dispatched successfully");
onDispatched(); onDispatched();
onClose(); onClose();
} catch { } catch {
@ -226,8 +226,8 @@ function DispatchModal({ wo, onClose, onDispatched }) {
{!loaded ? <div className="woDispatchLoading">Loading vendors...</div> : filteredVendors.length === 0 ? <div className="woDispatchLoading">No vendors found</div> : ( {!loaded ? <div className="woDispatchLoading">Loading vendors...</div> : filteredVendors.length === 0 ? <div className="woDispatchLoading">No vendors found</div> : (
<div className="woDispatchVendorList"> <div className="woDispatchVendorList">
{filteredVendors.map((v) => ( {filteredVendors.map((v) => (
<div key={v.id} className={`woDispatchVendorItem ${selectedIds.includes(v.id) ? "selected" : ""}`} onClick={() => toggleVendor(v.id)}> <div key={v.id} className={`woDispatchVendorItem ${selectedId === v.id ? "selected" : ""}`} onClick={() => selectVendor(v.id)}>
<input type="checkbox" checked={selectedIds.includes(v.id)} readOnly /> <input type="radio" name="vendorSelect" checked={selectedId === v.id} readOnly />
<div className="woDispatchVendorInfo"> <div className="woDispatchVendorInfo">
<span className="woDispatchVendorName">{v.companyName}</span> <span className="woDispatchVendorName">{v.companyName}</span>
{v.tradeSpecialties && <span className="woDispatchVendorTrades">{v.tradeSpecialties}</span>} {v.tradeSpecialties && <span className="woDispatchVendorTrades">{v.tradeSpecialties}</span>}
@ -243,12 +243,12 @@ function DispatchModal({ wo, onClose, onDispatched }) {
))} ))}
</div> </div>
)} )}
{selectedIds.length > 0 && ( {selectedId && (
<div className="woDispatchSelected"> <div className="woDispatchSelected">
{selectedIds.map((id) => { <span className="woDispatchSelectedChip">
const v = vendors.find((x) => x.id === id); {vendors.find((x) => x.id === selectedId)?.companyName}
return v ? <span key={id} className="woDispatchSelectedChip">{v.companyName} <button onClick={() => toggleVendor(id)}>&times;</button></span> : null; <button onClick={() => setSelectedId(null)}>&times;</button>
})} </span>
</div> </div>
)} )}
</div> </div>
@ -269,8 +269,8 @@ function DispatchModal({ wo, onClose, onDispatched }) {
</div> </div>
<div className="woDispatchFooter"> <div className="woDispatchFooter">
<button className="btn btn-default" onClick={onClose} disabled={sending}>Cancel</button> <button className="btn btn-default" onClick={onClose} disabled={sending}>Cancel</button>
<button className="btn btn-primary" onClick={handleSubmit} disabled={sending || selectedIds.length === 0}> <button className="btn btn-primary" onClick={handleSubmit} disabled={sending || !selectedId}>
{sending ? "Dispatching..." : `Dispatch to ${selectedIds.length} Vendor(s)`} {sending ? "Dispatching..." : "Dispatch"}
</button> </button>
</div> </div>
</div> </div>