updates on the design and added the riders route page
This commit is contained in:
@@ -115,18 +115,18 @@ const hasValidPickup = (o) => Number.isFinite(toNum(pickupLat(o))) && Number.isF
|
||||
// FRACTIONAL hours (e.g. 12.5 = 12:30). Half-hour boundaries are supported.
|
||||
// Three named batches, bucketed by assigntime per spec:
|
||||
// • Morning Batch: before 8 AM (00:00 → 08:00)
|
||||
// • Afternoon Batch: 9 AM → 12 PM (09:00 → 12:00)
|
||||
// • Afternoon Batch: 9 AM → 12:30 PM (09:00 → 12:30)
|
||||
// • Evening Batch: 4 PM → 7 PM (16:00 → 19:00)
|
||||
// Gaps (8–9 AM, 12 PM–4 PM, 7 PM+) intentionally fall outside every batch.
|
||||
// Gaps (8–9 AM, 12:30 PM–4 PM, 7 PM+) intentionally fall outside every batch.
|
||||
const BATCHES_DEFAULT_RAW = [
|
||||
{ id: 'morning', name: 'Morning Batch', startHour: 0, endHour: 8 },
|
||||
{ id: 'afternoon', name: 'Afternoon Batch', startHour: 9, endHour: 12 },
|
||||
{ id: 'afternoon', name: 'Afternoon Batch', startHour: 9, endHour: 12.5 },
|
||||
{ id: 'evening', name: 'Evening Batch', startHour: 16, endHour: 19 }
|
||||
];
|
||||
|
||||
// v7: three-named-batch layout (Morning / Afternoon / Evening).
|
||||
// Bumping the key drops cached 5-slot layouts from v6 and earlier.
|
||||
const SLOTS_STORAGE_KEY = 'dispatch.slots.v7';
|
||||
// v8: afternoon batch extended to 12:30 PM. Bumping from v7 wipes the
|
||||
// cached layouts that still hold the old endHour: 12 value.
|
||||
const SLOTS_STORAGE_KEY = 'dispatch.slots.v8';
|
||||
|
||||
// Every prior storage key. Wiped once on mount so stale layouts
|
||||
// from earlier code versions can't reappear on the next page load.
|
||||
@@ -136,7 +136,8 @@ const LEGACY_SLOTS_STORAGE_KEYS = [
|
||||
'dispatch.slots.v3',
|
||||
'dispatch.slots.v4',
|
||||
'dispatch.slots.v5',
|
||||
'dispatch.slots.v6'
|
||||
'dispatch.slots.v6',
|
||||
'dispatch.slots.v7'
|
||||
];
|
||||
|
||||
// Build a label like "Slot 1 · 8 AM" (or "Slot 2 · 12:30 PM") from a
|
||||
@@ -611,7 +612,7 @@ const Ico = ({ children }) => (
|
||||
// to the X-Batch-Window header value the backend expects.
|
||||
const ANALYSIS_BATCH_WINDOWS = [
|
||||
{ key: 'morning', label: 'Morning', timeRange: '12:00 AM – 8:00 AM', sub: 'Early shift orders', color: '#f59e0b', bg: '#fffbeb', border: '#fde68a' },
|
||||
{ key: 'afternoon', label: 'Noon', timeRange: '9:00 AM – 12:00 PM', sub: 'Lunch rush window', color: '#10b981', bg: '#ecfdf5', border: '#a7f3d0' },
|
||||
{ key: 'afternoon', label: 'Noon', timeRange: '9:00 AM – 12:30 PM', sub: 'Lunch rush window', color: '#10b981', bg: '#ecfdf5', border: '#a7f3d0' },
|
||||
{ key: 'evening', label: 'Evening', timeRange: '4:00 PM – 7:00 PM', sub: 'Dinner & end-of-day', color: '#6366f1', bg: '#eef2ff', border: '#c7d2fe' }
|
||||
];
|
||||
|
||||
@@ -1041,6 +1042,12 @@ const Dispatch = ({
|
||||
const preCompareCollapsedRef = useRef(false);
|
||||
const prevCompareOpenRef = useRef(false);
|
||||
|
||||
// Compare-data-panel collapse — mirrors the left sidebar peek tab on the
|
||||
// opposite edge so the operator can hide the right rail and let the map
|
||||
// claim the full width during compare. Resets to expanded each time
|
||||
// Compare opens fresh.
|
||||
const [compareDataCollapsed, setCompareDataCollapsed] = useState(false);
|
||||
|
||||
// Compare UI — step focus on the unified compare map.
|
||||
// focusedCompareStep: null = "overall" (whole day); 1..N = drill into
|
||||
// that single delivery. The unified map zooms to that step's bounds
|
||||
@@ -1725,6 +1732,9 @@ const Dispatch = ({
|
||||
if (compareOpen && !prevCompareOpenRef.current) {
|
||||
preCompareCollapsedRef.current = sidebarCollapsed;
|
||||
setSidebarCollapsed(true);
|
||||
// Fresh compare-open always reveals the data panel — last-collapsed state
|
||||
// shouldn't carry across compare sessions.
|
||||
setCompareDataCollapsed(false);
|
||||
} else if (!compareOpen && prevCompareOpenRef.current) {
|
||||
setSidebarCollapsed(preCompareCollapsedRef.current);
|
||||
}
|
||||
@@ -2726,6 +2736,24 @@ const Dispatch = ({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div id="dispatch-top-tabs" className="dtt-inline">
|
||||
<button
|
||||
type="button"
|
||||
className={`dtt-tab ${topView === 'live' ? 'active' : ''}`}
|
||||
onClick={() => setTopView('live')}
|
||||
>
|
||||
<span className="dtt-icon"><MdMap /></span>
|
||||
Live
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`dtt-tab ${topView === 'analysis' ? 'active' : ''}`}
|
||||
onClick={() => setTopView('analysis')}
|
||||
>
|
||||
<span className="dtt-icon"><MdInsights /></span>
|
||||
Analysis
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Header right-cluster: profit/loss chip, total-orders pill, date picker.
|
||||
@@ -2970,27 +2998,6 @@ const Dispatch = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!embedded && (
|
||||
<div id="dispatch-top-tabs">
|
||||
<button
|
||||
type="button"
|
||||
className={`dtt-tab ${topView === 'live' ? 'active' : ''}`}
|
||||
onClick={() => setTopView('live')}
|
||||
>
|
||||
<span className="dtt-icon"><MdMap /></span>
|
||||
Live
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`dtt-tab ${topView === 'analysis' ? 'active' : ''}`}
|
||||
onClick={() => setTopView('analysis')}
|
||||
>
|
||||
<span className="dtt-icon"><MdInsights /></span>
|
||||
Analysis
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(embedded || topView === 'live') && (<>
|
||||
<div id="strat-row">
|
||||
<button className={`sbt ${viewMode === 'kitchens' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: By Location'); setViewMode('kitchens'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPlace /></span> By Location</button>
|
||||
@@ -3439,7 +3446,7 @@ const Dispatch = ({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div id="body" className={`${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${compareOpen ? 'compare-mode' : ''}`.trim()}>
|
||||
<div id="body" className={`${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${compareOpen ? 'compare-mode' : ''} ${compareOpen && compareDataCollapsed ? 'compare-data-collapsed' : ''}`.trim()}>
|
||||
<button
|
||||
type="button"
|
||||
className={`sidebar-toggle-tab${sidebarCollapsed ? ' is-collapsed' : ''}`}
|
||||
@@ -3449,6 +3456,17 @@ const Dispatch = ({
|
||||
>
|
||||
{sidebarCollapsed ? <MdChevronRight /> : <MdChevronLeft />}
|
||||
</button>
|
||||
{compareOpen && focusedRider && (
|
||||
<button
|
||||
type="button"
|
||||
className={`compare-data-toggle-tab${compareDataCollapsed ? ' is-collapsed' : ''}`}
|
||||
onClick={() => setCompareDataCollapsed((v) => !v)}
|
||||
title={compareDataCollapsed ? 'Show details panel' : 'Hide details panel'}
|
||||
aria-label={compareDataCollapsed ? 'Show details panel' : 'Hide details panel'}
|
||||
>
|
||||
{compareDataCollapsed ? <MdChevronLeft /> : <MdChevronRight />}
|
||||
</button>
|
||||
)}
|
||||
<div id="sidebar">
|
||||
{/* Sidebar header — replaces the top-bar meta line. Hidden when a specific
|
||||
rider is focused, since the focused-rider view already shows that rider's
|
||||
@@ -4859,12 +4877,18 @@ const Dispatch = ({
|
||||
title={
|
||||
`Planned Step ${plannedStepNum}` +
|
||||
(d.deliverycustomer ? ` · ${d.deliverycustomer}` : '') +
|
||||
(d.expectedTs ? ` · ${d.expectedTs.format('hh:mm A')}` : '') +
|
||||
(d.anomaly ? ' · deviation flagged' : '')
|
||||
}
|
||||
>
|
||||
<span className="compare-step-circle">
|
||||
{isLoading ? <span className="compare-step-spin" /> : plannedStepNum}
|
||||
</span>
|
||||
{d.expectedTs && (
|
||||
<span className="compare-step-tick">
|
||||
{d.expectedTs.format('HH:mm')}
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -5176,6 +5200,8 @@ const Dispatch = ({
|
||||
const riders = Array.isArray(raw.rider_timelines) ? raw.rider_timelines : [];
|
||||
const subs = Array.isArray(raw.substitution_opportunities) ? raw.substitution_opportunities : [];
|
||||
const rec = raw.top_recommendation;
|
||||
const hasRecRider = !!(rec && (rec.idle_rider_name || rec.idle_rider_id));
|
||||
const hasRec = !!(rec && rec.action && rec.action !== 'none' && hasRecRider);
|
||||
const win = raw.window || {};
|
||||
|
||||
const fleetMetrics = [
|
||||
@@ -5240,7 +5266,7 @@ const Dispatch = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{rec && (
|
||||
{hasRec ? (
|
||||
<div className="da-section">
|
||||
<div className="da-section-label">Top Recommendation</div>
|
||||
<div className="da-rec">
|
||||
@@ -5289,6 +5315,16 @@ const Dispatch = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="da-section">
|
||||
<div className="da-section-label">Top Recommendation</div>
|
||||
<div className="da-rec da-rec-empty">
|
||||
<div className="da-rec-action">
|
||||
<MdInsights />
|
||||
<span>Fleet is balanced, no reassignment needed right now.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{riders.length > 0 && (
|
||||
|
||||
Reference in New Issue
Block a user