feat: show household health score

This commit is contained in:
MOHAN 2026-07-16 15:38:00 +05:30
parent 59ea77ed44
commit ab273ea7d0

View File

@ -43,6 +43,26 @@ type CashflowMonth = {
transactionCount: number;
};
type HealthScore = {
score: number;
rating: "excellent" | "strong" | "building" | "needs_attention" | string;
components: {
cashflow: number;
balanceBuffer: number;
goalProgress: number;
collaboration: number;
syncHealth: number;
};
metrics: {
savingsRate: number;
bufferMonths: number;
activeGoalCount: number;
jointAccountCount: number;
collaborativeTransactionCount: number;
};
recommendations: string[];
};
type RecentTransaction = {
date: string;
description: string;
@ -88,6 +108,7 @@ type DashboardData = {
monthlyNet: number;
};
ownershipBreakdown: Record<string, { accountCount: number; balance: number }>;
healthScore: HealthScore;
accounts: HouseholdAccount[];
cashflow: CashflowMonth[];
recentTransactions: RecentTransaction[];
@ -116,6 +137,10 @@ function accountLabel(account: HouseholdAccount | RecentTransaction["account"])
return `${account.institutionName ?? "Account"}${mask}`;
}
function healthRatingLabel(value: string) {
return value.replace(/_/g, " ");
}
export default function HouseholdSettingsPage() {
const [households, setHouseholds] = useState<Household[]>([]);
const [selectedId, setSelectedId] = useState("");
@ -343,7 +368,7 @@ export default function HouseholdSettingsPage() {
<div className="glass-panel rounded-2xl p-8 text-sm text-muted-foreground">Loading households...</div>
) : dashboard ? (
<>
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-4">
<div className="grid gap-4 md:grid-cols-2 xl:grid-cols-5">
<div className={cardClass}>
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Total balance</p>
<p className="mt-2 text-3xl font-bold text-foreground">{formatMoney(dashboard.summary.totalBalance)}</p>
@ -368,6 +393,51 @@ export default function HouseholdSettingsPage() {
<p className="mt-2 text-3xl font-bold text-foreground">{dashboard.summary.memberCount}</p>
<p className="mt-1 text-sm text-muted-foreground">Active household access</p>
</div>
<div className={cardClass}>
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Health score</p>
<p className="mt-2 text-3xl font-bold text-foreground">{dashboard.healthScore.score}</p>
<p className="mt-1 text-sm capitalize text-muted-foreground">{healthRatingLabel(dashboard.healthScore.rating)}</p>
</div>
</div>
<div className="glass-panel rounded-2xl p-6 shadow-sm">
<div className="grid gap-6 lg:grid-cols-[260px_1fr_1fr]">
<div>
<p className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">Couples financial health</p>
<p className="mt-2 text-5xl font-bold text-foreground">{dashboard.healthScore.score}</p>
<p className="mt-1 text-sm capitalize text-muted-foreground">{healthRatingLabel(dashboard.healthScore.rating)}</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">
{[
["Cashflow", dashboard.healthScore.components.cashflow, 35],
["Balance buffer", dashboard.healthScore.components.balanceBuffer, 20],
["Goal progress", dashboard.healthScore.components.goalProgress, 20],
["Collaboration", dashboard.healthScore.components.collaboration, 15],
["Sync health", dashboard.healthScore.components.syncHealth, 10],
].map(([label, value, max]) => (
<div key={label} className="rounded-xl border border-border bg-background/40 p-3">
<div className="flex items-center justify-between gap-3 text-sm">
<span className="font-semibold text-foreground">{label}</span>
<span className="text-muted-foreground">{value}/{max}</span>
</div>
<div className="mt-2 h-2 overflow-hidden rounded-full bg-secondary">
<div className="h-full bg-primary" style={{ width: `${Math.min((Number(value) / Number(max)) * 100, 100)}%` }} />
</div>
</div>
))}
</div>
<div className="rounded-xl border border-border bg-background/40 p-4">
<p className="text-sm font-semibold text-foreground">Next best moves</p>
<div className="mt-3 space-y-2">
{dashboard.healthScore.recommendations.map((recommendation) => (
<p key={recommendation} className="text-sm text-muted-foreground">{recommendation}</p>
))}
{!dashboard.healthScore.recommendations.length && (
<p className="text-sm text-muted-foreground">Shared cashflow, goals, collaboration, and sync health are all in strong shape.</p>
)}
</div>
</div>
</div>
</div>
<div className="grid gap-6 xl:grid-cols-[0.9fr_1.1fr]">