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
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)
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.
import { useDashboardContext } from "@quan-erp/base-frontend";
const { startDate, endDate } = useDashboardContext();
// Pass into react-query keys / API params so the widget refetches on range changeCritical 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