feat(dashboards): SPA widget view surface for saved layouts (PMS-472) #331

Merged
YousifShkara merged 3 commits from feat/PMS-472-dashboard-view-surface into main 2026-06-25 08:04:33 +02:00
Owner

PMS-453 phase 1 stored a per-user layout JSONB but the SPA treated it
as opaque. PMS-472 ships the read-only view surface: a typed schema
(DashboardLayout { widgets: [{ widget_key, grid_col, grid_row, grid_col_span, grid_row_span, filter_scope? }] }), a forward-compatible
widget catalog (tickets_by_status, time_this_week, sla_at_risk,
open_invoices, recent_audit_log), and a new /dashboards/:id/view
route that renders each widget into a 12-column CSS grid cell at the
encoded coordinates.

The existing /dashboard entrypoint now hits GET /dashboards/default
first; if the caller has pinned a saved row, the saved layout renders
through the new pipeline. Without a pin, the v1 hardcoded dashboard
keeps rendering verbatim so nothing regresses for users who never
visited /dashboards.

Widget bodies are placeholder copy in this slice. PMS-487 layers the
editor (add / reposition / save) and the per-widget data fetches on
top of this catalog.

Acceptance criteria:

  • The SPA renders a saved dashboard's widgets in the grid positions
    the layout encodes (render_widget + grid_style).
  • The pinned-default dashboard loads on app boot via
    GET /api/v1/dashboards/default (DefaultDashboardPage).
  • Editor + add-widget round-trip deferred to PMS-487 per the original
    ticket split.

#PMS-472

PMS-453 phase 1 stored a per-user `layout` JSONB but the SPA treated it as opaque. PMS-472 ships the read-only view surface: a typed schema (`DashboardLayout { widgets: [{ widget_key, grid_col, grid_row, grid_col_span, grid_row_span, filter_scope? }] }`), a forward-compatible widget catalog (`tickets_by_status`, `time_this_week`, `sla_at_risk`, `open_invoices`, `recent_audit_log`), and a new `/dashboards/:id/view` route that renders each widget into a 12-column CSS grid cell at the encoded coordinates. The existing `/dashboard` entrypoint now hits `GET /dashboards/default` first; if the caller has pinned a saved row, the saved layout renders through the new pipeline. Without a pin, the v1 hardcoded dashboard keeps rendering verbatim so nothing regresses for users who never visited `/dashboards`. Widget bodies are placeholder copy in this slice. PMS-487 layers the editor (add / reposition / save) and the per-widget data fetches on top of this catalog. Acceptance criteria: - The SPA renders a saved dashboard's widgets in the grid positions the layout encodes (`render_widget` + `grid_style`). - The pinned-default dashboard loads on app boot via `GET /api/v1/dashboards/default` (`DefaultDashboardPage`). - Editor + add-widget round-trip deferred to PMS-487 per the original ticket split. #PMS-472
feat(dashboards): SPA widget view surface for saved layouts (PMS-472)
Some checks failed
Check / fmt + clippy + tests (pull_request) Has been cancelled
44bb5debad
PMS-453 phase 1 stored a per-user `layout` JSONB but the SPA treated it
as opaque. PMS-472 ships the read-only view surface: a typed schema
(`DashboardLayout { widgets: [{ widget_key, grid_col, grid_row,
grid_col_span, grid_row_span, filter_scope? }] }`), a forward-compatible
widget catalog (`tickets_by_status`, `time_this_week`, `sla_at_risk`,
`open_invoices`, `recent_audit_log`), and a new `/dashboards/:id/view`
route that renders each widget into a 12-column CSS grid cell at the
encoded coordinates.

The existing `/dashboard` entrypoint now hits `GET /dashboards/default`
first; if the caller has pinned a saved row, the saved layout renders
through the new pipeline. Without a pin, the v1 hardcoded dashboard
keeps rendering verbatim so nothing regresses for users who never
visited `/dashboards`.

Widget bodies are placeholder copy in this slice. PMS-487 layers the
editor (add / reposition / save) and the per-widget data fetches on
top of this catalog.

Acceptance criteria:
- The SPA renders a saved dashboard's widgets in the grid positions
  the layout encodes (`render_widget` + `grid_style`).
- The pinned-default dashboard loads on app boot via
  `GET /api/v1/dashboards/default` (`DefaultDashboardPage`).
- Editor + add-widget round-trip deferred to PMS-487 per the original
  ticket split.

#PMS-472
PMS-472 shipped the read-only view surface for a saved dashboard's
layout. PMS-487 layers the editor and replaces the placeholder widget
bodies with real data fetches against existing PSA endpoints.

Editor (toggled via an "Edit layout" button on SavedDashboardViewPage):
- Per-widget number inputs for grid_col / grid_row / grid_col_span /
  grid_row_span. Number inputs are the v1 shape - drag-and-drop is
  nicer UX but locks the project into a heavy dnd dependency the
  PMS-487 AC explicitly does not require.
- "Add widget" panel listing every catalog key as a button; click
  appends a row to the in-memory draft with sensible defaults
  (col=1, row=next-after-max, col_span=6, row_span=1).
- "Save layout" PATCHes `/dashboards/{id}` with `{layout: ...}` and
  refetches the row on success.
- "Cancel" reverts the draft to the persisted layout.

Per-widget bodies:
- tickets_by_status: open-by-priority from `/reports/dashboard`.
- time_this_week: minutes since Monday from `/time-entries`.
- sla_at_risk: at-risk + breached from `/reports/dashboard`.
- open_invoices: count + total from `/invoices?status=sent&per_page=50`.
- recent_audit_log: last 5 rows of `/audit-log`.

Acceptance criteria:
- "Edit layout" toggle on SavedDashboardViewPage swaps into editor.
- User can add widgets from the catalog, change grid coords, save
  through `PATCH /dashboards/{id}` (UpdateLayoutBody + on_save).
- Each catalog widget renders real data via its own per-widget fetch.
- The pinned-default dashboard loads via `GET /dashboards/default`
  (DefaultDashboardPage, unchanged from PMS-472).

Builds on PMS-472 - rebase / merge that branch first.

#PMS-487
Merge pull request 'feat(dashboards): editor + per-widget data fetches (PMS-487)' (#332) from feat/PMS-487-dashboard-editor into feat/PMS-472-dashboard-view-surface
All checks were successful
Check / fmt + clippy + tests (pull_request) Successful in 1m13s
Create release / Create release from merged PR (pull_request) Has been skipped
a8da01fbfd
Reviewed-on: #332
YousifShkara deleted branch feat/PMS-472-dashboard-view-surface 2026-06-25 08:04:33 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
psa-systems/mokosh-apps!331
No description provided.