/* ══ Dispatch (xpressconsole Design Implementation) ═════════════════════════ Curated palette with deep purple/indigo accents (#662582), glassmorphic elements, live telemetry cards and interactive map timelines. */ /* ── Dispatch's palette is now the console's ──────────────────────────────── These fourteen names are kept, and every one of them is an ALIAS. About fifty rules in this file reference them, so pointing the names at the shared tokens restyles the whole page at once — and, more to the point, makes it impossible for Dispatch to drift away from the rest of the console again. It had drifted. This file arrived as a port of another product and brought that product's palette with it: `#e2e8f0` for a hairline where the console uses `#e0e4ea`, `#1e293b` for body text where the console uses `#0f172a`, `#f8fafc` for a sunken fill against the console's `#fafbfc`. Each is a shade off the real token — close enough to look like a rendering inconsistency rather than a decision, and far enough that a Dispatch card beside a Console card were visibly two different whites. The two shadow steps are now the card's, which is to say nothing at all. `--dispatch-shadow-lg` is referenced by no rule in this file and is gone rather than aliased. */ :root { --dispatch-bg: var(--color-surface); --dispatch-bg-sub: var(--color-surface-subtle); --dispatch-card-bg: var(--card-bg); --dispatch-border: var(--color-line); --dispatch-border-active: var(--color-brand); --dispatch-text: var(--color-ink-1); --dispatch-text-muted: var(--color-ink-3); --dispatch-accent: var(--color-brand); --dispatch-accent-light: #9255ab; --dispatch-accent-soft: color-mix(in oklab, var(--color-brand) 8%, transparent); --dispatch-success: var(--color-success, #1f9d55); --dispatch-warning: var(--color-warning, #b7860b); --dispatch-error: var(--color-error, #d64545); --dispatch-shadow-sm: var(--card-shadow); --dispatch-shadow-md: var(--card-shadow); } .dispatch-container { display: flex; flex-direction: column; gap: 8px; width: 100%; color: var(--dispatch-text); font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif); } /* ── Top Operations Header (xpressconsole #hdr) ───────────────────────────── GONE, because it was never rendered. This file arrived as a port of another product, and it brought that product's whole operations header with it: `.dispatch-header`, a logo lockup, a city picker, a demo/live toggle, a live-GPS chip, a running clock, and `.date-chip` — a pill with prev/next arrows for stepping through days. Not one of those class names appears in any markup in this repository. The Dispatch page reads the date from `useDateScope()`, which is the range picker in the app's own top bar; two comments in `DispatchPage.tsx` already tell the reader to "change the date range in the top bar". So the header was 215 lines of CSS for a component that does not exist, and `.date-chip` was a date filter nobody can see. If the ported header is wanted later it is in the git history, but it would need rebuilding against this console's own controls rather than restoring: the date range, the branch scope and the account menu are all in the app shell, and a second set of them on one page is the drift this pass has been removing everywhere else. */ /* ── Strategy & View Mode Bar (xpressconsole #strat-row) ───────────────────── */ .strat-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 6px 12px; min-height: 42px; background: var(--dispatch-card-bg); border: 1px solid var(--dispatch-border); border-radius: var(--card-radius); box-shadow: var(--dispatch-shadow-sm); flex-wrap: wrap; } .strat-tabs { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; } .strat-ops-cluster { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-left: auto; } .sbt { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 8px; border: 1px solid var(--dispatch-border); background: var(--dispatch-bg); color: var(--dispatch-text-muted); font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); font-family: inherit; } .sbt:hover { background: var(--dispatch-bg-sub); color: var(--dispatch-text); border-color: rgba(102, 37, 130, 0.3); } .sbt.active { background: var(--dispatch-accent); border-color: var(--dispatch-accent); color: #ffffff; box-shadow: 0 3px 10px rgba(102, 37, 130, 0.25); } .sbt-icon { display: inline-flex; align-items: center; } .sbt-badge { display: inline-flex; align-items: center; justify-content: center; min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px; background: var(--dispatch-bg-sub); font-size: 10px; font-weight: 700; color: var(--dispatch-text); } .sbt.active .sbt-badge { background: rgba(255, 255, 255, 0.25); color: #ffffff; } .sbt-dot-live { width: 5px; height: 5px; border-radius: 50%; background: #22c55e; animation: pulse-ring 1.5s infinite; } /* Wave / Batch Slots */ /* No KPI overrides. The shared `.kpi-grid` and `KpiCard` are what every other page uses. This file used to re-grid them at minmax(180px) and force a 74px min-height and 8px padding with !important, which is the only reason the dispatch cards looked unlike the ones on Sales. */ /* ── Main Operations Workspace ────────────────────────────────────────────── */ .dispatch-body { display: grid; grid-template-columns: 1fr; gap: 10px; align-items: start; } @media (min-width: 1050px) { .dispatch-body { grid-template-columns: 310px minmax(0, 1fr); } } .dispatch-rail { display: flex; flex-direction: column; gap: 10px; min-width: 0; } .dispatch-main { min-width: 0; } /* Rail Search and Filter */ .rail-search-box { position: relative; display: flex; align-items: center; width: 100%; } .rail-search-box .search-icon { position: absolute; left: 10px; color: var(--dispatch-text-muted); pointer-events: none; } .rail-search-input { width: 100%; padding: 8px 30px 8px 32px; border-radius: 10px; border: 1px solid var(--dispatch-border); background: #ffffff; font-size: 12.5px; font-family: inherit; color: var(--dispatch-text); outline: none; transition: all 0.15s ease; } .rail-search-input:focus { border-color: var(--dispatch-accent); box-shadow: 0 0 0 3px var(--dispatch-accent-soft); } .search-clear-btn { position: absolute; right: 8px; background: none; border: 0; font-size: 16px; color: var(--dispatch-text-muted); cursor: pointer; } .rail-filter-chips { display: flex; align-items: center; gap: 4px; overflow-x: auto; padding-bottom: 2px; } .filter-pill { padding: 4px 9px; border-radius: 999px; border: 1px solid var(--dispatch-border); background: #ffffff; font-size: 11px; font-weight: 600; color: var(--dispatch-text-muted); cursor: pointer; white-space: nowrap; transition: all 0.15s; } .filter-pill:hover { background: var(--dispatch-bg-sub); color: var(--dispatch-text); } .filter-pill.active { background: var(--dispatch-accent-soft); border-color: rgba(102, 37, 130, 0.35); color: var(--dispatch-accent); font-weight: 700; } /* ── Rail Group Cards (xpressconsole rcard design) ────────────────────────── */ .group-list { display: flex; flex-direction: column; gap: 10px; } .dispatch-container .rcard, .rcard { background: var(--card-bg); border: 1px solid var(--dispatch-border); border-radius: var(--card-radius); padding: 14px 16px; cursor: pointer; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); position: relative; text-align: left; user-select: none; } /* Hover is the line, not a lift -- see the note on `.pcard` in index.css. */ .dispatch-container .rcard:hover, .rcard:hover { border-color: var(--dispatch-accent); } .dispatch-container .rcard.is-focused, .dispatch-container .rcard[data-active='true'], .rcard.is-focused, .rcard[data-active='true'] { border-color: var(--dispatch-accent); background: linear-gradient(180deg, #ffffff, rgba(102, 37, 130, 0.02)); box-shadow: 0 0 0 2px var(--dispatch-accent), 0 8px 20px rgba(102, 37, 130, 0.12); transform: translateY(-2px); } .dispatch-container .rcard[data-urgent='true'], .rcard[data-urgent='true'] { border-color: #fca5a5; background: #fffafa; } .dispatch-container .rcard-top, .rcard-top { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; } .dispatch-container .rcard-emo, .rcard-emo { width: 38px; height: 38px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 18px; border: 1.5px solid var(--dispatch-border); flex-shrink: 0; transition: transform 0.2s ease; } .dispatch-container .rcard:hover .rcard-emo, .rcard:hover .rcard-emo { transform: scale(1.05); } .dispatch-container .rcard-info, .rcard-info { flex: 1; min-width: 0; } .dispatch-container .rcard-name, .rcard-name { font-size: 14.5px; font-weight: 700; color: var(--dispatch-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; letter-spacing: -0.01em; } .dispatch-container .rcard-zone, .rcard-zone { font-size: 12px; color: var(--dispatch-text-muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .dispatch-container .rcard-badge, .rcard-badge { font-size: 12px; font-weight: 700; padding: 3px 9px; border-radius: 8px; background: var(--dispatch-bg-sub); color: #475569; font-variant-numeric: tabular-nums; font-feature-settings: 'tnum'; white-space: nowrap; border: 1px solid var(--dispatch-border); } /* All deliveries done — flip to green so it pops vs the per-rider tint */ .dispatch-container .rcard-badge.is-done, .rcard-badge.is-done { background: rgba(22, 163, 74, 0.12) !important; color: #16a34a !important; border-color: rgba(22, 163, 74, 0.3) !important; } .dispatch-container .bar-bg, .bar-bg { background: var(--dispatch-bg-sub); border-radius: 4px; height: 5px; overflow: hidden; margin-bottom: 10px; } .dispatch-container .bar-fg, .bar-fg { height: 100%; border-radius: 4px; transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .dispatch-container .rcard-meta, .rcard-meta { display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: var(--dispatch-text-muted); font-weight: 500; } .dispatch-container .rcard-meta span, .rcard-meta span { display: inline-flex; align-items: center; gap: 4px; } .dispatch-container .rcard-est-meters, .rcard-est-meters { color: #0284c7; font-weight: 600; background: rgba(2, 132, 199, 0.08); padding: 1px 6px; border-radius: 4px; } .dispatch-container .step-ids, .step-ids { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 4px; } .dispatch-container .step-id, .step-id { font-size: 10px; font-weight: 700; padding: 2px 6px; border-radius: 4px; background: var(--dispatch-bg-sub); color: var(--dispatch-text-muted); border: 1px solid var(--dispatch-border); transition: all 0.15s ease; } .dispatch-container .step-id.is-done, .step-id.is-done { background: rgba(22, 163, 74, 0.08); color: #16a34a; border-color: rgba(22, 163, 74, 0.25); } .dispatch-container .step-id-more, .step-id-more { background: var(--dispatch-accent-soft); color: var(--dispatch-accent); border-color: rgba(102, 37, 130, 0.2); } /* ── Main Area Cards & Map Header ─────────────────────────────────────────── */ .dispatch-card-elevated, .dispatch-focus-card, .dispatch-map-card { background: var(--card-bg) !important; border: var(--card-border) !important; border-radius: var(--card-radius) !important; box-shadow: var(--card-shadow) !important; overflow: hidden; } /* Focused Rider Live Summary Bar */ .group-detail-avatar { width: 42px; height: 42px; border-radius: 12px; display: flex; align-items: center; justify-content: center; color: #ffffff; font-weight: 800; font-size: 14px; } .rider-phone-chip { display: inline-flex; align-items: center; gap: 4px; font-size: 11.5px; font-weight: 600; color: var(--dispatch-accent); background: var(--dispatch-accent-soft); padding: 2px 8px; border-radius: 6px; } .focus-metrics-cluster { display: flex; align-items: center; gap: 8px; } .focus-pill { font-size: 12px; font-weight: 600; padding: 6px 12px; border-radius: 10px; background: var(--dispatch-bg-sub); border: 1px solid var(--dispatch-border); color: var(--dispatch-text); } .focus-pill.value { background: var(--dispatch-accent-soft); border-color: rgba(102, 37, 130, 0.25); color: var(--dispatch-accent); font-weight: 700; } .rider-live-summary-bar { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; padding: 10px 14px; background: var(--dispatch-bg-sub); border: 1px solid var(--dispatch-border); border-radius: var(--card-radius); } .rl-item { display: flex; flex-direction: column; gap: 2px; } .rl-label { font-size: 10.5px; font-weight: 700; color: var(--dispatch-text-muted); text-transform: uppercase; letter-spacing: 0.05em; } .rl-val { font-size: 13px; font-weight: 700; color: var(--dispatch-text); } .status-badge-live { color: var(--dispatch-success) !important; } /* ── Stop Sequence Timeline Carousel (under Map) ──────────────────────────── */ .round-timeline-section, .stops-quick-carousel-wrap { display: flex; flex-direction: column; gap: 10px; margin-top: 10px; padding-top: 12px; border-top: 1px solid var(--dispatch-border); } .timeline-section-head, .quick-carousel-title { display: flex; align-items: center; gap: 6px; font-size: 12px; font-weight: 700; color: var(--dispatch-accent); text-transform: uppercase; letter-spacing: 0.04em; } .timeline-cards-row, .stops-timeline-carousel { display: flex; align-items: stretch; gap: 10px; overflow-x: auto; padding-bottom: 8px; } .timeline-step-card, .stop-timeline-card { flex: 0 0 220px; display: flex; flex-direction: column; justify-content: space-between; gap: 8px; padding: 10px 12px; border-radius: var(--card-radius); border: 1px solid var(--dispatch-border); background: var(--card-bg); cursor: pointer; transition: border-color 0.18s ease, background 0.18s ease; } .timeline-step-card:hover, .stop-timeline-card:hover { border-color: var(--dispatch-accent); } .timeline-step-card.is-current { border-color: var(--dispatch-accent); background: linear-gradient(180deg, #ffffff, rgba(102, 37, 130, 0.04)); box-shadow: 0 0 0 2px var(--dispatch-accent); } .tsc-head, .stop-card-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; } .tsc-num, .stop-seq-badge { font-size: 11px; font-weight: 800; color: var(--dispatch-accent); background: var(--dispatch-accent-soft); padding: 2px 6px; border-radius: 6px; } .tsc-status, .stop-card-status { font-size: 10.5px; font-weight: 700; padding: 2px 6px; border-radius: 6px; text-transform: capitalize; } .tsc-body, .stop-card-body { display: flex; flex-direction: column; gap: 2px; } .tsc-cust, .stop-card-body strong { font-size: 12.5px; font-weight: 700; color: var(--dispatch-text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .tsc-addr, .stop-card-addr { font-size: 11px; color: var(--dispatch-text-muted); line-height: 1.35; display: -webkit-box; -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; } .tsc-foot, .stop-card-foot { display: flex; align-items: center; justify-content: space-between; font-size: 11px; font-weight: 600; padding-top: 6px; border-top: 1px dashed var(--dispatch-border); } .tsc-val, .stop-val { color: var(--dispatch-accent); font-weight: 700; } .tsc-order { color: var(--dispatch-text-muted); } .stop-eta { color: var(--dispatch-success); font-weight: 700; } /* Loading and Empty Rail States */ .rail-loading-state, .rail-empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 24px; gap: 8px; color: var(--dispatch-text-muted); font-size: 13px; font-weight: 600; } .spin-icon { animation: spin 1s linear infinite; color: var(--dispatch-accent); } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* ── Stops Table Styling ─────────────────────────────────────────────────── */ .table-scroll { width: 100%; overflow-x: auto; } .stops-table { width: 100%; border-collapse: collapse; font-size: 13px; } .stops-table th { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--dispatch-border); font-size: 11.5px; font-weight: 700; color: var(--dispatch-text-muted); text-transform: uppercase; letter-spacing: 0.04em; white-space: nowrap; } .stops-table td { padding: 10px 14px; border-bottom: 1px solid rgba(226, 232, 240, 0.6); vertical-align: top; } .stops-table tbody tr { cursor: pointer; transition: background 0.12s; } .stops-table tbody tr:hover { background: var(--dispatch-bg-sub); } .stops-table td strong { display: block; font-size: 13px; font-weight: 700; color: var(--dispatch-text); } .stops-table td span { display: block; font-size: 11.5px; color: var(--dispatch-text-muted); max-width: 240px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .stops-table td.num { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 600; } .stop-status { display: inline-flex; padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; text-transform: capitalize; white-space: nowrap; } /* ── Auto-assign Banner ──────────────────────────────────────────────────── */ .aa-launch { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; padding: 12px 14px; border: 1px solid rgba(102, 37, 130, 0.25); border-radius: var(--card-radius); background: linear-gradient(135deg, rgba(102, 37, 130, 0.06), rgba(146, 85, 171, 0.03)); } .aa-launch span { flex: 1 1 260px; font-size: 12px; line-height: 1.45; color: var(--dispatch-text); } .aa-launch-btn { display: inline-flex; gap: 7px; align-items: center; flex: none; height: 34px; padding: 0 16px; border: 0; border-radius: 8px; background: var(--dispatch-accent); font-size: 13px; font-weight: 700; color: #ffffff; cursor: pointer; box-shadow: 0 3px 10px rgba(102, 37, 130, 0.25); transition: all 0.15s; } .aa-launch-btn:hover { background: #501b67; transform: translateY(-1px); }