@object-ui/plugin-dashboard 3.1.5 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.turbo/turbo-build.log +16 -9
  2. package/CHANGELOG.md +18 -0
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +809 -668
  5. package/dist/index.umd.cjs +3 -3
  6. package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.d.ts.map +1 -0
  7. package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.stories.d.ts.map +1 -0
  8. package/dist/packages/plugin-dashboard/src/DashboardGridLayout.d.ts.map +1 -0
  9. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts.map +1 -0
  10. package/dist/packages/plugin-dashboard/src/DashboardRenderer.stories.d.ts.map +1 -0
  11. package/dist/packages/plugin-dashboard/src/DashboardWithConfig.d.ts.map +1 -0
  12. package/dist/{src → packages/plugin-dashboard/src}/MetricCard.d.ts +4 -0
  13. package/dist/packages/plugin-dashboard/src/MetricCard.d.ts.map +1 -0
  14. package/dist/{src → packages/plugin-dashboard/src}/MetricWidget.d.ts +5 -1
  15. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -0
  16. package/dist/packages/plugin-dashboard/src/ObjectDataTable.d.ts.map +1 -0
  17. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +57 -0
  18. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -0
  19. package/dist/packages/plugin-dashboard/src/ObjectPivotTable.d.ts.map +1 -0
  20. package/dist/packages/plugin-dashboard/src/PivotTable.d.ts.map +1 -0
  21. package/dist/packages/plugin-dashboard/src/WidgetConfigPanel.d.ts.map +1 -0
  22. package/dist/{src → packages/plugin-dashboard/src}/index.d.ts +4 -2
  23. package/dist/packages/plugin-dashboard/src/index.d.ts.map +1 -0
  24. package/dist/packages/plugin-dashboard/src/utils.d.ts.map +1 -0
  25. package/package.json +11 -11
  26. package/src/DashboardRenderer.tsx +34 -0
  27. package/src/MetricCard.tsx +38 -18
  28. package/src/MetricWidget.tsx +38 -18
  29. package/src/ObjectMetricWidget.tsx +159 -0
  30. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +130 -2
  31. package/src/__tests__/MetricCard.test.tsx +25 -0
  32. package/src/__tests__/ObjectMetricWidget.test.tsx +196 -0
  33. package/src/index.tsx +23 -1
  34. package/vite.config.ts +1 -0
  35. package/dist/src/DashboardConfigPanel.d.ts.map +0 -1
  36. package/dist/src/DashboardConfigPanel.stories.d.ts.map +0 -1
  37. package/dist/src/DashboardGridLayout.d.ts.map +0 -1
  38. package/dist/src/DashboardRenderer.d.ts.map +0 -1
  39. package/dist/src/DashboardRenderer.stories.d.ts.map +0 -1
  40. package/dist/src/DashboardWithConfig.d.ts.map +0 -1
  41. package/dist/src/MetricCard.d.ts.map +0 -1
  42. package/dist/src/MetricWidget.d.ts.map +0 -1
  43. package/dist/src/ObjectDataTable.d.ts.map +0 -1
  44. package/dist/src/ObjectPivotTable.d.ts.map +0 -1
  45. package/dist/src/PivotTable.d.ts.map +0 -1
  46. package/dist/src/WidgetConfigPanel.d.ts.map +0 -1
  47. package/dist/src/index.d.ts.map +0 -1
  48. package/dist/src/utils.d.ts.map +0 -1
  49. /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.d.ts +0 -0
  50. /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.stories.d.ts +0 -0
  51. /package/dist/{src → packages/plugin-dashboard/src}/DashboardGridLayout.d.ts +0 -0
  52. /package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.d.ts +0 -0
  53. /package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.stories.d.ts +0 -0
  54. /package/dist/{src → packages/plugin-dashboard/src}/DashboardWithConfig.d.ts +0 -0
  55. /package/dist/{src → packages/plugin-dashboard/src}/ObjectDataTable.d.ts +0 -0
  56. /package/dist/{src → packages/plugin-dashboard/src}/ObjectPivotTable.d.ts +0 -0
  57. /package/dist/{src → packages/plugin-dashboard/src}/PivotTable.d.ts +0 -0
  58. /package/dist/{src → packages/plugin-dashboard/src}/WidgetConfigPanel.d.ts +0 -0
  59. /package/dist/{src → packages/plugin-dashboard/src}/utils.d.ts +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DashboardWithConfig.d.ts","sourceRoot":"","sources":["../../../../src/DashboardWithConfig.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,eAAe,EAAyB,MAAM,kBAAkB,CAAC;AAU/E,MAAM,WAAW,wBAAwB;IACvC,qCAAqC;IACrC,MAAM,EAAE,eAAe,CAAC;IACxB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,6DAA6D;IAC7D,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IACpD,iEAAiE;IACjE,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IACvE,2DAA2D;IAC3D,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,yBAAyB;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,MAAM,EACN,MAAM,EACN,YAAY,EACZ,YAAY,EACZ,SAAS,EACT,WAAW,EACX,iBAAyB,EACzB,SAAS,GACV,EAAE,wBAAwB,2CAiJ1B"}
@@ -13,6 +13,10 @@ export interface MetricCardProps {
13
13
  defaultValue?: string;
14
14
  };
15
15
  className?: string;
16
+ /** When true, the card is in a loading state (fetching data from server). */
17
+ loading?: boolean;
18
+ /** Error message from a failed data fetch. When set, the card shows an error state. */
19
+ error?: string | null;
16
20
  }
17
21
  /**
18
22
  * MetricCard - Standalone metric card component for dashboard KPIs
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricCard.d.ts","sourceRoot":"","sources":["../../../../src/MetricCard.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAa1B,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzD,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uFAAuF;IACvF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED;;;GAGG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA8DhD,CAAC"}
@@ -19,6 +19,10 @@ export interface MetricWidgetProps {
19
19
  key?: string;
20
20
  defaultValue?: string;
21
21
  };
22
+ /** When true, the widget is in a loading state (fetching data from server). */
23
+ loading?: boolean;
24
+ /** Error message from a failed data fetch. When set, the widget shows an error state. */
25
+ error?: string | null;
22
26
  }
23
- export declare const MetricWidget: ({ label, value, trend, icon, className, description, ...props }: MetricWidgetProps) => import("react/jsx-runtime").JSX.Element;
27
+ export declare const MetricWidget: ({ label, value, trend, icon, className, description, loading, error, ...props }: MetricWidgetProps) => import("react/jsx-runtime").JSX.Element;
24
28
  //# sourceMappingURL=MetricWidget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricWidget.d.ts","sourceRoot":"","sources":["../../../../src/MetricWidget.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAavC,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,GAAG;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACzD,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;KACvC,CAAC;IACF,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,+EAA+E;IAC/E,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,yFAAyF;IACzF,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,eAAO,MAAM,YAAY,GAAI,iFAU1B,iBAAiB,4CAuDnB,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectDataTable.d.ts","sourceRoot":"","sources":["../../../../src/ObjectDataTable.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAmD,MAAM,OAAO,CAAC;AAKxE,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,GAAG,CAAC;QACb,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,EAAE,GAAG,EAAE,CAAC;QAChB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;KACpB,CAAC;IACF,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,6EAA6E;AAC7E,UAAU,gBAAgB;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,GAAG,gBAAgB,EAAE,CAiB9F;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAiJ1D,CAAC"}
@@ -0,0 +1,57 @@
1
+ import { default as React } from 'react';
2
+ /**
3
+ * ObjectMetricWidget — Data-bound metric widget.
4
+ *
5
+ * When a metric widget has an `object` binding and a `dataSource` is available,
6
+ * this component attempts to fetch the metric value from the server using
7
+ * aggregation. If the fetch fails, it shows an error state instead of
8
+ * silently displaying stale/hardcoded data.
9
+ *
10
+ * Lifecycle states:
11
+ * - **Loading** → spinner placeholder
12
+ * - **Error** → error message (API failure is surfaced, not hidden)
13
+ * - **Data** → actual metric value from server
14
+ * - **Fallback** → when no dataSource is available, renders the static
15
+ * `options.value` as provided in the widget config (demo/fallback mode)
16
+ */
17
+ export interface ObjectMetricWidgetProps {
18
+ /** The object/resource name to query */
19
+ objectName: string;
20
+ /** Aggregation config (field, function, groupBy) */
21
+ aggregate?: {
22
+ field: string;
23
+ function: string;
24
+ groupBy?: string;
25
+ };
26
+ /** Filter conditions */
27
+ filter?: any;
28
+ /** Static label for the metric */
29
+ label: string | {
30
+ key?: string;
31
+ defaultValue?: string;
32
+ };
33
+ /** Fallback static value (used when no dataSource or in demo mode) */
34
+ fallbackValue?: string | number;
35
+ /** Trend info */
36
+ trend?: {
37
+ value: number;
38
+ label?: string | {
39
+ key?: string;
40
+ defaultValue?: string;
41
+ };
42
+ direction?: 'up' | 'down' | 'neutral';
43
+ };
44
+ /** Icon name or ReactNode */
45
+ icon?: React.ReactNode | string;
46
+ /** Additional CSS class */
47
+ className?: string;
48
+ /** Description */
49
+ description?: string | {
50
+ key?: string;
51
+ defaultValue?: string;
52
+ };
53
+ /** External data source (overrides context) */
54
+ dataSource?: any;
55
+ }
56
+ export declare const ObjectMetricWidget: React.FC<ObjectMetricWidgetProps>;
57
+ //# sourceMappingURL=ObjectMetricWidget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectMetricWidget.d.ts","sourceRoot":"","sources":["../../../../src/ObjectMetricWidget.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAuD,MAAM,OAAO,CAAC;AAI5E;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,uBAAuB;IACtC,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,oDAAoD;IACpD,SAAS,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAClE,wBAAwB;IACxB,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,kCAAkC;IAClC,KAAK,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxD,sEAAsE;IACtE,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAChC,iBAAiB;IACjB,KAAK,CAAC,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,GAAG;YAAE,GAAG,CAAC,EAAE,MAAM,CAAC;YAAC,YAAY,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACzD,SAAS,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;KACvC,CAAC;IACF,6BAA6B;IAC7B,IAAI,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;IAChC,2BAA2B;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kBAAkB;IAClB,WAAW,CAAC,EAAE,MAAM,GAAG;QAAE,GAAG,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC/D,+CAA+C;IAC/C,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB;AAED,eAAO,MAAM,kBAAkB,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAwGhE,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ObjectPivotTable.d.ts","sourceRoot":"","sources":["../../../../src/ObjectPivotTable.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAA0C,MAAM,OAAO,CAAC;AAK/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,gBAAgB,GAAG;QACzB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,YAAY,CAAC,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QACrD,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,GAAG,CAAC;KACd,CAAC;IACF,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,qBAAqB,CAyH5D,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PivotTable.d.ts","sourceRoot":"","sources":["../../../../src/PivotTable.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,kBAAkB,CAAC;AAG3E,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,gBAAgB,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,eAAe,CA2KhD,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WidgetConfigPanel.d.ts","sourceRoot":"","sources":["../../../../src/WidgetConfigPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAwE/B,MAAM,MAAM,YAAY,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAuX5D,MAAM,WAAW,sBAAsB;IACrC,gCAAgC;IAChC,IAAI,EAAE,OAAO,CAAC;IACd,oBAAoB;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,+EAA+E;IAC/E,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,wCAAwC;IACxC,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,CAAC;IAC9C,oCAAoC;IACpC,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,IAAI,CAAC;IACpD,oEAAoE;IACpE,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,YAAY,EAAE,CAAC;IAClC,+EAA+E;IAC/E,eAAe,CAAC,EAAE,YAAY,EAAE,CAAC;CAClC;AAiBD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,IAAI,EACJ,OAAO,EACP,MAAM,EACN,MAAM,EACN,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,eAAe,GAChB,EAAE,sBAAsB,2CA8BxB"}
@@ -2,18 +2,20 @@ import { DashboardRenderer } from './DashboardRenderer';
2
2
  import { DashboardGridLayout } from './DashboardGridLayout';
3
3
  import { MetricWidget } from './MetricWidget';
4
4
  import { MetricCard } from './MetricCard';
5
+ import { ObjectMetricWidget } from './ObjectMetricWidget';
5
6
  import { PivotTable } from './PivotTable';
6
7
  import { ObjectPivotTable } from './ObjectPivotTable';
7
8
  import { ObjectDataTable } from './ObjectDataTable';
8
9
  import { DashboardConfigPanel } from './DashboardConfigPanel';
9
10
  import { WidgetConfigPanel } from './WidgetConfigPanel';
10
11
  import { DashboardWithConfig } from './DashboardWithConfig';
11
- export { DashboardRenderer, DashboardGridLayout, MetricWidget, MetricCard, PivotTable, ObjectPivotTable, ObjectDataTable, DashboardConfigPanel, WidgetConfigPanel, DashboardWithConfig };
12
+ export { DashboardRenderer, DashboardGridLayout, MetricWidget, MetricCard, ObjectMetricWidget, PivotTable, ObjectPivotTable, ObjectDataTable, DashboardConfigPanel, WidgetConfigPanel, DashboardWithConfig };
12
13
  export declare const dashboardComponents: {
13
14
  DashboardRenderer: import('react').ForwardRefExoticComponent<Omit<import('./DashboardRenderer').DashboardRendererProps, "ref"> & import('react').RefAttributes<HTMLDivElement>>;
14
15
  DashboardGridLayout: import('react').FC<import('./DashboardGridLayout').DashboardGridLayoutProps>;
15
- MetricWidget: ({ label, value, trend, icon, className, description, ...props }: import('./MetricWidget').MetricWidgetProps) => import("react/jsx-runtime").JSX.Element;
16
+ MetricWidget: ({ label, value, trend, icon, className, description, loading, error, ...props }: import('./MetricWidget').MetricWidgetProps) => import("react/jsx-runtime").JSX.Element;
16
17
  MetricCard: import('react').FC<import('./MetricCard').MetricCardProps>;
18
+ ObjectMetricWidget: import('react').FC<import('./ObjectMetricWidget').ObjectMetricWidgetProps>;
17
19
  PivotTable: import('react').FC<import('./PivotTable').PivotTableProps>;
18
20
  ObjectPivotTable: import('react').FC<import('./ObjectPivotTable').ObjectPivotTableProps>;
19
21
  ObjectDataTable: import('react').FC<import('./ObjectDataTable').ObjectDataTableProps>;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/index.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAC1D,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,YAAY,EAAE,UAAU,EAAE,kBAAkB,EAAE,UAAU,EAAE,gBAAgB,EAAE,eAAe,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,CAAC;AA0M7M,eAAO,MAAM,mBAAmB;;;;;;;;;;;;CAY/B,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../../src/utils.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,4FAA4F;AAC5F,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,OAAO,GAAG,UAAU,IAAI;IAAE,QAAQ,EAAE,QAAQ,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,GAAG,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,CAAA;CAAE,CAO1I"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/plugin-dashboard",
3
- "version": "3.1.5",
3
+ "version": "3.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Dashboard plugin for Object UI",
@@ -16,15 +16,15 @@
16
16
  },
17
17
  "dependencies": {
18
18
  "clsx": "^2.1.1",
19
- "lucide-react": "^0.577.0",
20
- "react": "19.2.4",
21
- "react-dom": "19.2.4",
22
- "react-grid-layout": "^2.2.2",
19
+ "lucide-react": "^1.8.0",
20
+ "react": "19.2.5",
21
+ "react-dom": "19.2.5",
22
+ "react-grid-layout": "^2.2.3",
23
23
  "tailwind-merge": "^3.5.0",
24
- "@object-ui/components": "3.1.5",
25
- "@object-ui/core": "3.1.5",
26
- "@object-ui/react": "3.1.5",
27
- "@object-ui/types": "3.1.5"
24
+ "@object-ui/components": "3.3.0",
25
+ "@object-ui/core": "3.3.0",
26
+ "@object-ui/react": "3.3.0",
27
+ "@object-ui/types": "3.3.0"
28
28
  },
29
29
  "peerDependencies": {
30
30
  "react": "^18.0.0",
@@ -33,8 +33,8 @@
33
33
  "devDependencies": {
34
34
  "@types/react-grid-layout": "^2.1.0",
35
35
  "@vitejs/plugin-react": "^6.0.1",
36
- "typescript": "^5.9.3",
37
- "vite": "^8.0.1",
36
+ "typescript": "^6.0.2",
37
+ "vite": "^8.0.8",
38
38
  "vite-plugin-dts": "^4.5.4"
39
39
  },
40
40
  "scripts": {
@@ -124,6 +124,40 @@ export const DashboardRenderer = forwardRef<HTMLDivElement, DashboardRendererPro
124
124
  // Handle Shorthand Registry Mappings
125
125
  const widgetType = widget.type;
126
126
  const options = (widget.options || {}) as Record<string, any>;
127
+
128
+ // Metric widgets with object binding — delegate to ObjectMetricWidget
129
+ // for async data loading with proper error/loading states.
130
+ // Static metric options (label, value, trend, icon) are passed as
131
+ // fallback values that render only when no dataSource is available.
132
+ if (widgetType === 'metric' && widget.object) {
133
+ const widgetData = options.data;
134
+ const aggregate = isObjectProvider(widgetData) && widgetData.aggregate
135
+ ? {
136
+ field: widget.valueField || widgetData.aggregate.field,
137
+ function: widget.aggregate || widgetData.aggregate.function,
138
+ // Prefer explicit categoryField or aggregate.groupBy; otherwise, default to a single bucket.
139
+ groupBy: widget.categoryField ?? widgetData.aggregate.groupBy ?? '_all',
140
+ }
141
+ : widget.aggregate ? {
142
+ field: widget.valueField || 'value',
143
+ function: widget.aggregate,
144
+ // Default to a single group unless the user explicitly configures a categoryField.
145
+ groupBy: widget.categoryField || '_all',
146
+ } : undefined;
147
+
148
+ return {
149
+ type: 'object-metric',
150
+ objectName: widget.object || (isObjectProvider(widgetData) ? widgetData.object : undefined),
151
+ aggregate,
152
+ filter: (isObjectProvider(widgetData) ? widgetData.filter : undefined) || widget.filter,
153
+ label: options.label || resolveLabel(widget.title) || '',
154
+ fallbackValue: options.value,
155
+ trend: options.trend,
156
+ icon: options.icon,
157
+ description: options.description,
158
+ };
159
+ }
160
+
127
161
  if (widgetType === 'bar' || widgetType === 'line' || widgetType === 'area' || widgetType === 'pie' || widgetType === 'donut' || widgetType === 'scatter') {
128
162
  // Support data at widget level or nested inside options
129
163
  const widgetData = (widget as any).data || options.data;
@@ -9,7 +9,7 @@
9
9
  import React from 'react';
10
10
  import { Card, CardContent, CardHeader, CardTitle } from '@object-ui/components';
11
11
  import { cn } from '@object-ui/components';
12
- import { ArrowDownIcon, ArrowUpIcon, MinusIcon } from 'lucide-react';
12
+ import { ArrowDownIcon, ArrowUpIcon, MinusIcon, AlertCircle, Loader2 } from 'lucide-react';
13
13
  import * as LucideIcons from 'lucide-react';
14
14
 
15
15
  /** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */
@@ -27,6 +27,10 @@ export interface MetricCardProps {
27
27
  trendValue?: string;
28
28
  description?: string | { key?: string; defaultValue?: string };
29
29
  className?: string;
30
+ /** When true, the card is in a loading state (fetching data from server). */
31
+ loading?: boolean;
32
+ /** Error message from a failed data fetch. When set, the card shows an error state. */
33
+ error?: string | null;
30
34
  }
31
35
 
32
36
  /**
@@ -41,6 +45,8 @@ export const MetricCard: React.FC<MetricCardProps> = ({
41
45
  trendValue,
42
46
  description,
43
47
  className,
48
+ loading,
49
+ error,
44
50
  ...props
45
51
  }) => {
46
52
  // Resolve icon from lucide-react
@@ -57,24 +63,38 @@ export const MetricCard: React.FC<MetricCardProps> = ({
57
63
  )}
58
64
  </CardHeader>
59
65
  <CardContent>
60
- <div className="text-2xl font-bold">{value}</div>
61
- {(trend || trendValue || description) && (
62
- <p className="text-xs text-muted-foreground flex items-center mt-1">
63
- {trend && trendValue && (
64
- <span className={cn(
65
- "flex items-center mr-2",
66
- trend === 'up' && "text-green-500",
67
- trend === 'down' && "text-red-500",
68
- trend === 'neutral' && "text-yellow-500"
69
- )}>
70
- {trend === 'up' && <ArrowUpIcon className="h-3 w-3 mr-1" />}
71
- {trend === 'down' && <ArrowDownIcon className="h-3 w-3 mr-1" />}
72
- {trend === 'neutral' && <MinusIcon className="h-3 w-3 mr-1" />}
73
- {trendValue}
74
- </span>
66
+ {loading ? (
67
+ <div className="flex items-center gap-2 text-muted-foreground" data-testid="metric-card-loading">
68
+ <Loader2 className="h-4 w-4 animate-spin" />
69
+ <span className="text-sm">Loading…</span>
70
+ </div>
71
+ ) : error ? (
72
+ <div className="flex items-center gap-2" data-testid="metric-card-error" role="alert">
73
+ <AlertCircle className="h-4 w-4 text-destructive shrink-0" />
74
+ <span className="text-xs text-destructive truncate">{error}</span>
75
+ </div>
76
+ ) : (
77
+ <>
78
+ <div className="text-2xl font-bold">{value}</div>
79
+ {(trend || trendValue || description) && (
80
+ <p className="text-xs text-muted-foreground flex items-center mt-1">
81
+ {trend && trendValue && (
82
+ <span className={cn(
83
+ "flex items-center mr-2",
84
+ trend === 'up' && "text-green-500",
85
+ trend === 'down' && "text-red-500",
86
+ trend === 'neutral' && "text-yellow-500"
87
+ )}>
88
+ {trend === 'up' && <ArrowUpIcon className="h-3 w-3 mr-1" />}
89
+ {trend === 'down' && <ArrowDownIcon className="h-3 w-3 mr-1" />}
90
+ {trend === 'neutral' && <MinusIcon className="h-3 w-3 mr-1" />}
91
+ {trendValue}
92
+ </span>
93
+ )}
94
+ {resolveLabel(description)}
95
+ </p>
75
96
  )}
76
- {resolveLabel(description)}
77
- </p>
97
+ </>
78
98
  )}
79
99
  </CardContent>
80
100
  </Card>
@@ -1,7 +1,7 @@
1
1
  import React, { useMemo } from 'react';
2
2
  import { Card, CardContent, CardHeader, CardTitle } from '@object-ui/components';
3
3
  import { cn } from '@object-ui/components';
4
- import { ArrowDownIcon, ArrowUpIcon, MinusIcon } from 'lucide-react';
4
+ import { ArrowDownIcon, ArrowUpIcon, MinusIcon, AlertCircle, Loader2 } from 'lucide-react';
5
5
  import * as LucideIcons from 'lucide-react';
6
6
 
7
7
  /** Resolve an I18nLabel (string or {key, defaultValue}) to a plain string. */
@@ -22,6 +22,10 @@ export interface MetricWidgetProps {
22
22
  icon?: React.ReactNode | string;
23
23
  className?: string;
24
24
  description?: string | { key?: string; defaultValue?: string };
25
+ /** When true, the widget is in a loading state (fetching data from server). */
26
+ loading?: boolean;
27
+ /** Error message from a failed data fetch. When set, the widget shows an error state. */
28
+ error?: string | null;
25
29
  }
26
30
 
27
31
  export const MetricWidget = ({
@@ -31,6 +35,8 @@ export const MetricWidget = ({
31
35
  icon,
32
36
  className,
33
37
  description,
38
+ loading,
39
+ error,
34
40
  ...props
35
41
  }: MetricWidgetProps) => {
36
42
  // Resolve icon if it's a string
@@ -51,24 +57,38 @@ export const MetricWidget = ({
51
57
  {resolvedIcon && <div className="h-4 w-4 text-muted-foreground shrink-0">{resolvedIcon}</div>}
52
58
  </CardHeader>
53
59
  <CardContent>
54
- <div className="text-2xl font-bold truncate">{value}</div>
55
- {(trend || description) && (
56
- <p className="text-xs text-muted-foreground flex items-center mt-1 truncate">
57
- {trend && (
58
- <span className={cn(
59
- "flex items-center mr-2 shrink-0",
60
- trend.direction === 'up' && "text-green-500",
61
- trend.direction === 'down' && "text-red-500",
62
- trend.direction === 'neutral' && "text-yellow-500"
63
- )}>
64
- {trend.direction === 'up' && <ArrowUpIcon className="h-3 w-3 mr-1" />}
65
- {trend.direction === 'down' && <ArrowDownIcon className="h-3 w-3 mr-1" />}
66
- {trend.direction === 'neutral' && <MinusIcon className="h-3 w-3 mr-1" />}
67
- {trend.value}%
68
- </span>
60
+ {loading ? (
61
+ <div className="flex items-center gap-2 text-muted-foreground" data-testid="metric-loading">
62
+ <Loader2 className="h-4 w-4 animate-spin" />
63
+ <span className="text-sm">Loading…</span>
64
+ </div>
65
+ ) : error ? (
66
+ <div className="flex items-center gap-2" data-testid="metric-error" role="alert">
67
+ <AlertCircle className="h-4 w-4 text-destructive shrink-0" />
68
+ <span className="text-xs text-destructive truncate">{error}</span>
69
+ </div>
70
+ ) : (
71
+ <>
72
+ <div className="text-2xl font-bold truncate">{value}</div>
73
+ {(trend || description) && (
74
+ <p className="text-xs text-muted-foreground flex items-center mt-1 truncate">
75
+ {trend && (
76
+ <span className={cn(
77
+ "flex items-center mr-2 shrink-0",
78
+ trend.direction === 'up' && "text-green-500",
79
+ trend.direction === 'down' && "text-red-500",
80
+ trend.direction === 'neutral' && "text-yellow-500"
81
+ )}>
82
+ {trend.direction === 'up' && <ArrowUpIcon className="h-3 w-3 mr-1" />}
83
+ {trend.direction === 'down' && <ArrowDownIcon className="h-3 w-3 mr-1" />}
84
+ {trend.direction === 'neutral' && <MinusIcon className="h-3 w-3 mr-1" />}
85
+ {trend.value}%
86
+ </span>
87
+ )}
88
+ <span className="truncate">{resolveLabel(description) || resolveLabel(trend?.label)}</span>
89
+ </p>
69
90
  )}
70
- <span className="truncate">{resolveLabel(description) || resolveLabel(trend?.label)}</span>
71
- </p>
91
+ </>
72
92
  )}
73
93
  </CardContent>
74
94
  </Card>
@@ -0,0 +1,159 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+
9
+ import React, { useState, useEffect, useContext, useCallback } from 'react';
10
+ import { SchemaRendererContext } from '@object-ui/react';
11
+ import { MetricWidget } from './MetricWidget';
12
+
13
+ /**
14
+ * ObjectMetricWidget — Data-bound metric widget.
15
+ *
16
+ * When a metric widget has an `object` binding and a `dataSource` is available,
17
+ * this component attempts to fetch the metric value from the server using
18
+ * aggregation. If the fetch fails, it shows an error state instead of
19
+ * silently displaying stale/hardcoded data.
20
+ *
21
+ * Lifecycle states:
22
+ * - **Loading** → spinner placeholder
23
+ * - **Error** → error message (API failure is surfaced, not hidden)
24
+ * - **Data** → actual metric value from server
25
+ * - **Fallback** → when no dataSource is available, renders the static
26
+ * `options.value` as provided in the widget config (demo/fallback mode)
27
+ */
28
+ export interface ObjectMetricWidgetProps {
29
+ /** The object/resource name to query */
30
+ objectName: string;
31
+ /** Aggregation config (field, function, groupBy) */
32
+ aggregate?: { field: string; function: string; groupBy?: string };
33
+ /** Filter conditions */
34
+ filter?: any;
35
+ /** Static label for the metric */
36
+ label: string | { key?: string; defaultValue?: string };
37
+ /** Fallback static value (used when no dataSource or in demo mode) */
38
+ fallbackValue?: string | number;
39
+ /** Trend info */
40
+ trend?: {
41
+ value: number;
42
+ label?: string | { key?: string; defaultValue?: string };
43
+ direction?: 'up' | 'down' | 'neutral';
44
+ };
45
+ /** Icon name or ReactNode */
46
+ icon?: React.ReactNode | string;
47
+ /** Additional CSS class */
48
+ className?: string;
49
+ /** Description */
50
+ description?: string | { key?: string; defaultValue?: string };
51
+ /** External data source (overrides context) */
52
+ dataSource?: any;
53
+ }
54
+
55
+ export const ObjectMetricWidget: React.FC<ObjectMetricWidgetProps> = ({
56
+ objectName,
57
+ aggregate,
58
+ filter,
59
+ label,
60
+ fallbackValue,
61
+ trend,
62
+ icon,
63
+ className,
64
+ description,
65
+ dataSource: propDataSource,
66
+ }) => {
67
+ const context = useContext(SchemaRendererContext);
68
+ const dataSource = propDataSource || context?.dataSource;
69
+
70
+ const [fetchedValue, setFetchedValue] = useState<string | number | null>(null);
71
+ const [loading, setLoading] = useState(false);
72
+ const [error, setError] = useState<string | null>(null);
73
+
74
+ const fetchMetric = useCallback(async (ds: any, mounted: { current: boolean }) => {
75
+ if (!ds || !objectName) return;
76
+ if (mounted.current) {
77
+ setLoading(true);
78
+ setError(null);
79
+ }
80
+
81
+ try {
82
+ let value: string | number;
83
+
84
+ if (aggregate && typeof ds.aggregate === 'function') {
85
+ // Server-side aggregation
86
+ const results = await ds.aggregate(objectName, {
87
+ field: aggregate.field,
88
+ function: aggregate.function,
89
+ groupBy: aggregate.groupBy || '_all',
90
+ filter,
91
+ });
92
+ const data = Array.isArray(results) ? results : [];
93
+
94
+ if (data.length === 0) {
95
+ value = 0;
96
+ } else if (aggregate.function === 'count') {
97
+ // Sum all count results
98
+ value = data.reduce((sum: number, r: any) => sum + (Number(r[aggregate.field]) || Number(r.count) || 0), 0);
99
+ } else {
100
+ // Take the first result's value
101
+ value = data[0][aggregate.field] ?? 0;
102
+ }
103
+ } else if (typeof ds.find === 'function') {
104
+ // Fallback: count records
105
+ const results = await ds.find(objectName, { $filter: filter });
106
+ const records = Array.isArray(results) ? results : results?.data || results?.records || [];
107
+ value = records.length;
108
+ } else {
109
+ return;
110
+ }
111
+
112
+ if (mounted.current) {
113
+ setFetchedValue(value);
114
+ }
115
+ } catch (e) {
116
+ console.error('[ObjectMetricWidget] Fetch error:', e);
117
+ if (mounted.current) {
118
+ setError(e instanceof Error ? e.message : 'Failed to load metric');
119
+ }
120
+ } finally {
121
+ if (mounted.current) setLoading(false);
122
+ }
123
+ }, [objectName, aggregate, filter]);
124
+
125
+ useEffect(() => {
126
+ const mounted = { current: true };
127
+
128
+ if (dataSource && objectName) {
129
+ fetchMetric(dataSource, mounted);
130
+ } else {
131
+ // Reset state when dataSource becomes unavailable so we fall back
132
+ // to the static fallbackValue instead of showing stale server data.
133
+ setFetchedValue(null);
134
+ setError(null);
135
+ }
136
+
137
+ return () => { mounted.current = false; };
138
+ }, [dataSource, objectName, fetchMetric]);
139
+
140
+ // Determine the display value:
141
+ // - If we fetched a value from the server, use it
142
+ // - If there's no data source, use the fallback (demo/static value)
143
+ const displayValue = fetchedValue !== null
144
+ ? fetchedValue
145
+ : (!dataSource ? (fallbackValue ?? '—') : '—');
146
+
147
+ return (
148
+ <MetricWidget
149
+ label={label}
150
+ value={displayValue}
151
+ trend={trend}
152
+ icon={icon}
153
+ className={className}
154
+ description={description}
155
+ loading={loading}
156
+ error={error}
157
+ />
158
+ );
159
+ };