From dc7024d98a2622980e6690ae79d7fa79dd17a05c Mon Sep 17 00:00:00 2001 From: David Date: Fri, 2 Oct 2026 15:34:19 +0200 Subject: [PATCH] Fix: the mobile table was mislabelled and clipping its own totals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A screenshot at phone width showed an "INVOICE | LINES | TOTAL | STATUS" header row while the data under LINES was money. The header and its data cell had drifted apart: the earlier pass hid the Lines but not the matching , because the loop that added the hide classes only matched text-left headers and Lines is text-right. So the Total values rendered under a heading that named a column which no longer existed. The same screenshot showed the totals clipped — "41,710.50" reading as "710,50". With only three columns left, the 520px min-width was still forcing a sideways scroll, and the opaque sticky first column then sat on top of the left of the money values. The mobile floor is gone, so the three remaining columns size to their content, and the sticky and status gutters are trimmed at mobile widths to let them fit 375px. Below sm the table is now exactly Invoice, Total, Status — and every header matches a visible cell. Co-Authored-By: Claude Opus 4.8 (1M context) --- app/expenses/page.tsx | 6 ++--- app/globals.css | 44 +++++++++++++++++++++---------- app/layout.tsx | 4 +-- app/overheads/page.tsx | 6 ++--- app/page.tsx | 6 ++--- components/ChartsGrid.tsx | 14 +++++----- components/ExpenseSectionPage.tsx | 20 +++++++------- components/GlobalSearch.tsx | 10 +++---- components/IncomeView.tsx | 34 ++++++++++++------------ components/InvoiceIntake.tsx | 14 +++++----- components/InvoiceList.tsx | 30 ++++++++++----------- components/InvoiceTable.tsx | 22 ++++++++-------- components/PlantRegisterTable.tsx | 34 ++++++++++++------------ components/RunningView.tsx | 28 ++++++++++---------- components/ScrollFade.tsx | 2 +- components/Sidebar.tsx | 14 +++++----- 16 files changed, 152 insertions(+), 136 deletions(-) diff --git a/app/expenses/page.tsx b/app/expenses/page.tsx index 31af6b7..ac1cc4a 100644 --- a/app/expenses/page.tsx +++ b/app/expenses/page.tsx @@ -117,7 +117,7 @@ export default async function ExpensesHub() {
@@ -133,12 +133,12 @@ export default async function ExpensesHub() {
{s.description}
-
Monthly
+
Monthly
{ZAR(s.total)}
-
{s.count} items
+
{s.count} items
diff --git a/app/globals.css b/app/globals.css index 9892522..1d5e34a 100644 --- a/app/globals.css +++ b/app/globals.css @@ -36,19 +36,35 @@ --color-text-muted: #6b7280; --color-text-soft: #9ca3af; --color-row-hover: rgba(0, 0, 0, 0.02); + + /* "Ink" steps — the light-mode colour for text that borrows a brand hue. + The base brand palette above is never used as text on a light surface unless + it clears 4.5:1; these names exist so `text-accent-ink` works in BOTH modes + and only its value changes. See the .dark block for the dark counterparts. */ + --color-accent-ink: #30638E; + --color-brick-ink: #D1495B; + --color-indigo-ink: #003D5B; } /* Dark theme overrides */ .dark { - --color-bg: #0b1014; - --color-surface: #131a21; - --color-surface-muted: #0f1419; - --color-border: #1f2937; - --color-border-soft: #1a222b; - --color-text: #e6edf3; - --color-text-muted: #9ca3af; - --color-text-soft: #6b7280; - --color-row-hover: rgba(255, 255, 255, 0.03); + --color-bg: #0f151b; + --color-surface: #1e2732; + --color-surface-muted: #182029; + --color-border: #303c48; + --color-border-soft: #26313c; + --color-text: #e8eef4; + --color-text-muted: #adb8c2; + --color-text-soft: #87919a; + --color-row-hover: rgba(255, 255, 255, 0.04); + + /* Dark "ink" steps. The base brand hues are correct as *fills* but fail WCAG AA + as text on a dark surface (seaweed 3.6:1, casual 2.9:1, brick 3.7:1, indigo 1.6:1). + These are lighter steps of the same hues, reaching 4.8–7.3:1. Text-only — + never a fill — so the @theme brand palette stays untouched. */ + --color-accent-ink: #2BB0C4; + --color-brick-ink: #E86E7E; + --color-indigo-ink: #7FA8C9; /* Part A.4: dark-mode chart hues */ --color-chart-income: #00A6BE; @@ -81,9 +97,9 @@ body, .dark body { transition: background-color 0.2s ease, color 0.2s ease; } .recharts-legend-item-text { color: #6b7280 !important; font-size: 12px !important; } .dark .recharts-default-tooltip { - background: rgba(15, 20, 25, 0.98) !important; - border: 1px solid #1f2937 !important; - color: #e6edf3 !important; + background: rgba(30, 39, 50, 0.98) !important; + border: 1px solid #303c48 !important; + color: #e8eef4 !important; } -.dark .recharts-tooltip-label { color: #e6edf3 !important; } -.dark .recharts-legend-item-text { color: #9ca3af !important; } +.dark .recharts-tooltip-label { color: #e8eef4 !important; } +.dark .recharts-legend-item-text { color: #adb8c2 !important; } diff --git a/app/layout.tsx b/app/layout.tsx index 8bb8e15..ff8bbe7 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -99,13 +99,13 @@ export default async function RootLayout({ children }: { children: React.ReactNo -
+
Mega Plant
Mega Plant
diff --git a/app/overheads/page.tsx b/app/overheads/page.tsx index 0bdb567..8b1cb89 100644 --- a/app/overheads/page.tsx +++ b/app/overheads/page.tsx @@ -42,7 +42,7 @@ export default async function OverheadsPage() { const items = overheads.filter((o) => o.section === sec); const secTotal = items.reduce((s, o) => s + (o.amount_incl || 0), 0); return ( -
+

{sec}

@@ -53,7 +53,7 @@ export default async function OverheadsPage() {
- + @@ -64,7 +64,7 @@ export default async function OverheadsPage() { {items.map((o) => ( - + diff --git a/app/page.tsx b/app/page.tsx index db4572c..a3efe08 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -100,7 +100,7 @@ export default async function DashboardPage() { {/* Main charts */}
-
+

Income vs Fixed vs Running

@@ -115,7 +115,7 @@ export default async function DashboardPage() {
-
+

Overhead mix

By section · {ZAR(totalMonthlyIncl)} total

@@ -137,7 +137,7 @@ export default async function DashboardPage() { {/* Bottom row */}
-
+

Monthly Income trend

Project income per month

diff --git a/components/ChartsGrid.tsx b/components/ChartsGrid.tsx index 43e30a7..18c72a6 100644 --- a/components/ChartsGrid.tsx +++ b/components/ChartsGrid.tsx @@ -89,14 +89,14 @@ export function ChartsGrid({ items }: { items: Item[] }) {
{/* Toolbar — wraps to multiple rows on mobile. Round 4 Part B.1/B.3/B.5. */} -
+
setQuery(e.target.value)} placeholder="Search by code or name…" - className="w-full min-h-[44px] pl-9 pr-3 bg-gray-50 dark:bg-white/5 border border-transparent rounded-lg text-sm placeholder:text-gray-400 focus:bg-white dark:focus:bg-[#0f1419] focus:border-[#30638E] focus:outline-none" + className="w-full min-h-[44px] pl-9 pr-3 bg-gray-50 dark:bg-white/5 border border-transparent rounded-lg text-sm placeholder:text-gray-400 focus:bg-white dark:focus:bg-white/10 focus:border-[#30638E] focus:outline-none" />
@@ -119,7 +119,7 @@ export function ChartsGrid({ items }: { items: Item[] }) { ))}
- View + View {[ { id: "lines", label: "Lines", icon: LineChartIcon }, { id: "stack", label: "Cost stack", icon: BarChart3 }, @@ -142,10 +142,10 @@ export function ChartsGrid({ items }: { items: Item[] }) {
{filtered.length === 0 ? ( -
- +
+
No machines match "{query}"
-
@@ -157,7 +157,7 @@ export function ChartsGrid({ items }: { items: Item[] }) { const profit = computeProfit({ income: item.totalIncome, fixed: 0, running: 0 }); const positiveProfit = profit >= 0; return ( -
+
diff --git a/components/ExpenseSectionPage.tsx b/components/ExpenseSectionPage.tsx index 13e5696..f309408 100644 --- a/components/ExpenseSectionPage.tsx +++ b/components/ExpenseSectionPage.tsx @@ -65,7 +65,7 @@ export async function ExpenseSectionPage({
{glBreakdown.length > 1 && ( -
+

By GL account

{glBreakdown.map((g) => { @@ -75,7 +75,7 @@ export async function ExpenseSectionPage({
{g.gl} - {ZAR(g.total)} · {g.count} item{g.count !== 1 && "s"} · {pct.toFixed(0)}% + {ZAR(g.total)} · {g.count} item{g.count !== 1 && "s"} · {pct.toFixed(0)}%
@@ -93,16 +93,16 @@ export async function ExpenseSectionPage({ {/* Part B.1: empty state must say so plainly, not render a table of zeros */} {items.length === 0 ? ( -
+
No {section} entries in PocketBase.
-
+
This category either doesn't apply yet or lives elsewhere — see round-3 brief.
) : ( -
+

Line items

{items.length} entries @@ -110,7 +110,7 @@ export async function ExpenseSectionPage({
Description GL Account Supplier
{o.description} {o.gl_account || "—"} {o.supplier || "—"}
- + @@ -121,11 +121,11 @@ export async function ExpenseSectionPage({ {items.map((o) => ( - + ))} - + @@ -159,7 +159,7 @@ export async function ExpenseSectionPage({ function StatCard({ label, value, color }: { label: string; value: string; color: string }) { return ( -
+
{label}
{value}
diff --git a/components/GlobalSearch.tsx b/components/GlobalSearch.tsx index 5be3898..a2dd875 100644 --- a/components/GlobalSearch.tsx +++ b/components/GlobalSearch.tsx @@ -35,7 +35,7 @@ export function GlobalSearch({ assets }: { assets: Asset[] }) { return (
- + { @@ -52,12 +52,12 @@ export function GlobalSearch({ assets }: { assets: Asset[] }) { } }} placeholder="Search plant, supplier, code…" - className="w-full h-9 pl-9 pr-3 bg-gray-100 dark:bg-white/5 border border-transparent rounded-lg text-sm placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:bg-white dark:focus:bg-[#0f1419] focus:border-[#30638E] focus:outline-none transition" + className="w-full h-9 pl-9 pr-3 bg-gray-100 dark:bg-white/5 border border-transparent rounded-lg text-sm placeholder:text-gray-400 dark:placeholder:text-gray-500 focus:bg-white dark:focus:bg-white/10 focus:border-[#30638E] focus:outline-none transition" />
{open && matches.length > 0 && ( -
+
{matches.map((a) => ( {a.code ? ( @@ -86,7 +86,7 @@ export function GlobalSearch({ assets }: { assets: Asset[] }) { )}
{a.category && ( - + {a.category} )} diff --git a/components/IncomeView.tsx b/components/IncomeView.tsx index 3737a92..44e2aa8 100644 --- a/components/IncomeView.tsx +++ b/components/IncomeView.tsx @@ -50,7 +50,7 @@ export function IncomeView({ rows, palette }: { rows: IncomeRow[]; palette: { in
{/* Round 4 Part B.3: Sort collapses to a select on mobile. View toggle stays as buttons. */}
-
+
- Sort: + Sort: {[ { id: "profit", label: "Profit" }, { id: "income", label: "Income" }, @@ -137,7 +137,7 @@ function CardsView({ return (
-
YTD income
+
YTD income
{ZAR2(r.income)}
@@ -185,7 +185,7 @@ function CardsView({
-
YTD income
+
YTD income
{ZAR2(r.income)}
{isOpen && ( -
+
{/* 12-col on md+, 3-col on mobile — Part 3: unreadable at 375px otherwise */}
{r.months.map((v, i) => (
-
{MONTH_NAMES[i]}
+
{MONTH_NAMES[i]}
0 ? "bg-[#00798C]/5 text-[#00798C] dark:bg-[#00798C]/15" - : "text-gray-300 dark:text-gray-600" + : "text-gray-300 dark:text-text-soft" }`} > {v == null ? "—" : (v > 0 ? ZAR2(v) : "R 0")} @@ -219,7 +219,7 @@ function CardsView({
))}
-
+
YTD income
{ZAR(r.income)}
@@ -248,12 +248,12 @@ function CardsView({ function GridView({ rows }: { rows: IncomeRow[] }) { return ( -
+
Description GL Account Supplier
{o.description} {o.gl_account ? ( - + {o.gl_account} ) : ( @@ -140,7 +140,7 @@ export async function ExpenseSectionPage({
Total {ZAR(totalExcl)} {ZAR(totalVat)}
- - + + {MONTH_NAMES.map((m) => ( @@ -265,8 +265,8 @@ function GridView({ rows }: { rows: IncomeRow[] }) { {rows.map((r) => { const positive = r.ytdProfit >= 0; return ( - - + {r.months.map((v, i) => ( - ))} @@ -293,8 +293,8 @@ function GridView({ rows }: { rows: IncomeRow[] }) { {/* Totals row: the sum of every machine for each month. */} - - +
Code
Code Machine{m}
+
{r.code} @@ -278,7 +278,7 @@ function GridView({ rows }: { rows: IncomeRow[] }) { 0 ? "text-gray-700 dark:text-gray-300" : "text-gray-300 dark:text-gray-600"}`}> + 0 ? "text-gray-700 dark:text-gray-300" : "text-gray-300 dark:text-text-soft"}`}> {v == null ? "—" : (v > 0 ? ZAR2(v) : "R 0")}
+
Total diff --git a/components/InvoiceIntake.tsx b/components/InvoiceIntake.tsx index 48325bc..8295a8a 100644 --- a/components/InvoiceIntake.tsx +++ b/components/InvoiceIntake.tsx @@ -166,9 +166,9 @@ export function AddInvoiceButton() { role="dialog" aria-modal="true" aria-label="Add invoice" - className="relative w-full sm:max-w-lg bg-white dark:bg-[#131a21] rounded-t-2xl sm:rounded-2xl shadow-2xl border border-gray-100 dark:border-white/10 max-h-[90vh] overflow-y-auto" + className="relative w-full sm:max-w-lg bg-white dark:bg-surface rounded-t-2xl sm:rounded-2xl shadow-2xl border border-gray-100 dark:border-white/10 max-h-[90vh] overflow-y-auto" > -
+

Add invoice

-
+