dashboard-spec
Dashboard specification (implemented)
This document records the dashboard spec and notes what the current frontend implementation provides vs. what remains optional or unimplemented.
Stack and high-level
- Frontend: Vue 3 + Vite + TypeScript (Composition API), Bulma CSS, Apache ECharts.
- Backend adapter: Go in-memory adapter (existing). The app is intentionally read-only and in-memory; most aggregations are performed client-side.
High-level status
- Core pages implemented: Overview (
/), Projects (/projects), Project detail (/projects/:id), Customers (/customers), Customer detail (/customers/:id), Persons (/persons), Person detail (/persons/:id), Admin (/admin). - Timeline (Gantt-like) charts implemented in Overview, Person detail and Customer detail; charts include a vertical “Today” marker.
UI note: navbar-driven page indicator
- The app now uses the top navbar to indicate the current page (active navbar item is highlighted). Top-level H1 page headers have been removed from pages to reduce visual duplication; designers and developers should rely on the navbar for the primary page context. Minor page-level subtitles may remain for local context.
- Filters: multi-value filters are encoded as CSV by default; server
parseMultiParamaccepts CSV and repeated params. - Discovery endpoints:
/statusesand/prioritiesare used to populate selects. - Admin refresh endpoint exists (POST
/admin/refresh) and is wired to the Admin page.
What is implemented (concrete)
- Projects table (
frontend/src/components/ProjectsTable.vue): id, name (link), customers (linked), status badge, priority badge, progress bar + % and start/end dates. Row navigates to project detail. - Status color palette:
frontend/src/utils/statusColors.tsprovides a shared color map; timeline bars and status badges use the same colors. - Timeline charts: custom ECharts
customseries is used to render project bars; each bar is colored by status; a dashed red markLine shows today’s date. - Timeline charts: custom ECharts
customseries is used to render project bars; each bar is colored by status; a dashed red markLine shows today’s date.
New: Active projects (last 12 months)
- A new line chart “Active projects (last 12 months)” is shown on Overview and on Customer detail pages.
- Input: projects with
start_date(required) and optionalend_date(ISO-8601 strings). A project is considered active in a month if its time range overlaps the month (i.e. start < month_end && end >= month_start). Projects withoutend_dateare treated as running until now. - Aggregation: monthly counts for the previous 12 months. Labels use browser locale (e.g. “Sep 2025”).
- Visualization: smooth line chart with tooltips showing month + count. When no date data is available the chart shows a friendly empty state message.
- Implementation detail: the frontend now contains a small helper
src/utils/monthSeries.ts(unit-tested) that computes month labels and active counts. - Customers page (
frontend/src/pages/Customers.vue): shows project counts, shared-project counts, unique people count per customer; names link to/customers/:idand People links to/personswithperson_idquery. - Customers page (
frontend/src/pages/Customers.vue): shows project counts, shared-project counts, unique people count per customer; names link to/customers/:idand People links to/personswithperson_idquery. - New columns: Proposed / In progress / Done per-customer counts are displayed (computed from project
statuswhere available). Rows are clickable and keyboard-focusable and navigate to the customer detail; there is also a small “View projects” button that navigates to the projects list filtered by customer. - Persons page and Person detail:
Persons.vuegroups roles and links to project(s);PersonDetail.vuelists assigned projects and renders a timeline. - Customer detail:
CustomerDetail.vuelists the customer’s projects and renders a timeline. - API client:
frontend/src/api/client.tsencodes arrays as CSV by default and supports discovery calls. - ECharts wrapper:
frontend/src/components/charts/EChart.vueexists and provides reactive option handling and safe click payloads.
New: Entity statistics composables
- Purpose: centralize client-side aggregation of project→entity relationships (counts, shared counts, unique people per entity, and status buckets) for Customers and Persons pages and detail views.
- Exports:
useEntityStatistics(projects, entities, config),useCustomerStatistics(projects, customers),usePersonStatistics(projects, persons), andusePersonRoleStatistics(projects). - Inputs:
projects: Ref<Project[]>— full or filtered project list.entities: Ref<T[]>— list of entities (customers/persons) when using the generic API.config: getters for entity id, how to read related entities from a project, optional team getter, and optionalclassifyStatus.
- Outputs:
counts— total projects per entity id.sharedCounts— projects per entity that involve multiple related entities (e.g., shared customers).peopleCounts/peopleMap— unique team member count and ids per entity.statusCounts— per-entity buckets:{ proposed, inProgress, done }.
- Status classification: by default, strings or
{slug|name}are normalized to snake_case; keywords map toproposed,in_progress, ordone. The returnedstatusCountskeys are camelCase (inProgress). Provide a customclassifyStatus(project)to override. - Current usage:
Customers.vue,Persons.vue, andCustomerDetail.vueuse these composables for KPIs and table columns;PersonDetail.vuekeeps bespoke KPIs and leverages existing utilities.
Example
ProjectDetail: recent implementation notes
src/pages/ProjectDetail.vuewas updated to improve readability and accessibility:- Project title is promoted and the status badge is displayed inline in the header area.
- Presence-computed flags were added (hasDates, hasCustomers, hasTeam, hasPriority, hasProgress, hasExternalLinks) so UI blocks render only when data exists.
- Accessible progress bar (role=“progressbar” with aria-valuenow and an associated label id) was added.
- Conditional mini-timeline: when
start_dateexists a compact timeline (EChart) is shown with a date summary and a visual CTA; otherwise a friendly empty state explains how to enable the timeline. - Team list now renders avatars via
src/components/Avatar.vueand hides the block when no members exist. - External links were consolidated into compact anchor tags (tag-like UI) with accessible aria-labels.
Testing & CI (recent additions)
- Unit tests:
tests/utils/monthSeries.test.tscovers the month-series helper used for the active-projects chart. - Integration-smoke:
tests/pages/ProjectDetail.test.tsincludes a smoke test verifying key DOM fragments forProjectDetail. Note: the file currently uses an inline TestWrapper to avoid SFC transform issues during local runs; convert to a real SFC-mounted test oncevitest+@vitejs/plugin-vueare stable in your environment. - CI:
.github/workflows/ci.ymlrunsnpx tsc --noEmit,npm test, andnpm run buildon pushes/PRs.
What’s intentionally client-side today
- Count aggregations (projects by status/priority/top-customers) are computed client-side from the project list. This is appropriate for small–medium datasets and keeps the backend simple.
Remaining / optional work
- Server-side aggregation endpoints (counts by status/priority/customer) — not implemented. If you expect large datasets, adding dedicated backend endpoints is recommended.
- Auto-refresh (periodic refresh toggle): Admin page currently exposes manual refresh; the periodic auto-refresh (60s) is not enabled by default and not wired yet.
- Frontend integration tests / CI: backend unit tests exist; adding a CI job to run
go test ./...and the frontend build is recommended.
Routes and notable files
frontend/src/router.ts— routes registered for Overview, Projects, ProjectDetail, Customers, CustomerDetail, Persons, PersonDetail, Admin.frontend/src/pages/*— page components (Overview.vue, Projects.vue, ProjectDetail.vue, Customers.vue, CustomerDetail.vue, Persons.vue, PersonDetail.vue, Admin.vue).frontend/src/components/ProjectsTable.vue— main projects table.frontend/src/components/charts/EChart.vue— ECharts wrapper.frontend/src/utils/statusColors.ts— status→color map.
UX / query semantics
- Multi-value filters: encoded as CSV by default (e.g.
?status=active,planned). The backend accepts repeated params too. Theclient.listProjects()helper infrontend/src/api/client.tsconstructs these. - URL sync: Projects list filter state is encoded into the URL for sharing/bookmarking.
- Dates: localized with browser Intl APIs; timeline uses project
start_date/end_datewhen present.
Small implementation notes & gotchas
- Top-customers links: the frontend computes
topCustomersby name and count; customer detail links introduced aCustomerDetailpage which uses customer id — the top-customers data can be adjusted to include ids for robust linking. - Project badges retain Bulma classes for accessibility while using inline background color from
statusColorsfor visual consistency. - The Today mark uses client Date.now() when the chart is rendered; chart timezones follow the browser locale/timezone.
Suggested next steps (pick one)
- Update the backend with small aggregation endpoints for counts (if you expect large datasets): I can draft the server handlers and wire them into the adapter. Reply
counts-endpoint. - Add periodic auto-refresh toggle to Admin page: reply
auto-refreshand I’ll implement a 60s toggle. - Update this spec further to include example API responses or an OpenAPI → UI mapping file: reply
openapi-ui-mapping. - Add CI job to run
go test ./...and the frontend build: replyci. - Nothing more right now: reply
none.
Contract & runtime notes (frontend-focused)
-
API surface (important endpoints and shapes)
GET /projects->{ "projects": Project[] }GET /projects/{id}->{ "project": Project, "customers": Customer[], "team": Person[] }GET /customers->{ "customers": Customer[] }GET /customers/{id}->CustomerGET /persons->{ "persons": Person[] }GET /persons/{id}->PersonGET /statuses->{ "statuses": string[] }GET /priorities->{ "priorities": string[] }GET /counts/status->{ "counts": { "<status>": number, ... } }GET /counts/priority->{ "counts": { "<priority>": number, ... } }GET /counts/customer->{ "customers": [ { id, name, total, shared }, ... ] }(ordered by total desc)POST /admin/refresh->{ "status": "ok" }(optional token viaAuthorization: Bearer <ADMIN_TOKEN>)
-
Query param semantics (critical for URL-sync and filter UI)
- Multi-value params accept either comma-separated lists or repeated params:
- Examples:
?status=active,plannedand?status=active&status=plannedare both valid.
- Examples:
- Semantics:
- Multiple values inside the same parameter = OR (project matches if it has any of the listed values for that field).
- Different parameters combine with AND (project must satisfy all provided parameters).
- The frontend currently encodes arrays as CSV; keep that encoding unless you change
client.ts.
- Multi-value params accept either comma-separated lists or repeated params:
-
Sorting
- Supported
sortvalues:name(default),priority,progress. sort=progressorders by numericprogress(ascending by current code);priorityis lexicographic unless a semantic comparator is installed on the backend.
- Supported
-
Data shapes & types
- Dates:
start_dateandend_dateare RFC3339 timestamps (ISO-8601). Frontend parses them withDateand usesgetTime()for timelines.
- Dates:
-
Project required / expected fields (frontend usage)
-
id(string) -
name(string) -
start_date(ISO-8601 string) — used for timelines and active counts; absence means project omitted from timeline/active-series -
end_date(ISO-8601 string, optional) — if missing, treated as ongoing to the present -
status(string or object withslug/name) — used for status coloring and status buckets -
priority(string) -
progress(number 0-100) -
customers(array of {id,name}) -
team(array of Person objects or person ids) -
Dates:
start_dateandend_dateare RFC3339 timestamps (ISO-8601). Frontend parses them withDateand usesgetTime()for timelines. -
Project.progress: integer percent; the adapter clamps values to[0,100]. UI can rely on this and render progress bars accordingly. -
Project.customersis an array; projects may have multiple customers (shared projects). Thecounts/customerendpoint reports bothtotalandsharedper customer.
-
-
Backend test/dev contract notes (useful for mocks)
- The repo exposes a generic in-memory adapter; tests and mocks should call
Adapter.Refresh(ctx)to seed data before calling handlers. - For frontend development, include
openapi.yamland small fixtures (projects/customers/persons) so the UI can run against a mock server that matches these shapes.
- The repo exposes a generic in-memory adapter; tests and mocks should call
Testing & developer notes
-
A small, reusable helper for building monthly active-project series lives in
src/utils/monthSeries.tsand is covered by unit tests using Vitest (seetests/utils/monthSeries.test.ts). -
Vitest configuration (
vitest.config.ts) is included and tests run in ajsdomenvironment to support component tests with@vue/test-utils. -
To run tests locally:
Accessibility & UX fallbacks
-
Clickable rows used across lists are keyboard-focusable and have a visible focus outline.
-
Timeline and active-series charts show a friendly empty state message when there is insufficient date data; this prevents rendering empty charts and improves UX for sparse datasets.
-
UX/robustness recommendations for the frontend
- When showing top customers, include customer
idin the payload so links can be/customers/:idinstead of linking by name. - Treat
counts/customeras authoritative for totals if available; fall back to client aggregation from/projectswhen it is not. - Handle missing or empty
start_date/end_dategracefully in timeline rendering.
- When showing top customers, include customer
These notes are distilled from SPEC.md and ADRs and should make frontend-only development and mocking much easier.