Demo

Dashboard

How to create a dashboard widget and register it with AppRegistry.dashboard.add. Inline DashboardItem in index.tsx — never wrap it inside the widget file.

Overview

Plugins contribute widgets to the shared ERP dashboard during register(AppRegistry). The shell owns layout, date range, and persistence. Your plugin supplies content and registers a DashboardItem shell with a stable id.

  • Register in frontend/src/index.tsx via AppRegistry.dashboard.add
  • Inline DashboardItem at registration — widget files export content only
  • Registration id and DashboardItem id must match (layout persistence)
  • Pass requiredApis on DashboardItem for RBAC

1. Create the widget (content only)

Put the widget under frontend/src/page/dashboard/ (or similar). Export a React component that renders content only — no DashboardItem wrapper. Use useDashboardContext from @quan-erp/base-frontend for the shared date range.

  • Do not import or render DashboardItem inside the widget file
  • Fill the cell with flex + h-full so the layout grid sizes correctly
  • Read startDate / endDate from useDashboardContext when the widget is date-scoped
TSXfrontend/src/page/dashboard/analytics-widget.tsx
1import { useDashboardContext } from "@quan-erp/base-frontend"; 2 3export function AnalyticsWidget() { 4 const { startDate, endDate } = useDashboardContext(); 5 6 return ( 7 <div className="flex flex-col w-full h-full p-4 gap-2"> 8 <h3>Analytics</h3> 9 <span>Start {startDate.toLocaleDateString()}</span> 10 <span>End {endDate.toLocaleDateString()}</span> 11 </div> 12 ); 13}

2. Register with AppRegistry.dashboard.add

In the plugin entry (frontend/src/index.tsx), call AppRegistry.dashboard.add inside PluginModule.register. Wrap the widget with DashboardItem from @quan-erp/shared-ui — inline at the call site, not inside the widget file.

  • id — unique across plugins; prefer `${metadata.name}-…`
  • pluginName — must equal module.metadata.json name
  • element — <DashboardItem>…</DashboardItem> wrapping your widget
  • DashboardItem id — must equal the registration id
  • colSpan / rowSpan — grid footprint (defaults depend on the shell)
  • requiredApis — API permission descriptors from withApiMetadataFetchFn (use .api)
TSXfrontend/src/index.tsx
1import type { AppRegistryState, PluginModule } from "@quan-erp/shared-types"; 2import { DashboardItem } from "@quan-erp/shared-ui"; 3import metadata from "../module.metadata.json" with { type: "json" }; 4import { getAnalyticsApi } from "./api/analytics.api"; 5import { AnalyticsWidget } from "./page/dashboard/analytics-widget"; 6 7const Plugin: PluginModule = { 8 register(AppRegistry: AppRegistryState) { 9 AppRegistry.dashboard.add({ 10 id: `${metadata.name}-analytics`, 11 pluginName: metadata.name, 12 element: ( 13 <DashboardItem 14 id={`${metadata.name}-analytics`} 15 colSpan={2} 16 rowSpan={1} 17 pluginName={metadata.name} 18 requiredApis={[getAnalyticsApi.api]} 19 > 20 <AnalyticsWidget /> 21 </DashboardItem> 22 ), 23 }); 24 }, 25}; 26 27export default Plugin;

3. Dashboard context

useDashboardContext() exposes the dashboard’s global filters (for example startDate and endDate). Subscribe in the widget so charts and queries stay in sync when the user changes the range.

TSXuseDashboardContext
import { useDashboardContext } from "@quan-erp/base-frontend"; const { startDate, endDate } = useDashboardContext(); // Pass into react-query keys / API params so the widget refetches on range change

Critical rules

  • Inline DashboardItem in index.tsx at AppRegistry.dashboard.add — never wrap inside the widget component
  • Registration id and DashboardItem id must be identical or layout persistence breaks
  • ids must be unique across all installed plugins
  • Pass requiredApis for RBAC; omit only when the widget needs no protected APIs
  • Widget files export content only

Checklist

  • Create widget component (content only) under page/dashboard/
  • Use useDashboardContext when the widget is date-scoped
  • In register(), AppRegistry.dashboard.add with matching ids
  • Inline DashboardItem with pluginName, colSpan, rowSpan, requiredApis
  • Verify the widget appears after quan-erp watch and install

Common failures

  • Widget missing on dashboard — not registered, wrong pluginName, or plugin not installed
  • Layout jumps / duplicate tiles — registration id ≠ DashboardItem id
  • Permission denied / empty widget — missing or wrong requiredApis
  • Date range ignored — not reading useDashboardContext or not keying queries on dates
  • Broken layout — DashboardItem wrapped inside the widget file instead of index.tsx