kimai-plugin-heatmap/.planning/milestones/v1.0-phases/phase-4/PLAN.md
Christopher Mühl 244c7c66fc
chore: archive v1.0 milestone
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 23:25:26 +02:00

142 lines
5.6 KiB
Markdown

# Phase 4: Heatmap Interaction — Plan
**Goal:** Users can click through to daily details and filter the heatmap by project or activity
**Requirements:** HEAT-07, INTR-01, TEST-04
**Research:** phase-4/04-RESEARCH.md
## Success Criteria
1. Clicking a day cell navigates to Kimai's timesheet view filtered to that specific date
2. A dropdown allows filtering the heatmap to show data for a single project or activity
3. Filtering updates the heatmap in place without a full page reload
4. JavaScript tests verify click navigation and tooltip interaction behavior
## Key Decisions from Research
- **Click navigation** — `window.location.href` to `/timesheet` with date param. Timesheet URL passed via `data-timesheet-url` template attribute.
- **Project dropdown** — Fetch `/api/projects` client-side, render `<select>` above heatmap. On change, re-fetch `/heatmap/data?project=<id>` and re-render.
- **No backend changes** — HeatmapController already supports `?project=` filtering. Project API is built into Kimai.
- **Activity filtering deferred** — INTR-01 says "project or activity". Start with project filter; activity can be added as a follow-up if needed since the API pattern is identical.
## Waves
### Wave 1: Click-through + Project Filter (autonomous)
#### Plan 04-01: Day cell click navigation + project filter dropdown
**Objective:** Add click-to-navigate on day cells and a project filter dropdown that re-renders the heatmap.
**Task 1: Update types**
Add to `assets/src/types.ts`:
```typescript
export interface Project {
id: number;
name: string;
}
export interface HeatmapOptions {
dataUrl: string;
timesheetUrl: string;
}
```
**Task 2: Add click handler to heatmap cells**
In `assets/src/heatmap.ts`:
- Add a `timesheetUrl` parameter to `renderHeatmap` (passed via options or config)
- On each `<rect>` cell, add a `click` event handler:
- Navigate to `${timesheetUrl}?dateRange=${dateStr}+-+${dateStr}`
- Only navigate if the cell has data (skip empty days)
- Add `cursor: pointer` style to cells with data
- Add `data-date` attribute to each rect for testability
**Task 3: Add project filter dropdown**
In `assets/src/heatmap.ts`, update the `init` function:
1. Before fetching heatmap data, fetch `/api/projects?visible=1&orderBy=name`
2. Create a `<select>` element with:
- Default option: "All projects"
- One `<option>` per project with `value=project.id`
3. Insert the select above the heatmap container (or in a header area)
4. On `change`, re-fetch heatmap data with `?project=<selectedId>` and call `renderHeatmap` again
5. Style the select to match Kimai's form controls (use `form-select` Bootstrap class)
**Task 4: Update widget template**
Update `Resources/views/widget/heatmap.html.twig`:
- Add `data-timesheet-url="{{ path('timesheet') }}"` to the container div
- The `data-url` attribute already exists for the heatmap API
**Task 5: Build and verify**
- `npm run build` — produces updated bundle
- `npm test` — existing tests still pass
- `php dev/kimai/vendor/bin/phpunit --configuration Tests/phpunit.xml` — PHP tests pass
**Commit:** `feat: add day click navigation and project filter dropdown`
---
### Wave 2: Interaction Tests (autonomous)
#### Plan 04-02: Vitest tests for click navigation and filter behavior
**Objective:** Add JavaScript tests covering the new interaction features.
**Task 1: Write click navigation tests**
Add to `assets/test/heatmap.test.ts`:
1. **Click on cell with data** — simulate click on a cell, verify `window.location.href` was set to expected timesheet URL with date
2. **Click on empty cell** — simulate click, verify no navigation occurred
3. **Cell cursor style** — cells with data have `cursor: pointer`, empty cells don't
Test approach:
- Mock `window.location` (use `Object.defineProperty` or vitest's `vi.stubGlobal`)
- Pass `timesheetUrl` option to `renderHeatmap`
- Use `container.querySelector('[data-date="2026-04-01"]')` to find specific cells
- Dispatch `click` event
**Task 2: Write project filter tests**
Add tests for the filter dropdown behavior:
1. **Dropdown renders with projects** — after init with mocked fetch, a `<select>` element exists with project options
2. **Filter triggers re-fetch** — changing dropdown value triggers a new fetch with `?project=<id>` param
3. **Heatmap re-renders** — after filter change, new data is rendered (different rect count or content)
4. **"All projects" resets filter** — selecting default option fetches without project param
Test approach:
- Mock `fetch` to return project list and heatmap data
- Call `init(container)` and await fetch completion
- Programmatically change select value and dispatch `change` event
- Assert fetch was called with correct URL params
**Verification:**
- `npm test` — all tests pass (existing + new)
- `npm run build` — bundle builds successfully
**Commit:** `test: add click navigation and project filter tests`
---
## Requirement Coverage
| Requirement | Plan | Verified By |
|-------------|------|-------------|
| HEAT-07 | 04-01 | Click navigates to timesheet; Vitest click test |
| INTR-01 | 04-01 | Project dropdown filters heatmap; Vitest filter test |
| TEST-04 | 04-02 | Vitest tests for click + filter interaction |
## Risks
| Risk | Mitigation |
|------|------------|
| Timesheet date filter URL format wrong | Test with actual Kimai instance; adjust format if needed |
| `/api/projects` requires different auth | API uses same session auth as the dashboard; should work |
| Dropdown styling doesn't match Kimai | Use Bootstrap `form-select` class from Tabler theme |
| Re-render flickers | Clear container and re-render; fast enough for ~365 rects |
---
*Plan created: 2026-04-08*