docs(08): UI design contract for backend aggregation phase
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
9be43eb8f0
commit
96d70dd160
1 changed files with 207 additions and 0 deletions
207
.planning/phases/08-backend-aggregation-filtering/08-UI-SPEC.md
Normal file
207
.planning/phases/08-backend-aggregation-filtering/08-UI-SPEC.md
Normal 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
|
||||
Loading…
Add table
Reference in a new issue