Fastwhisper / frontend /src /pages /Stats.tsx
Mbonea's picture
Ship F2-F8 Habit Journal PWA: tabs, CRUD, daily, stats, coach, sheets, and installable shell.
391340f
Raw
History Blame Contribute Delete
2.48 kB
/** Server-computed statistics rendered as lean cards and CSS bars. */
import { useEffect, useState } from "preact/hooks";
import { getStats, type StatRow, type StatsData } from "../api";
const pct = (n = 0) => `${Math.round(n * 100)}%`;
function Rows({ title, rows, metric }: { title: string; rows: StatRow[]; metric: "p_helped" | "p_failed" }) {
return (
<section class="surface-card stats-card">
<h2>{title}</h2>
{!rows.length ? <p class="muted">No qualifying data.</p> : rows.map((row) => {
const value = row[metric] || 0;
return <div class="stat-row" key={row.key}><div><span>{row.key}</span><span class="muted">n={row.n} · {pct(value)}</span></div><div class="bar-track"><span style={{ width: pct(value) }} /></div></div>;
})}
</section>
);
}
export function Stats() {
const [data, setData] = useState<StatsData | null>(null);
useEffect(() => { getStats().then(setData).catch(() => {}); }, []);
if (!data) return <div class="gate-loading">Loading…</div>;
const outcomes = Object.entries(data.outcomes);
const dailyRows: StatRow[] = [
{ key: "Brick done", n: 0, p_helped: data.daily.p_brick_done },
{ key: "Corn okay", n: 0, p_helped: data.daily.p_corn_ok },
{ key: "No FC", n: 0, p_helped: data.daily.p_no_fc },
{ key: "Clean rerun", n: 0, p_helped: data.daily.p_rerun_clean },
{ key: "Court closed", n: 0, p_helped: data.daily.p_court_closed },
];
return (
<div class="app-shell">
<header class="top-bar material-bar"><h1>Stats</h1></header>
<main class="page stack">
<section class="surface-card stats-card"><h2>Outcomes</h2><div class="outcome-grid">{outcomes.map(([key, count]) => <div key={key}><strong>{count}</strong><span class={`result-label result-${key}`}>{key}</span></div>)}</div><p class="muted">{data.n_entries_scored} scored · {data.n_entries_total} total</p></section>
<Rows title="Remedies" rows={data.by_remedy} metric="p_helped" />
<Rows title="Tags · failed" rows={data.by_tag} metric="p_failed" />
<Rows title="Emotions" rows={data.by_emotion} metric="p_helped" />
<Rows title="Intensity" rows={data.by_intensity_bucket} metric="p_helped" />
<Rows title="Daily rates" rows={dailyRows} metric="p_helped" />
<section class="surface-card stats-card"><h2>Average daily points</h2><strong class="large-number">{data.daily.avg_points.toFixed(1)}/6</strong></section>
</main>
</div>
);
}