105 lines
3.4 KiB
Markdown
105 lines
3.4 KiB
Markdown
# Plan 05-01: Streak, Stats, Weekend Styling, Week Start
|
|
|
|
**Phase:** 5 — Polish
|
|
**Requirements:** POLI-01, POLI-02, POLI-03 + start-of-week preference
|
|
**Estimated tasks:** 6
|
|
|
|
## Goal
|
|
|
|
Add streak indicator, summary stats row, weekend cell styling, and configurable week start to the heatmap widget.
|
|
|
|
## Tasks
|
|
|
|
### Task 1: Add week start to PHP widget data
|
|
|
|
**File:** `Widget/HeatmapWidget.php`
|
|
**Action:** In `getData()`, read `$user->getFirstDayOfWeek()` and include it in the returned array as `'weekStart'`.
|
|
|
|
**File:** `Resources/views/widget/heatmap.html.twig`
|
|
**Action:** Add `data-week-start="{{ data.weekStart }}"` to the `#heatmap-container` div.
|
|
|
|
### Task 2: Make week start configurable in TypeScript
|
|
|
|
**File:** `assets/src/heatmap.ts`
|
|
**Changes:**
|
|
- In `init()`, read `data-week-start` attribute from container (default: `'monday'`)
|
|
- Pass `weekStart` string to `renderHeatmap()` via a new optional parameter
|
|
- In `generateCells()`, replace hardcoded `timeMonday` with the appropriate d3 time interval based on `weekStart`:
|
|
- `'sunday'` → `timeSunday` (import from d3-time)
|
|
- `'monday'` → `timeMonday` (existing)
|
|
- Update `DAY_LABELS` to rotate based on week start
|
|
- Update `dayOfWeek` calculation to match (row 0 = first day of week)
|
|
|
|
### Task 3: Add weekend cell styling
|
|
|
|
**File:** `assets/src/heatmap.ts`
|
|
**Changes:**
|
|
- In `generateCells()`, add `isWeekend: boolean` to `DayCell` interface (Saturday=6, Sunday=0 in JS `getDay()`)
|
|
- In the cell rendering `.attr('class', ...)`, append `heatmap-weekend` class for weekend cells
|
|
|
|
**File:** `Resources/public/heatmap.css`
|
|
**Add:**
|
|
```css
|
|
.heatmap-weekend {
|
|
opacity: 0.8;
|
|
}
|
|
|
|
.heatmap-weekend:hover {
|
|
opacity: 0.65;
|
|
}
|
|
```
|
|
|
|
### Task 4: Add streak calculation
|
|
|
|
**File:** `assets/src/heatmap.ts`
|
|
**Add function** `calculateStreak(days: DayEntry[]): number`:
|
|
- Sort days by date descending
|
|
- Starting from today (or yesterday if today has no entry), count consecutive calendar days with entries
|
|
- Return the count
|
|
- A day with `hours > 0` counts as tracked
|
|
|
|
### Task 5: Add summary stats calculation
|
|
|
|
**File:** `assets/src/heatmap.ts`
|
|
**Add function** `calculateStats(days: DayEntry[]): { totalHours: number, avgHours: number, busiestDay: { date: string, hours: number } | null }`:
|
|
- `totalHours`: sum of all hours, rounded to 1 decimal
|
|
- `avgHours`: total hours / number of days with entries, rounded to 1 decimal
|
|
- `busiestDay`: day entry with maximum hours
|
|
|
|
### Task 6: Render streak and stats below heatmap
|
|
|
|
**File:** `assets/src/heatmap.ts`
|
|
**In `init()`**, after `renderHeatmap()` resolves:
|
|
- Call `calculateStreak()` and `calculateStats()` with the fetched data
|
|
- Create a stats row div below the SVG area with class `heatmap-stats`
|
|
- Content: `🔥 N days | Total: Xh | Avg: Xh/day | Busiest: Mon, Mar 15 — Xh`
|
|
- When filter changes and data reloads, recalculate and update stats
|
|
|
|
**File:** `Resources/public/heatmap.css`
|
|
**Add:**
|
|
```css
|
|
.heatmap-stats {
|
|
display: flex;
|
|
gap: 16px;
|
|
padding: 8px 0 0;
|
|
font-size: 0.8125rem;
|
|
color: var(--tblr-secondary, #6c757d);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.heatmap-stats .stat-value {
|
|
color: var(--tblr-body-color);
|
|
font-weight: 600;
|
|
}
|
|
```
|
|
|
|
## Verification
|
|
|
|
- `npm run build` succeeds
|
|
- Widget shows streak count, three stat values, weekend cells with reduced opacity
|
|
- Changing week start preference changes grid layout and labels
|
|
- Filter change recalculates stats
|
|
|
|
## Commit
|
|
|
|
`feat: add streak, stats, weekend styling, week-start preference`
|