From 999feb37fa5e231ed42b4cfbfd6bda373c7480cc Mon Sep 17 00:00:00 2001 From: David Date: Fri, 2 Oct 2026 15:29:00 +0200 Subject: [PATCH] Mobile: make the invoice tables readable at 375px MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audited the invoice UI against the Round 4 checklist and fixed four things. The summary table carried eight columns at a fixed 820px min-width, which at 375px is a long sideways scroll through three figures before reaching the status badge — the one thing that needs acting on. Below sm it now keeps only Invoice, Site, Total and Status, and drops to a 520px min-width, so the useful columns are readable without scrolling. Period, line count and the ex-VAT/VAT split are one tap away in the expanded detail, and the full eight columns return from sm up where there is room. The summary table also had no right-edge fade. Rule 4 asks for a visible affordance on any wide table and it was simply missing, so scrolling sideways looked like the table ended. Same for the month grid on /income, which was also missing -webkit-overflow-scrolling. Both now share components/ScrollFade. Two 10px labels in the app header ("Overheads 2026", "FY") were invisible at 375px but visible from 640/768px — and 768x1024 is one of the two sizes the checklist tests, so they were a rule-2 violation at tablet width. Bumped to 12px. PlantRegisterTable's 10px filter count was checked and left alone: it sits inside hidden lg:flex, so it never renders on mobile. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/layout.tsx | 4 ++-- components/IncomeView.tsx | 16 +++++++++------- components/InvoiceList.tsx | 28 ++++++++++++++++------------ components/InvoiceTable.tsx | 12 +----------- components/ScrollFade.tsx | 10 ++++++++++ 5 files changed, 38 insertions(+), 32 deletions(-) create mode 100644 components/ScrollFade.tsx diff --git a/app/layout.tsx b/app/layout.tsx index c235348..8bb8e15 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -109,7 +109,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo />
Mega Plant
-
Overheads 2026
+
Overheads 2026
@@ -119,7 +119,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
- FY + FY 2026
diff --git a/components/IncomeView.tsx b/components/IncomeView.tsx index b24a51d..3737a92 100644 --- a/components/IncomeView.tsx +++ b/components/IncomeView.tsx @@ -6,6 +6,7 @@ import { Sparkline } from "@/components/Sparkline"; import { MONTH_NAMES } from "@/lib/data"; import type { IncomeRow } from "@/lib/rows"; import { ZAR, ZAR2 } from "@/lib/utils"; +import { ScrollFade } from "@/components/ScrollFade"; export function IncomeView({ rows, palette }: { rows: IncomeRow[]; palette: { income: string; profit: string; loss: string } }) { const [view, setView] = useState<"cards" | "grid">("cards"); @@ -202,7 +203,7 @@ function CardsView({ {isOpen && (
{/* 12-col on md+, 3-col on mobile — Part 3: unreadable at 375px otherwise */} -
+
{r.months.map((v, i) => (
{MONTH_NAMES[i]}
@@ -220,17 +221,17 @@ function CardsView({
-
YTD income
+
YTD income
{ZAR(r.income)}
-
Fixed + running
+
Fixed + running
{ZAR(r.fixed + r.running)}
-
Profit
+
Profit
{positive ? "+" : ""}{ZAR(r.ytdProfit)}
@@ -247,9 +248,9 @@ function CardsView({ function GridView({ rows }: { rows: IncomeRow[] }) { return ( -
-
- +
+
+
@@ -317,6 +318,7 @@ function GridView({ rows }: { rows: IncomeRow[] }) {
Code
+
); } diff --git a/components/InvoiceList.tsx b/components/InvoiceList.tsx index 759de3e..14dd648 100644 --- a/components/InvoiceList.tsx +++ b/components/InvoiceList.tsx @@ -7,6 +7,7 @@ import { BOOK_INCLUSIVE_VAT, BOOKED_LONG_LABEL } from "@/lib/invoice-booking"; import { ZAR, ZAR2 } from "@/lib/utils"; import { bookedAmount } from "@/lib/invoice-booking"; import { InvoiceSummary } from "@/components/InvoiceTable"; +import { ScrollFade } from "@/components/ScrollFade"; // One row per invoice. Clicking a row expands the full line-item detail underneath it, // so the common case (scan for problems) is a single table read and the detail is opt-in. @@ -61,19 +62,19 @@ export function InvoiceList({
{/* Rule 1 + 4: scrolls inside its own container; page never scrolls sideways. */} -
- +
+
- - + + - - - + + + @@ -113,6 +114,7 @@ export function InvoiceList({
Invoice SitePeriodSitePeriod LinesRevenue ex VATVATTotal incl VATRevenue ex VATVATTotal Status
+
); @@ -157,7 +159,7 @@ function InvoiceRow({ - + {invoice.site ? ( @@ -167,14 +169,16 @@ function InvoiceRow({ — )} - + {invoice.period_from.slice(0, 10)} → {invoice.period_to.slice(0, 10)} - {lineCount} - + + {lineCount} + + {ZAR2(invoice.total_excl)} - + {ZAR2(invoice.vat)} diff --git a/components/InvoiceTable.tsx b/components/InvoiceTable.tsx index 92f772c..538efda 100644 --- a/components/InvoiceTable.tsx +++ b/components/InvoiceTable.tsx @@ -2,19 +2,9 @@ import type { Asset, BookedLine, Invoice } from "@/lib/data"; import { ZAR2 } from "@/lib/utils"; import { AlertTriangle, CheckCircle2 } from "lucide-react"; import { BookingSelect } from "@/components/BookingSelect"; +import { ScrollFade } from "@/components/ScrollFade"; import { InvoiceUploader, AttachedDocument } from "@/components/InvoiceUploader"; -// Right-edge fade so a horizontally scrollable table reads as scrollable on touch. -// pointer-events-none so it never swallows a tap on the row underneath. -function ScrollFade() { - return ( -