From a1bfde40f80bfb90e90c069022d1f4bb161e00c3 Mon Sep 17 00:00:00 2001 From: Khalim Conn-Kowlessar Date: Sun, 19 Jul 2026 15:21:22 +0100 Subject: [PATCH] feat(reporting): restyle the Investment ledger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The scenario Investment ledger was a flat monochrome list. Give it a visual hierarchy while keeping the reporting design vocabulary (quiet navy-on-white, semantic green for benefits): - lead with a Net cost hero block — the headline figure a board commits to — with gross/home and the gross · funding breakdown beneath it; - turn the cost lines into a colour-keyed composition bar (navy → gold → grey, echoing the data-quality evidence bar) with matching dots; - give annual benefits green icon-chip rows (they're recurring savings) and value-for-money its own iconography; - add section and header icons throughout. Purely presentational — same LedgerView data, no behaviour change. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../reporting/components/InvestmentLedger.tsx | 286 +++++++++++++----- 1 file changed, 204 insertions(+), 82 deletions(-) diff --git a/src/app/portfolio/[slug]/(portfolio)/reporting/components/InvestmentLedger.tsx b/src/app/portfolio/[slug]/(portfolio)/reporting/components/InvestmentLedger.tsx index 3d9887a8..1d34b13f 100644 --- a/src/app/portfolio/[slug]/(portfolio)/reporting/components/InvestmentLedger.tsx +++ b/src/app/portfolio/[slug]/(portfolio)/reporting/components/InvestmentLedger.tsx @@ -6,7 +6,19 @@ import { TooltipProvider, TooltipTrigger, } from "@/app/shadcn_components/ui/tooltip"; -import { Info } from "lucide-react"; +import { + Info, + Wallet, + Coins, + Landmark, + Sprout, + Banknote, + Leaf, + Scale, + Gauge, + Cloud, + type LucideIcon, +} from "lucide-react"; import { moneyFull } from "./primitives"; import { carsOffTheRoad, PROJECT_DELIVERY_RATE } from "@/lib/reporting/model"; @@ -25,108 +37,218 @@ export interface LedgerView { costPerCarbon: number; } -function Row({ - label, - value, - tip, - emphatic = false, - credit = false, - note, -}: { - label: string; - value: string; - tip?: string; - emphatic?: boolean; - credit?: boolean; - note?: string; -}) { +const deliveryPct = Math.round(PROJECT_DELIVERY_RATE * 100); + +// The cost-composition palette echoes the data-quality evidence bar: quiet +// navy → gold → grey, so the reporting surfaces read as one system. +const COST_COLORS = { + construction: "#14163d", + delivery: "#a07c42", + contingency: "#c5cad8", +} as const; + +function InfoDot({ label, tip }: { label: string; tip: string }) { return ( -
- - {label} - {tip && ( - - - - - - - {tip} - - - - )} - - - {value} - {note && · {note}} - -
+ + + + + + + {tip} + + + ); } -function GroupLabel({ children }: { children: React.ReactNode }) { +function GroupLabel({ + icon: Icon, + children, +}: { + icon: LucideIcon; + children: React.ReactNode; +}) { return ( -
+
+ {children}
); } -const deliveryPct = Math.round(PROJECT_DELIVERY_RATE * 100); +/** A row led by an icon chip. `tone` tints benefits green, keeps the rest quiet. */ +function IconRow({ + icon: Icon, + label, + value, + note, + tone = "neutral", +}: { + icon: LucideIcon; + label: string; + value: string; + note?: string; + tone?: "neutral" | "benefit"; +}) { + const chip = + tone === "benefit" + ? "bg-[#e9f4ef] text-[#0c6b4a]" + : "bg-gray-50 text-gray-500"; + const valueColor = tone === "benefit" ? "text-[#0c6b4a]" : "text-brandblue"; + return ( +
+ + + + + {label} + + + {value} + {note && · {note}} + +
+ ); +} export function InvestmentLedger({ v }: { v: LedgerView }) { + const costParts = [ + { + label: "Construction works", + value: v.constructionCost, + color: COST_COLORS.construction, + }, + { + label: "Project delivery", + value: v.projectDelivery, + color: COST_COLORS.delivery, + }, + { + label: "Contingency", + value: v.contingency, + color: COST_COLORS.contingency, + }, + ]; + const grossForBar = v.grossCost || 1; + return (
-
+
+

Investment ledger

- Costs - - - - - + {/* Net cost — the headline figure a board commits to. */} +
+
+ + Net cost + + + + {moneyFull(v.grossPerHome)}{" "} + gross/home + +
+
+ {moneyFull(v.netCost)} +
+
+ {moneyFull(v.grossCost)} gross · {moneyFull(v.funding)} funding + secured +
+
- Annual benefits - - + {/* Cost breakdown — a proportion bar + rows keyed to it by colour. */} + Cost breakdown +
+
+ {costParts.map((p) => ( + + ))} +
+ {costParts.map((p) => ( +
+ + + {p.label} + + + {moneyFull(p.value)} + +
+ ))} +
+ + + Funding secured + + + −{moneyFull(v.funding)} + +
+
- Value for money - -
- + {/* Annual benefits — recurring, so tinted green like the savings they are. */} + Annual benefits +
+ + +
+ + {/* Value for money */} + Value for money +
+ +
);