kimai-plugin-heatmap/.planning/milestones/v1.0-phases/phase-3/03-RESEARCH.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

85 lines
3.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Phase 3 Research: Core Heatmap Rendering
**Researched:** 2026-04-08
## Kimai Widget Asset Serving
### How existing widgets load JS
- Kimai widgets use **inline `<script>` tags** in their Twig templates
- JS fires on `kimai.initialized` event (or immediately if `kimai_context.javascriptRequest` is true)
- Chart.js widgets (DailyWorkingTimeChart, YearChart) use global `Chart` object loaded via Encore
- No widget uses `<script type="module">` — all use plain `<script type="text/javascript">`
### Plugin asset path
- Plugin `Resources/public/``public/bundles/kimaiheatmap/` via `bin/console assets:install --symlink`
- Accessible in Twig: `asset('heatmap.js', 'KimaiHeatmap')` — but lowercase bundle name in path
- Alternative: inline the bundled JS directly in the template (simpler, avoids asset path issues)
### Recommended approach for d3 heatmap
**Bundle d3 modules with esbuild into a single IIFE file**, then either:
1. Serve from `Resources/public/heatmap.js` via asset symlink + `<script>` tag
2. Or inline the compiled JS in the template
Option 1 is cleaner. The esbuild output should be an IIFE (not ESM) since Kimai templates use plain script tags.
### kimai_context.javascriptRequest
Boolean flag set by Kimai. When the dashboard reloads via AJAX (e.g., GridStack widget repositioning), this is `true`. The pattern:
```twig
{% if kimai_context.javascriptRequest %}
renderHeatmap();
{% else %}
document.addEventListener('kimai.initialized', renderHeatmap);
{% endif %}
```
### Widget template variables
When `render_widget(widget)` is called, the template receives:
- `widget` — the widget instance
- `title` — from `widget.getTitle()`
- `data` — from `widget.getData()`
- `options` — from `widget.getOptions()`
### card.html.twig embed blocks
- `box_title` — card header text
- `box_body` — main content area
- `box_footer` — optional footer
- `box_tools` — header action buttons
- `box_header` — full header override
### Dashboard initialization events
- `kimai.initialized` — main app ready
- `dashboard.initialized` — GridStack layout ready (only in grid.html.twig)
## d3 Calendar Heatmap Pattern
### Grid layout
- 53 columns (weeks) × 7 rows (days, Mon-Sun)
- Cell size: ~12-14px square with 2px gap
- Total width: ~800px, fits full-width widget
### Color scale
Use `d3.scaleSequential` with interpolator mapped to CSS variables:
- 0 hours: `var(--heatmap-empty)` (light gray / dark theme equivalent)
- Low: light green
- High: dark green
- Use Kimai's `--bs-success` as base, generate lighter/darker variants
### Tooltip
HTML div positioned absolutely near the hovered cell. Show date, hours, entry count.
### Month labels
Detect when week crosses month boundary. Place label at first week of each month.
### Day-of-week labels
Mon, Wed, Fri on Y-axis (standard GitHub heatmap pattern — skip Tue/Thu/Sat/Sun for space).
## Testing Strategy
### Vitest + jsdom
- d3-selection works with jsdom's DOM
- Test SVG output: correct number of `<rect>` elements, color attributes, tooltip content
- Snapshot test for overall structure
- Unit test color scale mapping
### Test data
Generate mock API response matching `{ days: [{date, hours, count}], range: {begin, end} }` format.