updates on the responsiveness

This commit is contained in:
2026-09-28 12:02:28 +05:30
parent d288197c35
commit 912f3cbcd4
7 changed files with 718 additions and 665 deletions

1115
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,4 @@
import { Suspense, lazy } from 'react';
import { Toaster } from '@/components/ui/toaster';
import AppToaster from '@/components/third-party/AppToaster';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClientInstance } from '@/lib/query-client';
@@ -176,7 +175,6 @@ function App() {
<ScrollToTop />
<AuthenticatedApp />
</Router>
<Toaster />
{/* Every toast in the app (OpenToast, the design system's `toast`,
direct react-hot-toast calls) renders here: top-right, one card
design. Without a mounted renderer every toast fires into

View File

@@ -486,7 +486,7 @@ export default function Bookings() {
/>
</div>
<div className="flex min-w-0 flex-1 flex-wrap items-center justify-end gap-2 sm:flex-none">
<div className="flex min-w-0 flex-1 flex-wrap items-center justify-end gap-2 lg:flex-none">
<ListToolbar
search={search}
onSearchChange={setSearch}

View File

@@ -43,6 +43,32 @@
box-sizing: border-box;
}
/* ── Size container ─────────────────────────────────────────────────────────
Every max-width breakpoint in this file is a CONTAINER query against this
wrapper, not a viewport media query. The MileTruth dock narrows the page
without changing the viewport: 1366px screen, panel open, ~920px for this
page — and the old @media rules still applied their 1366px layout, so the
header row overflowed and clipped the date picker and clock.
Side effect, accepted: container queries need layout containment, which
makes this wrapper the containing block for position:fixed descendants.
The centred order card and the rider-position modal therefore centre on
the Control X area rather than the whole window.
max-height rules stay @media — height isn't what the dock takes. */
.dispatch-shell {
container: dispatch / inline-size;
width: 100%;
min-width: 0;
display: flex;
flex-direction: column;
flex: 1 1 auto;
}
.dispatch-shell.embedded {
height: 100%;
min-height: 0;
}
/* Header */
.dispatch-container #hdr {
height: 56px;
@@ -5753,7 +5779,7 @@
────────────────────────────────────────────────────────────────── */
/* Large laptop — subtle sidebar reduction */
@media (max-width: 1280px) {
@container dispatch (max-width: 1280px) {
.dispatch-container #sidebar {
width: 360px;
flex-basis: 360px;
@@ -5769,7 +5795,7 @@
}
/* Compact laptop (common 1366×768 screens) */
@media (max-width: 1180px) {
@container dispatch (max-width: 1180px) {
.dispatch-container #sidebar {
width: 320px;
flex-basis: 320px;
@@ -5816,7 +5842,7 @@
}
/* Small laptop / 1024px */
@media (max-width: 1080px) {
@container dispatch (max-width: 1080px) {
.dispatch-container #sidebar {
width: 290px;
flex-basis: 290px;
@@ -5830,12 +5856,9 @@
left: 0;
}
/* Header — hide decorative city pill, tighten spacing */
.dispatch-container .logo-city,
.dispatch-container .logo-city-wrap {
display: none;
}
/* The hub selector used to be hidden here to save width. It is the only way
to switch hub, so it stays; the header wraps onto a second line instead
(see the #hdr rule under "Header wrap"). */
.dispatch-container .logo-name {
font-size: 16px;
}
@@ -5962,7 +5985,7 @@
}
/* Very small laptop / tablet landscape — 960px */
@media (max-width: 960px) {
@container dispatch (max-width: 960px) {
.dispatch-container #sidebar {
width: 250px;
flex-basis: 250px;
@@ -6578,7 +6601,7 @@
}
/* Mobile — collapse the sidebar above the main panel, single-column stats */
@media (max-width: 600px) {
@container dispatch (max-width: 600px) {
.dispatch-container .rider-info-mode {
flex-direction: column;
}
@@ -6601,7 +6624,7 @@
}
/* ── Laptop Responsive Tuning (max-width: 1366px) ── */
@media (max-width: 1366px) {
@container dispatch (max-width: 1366px) {
/* Header adjustments */
.dispatch-container #hdr {
@@ -7247,7 +7270,7 @@
color: var(--accent, #000000);
}
@media (max-width: 1366px) {
@container dispatch (max-width: 1366px) {
.dispatch-container .compare-delta.is-collapsible.is-collapsed {
padding-bottom: 8px;
}
@@ -7995,7 +8018,7 @@
stacks on top, then the unified map, then the data panel scrolls
below. The map gets a generous min-height so the layer-switcher
overlay and timeline stay usable on tablets. */
@media (max-width: 1100px) {
@container dispatch (max-width: 1100px) {
.dispatch-container #body.compare-mode {
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto minmax(360px, 1fr) auto;
@@ -9240,7 +9263,7 @@
gap: 12px;
}
@media (max-width: 720px) {
@container dispatch (max-width: 720px) {
.dispatch-container .da-picker-row {
grid-template-columns: 1fr;
}
@@ -9615,7 +9638,7 @@
grid-template-columns: repeat(3, minmax(0, 1fr));
}
@media (max-width: 720px) {
@container dispatch (max-width: 720px) {
.dispatch-container .da-metric-grid-3 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -10162,7 +10185,7 @@
gap: 12px;
}
@media (max-width: 900px) {
@container dispatch (max-width: 900px) {
.dispatch-container .da-hero-row {
grid-template-columns: 1fr;
}
@@ -10232,13 +10255,13 @@
gap: 12px;
}
@media (max-width: 1100px) {
@container dispatch (max-width: 1100px) {
.dispatch-container .da-health-row {
grid-template-columns: 1fr 1fr;
}
}
@media (max-width: 600px) {
@container dispatch (max-width: 600px) {
.dispatch-container .da-health-row {
grid-template-columns: 1fr;
}
@@ -10588,7 +10611,7 @@
the rider/batch sidebar flows below it full-width and scrolls. Desktop
(≥ md) is untouched; every rule here is gated inside this media query.
========================================================================= */
@media (max-width: 768px) {
@container dispatch (max-width: 768px) {
/* The standalone page un-does MainCard's 24px padding via negative margin
+ 100vw-ish sizing; keep that, just allow the inner body to grow with
@@ -10763,7 +10786,7 @@
}
/* Date picker chip responsive utilities */
@media (max-width: 1280px) {
@container dispatch (max-width: 1280px) {
.dispatch-container .date-chip-full {
display: none !important;
}
@@ -10779,7 +10802,7 @@
}
}
@media (max-width: 768px) {
@container dispatch (max-width: 768px) {
.dispatch-container .date-chip-nav {
display: none !important;
}
@@ -10859,3 +10882,113 @@
line-height: 1.45;
color: #64748b;
}
/* ── Header: one line, compacting in steps ─────────────────────────────────
The header is title+hub+Live+Batch | date | clock. It used to WRAP below
1180px, which on a 1366 laptop with MileTruth open (≈920px here) left the
clock alone on a second line under the title — it looked misaligned, and
the row missed fitting by 3px. Instead of wrapping, each step below drops
something the operator can do without, so the row stays on ONE line down
to tablet width. Only phones (≤640px) get two lines, laid out on purpose.
Kept LAST in the file: earlier blocks (1366px etc.) pin #hdr's height and
padding, and a rule of equal specificity placed before them would lose. */
/* ≤1180: drop the "BATCH:" caption (the picker still names the batch) and
tighten gaps. At 920px this leaves ~50px to spare. */
@container dispatch (max-width: 1180px) {
.dispatch-container #hdr {
flex-wrap: nowrap;
gap: 8px;
padding-left: 12px;
padding-right: 12px;
}
.dispatch-container .logo {
gap: 8px;
flex: 0 1 auto;
}
.dispatch-container #dispatch-top-tabs {
gap: 6px;
}
.dispatch-container .dx-batch-label {
display: none;
}
.dispatch-container .hdr-stats {
flex: 0 0 auto;
gap: 6px;
margin-left: auto;
margin-right: 0;
}
.dispatch-container #clock {
flex: 0 0 auto;
}
}
/* ≤900 (tablet, or small laptop with the assistant open): the page title
text goes (the D logo stays), Live becomes icon-only, the TODAY pill goes,
and a long hub name truncates instead of pushing the row. */
@container dispatch (max-width: 900px) {
.dispatch-container .logo-name,
.dispatch-container .dtt-label,
.dispatch-container .date-chip-today-pill {
display: none;
}
.dispatch-container .dtt-tab {
padding-left: 10px;
padding-right: 10px;
}
.dispatch-container .logo-city-text {
max-width: 96px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
/* ≤640 (phones): one line is not physically possible, so two deliberate
lines — [logo · hub · Live · Batch] then [date ········ clock], with the
clock pinned right on the same line as the date rather than dropping
under the logo on its own. */
@container dispatch (max-width: 640px) {
.dispatch-container #hdr {
height: auto;
min-height: 48px;
flex-wrap: wrap;
row-gap: 6px;
padding-top: 6px;
padding-bottom: 6px;
}
.dispatch-container .logo {
flex: 1 1 100%;
min-width: 0;
}
.dispatch-container .hdr-stats {
flex: 1 1 auto;
margin-left: 0;
min-width: 0;
}
.dispatch-container #clock {
margin-left: auto;
}
}
/* ≤480 (phones): the title group itself can't fit hub + Live + Batch picker
side by side (at 414px the picker was cut off), so let it wrap inside its
own line too. */
@container dispatch (max-width: 480px) {
.dispatch-container .logo,
.dispatch-container #dispatch-top-tabs {
flex-wrap: wrap;
row-gap: 6px;
}
}

View File

@@ -16,8 +16,6 @@ import {
MdRestaurant,
MdPublic,
MdInventory2,
MdTrendingUp,
MdTrendingDown,
MdAccountBalanceWallet,
MdStraighten,
MdLocationOn,
@@ -361,6 +359,27 @@ function MapAutoResize({ trigger }) {
const timers = [0, 60, 120, 200, 300, 380].map((t) => setTimeout(() => map.invalidateSize({ animate: false }), t));
return () => timers.forEach(clearTimeout);
}, [trigger, map]);
// The pulse above only runs for the layout changes it is told about. The map
// box also changes size when the MileTruth panel opens or closes, when the
// header wraps or unwraps, and when the window moves between breakpoints —
// none of which fire a window resize. The map then kept drawing tiles for
// its OLD width: dragging it showed a grey band on the right with no tiles.
// Watching the container itself covers every cause at once.
useEffect(() => {
const el = map.getContainer();
if (!el || typeof ResizeObserver === 'undefined') return undefined;
let frame = 0;
const observer = new ResizeObserver(() => {
cancelAnimationFrame(frame);
frame = requestAnimationFrame(() => map.invalidateSize({ animate: false }));
});
observer.observe(el);
return () => {
cancelAnimationFrame(frame);
observer.disconnect();
};
}, [map]);
return null;
}
@@ -3592,6 +3611,12 @@ const Dispatch = ({
};
return (
// .dispatch-shell is the size container Dispatch.css's breakpoints query.
// They used to be viewport media queries, which can't see the MileTruth
// dock: on a 1366px screen with the panel open this page gets ~920px but
// every rule still behaved as if it had 1366px, so the header never wrapped
// and the date picker and clock were clipped off the right edge.
<div className={`dispatch-shell${embedded ? ' embedded' : ''}`}>
<div className={`dispatch-container${embedded ? ' embedded' : ''}${compareOpen ? ' compare-open' : ''}`}>
{!embedded && (
<div id="hdr">
@@ -3642,7 +3667,7 @@ const Dispatch = ({
<span className="dtt-icon">
<MdMap />
</span>
Live
<span className="dtt-label">Live</span>
</button>
{shouldFetchLive && viewMode !== 'rider-info' && (
<div className="relative inline-flex items-center ml-1" ref={batchMenuRef}>
@@ -3652,7 +3677,7 @@ const Dispatch = ({
className="flex items-center gap-2 bg-white hover:bg-slate-50 border border-slate-200/90 rounded-lg px-3 py-1.5 text-xs font-semibold text-slate-800 shadow-2xs transition-all cursor-pointer select-none"
>
<MdAccessTime className="text-slate-400 w-3.5 h-3.5 shrink-0" />
<span className="text-[10px] font-bold text-slate-400 uppercase tracking-wider">Batch:</span>
<span className="dx-batch-label text-[10px] font-bold text-slate-400 uppercase tracking-wider">Batch:</span>
<span className="font-bold text-slate-900 text-xs">
{BATCHES.find((b) => b.id === selectedBatch)?.label || 'All Batches'}
</span>
@@ -3726,40 +3751,16 @@ const Dispatch = ({
</div>
</div>
{/* Header right-cluster: profit/loss chip, total-orders pill, date picker.
Sits to the LEFT of the running clock so the operator sees fleet
health + current wave size + selected date together in one row. */}
{/* Header right-cluster: date picker (plus a "Failed to load" notice
when the live feed errors). The revenue chip and the
Loading / "N orders" pill that used to sit here were removed on
request — the sidebar tiles already show the order count, and the
header now has room to stay on one line on more screens. The
error notice stays: without it a failed load looks exactly like a
day with no orders. */}
<div className="hdr-stats">
{(() => {
const isLoss = activeStats.profit < 0;
const amount = Math.abs(activeStats.profit);
return (
<span
className={`strat-stat ${isLoss ? 'strat-stat-loss' : 'strat-stat-profit'}`}
title={`${isLoss ? 'Loss' : 'Revenue'} (${activeStats.label})`}
>
<span className="strat-stat-icon">{isLoss ? <MdTrendingDown /> : <MdTrendingUp />}</span>
<span className="strat-stat-label">{isLoss ? 'Loss' : 'Revenue'}</span>
<span className="strat-stat-value">
{isLoss ? '-' : ''}₹{amount.toFixed(0)}
</span>
</span>
);
})()}
{shouldFetchLive && (
<>
{liveIsFetching && (
<span className="live-status">
<span className="live-dot" /> Loading {liveRows.length ? `· ${liveRows.length} loaded` : ''}
</span>
)}
{!liveIsFetching && !liveIsError && (
<span className="live-status live-status-ready">
<span className="live-dot ready" /> {filteredLiveRows.length} orders
<span className="live-status-sub"> / {liveRows.length} total</span>
</span>
)}
{liveIsError && (
<span className="live-status live-status-error">
<span className="live-dot error" /> Failed to load
@@ -7191,6 +7192,7 @@ const Dispatch = ({
</div>
)}
</div>
</div>
);
};

View File

@@ -2247,6 +2247,9 @@ export default function Home() {
{/* BOTTOM APP LAUNCHER & SUITES */}
<footer className="w-full pt-4 pb-2 border-t border-slate-100">
{/* Tiles wrap inside each group: on a phone a group column is ~160px and
three 72px tiles didn't fit, so the row ran past the screen and
the whole page scrolled sideways. */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-6 md:gap-8 max-w-5xl mx-auto">
{/* 1. OPERATIONS */}
@@ -2254,7 +2257,7 @@ export default function Home() {
<div className="text-[11px] font-bold uppercase tracking-wider text-slate-400 mb-3 px-1">
OPERATIONS
</div>
<div className="flex items-start gap-2.5 sm:gap-3">
<div className="flex flex-wrap items-start gap-2.5 sm:gap-3">
{/* Orders Tile */}
<Link
to="/doormile/orders"
@@ -2314,7 +2317,7 @@ export default function Home() {
<div className="text-[11px] font-bold uppercase tracking-wider text-slate-400 mb-3 px-1">
FLEET
</div>
<div className="flex items-start gap-2.5 sm:gap-3">
<div className="flex flex-wrap items-start gap-2.5 sm:gap-3">
{/* Staff Directory */}
<Link
to="/doormile/milers"
@@ -2383,7 +2386,7 @@ export default function Home() {
<div className="text-[11px] font-bold uppercase tracking-wider text-slate-400 mb-3 px-1">
CLIENTS &amp; REVENUE
</div>
<div className="flex items-start gap-2.5 sm:gap-3">
<div className="flex flex-wrap items-start gap-2.5 sm:gap-3">
{/* Bookings.
Labelled "Invoices", which this console does not have — the
page is the customer-app booking list. Billing lives on the
@@ -2442,7 +2445,7 @@ export default function Home() {
<div className="text-[11px] font-bold uppercase tracking-wider text-slate-400 mb-3 px-1">
REPORTS &amp; AI
</div>
<div className="flex items-start gap-2.5 sm:gap-3">
<div className="flex flex-wrap items-start gap-2.5 sm:gap-3">
{/* Orders Summary.
Called "AI Reports", but nothing about the page it opens is
AI — it is the daily volume and status aggregation, computed

View File

@@ -71,7 +71,7 @@ export default function Settings() {
{/* Unified Toolbar: Segmented Tabs on Left, Tab-Specific Action on Right */}
<div className="flex flex-wrap items-center justify-between gap-3 pt-1">
{/* Pill Segmented Tabs */}
<div className="inline-flex items-center gap-1 p-1 bg-slate-100/90 rounded-2xl border border-slate-200/80 shadow-2xs">
<div className="no-scrollbar inline-flex max-w-full items-center gap-1 overflow-x-auto p-1 bg-slate-100/90 rounded-2xl border border-slate-200/80 shadow-2xs">
{TABS.map((tab) => {
const Icon = tab.icon;
const isActive = activeTab === tab.id;
@@ -81,7 +81,7 @@ export default function Settings() {
type="button"
onClick={() => handleTabChange(tab.id)}
className={cn(
'flex items-center gap-2 px-4 py-2 rounded-xl text-xs sm:text-sm font-medium transition-all whitespace-nowrap cursor-pointer select-none',
'flex shrink-0 items-center gap-2 px-3 sm:px-4 py-2 rounded-xl text-xs sm:text-sm font-medium transition-all whitespace-nowrap cursor-pointer select-none',
isActive
? 'bg-white text-slate-900 font-semibold shadow-xs border border-slate-200/80'
: 'text-slate-600 hover:text-slate-900 hover:bg-slate-200/50'