fix(mobile): audit and fix layout overflow across dashboard pages at 375/768px
Audited /dashboard, /dashboard/sites (+ detail), /dashboard/workforce/workers,
/dashboard/workforce/on-site, /dashboard/plant/vehicles (+ detail),
/dashboard/plant/equipment (+ detail), /dashboard/plant/fuel-logs,
/dashboard/financial/purchase-orders (+ detail),
/dashboard/financial/delivery-notes/inbox, /dashboard/financial/suppliers,
and /dashboard/chat at 375x812 and 768x1024 using a scroll-right test to
catch real horizontal page overflow (as opposed to a table's own,
intentional internal scroll).
Two repeating bug patterns accounted for every overflow found:
1. A page-specific `right={<div className="flex items-center gap-2">...}`
passed to Topbar can carry two or three labelled buttons (edit/import/
merge/back etc.). Without `flex-wrap` they don't fit a 375px screen in
one row and push the whole page sideways. Fixed on the vehicles,
equipment, and site detail pages, plus their list-page counterparts
(vehicles fuel-view, equipment plant-view) — all five instances of
this exact pattern. Same fix applied to a delivery-note card's button
cluster on the PO detail page, and to Topbar's own right-side
container as a general backstop.
2. `Intl.NumberFormat("en-ZA", ...)` joins currency/number groups with
non-breaking spaces, so a wide value like "R 24 988 663" can't wrap.
In a 3-up grid (site cards' Contract/Spent/Remaining) that overflowed
the column; fixed by stacking those to 1-up below `sm`. In a 2-up grid
(PO detail KPI tiles) added `min-w-0` so the tile clips instead of
growing the grid track past the viewport.
Also: the shared Dialog now caps at `max-h-[90dvh]` with internal
scroll (a dialog taller than the phone viewport had no way to reach
its close button or footer before), and the sidebar hamburger button
was overlapping the Topbar's title on every page below `lg` — reserved
space for it instead.
Tables already scroll horizontally inside their own card via the
shared `<Table>` component or an `overflow-auto` wrapper, and KPI
grids already collapse responsively — both were fine everywhere checked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
7009197716
commit
3feddebcaa
9 changed files with 44 additions and 12 deletions
|
|
@ -124,7 +124,11 @@ function amberTile(
|
|||
? "text-blue-600 dark:text-blue-400"
|
||||
: "text-[#F2B165]";
|
||||
return (
|
||||
<Card className="relative overflow-hidden border-border/60">
|
||||
// `min-w-0`: without it, a grid item won't shrink below its content's
|
||||
// intrinsic width — a wide, non-breaking-space-grouped number (e.g.
|
||||
// "4 777,92 u") then grows the grid track past the column's share,
|
||||
// pushing the whole page wider than the viewport at 2-up.
|
||||
<Card className="relative min-w-0 overflow-hidden border-border/60">
|
||||
<span
|
||||
aria-hidden
|
||||
className={`absolute inset-y-0 left-0 w-1 bg-gradient-to-b ${strip}`}
|
||||
|
|
@ -857,7 +861,9 @@ function DeliveryNoteCard({
|
|||
</Badge>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
{/* flex-wrap: up to three buttons (review link, view slip,
|
||||
verify) here would overflow a 375px screen in one row. */}
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
{dn.batchNoteId && (
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<Link href={`/dashboard/financial/delivery-notes/document/${dn.batchNoteId}`}>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,8 @@ export default async function PlantDetailPage({
|
|||
title={vehicle.description}
|
||||
subtitle={`${vehicleProjectCode} · ${vehicle.fuel_limits || "No fuel limit set"}`}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
// flex-wrap: two labelled buttons here would overflow a 375px screen.
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<MergeVehicleButton
|
||||
vehicleId={vehicleId}
|
||||
vehicleLabel={vehicle.description}
|
||||
|
|
|
|||
|
|
@ -264,7 +264,9 @@ export function PlantView({
|
|||
title={title}
|
||||
subtitle={subtitle}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
// flex-wrap: three items here would overflow a 375px screen
|
||||
// in one row.
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<a
|
||||
href="/api/plant-hours/template"
|
||||
className="inline-flex h-9 items-center gap-1 rounded-md border border-border bg-card px-3 text-sm hover:bg-muted"
|
||||
|
|
|
|||
|
|
@ -179,7 +179,8 @@ export default async function VehicleDetailPage({
|
|||
title={vehicle.description}
|
||||
subtitle={`${vehicleProjectCode} · ${limitStr || "No fuel limit set"}`}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
// flex-wrap: two labelled buttons here would overflow a 375px screen.
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<MergeVehicleButton
|
||||
vehicleId={vehicleId}
|
||||
vehicleLabel={vehicle.description}
|
||||
|
|
|
|||
|
|
@ -330,7 +330,10 @@ export function FuelView({
|
|||
title={title}
|
||||
subtitle={subtitle}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
// flex-wrap: two labelled buttons here (plus the scope switcher
|
||||
// and theme toggle already in Topbar) are wider than a 375px
|
||||
// screen; without wrapping this pushed the whole page sideways.
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<EditModeToggle
|
||||
editing={edit.editing}
|
||||
onToggle={handleToggle}
|
||||
|
|
|
|||
|
|
@ -259,7 +259,8 @@ export default async function SiteDetailPage({
|
|||
title={site.name}
|
||||
subtitle={`${site.project_code} · ${site.duration || "—"}`}
|
||||
right={
|
||||
<div className="flex items-center gap-2">
|
||||
// flex-wrap: two labelled buttons here would overflow a 375px screen.
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<EditSiteButton
|
||||
site={{
|
||||
id: site.id,
|
||||
|
|
|
|||
|
|
@ -179,8 +179,13 @@ function SiteCard({ site }: { site: SiteSummary }) {
|
|||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4 pl-5">
|
||||
{/* Contract + burn */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
{/* Contract + burn. `Intl.NumberFormat` inserts non-breaking
|
||||
spaces in these currency strings (so "R 24 988 663" never
|
||||
splits mid-amount), which means a wide value can't wrap onto
|
||||
a second line — at 3-up it just overflows into the next
|
||||
column. Stack to 1-up below `sm` where there's no room for
|
||||
three currency columns; 3-up returns once there's space. */}
|
||||
<div className="grid grid-cols-1 gap-1 sm:grid-cols-3 sm:gap-3">
|
||||
<Metric label="Contract" value={formatCurrency(site.contractValue)} />
|
||||
<Metric
|
||||
label="Spent"
|
||||
|
|
@ -262,7 +267,10 @@ function Metric({
|
|||
sub?: string;
|
||||
}) {
|
||||
return (
|
||||
<div>
|
||||
// `min-w-0`: without it, a grid item won't shrink below its content's
|
||||
// natural width, so a wide currency value (e.g. "R 24 988 663") spills
|
||||
// into the next column instead of wrapping at 375px.
|
||||
<div className="min-w-0">
|
||||
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">
|
||||
{label}
|
||||
</p>
|
||||
|
|
|
|||
|
|
@ -328,7 +328,12 @@ export function Topbar({ title, subtitle, right }: { title: string; subtitle?: s
|
|||
</React.Suspense>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center gap-2">
|
||||
{/* flex-wrap: a page's `right` prop can carry more than one
|
||||
labelled button (e.g. the vehicles page's "Edit descriptions"
|
||||
+ "Import fuel log"), which together with the scope dropdown
|
||||
and theme toggle is wider than a 375px screen — without
|
||||
wrapping, the overflow pushed the whole page sideways. */}
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-2">
|
||||
<div className="hidden lg:block">
|
||||
<React.Suspense fallback={null}>
|
||||
<SiteScopeSwitcher variant="dropdown" />
|
||||
|
|
|
|||
|
|
@ -34,7 +34,12 @@ const DialogContent = React.forwardRef<
|
|||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg -translate-x-1/2 -translate-y-1/2 gap-4 border border-border bg-card p-6 shadow-xl rounded-xl",
|
||||
// `max-h-[90dvh] overflow-y-auto`: a dialog taller than the
|
||||
// viewport (long forms, big tables) used to extend off both the
|
||||
// top and bottom on phones with no way to reach content or the
|
||||
// close button — cap the height and let the content scroll
|
||||
// internally instead.
|
||||
"fixed left-1/2 top-1/2 z-50 grid w-full max-w-lg max-h-[90dvh] -translate-x-1/2 -translate-y-1/2 gap-4 overflow-y-auto border border-border bg-card p-6 shadow-xl rounded-xl",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
|
|
|
|||
Loading…
Reference in a new issue