mirror of
https://github.com/prdlk/leetcode.git
synced 2026-09-17 07:26:27 +00:00
docs(docs): revamp progress dashboard UI and add week field comment
This commit is contained in:
@@ -35,6 +35,7 @@ interface Stats {
|
||||
deferred_total: number;
|
||||
}[];
|
||||
ladder: Record<Stage, number>;
|
||||
/** week is the 1-based campaign week — the Worker converts from ISO. */
|
||||
gates: {
|
||||
week: number;
|
||||
issue: number | null;
|
||||
@@ -76,7 +77,12 @@ function dayLabel(iso: string): string {
|
||||
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 = {
|
||||
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 }) {
|
||||
const maxTotal = Math.max(
|
||||
1,
|
||||
...stats.phases.map((p) => p.core_total + p.optional_total + p.deferred_total),
|
||||
);
|
||||
return (
|
||||
<div style={card}>
|
||||
<div style={heading}>Phases</div>
|
||||
<table style={{ borderCollapse: "collapse", width: "100%" }}>
|
||||
<tbody>
|
||||
{stats.phases.map((p) => (
|
||||
<tr key={p.milestone}>
|
||||
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>{p.name}</td>
|
||||
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>
|
||||
<Bar done={p.core_done} total={p.core_total} />{" "}
|
||||
<span style={muted}>
|
||||
core {p.core_done}/{p.core_total}
|
||||
<div style={{ display: "flex", gap: "1rem", flexWrap: "wrap", marginBottom: "0.6rem" }}>
|
||||
{SETS.map(([set, color]) => (
|
||||
<span key={set} style={muted}>
|
||||
<span
|
||||
style={{
|
||||
display: "inline-block",
|
||||
width: "0.65rem",
|
||||
height: "0.65rem",
|
||||
borderRadius: "2px",
|
||||
background: color,
|
||||
marginRight: "0.35rem",
|
||||
}}
|
||||
/>
|
||||
{set}
|
||||
</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>
|
||||
</table>
|
||||
</div>
|
||||
{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>
|
||||
);
|
||||
}
|
||||
@@ -224,25 +276,22 @@ function Gates({ stats }: { stats: Stats }) {
|
||||
<div style={card}>
|
||||
<div style={heading}>Gate log</div>
|
||||
{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%" }}>
|
||||
<thead>
|
||||
<tr>
|
||||
{["Week", "Issue", "Pass rate", "Closed"].map((h) => (
|
||||
<th key={h} style={{ ...muted, textAlign: "left", padding: "0.2rem 1rem 0.2rem 0" }}>
|
||||
{h}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{stats.gates.map((g) => (
|
||||
<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" }}>
|
||||
stats.gates.map((g) => (
|
||||
<div
|
||||
key={g.week}
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "0.75rem",
|
||||
alignItems: "baseline",
|
||||
flexWrap: "wrap",
|
||||
padding: "0.15rem 0",
|
||||
}}
|
||||
>
|
||||
<span style={{ width: "4.5rem", flexShrink: 0 }}>Week {g.week}</span>
|
||||
{g.issue === null ? (
|
||||
<span style={muted}>—</span>
|
||||
<span style={muted}>no review issue</span>
|
||||
) : (
|
||||
<a
|
||||
href={`https://github.com/prdlk/leetcode/issues/${g.issue}`}
|
||||
@@ -252,48 +301,70 @@ function Gates({ stats }: { stats: Stats }) {
|
||||
#{g.issue}
|
||||
</a>
|
||||
)}
|
||||
</td>
|
||||
<td style={{ padding: "0.2rem 1rem 0.2rem 0" }}>
|
||||
{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>
|
||||
<td style={{ padding: "0.2rem 0" }}>
|
||||
{g.closed_on ?? <span style={muted}>—</span>}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
<div style={{ ...muted, marginTop: "0.4rem" }}>
|
||||
One gate per campaign week — scored when its review issue closes.
|
||||
</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 }) {
|
||||
return (
|
||||
<div style={card}>
|
||||
<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) => (
|
||||
<span
|
||||
key={r.date}
|
||||
style={{
|
||||
border: "1px solid var(--blume-border)",
|
||||
borderRadius: "var(--blume-radius)",
|
||||
padding: "0.35rem 0.6rem",
|
||||
textAlign: "center",
|
||||
}}
|
||||
title={`${r.date}: ${r.passes}/${r.attempts} passed`}
|
||||
style={{ textAlign: "center" }}
|
||||
>
|
||||
<span style={{ ...muted, display: "block" }}>{dayLabel(r.date)}</span>
|
||||
<strong>{r.passes}</strong>
|
||||
<span style={muted}>/{r.attempts}</span>
|
||||
<span
|
||||
style={{
|
||||
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>
|
||||
))}
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user