@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/auth/lib/sessionIntegrity.js +16 -13
- package/dist/modules/auth/lib/sessionIntegrity.js.map +2 -2
- package/dist/modules/customers/api/utils.js +14 -9
- package/dist/modules/customers/api/utils.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/batch/route.js +137 -0
- package/dist/modules/dashboards/api/widgets/data/batch/route.js.map +7 -0
- package/dist/modules/dashboards/api/widgets/data/route.js +1 -75
- package/dist/modules/dashboards/api/widgets/data/route.js.map +2 -2
- package/dist/modules/dashboards/api/widgets/data/schema.js +85 -0
- package/dist/modules/dashboards/api/widgets/data/schema.js.map +7 -0
- package/dist/modules/dashboards/lib/widgetDataBatch.js +49 -0
- package/dist/modules/dashboards/lib/widgetDataBatch.js.map +7 -0
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/top-customers/widget.client.js.map +2 -2
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js +6 -14
- package/dist/modules/dashboards/widgets/dashboard/top-products/widget.client.js.map +2 -2
- package/dist/modules/directory/utils/organizationScope.js +33 -20
- package/dist/modules/directory/utils/organizationScope.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/auth/lib/sessionIntegrity.ts +37 -16
- package/src/modules/customers/api/utils.ts +17 -11
- package/src/modules/dashboards/api/widgets/data/batch/route.ts +168 -0
- package/src/modules/dashboards/api/widgets/data/route.ts +1 -90
- package/src/modules/dashboards/api/widgets/data/schema.ts +90 -0
- package/src/modules/dashboards/lib/widgetDataBatch.ts +89 -0
- package/src/modules/dashboards/widgets/dashboard/aov-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/new-customers-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/orders-by-status/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/orders-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/pipeline-summary/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/revenue-kpi/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/revenue-trend/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/sales-by-region/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/top-customers/widget.client.tsx +6 -16
- package/src/modules/dashboards/widgets/dashboard/top-products/widget.client.tsx +6 -16
- package/src/modules/directory/utils/organizationScope.ts +51 -20
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
const aggregateFunctionSchema = z.enum(["count", "sum", "avg", "min", "max"]);
|
|
3
|
+
const dateGranularitySchema = z.enum(["day", "week", "month", "quarter", "year"]);
|
|
4
|
+
const dateRangePresetSchema = z.enum([
|
|
5
|
+
"today",
|
|
6
|
+
"yesterday",
|
|
7
|
+
"this_week",
|
|
8
|
+
"last_week",
|
|
9
|
+
"this_month",
|
|
10
|
+
"last_month",
|
|
11
|
+
"this_quarter",
|
|
12
|
+
"last_quarter",
|
|
13
|
+
"this_year",
|
|
14
|
+
"last_year",
|
|
15
|
+
"last_7_days",
|
|
16
|
+
"last_30_days",
|
|
17
|
+
"last_90_days"
|
|
18
|
+
]);
|
|
19
|
+
const filterOperatorSchema = z.enum([
|
|
20
|
+
"eq",
|
|
21
|
+
"neq",
|
|
22
|
+
"gt",
|
|
23
|
+
"gte",
|
|
24
|
+
"lt",
|
|
25
|
+
"lte",
|
|
26
|
+
"in",
|
|
27
|
+
"not_in",
|
|
28
|
+
"is_null",
|
|
29
|
+
"is_not_null"
|
|
30
|
+
]);
|
|
31
|
+
const widgetDataRequestSchema = z.object({
|
|
32
|
+
entityType: z.string().min(1),
|
|
33
|
+
metric: z.object({
|
|
34
|
+
field: z.string().min(1),
|
|
35
|
+
aggregate: aggregateFunctionSchema
|
|
36
|
+
}),
|
|
37
|
+
groupBy: z.object({
|
|
38
|
+
field: z.string().min(1),
|
|
39
|
+
granularity: dateGranularitySchema.optional(),
|
|
40
|
+
limit: z.number().int().min(1).max(100).optional(),
|
|
41
|
+
resolveLabels: z.boolean().optional()
|
|
42
|
+
}).optional(),
|
|
43
|
+
filters: z.array(
|
|
44
|
+
z.object({
|
|
45
|
+
field: z.string().min(1),
|
|
46
|
+
operator: filterOperatorSchema,
|
|
47
|
+
value: z.unknown().optional()
|
|
48
|
+
})
|
|
49
|
+
).optional(),
|
|
50
|
+
dateRange: z.object({
|
|
51
|
+
field: z.string().min(1),
|
|
52
|
+
preset: dateRangePresetSchema
|
|
53
|
+
}).optional(),
|
|
54
|
+
comparison: z.object({
|
|
55
|
+
type: z.enum(["previous_period", "previous_year"])
|
|
56
|
+
}).optional()
|
|
57
|
+
});
|
|
58
|
+
const widgetDataItemSchema = z.object({
|
|
59
|
+
groupKey: z.unknown(),
|
|
60
|
+
groupLabel: z.string().optional(),
|
|
61
|
+
value: z.number().nullable()
|
|
62
|
+
});
|
|
63
|
+
const widgetDataResponseSchema = z.object({
|
|
64
|
+
value: z.number().nullable(),
|
|
65
|
+
data: z.array(widgetDataItemSchema),
|
|
66
|
+
comparison: z.object({
|
|
67
|
+
value: z.number().nullable(),
|
|
68
|
+
change: z.number(),
|
|
69
|
+
direction: z.enum(["up", "down", "unchanged"])
|
|
70
|
+
}).optional(),
|
|
71
|
+
metadata: z.object({
|
|
72
|
+
fetchedAt: z.string(),
|
|
73
|
+
recordCount: z.number()
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
export {
|
|
77
|
+
aggregateFunctionSchema,
|
|
78
|
+
dateGranularitySchema,
|
|
79
|
+
dateRangePresetSchema,
|
|
80
|
+
filterOperatorSchema,
|
|
81
|
+
widgetDataItemSchema,
|
|
82
|
+
widgetDataRequestSchema,
|
|
83
|
+
widgetDataResponseSchema
|
|
84
|
+
};
|
|
85
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../../src/modules/dashboards/api/widgets/data/schema.ts"],
|
|
4
|
+
"sourcesContent": ["import { z } from 'zod'\n\nexport const aggregateFunctionSchema = z.enum(['count', 'sum', 'avg', 'min', 'max'])\nexport const dateGranularitySchema = z.enum(['day', 'week', 'month', 'quarter', 'year'])\nexport const dateRangePresetSchema = z.enum([\n 'today',\n 'yesterday',\n 'this_week',\n 'last_week',\n 'this_month',\n 'last_month',\n 'this_quarter',\n 'last_quarter',\n 'this_year',\n 'last_year',\n 'last_7_days',\n 'last_30_days',\n 'last_90_days',\n])\n\nexport const filterOperatorSchema = z.enum([\n 'eq',\n 'neq',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'in',\n 'not_in',\n 'is_null',\n 'is_not_null',\n])\n\nexport const widgetDataRequestSchema = z.object({\n entityType: z.string().min(1),\n metric: z.object({\n field: z.string().min(1),\n aggregate: aggregateFunctionSchema,\n }),\n groupBy: z\n .object({\n field: z.string().min(1),\n granularity: dateGranularitySchema.optional(),\n limit: z.number().int().min(1).max(100).optional(),\n resolveLabels: z.boolean().optional(),\n })\n .optional(),\n filters: z\n .array(\n z.object({\n field: z.string().min(1),\n operator: filterOperatorSchema,\n value: z.unknown().optional(),\n }),\n )\n .optional(),\n dateRange: z\n .object({\n field: z.string().min(1),\n preset: dateRangePresetSchema,\n })\n .optional(),\n comparison: z\n .object({\n type: z.enum(['previous_period', 'previous_year']),\n })\n .optional(),\n})\n\nexport const widgetDataItemSchema = z.object({\n groupKey: z.unknown(),\n groupLabel: z.string().optional(),\n value: z.number().nullable(),\n})\n\nexport const widgetDataResponseSchema = z.object({\n value: z.number().nullable(),\n data: z.array(widgetDataItemSchema),\n comparison: z\n .object({\n value: z.number().nullable(),\n change: z.number(),\n direction: z.enum(['up', 'down', 'unchanged']),\n })\n .optional(),\n metadata: z.object({\n fetchedAt: z.string(),\n recordCount: z.number(),\n }),\n})\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,SAAS;AAEX,MAAM,0BAA0B,EAAE,KAAK,CAAC,SAAS,OAAO,OAAO,OAAO,KAAK,CAAC;AAC5E,MAAM,wBAAwB,EAAE,KAAK,CAAC,OAAO,QAAQ,SAAS,WAAW,MAAM,CAAC;AAChF,MAAM,wBAAwB,EAAE,KAAK;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,uBAAuB,EAAE,KAAK;AAAA,EACzC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,MAAM,0BAA0B,EAAE,OAAO;AAAA,EAC9C,YAAY,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,EAC5B,QAAQ,EAAE,OAAO;AAAA,IACf,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IACvB,WAAW;AAAA,EACb,CAAC;AAAA,EACD,SAAS,EACN,OAAO;AAAA,IACN,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IACvB,aAAa,sBAAsB,SAAS;AAAA,IAC5C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,IACjD,eAAe,EAAE,QAAQ,EAAE,SAAS;AAAA,EACtC,CAAC,EACA,SAAS;AAAA,EACZ,SAAS,EACN;AAAA,IACC,EAAE,OAAO;AAAA,MACP,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,MACvB,UAAU;AAAA,MACV,OAAO,EAAE,QAAQ,EAAE,SAAS;AAAA,IAC9B,CAAC;AAAA,EACH,EACC,SAAS;AAAA,EACZ,WAAW,EACR,OAAO;AAAA,IACN,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,IACvB,QAAQ;AAAA,EACV,CAAC,EACA,SAAS;AAAA,EACZ,YAAY,EACT,OAAO;AAAA,IACN,MAAM,EAAE,KAAK,CAAC,mBAAmB,eAAe,CAAC;AAAA,EACnD,CAAC,EACA,SAAS;AACd,CAAC;AAEM,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,UAAU,EAAE,QAAQ;AAAA,EACpB,YAAY,EAAE,OAAO,EAAE,SAAS;AAAA,EAChC,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC;AAEM,MAAM,2BAA2B,EAAE,OAAO;AAAA,EAC/C,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,EAC3B,MAAM,EAAE,MAAM,oBAAoB;AAAA,EAClC,YAAY,EACT,OAAO;AAAA,IACN,OAAO,EAAE,OAAO,EAAE,SAAS;AAAA,IAC3B,QAAQ,EAAE,OAAO;AAAA,IACjB,WAAW,EAAE,KAAK,CAAC,MAAM,QAAQ,WAAW,CAAC;AAAA,EAC/C,CAAC,EACA,SAAS;AAAA,EACZ,UAAU,EAAE,OAAO;AAAA,IACjB,WAAW,EAAE,OAAO;AAAA,IACpB,aAAa,EAAE,OAAO;AAAA,EACxB,CAAC;AACH,CAAC;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
async function resolveEntityFeatureAccess(entityTypes, getRequiredFeatures, checkFeatures) {
|
|
2
|
+
const access = /* @__PURE__ */ new Map();
|
|
3
|
+
const featuresByEntity = /* @__PURE__ */ new Map();
|
|
4
|
+
const unionFeatures = /* @__PURE__ */ new Set();
|
|
5
|
+
for (const entityType of new Set(entityTypes)) {
|
|
6
|
+
const features = getRequiredFeatures(entityType) ?? [];
|
|
7
|
+
featuresByEntity.set(entityType, features);
|
|
8
|
+
if (features.length === 0) {
|
|
9
|
+
access.set(entityType, true);
|
|
10
|
+
} else {
|
|
11
|
+
for (const feature of features) unionFeatures.add(feature);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
const gated = [...featuresByEntity.entries()].filter(([, features]) => features.length > 0);
|
|
15
|
+
if (gated.length === 0) return access;
|
|
16
|
+
if (await checkFeatures([...unionFeatures])) {
|
|
17
|
+
for (const [entityType] of gated) access.set(entityType, true);
|
|
18
|
+
return access;
|
|
19
|
+
}
|
|
20
|
+
for (const [entityType, features] of gated) {
|
|
21
|
+
access.set(entityType, await checkFeatures(features));
|
|
22
|
+
}
|
|
23
|
+
return access;
|
|
24
|
+
}
|
|
25
|
+
async function runWidgetDataBatch(entries, deps) {
|
|
26
|
+
const access = await resolveEntityFeatureAccess(
|
|
27
|
+
entries.map((entry) => entry.request.entityType),
|
|
28
|
+
deps.getRequiredFeatures,
|
|
29
|
+
deps.checkFeatures
|
|
30
|
+
);
|
|
31
|
+
return Promise.all(
|
|
32
|
+
entries.map(async (entry) => {
|
|
33
|
+
if (access.get(entry.request.entityType) === false) {
|
|
34
|
+
return { id: entry.id, ok: false, error: "Forbidden" };
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
const data = await deps.fetchOne(entry.request);
|
|
38
|
+
return { id: entry.id, ok: true, data };
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return { id: entry.id, ok: false, error: deps.describeError(error) };
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
export {
|
|
46
|
+
resolveEntityFeatureAccess,
|
|
47
|
+
runWidgetDataBatch
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=widgetDataBatch.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/modules/dashboards/lib/widgetDataBatch.ts"],
|
|
4
|
+
"sourcesContent": ["import type { WidgetDataRequest, WidgetDataResponse } from '../services/widgetDataService'\n\nexport type WidgetDataBatchEntry = {\n id: string\n request: WidgetDataRequest\n}\n\nexport type WidgetDataBatchResult =\n | { id: string; ok: true; data: WidgetDataResponse }\n | { id: string; ok: false; error: string }\n\nexport type WidgetDataBatchDeps = {\n getRequiredFeatures: (entityType: string) => string[] | null\n checkFeatures: (features: string[]) => Promise<boolean>\n fetchOne: (request: WidgetDataRequest) => Promise<WidgetDataResponse>\n describeError: (error: unknown) => string\n}\n\n/**\n * Resolves per-entity-type feature access for a batch of widget requests while\n * collapsing the common case to a single RBAC resolution. The happy path checks\n * the union of all required features once; only when the union check fails do we\n * fall back to per-entity-type checks so a single privileged entity type does\n * not reject widgets the caller is allowed to see.\n */\nexport async function resolveEntityFeatureAccess(\n entityTypes: string[],\n getRequiredFeatures: (entityType: string) => string[] | null,\n checkFeatures: (features: string[]) => Promise<boolean>,\n): Promise<Map<string, boolean>> {\n const access = new Map<string, boolean>()\n const featuresByEntity = new Map<string, string[]>()\n const unionFeatures = new Set<string>()\n\n for (const entityType of new Set(entityTypes)) {\n const features = getRequiredFeatures(entityType) ?? []\n featuresByEntity.set(entityType, features)\n if (features.length === 0) {\n access.set(entityType, true)\n } else {\n for (const feature of features) unionFeatures.add(feature)\n }\n }\n\n const gated = [...featuresByEntity.entries()].filter(([, features]) => features.length > 0)\n if (gated.length === 0) return access\n\n if (await checkFeatures([...unionFeatures])) {\n for (const [entityType] of gated) access.set(entityType, true)\n return access\n }\n\n for (const [entityType, features] of gated) {\n access.set(entityType, await checkFeatures(features))\n }\n return access\n}\n\n/**\n * Runs a batch of widget-data requests against shared request-scoped\n * dependencies (a single container, RBAC resolution, org-scope, and EM fork).\n * Feature access is resolved once up front; each request is then executed\n * concurrently with per-widget error isolation so one bad request never fails\n * the whole batch.\n */\nexport async function runWidgetDataBatch(\n entries: WidgetDataBatchEntry[],\n deps: WidgetDataBatchDeps,\n): Promise<WidgetDataBatchResult[]> {\n const access = await resolveEntityFeatureAccess(\n entries.map((entry) => entry.request.entityType),\n deps.getRequiredFeatures,\n deps.checkFeatures,\n )\n\n return Promise.all(\n entries.map(async (entry): Promise<WidgetDataBatchResult> => {\n if (access.get(entry.request.entityType) === false) {\n return { id: entry.id, ok: false, error: 'Forbidden' }\n }\n try {\n const data = await deps.fetchOne(entry.request)\n return { id: entry.id, ok: true, data }\n } catch (error) {\n return { id: entry.id, ok: false, error: deps.describeError(error) }\n }\n }),\n )\n}\n"],
|
|
5
|
+
"mappings": "AAyBA,eAAsB,2BACpB,aACA,qBACA,eAC+B;AAC/B,QAAM,SAAS,oBAAI,IAAqB;AACxC,QAAM,mBAAmB,oBAAI,IAAsB;AACnD,QAAM,gBAAgB,oBAAI,IAAY;AAEtC,aAAW,cAAc,IAAI,IAAI,WAAW,GAAG;AAC7C,UAAM,WAAW,oBAAoB,UAAU,KAAK,CAAC;AACrD,qBAAiB,IAAI,YAAY,QAAQ;AACzC,QAAI,SAAS,WAAW,GAAG;AACzB,aAAO,IAAI,YAAY,IAAI;AAAA,IAC7B,OAAO;AACL,iBAAW,WAAW,SAAU,eAAc,IAAI,OAAO;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,QAAQ,CAAC,GAAG,iBAAiB,QAAQ,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,QAAQ,MAAM,SAAS,SAAS,CAAC;AAC1F,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,MAAI,MAAM,cAAc,CAAC,GAAG,aAAa,CAAC,GAAG;AAC3C,eAAW,CAAC,UAAU,KAAK,MAAO,QAAO,IAAI,YAAY,IAAI;AAC7D,WAAO;AAAA,EACT;AAEA,aAAW,CAAC,YAAY,QAAQ,KAAK,OAAO;AAC1C,WAAO,IAAI,YAAY,MAAM,cAAc,QAAQ,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AASA,eAAsB,mBACpB,SACA,MACkC;AAClC,QAAM,SAAS,MAAM;AAAA,IACnB,QAAQ,IAAI,CAAC,UAAU,MAAM,QAAQ,UAAU;AAAA,IAC/C,KAAK;AAAA,IACL,KAAK;AAAA,EACP;AAEA,SAAO,QAAQ;AAAA,IACb,QAAQ,IAAI,OAAO,UAA0C;AAC3D,UAAI,OAAO,IAAI,MAAM,QAAQ,UAAU,MAAM,OAAO;AAClD,eAAO,EAAE,IAAI,MAAM,IAAI,IAAI,OAAO,OAAO,YAAY;AAAA,MACvD;AACA,UAAI;AACF,cAAM,OAAO,MAAM,KAAK,SAAS,MAAM,OAAO;AAC9C,eAAO,EAAE,IAAI,MAAM,IAAI,IAAI,MAAM,KAAK;AAAA,MACxC,SAAS,OAAO;AACd,eAAO,EAAE,IAAI,MAAM,IAAI,IAAI,OAAO,OAAO,KAAK,cAAc,KAAK,EAAE;AAAA,MACrE;AAAA,IACF,CAAC;AAAA,EACH;AACF;",
|
|
6
|
+
"names": []
|
|
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 {
|
|
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 { formatCurrencyWithDecimals } from "../../../lib/formatters.js";
|
|
14
|
-
async function fetchAovData(settings) {
|
|
14
|
+
async function fetchAovData(settings, fetchWidgetData) {
|
|
15
15
|
const body = {
|
|
16
16
|
entityType: "sales:orders",
|
|
17
17
|
metric: {
|
|
@@ -24,16 +24,7 @@ async function fetchAovData(settings) {
|
|
|
24
24
|
},
|
|
25
25
|
comparison: settings.showComparison ? { type: "previous_period" } : void 0
|
|
26
26
|
};
|
|
27
|
-
|
|
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 AOV data");
|
|
35
|
-
}
|
|
36
|
-
return call.result;
|
|
27
|
+
return fetchWidgetData(body);
|
|
37
28
|
}
|
|
38
29
|
const AovKpiWidget = ({
|
|
39
30
|
mode,
|
|
@@ -48,12 +39,13 @@ const AovKpiWidget = ({
|
|
|
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 fetchAovData(hydrated);
|
|
48
|
+
const data = await fetchAovData(hydrated, fetchWidgetData);
|
|
57
49
|
setValue(data.value);
|
|
58
50
|
if (data.comparison) {
|
|
59
51
|
setTrend({
|
|
@@ -70,7 +62,7 @@ const AovKpiWidget = ({
|
|
|
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/aov-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 {
|
|
5
|
-
"mappings": ";
|
|
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 AovKpiSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\nimport { formatCurrencyWithDecimals } from '../../../lib/formatters'\n\nasync function fetchAovData(settings: AovKpiSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'sales:orders',\n metric: {\n field: 'grandTotalGrossAmount',\n aggregate: 'avg',\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 AovKpiWidget: React.FC<DashboardWidgetComponentProps<AovKpiSettings>> = ({\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 fetchAovData(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 AOV KPI data', err)\n setError(t('dashboards.analytics.widgets.aovKpi.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=\"aov-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={formatCurrencyWithDecimals}\n headerAction={\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange })}\n />\n }\n />\n )\n}\n\nexport default AovKpiWidget\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 {
|
|
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 {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
getComparisonLabelKey
|
|
11
11
|
} from "@open-mercato/ui/backend/date-range";
|
|
12
12
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
13
|
-
async function fetchNewCustomersData(settings) {
|
|
13
|
+
async function fetchNewCustomersData(settings, fetchWidgetData) {
|
|
14
14
|
const body = {
|
|
15
15
|
entityType: "customers:entities",
|
|
16
16
|
metric: {
|
|
@@ -23,16 +23,7 @@ async function fetchNewCustomersData(settings) {
|
|
|
23
23
|
},
|
|
24
24
|
comparison: settings.showComparison ? { type: "previous_period" } : void 0
|
|
25
25
|
};
|
|
26
|
-
|
|
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 new customers data");
|
|
34
|
-
}
|
|
35
|
-
return call.result;
|
|
26
|
+
return fetchWidgetData(body);
|
|
36
27
|
}
|
|
37
28
|
const NewCustomersKpiWidget = ({
|
|
38
29
|
mode,
|
|
@@ -47,12 +38,13 @@ const NewCustomersKpiWidget = ({
|
|
|
47
38
|
const [trend, setTrend] = React.useState(void 0);
|
|
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 data = await fetchNewCustomersData(hydrated);
|
|
47
|
+
const data = await fetchNewCustomersData(hydrated, fetchWidgetData);
|
|
56
48
|
setValue(data.value);
|
|
57
49
|
if (data.comparison) {
|
|
58
50
|
setTrend({
|
|
@@ -69,7 +61,7 @@ const NewCustomersKpiWidget = ({
|
|
|
69
61
|
setLoading(false);
|
|
70
62
|
onRefreshStateChange?.(false);
|
|
71
63
|
}
|
|
72
|
-
}, [hydrated, onRefreshStateChange, t]);
|
|
64
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
|
|
73
65
|
React.useEffect(() => {
|
|
74
66
|
refresh().catch(() => {
|
|
75
67
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/dashboards/widgets/dashboard/new-customers-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 {
|
|
5
|
-
"mappings": ";
|
|
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 NewCustomersKpiSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\n\nasync function fetchNewCustomersData(settings: NewCustomersKpiSettings, fetchWidgetData: WidgetDataFetcher): Promise<WidgetDataResponse> {\n const body = {\n entityType: 'customers:entities',\n metric: {\n field: 'id',\n aggregate: 'count',\n },\n dateRange: {\n field: 'createdAt',\n preset: settings.dateRange,\n },\n comparison: settings.showComparison ? { type: 'previous_period' } : undefined,\n }\n\n return fetchWidgetData<WidgetDataResponse>(body)\n}\n\nconst NewCustomersKpiWidget: React.FC<DashboardWidgetComponentProps<NewCustomersKpiSettings>> = ({\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 fetchNewCustomersData(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 new customers KPI data', err)\n setError(t('dashboards.analytics.widgets.newCustomersKpi.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=\"new-customers-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 headerAction={\n <InlineDateRangeSelect\n value={hydrated.dateRange}\n onChange={(dateRange) => onSettingsChange({ ...hydrated, dateRange })}\n />\n }\n />\n )\n}\n\nexport default NewCustomersKpiWidget\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 {
|
|
4
|
+
import { useWidgetData } from "@open-mercato/ui/backend/dashboard/widgetData";
|
|
5
5
|
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
6
|
import { PieChart } from "@open-mercato/ui/backend/charts";
|
|
7
7
|
import {
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
SelectValue
|
|
17
17
|
} from "@open-mercato/ui/primitives/select";
|
|
18
18
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
19
|
-
async function fetchOrdersByStatusData(settings) {
|
|
19
|
+
async function fetchOrdersByStatusData(settings, fetchWidgetData) {
|
|
20
20
|
const body = {
|
|
21
21
|
entityType: "sales:orders",
|
|
22
22
|
metric: {
|
|
@@ -31,16 +31,7 @@ async function fetchOrdersByStatusData(settings) {
|
|
|
31
31
|
preset: settings.dateRange
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
|
-
|
|
35
|
-
method: "POST",
|
|
36
|
-
headers: { "Content-Type": "application/json" },
|
|
37
|
-
body: JSON.stringify(body)
|
|
38
|
-
});
|
|
39
|
-
if (!call.ok) {
|
|
40
|
-
const errorMsg = call.result?.error;
|
|
41
|
-
throw new Error(typeof errorMsg === "string" ? errorMsg : "Failed to fetch orders by status data");
|
|
42
|
-
}
|
|
43
|
-
return call.result;
|
|
34
|
+
return fetchWidgetData(body);
|
|
44
35
|
}
|
|
45
36
|
const ORDER_STATUS_KEYS = {
|
|
46
37
|
draft: "dashboards.analytics.orderStatus.draft",
|
|
@@ -71,12 +62,13 @@ const OrdersByStatusWidget = ({
|
|
|
71
62
|
const [data, setData] = React.useState([]);
|
|
72
63
|
const [loading, setLoading] = React.useState(true);
|
|
73
64
|
const [error, setError] = React.useState(null);
|
|
65
|
+
const fetchWidgetData = useWidgetData();
|
|
74
66
|
const refresh = React.useCallback(async () => {
|
|
75
67
|
onRefreshStateChange?.(true);
|
|
76
68
|
setLoading(true);
|
|
77
69
|
setError(null);
|
|
78
70
|
try {
|
|
79
|
-
const result = await fetchOrdersByStatusData(hydrated);
|
|
71
|
+
const result = await fetchOrdersByStatusData(hydrated, fetchWidgetData);
|
|
80
72
|
const chartData = result.data.map((item) => ({
|
|
81
73
|
name: formatStatusLabel(item.groupKey, t),
|
|
82
74
|
value: item.value ?? 0
|
|
@@ -89,7 +81,7 @@ const OrdersByStatusWidget = ({
|
|
|
89
81
|
setLoading(false);
|
|
90
82
|
onRefreshStateChange?.(false);
|
|
91
83
|
}
|
|
92
|
-
}, [hydrated, onRefreshStateChange, t]);
|
|
84
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
|
|
93
85
|
React.useEffect(() => {
|
|
94
86
|
refresh().catch(() => {
|
|
95
87
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 {
|
|
5
|
-
"mappings": ";
|
|
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 { 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 {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { DEFAULT_SETTINGS, hydrateSettings, type OrdersByStatusSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\n\nasync function fetchOrdersByStatusData(settings: OrdersByStatusSettings, fetchWidgetData: WidgetDataFetcher): 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 return fetchWidgetData<WidgetDataResponse>(body)\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 fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const result = await fetchOrdersByStatusData(hydrated, fetchWidgetData)\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, 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=\"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 value={hydrated.variant}\n onValueChange={(value) => onSettingsChange({ ...hydrated, variant: value as 'pie' | 'donut' })}\n >\n <SelectTrigger id=\"orders-by-status-variant\" size=\"sm\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"donut\">{t('dashboards.analytics.chartVariant.donut', 'Donut')}</SelectItem>\n <SelectItem value=\"pie\">{t('dashboards.analytics.chartVariant.pie', 'Pie')}</SelectItem>\n </SelectContent>\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
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 {
|
|
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 {
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
getComparisonLabelKey
|
|
11
11
|
} from "@open-mercato/ui/backend/date-range";
|
|
12
12
|
import { DEFAULT_SETTINGS, hydrateSettings } from "./config.js";
|
|
13
|
-
async function fetchOrdersData(settings) {
|
|
13
|
+
async function fetchOrdersData(settings, fetchWidgetData) {
|
|
14
14
|
const body = {
|
|
15
15
|
entityType: "sales:orders",
|
|
16
16
|
metric: {
|
|
@@ -23,16 +23,7 @@ async function fetchOrdersData(settings) {
|
|
|
23
23
|
},
|
|
24
24
|
comparison: settings.showComparison ? { type: "previous_period" } : void 0
|
|
25
25
|
};
|
|
26
|
-
|
|
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;
|
|
26
|
+
return fetchWidgetData(body);
|
|
36
27
|
}
|
|
37
28
|
const OrdersKpiWidget = ({
|
|
38
29
|
mode,
|
|
@@ -47,12 +38,13 @@ const OrdersKpiWidget = ({
|
|
|
47
38
|
const [trend, setTrend] = React.useState(void 0);
|
|
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 data = await fetchOrdersData(hydrated);
|
|
47
|
+
const data = await fetchOrdersData(hydrated, fetchWidgetData);
|
|
56
48
|
setValue(data.value);
|
|
57
49
|
if (data.comparison) {
|
|
58
50
|
setTrend({
|
|
@@ -69,7 +61,7 @@ const OrdersKpiWidget = ({
|
|
|
69
61
|
setLoading(false);
|
|
70
62
|
onRefreshStateChange?.(false);
|
|
71
63
|
}
|
|
72
|
-
}, [hydrated, onRefreshStateChange, t]);
|
|
64
|
+
}, [hydrated, fetchWidgetData, onRefreshStateChange, t]);
|
|
73
65
|
React.useEffect(() => {
|
|
74
66
|
refresh().catch(() => {
|
|
75
67
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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 {
|
|
5
|
-
"mappings": ";
|
|
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 OrdersKpiSettings } from './config'\nimport type { WidgetDataResponse } from '../../../services/widgetDataService'\n\nasync function fetchOrdersData(settings: OrdersKpiSettings, fetchWidgetData: WidgetDataFetcher): 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 return fetchWidgetData<WidgetDataResponse>(body)\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 fetchWidgetData = useWidgetData()\n const refresh = React.useCallback(async () => {\n onRefreshStateChange?.(true)\n setLoading(true)\n setError(null)\n try {\n const data = await fetchOrdersData(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 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, 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=\"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-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 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": ";AA+EQ,cAOE,YAPF;AA7ER,YAAY,WAAW;AAEvB,SAAS,qBAA6C;AACtD,SAAS,YAAY;AACrB,SAAS,eAA8B;AACvC;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,OACK;AACP,SAAS,kBAAkB,uBAA+C;AAG1E,eAAe,gBAAgB,UAA6B,iBAAiE;AAC3H,QAAM,OAAO;AAAA,IACX,YAAY;AAAA,IACZ,QAAQ;AAAA,MACN,OAAO;AAAA,MACP,WAAW;AAAA,IACb;AAAA,IACA,WAAW;AAAA,MACT,OAAO;AAAA,MACP,QAAQ,SAAS;AAAA,IACnB;AAAA,IACA,YAAY,SAAS,iBAAiB,EAAE,MAAM,kBAAkB,IAAI;AAAA,EACtE;AAEA,SAAO,gBAAoC,IAAI;AACjD;AAEA,MAAM,kBAA8E,CAAC;AAAA,EACnF;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AACF,MAAM;AACJ,QAAM,IAAI,KAAK;AACf,QAAM,WAAW,MAAM,QAAQ,MAAM,gBAAgB,QAAQ,GAAG,CAAC,QAAQ,CAAC;AAC1E,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAC5D,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAA+B,MAAS;AACxE,QAAM,CAAC,SAAS,UAAU,IAAI,MAAM,SAAS,IAAI;AACjD,QAAM,CAAC,OAAO,QAAQ,IAAI,MAAM,SAAwB,IAAI;AAE5D,QAAM,kBAAkB,cAAc;AACtC,QAAM,UAAU,MAAM,YAAY,YAAY;AAC5C,2BAAuB,IAAI;AAC3B,eAAW,IAAI;AACf,aAAS,IAAI;AACb,QAAI;AACF,YAAM,OAAO,MAAM,gBAAgB,UAAU,eAAe;AAC5D,eAAS,KAAK,KAAK;AACnB,UAAI,KAAK,YAAY;AACnB,iBAAS;AAAA,UACP,OAAO,KAAK,WAAW;AAAA,UACvB,WAAW,KAAK,WAAW;AAAA,QAC7B,CAAC;AAAA,MACH,OAAO;AACL,iBAAS,MAAS;AAAA,MACpB;AAAA,IACF,SAAS,KAAK;AACZ,cAAQ,MAAM,kCAAkC,GAAG;AACnD,eAAS,EAAE,gDAAgD,qBAAqB,CAAC;AAAA,IACnF,UAAE;AACA,iBAAW,KAAK;AAChB,6BAAuB,KAAK;AAAA,IAC9B;AAAA,EACF,GAAG,CAAC,UAAU,iBAAiB,sBAAsB,CAAC,CAAC;AAEvD,QAAM,UAAU,MAAM;AACpB,YAAQ,EAAE,MAAM,MAAM;AAAA,IAAC,CAAC;AAAA,EAC1B,GAAG,CAAC,SAAS,YAAY,CAAC;AAE1B,MAAI,SAAS,YAAY;AACvB,WACE,qBAAC,SAAI,WAAU,qBACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,IAAG;AAAA,UACH,OAAO,EAAE,2CAA2C,YAAY;AAAA,UAChE,OAAO,SAAS;AAAA,UAChB,UAAU,CAAC,cAA+B,iBAAiB,EAAE,GAAG,UAAU,UAAU,CAAC;AAAA;AAAA,MACvF;AAAA,MACA,oBAAC,SAAI,WAAU,eACb,+BAAC,WAAM,WAAU,mCACf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,SAAS;AAAA,YAClB,UAAU,CAAC,MAAM,iBAAiB,EAAE,GAAG,UAAU,gBAAgB,EAAE,OAAO,QAAQ,CAAC;AAAA,YACnF,WAAU;AAAA;AAAA,QACZ;AAAA,QACC,EAAE,gDAAgD,iBAAiB;AAAA,SACtE,GACF;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,sBAAsB,sBAAsB,SAAS,SAAS;AACpE,QAAM,kBAAkB,SAAS,iBAC7B,EAAE,oBAAoB,KAAK,oBAAoB,QAAQ,IACvD;AAEJ,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,cACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,SAAS;AAAA,UAChB,UAAU,CAAC,cAAc,iBAAiB,EAAE,GAAG,UAAU,UAAU,CAAC;AAAA;AAAA,MACtE;AAAA;AAAA,EAEJ;AAEJ;AAEA,IAAO,wBAAQ;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|