docs(08): UI design contract for backend aggregation phase

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Christopher Mühl 2026-04-09 20:58:52 +02:00
parent 9be43eb8f0
commit 96d70dd160
No known key found for this signature in database
GPG key ID: 925AC7D69955293F

View file

@ -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