162 lines
13 KiB
Markdown
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*
|