docs(docs): revamp progress dashboard UI and add week field comment

This commit is contained in:
Prad Nukala
2026-08-27 11:05:05 -04:00
parent 72a8399e6d
commit 5e4628b20f
+132 -61
View File
@@ -35,6 +35,7 @@ interface Stats {
deferred_total: number; deferred_total: number;
}[]; }[];
ladder: Record<Stage, number>; ladder: Record<Stage, number>;
/** week is the 1-based campaign week — the Worker converts from ISO. */
gates: { gates: {
week: number; week: number;
issue: number | null; issue: number | null;
@@ -76,7 +77,12 @@ function dayLabel(iso: string): string {
return `${weekday} ${iso.slice(8)}`; return `${weekday} ${iso.slice(8)}`;
} }
// ── shared styles (theme tokens only — no hand-rolled palette) ─── // ── shared styles ────────────────────────────────────────────────
// Theme tokens only, with two deliberate exceptions: the phase-bar sets get
// fixed hues (accent-blue core, yellow optional, red deferred — they must be
// distinguishable, not shades of one colour), and the last-7-days heatmap
// reuses the GREENS ramp from apps/api/src/charts.ts so the page matches the
// README/email heatmaps (level 0 stays a theme token so light mode works).
const card: CSSProperties = { const card: CSSProperties = {
border: "1px solid var(--blume-border)", border: "1px solid var(--blume-border)",
@@ -142,32 +148,78 @@ function Header({ stats }: { stats: Stats }) {
); );
} }
// One stacked bar per phase: solved core / optional / deferred segments over
// a muted track sized by the phase's total problem count, so both phase size
// and progress read at a glance.
const SETS = [
["core", "var(--blume-accent)"],
["optional", "#d29922"],
["deferred", "#e5534b"],
] as const;
function Phases({ stats }: { stats: Stats }) { function Phases({ stats }: { stats: Stats }) {
const maxTotal = Math.max(
1,
...stats.phases.map((p) => p.core_total + p.optional_total + p.deferred_total),
);
return ( return (
<div style={card}> <div style={card}>
<div style={heading}>Phases</div> <div style={heading}>Phases</div>
<table style={{ borderCollapse: "collapse", width: "100%" }}> <div style={{ display: "flex", gap: "1rem", flexWrap: "wrap", marginBottom: "0.6rem" }}>
<tbody> {SETS.map(([set, color]) => (
{stats.phases.map((p) => ( <span key={set} style={muted}>
<tr key={p.milestone}> <span
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>{p.name}</td> style={{
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}> display: "inline-block",
<Bar done={p.core_done} total={p.core_total} />{" "} width: "0.65rem",
<span style={muted}> height: "0.65rem",
core {p.core_done}/{p.core_total} borderRadius: "2px",
background: color,
marginRight: "0.35rem",
}}
/>
{set}
</span> </span>
</td>
<td style={{ padding: "0.2rem 0" }}>
<span style={muted}>
optional {p.optional_done}/{p.optional_total}
{p.deferred_total > 0 &&
` · deferred ${p.deferred_done}/${p.deferred_total}`}
</span>
</td>
</tr>
))} ))}
</tbody> </div>
</table> {stats.phases.map((p) => {
const total = p.core_total + p.optional_total + p.deferred_total;
const done = p.core_done + p.optional_done + p.deferred_done;
return (
<div
key={p.milestone}
title={`core ${p.core_done}/${p.core_total} · optional ${p.optional_done}/${p.optional_total} · deferred ${p.deferred_done}/${p.deferred_total}`}
style={{ display: "flex", alignItems: "center", gap: "0.75rem", padding: "0.25rem 0" }}
>
<span style={{ width: "9.5rem", flexShrink: 0 }}>{p.name}</span>
<span style={{ flex: 1 }}>
<span
style={{
display: "flex",
width: `${(total / maxTotal) * 100}%`,
height: "0.65rem",
borderRadius: "var(--blume-radius)",
background: "var(--blume-muted)",
overflow: "hidden",
}}
>
{SETS.map(([set, color]) => {
const solved = p[`${set}_done`];
return solved > 0 ? (
<span
key={set}
style={{ width: `${(solved / total) * 100}%`, background: color }}
/>
) : null;
})}
</span>
</span>
<span style={{ ...muted, width: "3.5rem", textAlign: "right", flexShrink: 0 }}>
{done}/{total}
</span>
</div>
);
})}
</div> </div>
); );
} }
@@ -224,25 +276,22 @@ function Gates({ stats }: { stats: Stats }) {
<div style={card}> <div style={card}>
<div style={heading}>Gate log</div> <div style={heading}>Gate log</div>
{stats.gates.length === 0 ? ( {stats.gates.length === 0 ? (
<span style={muted}>No gates yet.</span> <span style={muted}>No gates yet the first weekly review opens Saturday night.</span>
) : ( ) : (
<table style={{ borderCollapse: "collapse", width: "100%" }}> stats.gates.map((g) => (
<thead> <div
<tr> key={g.week}
{["Week", "Issue", "Pass rate", "Closed"].map((h) => ( style={{
<th key={h} style={{ ...muted, textAlign: "left", padding: "0.2rem 1rem 0.2rem 0" }}> display: "flex",
{h} gap: "0.75rem",
</th> alignItems: "baseline",
))} flexWrap: "wrap",
</tr> padding: "0.15rem 0",
</thead> }}
<tbody> >
{stats.gates.map((g) => ( <span style={{ width: "4.5rem", flexShrink: 0 }}>Week {g.week}</span>
<tr key={g.week}>
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>{g.week}</td>
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>
{g.issue === null ? ( {g.issue === null ? (
<span style={muted}></span> <span style={muted}>no review issue</span>
) : ( ) : (
<a <a
href={`https://github.com/prdlk/leetcode/issues/${g.issue}`} href={`https://github.com/prdlk/leetcode/issues/${g.issue}`}
@@ -252,48 +301,70 @@ function Gates({ stats }: { stats: Stats }) {
#{g.issue} #{g.issue}
</a> </a>
)} )}
</td>
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>
{g.pass_rate === null ? ( {g.pass_rate === null ? (
<span style={muted}>open</span> <span style={muted}>in progress</span>
) : ( ) : (
percent(g.pass_rate) <span>
{percent(g.pass_rate)} passed
{g.closed_on && <span style={muted}> · closed {g.closed_on}</span>}
</span>
)} )}
</td> </div>
<td style={{ padding: "0.2rem 0" }}> ))
{g.closed_on ?? <span style={muted}></span>}
</td>
</tr>
))}
</tbody>
</table>
)} )}
<div style={{ ...muted, marginTop: "0.4rem" }}>
One gate per campaign week scored when its review issue closes.
</div>
</div> </div>
); );
} }
// GREENS ramp from apps/api/src/charts.ts; level 0 swapped for the theme's
// muted token so an empty day looks right in both colour schemes.
const GREENS = [
"var(--blume-muted)",
"#0e4429",
"#006d32",
"#26a641",
"#39d353",
];
function Recent({ stats }: { stats: Stats }) { function Recent({ stats }: { stats: Stats }) {
return ( return (
<div style={card}> <div style={card}>
<div style={heading}>Last 7 days</div> <div style={heading}>Last 7 days</div>
<div style={{ display: "flex", gap: "0.5rem", flexWrap: "wrap" }}> <div style={{ display: "flex", gap: "0.4rem", flexWrap: "wrap" }}>
{stats.recent.map((r) => ( {stats.recent.map((r) => (
<span <span
key={r.date} key={r.date}
style={{ title={`${r.date}: ${r.passes}/${r.attempts} passed`}
border: "1px solid var(--blume-border)", style={{ textAlign: "center" }}
borderRadius: "var(--blume-radius)",
padding: "0.35rem 0.6rem",
textAlign: "center",
}}
> >
<span style={{ ...muted, display: "block" }}>{dayLabel(r.date)}</span> <span
<strong>{r.passes}</strong> style={{
<span style={muted}>/{r.attempts}</span> display: "block",
width: "2.6rem",
height: "2rem",
borderRadius: "var(--blume-radius)",
background: GREENS[Math.min(r.attempts, 4)],
}}
/>
<span style={{ ...muted, display: "block", marginTop: "0.25rem", fontSize: "0.75em" }}>
{dayLabel(r.date)}
</span>
</span> </span>
))} ))}
</div> </div>
<div style={{ ...muted, marginTop: "0.4rem" }}>passes / attempts</div> <div style={{ ...muted, marginTop: "0.5rem", display: "flex", alignItems: "center", gap: "0.25rem" }}>
<span>Less</span>
{GREENS.map((color) => (
<span
key={color}
style={{ display: "inline-block", width: "0.65rem", height: "0.65rem", borderRadius: "2px", background: color }}
/>
))}
<span>More · colour = attempts, hover for passes</span>
</div>
</div> </div>
); );
} }