/* global window, React */ // ============================================================ // Edit drawer — manually update one or many trips // ============================================================ const { useState: useEditState } = React; function Field({ label, sub, children }) { return (
{children} {sub &&
{sub}
}
); } function Seg({ value, options, onChange, multi }) { if (multi) { return (
{options.map(o => ( ))}
); } return (
{options.map(o => ( ))}
); } function EditDrawer({ trip, trips, onClose, onSave }) { const isMulti = !trip && trips && trips.length > 0; const targetCount = isMulti ? trips.length : 1; // Tabs — Orders & drops only meaningful when ≥1 trip; works for single OR multi. const [tab, setTab] = useEditState('props'); // Default field values const initialFleet = trip ? trip.fleet : ''; const initialMode = trip ? trip.mode : ''; const initialStatus = trip ? trip.status : ''; const initialPriority = 'normal'; const [fleet, setFleet] = useEditState(initialFleet); const [mode, setMode] = useEditState(initialMode); const [status, setStatus] = useEditState(initialStatus); const [priority, setPriority] = useEditState(initialPriority); const [pickup, setPickup] = useEditState(trip?.routing?.nodes?.[0]?.time || ''); const [delivery, setDelivery] = useEditState(trip?.routing?.nodes?.[2]?.time || ''); const [tolerance, setTolerance] = useEditState(0); const [note, setNote] = useEditState(''); // Orders & drops working state (single-trip primary; bulk uses first checked trip) const workingTrip = trip || (trips && trips[0]); const [moves, setMoves] = useEditState([]); // {orderId, fromTripId, toTripId} const [adds, setAdds] = useEditState([]); // {poolOrderId, toTripId} // For bulk, only show fields where the user explicitly chooses to override const [bulkFields, setBulkFields] = useEditState({ fleet: false, mode: false, status: false, priority: false, delivery: false, }); const toggleBulkField = (k) => setBulkFields(f => ({ ...f, [k]: !f[k] })); return ( <>
{isMulti ? `Bulk edit · ${targetCount} trips` : `Edit · ${trip.id}`}
{isMulti ? `${targetCount} trips · ${new Set(trips.map(t => `${t.route.from}→${t.route.to}`)).size} lanes` : `${trip.route.from} → ${trip.route.to}` }
{isMulti ? trips.map(t => t.id).join(' · ') : trip.customer }
{/* Tabs */}
{tab === 'props' && ( )} {tab === 'orders' && ( )}
); } function EditProperties({ isMulti, targetCount, trip, trips, fleet, setFleet, mode, setMode, status, setStatus, priority, setPriority, pickup, setPickup, delivery, setDelivery, tolerance, setTolerance, note, setNote, bulkFields, toggleBulkField, }) { return ( <> {isMulti && (
Bulk edit. Toggle a field to override it across all {targetCount} trips. Fields left untoggled keep each trip's current value.
)} Fleet type {isMulti && toggleBulkField('fleet')} />} }> {isMulti && !bulkFields.fleet &&
Will keep each trip's current fleet type.
} {!isMulti && trip && trip.fleet !== fleet && (
Changing fleet may require manager approval per policy "Fleet type change from private to spot — always TP decision".
)}
Mode {isMulti && toggleBulkField('mode')} />} }> Status {isMulti && toggleBulkField('status')} />} }> Priority {isMulti && toggleBulkField('priority')} />} }> {!isMulti && ( <>
setPickup(e.target.value)} /> setDelivery(e.target.value)} />
setTolerance(parseFloat(e.target.value))} style={{ flex: 1 }} /> {tolerance === 0 ? 'none' : `+${tolerance}h`}
{tolerance > 4 &&
Tolerance {tolerance}h exceeds 4h — requires Carrier Manager sign-off.
}
)} {isMulti && ( Window tolerance {isMulti && toggleBulkField('delivery')} />} }>
setTolerance(parseFloat(e.target.value))} style={{ flex: 1 }} /> {tolerance === 0 ? 'none' : `+${tolerance}h`}
)}
TP Agent will: re-run feasibility on {isMulti ? `${targetCount} trips` : 'this trip'} after save · update HOS gap · refresh fleet recommendations · log edit to audit trail.
); } // ============================================================ // Orders & drops — add from pool, move drops between trips, // visualise on the route map. // ============================================================ function EditOrdersDrops({ isMulti, primaryTrip, trips, moves, setMoves, adds, setAdds }) { const allTrips = window.TRIPS || []; const [activeTripId, setActiveTripId] = React.useState(primaryTrip ? primaryTrip.id : ''); const activeTrip = allTrips.find(t => t.id === activeTripId) || primaryTrip; // Other trips this user can move drops to: in multi-edit, the bulk-selected trips; // in single, all other planned trips on a similar lane. const moveTargets = isMulti && trips ? trips.filter(t => t.id !== activeTrip.id) : allTrips.filter(t => t.id !== activeTrip.id && t.status === 'PLANNED'); // Pool orders available to add — use activeTrip.poolOrders + cross-lane pool. const pool = activeTrip.poolOrders || []; const addToTrip = (poolOrder) => { if (adds.some(a => a.poolOrderId === poolOrder.id)) return; setAdds([...adds, { poolOrderId: poolOrder.id, toTripId: activeTrip.id, meta: poolOrder }]); }; const removeAdd = (id) => setAdds(adds.filter(a => a.poolOrderId !== id)); const moveDrop = (orderId, toTripId) => { setMoves([...moves.filter(m => m.orderId !== orderId), { orderId, fromTripId: activeTrip.id, toTripId }]); }; const undoMove = (orderId) => setMoves(moves.filter(m => m.orderId !== orderId)); // Build the "after save" projection for the active trip const movedAway = new Set(moves.filter(m => m.fromTripId === activeTrip.id).map(m => m.orderId)); const addedHere = adds.filter(a => a.toTripId === activeTrip.id); const projectedOrders = activeTrip.orders.filter(o => !movedAway.has(o.id)); const projectedLbs = projectedOrders.reduce((a, o) => a + o.lbs, 0) + addedHere.reduce((a, x) => { // pool order meta has "X pal · Y lbs · ..." const m = (x.meta?.meta || '').match(/(\d[\d,]*)\s*lbs/); return a + (m ? parseInt(m[1].replace(/,/g, ''), 10) : 0); }, 0); const projectedUtil = Math.round((projectedLbs / 45000) * 100); const utilKind = projectedUtil > 100 ? 'red' : projectedUtil > 95 ? 'amber' : projectedUtil < 70 ? 'amber' : 'green'; // Extra trips for map preview = the trips orders are being moved TO const moveTargetIds = Array.from(new Set(moves.map(m => m.toTripId))); const extraTripsForMap = moveTargetIds .map(id => allTrips.find(t => t.id === id)) .filter(Boolean); return (
{/* Trip switcher when bulk editing */} {isMulti && trips && (
Editing {trips.map(t => ( ))}
)} {/* Live impact summary */}
Active trip
{activeTrip.id}
{activeTrip.route.from} → {activeTrip.route.to}
After save
{projectedUtil}%
{projectedOrders.length + addedHere.length} orders · {projectedLbs.toLocaleString()} lbs
Changes staged
{moves.length + adds.length}
{adds.length} add{adds.length === 1 ? '' : 's'} · {moves.length} move{moves.length === 1 ? '' : 's'}
{/* Two-column layout: current orders + pool */}
{/* Current orders / drops */}
Drops on {activeTrip.id} · {activeTrip.orders.length} Reorder, remove, or move to another trip
{activeTrip.orders.map(o => { const moveTo = moves.find(m => m.orderId === o.id); return (
{o.id}
{o.customer}
{o.pallets} pal · {o.lbs.toLocaleString()} lbs · ship {o.ship}
{moveTo ? (
→ moving to {moveTo.toTripId}
) : (
)}
); })} {addedHere.length > 0 && (
+ added from pool
)} {addedHere.map(a => (
{a.poolOrderId} POOL
{a.meta?.customer}
{a.meta?.meta}
))}
{/* Pool orders */}
Pool · same lane · {pool.length} Tap + to add to this trip
{pool.length === 0 &&
No pool orders on this lane.
} {pool.map(p => { const already = adds.some(a => a.poolOrderId === p.id); return (
{p.id}
{p.customer}
{p.meta}
); })}
{/* Map preview */}
Map preview
Solid line = {activeTrip.id} · dashed = trips receiving moved drops {moveTargetIds.length === 0 && adds.length === 0 ? ' · no changes staged yet' : ''}
); } function Toggle({ on, onChange }) { return ( ); } Object.assign(window, { EditDrawer });