@open-mercato/core 0.4.2-canary-cf7d9b4116 → 0.4.2-canary-e6bf6a353e

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 (136) hide show
  1. package/dist/modules/auth/lib/setup-app.js +2 -0
  2. package/dist/modules/auth/lib/setup-app.js.map +2 -2
  3. package/dist/modules/catalog/analytics.js +27 -0
  4. package/dist/modules/catalog/analytics.js.map +7 -0
  5. package/dist/modules/customers/analytics.js +50 -0
  6. package/dist/modules/customers/analytics.js.map +7 -0
  7. package/dist/modules/dashboards/acl.js +2 -1
  8. package/dist/modules/dashboards/acl.js.map +2 -2
  9. package/dist/modules/dashboards/api/widgets/data/route.js +187 -0
  10. package/dist/modules/dashboards/api/widgets/data/route.js.map +7 -0
  11. package/dist/modules/dashboards/cli.js +142 -1
  12. package/dist/modules/dashboards/cli.js.map +2 -2
  13. package/dist/modules/dashboards/di.js +11 -0
  14. package/dist/modules/dashboards/di.js.map +7 -0
  15. package/dist/modules/dashboards/lib/aggregations.js +162 -0
  16. package/dist/modules/dashboards/lib/aggregations.js.map +7 -0
  17. package/dist/modules/dashboards/lib/formatters.js +34 -0
  18. package/dist/modules/dashboards/lib/formatters.js.map +7 -0
  19. package/dist/modules/dashboards/seed/analytics.js +383 -0
  20. package/dist/modules/dashboards/seed/analytics.js.map +7 -0
  21. package/dist/modules/dashboards/services/analyticsRegistry.js +52 -0
  22. package/dist/modules/dashboards/services/analyticsRegistry.js.map +7 -0
  23. package/dist/modules/dashboards/services/widgetDataService.js +207 -0
  24. package/dist/modules/dashboards/services/widgetDataService.js.map +7 -0
  25. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js +18 -0
  26. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js.map +7 -0
  27. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +128 -0
  28. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +7 -0
  29. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js +25 -0
  30. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js.map +7 -0
  31. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js +18 -0
  32. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js.map +7 -0
  33. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +126 -0
  34. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +7 -0
  35. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js +25 -0
  36. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js.map +7 -0
  37. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js +18 -0
  38. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js.map +7 -0
  39. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +151 -0
  40. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +7 -0
  41. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js +25 -0
  42. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js.map +7 -0
  43. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js +18 -0
  44. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js.map +7 -0
  45. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +126 -0
  46. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +7 -0
  47. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js +25 -0
  48. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js.map +7 -0
  49. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js +16 -0
  50. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +7 -0
  51. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +123 -0
  52. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +7 -0
  53. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js +25 -0
  54. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js.map +7 -0
  55. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js +18 -0
  56. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js.map +7 -0
  57. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +128 -0
  58. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +7 -0
  59. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js +25 -0
  60. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js.map +7 -0
  61. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js +21 -0
  62. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js.map +7 -0
  63. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +211 -0
  64. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +7 -0
  65. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js +25 -0
  66. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js.map +7 -0
  67. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js +19 -0
  68. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js.map +7 -0
  69. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +131 -0
  70. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +7 -0
  71. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js +25 -0
  72. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js.map +7 -0
  73. package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js +19 -0
  74. package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js.map +7 -0
  75. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +153 -0
  76. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +7 -0
  77. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js +25 -0
  78. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js.map +7 -0
  79. package/dist/modules/dashboards/widgets/dashboard/top-products/config.js +22 -0
  80. package/dist/modules/dashboards/widgets/dashboard/top-products/config.js.map +7 -0
  81. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +180 -0
  82. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +7 -0
  83. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js +25 -0
  84. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js.map +7 -0
  85. package/dist/modules/sales/analytics.js +67 -0
  86. package/dist/modules/sales/analytics.js.map +7 -0
  87. package/package.json +2 -2
  88. package/src/modules/auth/lib/setup-app.ts +2 -0
  89. package/src/modules/catalog/analytics.ts +24 -0
  90. package/src/modules/customers/analytics.ts +47 -0
  91. package/src/modules/dashboards/acl.ts +1 -0
  92. package/src/modules/dashboards/api/widgets/data/route.ts +221 -0
  93. package/src/modules/dashboards/cli.ts +164 -1
  94. package/src/modules/dashboards/di.ts +9 -0
  95. package/src/modules/dashboards/i18n/de.json +115 -1
  96. package/src/modules/dashboards/i18n/en.json +115 -1
  97. package/src/modules/dashboards/i18n/es.json +115 -1
  98. package/src/modules/dashboards/i18n/pl.json +115 -1
  99. package/src/modules/dashboards/lib/__tests__/aggregations.test.ts +327 -0
  100. package/src/modules/dashboards/lib/__tests__/formatters.test.ts +128 -0
  101. package/src/modules/dashboards/lib/aggregations.ts +225 -0
  102. package/src/modules/dashboards/lib/formatters.ts +36 -0
  103. package/src/modules/dashboards/seed/analytics.ts +405 -0
  104. package/src/modules/dashboards/services/analyticsRegistry.ts +79 -0
  105. package/src/modules/dashboards/services/widgetDataService.ts +329 -0
  106. package/src/modules/dashboards/widgets/dashboard/aov-kpi/config.ts +20 -0
  107. package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +135 -0
  108. package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.ts +24 -0
  109. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/config.ts +20 -0
  110. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +133 -0
  111. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts +24 -0
  112. package/src/modules/dashboards/widgets/dashboard/orders-by-status/config.ts +20 -0
  113. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +154 -0
  114. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts +24 -0
  115. package/src/modules/dashboards/widgets/dashboard/orders-kpi/config.ts +20 -0
  116. package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +133 -0
  117. package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts +24 -0
  118. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +17 -0
  119. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +137 -0
  120. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts +24 -0
  121. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/config.ts +20 -0
  122. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +135 -0
  123. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.ts +24 -0
  124. package/src/modules/dashboards/widgets/dashboard/revenue-trend/config.ts +24 -0
  125. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +220 -0
  126. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.ts +24 -0
  127. package/src/modules/dashboards/widgets/dashboard/sales-by-region/config.ts +21 -0
  128. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +131 -0
  129. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.ts +24 -0
  130. package/src/modules/dashboards/widgets/dashboard/top-customers/config.ts +21 -0
  131. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +161 -0
  132. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.ts +24 -0
  133. package/src/modules/dashboards/widgets/dashboard/top-products/config.ts +27 -0
  134. package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +181 -0
  135. package/src/modules/dashboards/widgets/dashboard/top-products/widget.ts +24 -0
  136. package/src/modules/sales/analytics.ts +64 -0
@@ -0,0 +1,25 @@
1
+ import NewCustomersKpiWidget from "./widget.client.js";
2
+ import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
3
+ const widget = {
4
+ metadata: {
5
+ id: "dashboards.analytics.newCustomersKpi",
6
+ title: "Customer Growth",
7
+ description: "New customer count with period comparison",
8
+ features: ["analytics.view", "customers.people.view"],
9
+ defaultSize: "sm",
10
+ defaultEnabled: false,
11
+ defaultSettings: DEFAULT_SETTINGS,
12
+ tags: ["analytics", "customers", "kpi"],
13
+ category: "analytics",
14
+ icon: "user-plus",
15
+ supportsRefresh: true
16
+ },
17
+ Widget: NewCustomersKpiWidget,
18
+ hydrateSettings,
19
+ dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison })
20
+ };
21
+ var widget_default = widget;
22
+ export {
23
+ widget_default as default
24
+ };
25
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts"],
4
+ "sourcesContent": ["import type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport NewCustomersKpiWidget from './widget.client'\nimport { DEFAULT_SETTINGS, hydrateSettings, type NewCustomersKpiSettings } from './config'\n\nconst widget: DashboardWidgetModule<NewCustomersKpiSettings> = {\n metadata: {\n id: 'dashboards.analytics.newCustomersKpi',\n title: 'Customer Growth',\n description: 'New customer count with period comparison',\n features: ['analytics.view', 'customers.people.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'customers', 'kpi'],\n category: 'analytics',\n icon: 'user-plus',\n supportsRefresh: true,\n },\n Widget: NewCustomersKpiWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison }),\n}\n\nexport default widget\n"],
5
+ "mappings": "AACA,OAAO,2BAA2B;AAClC,SAAS,kBAAkB,uBAAqD;AAEhF,MAAM,SAAyD;AAAA,EAC7D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,uBAAuB;AAAA,IACpD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,aAAa,KAAK;AAAA,IACtC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,gBAAgB,EAAE,eAAe;AACxF;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import { isValidDateRangePreset } from "@open-mercato/ui/backend/date-range";
2
+ const DEFAULT_SETTINGS = {
3
+ dateRange: "this_month",
4
+ variant: "donut"
5
+ };
6
+ function hydrateSettings(raw) {
7
+ if (!raw || typeof raw !== "object") return { ...DEFAULT_SETTINGS };
8
+ const obj = raw;
9
+ return {
10
+ dateRange: isValidDateRangePreset(obj.dateRange) ? obj.dateRange : DEFAULT_SETTINGS.dateRange,
11
+ variant: obj.variant === "pie" || obj.variant === "donut" ? obj.variant : DEFAULT_SETTINGS.variant
12
+ };
13
+ }
14
+ export {
15
+ DEFAULT_SETTINGS,
16
+ hydrateSettings
17
+ };
18
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-by-status/config.ts"],
4
+ "sourcesContent": ["import { type DateRangePreset, isValidDateRangePreset } from '@open-mercato/ui/backend/date-range'\n\nexport type OrdersByStatusSettings = {\n dateRange: DateRangePreset\n variant: 'pie' | 'donut'\n}\n\nexport const DEFAULT_SETTINGS: OrdersByStatusSettings = {\n dateRange: 'this_month',\n variant: 'donut',\n}\n\nexport function hydrateSettings(raw: unknown): OrdersByStatusSettings {\n if (!raw || typeof raw !== 'object') return { ...DEFAULT_SETTINGS }\n const obj = raw as Record<string, unknown>\n return {\n dateRange: isValidDateRangePreset(obj.dateRange) ? obj.dateRange : DEFAULT_SETTINGS.dateRange,\n variant: obj.variant === 'pie' || obj.variant === 'donut' ? obj.variant : DEFAULT_SETTINGS.variant,\n }\n}\n"],
5
+ "mappings": "AAAA,SAA+B,8BAA8B;AAOtD,MAAM,mBAA2C;AAAA,EACtD,WAAW;AAAA,EACX,SAAS;AACX;AAEO,SAAS,gBAAgB,KAAsC;AACpE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,EAAE,GAAG,iBAAiB;AAClE,QAAM,MAAM;AACZ,SAAO;AAAA,IACL,WAAW,uBAAuB,IAAI,SAAS,IAAI,IAAI,YAAY,iBAAiB;AAAA,IACpF,SAAS,IAAI,YAAY,SAAS,IAAI,YAAY,UAAU,IAAI,UAAU,iBAAiB;AAAA,EAC7F;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,151 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { PieChart } from "@open-mercato/ui/backend/charts";
7
+ import {
8
+ DateRangeSelect,
9
+ InlineDateRangeSelect
10
+ } from "@open-mercato/ui/backend/date-range";
11
+ import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
12
+ async function fetchOrdersByStatusData(settings) {
13
+ const body = {
14
+ entityType: "sales:orders",
15
+ metric: {
16
+ field: "id",
17
+ aggregate: "count"
18
+ },
19
+ groupBy: {
20
+ field: "status"
21
+ },
22
+ dateRange: {
23
+ field: "placedAt",
24
+ preset: settings.dateRange
25
+ }
26
+ };
27
+ const call = await apiCall("/api/dashboards/widgets/data", {
28
+ method: "POST",
29
+ headers: { "Content-Type": "application/json" },
30
+ body: JSON.stringify(body)
31
+ });
32
+ if (!call.ok) {
33
+ const errorMsg = call.result?.error;
34
+ throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch orders by status data");
35
+ }
36
+ return call.result;
37
+ }
38
+ const ORDER_STATUS_KEYS = {
39
+ draft: "dashboards.analytics.orderStatus.draft",
40
+ pending: "dashboards.analytics.orderStatus.pending",
41
+ confirmed: "dashboards.analytics.orderStatus.confirmed",
42
+ processing: "dashboards.analytics.orderStatus.processing",
43
+ shipped: "dashboards.analytics.orderStatus.shipped",
44
+ delivered: "dashboards.analytics.orderStatus.delivered",
45
+ cancelled: "dashboards.analytics.orderStatus.cancelled"
46
+ };
47
+ function formatStatusLabel(status, t) {
48
+ if (!status) return t("dashboards.analytics.labels.unknown", "Unknown");
49
+ const key = ORDER_STATUS_KEYS[status.toLowerCase()];
50
+ if (key) {
51
+ return t(key, status.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()));
52
+ }
53
+ return status.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
54
+ }
55
+ const OrdersByStatusWidget = ({
56
+ mode,
57
+ settings = DEFAULT_SETTINGS,
58
+ onSettingsChange,
59
+ refreshToken,
60
+ onRefreshStateChange
61
+ }) => {
62
+ const t = useT();
63
+ const hydrated = React.useMemo(() => hydrateSettings(settings), [settings]);
64
+ const [data, setData] = React.useState([]);
65
+ const [loading, setLoading] = React.useState(true);
66
+ const [error, setError] = React.useState(null);
67
+ const refresh = React.useCallback(async () => {
68
+ onRefreshStateChange?.(true);
69
+ setLoading(true);
70
+ setError(null);
71
+ try {
72
+ const result = await fetchOrdersByStatusData(hydrated);
73
+ const chartData = result.data.map((item) => ({
74
+ name: formatStatusLabel(item.groupKey, t),
75
+ value: item.value ?? 0
76
+ }));
77
+ setData(chartData);
78
+ } catch (err) {
79
+ console.error("Failed to load orders by status data", err);
80
+ setError(t("dashboards.analytics.widgets.ordersByStatus.error", "Failed to load data"));
81
+ } finally {
82
+ setLoading(false);
83
+ onRefreshStateChange?.(false);
84
+ }
85
+ }, [hydrated, onRefreshStateChange, t]);
86
+ React.useEffect(() => {
87
+ refresh().catch(() => {
88
+ });
89
+ }, [refresh, refreshToken]);
90
+ if (mode === "settings") {
91
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 text-sm", children: [
92
+ /* @__PURE__ */ jsx(
93
+ DateRangeSelect,
94
+ {
95
+ id: "orders-by-status-date-range",
96
+ label: t("dashboards.analytics.settings.dateRange", "Date Range"),
97
+ value: hydrated.dateRange,
98
+ onChange: (dateRange) => onSettingsChange({ ...hydrated, dateRange })
99
+ }
100
+ ),
101
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
102
+ /* @__PURE__ */ jsx(
103
+ "label",
104
+ {
105
+ htmlFor: "orders-by-status-variant",
106
+ className: "text-xs font-semibold uppercase text-muted-foreground",
107
+ children: t("dashboards.analytics.settings.chartVariant", "Chart Style")
108
+ }
109
+ ),
110
+ /* @__PURE__ */ jsxs(
111
+ "select",
112
+ {
113
+ id: "orders-by-status-variant",
114
+ className: "w-full rounded-md border bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary",
115
+ value: hydrated.variant,
116
+ onChange: (e) => onSettingsChange({ ...hydrated, variant: e.target.value }),
117
+ children: [
118
+ /* @__PURE__ */ jsx("option", { value: "donut", children: t("dashboards.analytics.chartVariant.donut", "Donut") }),
119
+ /* @__PURE__ */ jsx("option", { value: "pie", children: t("dashboards.analytics.chartVariant.pie", "Pie") })
120
+ ]
121
+ }
122
+ )
123
+ ] })
124
+ ] });
125
+ }
126
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
127
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsx(
128
+ InlineDateRangeSelect,
129
+ {
130
+ value: hydrated.dateRange,
131
+ onChange: (dateRange) => onSettingsChange({ ...hydrated, dateRange })
132
+ }
133
+ ) }),
134
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(
135
+ PieChart,
136
+ {
137
+ data,
138
+ loading,
139
+ error,
140
+ variant: hydrated.variant,
141
+ colors: ["blue", "emerald", "amber", "rose", "violet", "cyan"],
142
+ emptyMessage: t("dashboards.analytics.widgets.ordersByStatus.empty", "No orders for this period")
143
+ }
144
+ ) })
145
+ ] });
146
+ };
147
+ var widget_client_default = OrdersByStatusWidget;
148
+ export {
149
+ widget_client_default as default
150
+ };
151
+ //# sourceMappingURL=widget.client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { PieChart, type PieChartDataItem } from '@open-mercato/ui/backend/charts'\nimport {\n DateRangeSelect,\n InlineDateRangeSelect,\n type DateRangePreset,\n} from '@open-mercato/ui/backend/date-range'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersByStatusSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\n\nasync function fetchOrdersByStatusData(settings: OrdersByStatusSettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'id',\n aggregate: 'count',\n },\n groupBy: {\n field: 'status',\n },\n dateRange: {\n field: 'placedAt',\n preset: settings.dateRange,\n },\n }\n\n const call = await apiCall<WidgetDataResponse>('/api/dashboards/widgets/data', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n\n if (!call.ok) {\n const errorMsg = (call.result as Record<string, unknown>)?.error\n throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch orders by status data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nconst ORDER_STATUS_KEYS: Record<string, string> = {\n draft: 'dashboards.analytics.orderStatus.draft',\n pending: 'dashboards.analytics.orderStatus.pending',\n confirmed: 'dashboards.analytics.orderStatus.confirmed',\n processing: 'dashboards.analytics.orderStatus.processing',\n shipped: 'dashboards.analytics.orderStatus.shipped',\n delivered: 'dashboards.analytics.orderStatus.delivered',\n cancelled: 'dashboards.analytics.orderStatus.cancelled',\n}\n\nfunction formatStatusLabel(status: string | null, t: (key: string, fallback: string) => string): string {\n if (!status) return t('dashboards.analytics.labels.unknown', 'Unknown')\n const key = ORDER_STATUS_KEYS[status.toLowerCase()]\n if (key) {\n return t(key, status.replace(/_/g, ' ').replace(/\\b\\w/g, (l) => l.toUpperCase()))\n }\n return status.replace(/_/g, ' ').replace(/\\b\\w/g, (l) => l.toUpperCase())\n}\n\nconst OrdersByStatusWidget: React.FC<DashboardWidgetComponentProps<OrdersByStatusSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateSettings(settings), [settings])\n const [data, setData] = React.useState<PieChartDataItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchOrdersByStatusData(hydrated)\n const chartData = result.data.map((item) => ({\n name: formatStatusLabel(item.groupKey as string | null, t),\n value: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load orders by status data', err)\n setError(t('dashboards.analytics.widgets.ordersByStatus.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <DateRangeSelect\n id=\"orders-by-status-date-range\"\n label={t('dashboards.analytics.settings.dateRange', 'Date Range')}\n value={hydrated.dateRange}\n onChange={(dateRange: DateRangePreset) => onSettingsChange({ ...hydrated, dateRange })}\n />\n <div className=\"space-y-1.5\">\n <label\n htmlFor=\"orders-by-status-variant\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.chartVariant', 'Chart Style')}\n </label>\n <select\n id=\"orders-by-status-variant\"\n className=\"w-full rounded-md border bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\n value={hydrated.variant}\n onChange={(e) => onSettingsChange({ ...hydrated, variant: e.target.value as 'pie' | 'donut' })}\n >\n <option value=\"donut\">{t('dashboards.analytics.chartVariant.donut', 'Donut')}</option>\n <option value=\"pie\">{t('dashboards.analytics.chartVariant.pie', 'Pie')}</option>\n </select>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"flex justify-end mb-2\">\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange })}\n />\n </div>\n <div className=\"flex-1 min-h-0\">\n <PieChart\n data={data}\n loading={loading}\n error={error}\n variant={hydrated.variant}\n colors={['blue', 'emerald', 'amber', 'rose', 'violet', 'cyan']}\n emptyMessage={t('dashboards.analytics.widgets.ordersByStatus.empty', 'No orders for this period')}\n />\n </div>\n </div>\n )\n}\n\nexport default OrdersByStatusWidget\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ import OrdersByStatusWidget from "./widget.client.js";
2
+ import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
3
+ const widget = {
4
+ metadata: {
5
+ id: "dashboards.analytics.ordersByStatus",
6
+ title: "Orders by Status",
7
+ description: "Distribution of orders by status",
8
+ features: ["analytics.view", "sales.orders.view"],
9
+ defaultSize: "sm",
10
+ defaultEnabled: false,
11
+ defaultSettings: DEFAULT_SETTINGS,
12
+ tags: ["analytics", "sales", "chart"],
13
+ category: "analytics",
14
+ icon: "pie-chart",
15
+ supportsRefresh: true
16
+ },
17
+ Widget: OrdersByStatusWidget,
18
+ hydrateSettings,
19
+ dehydrateSettings: (s) => ({ dateRange: s.dateRange, variant: s.variant })
20
+ };
21
+ var widget_default = widget;
22
+ export {
23
+ widget_default as default
24
+ };
25
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts"],
4
+ "sourcesContent": ["import type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport OrdersByStatusWidget from './widget.client'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersByStatusSettings } from './config'\n\nconst widget: DashboardWidgetModule<OrdersByStatusSettings> = {\n metadata: {\n id: 'dashboards.analytics.ordersByStatus',\n title: 'Orders by Status',\n description: 'Distribution of orders by status',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'chart'],\n category: 'analytics',\n icon: 'pie-chart',\n supportsRefresh: true,\n },\n Widget: OrdersByStatusWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, variant: s.variant }),\n}\n\nexport default widget\n"],
5
+ "mappings": "AACA,OAAO,0BAA0B;AACjC,SAAS,kBAAkB,uBAAoD;AAE/E,MAAM,SAAwD;AAAA,EAC5D,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,OAAO;AAAA,IACpC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,SAAS,EAAE,QAAQ;AAC1E;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,18 @@
1
+ import { isValidDateRangePreset } from "@open-mercato/ui/backend/date-range";
2
+ const DEFAULT_SETTINGS = {
3
+ dateRange: "this_month",
4
+ showComparison: true
5
+ };
6
+ function hydrateSettings(raw) {
7
+ if (!raw || typeof raw !== "object") return { ...DEFAULT_SETTINGS };
8
+ const obj = raw;
9
+ return {
10
+ dateRange: isValidDateRangePreset(obj.dateRange) ? obj.dateRange : DEFAULT_SETTINGS.dateRange,
11
+ showComparison: typeof obj.showComparison === "boolean" ? obj.showComparison : DEFAULT_SETTINGS.showComparison
12
+ };
13
+ }
14
+ export {
15
+ DEFAULT_SETTINGS,
16
+ hydrateSettings
17
+ };
18
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-kpi/config.ts"],
4
+ "sourcesContent": ["import { type DateRangePreset, isValidDateRangePreset } from '@open-mercato/ui/backend/date-range'\n\nexport type OrdersKpiSettings = {\n dateRange: DateRangePreset\n showComparison: boolean\n}\n\nexport const DEFAULT_SETTINGS: OrdersKpiSettings = {\n dateRange: 'this_month',\n showComparison: true,\n}\n\nexport function hydrateSettings(raw: unknown): OrdersKpiSettings {\n if (!raw || typeof raw !== 'object') return { ...DEFAULT_SETTINGS }\n const obj = raw as Record<string, unknown>\n return {\n dateRange: isValidDateRangePreset(obj.dateRange) ? obj.dateRange : DEFAULT_SETTINGS.dateRange,\n showComparison: typeof obj.showComparison === 'boolean' ? obj.showComparison : DEFAULT_SETTINGS.showComparison,\n }\n}\n"],
5
+ "mappings": "AAAA,SAA+B,8BAA8B;AAOtD,MAAM,mBAAsC;AAAA,EACjD,WAAW;AAAA,EACX,gBAAgB;AAClB;AAEO,SAAS,gBAAgB,KAAiC;AAC/D,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,EAAE,GAAG,iBAAiB;AAClE,QAAM,MAAM;AACZ,SAAO;AAAA,IACL,WAAW,uBAAuB,IAAI,SAAS,IAAI,IAAI,YAAY,iBAAiB;AAAA,IACpF,gBAAgB,OAAO,IAAI,mBAAmB,YAAY,IAAI,iBAAiB,iBAAiB;AAAA,EAClG;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,126 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { KpiCard } from "@open-mercato/ui/backend/charts";
7
+ import {
8
+ DateRangeSelect,
9
+ InlineDateRangeSelect,
10
+ getComparisonLabelKey
11
+ } from "@open-mercato/ui/backend/date-range";
12
+ import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
13
+ async function fetchOrdersData(settings) {
14
+ const body = {
15
+ entityType: "sales:orders",
16
+ metric: {
17
+ field: "id",
18
+ aggregate: "count"
19
+ },
20
+ dateRange: {
21
+ field: "placedAt",
22
+ preset: settings.dateRange
23
+ },
24
+ comparison: settings.showComparison ? { type: "previous_period" } : void 0
25
+ };
26
+ const call = await apiCall("/api/dashboards/widgets/data", {
27
+ method: "POST",
28
+ headers: { "Content-Type": "application/json" },
29
+ body: JSON.stringify(body)
30
+ });
31
+ if (!call.ok) {
32
+ const errorMsg = call.result?.error;
33
+ throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch orders data");
34
+ }
35
+ return call.result;
36
+ }
37
+ const OrdersKpiWidget = ({
38
+ mode,
39
+ settings = DEFAULT_SETTINGS,
40
+ onSettingsChange,
41
+ refreshToken,
42
+ onRefreshStateChange
43
+ }) => {
44
+ const t = useT();
45
+ const hydrated = React.useMemo(() => hydrateSettings(settings), [settings]);
46
+ const [value, setValue] = React.useState(null);
47
+ const [trend, setTrend] = React.useState(void 0);
48
+ const [loading, setLoading] = React.useState(true);
49
+ const [error, setError] = React.useState(null);
50
+ const refresh = React.useCallback(async () => {
51
+ onRefreshStateChange?.(true);
52
+ setLoading(true);
53
+ setError(null);
54
+ try {
55
+ const data = await fetchOrdersData(hydrated);
56
+ setValue(data.value);
57
+ if (data.comparison) {
58
+ setTrend({
59
+ value: data.comparison.change,
60
+ direction: data.comparison.direction
61
+ });
62
+ } else {
63
+ setTrend(void 0);
64
+ }
65
+ } catch (err) {
66
+ console.error("Failed to load orders KPI data", err);
67
+ setError(t("dashboards.analytics.widgets.ordersKpi.error", "Failed to load data"));
68
+ } finally {
69
+ setLoading(false);
70
+ onRefreshStateChange?.(false);
71
+ }
72
+ }, [hydrated, onRefreshStateChange, t]);
73
+ React.useEffect(() => {
74
+ refresh().catch(() => {
75
+ });
76
+ }, [refresh, refreshToken]);
77
+ if (mode === "settings") {
78
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 text-sm", children: [
79
+ /* @__PURE__ */ jsx(
80
+ DateRangeSelect,
81
+ {
82
+ id: "orders-kpi-date-range",
83
+ label: t("dashboards.analytics.settings.dateRange", "Date Range"),
84
+ value: hydrated.dateRange,
85
+ onChange: (dateRange) => onSettingsChange({ ...hydrated, dateRange })
86
+ }
87
+ ),
88
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
89
+ /* @__PURE__ */ jsx(
90
+ "input",
91
+ {
92
+ type: "checkbox",
93
+ checked: hydrated.showComparison,
94
+ onChange: (e) => onSettingsChange({ ...hydrated, showComparison: e.target.checked }),
95
+ className: "h-4 w-4 rounded border focus:ring-primary"
96
+ }
97
+ ),
98
+ t("dashboards.analytics.settings.showComparison", "Show comparison")
99
+ ] }) })
100
+ ] });
101
+ }
102
+ const comparisonLabelInfo = getComparisonLabelKey(hydrated.dateRange);
103
+ const comparisonLabel = hydrated.showComparison ? t(comparisonLabelInfo.key, comparisonLabelInfo.fallback) : void 0;
104
+ return /* @__PURE__ */ jsx(
105
+ KpiCard,
106
+ {
107
+ value,
108
+ trend,
109
+ comparisonLabel,
110
+ loading,
111
+ error,
112
+ headerAction: /* @__PURE__ */ jsx(
113
+ InlineDateRangeSelect,
114
+ {
115
+ value: hydrated.dateRange,
116
+ onChange: (dateRange) => onSettingsChange({ ...hydrated, dateRange })
117
+ }
118
+ )
119
+ }
120
+ );
121
+ };
122
+ var widget_client_default = OrdersKpiWidget;
123
+ export {
124
+ widget_client_default as default
125
+ };
126
+ //# sourceMappingURL=widget.client.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { KpiCard, type KpiTrend } from '@open-mercato/ui/backend/charts'\nimport {\n DateRangeSelect,\n InlineDateRangeSelect,\n type DateRangePreset,\n getComparisonLabelKey,\n} from '@open-mercato/ui/backend/date-range'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersKpiSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\n\nasync function fetchOrdersData(settings: OrdersKpiSettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'id',\n aggregate: 'count',\n },\n dateRange: {\n field: 'placedAt',\n preset: settings.dateRange,\n },\n comparison: settings.showComparison ? { type: 'previous_period' } : undefined,\n }\n\n const call = await apiCall<WidgetDataResponse>('/api/dashboards/widgets/data', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(body),\n })\n\n if (!call.ok) {\n const errorMsg = (call.result as Record<string, unknown>)?.error\n throw new Error(typeof errorMsg === 'string' ? errorMsg : 'Failed to fetch orders data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nconst OrdersKpiWidget: React.FC<DashboardWidgetComponentProps<OrdersKpiSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const hydrated = React.useMemo(() => hydrateSettings(settings), [settings])\n const [value, setValue] = React.useState<number | null>(null)\n const [trend, setTrend] = React.useState<KpiTrend | undefined>(undefined)\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await fetchOrdersData(hydrated)\n setValue(data.value)\n if (data.comparison) {\n setTrend({\n value: data.comparison.change,\n direction: data.comparison.direction,\n })\n } else {\n setTrend(undefined)\n }\n } catch (err) {\n console.error('Failed to load orders KPI data', err)\n setError(t('dashboards.analytics.widgets.ordersKpi.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, onRefreshStateChange, t])\n\n React.useEffect(() => {\n refresh().catch(() => {})\n }, [refresh, refreshToken])\n\n if (mode === 'settings') {\n return (\n <div className=\"space-y-4 text-sm\">\n <DateRangeSelect\n id=\"orders-kpi-date-range\"\n label={t('dashboards.analytics.settings.dateRange', 'Date Range')}\n value={hydrated.dateRange}\n onChange={(dateRange: DateRangePreset) => onSettingsChange({ ...hydrated, dateRange })}\n />\n <div className=\"space-y-1.5\">\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={hydrated.showComparison}\n onChange={(e) => onSettingsChange({ ...hydrated, showComparison: e.target.checked })}\n className=\"h-4 w-4 rounded border focus:ring-primary\"\n />\n {t('dashboards.analytics.settings.showComparison', 'Show comparison')}\n </label>\n </div>\n </div>\n )\n }\n\n const comparisonLabelInfo = getComparisonLabelKey(hydrated.dateRange)\n const comparisonLabel = hydrated.showComparison\n ? t(comparisonLabelInfo.key, comparisonLabelInfo.fallback)\n : undefined\n\n return (\n <KpiCard\n value={value}\n trend={trend}\n comparisonLabel={comparisonLabel}\n loading={loading}\n error={error}\n headerAction={\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange })}\n />\n }\n />\n )\n}\n\nexport default OrdersKpiWidget\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,25 @@
1
+ import OrdersKpiWidget from "./widget.client.js";
2
+ import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
3
+ const widget = {
4
+ metadata: {
5
+ id: "dashboards.analytics.ordersKpi",
6
+ title: "Orders",
7
+ description: "Total order count with period comparison",
8
+ features: ["analytics.view", "sales.orders.view"],
9
+ defaultSize: "sm",
10
+ defaultEnabled: false,
11
+ defaultSettings: DEFAULT_SETTINGS,
12
+ tags: ["analytics", "sales", "kpi"],
13
+ category: "analytics",
14
+ icon: "shopping-cart",
15
+ supportsRefresh: true
16
+ },
17
+ Widget: OrdersKpiWidget,
18
+ hydrateSettings,
19
+ dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison })
20
+ };
21
+ var widget_default = widget;
22
+ export {
23
+ widget_default as default
24
+ };
25
+ //# sourceMappingURL=widget.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts"],
4
+ "sourcesContent": ["import type { DashboardWidgetModule } from '@open-mercato/shared/modules/dashboard/widgets'\nimport OrdersKpiWidget from './widget.client'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersKpiSettings } from './config'\n\nconst widget: DashboardWidgetModule<OrdersKpiSettings> = {\n metadata: {\n id: 'dashboards.analytics.ordersKpi',\n title: 'Orders',\n description: 'Total order count with period comparison',\n features: ['analytics.view', 'sales.orders.view'],\n defaultSize: 'sm',\n defaultEnabled: false,\n defaultSettings: DEFAULT_SETTINGS,\n tags: ['analytics', 'sales', 'kpi'],\n category: 'analytics',\n icon: 'shopping-cart',\n supportsRefresh: true,\n },\n Widget: OrdersKpiWidget,\n hydrateSettings,\n dehydrateSettings: (s) => ({ dateRange: s.dateRange, showComparison: s.showComparison }),\n}\n\nexport default widget\n"],
5
+ "mappings": "AACA,OAAO,qBAAqB;AAC5B,SAAS,kBAAkB,uBAA+C;AAE1E,MAAM,SAAmD;AAAA,EACvD,UAAU;AAAA,IACR,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,UAAU,CAAC,kBAAkB,mBAAmB;AAAA,IAChD,aAAa;AAAA,IACb,gBAAgB;AAAA,IAChB,iBAAiB;AAAA,IACjB,MAAM,CAAC,aAAa,SAAS,KAAK;AAAA,IAClC,UAAU;AAAA,IACV,MAAM;AAAA,IACN,iBAAiB;AAAA,EACnB;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,EACA,mBAAmB,CAAC,OAAO,EAAE,WAAW,EAAE,WAAW,gBAAgB,EAAE,eAAe;AACxF;AAEA,IAAO,iBAAQ;",
6
+ "names": []
7
+ }
@@ -0,0 +1,16 @@
1
+ import { isValidDateRangePreset } from "@open-mercato/ui/backend/date-range";
2
+ const DEFAULT_SETTINGS = {
3
+ dateRange: "this_month"
4
+ };
5
+ function hydrateSettings(raw) {
6
+ if (!raw || typeof raw !== "object") return { ...DEFAULT_SETTINGS };
7
+ const obj = raw;
8
+ return {
9
+ dateRange: isValidDateRangePreset(obj.dateRange) ? obj.dateRange : DEFAULT_SETTINGS.dateRange
10
+ };
11
+ }
12
+ export {
13
+ DEFAULT_SETTINGS,
14
+ hydrateSettings
15
+ };
16
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts"],
4
+ "sourcesContent": ["import { type DateRangePreset, isValidDateRangePreset } from '@open-mercato/ui/backend/date-range'\n\nexport type PipelineSummarySettings = {\n dateRange: DateRangePreset\n}\n\nexport const DEFAULT_SETTINGS: PipelineSummarySettings = {\n dateRange: 'this_month',\n}\n\nexport function hydrateSettings(raw: unknown): PipelineSummarySettings {\n if (!raw || typeof raw !== 'object') return { ...DEFAULT_SETTINGS }\n const obj = raw as Record<string, unknown>\n return {\n dateRange: isValidDateRangePreset(obj.dateRange) ? obj.dateRange : DEFAULT_SETTINGS.dateRange,\n }\n}\n"],
5
+ "mappings": "AAAA,SAA+B,8BAA8B;AAMtD,MAAM,mBAA4C;AAAA,EACvD,WAAW;AACb;AAEO,SAAS,gBAAgB,KAAuC;AACrE,MAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO,EAAE,GAAG,iBAAiB;AAClE,QAAM,MAAM;AACZ,SAAO;AAAA,IACL,WAAW,uBAAuB,IAAI,SAAS,IAAI,IAAI,YAAY,iBAAiB;AAAA,EACtF;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,123 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ import { BarChart } from "@open-mercato/ui/backend/charts";
7
+ import {
8
+ DateRangeSelect,
9
+ InlineDateRangeSelect
10
+ } from "@open-mercato/ui/backend/date-range";
11
+ import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
12
+ import { formatCurrencyCompact } from "../../../lib/formatters.js";
13
+ async function fetchPipelineData(settings) {
14
+ const body = {
15
+ entityType: "customers:deals",
16
+ metric: {
17
+ field: "valueAmount",
18
+ aggregate: "sum"
19
+ },
20
+ groupBy: {
21
+ field: "pipelineStage",
22
+ resolveLabels: true
23
+ },
24
+ dateRange: {
25
+ field: "createdAt",
26
+ preset: settings.dateRange
27
+ }
28
+ };
29
+ const call = await apiCall("/api/dashboards/widgets/data", {
30
+ method: "POST",
31
+ headers: { "Content-Type": "application/json" },
32
+ body: JSON.stringify(body)
33
+ });
34
+ if (!call.ok) {
35
+ const errorMsg = call.result?.error;
36
+ throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch pipeline data");
37
+ }
38
+ return call.result;
39
+ }
40
+ function formatStageLabel(stage, t) {
41
+ if (stage == null || stage === "") return t("dashboards.analytics.labels.unknown", "Unknown");
42
+ const stageStr = String(stage);
43
+ if (stageStr === "0" || stageStr === "null" || stageStr === "undefined") {
44
+ return t("dashboards.analytics.labels.unknown", "Unknown");
45
+ }
46
+ return stageStr.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase());
47
+ }
48
+ const PipelineSummaryWidget = ({
49
+ mode,
50
+ settings = DEFAULT_SETTINGS,
51
+ onSettingsChange,
52
+ refreshToken,
53
+ onRefreshStateChange
54
+ }) => {
55
+ const t = useT();
56
+ const hydrated = React.useMemo(() => hydrateSettings(settings), [settings]);
57
+ const [data, setData] = React.useState([]);
58
+ const [loading, setLoading] = React.useState(true);
59
+ const [error, setError] = React.useState(null);
60
+ const refresh = React.useCallback(async () => {
61
+ onRefreshStateChange?.(true);
62
+ setLoading(true);
63
+ setError(null);
64
+ try {
65
+ const result = await fetchPipelineData(hydrated);
66
+ const chartData = result.data.filter((item) => item.groupKey != null && item.groupKey !== "" && String(item.groupKey) !== "0").map((item) => ({
67
+ stage: formatStageLabel(item.groupLabel ?? item.groupKey, t),
68
+ Value: item.value ?? 0
69
+ }));
70
+ setData(chartData);
71
+ } catch (err) {
72
+ console.error("Failed to load pipeline data", err);
73
+ setError(t("dashboards.analytics.widgets.pipelineSummary.error", "Failed to load data"));
74
+ } finally {
75
+ setLoading(false);
76
+ onRefreshStateChange?.(false);
77
+ }
78
+ }, [hydrated, onRefreshStateChange, t]);
79
+ React.useEffect(() => {
80
+ refresh().catch(() => {
81
+ });
82
+ }, [refresh, refreshToken]);
83
+ if (mode === "settings") {
84
+ return /* @__PURE__ */ jsx("div", { className: "space-y-4 text-sm", children: /* @__PURE__ */ jsx(
85
+ DateRangeSelect,
86
+ {
87
+ id: "pipeline-summary-date-range",
88
+ label: t("dashboards.analytics.settings.dateRange", "Date Range"),
89
+ value: hydrated.dateRange,
90
+ onChange: (dateRange) => onSettingsChange({ ...hydrated, dateRange })
91
+ }
92
+ ) });
93
+ }
94
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col h-full", children: [
95
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end mb-2", children: /* @__PURE__ */ jsx(
96
+ InlineDateRangeSelect,
97
+ {
98
+ value: hydrated.dateRange,
99
+ onChange: (dateRange) => onSettingsChange({ ...hydrated, dateRange })
100
+ }
101
+ ) }),
102
+ /* @__PURE__ */ jsx("div", { className: "flex-1 min-h-0", children: /* @__PURE__ */ jsx(
103
+ BarChart,
104
+ {
105
+ data,
106
+ index: "stage",
107
+ categories: ["Value"],
108
+ categoryLabels: { Value: t("dashboards.analytics.labels.value", "Value") },
109
+ loading,
110
+ error,
111
+ valueFormatter: formatCurrencyCompact,
112
+ colors: ["violet"],
113
+ showLegend: false,
114
+ emptyMessage: t("dashboards.analytics.widgets.pipelineSummary.empty", "No deal data for this period")
115
+ }
116
+ ) })
117
+ ] });
118
+ };
119
+ var widget_client_default = PipelineSummaryWidget;
120
+ export {
121
+ widget_client_default as default
122
+ };
123
+ //# sourceMappingURL=widget.client.js.map