Skip to content

feat(panel): customisable dashboard with registrable widgets (spec 0058)#2579

Merged
glennjacobs merged 2 commits into
2.xfrom
panel-dashboard-widgets
Jul 22, 2026
Merged

feat(panel): customisable dashboard with registrable widgets (spec 0058)#2579
glennjacobs merged 2 commits into
2.xfrom
panel-dashboard-widgets

Conversation

@glennjacobs

Copy link
Copy Markdown
Contributor

Implements the panel dashboard from spec 0058 — a per-staff customisable dashboard for the Inertia panel, with a widget extension point add-ons can plug into, nine first-party widgets over real data, and a brand-anchored chart palette.

What's included

Widget extension point

  • Widget abstract + WidgetRegistry, registered via Section::widgets() or Panel::widget(), ordered through the shared OrderResolver and permission-filtered.
  • Each visible widget's data(DashboardRange) ships as a deferred Inertia prop in its own group, so widgets load in parallel behind skeletons; a slow add-on widget never blocks the rest, and hidden widgets compute nothing.
  • The grid owns the card chrome (drag handle, hide, header) — widget components render body only, so add-on widgets get reorder/hide/permission behaviour for free.

Per-staff layout

  • New panel-owned staff_preferences table (generic per-staff key/value). Merge-on-read reconciles the stored layout with the live registry (uninstalled keys drop, new widgets append with defaults).
  • PUT/DELETE panel/dashboard/preferences; customise mode with drag-reorder, hide, an "Add a widget" dialog, and reset-to-defaults. Range + layout persist on every change.

Nine first-party widgets — KPIs (deltas + sparklines), revenue-over-time, recent orders, top products, sales by channel, new-vs-returning, customer-group split, low stock, tasks. Revenue is valued in the default currency via the exchange rate captured at placement (the spec-0050 basis); figures aggregate in SQL.

Charts & brand colours

  • New --color-chart-* theme tokens anchored on the Lunar icon blue, dataviz-validated in light and dark. TimeSeriesChart recoloured off sage.
  • New public Sparkline and DonutChart components (dependency-free SVG), exported via ui.ts / @lunarphp/panel.
  • Large summary totals abbreviate (£1.4M) in tight readouts via OrderMetrics::formatCompact(), with the exact value preserved in tooltips and donut legends.

Add-on surface — the example add-on gains a CustomerCountWidget; extension docs (TMP-DOCS.md, example README) updated. Translations across all 16 locales.

Design decisions

  • Per-staff, server-side layout (not global, not localStorage): permission filtering already personalises the widget set, roles want different landing pages, and it follows staff across devices. A store-curated default layout is noted as a follow-on.
  • Server-side money formatting kept throughout (including compaction), consistent with the panel convention. Compact k/M/B suffixes are en-centric via lang keys (ICU has no compact-currency mode); locale-perfect compaction is a documented follow-on.

Testing

  • Panel Pest suite: 722 passing — registry ordering/permissions, preference merge/validation/isolation, per-widget data correctness (currency conversion, deltas, buckets), deferred-prop assertions, compaction, and add-on widget registration (fixture + example add-on).
  • Frontend: 231 vitest (charts, widget card drag/hide, tooltip props), type-check, npm run build + add-on build.
  • PHPStan and Pint clean.
  • Verified end-to-end in the running panel: all widgets render with demo data, range switch + customise-mode changes persist correctly.

🤖 Generated with Claude Code

Add a per-staff customisable dashboard to the Inertia panel: a widget
extension point, nine first-party widgets over real order/product data, a
brand-anchored chart palette, and two new public chart components.

- Widget extension point: `Widget` abstract, `WidgetRegistry`,
  `DashboardRange`/`WidgetSpan`, `Section::widgets()` + `Panel::widget()`,
  ordered through the shared `OrderResolver` and permission-filtered. Each
  visible widget's `data()` ships as a deferred Inertia prop in its own group,
  so widgets load in parallel behind skeletons and hidden widgets cost nothing.
- Per-staff layout persisted server-side in a new `staff_preferences` table
  (merge-on-read reconciles stored layout with the live registry); PUT/DELETE
  preference endpoints; customise mode with drag reorder, hide, add dialog and
  reset.
- Nine widgets: KPIs (deltas + sparklines), revenue-over-time, recent orders,
  top products, sales by channel, new-vs-returning, customer-group split, low
  stock, tasks. Revenue valued in the default currency via the exchange rate
  captured at placement.
- Brand chart palette (`--color-chart-*`, anchored on the icon blue),
  dataviz-validated in both themes; `Sparkline` and `DonutChart` added to the
  public surface; `TimeSeriesChart` recoloured off sage.
- Large summary totals abbreviate (£1.4M) in tight readouts via
  `OrderMetrics::formatCompact()`, exact value preserved in tooltips and donut
  legends.
- Example add-on gains a dashboard widget; extension docs updated. Translations
  across all 16 locales.

Spec: specs/0058-panel-dashboard-widgets.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The cross-db job loads every suite's files in one process; two bare top-level
helper functions collided there — the dashboard OrderMetrics test's
`placedOrder()` with core's `DerivedOrderStatusTest`, and the generically named
`metrics()`. Rename to `placedDashboardOrder()` / `dashboardMetrics()`. Per-suite
runs never hit this because each suite loads only its own files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@glennjacobs
glennjacobs merged commit 9c2b292 into 2.x Jul 22, 2026
22 checks passed
@glennjacobs
glennjacobs deleted the panel-dashboard-widgets branch July 22, 2026 10:54
@github-project-automation github-project-automation Bot moved this from Todo to Done in Roadmap Jul 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant