@open-mercato/core 0.6.4-develop.4113.1.5e87922616 → 0.6.4-develop.4133.1.48fc6c8f7b

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 (53) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/auth/lib/sessionIntegrity.js +16 -13
  3. package/dist/modules/auth/lib/sessionIntegrity.js.map +2 -2
  4. package/dist/modules/customers/api/utils.js +14 -9
  5. package/dist/modules/customers/api/utils.js.map +2 -2
  6. package/dist/modules/dashboards/api/widgets/data/batch/route.js +137 -0
  7. package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +7 -0
  8. package/dist/modules/dashboards/api/widgets/data/route.js +1 -75
  9. package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
  10. package/dist/modules/dashboards/api/widgets/data/schema.js +85 -0
  11. package/dist/modules/dashboards/api/widgets/data/schema.js.map +7 -0
  12. package/dist/modules/dashboards/lib/widgetDataBatch.js +49 -0
  13. package/dist/modules/dashboards/lib/widgetDataBatch.js.map +7 -0
  14. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +6 -14
  15. package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +2 -2
  16. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +6 -14
  17. package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +2 -2
  18. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +6 -14
  19. package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +2 -2
  20. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +6 -14
  21. package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +2 -2
  22. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +6 -14
  23. package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +2 -2
  24. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +6 -14
  25. package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +2 -2
  26. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +6 -14
  27. package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
  28. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +6 -14
  29. package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
  30. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +6 -14
  31. package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
  32. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +6 -14
  33. package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
  34. package/dist/modules/directory/utils/organizationScope.js +33 -20
  35. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  36. package/package.json +7 -7
  37. package/src/modules/auth/lib/sessionIntegrity.ts +37 -16
  38. package/src/modules/customers/api/utils.ts +17 -11
  39. package/src/modules/dashboards/api/widgets/data/batch/route.ts +168 -0
  40. package/src/modules/dashboards/api/widgets/data/route.ts +1 -90
  41. package/src/modules/dashboards/api/widgets/data/schema.ts +90 -0
  42. package/src/modules/dashboards/lib/widgetDataBatch.ts +89 -0
  43. package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +6 -16
  44. package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +6 -16
  45. package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +6 -16
  46. package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +6 -16
  47. package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +6 -16
  48. package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +6 -16
  49. package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +6 -16
  50. package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +6 -16
  51. package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +6 -16
  52. package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +6 -16
  53. package/src/modules/directory/utils/organizationScope.ts +51 -20
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { BarChart } from "@open-mercato/ui/backend/charts";
7
7
  import {
@@ -10,7 +10,7 @@ import {
10
10
  } from "@open-mercato/ui/backend/date-range";
11
11
  import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
12
12
  import { formatCurrencyCompact } from "../../../lib/formatters.js";
13
- async function fetchPipelineData(settings) {
13
+ async function fetchPipelineData(settings, fetchWidgetData) {
14
14
  const body = {
15
15
  entityType: "customers:deals",
16
16
  metric: {
@@ -26,16 +26,7 @@ async function fetchPipelineData(settings) {
26
26
  preset: settings.dateRange
27
27
  }
28
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;
29
+ return fetchWidgetData(body);
39
30
  }
40
31
  function formatStageLabel(stage, t) {
41
32
  if (stage == null || stage === "") return t("dashboards.analytics.labels.unknown", "Unknown");
@@ -57,12 +48,13 @@ const PipelineSummaryWidget = ({
57
48
  const [data, setData] = React.useState([]);
58
49
  const [loading, setLoading] = React.useState(true);
59
50
  const [error, setError] = React.useState(null);
51
+ const fetchWidgetData = useWidgetData();
60
52
  const refresh = React.useCallback(async () => {
61
53
  onRefreshStateChange?.(true);
62
54
  setLoading(true);
63
55
  setError(null);
64
56
  try {
65
- const result = await fetchPipelineData(hydrated);
57
+ const result = await fetchPipelineData(hydrated, fetchWidgetData);
66
58
  const chartData = result.data.filter((item) => item.groupKey != null && item.groupKey !== "" && String(item.groupKey) !== "0").map((item) => ({
67
59
  stage: formatStageLabel(item.groupLabel ?? item.groupKey, t),
68
60
  Value: item.value ?? 0
@@ -75,7 +67,7 @@ const PipelineSummaryWidget = ({
75
67
  setLoading(false);
76
68
  onRefreshStateChange?.(false);
77
69
  }
78
- }, [hydrated, onRefreshStateChange, t]);
70
+ }, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
79
71
  React.useEffect(() => {
80
72
  refresh().catch(() => {
81
73
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/pipeline-summary/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 { BarChart, type BarChartDataItem } 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 PipelineSummarySettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyCompact } from '../../../lib/formatters'\n\nasync function fetchPipelineData(settings: PipelineSummarySettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'customers:deals',\n metric: {\n field: 'valueAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'pipelineStage',\n resolveLabels: true,\n },\n dateRange: {\n field: 'createdAt',\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 pipeline data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nfunction formatStageLabel(stage: unknown, t: (key: string, fallback: string) => string): string {\n if (stage == null || stage === '') return t('dashboards.analytics.labels.unknown', 'Unknown')\n const stageStr = String(stage)\n if (stageStr === '0' || stageStr === 'null' || stageStr === 'undefined') {\n return t('dashboards.analytics.labels.unknown', 'Unknown')\n }\n return stageStr\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (l) => l.toUpperCase())\n}\n\nconst PipelineSummaryWidget: React.FC<DashboardWidgetComponentProps<PipelineSummarySettings>> = ({\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<BarChartDataItem[]>([])\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 fetchPipelineData(hydrated)\n const chartData = result.data\n .filter((item) => item.groupKey != null && item.groupKey !== '' && String(item.groupKey) !== '0')\n .map((item) => ({\n stage: formatStageLabel(item.groupLabel ?? item.groupKey, t),\n Value: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load pipeline data', err)\n setError(t('dashboards.analytics.widgets.pipelineSummary.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=\"pipeline-summary-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>\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 <BarChart\n data={data}\n index=\"stage\"\n categories={['Value']}\n categoryLabels={{ Value: t('dashboards.analytics.labels.value', 'Value') }}\n loading={loading}\n error={error}\n valueFormatter={formatCurrencyCompact}\n colors={['violet']}\n showLegend={false}\n emptyMessage={t('dashboards.analytics.widgets.pipelineSummary.empty', 'No deal data for this period')}\n />\n </div>\n </div>\n )\n}\n\nexport default PipelineSummaryWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { BarChart, type BarChartDataItem } 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 PipelineSummarySettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyCompact } from '../../../lib/formatters'\n\nasync function fetchPipelineData(settings: PipelineSummarySettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'customers:deals',\n metric: {\n field: 'valueAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'pipelineStage',\n resolveLabels: true,\n },\n dateRange: {\n field: 'createdAt',\n preset: settings.dateRange,\n },\n }\n\n return fetchWidgetData<WidgetDataResponse>(body)\n}\n\nfunction formatStageLabel(stage: unknown, t: (key: string, fallback: string) => string): string {\n if (stage == null || stage === '') return t('dashboards.analytics.labels.unknown', 'Unknown')\n const stageStr = String(stage)\n if (stageStr === '0' || stageStr === 'null' || stageStr === 'undefined') {\n return t('dashboards.analytics.labels.unknown', 'Unknown')\n }\n return stageStr\n .replace(/_/g, ' ')\n .replace(/\\b\\w/g, (l) => l.toUpperCase())\n}\n\nconst PipelineSummaryWidget: React.FC<DashboardWidgetComponentProps<PipelineSummarySettings>> = ({\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<BarChartDataItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchPipelineData(hydrated, fetchWidgetData)\n const chartData = result.data\n .filter((item) => item.groupKey != null && item.groupKey !== '' && String(item.groupKey) !== '0')\n .map((item) => ({\n stage: formatStageLabel(item.groupLabel ?? item.groupKey, t),\n Value: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load pipeline data', err)\n setError(t('dashboards.analytics.widgets.pipelineSummary.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, fetchWidgetData, 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=\"pipeline-summary-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>\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 <BarChart\n data={data}\n index=\"stage\"\n categories={['Value']}\n categoryLabels={{ Value: t('dashboards.analytics.labels.value', 'Value') }}\n loading={loading}\n error={error}\n valueFormatter={formatCurrencyCompact}\n colors={['violet']}\n showLegend={false}\n emptyMessage={t('dashboards.analytics.widgets.pipelineSummary.empty', 'No deal data for this period')}\n />\n </div>\n </div>\n )\n}\n\nexport default PipelineSummaryWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { KpiCard } from "@open-mercato/ui/backend/charts";
7
7
  import {
@@ -11,7 +11,7 @@ import {
11
11
  } from "@open-mercato/ui/backend/date-range";
12
12
  import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
13
13
  import { formatCurrency } from "../../../lib/formatters.js";
14
- async function fetchRevenueData(settings) {
14
+ async function fetchRevenueData(settings, fetchWidgetData) {
15
15
  const body = {
16
16
  entityType: "sales:orders",
17
17
  metric: {
@@ -24,16 +24,7 @@ async function fetchRevenueData(settings) {
24
24
  },
25
25
  comparison: settings.showComparison ? { type: "previous_period" } : void 0
26
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 revenue data");
35
- }
36
- return call.result;
27
+ return fetchWidgetData(body);
37
28
  }
38
29
  const RevenueKpiWidget = ({
39
30
  mode,
@@ -48,12 +39,13 @@ const RevenueKpiWidget = ({
48
39
  const [trend, setTrend] = React.useState(void 0);
49
40
  const [loading, setLoading] = React.useState(true);
50
41
  const [error, setError] = React.useState(null);
42
+ const fetchWidgetData = useWidgetData();
51
43
  const refresh = React.useCallback(async () => {
52
44
  onRefreshStateChange?.(true);
53
45
  setLoading(true);
54
46
  setError(null);
55
47
  try {
56
- const data = await fetchRevenueData(hydrated);
48
+ const data = await fetchRevenueData(hydrated, fetchWidgetData);
57
49
  setValue(data.value);
58
50
  if (data.comparison) {
59
51
  setTrend({
@@ -70,7 +62,7 @@ const RevenueKpiWidget = ({
70
62
  setLoading(false);
71
63
  onRefreshStateChange?.(false);
72
64
  }
73
- }, [hydrated, onRefreshStateChange, t]);
65
+ }, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
74
66
  React.useEffect(() => {
75
67
  refresh().catch(() => {
76
68
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/revenue-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 RevenueKpiSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrency } from '../../../lib/formatters'\n\nasync function fetchRevenueData(settings: RevenueKpiSettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\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 revenue data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nconst RevenueKpiWidget: React.FC<DashboardWidgetComponentProps<RevenueKpiSettings>> = ({\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 fetchRevenueData(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 revenue KPI data', err)\n setError(t('dashboards.analytics.widgets.revenueKpi.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=\"revenue-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-visible:ring-ring\"\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 formatValue={formatCurrency}\n headerAction={\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange })}\n />\n }\n />\n )\n}\n\nexport default RevenueKpiWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'\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 RevenueKpiSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrency } from '../../../lib/formatters'\n\nasync function fetchRevenueData(settings: RevenueKpiSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n dateRange: {\n field: 'placedAt',\n preset: settings.dateRange,\n },\n comparison: settings.showComparison ? { type: 'previous_period' } : undefined,\n }\n\n return fetchWidgetData<WidgetDataResponse>(body)\n}\n\nconst RevenueKpiWidget: React.FC<DashboardWidgetComponentProps<RevenueKpiSettings>> = ({\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 fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await fetchRevenueData(hydrated, fetchWidgetData)\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 revenue KPI data', err)\n setError(t('dashboards.analytics.widgets.revenueKpi.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, fetchWidgetData, 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=\"revenue-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-visible:ring-ring\"\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 formatValue={formatCurrency}\n headerAction={\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange })}\n />\n }\n />\n )\n}\n\nexport default RevenueKpiWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
5
5
  import { useT, useLocale } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { LineChart } from "@open-mercato/ui/backend/charts";
7
7
  import {
@@ -17,7 +17,7 @@ import {
17
17
  } from "@open-mercato/ui/primitives/select";
18
18
  import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
19
19
  import { formatCurrencyCompact } from "../../../lib/formatters.js";
20
- async function fetchRevenueTrendData(settings) {
20
+ async function fetchRevenueTrendData(settings, fetchWidgetData) {
21
21
  const body = {
22
22
  entityType: "sales:orders",
23
23
  metric: {
@@ -33,16 +33,7 @@ async function fetchRevenueTrendData(settings) {
33
33
  preset: settings.dateRange
34
34
  }
35
35
  };
36
- const call = await apiCall("/api/dashboards/widgets/data", {
37
- method: "POST",
38
- headers: { "Content-Type": "application/json" },
39
- body: JSON.stringify(body)
40
- });
41
- if (!call.ok) {
42
- const errorMsg = call.result?.error;
43
- throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch revenue trend data");
44
- }
45
- return call.result;
36
+ return fetchWidgetData(body);
46
37
  }
47
38
  function formatDate(dateStr, granularity, locale) {
48
39
  if (!dateStr) return "--";
@@ -112,12 +103,13 @@ const RevenueTrendWidget = ({
112
103
  const [data, setData] = React.useState([]);
113
104
  const [loading, setLoading] = React.useState(true);
114
105
  const [error, setError] = React.useState(null);
106
+ const fetchWidgetData = useWidgetData();
115
107
  const refresh = React.useCallback(async () => {
116
108
  onRefreshStateChange?.(true);
117
109
  setLoading(true);
118
110
  setError(null);
119
111
  try {
120
- const result = await fetchRevenueTrendData(hydrated);
112
+ const result = await fetchRevenueTrendData(hydrated, fetchWidgetData);
121
113
  const sortedData = [...result.data].sort((a, b) => {
122
114
  const aTime = new Date(a.groupKey || 0).getTime();
123
115
  const bTime = new Date(b.groupKey || 0).getTime();
@@ -135,7 +127,7 @@ const RevenueTrendWidget = ({
135
127
  setLoading(false);
136
128
  onRefreshStateChange?.(false);
137
129
  }
138
- }, [hydrated, locale, onRefreshStateChange, t]);
130
+ }, [hydrated, fetchWidgetData, locale, onRefreshStateChange, t]);
139
131
  React.useEffect(() => {
140
132
  refresh().catch(() => {
141
133
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/revenue-trend/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, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { LineChart, type LineChartDataItem } from '@open-mercato/ui/backend/charts'\nimport {\n DateRangeSelect,\n InlineDateRangeSelect,\n type DateRangePreset,\n} from '@open-mercato/ui/backend/date-range'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport type { DateGranularity } from '@open-mercato/shared/modules/analytics'\nimport { DEFAULT_SETTINGS, hydrateSettings, type RevenueTrendSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyCompact } from '../../../lib/formatters'\n\nasync function fetchRevenueTrendData(settings: RevenueTrendSettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'placedAt',\n granularity: settings.granularity,\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 revenue trend data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nfunction formatDate(dateStr: string | null, granularity: DateGranularity, locale?: string): string {\n if (!dateStr) return '--'\n try {\n const date = new Date(dateStr)\n const localeStr = locale ?? undefined\n switch (granularity) {\n case 'day':\n case 'week':\n return date.toLocaleDateString(localeStr, { month: 'short', day: 'numeric' })\n case 'month':\n return date.toLocaleDateString(localeStr, { month: 'short', year: 'numeric' })\n case 'quarter': {\n const quarter = Math.floor(date.getMonth() / 3) + 1\n return `Q${quarter} ${date.getFullYear()}`\n }\n case 'year':\n return date.toLocaleDateString(localeStr, { year: 'numeric' })\n default:\n return date.toLocaleDateString(localeStr, { month: 'short', day: 'numeric' })\n }\n } catch {\n return String(dateStr)\n }\n}\n\nconst GRANULARITY_OPTIONS: { value: DateGranularity; labelKey: string }[] = [\n { value: 'day', labelKey: 'dashboards.analytics.granularity.day' },\n { value: 'week', labelKey: 'dashboards.analytics.granularity.week' },\n { value: 'month', labelKey: 'dashboards.analytics.granularity.month' },\n { value: 'quarter', labelKey: 'dashboards.analytics.granularity.quarter' },\n { value: 'year', labelKey: 'dashboards.analytics.granularity.year' },\n]\n\nfunction getAutoGranularity(dateRange: DateRangePreset): DateGranularity {\n switch (dateRange) {\n case 'today':\n case 'yesterday':\n case 'last_7_days':\n return 'day'\n case 'this_week':\n case 'last_week':\n case 'last_30_days':\n return 'day'\n case 'this_month':\n case 'last_month':\n case 'last_90_days':\n return 'week'\n case 'this_quarter':\n case 'last_quarter':\n return 'week'\n case 'this_year':\n case 'last_year':\n return 'month'\n default:\n return 'day'\n }\n}\n\nconst RevenueTrendWidget: React.FC<DashboardWidgetComponentProps<RevenueTrendSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const locale = useLocale()\n const hydrated = React.useMemo(() => hydrateSettings(settings), [settings])\n const [data, setData] = React.useState<LineChartDataItem[]>([])\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 fetchRevenueTrendData(hydrated)\n const sortedData = [...result.data].sort((a, b) => {\n const aTime = new Date(a.groupKey as string || 0).getTime()\n const bTime = new Date(b.groupKey as string || 0).getTime()\n return aTime - bTime\n })\n const chartData = sortedData.map((item) => ({\n date: formatDate(item.groupKey as string | null, hydrated.granularity, locale),\n Revenue: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load revenue trend data', err)\n setError(t('dashboards.analytics.widgets.revenueTrend.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, locale, 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=\"revenue-trend-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=\"revenue-trend-granularity\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.granularity', 'Granularity')}\n </label>\n <Select\n value={hydrated.granularity}\n onValueChange={(value) => onSettingsChange({ ...hydrated, granularity: value as DateGranularity })}\n >\n <SelectTrigger id=\"revenue-trend-granularity\" size=\"sm\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {GRANULARITY_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {t(opt.labelKey, opt.value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\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.showArea}\n onChange={(e) => onSettingsChange({ ...hydrated, showArea: e.target.checked })}\n className=\"h-4 w-4 rounded border focus-visible:ring-ring\"\n />\n {t('dashboards.analytics.settings.showArea', 'Show area fill')}\n </label>\n </div>\n </div>\n )\n }\n\n const effectiveGranularity = hydrated.granularity === 'day' ? getAutoGranularity(hydrated.dateRange) : hydrated.granularity\n\n return (\n <div>\n <div className=\"mb-2 flex justify-end\">\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange, granularity: getAutoGranularity(dateRange) })}\n />\n </div>\n <LineChart\n data={data}\n index=\"date\"\n categories={['Revenue']}\n categoryLabels={{ Revenue: t('dashboards.analytics.widgets.topCustomers.column.revenue', 'Revenue') }}\n loading={loading}\n error={error}\n showArea={hydrated.showArea}\n valueFormatter={formatCurrencyCompact}\n colors={['blue']}\n emptyMessage={t('dashboards.analytics.widgets.revenueTrend.empty', 'No revenue data for this period')}\n />\n </div>\n )\n}\n\nexport default RevenueTrendWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'\nimport { useT, useLocale } from '@open-mercato/shared/lib/i18n/context'\nimport { LineChart, type LineChartDataItem } from '@open-mercato/ui/backend/charts'\nimport {\n DateRangeSelect,\n InlineDateRangeSelect,\n type DateRangePreset,\n} from '@open-mercato/ui/backend/date-range'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport type { DateGranularity } from '@open-mercato/shared/modules/analytics'\nimport { DEFAULT_SETTINGS, hydrateSettings, type RevenueTrendSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyCompact } from '../../../lib/formatters'\n\nasync function fetchRevenueTrendData(settings: RevenueTrendSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'placedAt',\n granularity: settings.granularity,\n },\n dateRange: {\n field: 'placedAt',\n preset: settings.dateRange,\n },\n }\n\n return fetchWidgetData<WidgetDataResponse>(body)\n}\n\nfunction formatDate(dateStr: string | null, granularity: DateGranularity, locale?: string): string {\n if (!dateStr) return '--'\n try {\n const date = new Date(dateStr)\n const localeStr = locale ?? undefined\n switch (granularity) {\n case 'day':\n case 'week':\n return date.toLocaleDateString(localeStr, { month: 'short', day: 'numeric' })\n case 'month':\n return date.toLocaleDateString(localeStr, { month: 'short', year: 'numeric' })\n case 'quarter': {\n const quarter = Math.floor(date.getMonth() / 3) + 1\n return `Q${quarter} ${date.getFullYear()}`\n }\n case 'year':\n return date.toLocaleDateString(localeStr, { year: 'numeric' })\n default:\n return date.toLocaleDateString(localeStr, { month: 'short', day: 'numeric' })\n }\n } catch {\n return String(dateStr)\n }\n}\n\nconst GRANULARITY_OPTIONS: { value: DateGranularity; labelKey: string }[] = [\n { value: 'day', labelKey: 'dashboards.analytics.granularity.day' },\n { value: 'week', labelKey: 'dashboards.analytics.granularity.week' },\n { value: 'month', labelKey: 'dashboards.analytics.granularity.month' },\n { value: 'quarter', labelKey: 'dashboards.analytics.granularity.quarter' },\n { value: 'year', labelKey: 'dashboards.analytics.granularity.year' },\n]\n\nfunction getAutoGranularity(dateRange: DateRangePreset): DateGranularity {\n switch (dateRange) {\n case 'today':\n case 'yesterday':\n case 'last_7_days':\n return 'day'\n case 'this_week':\n case 'last_week':\n case 'last_30_days':\n return 'day'\n case 'this_month':\n case 'last_month':\n case 'last_90_days':\n return 'week'\n case 'this_quarter':\n case 'last_quarter':\n return 'week'\n case 'this_year':\n case 'last_year':\n return 'month'\n default:\n return 'day'\n }\n}\n\nconst RevenueTrendWidget: React.FC<DashboardWidgetComponentProps<RevenueTrendSettings>> = ({\n mode,\n settings = DEFAULT_SETTINGS,\n onSettingsChange,\n refreshToken,\n onRefreshStateChange,\n}) => {\n const t = useT()\n const locale = useLocale()\n const hydrated = React.useMemo(() => hydrateSettings(settings), [settings])\n const [data, setData] = React.useState<LineChartDataItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchRevenueTrendData(hydrated, fetchWidgetData)\n const sortedData = [...result.data].sort((a, b) => {\n const aTime = new Date(a.groupKey as string || 0).getTime()\n const bTime = new Date(b.groupKey as string || 0).getTime()\n return aTime - bTime\n })\n const chartData = sortedData.map((item) => ({\n date: formatDate(item.groupKey as string | null, hydrated.granularity, locale),\n Revenue: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load revenue trend data', err)\n setError(t('dashboards.analytics.widgets.revenueTrend.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, fetchWidgetData, locale, 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=\"revenue-trend-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=\"revenue-trend-granularity\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.granularity', 'Granularity')}\n </label>\n <Select\n value={hydrated.granularity}\n onValueChange={(value) => onSettingsChange({ ...hydrated, granularity: value as DateGranularity })}\n >\n <SelectTrigger id=\"revenue-trend-granularity\" size=\"sm\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {GRANULARITY_OPTIONS.map((opt) => (\n <SelectItem key={opt.value} value={opt.value}>\n {t(opt.labelKey, opt.value)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\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.showArea}\n onChange={(e) => onSettingsChange({ ...hydrated, showArea: e.target.checked })}\n className=\"h-4 w-4 rounded border focus-visible:ring-ring\"\n />\n {t('dashboards.analytics.settings.showArea', 'Show area fill')}\n </label>\n </div>\n </div>\n )\n }\n\n const effectiveGranularity = hydrated.granularity === 'day' ? getAutoGranularity(hydrated.dateRange) : hydrated.granularity\n\n return (\n <div>\n <div className=\"mb-2 flex justify-end\">\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange, granularity: getAutoGranularity(dateRange) })}\n />\n </div>\n <LineChart\n data={data}\n index=\"date\"\n categories={['Revenue']}\n categoryLabels={{ Revenue: t('dashboards.analytics.widgets.topCustomers.column.revenue', 'Revenue') }}\n loading={loading}\n error={error}\n showArea={hydrated.showArea}\n valueFormatter={formatCurrencyCompact}\n colors={['blue']}\n emptyMessage={t('dashboards.analytics.widgets.revenueTrend.empty', 'No revenue data for this period')}\n />\n </div>\n )\n}\n\nexport default RevenueTrendWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { BarChart } from "@open-mercato/ui/backend/charts";
7
7
  import { DateRangeSelect } from "@open-mercato/ui/backend/date-range";
8
8
  import { Input } from "@open-mercato/ui/primitives/input";
9
9
  import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
10
10
  import { formatCurrencyCompact } from "../../../lib/formatters.js";
11
- async function fetchSalesByRegionData(settings) {
11
+ async function fetchSalesByRegionData(settings, fetchWidgetData) {
12
12
  const body = {
13
13
  entityType: "sales:orders",
14
14
  metric: {
@@ -24,16 +24,7 @@ async function fetchSalesByRegionData(settings) {
24
24
  preset: settings.dateRange
25
25
  }
26
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 sales by region data");
35
- }
36
- return call.result;
27
+ return fetchWidgetData(body);
37
28
  }
38
29
  const SalesByRegionWidget = ({
39
30
  mode,
@@ -47,12 +38,13 @@ const SalesByRegionWidget = ({
47
38
  const [data, setData] = React.useState([]);
48
39
  const [loading, setLoading] = React.useState(true);
49
40
  const [error, setError] = React.useState(null);
41
+ const fetchWidgetData = useWidgetData();
50
42
  const refresh = React.useCallback(async () => {
51
43
  onRefreshStateChange?.(true);
52
44
  setLoading(true);
53
45
  setError(null);
54
46
  try {
55
- const result = await fetchSalesByRegionData(hydrated);
47
+ const result = await fetchSalesByRegionData(hydrated, fetchWidgetData);
56
48
  const chartData = result.data.map((item) => ({
57
49
  region: String(item.groupKey || t("dashboards.analytics.labels.unknown", "Unknown")),
58
50
  Revenue: item.value ?? 0
@@ -65,7 +57,7 @@ const SalesByRegionWidget = ({
65
57
  setLoading(false);
66
58
  onRefreshStateChange?.(false);
67
59
  }
68
- }, [hydrated, onRefreshStateChange, t]);
60
+ }, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
69
61
  React.useEffect(() => {
70
62
  refresh().catch(() => {
71
63
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/sales-by-region/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 { BarChart, type BarChartDataItem } from '@open-mercato/ui/backend/charts'\nimport { DateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { DEFAULT_SETTINGS, hydrateSettings, type SalesByRegionSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyCompact } from '../../../lib/formatters'\n\nasync function fetchSalesByRegionData(settings: SalesByRegionSettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'shippingAddressSnapshot.region',\n limit: settings.limit,\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 sales by region data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nconst SalesByRegionWidget: React.FC<DashboardWidgetComponentProps<SalesByRegionSettings>> = ({\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<BarChartDataItem[]>([])\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 fetchSalesByRegionData(hydrated)\n const chartData = result.data.map((item) => ({\n region: String(item.groupKey || t('dashboards.analytics.labels.unknown', 'Unknown')),\n Revenue: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load sales by region data', err)\n setError(t('dashboards.analytics.widgets.salesByRegion.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=\"sales-by-region-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=\"sales-by-region-limit\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.limit', 'Number of items')}\n </label>\n <Input\n id=\"sales-by-region-limit\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.limit}\n onChange={(e) => {\n const next = Number(e.target.value)\n onSettingsChange({ ...hydrated, limit: Number.isFinite(next) ? next : hydrated.limit })\n }}\n />\n </div>\n </div>\n )\n }\n\n return (\n <BarChart\n data={data}\n index=\"region\"\n categories={['Revenue']}\n categoryLabels={{ Revenue: t('dashboards.analytics.widgets.topCustomers.column.revenue', 'Revenue') }}\n loading={loading}\n error={error}\n layout=\"horizontal\"\n valueFormatter={formatCurrencyCompact}\n colors={['cyan']}\n showLegend={false}\n emptyMessage={t('dashboards.analytics.widgets.salesByRegion.empty', 'No regional sales data for this period')}\n />\n )\n}\n\nexport default SalesByRegionWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { BarChart, type BarChartDataItem } from '@open-mercato/ui/backend/charts'\nimport { DateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { DEFAULT_SETTINGS, hydrateSettings, type SalesByRegionSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyCompact } from '../../../lib/formatters'\n\nasync function fetchSalesByRegionData(settings: SalesByRegionSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'shippingAddressSnapshot.region',\n limit: settings.limit,\n },\n dateRange: {\n field: 'placedAt',\n preset: settings.dateRange,\n },\n }\n\n return fetchWidgetData<WidgetDataResponse>(body)\n}\n\nconst SalesByRegionWidget: React.FC<DashboardWidgetComponentProps<SalesByRegionSettings>> = ({\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<BarChartDataItem[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchSalesByRegionData(hydrated, fetchWidgetData)\n const chartData = result.data.map((item) => ({\n region: String(item.groupKey || t('dashboards.analytics.labels.unknown', 'Unknown')),\n Revenue: item.value ?? 0,\n }))\n setData(chartData)\n } catch (err) {\n console.error('Failed to load sales by region data', err)\n setError(t('dashboards.analytics.widgets.salesByRegion.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, fetchWidgetData, 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=\"sales-by-region-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=\"sales-by-region-limit\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.limit', 'Number of items')}\n </label>\n <Input\n id=\"sales-by-region-limit\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.limit}\n onChange={(e) => {\n const next = Number(e.target.value)\n onSettingsChange({ ...hydrated, limit: Number.isFinite(next) ? next : hydrated.limit })\n }}\n />\n </div>\n </div>\n )\n }\n\n return (\n <BarChart\n data={data}\n index=\"region\"\n categories={['Revenue']}\n categoryLabels={{ Revenue: t('dashboards.analytics.widgets.topCustomers.column.revenue', 'Revenue') }}\n loading={loading}\n error={error}\n layout=\"horizontal\"\n valueFormatter={formatCurrencyCompact}\n colors={['cyan']}\n showLegend={false}\n emptyMessage={t('dashboards.analytics.widgets.salesByRegion.empty', 'No regional sales data for this period')}\n />\n )\n}\n\nexport default SalesByRegionWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,14 +1,14 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { TopNTable } from "@open-mercato/ui/backend/charts";
7
7
  import { DateRangeSelect } from "@open-mercato/ui/backend/date-range";
8
8
  import { Input } from "@open-mercato/ui/primitives/input";
9
9
  import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
10
10
  import { formatCurrencySafe } from "../../../lib/formatters.js";
11
- async function fetchTopCustomersData(settings) {
11
+ async function fetchTopCustomersData(settings, fetchWidgetData) {
12
12
  const body = {
13
13
  entityType: "sales:orders",
14
14
  metric: {
@@ -25,16 +25,7 @@ async function fetchTopCustomersData(settings) {
25
25
  preset: settings.dateRange
26
26
  }
27
27
  };
28
- const call = await apiCall("/api/dashboards/widgets/data", {
29
- method: "POST",
30
- headers: { "Content-Type": "application/json" },
31
- body: JSON.stringify(body)
32
- });
33
- if (!call.ok) {
34
- const errorMsg = call.result?.error;
35
- throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch top customers data");
36
- }
37
- return call.result;
28
+ return fetchWidgetData(body);
38
29
  }
39
30
  function formatCustomerName(name, unknownLabel) {
40
31
  if (!name) return unknownLabel;
@@ -74,12 +65,13 @@ const TopCustomersWidget = ({
74
65
  ],
75
66
  [t, unknownLabel]
76
67
  );
68
+ const fetchWidgetData = useWidgetData();
77
69
  const refresh = React.useCallback(async () => {
78
70
  onRefreshStateChange?.(true);
79
71
  setLoading(true);
80
72
  setError(null);
81
73
  try {
82
- const result = await fetchTopCustomersData(hydrated);
74
+ const result = await fetchTopCustomersData(hydrated, fetchWidgetData);
83
75
  const tableData = result.data.map((item, index) => ({
84
76
  rank: index + 1,
85
77
  customerId: item.groupLabel || String(item.groupKey || t("dashboards.analytics.labels.unknown", "Unknown")),
@@ -93,7 +85,7 @@ const TopCustomersWidget = ({
93
85
  setLoading(false);
94
86
  onRefreshStateChange?.(false);
95
87
  }
96
- }, [hydrated, onRefreshStateChange, t]);
88
+ }, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
97
89
  React.useEffect(() => {
98
90
  refresh().catch(() => {
99
91
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/top-customers/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 { TopNTable, type TopNTableColumn } from '@open-mercato/ui/backend/charts'\nimport { DateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { DEFAULT_SETTINGS, hydrateSettings, type TopCustomersSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencySafe } from '../../../lib/formatters'\n\ntype CustomerRow = {\n rank: number\n customerId: string\n revenue: number\n}\n\nasync function fetchTopCustomersData(settings: TopCustomersSettings): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'customerEntityId',\n limit: settings.limit,\n resolveLabels: true,\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 top customers data')\n }\n\n return call.result as WidgetDataResponse\n}\n\nfunction formatCustomerName(name: string | null, unknownLabel: string): string {\n if (!name) return unknownLabel\n return name\n}\n\nconst TopCustomersWidget: React.FC<DashboardWidgetComponentProps<TopCustomersSettings>> = ({\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<CustomerRow[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const unknownLabel = t('dashboards.analytics.labels.unknown', 'Unknown')\n const columns: TopNTableColumn<CustomerRow>[] = React.useMemo(\n () => [\n {\n key: 'rank',\n header: '#',\n width: '40px',\n },\n {\n key: 'customerId',\n header: t('dashboards.analytics.widgets.topCustomers.column.customer', 'Customer'),\n formatter: (value) => formatCustomerName(String(value || ''), unknownLabel),\n },\n {\n key: 'revenue',\n header: t('dashboards.analytics.widgets.topCustomers.column.revenue', 'Revenue'),\n align: 'right',\n formatter: (value: unknown) => formatCurrencySafe(value),\n },\n ],\n [t, unknownLabel],\n )\n\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchTopCustomersData(hydrated)\n const tableData: CustomerRow[] = result.data.map((item, index) => ({\n rank: index + 1,\n customerId: item.groupLabel || String(item.groupKey || t('dashboards.analytics.labels.unknown', 'Unknown')),\n revenue: item.value ?? 0,\n }))\n setData(tableData)\n } catch (err) {\n console.error('Failed to load top customers data', err)\n setError(t('dashboards.analytics.widgets.topCustomers.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=\"top-customers-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=\"top-customers-limit\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.limit', 'Number of items')}\n </label>\n <Input\n id=\"top-customers-limit\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.limit}\n onChange={(e) => {\n const next = Number(e.target.value)\n onSettingsChange({ ...hydrated, limit: Number.isFinite(next) ? next : hydrated.limit })\n }}\n />\n </div>\n </div>\n )\n }\n\n return (\n <TopNTable\n data={data}\n columns={columns}\n loading={loading}\n error={error}\n emptyMessage={t('dashboards.analytics.widgets.topCustomers.empty', 'No customer data for this period')}\n />\n )\n}\n\nexport default TopCustomersWidget\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\nimport { useWidgetData, type WidgetDataFetcher } from '@open-mercato/ui/backend/dashboard/widgetData'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { TopNTable, type TopNTableColumn } from '@open-mercato/ui/backend/charts'\nimport { DateRangeSelect, type DateRangePreset } from '@open-mercato/ui/backend/date-range'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { DEFAULT_SETTINGS, hydrateSettings, type TopCustomersSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencySafe } from '../../../lib/formatters'\n\ntype CustomerRow = {\n rank: number\n customerId: string\n revenue: number\n}\n\nasync function fetchTopCustomersData(settings: TopCustomersSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'sum',\n },\n groupBy: {\n field: 'customerEntityId',\n limit: settings.limit,\n resolveLabels: true,\n },\n dateRange: {\n field: 'placedAt',\n preset: settings.dateRange,\n },\n }\n\n return fetchWidgetData<WidgetDataResponse>(body)\n}\n\nfunction formatCustomerName(name: string | null, unknownLabel: string): string {\n if (!name) return unknownLabel\n return name\n}\n\nconst TopCustomersWidget: React.FC<DashboardWidgetComponentProps<TopCustomersSettings>> = ({\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<CustomerRow[]>([])\n const [loading, setLoading] = React.useState(true)\n const [error, setError] = React.useState<string | null>(null)\n\n const unknownLabel = t('dashboards.analytics.labels.unknown', 'Unknown')\n const columns: TopNTableColumn<CustomerRow>[] = React.useMemo(\n () => [\n {\n key: 'rank',\n header: '#',\n width: '40px',\n },\n {\n key: 'customerId',\n header: t('dashboards.analytics.widgets.topCustomers.column.customer', 'Customer'),\n formatter: (value) => formatCustomerName(String(value || ''), unknownLabel),\n },\n {\n key: 'revenue',\n header: t('dashboards.analytics.widgets.topCustomers.column.revenue', 'Revenue'),\n align: 'right',\n formatter: (value: unknown) => formatCurrencySafe(value),\n },\n ],\n [t, unknownLabel],\n )\n\n const fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchTopCustomersData(hydrated, fetchWidgetData)\n const tableData: CustomerRow[] = result.data.map((item, index) => ({\n rank: index + 1,\n customerId: item.groupLabel || String(item.groupKey || t('dashboards.analytics.labels.unknown', 'Unknown')),\n revenue: item.value ?? 0,\n }))\n setData(tableData)\n } catch (err) {\n console.error('Failed to load top customers data', err)\n setError(t('dashboards.analytics.widgets.topCustomers.error', 'Failed to load data'))\n } finally {\n setLoading(false)\n onRefreshStateChange?.(false)\n }\n }, [hydrated, fetchWidgetData, 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=\"top-customers-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=\"top-customers-limit\"\n className=\"text-xs font-semibold uppercase text-muted-foreground\"\n >\n {t('dashboards.analytics.settings.limit', 'Number of items')}\n </label>\n <Input\n id=\"top-customers-limit\"\n type=\"number\"\n min={1}\n max={20}\n className=\"w-24\"\n value={hydrated.limit}\n onChange={(e) => {\n const next = Number(e.target.value)\n onSettingsChange({ ...hydrated, limit: Number.isFinite(next) ? next : hydrated.limit })\n }}\n />\n </div>\n </div>\n )\n }\n\n return (\n <TopNTable\n data={data}\n columns={columns}\n loading={loading}\n error={error}\n emptyMessage={t('dashboards.analytics.widgets.topCustomers.empty', 'No customer data for this period')}\n />\n )\n}\n\nexport default TopCustomersWidget\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
4
+ import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
6
  import { BarChart } from "@open-mercato/ui/backend/charts";
7
7
  import { DateRangeSelect, InlineDateRangeSelect } from "@open-mercato/ui/backend/date-range";
@@ -15,7 +15,7 @@ import {
15
15
  } from "@open-mercato/ui/primitives/select";
16
16
  import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
17
17
  import { formatCurrencyCompact } from "../../../lib/formatters.js";
18
- async function fetchTopProductsData(settings) {
18
+ async function fetchTopProductsData(settings, fetchWidgetData) {
19
19
  const body = {
20
20
  entityType: "sales:order_lines",
21
21
  metric: {
@@ -32,16 +32,7 @@ async function fetchTopProductsData(settings) {
32
32
  preset: settings.dateRange
33
33
  }
34
34
  };
35
- const call = await apiCall("/api/dashboards/widgets/data", {
36
- method: "POST",
37
- headers: { "Content-Type": "application/json" },
38
- body: JSON.stringify(body)
39
- });
40
- if (!call.ok) {
41
- const errorMsg = call.result?.error;
42
- throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch top products data");
43
- }
44
- return call.result;
35
+ return fetchWidgetData(body);
45
36
  }
46
37
  function truncateLabel(label, t, maxLength = 20) {
47
38
  if (label == null || label === "") return t("dashboards.analytics.labels.unknownProduct", "Unknown Product");
@@ -68,6 +59,7 @@ const TopProductsWidget = ({
68
59
  const [loading, setLoading] = React.useState(true);
69
60
  const [error, setError] = React.useState(null);
70
61
  const fetchingRef = React.useRef(false);
62
+ const fetchWidgetData = useWidgetData();
71
63
  const refresh = React.useCallback(async () => {
72
64
  if (fetchingRef.current) return;
73
65
  fetchingRef.current = true;
@@ -75,7 +67,7 @@ const TopProductsWidget = ({
75
67
  setLoading(true);
76
68
  setError(null);
77
69
  try {
78
- const result = await fetchTopProductsData(hydrated);
70
+ const result = await fetchTopProductsData(hydrated, fetchWidgetData);
79
71
  const chartData = result.data.map((item, index) => ({
80
72
  name: truncateLabel(item.groupLabel ?? item.groupKey ?? `Product ${index + 1}`, t),
81
73
  Revenue: item.value ?? 0
@@ -89,7 +81,7 @@ const TopProductsWidget = ({
89
81
  onRefreshStateChange?.(false);
90
82
  fetchingRef.current = false;
91
83
  }
92
- }, [hydrated, onRefreshStateChange, t]);
84
+ }, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
93
85
  React.useEffect(() => {
94
86
  refresh().catch(() => {
95
87
  });