mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 06:53:12 +00:00
Add searchable vendor picker and scheduled date to dispatch modal
- Vendor list now has search input to filter by name or trade - Selected vendors shown as removable chips below the list - NTE and Vendor Scheduled Date side-by-side in the modal - Sends ScheduledDate with dispatch request
This commit is contained in:
parent
0167a1aa75
commit
2a4cd5da75
2 changed files with 87 additions and 5 deletions
|
|
@ -443,6 +443,59 @@
|
|||
color: #333;
|
||||
}
|
||||
|
||||
.woDispatchSearch {
|
||||
width: 100%;
|
||||
height: 36px;
|
||||
padding: 6px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.woDispatchSearch:focus {
|
||||
border-color: #66afe9;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.woDispatchSelected {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.woDispatchSelectedChip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 3px 10px;
|
||||
background: #e6f0ff;
|
||||
border-radius: 12px;
|
||||
font-size: 12px;
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.woDispatchSelectedChip button {
|
||||
background: none;
|
||||
border: none;
|
||||
color: #007bff;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
padding: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.woDispatchFieldRow {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.woDispatchFieldHalf {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.woDispatchVendorList {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
|
|
|
|||
|
|
@ -164,7 +164,9 @@ function DispatchModal({ wo, onClose, onDispatched }) {
|
|||
const [vendors, setVendors] = useState([]);
|
||||
const [selectedIds, setSelectedIds] = useState([]);
|
||||
const [nteAmount, setNteAmount] = useState(wo.vendorNTE || "");
|
||||
const [scheduledDate, setScheduledDate] = useState("");
|
||||
const [description, setDescription] = useState(wo.description || "");
|
||||
const [vendorSearch, setVendorSearch] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
|
||||
|
|
@ -179,6 +181,11 @@ function DispatchModal({ wo, onClose, onDispatched }) {
|
|||
setSelectedIds(prev => prev.includes(id) ? prev.filter(v => v !== id) : [...prev, id]);
|
||||
};
|
||||
|
||||
const filteredVendors = vendors.filter((v) =>
|
||||
(v.companyName || "").toLowerCase().includes(vendorSearch.toLowerCase()) ||
|
||||
(v.tradeSpecialties || "").toLowerCase().includes(vendorSearch.toLowerCase())
|
||||
);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (selectedIds.length === 0) { toast.error("Select at least one vendor"); return; }
|
||||
setSending(true);
|
||||
|
|
@ -186,6 +193,7 @@ function DispatchModal({ wo, onClose, onDispatched }) {
|
|||
await dispatchToVendor(wo.id, {
|
||||
VendorIds: selectedIds,
|
||||
NTEAmount: nteAmount ? parseFloat(nteAmount) : null,
|
||||
ScheduledDate: scheduledDate || null,
|
||||
Description: description,
|
||||
});
|
||||
toast.success(`Dispatched to ${selectedIds.length} vendor(s)`);
|
||||
|
|
@ -208,9 +216,16 @@ function DispatchModal({ wo, onClose, onDispatched }) {
|
|||
<div className="woDispatchBody">
|
||||
<div className="woDispatchField">
|
||||
<label>Select Vendors</label>
|
||||
{!loaded ? <div className="woDispatchLoading">Loading vendors...</div> : vendors.length === 0 ? <div className="woDispatchLoading">No vendors found</div> : (
|
||||
<input
|
||||
className="woDispatchSearch"
|
||||
type="text"
|
||||
value={vendorSearch}
|
||||
onChange={(e) => setVendorSearch(e.target.value)}
|
||||
placeholder="Search vendors..."
|
||||
/>
|
||||
{!loaded ? <div className="woDispatchLoading">Loading vendors...</div> : filteredVendors.length === 0 ? <div className="woDispatchLoading">No vendors found</div> : (
|
||||
<div className="woDispatchVendorList">
|
||||
{vendors.map((v) => (
|
||||
{filteredVendors.map((v) => (
|
||||
<label key={v.id} className={`woDispatchVendorItem ${selectedIds.includes(v.id) ? "selected" : ""}`}>
|
||||
<input type="checkbox" checked={selectedIds.includes(v.id)} onChange={() => toggleVendor(v.id)} />
|
||||
<div>
|
||||
|
|
@ -221,10 +236,24 @@ function DispatchModal({ wo, onClose, onDispatched }) {
|
|||
))}
|
||||
</div>
|
||||
)}
|
||||
{selectedIds.length > 0 && (
|
||||
<div className="woDispatchSelected">
|
||||
{selectedIds.map((id) => {
|
||||
const v = vendors.find((x) => x.id === id);
|
||||
return v ? <span key={id} className="woDispatchSelectedChip">{v.companyName} <button onClick={() => toggleVendor(id)}>×</button></span> : null;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="woDispatchField">
|
||||
<label>NTE Amount</label>
|
||||
<input type="number" className="form-control" value={nteAmount} onChange={(e) => setNteAmount(e.target.value)} placeholder="0.00" step="0.01" min="0" />
|
||||
<div className="woDispatchFieldRow">
|
||||
<div className="woDispatchField woDispatchFieldHalf">
|
||||
<label>NTE Amount</label>
|
||||
<input type="number" className="form-control" value={nteAmount} onChange={(e) => setNteAmount(e.target.value)} placeholder="0.00" step="0.01" min="0" />
|
||||
</div>
|
||||
<div className="woDispatchField woDispatchFieldHalf">
|
||||
<label>Vendor Scheduled Date</label>
|
||||
<input type="date" className="form-control" value={scheduledDate} onChange={(e) => setScheduledDate(e.target.value)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="woDispatchField">
|
||||
<label>Description</label>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue