Skip to content

Commit e7a2b07

Browse files
dsshimelclaude
andcommitted
replace Sunday rows with weekly summary in student stats
Sundays are not meeting days. Instead of showing a row for each Sunday, display a weekly aggregation (Mon-Sat): attendance as on-time/late/missed, midday PR and EOD as days-with/6, and total PRs summed for the week. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 33b2e24 commit e7a2b07

File tree

1 file changed

+119
-38
lines changed

1 file changed

+119
-38
lines changed
Lines changed: 119 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
/**
22
* @fileoverview Displays daily stats for the student: attendance, PRs, EOD.
3+
* Sundays are replaced with a weekly summary row aggregating Mon-Sat.
34
*/
45

5-
import { useState, useEffect } from "react";
6+
import { useState, useEffect, useMemo } from "react";
67
import { getMyStats } from "../api/client";
78
import type { DailyStats } from "../api/client";
89

910
interface StudentStatsProps {
1011
studentDiscordId?: string;
1112
}
1213

14+
/** A row in the stats table — either a single day or a weekly summary. */
15+
type StatsRow =
16+
| { type: "day"; day: DailyStats }
17+
| { type: "week"; sundayDate: string; weekDays: DailyStats[] };
18+
1319
/** Renders a table of daily stats for the student over the last 30 days. */
1420
export function StudentStats({ studentDiscordId }: StudentStatsProps) {
1521
const [days, setDays] = useState<DailyStats[]>([]);
@@ -31,6 +37,35 @@ export function StudentStats({ studentDiscordId }: StudentStatsProps) {
3137
});
3238
}, [studentDiscordId]);
3339

40+
/** Build rows: normal days for Mon-Sat, weekly summary rows replacing Sundays. */
41+
const rows: StatsRow[] = useMemo(() => {
42+
// days is sorted DESC by date
43+
const result: StatsRow[] = [];
44+
// Group days by their week (Sunday = week boundary)
45+
// For each Sunday, collect the preceding Mon-Sat
46+
const daysByDate = new Map(days.map((d) => [d.date, d]));
47+
48+
for (const day of days) {
49+
const date = new Date(day.date + "T12:00:00");
50+
const dow = date.getDay(); // 0 = Sunday
51+
if (dow === 0) {
52+
// Collect Mon-Sat of this week (Mon = Sunday-6, Sat = Sunday-1)
53+
const weekDays: DailyStats[] = [];
54+
for (let offset = 6; offset >= 1; offset--) {
55+
const d = new Date(date);
56+
d.setDate(d.getDate() - offset);
57+
const key = d.toISOString().split("T")[0];
58+
const found = daysByDate.get(key);
59+
if (found) weekDays.push(found);
60+
}
61+
result.push({ type: "week", sundayDate: day.date, weekDays });
62+
} else {
63+
result.push({ type: "day", day });
64+
}
65+
}
66+
return result;
67+
}, [days]);
68+
3469
if (loading) {
3570
return (
3671
<div className="panel" style={{ gridColumn: "span 2" }}>
@@ -58,47 +93,93 @@ export function StudentStats({ studentDiscordId }: StudentStatsProps) {
5893
</tr>
5994
</thead>
6095
<tbody>
61-
{days.map((day) => (
62-
<tr key={day.date}>
63-
<td>
64-
{new Date(day.date + "T12:00:00").toLocaleDateString("en-US", {
65-
weekday: "short",
66-
month: "short",
67-
day: "numeric",
68-
})}
69-
</td>
70-
<td>
71-
{day.attendancePosted ? (
72-
<span style={{ color: day.attendanceOnTime ? "#2d6a2e" : "#8B6914" }}>
73-
{day.attendanceOnTime ? "On time" : "Late"}
74-
</span>
75-
) : (
76-
<span style={{ color: "var(--color-error)" }}>Missed</span>
77-
)}
78-
</td>
79-
<td>
80-
{day.middayPrPosted ? (
81-
<span style={{ color: "#2d6a2e" }}>
82-
{day.middayPrCount} PR{day.middayPrCount !== 1 ? "s" : ""}
83-
</span>
84-
) : (
85-
<span style={{ color: "var(--color-error)" }}>None</span>
86-
)}
87-
</td>
88-
<td>
89-
{day.eodPosted ? (
90-
<span style={{ color: "#2d6a2e" }}>Posted</span>
91-
) : (
92-
<span style={{ color: "var(--color-error)" }}>Missing</span>
93-
)}
94-
</td>
95-
<td style={{ fontWeight: 700 }}>{day.totalPrCount}</td>
96-
</tr>
97-
))}
96+
{rows.map((row) =>
97+
row.type === "day" ? (
98+
<DayRow key={row.day.date} day={row.day} />
99+
) : (
100+
<WeekRow key={row.sundayDate} sundayDate={row.sundayDate} weekDays={row.weekDays} />
101+
),
102+
)}
98103
</tbody>
99104
</table>
100105
</div>
101106
)}
102107
</div>
103108
);
104109
}
110+
111+
/** Renders a single day row. */
112+
function DayRow({ day }: { day: DailyStats }) {
113+
return (
114+
<tr>
115+
<td>
116+
{new Date(day.date + "T12:00:00").toLocaleDateString("en-US", {
117+
weekday: "short",
118+
month: "short",
119+
day: "numeric",
120+
})}
121+
</td>
122+
<td>
123+
{day.attendancePosted ? (
124+
<span style={{ color: day.attendanceOnTime ? "#2d6a2e" : "#8B6914" }}>
125+
{day.attendanceOnTime ? "On time" : "Late"}
126+
</span>
127+
) : (
128+
<span style={{ color: "var(--color-error)" }}>Missed</span>
129+
)}
130+
</td>
131+
<td>
132+
{day.middayPrPosted ? (
133+
<span style={{ color: "#2d6a2e" }}>
134+
{day.middayPrCount} PR{day.middayPrCount !== 1 ? "s" : ""}
135+
</span>
136+
) : (
137+
<span style={{ color: "var(--color-error)" }}>None</span>
138+
)}
139+
</td>
140+
<td>
141+
{day.eodPosted ? (
142+
<span style={{ color: "#2d6a2e" }}>Posted</span>
143+
) : (
144+
<span style={{ color: "var(--color-error)" }}>Missing</span>
145+
)}
146+
</td>
147+
<td style={{ fontWeight: 700 }}>{day.totalPrCount}</td>
148+
</tr>
149+
);
150+
}
151+
152+
/** Renders a weekly summary row replacing a Sunday. */
153+
function WeekRow({ sundayDate, weekDays }: { sundayDate: string; weekDays: DailyStats[] }) {
154+
const onTime = weekDays.filter((d) => d.attendancePosted && d.attendanceOnTime).length;
155+
const late = weekDays.filter((d) => d.attendancePosted && !d.attendanceOnTime).length;
156+
const missed = weekDays.filter((d) => !d.attendancePosted).length;
157+
const middayDays = weekDays.filter((d) => d.middayPrPosted).length;
158+
const eodDays = weekDays.filter((d) => d.eodPosted).length;
159+
const totalPrs = weekDays.reduce((sum, d) => sum + d.totalPrCount, 0);
160+
161+
// Find Mon date for the label (Sunday - 6)
162+
const sun = new Date(sundayDate + "T12:00:00");
163+
const mon = new Date(sun);
164+
mon.setDate(mon.getDate() - 6);
165+
const fmt = (d: Date) =>
166+
d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
167+
168+
return (
169+
<tr style={{ background: "var(--color-platinum)" }}>
170+
<td style={{ fontWeight: 700 }}>
171+
Week: {fmt(mon)}{fmt(sun)}
172+
</td>
173+
<td>
174+
<span style={{ color: "#2d6a2e" }}>{onTime}</span>
175+
{" / "}
176+
<span style={{ color: "#8B6914" }}>{late}</span>
177+
{" / "}
178+
<span style={{ color: "var(--color-error)" }}>{missed}</span>
179+
</td>
180+
<td>{middayDays}/6</td>
181+
<td>{eodDays}/6</td>
182+
<td style={{ fontWeight: 700 }}>{totalPrs}</td>
183+
</tr>
184+
);
185+
}

0 commit comments

Comments
 (0)