kimai-plugin-heatmap/.planning/research/SUMMARY.md

162 lines
13 KiB
Markdown

# Project Research Summary
**Project:** Kimai Heatmap Plugin v1.1 (Modes & Filtering)
**Domain:** Time-tracking dashboard visualization -- multi-mode heatmap with cascading entity filters
**Researched:** 2026-04-08
**Confidence:** HIGH
## Executive Summary
v1.1 adds four visualization modes (year, week, day-of-week, day/hour combined matrix), cascading entity pickers (customer/project/activity), and an hours-vs-count display toggle to the existing heatmap widget. The existing stack (PHP 8.2, Symfony 6.4, d3 v7, TypeScript, esbuild) handles everything. The only new dependency is TomSelect for entity pickers -- and even that should be deferred until the final phase. The core work is a rendering architecture refactor (strategy pattern for modes), three new backend aggregation queries, and a state management object to coordinate filters, modes, and display metric.
The recommended approach is to refactor the monolithic `renderHeatmap()` into a mode-dispatched renderer system BEFORE adding any new visualization. This is the critical sequencing insight: state management and the renderer interface must exist before the first new mode lands, or every subsequent mode will be a retrofit nightmare. Week-mode (day-of-week aggregation) should come first because it needs zero backend changes -- it aggregates existing daily data client-side -- which validates the renderer architecture cheaply. Entity pickers come last because they are the most complex integration point and everything else works fine with the existing project dropdown.
The primary risks are: (1) accidentally coupling to Kimai's internal KimaiFormSelect.js system, which silently fails outside Kimai's form lifecycle; (2) tooltip/DOM cleanup leaks when switching between modes; and (3) TomSelect bundle duplication. All three are well-understood and have clear preventions documented in the research. The hour-level aggregation queries (day/combined modes) are the only performance concern -- they defeat index usage -- but for personal use with <10K entries this is acceptable latency, not a blocker.
## Key Findings
### Recommended Stack
No new PHP/Composer dependencies. One new npm dependency: `tom-select@^2.4.3` (only when entity pickers are implemented). All four visualization modes use existing d3 sub-modules -- no new d3 packages needed. `d3-shape` was listed in v1.0 research but was never needed and should not be added.
**New additions only:**
- **TomSelect ^2.4.3**: Cascading entity pickers -- matches Kimai's own version for visual consistency. Bundle JS only (Kimai already loads TomSelect CSS globally). Defer to final phase.
- **Tabler btn-group**: Mode switcher and display toggle UI -- already loaded by Kimai, zero cost.
**Explicitly rejected:** cal-heatmap, d3-axis, d3-transition, d3-shape, luxon/date-fns, TomSelect CSS bundle, Kimai's KimaiFormSelect.js.
### Expected Features
**Must have (table stakes):**
- Mode switcher UI (segmented control) with year + week modes
- Hours/count display toggle (data already present, purely frontend)
- Activity filtering (backend `?activity=N` param)
- Cascading entity pickers (customer -> project -> activity)
- Persistent filter/mode state across re-renders
**Should have (differentiators):**
- Day-of-week mode (week view) -- which weekdays are busiest
- Time-of-day mode (day view) -- when during the day work happens
- Combined day/hour matrix -- full punchcard (7x24 grid)
- Color scale legend
- Customer-level filtering
**Defer (v2+):**
- Configurable date range selector
- Animated transitions between modes
- Drag-to-select, drill-down charts, export/share
- Multi-user comparison, real-time refresh, custom color themes
### Architecture Approach
Refactor the monolithic `renderHeatmap()` into a strategy-pattern mode system. A `ModeController` dispatches to mode-specific renderers (`year.ts`, `week.ts`, `day.ts`, `combined.ts`) that share extracted utilities (tooltip, color scale, cell click handler). A centralized `HeatmapState` object tracks mode, display metric, filters, and cached data. All UI changes (mode switch, filter change, metric toggle) update state then call a unified render dispatcher. The display toggle (hours/count) re-renders from cached data without a new fetch; all other changes trigger a fetch.
**Major components:**
1. **ModeController** -- mode switching, data fetching orchestration, state management
2. **Renderers (year/week/day/combined)** -- mode-specific layout logic implementing a shared `ModeRenderer` interface
3. **Shared utilities (tooltip, colorScale)** -- extracted from current `renderHeatmap()`, reused by all modes
4. **Filter bar (filters.ts)** -- TomSelect entity pickers with cascade logic, independent of Kimai's form system
5. **HeatmapService (PHP)** -- three new aggregation methods (`getWeekdayAggregation`, `getHourlyAggregation`, `getCombinedAggregation`)
6. **HeatmapController (PHP)** -- `mode` and `activity` query params, custom cascade endpoints
### Critical Pitfalls
1. **KimaiFormSelect cannot be used standalone** -- It depends on Kimai's plugin container (`getContainer().getPlugin('api')`). Selects with `data-api-url` inside the widget card silently fail. Prevention: roll your own cascade with plain `fetch()` calls to custom controller endpoints.
2. **Tooltip DOM leaks on mode switch** -- Tooltips are appended to `document.body`, outside the SVG container. `container.innerHTML = ''` does not clean them up. Prevention: shared tooltip module with a single reusable tooltip div.
3. **Mode state lost on filter change** -- Current `doRender()` always calls `renderHeatmap()` (year view). Without a state object, any filter change resets the mode. Prevention: implement `HeatmapState` BEFORE adding any new mode.
4. **Color scale domain mismatch across modes** -- Year mode maxes at ~12h, week mode at ~200h, day mode at ~3h. A shared scale makes some modes unreadable. Prevention: each mode computes its own color scale domain.
5. **TomSelect bundle duplication** -- Kimai already bundles TomSelect but does not expose it globally. Importing it again adds ~30KB. Prevention: start with plain `<select>` elements; add TomSelect only if list sizes demand it, and verify `window.TomSelect` availability first.
## Implications for Roadmap
### Phase 1: Renderer Refactor + State Management
**Rationale:** Everything depends on this. Cannot add modes without the renderer interface. Cannot coordinate filters/modes without state management. Zero new features -- pure refactor that preserves existing behavior.
**Delivers:** Strategy-pattern renderer system, extracted shared utilities (tooltip, colorScale), `HeatmapState` object, `ModeRenderer` interface, year-view refactored into `renderers/year.ts`.
**Addresses:** Architectural foundation for all subsequent phases.
**Avoids:** Pitfall 4 (mode state lost), Pitfall 2 (tooltip leaks), Pitfall 12 (renderer divergence).
### Phase 2: Mode Switcher + Week Mode + Display Toggle
**Rationale:** First user-visible v1.1 feature with zero backend changes. Week mode aggregates existing daily data client-side. Proves the mode system works end-to-end before adding backend complexity.
**Delivers:** Mode switcher UI (Tabler segmented control), week-mode renderer, hours/count toggle, all wired through state management.
**Addresses:** Mode switcher UI, day-of-week mode, hours/count toggle (3 table-stakes features).
**Avoids:** Pitfall 6 (color scale mismatch -- week mode has very different domain than year), Pitfall 10 (toggle is a full re-render, not CSS swap), Pitfall 11 (week-start must propagate).
### Phase 3: Backend Aggregation + Activity Filtering + Custom Endpoints
**Rationale:** Day and combined modes need new backend queries. Activity filtering needs a new query param. Custom cascade endpoints are needed for Phase 5's entity pickers and avoid API auth pitfalls. Group all backend work together.
**Delivers:** `getHourlyAggregation()`, `getCombinedAggregation()`, `getWeekdayAggregation()` (backend optimization over client-side), `mode` query param on data endpoint, `activity` filter param, cascade endpoints (`/heatmap/customers`, `/heatmap/projects`, `/heatmap/activities`).
**Addresses:** Backend foundation for day/combined modes, activity filtering.
**Avoids:** Pitfall 3 (hour query performance -- profile with EXPLAIN), Pitfall 5 (API response format -- own endpoints return simple `{id, name}`), Pitfall 8 (API auth -- own endpoints use session auth).
### Phase 4: Day + Combined Visualization Modes
**Rationale:** Backend data is ready from Phase 3. Fill in the remaining renderers.
**Delivers:** Day-mode (24-column hour-of-day heatmap), combined mode (7x24 punchcard matrix), color scale legend.
**Addresses:** Time-of-day mode, combined matrix, legend (differentiator features).
**Avoids:** Pitfall 6 (per-mode color scales), Pitfall 11 (week-start in combined matrix).
### Phase 5: Entity Pickers (TomSelect Cascade)
**Rationale:** Most complex integration point. Everything else works with the existing plain project dropdown. Upgrades filtering UX without blocking other features. Needs runtime verification of TomSelect global availability.
**Delivers:** TomSelect-enhanced customer/project/activity pickers with cascading, replaces plain `<select>`, uses custom endpoints from Phase 3.
**Addresses:** Cascading entity pickers, customer-level filtering.
**Avoids:** Pitfall 1 (KimaiFormSelect dependency -- own cascade logic), Pitfall 7 (TomSelect duplication -- verify global first, bundle only as fallback).
### Phase Ordering Rationale
- Phases 1-2 deliver visible value with zero backend changes, validating the architecture cheaply.
- Phase 3 groups all backend work (queries + endpoints) to minimize PHP context-switching.
- Phase 4 depends on Phase 3's data but is purely frontend work.
- Phase 5 is isolated from everything else and has the most unknowns (TomSelect availability, cascade edge cases). Doing it last means it cannot block other features.
### Research Flags
Phases likely needing deeper research during planning:
- **Phase 5 (Entity Pickers):** TomSelect global availability must be verified in the dev environment before implementation. Cascade edge cases (global activities, empty lists) need testing against real data.
Phases with standard patterns (skip research-phase):
- **Phase 1 (Refactor):** Standard strategy pattern extraction. Existing code is well-understood.
- **Phase 2 (Modes + Toggle):** Tabler segmented controls are documented. Week-mode is simple client-side aggregation.
- **Phase 3 (Backend):** Doctrine DQL GROUP BY queries -- same pattern as existing `getDailyAggregation()`.
- **Phase 4 (Renderers):** d3 rect grid rendering -- same pattern as year-view with different layout math.
## Confidence Assessment
| Area | Confidence | Notes |
|------|------------|-------|
| Stack | HIGH | No new dependencies except TomSelect. All tools verified against existing codebase and Kimai source. |
| Features | HIGH | Feature set derived from existing codebase analysis and time-tracking domain conventions. Clear table-stakes vs differentiator separation. |
| Architecture | HIGH | Based on direct reading of existing plugin code and Kimai internals. Strategy pattern is well-understood. |
| Pitfalls | HIGH | All pitfalls identified from actual source code analysis (line numbers cited). Prevention strategies are concrete. |
**Overall confidence:** HIGH
### Gaps to Address
- **TomSelect global availability:** Must verify `window.TomSelect` in browser console on Kimai dashboard before Phase 5. If not available, decision: bundle (~30KB cost) or stick with plain selects.
- **Hour-level query performance:** Profile `GROUP BY HOUR(t.begin)` with EXPLAIN against the dev database during Phase 3. If slow, consider narrowing date range for day/combined modes or client-side aggregation of raw entries.
- **`t.begin` time component:** Verify that Kimai's Timesheet entity stores actual time-of-day in `t.begin` (not just date). Required for day and combined modes.
- **Week-start in DAYOFWEEK():** MySQL's `DAYOFWEEK()` returns 1=Sunday. Must map correctly based on user's week-start preference. Test both configurations.
## Sources
### Primary (HIGH confidence)
- Kimai source code (`dev/kimai/`) -- KimaiFormSelect.js, webpack.config.js, API controllers, form extensions
- Existing plugin codebase -- `assets/src/heatmap.ts`, `src/Service/HeatmapService.php`, `src/Controller/HeatmapController.php`
- d3.js documentation: https://d3js.org/
- TomSelect documentation: https://tom-select.js.org/
### Secondary (MEDIUM confidence)
- Tabler segmented control docs: https://docs.tabler.io/ui/components/segmented-control
- d3 heatmap patterns: https://d3-graph-gallery.com/heatmap.html
- tom-select on npm/bundlephobia (bundle size analysis)
---
*Research completed: 2026-04-08*
*Ready for roadmap: yes*