@open-mercato/core 0.4.2-canary-10c7a8bf2a → 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.
- package/dist/modules/auth/lib/setup-app.js +2 -0
- package/dist/modules/auth/lib/setup-app.js.map +2 -2
- package/dist/modules/catalog/analytics.js +27 -0
- package/dist/modules/catalog/analytics.js.map +7 -0
- package/dist/modules/customers/analytics.js +50 -0
- package/dist/modules/customers/analytics.js.map +7 -0
- package/dist/modules/dashboards/acl.js +2 -1
- package/dist/modules/dashboards/acl.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/route.js +187 -0
- package/dist/modules/dashboards/api/widgets/data/route.js.map +7 -0
- package/dist/modules/dashboards/cli.js +142 -1
- package/dist/modules/dashboards/cli.js.map +2 -2
- package/dist/modules/dashboards/di.js +11 -0
- package/dist/modules/dashboards/di.js.map +7 -0
- package/dist/modules/dashboards/lib/aggregations.js +162 -0
- package/dist/modules/dashboards/lib/aggregations.js.map +7 -0
- package/dist/modules/dashboards/lib/formatters.js +34 -0
- package/dist/modules/dashboards/lib/formatters.js.map +7 -0
- package/dist/modules/dashboards/seed/analytics.js +383 -0
- package/dist/modules/dashboards/seed/analytics.js.map +7 -0
- package/dist/modules/dashboards/services/analyticsRegistry.js +52 -0
- package/dist/modules/dashboards/services/analyticsRegistry.js.map +7 -0
- package/dist/modules/dashboards/services/widgetDataService.js +207 -0
- package/dist/modules/dashboards/services/widgetDataService.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +128 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +126 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +151 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +126 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js +16 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +123 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js +18 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +128 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js +21 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +211 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js +19 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +131 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js +19 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +153 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/config.js +22 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/config.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +180 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js +25 -0
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.js.map +7 -0
- package/dist/modules/sales/analytics.js +67 -0
- package/dist/modules/sales/analytics.js.map +7 -0
- package/package.json +2 -2
- package/src/modules/auth/lib/setup-app.ts +2 -0
- package/src/modules/catalog/analytics.ts +24 -0
- package/src/modules/customers/analytics.ts +47 -0
- package/src/modules/dashboards/acl.ts +1 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +221 -0
- package/src/modules/dashboards/cli.ts +164 -1
- package/src/modules/dashboards/di.ts +9 -0
- package/src/modules/dashboards/i18n/de.json +115 -1
- package/src/modules/dashboards/i18n/en.json +115 -1
- package/src/modules/dashboards/i18n/es.json +115 -1
- package/src/modules/dashboards/i18n/pl.json +115 -1
- package/src/modules/dashboards/lib/__tests__/aggregations.test.ts +327 -0
- package/src/modules/dashboards/lib/__tests__/formatters.test.ts +128 -0
- package/src/modules/dashboards/lib/aggregations.ts +225 -0
- package/src/modules/dashboards/lib/formatters.ts +36 -0
- package/src/modules/dashboards/seed/analytics.ts +405 -0
- package/src/modules/dashboards/services/analyticsRegistry.ts +79 -0
- package/src/modules/dashboards/services/widgetDataService.ts +329 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +135 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +133 -0
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +154 -0
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +133 -0
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/config.ts +17 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +137 -0
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/config.ts +20 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +135 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/config.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +220 -0
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/config.ts +21 -0
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +131 -0
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/top-customers/config.ts +21 -0
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +161 -0
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.ts +24 -0
- package/src/modules/dashboards/widgets/dashboard/top-products/config.ts +27 -0
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +181 -0
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.ts +24 -0
- 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
|