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 (
-
+
- 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
+
+
+
);