From 96d70dd1608b12248007de54ae63fd8e1ff8cbde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christopher=20M=C3=BChl?= Date: Thu, 9 Apr 2026 20:58:52 +0200 Subject: [PATCH] docs(08): UI design contract for backend aggregation phase Co-Authored-By: Claude Opus 4.6 --- .../08-UI-SPEC.md | 207 ++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 .planning/phases/08-backend-aggregation-filtering/08-UI-SPEC.md diff --git a/.planning/phases/08-backend-aggregation-filtering/08-UI-SPEC.md b/.planning/phases/08-backend-aggregation-filtering/08-UI-SPEC.md new file mode 100644 index 0000000..4364f4d --- /dev/null +++ b/.planning/phases/08-backend-aggregation-filtering/08-UI-SPEC.md @@ -0,0 +1,207 @@ +--- +phase: 8 +slug: backend-aggregation-filtering +status: draft +shadcn_initialized: false +preset: none +created: 2026-04-09 +--- + +# Phase 8 — UI Design Contract + +> Visual and interaction contract for Phase 8. This phase is backend-only: new PHP endpoints, service methods, and PHPUnit tests. No new visual components, no CSS changes, no rendering changes. + +--- + +## Design System + +| Property | Value | +|----------|-------| +| Tool | none | +| Preset | not applicable | +| Component library | none (Kimai Tabler theme) | +| Icon library | none (no new icons this phase) | +| Font | inherited from Kimai/Tabler | + +--- + +## Phase UI Surface + +**This phase has no visual changes.** It builds the data layer consumed by Phase 9 (Day + Combined Modes) and Phase 10 (Entity Pickers). + +Frontend touch is limited to extending the existing `fetch()` call with additional URL query parameters (mode, customer, activity). No new DOM elements, no new CSS classes, no layout changes. + +### What This Phase Does NOT Add +- No new visualization renderers +- No new filter UI elements (pickers come in Phase 10) +- No new CSS rules +- No Twig template changes +- No new user-visible states + +--- + +## Spacing Scale + +Not applicable -- no new visual elements in this phase. + +Existing spacing from `heatmap.css` remains unchanged: +- Wrapper gap: 16px +- Stats padding-top: 12px +- Filter select min-width: 140px, max-width: 200px + +--- + +## Typography + +Not applicable -- no new text elements in this phase. + +--- + +## Color + +Not applicable -- no new color usage in this phase. + +--- + +## Copywriting Contract + +No new user-facing copy in this phase. The only text change is extending the existing error console message pattern. + +| Element | Copy | +|---------|------| +| Fetch error (console) | `KimaiHeatmap: failed to load filtered data` (existing pattern, no change) | +| Empty state | Handled by renderer layer (Phase 9), not this phase | + +--- + +## API Response Contracts + +These are the data contracts downstream phases depend on. Source: 08-CONTEXT.md decisions D-01 through D-09. + +### Existing Endpoint Extension: `/heatmap/data` + +New query parameters (all optional, additive AND logic): + +| Param | Type | Default | Purpose | +|-------|------|---------|---------| +| `mode` | `daily` \| `hourly` \| `dayhour` | `daily` | Aggregation mode | +| `project` | integer | null | Filter by project (existing) | +| `customer` | integer | null | Filter by customer (all projects under customer) | +| `activity` | integer | null | Filter by activity | + +### Response: mode=daily (existing, unchanged) + +```json +{ + "days": [{"date": "2026-01-15", "hours": 4.5, "count": 3}], + "range": {"begin": "2025-04-09", "end": "2026-04-09"} +} +``` + +### Response: mode=hourly (new -- consumed by Phase 9 day-mode) + +```json +{ + "hours": [{"hour": 9, "hours": 12.5, "count": 8}], + "range": {"begin": "2025-04-09", "end": "2026-04-09"} +} +``` + +- `hour`: 0-23, hour-of-day in user's timezone +- `hours`: total tracked hours in that hour slot across the date range +- `count`: total timesheet entries in that hour slot + +### Response: mode=dayhour (new -- consumed by Phase 9 combined-mode) + +```json +{ + "matrix": [{"day": 0, "hour": 9, "hours": 3.2, "count": 2}], + "range": {"begin": "2025-04-09", "end": "2026-04-09"} +} +``` + +- `day`: 0-6, day-of-week relative to user's weekStart preference +- `hour`: 0-23, hour-of-day in user's timezone +- `hours`: total tracked hours for that day/hour combination +- `count`: total entries for that day/hour combination + +### Cascade Endpoints (new -- consumed by Phase 10 pickers) + +| Endpoint | Params | Response | +|----------|--------|----------| +| `/heatmap/customers` | none | `[{"id": 1, "name": "Acme Corp"}]` | +| `/heatmap/projects` | `customer={id}` (optional) | `[{"id": 1, "name": "Website"}]` | +| `/heatmap/activities` | `project={id}` (optional) | `[{"id": 1, "name": "Development"}]` | + +All cascade endpoints: +- Use session auth (`IS_AUTHENTICATED_REMEMBERED`), not API auth +- Scoped to entities the current user has timesheets for +- Return `[{id: number, name: string}]` matching existing `ProjectOption` type + +--- + +## Frontend Fetch Integration + +The existing fetch in `heatmap.ts` (lines 113-127, 145-157) will be extended to construct URLs with the new params. Pattern: + +``` +${baseUrl}?mode=${state.mode}&project=${id}&customer=${id}&activity=${id} +``` + +Only non-null filter values are appended. The `mode` param maps from `HeatmapMode` to API mode: +- `year` -> `daily` (or omit, it's the default) +- `week` -> `daily` (client-side aggregation, no backend change) +- `day` -> `hourly` +- `combined` -> `dayhour` + +--- + +## TypeScript Type Extensions + +New types to add to `assets/src/types.ts` for Phase 9 consumption: + +```typescript +export interface HourEntry { + hour: number; // 0-23 + hours: number; + count: number; +} + +export interface DayHourEntry { + day: number; // 0-6, relative to weekStart + hour: number; // 0-23 + hours: number; + count: number; +} + +export interface HourlyData { + hours: HourEntry[]; + range: { begin: string; end: string }; +} + +export interface DayHourData { + matrix: DayHourEntry[]; + range: { begin: string; end: string }; +} +``` + +--- + +## Registry Safety + +| Registry | Blocks Used | Safety Gate | +|----------|-------------|-------------| +| none | none | not applicable | + +--- + +## Checker Sign-Off + +- [ ] Dimension 1 Copywriting: N/A (no new user-facing copy) +- [ ] Dimension 2 Visuals: N/A (no visual changes) +- [ ] Dimension 3 Color: N/A (no color changes) +- [ ] Dimension 4 Typography: N/A (no text changes) +- [ ] Dimension 5 Spacing: N/A (no layout changes) +- [ ] Dimension 6 Registry Safety: PASS (no registries used) + +**Approval:** pending