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 parseMultiParam accepts CSV and repeated params.
  • Discovery endpoints: /statuses and /priorities are 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.ts provides a shared color map; timeline bars and status badges use the same colors.
  • Timeline charts: custom ECharts custom series is used to render project bars; each bar is colored by status; a dashed red markLine shows today’s date.
  • Timeline charts: custom ECharts custom series 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 optional end_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 without end_date are 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/:id and People links to /persons with person_id query.
  • Customers page (frontend/src/pages/Customers.vue): shows project counts, shared-project counts, unique people count per customer; names link to /customers/:id and People links to /persons with person_id query.
  • New columns: Proposed / In progress / Done per-customer counts are displayed (computed from project status where 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.vue groups roles and links to project(s); PersonDetail.vue lists assigned projects and renders a timeline.
  • Customer detail: CustomerDetail.vue lists the customer’s projects and renders a timeline.
  • API client: frontend/src/api/client.ts encodes arrays as CSV by default and supports discovery calls.
  • ECharts wrapper: frontend/src/components/charts/EChart.vue exists 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), and usePersonRoleStatistics(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 optional classifyStatus.
  • 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 to proposed, in_progress, or done. The returned statusCounts keys are camelCase (inProgress). Provide a custom classifyStatus(project) to override.
  • Current usage: Customers.vue, Persons.vue, and CustomerDetail.vue use these composables for KPIs and table columns; PersonDetail.vue keeps bespoke KPIs and leverages existing utilities.

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { ref } from 'vue'
import { useCustomerStatistics, usePersonRoleStatistics } from '@/composables/useEntityStatistics'

const projects = ref<Project[]>([])
const customers = ref<{ id: string; name: string }[]>([])

const stats = useCustomerStatistics(projects, customers)
// stats.counts.value[customerId], stats.sharedCounts.value[customerId],
// stats.peopleCounts.value[customerId], stats.statusCounts.value.inProgress[customerId]

const { personRoles } = usePersonRoleStatistics(projects)
// personRoles.value[personId] -> [{ role, projects: [{ projectId, projectName }] }]

ProjectDetail: recent implementation notes

  • src/pages/ProjectDetail.vue was 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_date exists 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.vue and 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.ts covers the month-series helper used for the active-projects chart.
  • Integration-smoke: tests/pages/ProjectDetail.test.ts includes a smoke test verifying key DOM fragments for ProjectDetail. Note: the file currently uses an inline TestWrapper to avoid SFC transform issues during local runs; convert to a real SFC-mounted test once vitest + @vitejs/plugin-vue are stable in your environment.
  • CI: .github/workflows/ci.yml runs npx tsc --noEmit, npm test, and npm run build on 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. The client.listProjects() helper in frontend/src/api/client.ts constructs 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_date when present.

Small implementation notes & gotchas

  • Top-customers links: the frontend computes topCustomers by name and count; customer detail links introduced a CustomerDetail page 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 statusColors for 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-refresh and 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: reply ci.
  • 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} -> Customer
    • GET /persons -> { "persons": Person[] }
    • GET /persons/{id} -> Person
    • GET /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 via Authorization: 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,planned and ?status=active&status=planned are both valid.
    • 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.
  • Sorting

    • Supported sort values: name (default), priority, progress.
    • sort=progress orders by numeric progress (ascending by current code); priority is lexicographic unless a semantic comparator is installed on the backend.
  • Data shapes & types

    • Dates: start_date and end_date are RFC3339 timestamps (ISO-8601). Frontend parses them with Date and uses getTime() for timelines.
  • 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 with slug/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_date and end_date are RFC3339 timestamps (ISO-8601). Frontend parses them with Date and uses getTime() for timelines.

    • Project.progress: integer percent; the adapter clamps values to [0,100]. UI can rely on this and render progress bars accordingly.

    • Project.customers is an array; projects may have multiple customers (shared projects). The counts/customer endpoint reports both total and shared per 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.yaml and small fixtures (projects/customers/persons) so the UI can run against a mock server that matches these shapes.

Testing & developer notes

  • A small, reusable helper for building monthly active-project series lives in src/utils/monthSeries.ts and is covered by unit tests using Vitest (see tests/utils/monthSeries.test.ts).

  • Vitest configuration (vitest.config.ts) is included and tests run in a jsdom environment to support component tests with @vue/test-utils.

  • To run tests locally:

    1
    2
    
    npm install
    npm test

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 id in the payload so links can be /customers/:id instead of linking by name.
    • Treat counts/customer as authoritative for totals if available; fall back to client aggregation from /projects when it is not.
    • Handle missing or empty start_date/end_date gracefully in timeline rendering.

These notes are distilled from SPEC.md and ADRs and should make frontend-only development and mocking much easier.