docs(002-reporting-dashboards-ui): plan, data model, quickstart, tasks

Four new dataviz-informed UI primitives (stat tile, ranked bar list,
status distribution, meter) shared across all four dashboards, no new
charting dependency. 24 tasks across a shared Foundational phase and
4 independently-testable dashboard user stories.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
saqib mir
2026-09-09 12:12:48 +05:30
co-authored by Claude Sonnet 5
parent e7e6e853d5
commit a37539794f
4 changed files with 409 additions and 0 deletions
@@ -0,0 +1,104 @@
# Data Model: Reporting and Analytics Dashboards UI
Response shapes mirror `supporthub-api`'s `specs/015-reporting-dashboards/data-model.md`
exactly — reproduced here as the frontend's own typed contract (Constitution Principle IV),
plus the one derived view-model type each dashboard actually renders.
## API Response Types (`lib/api/types/reports.ts`)
```ts
export interface DateRangeDTO { from: string; to: string }
export interface ManagementDashboardDTO {
range: DateRangeDTO;
totalCases: number;
aiResolved: number;
humanEscalated: number;
resolved: number;
open: number;
slaCompliance: { met: number; breached: number; rate: number | null };
escalationCount: number;
averageResponseSeconds: number | null;
averageResolutionSeconds: number | null;
}
export interface ProductDashboardDTO {
productId: string;
range: DateRangeDTO;
supportVolume: number;
problemsByCategory: Array<{ categoryId: string | null; count: number }>;
recurringProblems: Array<{ categoryId: string | null; count: number }>;
aiResolutionRate: number | null;
humanEscalationRate: number | null;
topErrors: Array<{ code: string; count: number }>;
}
export interface SupportDashboardDTO {
generatedAt: string;
range: DateRangeDTO;
workloadByAgent: Array<{ agentId: string; openAssignments: number }>;
slaAtRisk: number;
slaBreached: number;
escalationCount: number;
averageResponseSeconds: number | null;
averageResolutionSeconds: number | null;
}
export interface AiDashboardDTO {
range: DateRangeDTO;
totalSessions: number;
aiResolutionRate: number | null;
humanHandoffRate: number | null;
failedTroubleshootingEscalationRate: number | null;
knowledgeMatchRate: number | null;
confidenceDistribution: { proceed: number; ask: number; escalate: number };
toolInvocations: { success: number; failed: number };
}
```
## Display Helpers (`lib/format/duration.ts`, inline in each dashboard)
- **`formatDurationSeconds(value: number | null): string`** — `null` -> `"No data"`; otherwise a
compact human duration (`"2h 15m"`, `"45s"`) — never raw seconds.
- **`formatRate(value: number | null): string`** — `null` -> `"No data"`; otherwise a percentage,
one decimal place (`"62.5%"`).
- **`formatCount(value: number): string`** — thousands-comma'd (dataviz `marks-and-anatomy.md`'s
own labeling convention), no special-casing needed since a count is never `null`.
## Ranked List View-Model
Every ranked list (`problemsByCategory`, `recurringProblems`, `topErrors`, `workloadByAgent`)
renders through one shared `RankedBarList` primitive taking:
```ts
interface RankedBarListItem {
label: string; // categoryId, error code, or agentId — resolved to a display label upstream
// where a friendlier name exists, falling back to the raw id otherwise
value: number;
}
```
Bar width is `value / max(values)` — proportional within the list shown, not against some
absolute scale, per `dataviz` choosing-a-form.md's "compare magnitude" guidance for a bounded
top-N list.
## Status Distribution View-Model
Confidence distribution and tool invocations both render through one shared
`StatusDistribution` primitive:
```ts
interface StatusDistributionSegment {
label: string; // "Proceed" | "Ask" | "Escalate" | "Success" | "Failed"
value: number;
tone: 'success' | 'warning' | 'destructive';
}
```
Mapping (fixed, never inferred from label text):
| Source | proceed/success | ask | escalate/failed |
|---|---|---|---|
| `confidenceDistribution` | `proceed` -> success | `ask` -> warning | `escalate` -> destructive |
| `toolInvocations` | `success` -> success | — (two-segment) | `failed` -> destructive |
| `slaCompliance` | `met` -> success | — (two-segment) | `breached` -> destructive |
+124
View File
@@ -0,0 +1,124 @@
# Implementation Plan: Reporting and Analytics Dashboards UI
**Branch**: `002-reporting-dashboards-ui` | **Date**: 2026-09-09 | **Spec**: [spec.md](./spec.md)
**Input**: Feature specification from `specs/002-reporting-dashboards-ui/spec.md`
## Summary
Presents `supporthub-api`'s already-complete 015-reporting-dashboards contract (four `GET
/admin/reports/*` endpoints) as a tabbed admin surface, replacing nothing existing —
`admin/reports/page.tsx` already hosts the SLA Monitor and Escalation Matrix (012-admin-list-
views work); this feature adds Management/Product/Support/AI as four more tabs on that same
page. New reusable dashboard primitives (stat tile, ranked bar list, status distribution bar,
meter) are built once in `components/ui` and reused across all four dashboards, following the
`dataviz` skill's methodology: pick the form before color, status colors (already defined in
this project's own design tokens) for status-flavored splits, a single sequential hue for
magnitude rankings, validated in both light and dark mode.
## Technical Context
**Language/Version**: TypeScript, Next.js 14 App Router (unchanged).
**Primary Dependencies**: None new — TanStack Query (already the standard), the existing
`components/ui` kit, this project's own Tailwind design tokens. No charting library added
(spec.md Assumptions) — every visual is plain HTML/CSS per the `dataviz` skill's Tier 0/1
component guidance (a proportional div-based bar needs no SVG/canvas dependency).
**Storage**: N/A — no client-side persistence; date-range selection is component state
(spec.md Assumptions).
**Testing**: Vitest for the new pure helpers (duration/rate formatting, the "no data" guard);
component tests for each dashboard's loading/empty/error/ready rendering (mocked query client,
matching 001-agent-admin-ui's own established testing pattern); Playwright E2E against the real
running `supporthub-api` + `supporthub-web` pair for the full tab-switching, date-range, and
product-selection flow — this session's standing rule of never claiming a frontend scenario done
without exercising it against real, live infrastructure.
**Target Platform**: Web, `(admin)` portal only (Constitution Principle III).
**Project Type**: Frontend — single Next.js app, no new module boundary crossed.
**Performance Goals**: Each dashboard fetches independently (its own `useQuery`) so switching
tabs doesn't block on data the current tab doesn't need; date-range changes debounce-free (a
single explicit "Apply" action, not a fetch per keystroke) to avoid hammering the backend while
typing a date.
**Constraints**: FR-008 — no new/changed backend endpoint; this is presentation-only. FR-004 —
every `null` rate/average renders as an explicit "No data" treatment, computed once in the
mapping layer (`lib/api/reports.ts`) so every consuming component gets an already-safe shape,
never a raw `null` a component might accidentally interpolate into text.
**Scale/Scope**: One new `lib/api/reports.ts` (+types), four new `features/reports/*-dashboard.tsx`
components, four new `components/ui` primitives (stat tile, ranked bar list, status distribution
bar, meter), one modified `admin/reports/page.tsx` (adds tabs), one shared date-range control.
## Constitution Check
*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*
| Principle / Section | Check | Result |
|---|---|---|
| I. Each Identity Has Exactly One Authority, and the Frontend Is Never It | Not applicable — no identity/access surface touched. | PASS — N/A |
| II. The Backend Is the Sole Source of Business Logic | Every figure (rates, distributions, rankings) is rendered exactly as the backend computed it — this feature does no business-rule computation, only display formatting (e.g. seconds -> "2h 15m"). | PASS |
| III. Strict Portal Boundaries | All new code lives under `(admin)`/`features/reports` — no customer- or support-portal import, no shared primitive leaks admin-only data (the new `components/ui` primitives are generic value/label/color renderers, not admin-shaped). | PASS |
| IV. Typed API Boundary, No Ad Hoc Fetching | All four dashboards fetched through new `lib/api/reports.ts` functions wrapped in TanStack Query hooks — no direct `fetch`/`axios` in any component. | PASS |
| V. Configuration Over Hardcoding | Not applicable — no admin-configurable enum rendered by this feature (the confidence bands/status labels are fixed, backend-defined vocabulary, not business policy). | PASS — N/A |
| VI. Accessible, Responsive, Enterprise-Grade UI | New primitives keep density over decoration per this principle's own wording — status colors always paired with a text label (never color-alone, satisfying both this principle's WCAG requirement and the `dataviz` skill's own non-negotiable), keyboard-reachable tab/date controls, responsive down to a single column. | PASS |
| VII. Testing Gates | Typecheck/lint clean; new Vitest coverage for the mapping/formatting helpers and each dashboard's query-state rendering; Playwright coverage added for the reports tab-switching flow (not one of the constitution's two named cross-cutting journeys, but this project's own established practice of E2E-verifying every admin feature against a real backend, per 001's own precedent). | PASS |
No violations requiring Complexity Tracking justification.
## Project Structure
### Documentation (this feature)
```text
specs/002-reporting-dashboards-ui/
├── plan.md
├── data-model.md
├── quickstart.md
└── tasks.md
```
### Source Code (repository root)
```text
supporthub-web/
├── src/
│ ├── components/ui/
│ │ ├── stat-tile.tsx # NEW
│ │ ├── ranked-bar-list.tsx # NEW
│ │ ├── status-distribution.tsx # NEW
│ │ ├── meter.tsx # NEW
│ │ └── index.ts # MODIFIED — export the four above
│ ├── lib/
│ │ ├── api/
│ │ │ ├── reports.ts # NEW — 4 fetch functions
│ │ │ └── types/
│ │ │ └── reports.ts # NEW — response + view-model types
│ │ └── format/
│ │ └── duration.ts # NEW — seconds -> "2h 15m" / "No data"
│ ├── features/reports/
│ │ ├── management-dashboard.tsx # NEW
│ │ ├── product-dashboard.tsx # NEW
│ │ ├── support-dashboard.tsx # NEW
│ │ ├── ai-dashboard.tsx # NEW
│ │ ├── date-range-control.tsx # NEW — shared by Management/Product/AI
│ │ ├── sla-monitor.tsx # UNCHANGED (012's own)
│ │ └── escalation-matrix.tsx # UNCHANGED (012's own)
│ └── app/(admin)/admin/reports/
│ └── page.tsx # MODIFIED — adds 4 tabs alongside the 2 existing sections
└── tests/
├── unit/lib/format/ # duration formatting, no-data guard
├── unit/features/reports/ # per-dashboard query-state rendering
└── e2e/ # reports tab-switching + date-range Playwright spec
```
**Structure Decision**: No new route, no new portal — extends the existing `(admin)/admin/reports`
page and `features/reports` module already established by 012-admin-list-views' SLA Monitor/
Escalation Matrix work.
## Complexity Tracking
*No constitution violations — table intentionally omitted.*
@@ -0,0 +1,53 @@
# Quickstart: Reporting and Analytics Dashboards UI
Manual verification against a real, running `supporthub-api` (with real ticket/SLA/AI data —
reuse the throwaway Postgres/Redis and admin login already established this session) and a
real, running `supporthub-web` dev server, logged in as an admin.
## Scenario 1 — Management dashboard (User Story 1)
1. Open Admin -> Reports -> Management tab.
2. **Expected**: stat tiles for total cases, AI resolved, human escalated, resolved, open;
an SLA compliance meter; escalation count; average response/resolution time — all real
numbers, not placeholders.
3. Change the date range to one with no activity.
4. **Expected**: counts show `0`, rates/averages show "No data," never blank or `NaN`.
5. Change the date range back.
6. **Expected**: the dashboard re-fetches and every figure updates.
## Scenario 2 — Product dashboard (User Story 2)
1. Select Product A in the product picker.
2. **Expected**: support volume, problem-category ranked bars, AI/human resolution rates, and
top-error ranked bars all reflect Product A only.
3. Switch to Product B.
4. **Expected**: every figure fully replaces — no leftover Product A data visible mid-transition
beyond the loading state.
## Scenario 3 — Support dashboard (User Story 3)
1. Open the Support tab.
2. **Expected**: a ranked bar list of per-agent workload; SLA at-risk and SLA breached shown as
two visually distinct figures; response/resolution performance stat tiles.
## Scenario 4 — AI dashboard (User Story 4)
1. Open the AI tab.
2. **Expected**: AI resolution rate, human-handoff rate, knowledge-match rate stat tiles; a
confidence distribution bar (proceed/ask/escalate, success/warning/destructive-toned, each
labeled); a tool success/failure distribution bar, same treatment.
## Scenario 5 — Responsive and dark mode
1. Resize the viewport down to a narrow mobile width on each tab.
2. **Expected**: stat tiles stack to one column, ranked bars remain fully readable, nothing
clips or requires horizontal scroll.
3. Toggle dark mode.
4. **Expected**: every figure, bar, and status color remains legible and uses this project's
existing dark-mode tokens.
## What "done" looks like
All five scenarios pass against a real running backend and frontend pair, verified visually
(not just by reading component code) and via the Playwright spec covering tab-switching,
date-range changes, and product selection.
+128
View File
@@ -0,0 +1,128 @@
---
description: "Task list for 002-reporting-dashboards-ui"
---
# Tasks: Reporting and Analytics Dashboards UI
**Input**: Design documents from `specs/002-reporting-dashboards-ui/`
**Organization**: Tasks are grouped by user story (US1 = P1 Management, US2 = P1 Product,
US3 = P2 Support, US4 = P2 AI), sharing one Foundational phase (types, format helpers, the four
new `components/ui` primitives, the shared date-range control).
## Format: `[ID] [P?] [Story] Description`
All file paths are relative to `supporthub-web/` (repo root).
---
## Phase 1: Foundational (Blocking Prerequisites)
- [ ] T001 [P] Add `lib/api/types/reports.ts` per data-model.md's four DTO interfaces
- [ ] T002 [P] Add `lib/format/duration.ts``formatDurationSeconds`, `formatRate`,
`formatCount` (depends on nothing — pure functions)
- [ ] T003 [P] Add `components/ui/stat-tile.tsx` — label/value/optional-delta contract per
`dataviz` marks-and-anatomy.md's "figures" section; sans semibold value, sentence-case
label
- [ ] T004 [P] Add `components/ui/ranked-bar-list.tsx` — takes `RankedBarListItem[]`, renders
each as a label + proportional bar (single sequential hue = `primary`) + value, sorted as
given (backend already sorts), truncating long labels with a `title` tooltip
- [ ] T005 [P] Add `components/ui/status-distribution.tsx` — takes
`StatusDistributionSegment[]`, renders a segmented bar (2px surface gaps between segments
per `dataviz` marks-and-anatomy.md) plus a visible legend row (label + swatch + value for
each segment — never color alone)
- [ ] T006 [P] Add `components/ui/meter.tsx` — a single-ratio track/fill pair (SLA compliance
rate), fill tone success/warning/destructive by value, unfilled track a lighter step of
the same tone
- [ ] T007 Export T003-T006 from `components/ui/index.ts`
- [ ] T008 Add `features/reports/date-range-control.tsx` — two date inputs + an explicit
"Apply" action (plan.md: no fetch-per-keystroke), shared by Management/Product/AI
- [ ] T009 Add `lib/api/reports.ts``getManagementDashboard(range)`,
`getProductDashboard(externalProductId, range)`, `getSupportDashboard()`,
`getAiDashboard(range)`, each a thin typed wrapper over `apiClient` per Constitution
Principle IV (depends on T001)
**Checkpoint**: Primitives and typed client in place. Each dashboard can now be built
independently.
---
## Phase 2: User Story 1 - Management dashboard (Priority: P1)
- [ ] T010 [US1] Add `features/reports/management-dashboard.tsx``useQuery` +
`getQueryState`, stat tiles (T003) for totals, `Meter` (T006) for SLA compliance,
`formatDurationSeconds` for the two averages, `DateRangeControl` (T008) wired to
`queryKey` (depends on T003, T006, T008, T009)
- [ ] T011 [US1] Unit tests: `formatDurationSeconds`/`formatRate` null -> "No data" guard, a
real-value case each, in `tests/unit/lib/format/duration.test.ts` (depends on T002)
- [ ] T012 [US1] Component test: Management dashboard renders loading/empty/error/ready
correctly against a mocked query client, in
`tests/unit/features/reports/management-dashboard.test.tsx` (depends on T010)
**Checkpoint**: Quickstart Scenario 1 passes against a real backend.
---
## Phase 3: User Story 2 - Product dashboard (Priority: P1)
- [ ] T013 [US2] Add a product picker reusing the existing `GET /admin/products` list (spec.md
Assumptions) — check for an existing products-list hook/component from
012-admin-list-views' own admin/products page before adding a new one
- [ ] T014 [US2] Add `features/reports/product-dashboard.tsx``RankedBarList` (T004) for
problem categories and top errors, stat tiles for support volume/rates, wired to T013's
picker + `DateRangeControl` (depends on T004, T008, T009, T013)
- [ ] T015 [US2] Component test: switching products fully replaces the rendered figures, an
unknown-product error state renders correctly, in
`tests/unit/features/reports/product-dashboard.test.tsx` (depends on T014)
**Checkpoint**: Quickstart Scenario 2 passes against a real backend.
---
## Phase 4: User Story 3 - Support dashboard (Priority: P2)
- [ ] T016 [US3] Add `features/reports/support-dashboard.tsx``RankedBarList` (T004) for
per-agent workload, stat tiles for at-risk/breached/escalation count and performance
averages — no date-range control (current-state, per data-model.md) (depends on T003,
T004, T009)
- [ ] T017 [US3] Component test: at-risk and breached render as visually distinct figures, in
`tests/unit/features/reports/support-dashboard.test.tsx` (depends on T016)
**Checkpoint**: Quickstart Scenario 3 passes against a real backend.
---
## Phase 5: User Story 4 - AI dashboard (Priority: P2)
- [ ] T018 [US4] Add `features/reports/ai-dashboard.tsx` — stat tiles for the four rates,
`StatusDistribution` (T005) for confidence bands and tool invocations, `DateRangeControl`
(depends on T003, T005, T008, T009)
- [ ] T019 [US4] Component test: confidence/tool distributions map to the correct tone per
data-model.md's fixed mapping table, in
`tests/unit/features/reports/ai-dashboard.test.tsx` (depends on T018)
**Checkpoint**: Quickstart Scenario 4 passes against a real backend.
---
## Phase 6: Polish & Cross-Cutting Concerns
- [ ] T020 Wire all four dashboards into `app/(admin)/admin/reports/page.tsx` as tabs
(`components/ui/tabs`) alongside the existing SLA Monitor/Escalation Matrix sections
(depends on T010, T014, T016, T018)
- [ ] T021 Playwright E2E: tab-switching, date-range change, and product-selection flow against
a real running backend + frontend pair, in `tests/e2e/reports-dashboards.spec.ts`
(depends on T020)
- [ ] T022 Manually verify Quickstart Scenario 5 (responsive + dark mode) directly in a browser
- [ ] T023 Update `specs/002-reporting-dashboards-ui/checklists/requirements.md` Notes with any
implementation-time findings
- [ ] T024 `npm run typecheck`/`npm run lint` clean; full existing Vitest + Playwright suite
re-run to confirm no regression in 001-agent-admin-ui's own coverage
---
## Dependencies & Execution Order
- **Foundational (Phase 1)**: No dependencies — BLOCKS all four user stories
- **User Stories 2-5**: Each depends only on Foundational — independent of each other
- **Polish (Phase 6)**: Depends on all four user stories