@open-mercato/core 0.4.6-develop-f7d3079656 → 0.4.6-develop-0861f05ea9

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 (107) hide show
  1. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +17 -154
  2. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +3 -3
  3. package/dist/modules/currencies/backend/exchange-rates/create/page.js +14 -152
  4. package/dist/modules/currencies/backend/exchange-rates/create/page.js.map +2 -2
  5. package/dist/modules/currencies/lib/exchangeRateFormConfig.js +167 -0
  6. package/dist/modules/currencies/lib/exchangeRateFormConfig.js.map +7 -0
  7. package/dist/modules/customers/api/dashboard/widgets/utils.js +1 -34
  8. package/dist/modules/customers/api/dashboard/widgets/utils.js.map +2 -2
  9. package/dist/modules/customers/commands/activities.js +3 -8
  10. package/dist/modules/customers/commands/activities.js.map +2 -2
  11. package/dist/modules/customers/commands/comments.js +2 -8
  12. package/dist/modules/customers/commands/comments.js.map +2 -2
  13. package/dist/modules/dashboards/lib/widgetScope.js +38 -0
  14. package/dist/modules/dashboards/lib/widgetScope.js.map +7 -0
  15. package/dist/modules/entities/lib/makeActivityRoute.js +265 -0
  16. package/dist/modules/entities/lib/makeActivityRoute.js.map +7 -0
  17. package/dist/modules/resources/api/activities.js +24 -232
  18. package/dist/modules/resources/api/activities.js.map +2 -2
  19. package/dist/modules/resources/commands/activities.js +3 -8
  20. package/dist/modules/resources/commands/activities.js.map +2 -2
  21. package/dist/modules/resources/commands/comments.js +2 -8
  22. package/dist/modules/resources/commands/comments.js.map +2 -2
  23. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js +27 -182
  24. package/dist/modules/sales/api/dashboard/widgets/new-orders/route.js.map +2 -2
  25. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js +28 -183
  26. package/dist/modules/sales/api/dashboard/widgets/new-quotes/route.js.map +2 -2
  27. package/dist/modules/sales/api/order-line-statuses/route.js +15 -194
  28. package/dist/modules/sales/api/order-line-statuses/route.js.map +2 -2
  29. package/dist/modules/sales/api/order-lines/route.js +15 -281
  30. package/dist/modules/sales/api/order-lines/route.js.map +2 -2
  31. package/dist/modules/sales/api/order-statuses/route.js +15 -194
  32. package/dist/modules/sales/api/order-statuses/route.js.map +2 -2
  33. package/dist/modules/sales/api/payment-statuses/route.js +15 -194
  34. package/dist/modules/sales/api/payment-statuses/route.js.map +2 -2
  35. package/dist/modules/sales/api/quote-lines/route.js +15 -279
  36. package/dist/modules/sales/api/quote-lines/route.js.map +2 -2
  37. package/dist/modules/sales/api/shipment-statuses/route.js +15 -194
  38. package/dist/modules/sales/api/shipment-statuses/route.js.map +2 -2
  39. package/dist/modules/sales/components/PaymentMethodsSettings.js +3 -84
  40. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  41. package/dist/modules/sales/components/ProviderFieldInput.js +86 -0
  42. package/dist/modules/sales/components/ProviderFieldInput.js.map +7 -0
  43. package/dist/modules/sales/components/ShippingMethodsSettings.js +3 -82
  44. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  45. package/dist/modules/sales/lib/makeSalesLineRoute.js +308 -0
  46. package/dist/modules/sales/lib/makeSalesLineRoute.js.map +7 -0
  47. package/dist/modules/sales/lib/makeStatusDictionaryRoute.js +206 -0
  48. package/dist/modules/sales/lib/makeStatusDictionaryRoute.js.map +7 -0
  49. package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js +178 -0
  50. package/dist/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.js.map +7 -0
  51. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js +1 -39
  52. package/dist/modules/sales/widgets/dashboard/new-orders/widget.client.js.map +2 -2
  53. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js +1 -39
  54. package/dist/modules/sales/widgets/dashboard/new-quotes/widget.client.js.map +2 -2
  55. package/dist/modules/sales/widgets/dashboard/shared.js +46 -0
  56. package/dist/modules/sales/widgets/dashboard/shared.js.map +7 -0
  57. package/dist/modules/staff/api/activities.js +24 -232
  58. package/dist/modules/staff/api/activities.js.map +2 -2
  59. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +14 -34
  60. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  61. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +15 -34
  62. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  63. package/dist/modules/staff/commands/activities.js +3 -8
  64. package/dist/modules/staff/commands/activities.js.map +2 -2
  65. package/dist/modules/staff/commands/comments.js +2 -8
  66. package/dist/modules/staff/commands/comments.js.map +2 -2
  67. package/dist/modules/staff/lib/leaveRequestHelpers.js +41 -0
  68. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +7 -0
  69. package/package.json +2 -2
  70. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +20 -180
  71. package/src/modules/currencies/backend/exchange-rates/create/page.tsx +16 -175
  72. package/src/modules/currencies/lib/exchangeRateFormConfig.ts +200 -0
  73. package/src/modules/customers/api/dashboard/widgets/utils.ts +1 -53
  74. package/src/modules/customers/commands/activities.ts +2 -8
  75. package/src/modules/customers/commands/comments.ts +2 -8
  76. package/src/modules/dashboards/i18n/de.json +3 -0
  77. package/src/modules/dashboards/i18n/en.json +3 -0
  78. package/src/modules/dashboards/i18n/es.json +3 -0
  79. package/src/modules/dashboards/i18n/pl.json +3 -0
  80. package/src/modules/dashboards/lib/widgetScope.ts +53 -0
  81. package/src/modules/entities/lib/makeActivityRoute.ts +327 -0
  82. package/src/modules/resources/api/activities.ts +25 -269
  83. package/src/modules/resources/commands/activities.ts +2 -7
  84. package/src/modules/resources/commands/comments.ts +2 -8
  85. package/src/modules/sales/api/dashboard/widgets/new-orders/route.ts +29 -244
  86. package/src/modules/sales/api/dashboard/widgets/new-quotes/route.ts +30 -245
  87. package/src/modules/sales/api/order-line-statuses/route.ts +16 -209
  88. package/src/modules/sales/api/order-lines/route.ts +16 -300
  89. package/src/modules/sales/api/order-statuses/route.ts +16 -209
  90. package/src/modules/sales/api/payment-statuses/route.ts +16 -209
  91. package/src/modules/sales/api/quote-lines/route.ts +16 -298
  92. package/src/modules/sales/api/shipment-statuses/route.ts +16 -209
  93. package/src/modules/sales/components/PaymentMethodsSettings.tsx +3 -88
  94. package/src/modules/sales/components/ProviderFieldInput.tsx +85 -0
  95. package/src/modules/sales/components/ShippingMethodsSettings.tsx +3 -86
  96. package/src/modules/sales/lib/makeSalesLineRoute.ts +345 -0
  97. package/src/modules/sales/lib/makeStatusDictionaryRoute.ts +229 -0
  98. package/src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts +247 -0
  99. package/src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx +7 -50
  100. package/src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx +7 -49
  101. package/src/modules/sales/widgets/dashboard/shared.ts +44 -0
  102. package/src/modules/staff/api/activities.ts +25 -269
  103. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +15 -69
  104. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +16 -65
  105. package/src/modules/staff/commands/activities.ts +2 -7
  106. package/src/modules/staff/commands/comments.ts +2 -8
  107. package/src/modules/staff/lib/leaveRequestHelpers.ts +78 -0
@@ -0,0 +1,178 @@
1
+ import { createHash } from "node:crypto";
2
+ import { NextResponse } from "next/server";
3
+ import { z } from "zod";
4
+ import { resolveTranslations } from "@open-mercato/shared/lib/i18n/server";
5
+ import { runWithCacheTenant } from "@open-mercato/cache";
6
+ import { CrudHttpError } from "@open-mercato/shared/lib/crud/errors";
7
+ import { findAndCountWithDecryption } from "@open-mercato/shared/lib/encryption/find";
8
+ import { resolveDateRange } from "@open-mercato/ui/backend/date-range";
9
+ import { resolveWidgetScope } from "@open-mercato/core/modules/dashboards/lib/widgetScope";
10
+ const WIDGET_CACHE_TTL = 12e4;
11
+ const WIDGET_CACHE_SEGMENT_TTL = 864e5;
12
+ const WIDGET_CACHE_SEGMENT_KEY = "widget-data:__segment__";
13
+ const querySchema = z.object({
14
+ limit: z.coerce.number().min(1).max(20).default(5),
15
+ datePeriod: z.enum(["last24h", "last7d", "last30d", "custom"]).default("last24h"),
16
+ customFrom: z.string().optional(),
17
+ customTo: z.string().optional(),
18
+ tenantId: z.string().uuid().optional(),
19
+ organizationId: z.string().uuid().optional()
20
+ });
21
+ function normalizeOrganizationIds(organizationIds) {
22
+ if (organizationIds === null) return null;
23
+ const set = new Set(organizationIds);
24
+ return Array.from(set).sort((a, b) => a.localeCompare(b));
25
+ }
26
+ function buildCacheKey(cacheId, params) {
27
+ const hash = createHash("sha256");
28
+ hash.update(
29
+ JSON.stringify({
30
+ widget: cacheId,
31
+ ...params,
32
+ organizationIds: normalizeOrganizationIds(params.organizationIds)
33
+ })
34
+ );
35
+ return `widget-data:${hash.digest("hex").slice(0, 16)}`;
36
+ }
37
+ async function resolveContext(req, translate) {
38
+ const url = new URL(req.url);
39
+ const rawQuery = {};
40
+ for (const [key, value] of url.searchParams.entries()) rawQuery[key] = value;
41
+ const parsed = querySchema.safeParse(rawQuery);
42
+ if (!parsed.success) {
43
+ throw new CrudHttpError(400, { error: translate("sales.errors.invalid_query", "Invalid query parameters") });
44
+ }
45
+ const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {
46
+ tenantId: parsed.data.tenantId ?? null,
47
+ organizationId: parsed.data.organizationId ?? null
48
+ });
49
+ return {
50
+ container,
51
+ em,
52
+ tenantId,
53
+ organizationIds,
54
+ limit: parsed.data.limit,
55
+ datePeriod: parsed.data.datePeriod,
56
+ customFrom: parsed.data.customFrom,
57
+ customTo: parsed.data.customTo
58
+ };
59
+ }
60
+ const widgetErrorSchema = z.object({ error: z.string() });
61
+ function makeDashboardWidgetRoute(config) {
62
+ const cacheTags = ["widget-data", ...config.cacheTags];
63
+ const metadata = {
64
+ GET: { requireAuth: true, requireFeatures: ["dashboards.view", config.feature] }
65
+ };
66
+ async function GET(req) {
67
+ const { translate } = await resolveTranslations();
68
+ try {
69
+ const { container, em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(
70
+ req,
71
+ translate
72
+ );
73
+ const range = (() => {
74
+ if (datePeriod === "custom") {
75
+ const from = customFrom ? new Date(customFrom) : /* @__PURE__ */ new Date(0);
76
+ const to = customTo ? new Date(customTo) : /* @__PURE__ */ new Date();
77
+ return { start: from, end: to };
78
+ }
79
+ const preset = datePeriod === "last7d" ? "last_7_days" : datePeriod === "last30d" ? "last_30_days" : "today";
80
+ return resolveDateRange(preset);
81
+ })();
82
+ let cache = null;
83
+ try {
84
+ cache = container.resolve("cache");
85
+ } catch {
86
+ cache = null;
87
+ }
88
+ const cacheKey = buildCacheKey(config.cacheId, { tenantId, organizationIds, limit, datePeriod, customFrom, customTo });
89
+ const tenantScope = tenantId ?? null;
90
+ if (cache) {
91
+ try {
92
+ const cached = await runWithCacheTenant(tenantScope, () => cache.get(cacheKey));
93
+ if (cached && typeof cached === "object" && "items" in cached) {
94
+ return NextResponse.json(cached);
95
+ }
96
+ } catch (err) {
97
+ console.debug("[widget-cache] read failed", err);
98
+ }
99
+ }
100
+ const where = {
101
+ tenantId,
102
+ deletedAt: null,
103
+ createdAt: { $gte: range.start, $lte: range.end }
104
+ };
105
+ if (Array.isArray(organizationIds)) {
106
+ const unique = Array.from(new Set(organizationIds));
107
+ where.organizationId = unique.length === 1 ? unique[0] : { $in: unique };
108
+ }
109
+ const organizationIdScope = Array.isArray(organizationIds) && organizationIds.length === 1 ? organizationIds[0] : null;
110
+ const [entities, total] = await findAndCountWithDecryption(
111
+ em,
112
+ config.entity,
113
+ where,
114
+ { limit, orderBy: { createdAt: "desc" } },
115
+ { tenantId, organizationId: organizationIdScope }
116
+ );
117
+ const items = entities.map(config.mapItem);
118
+ const response = {
119
+ items,
120
+ total,
121
+ dateRange: { from: range.start.toISOString(), to: range.end.toISOString() }
122
+ };
123
+ if (cache) {
124
+ try {
125
+ await runWithCacheTenant(tenantScope, () => cache.set(cacheKey, response, { ttl: WIDGET_CACHE_TTL, tags: cacheTags }));
126
+ await runWithCacheTenant(tenantScope, () => cache.set(
127
+ WIDGET_CACHE_SEGMENT_KEY,
128
+ { updatedAt: response.dateRange.to },
129
+ { ttl: WIDGET_CACHE_SEGMENT_TTL, tags: ["widget-data"] }
130
+ ));
131
+ } catch (err) {
132
+ console.debug("[widget-cache] write failed", err);
133
+ }
134
+ }
135
+ return NextResponse.json(response);
136
+ } catch (err) {
137
+ if (err instanceof CrudHttpError) {
138
+ return NextResponse.json(err.body, { status: err.status });
139
+ }
140
+ console.error(`${config.errorPrefix} failed`, err);
141
+ return NextResponse.json(
142
+ { error: translate(`${config.errorPrefix}.error`, config.openApi.errorFallback) },
143
+ { status: 500 }
144
+ );
145
+ }
146
+ }
147
+ const responseSchema = z.object({
148
+ items: z.array(config.itemSchema),
149
+ total: z.number(),
150
+ dateRange: z.object({
151
+ from: z.string(),
152
+ to: z.string()
153
+ })
154
+ });
155
+ const openApi = {
156
+ tag: "Sales",
157
+ summary: config.openApi.summary,
158
+ description: config.openApi.description,
159
+ methods: {
160
+ GET: {
161
+ summary: config.openApi.getSummary,
162
+ query: querySchema,
163
+ responses: [{ status: 200, description: config.openApi.itemDescription, schema: responseSchema }],
164
+ errors: [
165
+ { status: 400, description: "Invalid query parameters", schema: widgetErrorSchema },
166
+ { status: 401, description: "Unauthorized", schema: widgetErrorSchema },
167
+ { status: 403, description: "Forbidden", schema: widgetErrorSchema },
168
+ { status: 500, description: "Widget failed to load", schema: widgetErrorSchema }
169
+ ]
170
+ }
171
+ }
172
+ };
173
+ return { GET, metadata, openApi };
174
+ }
175
+ export {
176
+ makeDashboardWidgetRoute
177
+ };
178
+ //# sourceMappingURL=makeDashboardWidgetRoute.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sales/widgets/dashboard/makeDashboardWidgetRoute.ts"],
4
+ "sourcesContent": ["import { createHash } from 'node:crypto'\nimport { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport type { EntityName, FilterQuery, FindOptions } from '@mikro-orm/core'\nimport type { CacheStrategy } from '@open-mercato/cache'\nimport type { OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { runWithCacheTenant } from '@open-mercato/cache'\nimport { CrudHttpError } from '@open-mercato/shared/lib/crud/errors'\nimport { findAndCountWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { resolveDateRange } from '@open-mercato/ui/backend/date-range'\nimport type { DatePeriodOption } from '../../api/dashboard/widgets/helpers'\nimport { resolveWidgetScope, type WidgetScopeContext } from '@open-mercato/core/modules/dashboards/lib/widgetScope'\n\nconst WIDGET_CACHE_TTL = 120_000\nconst WIDGET_CACHE_SEGMENT_TTL = 86_400_000\nconst WIDGET_CACHE_SEGMENT_KEY = 'widget-data:__segment__'\n\nconst querySchema = z.object({\n limit: z.coerce.number().min(1).max(20).default(5),\n datePeriod: z.enum(['last24h', 'last7d', 'last30d', 'custom']).default('last24h'),\n customFrom: z.string().optional(),\n customTo: z.string().optional(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\ntype WidgetContext = WidgetScopeContext & {\n limit: number\n datePeriod: DatePeriodOption\n customFrom?: string\n customTo?: string\n}\n\nfunction normalizeOrganizationIds(organizationIds: string[] | null): string[] | null {\n if (organizationIds === null) return null\n const set = new Set(organizationIds)\n return Array.from(set).sort((a, b) => a.localeCompare(b))\n}\n\nfunction buildCacheKey(\n cacheId: string,\n params: {\n tenantId: string\n organizationIds: string[] | null\n limit: number\n datePeriod: DatePeriodOption\n customFrom?: string\n customTo?: string\n }\n): string {\n const hash = createHash('sha256')\n hash.update(\n JSON.stringify({\n widget: cacheId,\n ...params,\n organizationIds: normalizeOrganizationIds(params.organizationIds),\n })\n )\n return `widget-data:${hash.digest('hex').slice(0, 16)}`\n}\n\nasync function resolveContext(req: Request, translate: (key: string, fallback?: string) => string): Promise<WidgetContext> {\n const url = new URL(req.url)\n const rawQuery: Record<string, string> = {}\n for (const [key, value] of url.searchParams.entries()) rawQuery[key] = value\n const parsed = querySchema.safeParse(rawQuery)\n if (!parsed.success) {\n throw new CrudHttpError(400, { error: translate('sales.errors.invalid_query', 'Invalid query parameters') })\n }\n\n const { container, em, tenantId, organizationIds } = await resolveWidgetScope(req, translate, {\n tenantId: parsed.data.tenantId ?? null,\n organizationId: parsed.data.organizationId ?? null,\n })\n\n return {\n container,\n em,\n tenantId,\n organizationIds,\n limit: parsed.data.limit,\n datePeriod: parsed.data.datePeriod,\n customFrom: parsed.data.customFrom,\n customTo: parsed.data.customTo,\n }\n}\n\nexport interface DashboardWidgetRouteConfig<TEntity extends object, TItem extends Record<string, unknown>> {\n entity: { new (...args: unknown[]): TEntity }\n cacheId: string\n cacheTags: string[]\n feature: string\n mapItem: (entity: Record<string, unknown>) => TItem\n itemSchema: z.ZodTypeAny\n openApi: {\n summary: string\n description: string\n getSummary: string\n itemDescription: string\n errorFallback: string\n }\n errorPrefix: string\n}\n\ntype WidgetResponse<TItem> = {\n items: TItem[]\n total: number\n dateRange: {\n from: string\n to: string\n }\n}\n\nconst widgetErrorSchema = z.object({ error: z.string() })\n\nexport function makeDashboardWidgetRoute<TEntity extends object, TItem extends Record<string, unknown>>(config: DashboardWidgetRouteConfig<TEntity, TItem>) {\n const cacheTags = ['widget-data', ...config.cacheTags]\n\n const metadata = {\n GET: { requireAuth: true, requireFeatures: ['dashboards.view', config.feature] },\n }\n\n async function GET(req: Request) {\n const { translate } = await resolveTranslations()\n try {\n const { container, em, tenantId, organizationIds, limit, datePeriod, customFrom, customTo } = await resolveContext(\n req,\n translate\n )\n const range = (() => {\n if (datePeriod === 'custom') {\n const from = customFrom ? new Date(customFrom) : new Date(0)\n const to = customTo ? new Date(customTo) : new Date()\n return { start: from, end: to }\n }\n const preset = datePeriod === 'last7d' ? 'last_7_days' : datePeriod === 'last30d' ? 'last_30_days' : 'today'\n return resolveDateRange(preset)\n })()\n\n let cache: CacheStrategy | null = null\n try {\n cache = container.resolve<CacheStrategy>('cache')\n } catch {\n cache = null\n }\n\n const cacheKey = buildCacheKey(config.cacheId, { tenantId, organizationIds, limit, datePeriod, customFrom, customTo })\n const tenantScope = tenantId ?? null\n\n if (cache) {\n try {\n const cached = await runWithCacheTenant(tenantScope, () => cache!.get(cacheKey))\n if (cached && typeof cached === 'object' && 'items' in (cached as object)) {\n return NextResponse.json(cached)\n }\n } catch (err) {\n console.debug('[widget-cache] read failed', err)\n }\n }\n\n const where: FilterQuery<{ tenantId: string; deletedAt: Date | null; createdAt: Date; organizationId: string }> = {\n tenantId,\n deletedAt: null,\n createdAt: { $gte: range.start, $lte: range.end },\n }\n\n if (Array.isArray(organizationIds)) {\n const unique = Array.from(new Set(organizationIds))\n where.organizationId = unique.length === 1 ? unique[0] : { $in: unique }\n }\n\n const organizationIdScope = Array.isArray(organizationIds) && organizationIds.length === 1 ? organizationIds[0] : null\n // Generic boundary: config.entity is a class constructor from the factory caller,\n // so we cast to EntityName/FilterQuery at the call site (matching findAndCountWithDecryption's own internal casts)\n const [entities, total] = await findAndCountWithDecryption(\n em,\n config.entity as EntityName<TEntity>,\n where as FilterQuery<TEntity>,\n { limit, orderBy: { createdAt: 'desc' as const } } as FindOptions<TEntity>,\n { tenantId, organizationId: organizationIdScope },\n )\n\n const items = (entities as unknown as Record<string, unknown>[]).map(config.mapItem)\n\n const response: WidgetResponse<TItem> = {\n items,\n total,\n dateRange: { from: range.start.toISOString(), to: range.end.toISOString() },\n }\n\n if (cache) {\n try {\n await runWithCacheTenant(tenantScope, () => cache!.set(cacheKey, response, { ttl: WIDGET_CACHE_TTL, tags: cacheTags }))\n await runWithCacheTenant(tenantScope, () => cache!.set(\n WIDGET_CACHE_SEGMENT_KEY,\n { updatedAt: response.dateRange.to },\n { ttl: WIDGET_CACHE_SEGMENT_TTL, tags: ['widget-data'] },\n ))\n } catch (err) {\n console.debug('[widget-cache] write failed', err)\n }\n }\n\n return NextResponse.json(response)\n } catch (err) {\n if (err instanceof CrudHttpError) {\n return NextResponse.json(err.body, { status: err.status })\n }\n console.error(`${config.errorPrefix} failed`, err)\n return NextResponse.json(\n { error: translate(`${config.errorPrefix}.error`, config.openApi.errorFallback) },\n { status: 500 },\n )\n }\n }\n\n const responseSchema = z.object({\n items: z.array(config.itemSchema),\n total: z.number(),\n dateRange: z.object({\n from: z.string(),\n to: z.string(),\n }),\n })\n\n const openApi: OpenApiRouteDoc = {\n tag: 'Sales',\n summary: config.openApi.summary,\n description: config.openApi.description,\n methods: {\n GET: {\n summary: config.openApi.getSummary,\n query: querySchema,\n responses: [{ status: 200, description: config.openApi.itemDescription, schema: responseSchema }],\n errors: [\n { status: 400, description: 'Invalid query parameters', schema: widgetErrorSchema },\n { status: 401, description: 'Unauthorized', schema: widgetErrorSchema },\n { status: 403, description: 'Forbidden', schema: widgetErrorSchema },\n { status: 500, description: 'Widget failed to load', schema: widgetErrorSchema },\n ],\n },\n },\n }\n\n return { GET, metadata, openApi }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -8,9 +8,7 @@ import { Badge } from "@open-mercato/ui/primitives/badge";
8
8
  import { formatRelativeTime } from "@open-mercato/shared/lib/time";
9
9
  import { useT } from "@open-mercato/shared/lib/i18n/context";
10
10
  import { DEFAULT_SETTINGS, hydrateSalesNewOrdersSettings } from "./config.js";
11
- function readString(value) {
12
- return typeof value === "string" ? value : null;
13
- }
11
+ import { readString, toDateInputValue, openNativeDatePicker, formatAmount } from "../shared.js";
14
12
  function parseNewOrderItems(payload) {
15
13
  const rawItems = Array.isArray(payload?.items) ? payload?.items : [];
16
14
  return rawItems.map((item) => {
@@ -52,42 +50,6 @@ async function loadNewOrders(settings) {
52
50
  function resolveDetailHref(item) {
53
51
  return item.id ? `/backend/sales/orders/${encodeURIComponent(item.id)}` : null;
54
52
  }
55
- function toDateInputValue(value) {
56
- if (!value) return "";
57
- const parsed = new Date(value);
58
- if (Number.isNaN(parsed.getTime())) return "";
59
- const year = String(parsed.getFullYear());
60
- const month = String(parsed.getMonth() + 1).padStart(2, "0");
61
- const day = String(parsed.getDate()).padStart(2, "0");
62
- return `${year}-${month}-${day}`;
63
- }
64
- function openNativeDatePicker(event) {
65
- const input = event.currentTarget;
66
- if (typeof input.showPicker === "function") {
67
- input.showPicker();
68
- }
69
- }
70
- function formatAmount(value, currency, locale) {
71
- const numeric = Number(value);
72
- if (!Number.isFinite(numeric)) return "--";
73
- try {
74
- if (currency && currency.trim().length > 0) {
75
- return new Intl.NumberFormat(locale ?? void 0, {
76
- style: "currency",
77
- currency,
78
- minimumFractionDigits: 2,
79
- maximumFractionDigits: 2
80
- }).format(numeric);
81
- }
82
- return new Intl.NumberFormat(locale ?? void 0, {
83
- style: "decimal",
84
- minimumFractionDigits: 0,
85
- maximumFractionDigits: 2
86
- }).format(numeric);
87
- } catch {
88
- return String(numeric);
89
- }
90
- }
91
53
  const SalesNewOrdersWidget = ({
92
54
  mode,
93
55
  settings = DEFAULT_SETTINGS,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/sales/widgets/dashboard/new-orders/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\r\n\r\nimport * as React from 'react'\r\nimport Link from 'next/link'\r\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\r\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { formatRelativeTime } from '@open-mercato/shared/lib/time'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DEFAULT_SETTINGS, hydrateSalesNewOrdersSettings, type DatePeriodOption, type SalesNewOrdersSettings } from './config'\n\r\ntype NewOrderItem = {\r\n id: string\r\n orderNumber: string\r\n status: string | null\r\n customerName: string | null\r\n customerEntityId: string | null\r\n netAmount: string\r\n grossAmount: string\r\n currency: string | null\r\n createdAt: string\r\n}\r\n\r\ntype NewOrdersApiPayload = {\r\n items?: unknown[]\r\n error?: string\r\n}\r\n\r\nfunction readString(value: unknown): string | null {\r\n return typeof value === 'string' ? value : null\r\n}\r\n\r\nfunction parseNewOrderItems(payload: NewOrdersApiPayload | null): NewOrderItem[] {\r\n const rawItems = Array.isArray(payload?.items) ? payload?.items : []\r\n return rawItems\r\n .map((item) => {\r\n if (!item || typeof item !== 'object') return null\r\n const data = item as Record<string, unknown>\r\n const id = readString(data.id)\r\n const orderNumber = readString(data.orderNumber)\r\n const createdAt = readString(data.createdAt)\r\n if (!id || !orderNumber || !createdAt) return null\r\n return {\r\n id,\r\n orderNumber,\r\n status: readString(data.status),\r\n customerName: readString(data.customerName),\r\n customerEntityId: readString(data.customerEntityId),\r\n netAmount: readString(data.netAmount) ?? '0',\r\n grossAmount: readString(data.grossAmount) ?? '0',\r\n currency: readString(data.currency),\r\n createdAt,\r\n }\r\n })\r\n .filter((item): item is NewOrderItem => !!item)\r\n}\r\n\r\nasync function loadNewOrders(settings: SalesNewOrdersSettings): Promise<NewOrderItem[]> {\r\n const params = new URLSearchParams({\r\n limit: String(settings.pageSize),\r\n datePeriod: settings.datePeriod,\r\n })\r\n if (settings.datePeriod === 'custom') {\r\n if (settings.customFrom) params.set('customFrom', settings.customFrom)\r\n if (settings.customTo) params.set('customTo', settings.customTo)\r\n }\r\n\r\n const call = await apiCall<NewOrdersApiPayload>(`/api/sales/dashboard/widgets/new-orders?${params.toString()}`)\r\n if (!call.ok) {\r\n const message =\r\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\r\n ? ((call.result as Record<string, unknown>).error as string)\r\n : `Request failed with status ${call.status}`\r\n throw new Error(message)\r\n }\r\n return parseNewOrderItems(call.result ?? null)\r\n}\r\n\r\nfunction resolveDetailHref(item: NewOrderItem): string | null {\r\n return item.id ? `/backend/sales/orders/${encodeURIComponent(item.id)}` : null\r\n}\r\n\r\nfunction toDateInputValue(value: string | null | undefined): string {\r\n if (!value) return ''\r\n const parsed = new Date(value)\r\n if (Number.isNaN(parsed.getTime())) return ''\r\n const year = String(parsed.getFullYear())\r\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\r\n const day = String(parsed.getDate()).padStart(2, '0')\r\n return `${year}-${month}-${day}`\r\n}\r\n\r\nfunction openNativeDatePicker(event: React.SyntheticEvent<HTMLInputElement>) {\r\n const input = event.currentTarget\r\n if (typeof input.showPicker === 'function') {\r\n input.showPicker()\r\n }\r\n}\r\n\r\nfunction formatAmount(value: string, currency: string | null, locale?: string): string {\r\n const numeric = Number(value)\r\n if (!Number.isFinite(numeric)) return '--'\r\n try {\r\n if (currency && currency.trim().length > 0) {\r\n return new Intl.NumberFormat(locale ?? undefined, {\r\n style: 'currency',\r\n currency,\r\n minimumFractionDigits: 2,\r\n maximumFractionDigits: 2,\r\n }).format(numeric)\r\n }\r\n return new Intl.NumberFormat(locale ?? undefined, {\r\n style: 'decimal',\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 2,\r\n }).format(numeric)\r\n } catch {\r\n return String(numeric)\r\n }\r\n}\r\n\r\n\r\n\r\nconst SalesNewOrdersWidget: React.FC<DashboardWidgetComponentProps<SalesNewOrdersSettings>> = ({\r\n mode,\r\n settings = DEFAULT_SETTINGS,\r\n onSettingsChange,\r\n refreshToken,\r\n onRefreshStateChange,\r\n}) => {\r\n const translate = useT()\r\n const hydrated = React.useMemo(() => hydrateSalesNewOrdersSettings(settings), [settings])\r\n const [items, setItems] = React.useState<NewOrderItem[]>([])\r\n const [loading, setLoading] = React.useState(true)\r\n const [error, setError] = React.useState<string | null>(null)\r\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\r\n\r\n React.useEffect(() => {\r\n if (typeof navigator !== 'undefined') {\r\n setLocale(navigator.language)\r\n }\r\n }, [])\r\n\r\n const refresh = React.useCallback(async () => {\r\n onRefreshStateChange?.(true)\r\n setLoading(true)\r\n setError(null)\r\n try {\r\n const data = await loadNewOrders(hydrated)\r\n setItems(data)\r\n } catch (err) {\r\n console.error('Failed to load new orders widget data', err)\r\n setError(translate('sales.widgets.newOrders.error', 'Failed to load orders'))\r\n } finally {\r\n setLoading(false)\r\n onRefreshStateChange?.(false)\r\n }\r\n }, [hydrated, onRefreshStateChange, translate])\r\n\r\n React.useEffect(() => {\r\n refresh().catch(() => {})\r\n }, [refresh, refreshToken])\r\n\r\n if (mode === 'settings') {\r\n return (\r\n <div className=\"space-y-4 text-sm\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.pageSize', 'Number of Orders')}\r\n </label>\r\n <input\r\n id=\"sales-new-orders-page-size\"\r\n type=\"number\"\r\n min={1}\r\n max={20}\r\n className=\"w-24 rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n value={hydrated.pageSize}\r\n onChange={(event) => {\r\n const next = Number(event.target.value)\r\n if (!Number.isFinite(next)) return\r\n const clamped = Math.min(20, Math.max(1, Math.floor(next)))\r\n onSettingsChange?.({ ...hydrated, pageSize: clamped })\r\n }}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-date-period\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.datePeriod', 'Date Period')}\r\n </label>\r\n <select\r\n id=\"sales-new-orders-date-period\"\r\n value={hydrated.datePeriod}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, datePeriod: event.target.value as DatePeriodOption })\r\n }}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n >\r\n <option value=\"last24h\">{translate('sales.widgets.newOrders.settings.last24h', 'Last 24 hours')}</option>\r\n <option value=\"last7d\">{translate('sales.widgets.newOrders.settings.last7d', 'Last 7 days')}</option>\r\n <option value=\"last30d\">{translate('sales.widgets.newOrders.settings.last30d', 'Last 30 days')}</option>\r\n <option value=\"custom\">{translate('sales.widgets.newOrders.settings.custom', 'Custom range')}</option>\r\n </select>\r\n </div>\r\n\r\n {hydrated.datePeriod === 'custom' ? (\r\n <div className=\"grid gap-3\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-custom-from\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.customFrom', 'From')}\r\n </label>\r\n <input\r\n id=\"sales-new-orders-custom-from\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customFrom)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customFrom: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-custom-to\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.customTo', 'To')}\r\n </label>\r\n <input\r\n id=\"sales-new-orders-custom-to\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customTo)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customTo: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n </div>\r\n ) : null}\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return <p className=\"text-sm text-destructive\">{error}</p>\r\n }\r\n\r\n if (loading) {\r\n return (\r\n <div className=\"flex h-32 items-center justify-center\">\r\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\r\n </div>\r\n )\r\n }\r\n\r\n if (!items.length) {\r\n return <p className=\"text-sm text-muted-foreground\">{translate('sales.widgets.newOrders.empty', 'No orders found')}</p>\r\n }\r\n\r\n return (\r\n <ul className=\"space-y-3\">\r\n {items.map((item) => {\r\n const detailHref = resolveDetailHref(item)\r\n const amountLabel = formatAmount(item.grossAmount, item.currency, locale)\r\n const createdLabel = formatRelativeTime(item.createdAt) ?? ''\r\n return (\r\n <li key={item.id} className=\"rounded-md border p-3\">\r\n <div className=\"flex items-start justify-between gap-3\">\r\n <div className=\"space-y-1\">\r\n <div className=\"flex items-center gap-2\">\r\n {detailHref ? (\r\n <Link href={detailHref} className=\"text-sm font-semibold text-foreground hover:underline\">\r\n {item.orderNumber}\r\n </Link>\r\n ) : (\r\n <span className=\"text-sm font-semibold text-foreground\">{item.orderNumber}</span>\r\n )}\r\n {item.status ? (\r\n <Badge variant=\"outline\" className=\"text-xs\">\r\n {item.status}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {item.customerName ?? translate('sales.widgets.newOrders.noCustomer', 'No customer')}\r\n </p>\r\n <p className=\"text-xs text-muted-foreground\">{createdLabel}</p>\r\n </div>\r\n <div className=\"text-right\">\r\n <p className=\"text-sm font-semibold\">{amountLabel}</p>\r\n </div>\r\n </div>\r\n </li>\r\n )\r\n })}\r\n </ul>\r\n )\r\n}\r\n\r\nexport default SalesNewOrdersWidget\r\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\r\n\r\nimport * as React from 'react'\r\nimport Link from 'next/link'\r\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\r\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\r\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\r\nimport { Badge } from '@open-mercato/ui/primitives/badge'\r\nimport { formatRelativeTime } from '@open-mercato/shared/lib/time'\r\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\r\nimport { DEFAULT_SETTINGS, hydrateSalesNewOrdersSettings, type DatePeriodOption, type SalesNewOrdersSettings } from './config'\r\nimport { readString, toDateInputValue, openNativeDatePicker, formatAmount } from '../shared'\r\n\r\ntype NewOrderItem = {\r\n id: string\r\n orderNumber: string\r\n status: string | null\r\n customerName: string | null\r\n customerEntityId: string | null\r\n netAmount: string\r\n grossAmount: string\r\n currency: string | null\r\n createdAt: string\r\n}\r\n\r\ntype NewOrdersApiPayload = {\r\n items?: unknown[]\r\n error?: string\r\n}\r\n\r\nfunction parseNewOrderItems(payload: NewOrdersApiPayload | null): NewOrderItem[] {\r\n const rawItems = Array.isArray(payload?.items) ? payload?.items : []\r\n return rawItems\r\n .map((item) => {\r\n if (!item || typeof item !== 'object') return null\r\n const data = item as Record<string, unknown>\r\n const id = readString(data.id)\r\n const orderNumber = readString(data.orderNumber)\r\n const createdAt = readString(data.createdAt)\r\n if (!id || !orderNumber || !createdAt) return null\r\n return {\r\n id,\r\n orderNumber,\r\n status: readString(data.status),\r\n customerName: readString(data.customerName),\r\n customerEntityId: readString(data.customerEntityId),\r\n netAmount: readString(data.netAmount) ?? '0',\r\n grossAmount: readString(data.grossAmount) ?? '0',\r\n currency: readString(data.currency),\r\n createdAt,\r\n }\r\n })\r\n .filter((item): item is NewOrderItem => !!item)\r\n}\r\n\r\nasync function loadNewOrders(settings: SalesNewOrdersSettings): Promise<NewOrderItem[]> {\r\n const params = new URLSearchParams({\r\n limit: String(settings.pageSize),\r\n datePeriod: settings.datePeriod,\r\n })\r\n if (settings.datePeriod === 'custom') {\r\n if (settings.customFrom) params.set('customFrom', settings.customFrom)\r\n if (settings.customTo) params.set('customTo', settings.customTo)\r\n }\r\n\r\n const call = await apiCall<NewOrdersApiPayload>(`/api/sales/dashboard/widgets/new-orders?${params.toString()}`)\r\n if (!call.ok) {\r\n const message =\r\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\r\n ? ((call.result as Record<string, unknown>).error as string)\r\n : `Request failed with status ${call.status}`\r\n throw new Error(message)\r\n }\r\n return parseNewOrderItems(call.result ?? null)\r\n}\r\n\r\nfunction resolveDetailHref(item: NewOrderItem): string | null {\r\n return item.id ? `/backend/sales/orders/${encodeURIComponent(item.id)}` : null\r\n}\r\n\r\n\r\nconst SalesNewOrdersWidget: React.FC<DashboardWidgetComponentProps<SalesNewOrdersSettings>> = ({\r\n mode,\r\n settings = DEFAULT_SETTINGS,\r\n onSettingsChange,\r\n refreshToken,\r\n onRefreshStateChange,\r\n}) => {\r\n const translate = useT()\r\n const hydrated = React.useMemo(() => hydrateSalesNewOrdersSettings(settings), [settings])\r\n const [items, setItems] = React.useState<NewOrderItem[]>([])\r\n const [loading, setLoading] = React.useState(true)\r\n const [error, setError] = React.useState<string | null>(null)\r\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\r\n\r\n React.useEffect(() => {\r\n if (typeof navigator !== 'undefined') {\r\n setLocale(navigator.language)\r\n }\r\n }, [])\r\n\r\n const refresh = React.useCallback(async () => {\r\n onRefreshStateChange?.(true)\r\n setLoading(true)\r\n setError(null)\r\n try {\r\n const data = await loadNewOrders(hydrated)\r\n setItems(data)\r\n } catch (err) {\r\n console.error('Failed to load new orders widget data', err)\r\n setError(translate('sales.widgets.newOrders.error', 'Failed to load orders'))\r\n } finally {\r\n setLoading(false)\r\n onRefreshStateChange?.(false)\r\n }\r\n }, [hydrated, onRefreshStateChange, translate])\r\n\r\n React.useEffect(() => {\r\n refresh().catch(() => {})\r\n }, [refresh, refreshToken])\r\n\r\n if (mode === 'settings') {\r\n return (\r\n <div className=\"space-y-4 text-sm\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.pageSize', 'Number of Orders')}\r\n </label>\r\n <input\r\n id=\"sales-new-orders-page-size\"\r\n type=\"number\"\r\n min={1}\r\n max={20}\r\n className=\"w-24 rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n value={hydrated.pageSize}\r\n onChange={(event) => {\r\n const next = Number(event.target.value)\r\n if (!Number.isFinite(next)) return\r\n const clamped = Math.min(20, Math.max(1, Math.floor(next)))\r\n onSettingsChange?.({ ...hydrated, pageSize: clamped })\r\n }}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-date-period\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.datePeriod', 'Date Period')}\r\n </label>\r\n <select\r\n id=\"sales-new-orders-date-period\"\r\n value={hydrated.datePeriod}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, datePeriod: event.target.value as DatePeriodOption })\r\n }}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n >\r\n <option value=\"last24h\">{translate('sales.widgets.newOrders.settings.last24h', 'Last 24 hours')}</option>\r\n <option value=\"last7d\">{translate('sales.widgets.newOrders.settings.last7d', 'Last 7 days')}</option>\r\n <option value=\"last30d\">{translate('sales.widgets.newOrders.settings.last30d', 'Last 30 days')}</option>\r\n <option value=\"custom\">{translate('sales.widgets.newOrders.settings.custom', 'Custom range')}</option>\r\n </select>\r\n </div>\r\n\r\n {hydrated.datePeriod === 'custom' ? (\r\n <div className=\"grid gap-3\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-custom-from\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.customFrom', 'From')}\r\n </label>\r\n <input\r\n id=\"sales-new-orders-custom-from\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customFrom)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customFrom: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-orders-custom-to\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newOrders.settings.customTo', 'To')}\r\n </label>\r\n <input\r\n id=\"sales-new-orders-custom-to\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customTo)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customTo: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n </div>\r\n ) : null}\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return <p className=\"text-sm text-destructive\">{error}</p>\r\n }\r\n\r\n if (loading) {\r\n return (\r\n <div className=\"flex h-32 items-center justify-center\">\r\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\r\n </div>\r\n )\r\n }\r\n\r\n if (!items.length) {\r\n return <p className=\"text-sm text-muted-foreground\">{translate('sales.widgets.newOrders.empty', 'No orders found')}</p>\r\n }\r\n\r\n return (\r\n <ul className=\"space-y-3\">\r\n {items.map((item) => {\r\n const detailHref = resolveDetailHref(item)\r\n const amountLabel = formatAmount(item.grossAmount, item.currency, locale)\r\n const createdLabel = formatRelativeTime(item.createdAt) ?? ''\r\n return (\r\n <li key={item.id} className=\"rounded-md border p-3\">\r\n <div className=\"flex items-start justify-between gap-3\">\r\n <div className=\"space-y-1\">\r\n <div className=\"flex items-center gap-2\">\r\n {detailHref ? (\r\n <Link href={detailHref} className=\"text-sm font-semibold text-foreground hover:underline\">\r\n {item.orderNumber}\r\n </Link>\r\n ) : (\r\n <span className=\"text-sm font-semibold text-foreground\">{item.orderNumber}</span>\r\n )}\r\n {item.status ? (\r\n <Badge variant=\"outline\" className=\"text-xs\">\r\n {item.status}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {item.customerName ?? translate('sales.widgets.newOrders.noCustomer', 'No customer')}\r\n </p>\r\n <p className=\"text-xs text-muted-foreground\">{createdLabel}</p>\r\n </div>\r\n <div className=\"text-right\">\r\n <p className=\"text-sm font-semibold\">{amountLabel}</p>\r\n </div>\r\n </div>\r\n </li>\r\n )\r\n })}\r\n </ul>\r\n )\r\n}\r\n\r\nexport default SalesNewOrdersWidget\r\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -8,9 +8,7 @@ import { Badge } from "@open-mercato/ui/primitives/badge";
8
8
  import { formatRelativeTime } from "@open-mercato/shared/lib/time";
9
9
  import { useT } from "@open-mercato/shared/lib/i18n/context";
10
10
  import { DEFAULT_SETTINGS, hydrateSalesNewQuotesSettings } from "./config.js";
11
- function readString(value) {
12
- return typeof value === "string" ? value : null;
13
- }
11
+ import { readString, toDateInputValue, openNativeDatePicker, formatAmount } from "../shared.js";
14
12
  function parseNewQuoteItems(payload) {
15
13
  const rawItems = Array.isArray(payload?.items) ? payload?.items : [];
16
14
  return rawItems.map((item) => {
@@ -55,42 +53,6 @@ async function loadNewQuotes(settings) {
55
53
  function resolveDetailHref(item) {
56
54
  return item.id ? `/backend/sales/quotes/${encodeURIComponent(item.id)}` : null;
57
55
  }
58
- function toDateInputValue(value) {
59
- if (!value) return "";
60
- const parsed = new Date(value);
61
- if (Number.isNaN(parsed.getTime())) return "";
62
- const year = String(parsed.getFullYear());
63
- const month = String(parsed.getMonth() + 1).padStart(2, "0");
64
- const day = String(parsed.getDate()).padStart(2, "0");
65
- return `${year}-${month}-${day}`;
66
- }
67
- function openNativeDatePicker(event) {
68
- const input = event.currentTarget;
69
- if (typeof input.showPicker === "function") {
70
- input.showPicker();
71
- }
72
- }
73
- function formatAmount(value, currency, locale) {
74
- const numeric = Number(value);
75
- if (!Number.isFinite(numeric)) return "--";
76
- try {
77
- if (currency && currency.trim().length > 0) {
78
- return new Intl.NumberFormat(locale ?? void 0, {
79
- style: "currency",
80
- currency,
81
- minimumFractionDigits: 2,
82
- maximumFractionDigits: 2
83
- }).format(numeric);
84
- }
85
- return new Intl.NumberFormat(locale ?? void 0, {
86
- style: "decimal",
87
- minimumFractionDigits: 0,
88
- maximumFractionDigits: 2
89
- }).format(numeric);
90
- } catch {
91
- return String(numeric);
92
- }
93
- }
94
56
  const SalesNewQuotesWidget = ({
95
57
  mode,
96
58
  settings = DEFAULT_SETTINGS,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/sales/widgets/dashboard/new-quotes/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\r\n\r\nimport * as React from 'react'\r\nimport Link from 'next/link'\r\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\r\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Badge } from '@open-mercato/ui/primitives/badge'\nimport { formatRelativeTime } from '@open-mercato/shared/lib/time'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { DEFAULT_SETTINGS, hydrateSalesNewQuotesSettings, type DatePeriodOption, type SalesNewQuotesSettings } from './config'\n\r\ntype NewQuoteItem = {\r\n id: string\r\n quoteNumber: string\r\n status: string | null\r\n customerName: string | null\r\n customerEntityId: string | null\r\n netAmount: string\r\n grossAmount: string\r\n currency: string | null\r\n createdAt: string\r\n validFrom: string | null\r\n validUntil: string | null\r\n convertedOrderId: string | null\r\n}\r\n\r\ntype NewQuotesApiPayload = {\r\n items?: unknown[]\r\n error?: string\r\n}\r\n\r\nfunction readString(value: unknown): string | null {\r\n return typeof value === 'string' ? value : null\r\n}\r\n\r\nfunction parseNewQuoteItems(payload: NewQuotesApiPayload | null): NewQuoteItem[] {\r\n const rawItems = Array.isArray(payload?.items) ? payload?.items : []\r\n return rawItems\r\n .map((item) => {\r\n if (!item || typeof item !== 'object') return null\r\n const data = item as Record<string, unknown>\r\n const id = readString(data.id)\r\n const quoteNumber = readString(data.quoteNumber)\r\n const createdAt = readString(data.createdAt)\r\n if (!id || !quoteNumber || !createdAt) return null\r\n return {\r\n id,\r\n quoteNumber,\r\n status: readString(data.status),\r\n customerName: readString(data.customerName),\r\n customerEntityId: readString(data.customerEntityId),\r\n netAmount: readString(data.netAmount) ?? '0',\r\n grossAmount: readString(data.grossAmount) ?? '0',\r\n currency: readString(data.currency),\r\n createdAt,\r\n validFrom: readString(data.validFrom),\r\n validUntil: readString(data.validUntil),\r\n convertedOrderId: readString(data.convertedOrderId),\r\n }\r\n })\r\n .filter((item): item is NewQuoteItem => !!item)\r\n}\r\n\r\nasync function loadNewQuotes(settings: SalesNewQuotesSettings): Promise<NewQuoteItem[]> {\r\n const params = new URLSearchParams({\r\n limit: String(settings.pageSize),\r\n datePeriod: settings.datePeriod,\r\n })\r\n if (settings.datePeriod === 'custom') {\r\n if (settings.customFrom) params.set('customFrom', settings.customFrom)\r\n if (settings.customTo) params.set('customTo', settings.customTo)\r\n }\r\n\r\n const call = await apiCall<NewQuotesApiPayload>(`/api/sales/dashboard/widgets/new-quotes?${params.toString()}`)\r\n if (!call.ok) {\r\n const message =\r\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\r\n ? ((call.result as Record<string, unknown>).error as string)\r\n : `Request failed with status ${call.status}`\r\n throw new Error(message)\r\n }\r\n return parseNewQuoteItems(call.result ?? null)\r\n}\r\n\r\nfunction resolveDetailHref(item: NewQuoteItem): string | null {\r\n return item.id ? `/backend/sales/quotes/${encodeURIComponent(item.id)}` : null\r\n}\r\n\r\nfunction toDateInputValue(value: string | null | undefined): string {\r\n if (!value) return ''\r\n const parsed = new Date(value)\r\n if (Number.isNaN(parsed.getTime())) return ''\r\n const year = String(parsed.getFullYear())\r\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\r\n const day = String(parsed.getDate()).padStart(2, '0')\r\n return `${year}-${month}-${day}`\r\n}\r\n\r\nfunction openNativeDatePicker(event: React.SyntheticEvent<HTMLInputElement>) {\r\n const input = event.currentTarget\r\n if (typeof input.showPicker === 'function') {\r\n input.showPicker()\r\n }\r\n}\r\n\r\nfunction formatAmount(value: string, currency: string | null, locale?: string): string {\r\n const numeric = Number(value)\r\n if (!Number.isFinite(numeric)) return '--'\r\n try {\r\n if (currency && currency.trim().length > 0) {\r\n return new Intl.NumberFormat(locale ?? undefined, {\r\n style: 'currency',\r\n currency,\r\n minimumFractionDigits: 2,\r\n maximumFractionDigits: 2,\r\n }).format(numeric)\r\n }\r\n return new Intl.NumberFormat(locale ?? undefined, {\r\n style: 'decimal',\r\n minimumFractionDigits: 0,\r\n maximumFractionDigits: 2,\r\n }).format(numeric)\r\n } catch {\r\n return String(numeric)\r\n }\r\n}\r\n\r\n\r\nconst SalesNewQuotesWidget: React.FC<DashboardWidgetComponentProps<SalesNewQuotesSettings>> = ({\r\n mode,\r\n settings = DEFAULT_SETTINGS,\r\n onSettingsChange,\r\n refreshToken,\r\n onRefreshStateChange,\r\n}) => {\r\n const translate = useT()\r\n const hydrated = React.useMemo(() => hydrateSalesNewQuotesSettings(settings), [settings])\r\n const [items, setItems] = React.useState<NewQuoteItem[]>([])\r\n const [loading, setLoading] = React.useState(true)\r\n const [error, setError] = React.useState<string | null>(null)\r\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\r\n\r\n React.useEffect(() => {\r\n if (typeof navigator !== 'undefined') {\r\n setLocale(navigator.language)\r\n }\r\n }, [])\r\n\r\n const refresh = React.useCallback(async () => {\r\n onRefreshStateChange?.(true)\r\n setLoading(true)\r\n setError(null)\r\n try {\r\n const data = await loadNewQuotes(hydrated)\r\n setItems(data)\r\n } catch (err) {\r\n console.error('Failed to load new quotes widget data', err)\r\n setError(translate('sales.widgets.newQuotes.error', 'Failed to load quotes'))\r\n } finally {\r\n setLoading(false)\r\n onRefreshStateChange?.(false)\r\n }\r\n }, [hydrated, onRefreshStateChange, translate])\r\n\r\n React.useEffect(() => {\r\n refresh().catch(() => {})\r\n }, [refresh, refreshToken])\r\n\r\n if (mode === 'settings') {\r\n return (\r\n <div className=\"space-y-4 text-sm\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.pageSize', 'Number of Quotes')}\r\n </label>\r\n <input\r\n id=\"sales-new-quotes-page-size\"\r\n type=\"number\"\r\n min={1}\r\n max={20}\r\n className=\"w-24 rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n value={hydrated.pageSize}\r\n onChange={(event) => {\r\n const next = Number(event.target.value)\r\n if (!Number.isFinite(next)) return\r\n const clamped = Math.min(20, Math.max(1, Math.floor(next)))\r\n onSettingsChange?.({ ...hydrated, pageSize: clamped })\r\n }}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-date-period\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.datePeriod', 'Date Period')}\r\n </label>\r\n <select\r\n id=\"sales-new-quotes-date-period\"\r\n value={hydrated.datePeriod}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, datePeriod: event.target.value as DatePeriodOption })\r\n }}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n >\r\n <option value=\"last24h\">{translate('sales.widgets.newQuotes.settings.last24h', 'Last 24 hours')}</option>\r\n <option value=\"last7d\">{translate('sales.widgets.newQuotes.settings.last7d', 'Last 7 days')}</option>\r\n <option value=\"last30d\">{translate('sales.widgets.newQuotes.settings.last30d', 'Last 30 days')}</option>\r\n <option value=\"custom\">{translate('sales.widgets.newQuotes.settings.custom', 'Custom range')}</option>\r\n </select>\r\n </div>\r\n\r\n {hydrated.datePeriod === 'custom' ? (\r\n <div className=\"grid gap-3\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-custom-from\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.customFrom', 'From')}\r\n </label>\r\n <input\r\n id=\"sales-new-quotes-custom-from\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customFrom)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customFrom: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-custom-to\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.customTo', 'To')}\r\n </label>\r\n <input\r\n id=\"sales-new-quotes-custom-to\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customTo)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customTo: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n </div>\r\n ) : null}\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return <p className=\"text-sm text-destructive\">{error}</p>\r\n }\r\n\r\n if (loading) {\r\n return (\r\n <div className=\"flex h-32 items-center justify-center\">\r\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\r\n </div>\r\n )\r\n }\r\n\r\n if (!items.length) {\r\n return <p className=\"text-sm text-muted-foreground\">{translate('sales.widgets.newQuotes.empty', 'No quotes found')}</p>\r\n }\r\n\r\n return (\r\n <ul className=\"space-y-3\">\r\n {items.map((item) => {\r\n const detailHref = resolveDetailHref(item)\r\n const amountLabel = formatAmount(item.grossAmount, item.currency, locale)\r\n const createdLabel = formatRelativeTime(item.createdAt) ?? ''\r\n return (\r\n <li key={item.id} className=\"rounded-md border p-3\">\r\n <div className=\"flex items-start justify-between gap-3\">\r\n <div className=\"space-y-1\">\r\n <div className=\"flex items-center gap-2\">\r\n {detailHref ? (\r\n <Link href={detailHref} className=\"text-sm font-semibold text-foreground hover:underline\">\r\n {item.quoteNumber}\r\n </Link>\r\n ) : (\r\n <span className=\"text-sm font-semibold text-foreground\">{item.quoteNumber}</span>\r\n )}\r\n {item.status ? (\r\n <Badge variant=\"outline\" className=\"text-xs\">\r\n {item.status}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {item.customerName ?? translate('sales.widgets.newQuotes.noCustomer', 'No customer')}\r\n </p>\r\n <p className=\"text-xs text-muted-foreground\">{createdLabel}</p>\r\n </div>\r\n <div className=\"text-right\">\r\n <p className=\"text-sm font-semibold\">{amountLabel}</p>\r\n </div>\r\n </div>\r\n </li>\r\n )\r\n })}\r\n </ul>\r\n )\r\n}\r\n\r\nexport default SalesNewQuotesWidget\r\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\r\n\r\nimport * as React from 'react'\r\nimport Link from 'next/link'\r\nimport type { DashboardWidgetComponentProps } from '@open-mercato/shared/modules/dashboard/widgets'\r\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\r\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\r\nimport { Badge } from '@open-mercato/ui/primitives/badge'\r\nimport { formatRelativeTime } from '@open-mercato/shared/lib/time'\r\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\r\nimport { DEFAULT_SETTINGS, hydrateSalesNewQuotesSettings, type DatePeriodOption, type SalesNewQuotesSettings } from './config'\r\nimport { readString, toDateInputValue, openNativeDatePicker, formatAmount } from '../shared'\r\n\r\ntype NewQuoteItem = {\r\n id: string\r\n quoteNumber: string\r\n status: string | null\r\n customerName: string | null\r\n customerEntityId: string | null\r\n netAmount: string\r\n grossAmount: string\r\n currency: string | null\r\n createdAt: string\r\n validFrom: string | null\r\n validUntil: string | null\r\n convertedOrderId: string | null\r\n}\r\n\r\ntype NewQuotesApiPayload = {\r\n items?: unknown[]\r\n error?: string\r\n}\r\n\r\nfunction parseNewQuoteItems(payload: NewQuotesApiPayload | null): NewQuoteItem[] {\r\n const rawItems = Array.isArray(payload?.items) ? payload?.items : []\r\n return rawItems\r\n .map((item) => {\r\n if (!item || typeof item !== 'object') return null\r\n const data = item as Record<string, unknown>\r\n const id = readString(data.id)\r\n const quoteNumber = readString(data.quoteNumber)\r\n const createdAt = readString(data.createdAt)\r\n if (!id || !quoteNumber || !createdAt) return null\r\n return {\r\n id,\r\n quoteNumber,\r\n status: readString(data.status),\r\n customerName: readString(data.customerName),\r\n customerEntityId: readString(data.customerEntityId),\r\n netAmount: readString(data.netAmount) ?? '0',\r\n grossAmount: readString(data.grossAmount) ?? '0',\r\n currency: readString(data.currency),\r\n createdAt,\r\n validFrom: readString(data.validFrom),\r\n validUntil: readString(data.validUntil),\r\n convertedOrderId: readString(data.convertedOrderId),\r\n }\r\n })\r\n .filter((item): item is NewQuoteItem => !!item)\r\n}\r\n\r\nasync function loadNewQuotes(settings: SalesNewQuotesSettings): Promise<NewQuoteItem[]> {\r\n const params = new URLSearchParams({\r\n limit: String(settings.pageSize),\r\n datePeriod: settings.datePeriod,\r\n })\r\n if (settings.datePeriod === 'custom') {\r\n if (settings.customFrom) params.set('customFrom', settings.customFrom)\r\n if (settings.customTo) params.set('customTo', settings.customTo)\r\n }\r\n\r\n const call = await apiCall<NewQuotesApiPayload>(`/api/sales/dashboard/widgets/new-quotes?${params.toString()}`)\r\n if (!call.ok) {\r\n const message =\r\n typeof (call.result as Record<string, unknown> | null)?.error === 'string'\r\n ? ((call.result as Record<string, unknown>).error as string)\r\n : `Request failed with status ${call.status}`\r\n throw new Error(message)\r\n }\r\n return parseNewQuoteItems(call.result ?? null)\r\n}\r\n\r\nfunction resolveDetailHref(item: NewQuoteItem): string | null {\r\n return item.id ? `/backend/sales/quotes/${encodeURIComponent(item.id)}` : null\r\n}\r\n\r\n\r\nconst SalesNewQuotesWidget: React.FC<DashboardWidgetComponentProps<SalesNewQuotesSettings>> = ({\r\n mode,\r\n settings = DEFAULT_SETTINGS,\r\n onSettingsChange,\r\n refreshToken,\r\n onRefreshStateChange,\r\n}) => {\r\n const translate = useT()\r\n const hydrated = React.useMemo(() => hydrateSalesNewQuotesSettings(settings), [settings])\r\n const [items, setItems] = React.useState<NewQuoteItem[]>([])\r\n const [loading, setLoading] = React.useState(true)\r\n const [error, setError] = React.useState<string | null>(null)\r\n const [locale, setLocale] = React.useState<string | undefined>(undefined)\r\n\r\n React.useEffect(() => {\r\n if (typeof navigator !== 'undefined') {\r\n setLocale(navigator.language)\r\n }\r\n }, [])\r\n\r\n const refresh = React.useCallback(async () => {\r\n onRefreshStateChange?.(true)\r\n setLoading(true)\r\n setError(null)\r\n try {\r\n const data = await loadNewQuotes(hydrated)\r\n setItems(data)\r\n } catch (err) {\r\n console.error('Failed to load new quotes widget data', err)\r\n setError(translate('sales.widgets.newQuotes.error', 'Failed to load quotes'))\r\n } finally {\r\n setLoading(false)\r\n onRefreshStateChange?.(false)\r\n }\r\n }, [hydrated, onRefreshStateChange, translate])\r\n\r\n React.useEffect(() => {\r\n refresh().catch(() => {})\r\n }, [refresh, refreshToken])\r\n\r\n if (mode === 'settings') {\r\n return (\r\n <div className=\"space-y-4 text-sm\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-page-size\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.pageSize', 'Number of Quotes')}\r\n </label>\r\n <input\r\n id=\"sales-new-quotes-page-size\"\r\n type=\"number\"\r\n min={1}\r\n max={20}\r\n className=\"w-24 rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n value={hydrated.pageSize}\r\n onChange={(event) => {\r\n const next = Number(event.target.value)\r\n if (!Number.isFinite(next)) return\r\n const clamped = Math.min(20, Math.max(1, Math.floor(next)))\r\n onSettingsChange?.({ ...hydrated, pageSize: clamped })\r\n }}\r\n />\r\n </div>\r\n\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-date-period\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.datePeriod', 'Date Period')}\r\n </label>\r\n <select\r\n id=\"sales-new-quotes-date-period\"\r\n value={hydrated.datePeriod}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, datePeriod: event.target.value as DatePeriodOption })\r\n }}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n >\r\n <option value=\"last24h\">{translate('sales.widgets.newQuotes.settings.last24h', 'Last 24 hours')}</option>\r\n <option value=\"last7d\">{translate('sales.widgets.newQuotes.settings.last7d', 'Last 7 days')}</option>\r\n <option value=\"last30d\">{translate('sales.widgets.newQuotes.settings.last30d', 'Last 30 days')}</option>\r\n <option value=\"custom\">{translate('sales.widgets.newQuotes.settings.custom', 'Custom range')}</option>\r\n </select>\r\n </div>\r\n\r\n {hydrated.datePeriod === 'custom' ? (\r\n <div className=\"grid gap-3\">\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-custom-from\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.customFrom', 'From')}\r\n </label>\r\n <input\r\n id=\"sales-new-quotes-custom-from\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customFrom)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customFrom: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n <div className=\"space-y-1.5\">\r\n <label htmlFor=\"sales-new-quotes-custom-to\" className=\"text-xs font-semibold uppercase text-muted-foreground\">\r\n {translate('sales.widgets.newQuotes.settings.customTo', 'To')}\r\n </label>\r\n <input\r\n id=\"sales-new-quotes-custom-to\"\r\n type=\"date\"\r\n value={toDateInputValue(hydrated.customTo)}\r\n onChange={(event) => {\r\n onSettingsChange?.({ ...hydrated, customTo: event.target.value })\r\n }}\r\n onFocus={openNativeDatePicker}\r\n onClick={openNativeDatePicker}\r\n className=\"w-full rounded-md border border-input bg-background px-2 py-1 text-sm text-foreground focus:border-primary focus:outline-none focus:ring-1 focus:ring-primary\"\r\n />\r\n </div>\r\n </div>\r\n ) : null}\r\n </div>\r\n )\r\n }\r\n\r\n if (error) {\r\n return <p className=\"text-sm text-destructive\">{error}</p>\r\n }\r\n\r\n if (loading) {\r\n return (\r\n <div className=\"flex h-32 items-center justify-center\">\r\n <Spinner className=\"h-6 w-6 text-muted-foreground\" />\r\n </div>\r\n )\r\n }\r\n\r\n if (!items.length) {\r\n return <p className=\"text-sm text-muted-foreground\">{translate('sales.widgets.newQuotes.empty', 'No quotes found')}</p>\r\n }\r\n\r\n return (\r\n <ul className=\"space-y-3\">\r\n {items.map((item) => {\r\n const detailHref = resolveDetailHref(item)\r\n const amountLabel = formatAmount(item.grossAmount, item.currency, locale)\r\n const createdLabel = formatRelativeTime(item.createdAt) ?? ''\r\n return (\r\n <li key={item.id} className=\"rounded-md border p-3\">\r\n <div className=\"flex items-start justify-between gap-3\">\r\n <div className=\"space-y-1\">\r\n <div className=\"flex items-center gap-2\">\r\n {detailHref ? (\r\n <Link href={detailHref} className=\"text-sm font-semibold text-foreground hover:underline\">\r\n {item.quoteNumber}\r\n </Link>\r\n ) : (\r\n <span className=\"text-sm font-semibold text-foreground\">{item.quoteNumber}</span>\r\n )}\r\n {item.status ? (\r\n <Badge variant=\"outline\" className=\"text-xs\">\r\n {item.status}\r\n </Badge>\r\n ) : null}\r\n </div>\r\n <p className=\"text-xs text-muted-foreground\">\r\n {item.customerName ?? translate('sales.widgets.newQuotes.noCustomer', 'No customer')}\r\n </p>\r\n <p className=\"text-xs text-muted-foreground\">{createdLabel}</p>\r\n </div>\r\n <div className=\"text-right\">\r\n <p className=\"text-sm font-semibold\">{amountLabel}</p>\r\n </div>\r\n </div>\r\n </li>\r\n )\r\n })}\r\n </ul>\r\n )\r\n}\r\n\r\nexport default SalesNewQuotesWidget\r\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,46 @@
1
+ function readString(value) {
2
+ return typeof value === "string" ? value : null;
3
+ }
4
+ function toDateInputValue(value) {
5
+ if (!value) return "";
6
+ const parsed = new Date(value);
7
+ if (Number.isNaN(parsed.getTime())) return "";
8
+ const year = String(parsed.getFullYear());
9
+ const month = String(parsed.getMonth() + 1).padStart(2, "0");
10
+ const day = String(parsed.getDate()).padStart(2, "0");
11
+ return `${year}-${month}-${day}`;
12
+ }
13
+ function openNativeDatePicker(event) {
14
+ const input = event.currentTarget;
15
+ if (typeof input.showPicker === "function") {
16
+ input.showPicker();
17
+ }
18
+ }
19
+ function formatAmount(value, currency, locale) {
20
+ const numeric = Number(value);
21
+ if (!Number.isFinite(numeric)) return "--";
22
+ try {
23
+ if (currency && currency.trim().length > 0) {
24
+ return new Intl.NumberFormat(locale ?? void 0, {
25
+ style: "currency",
26
+ currency,
27
+ minimumFractionDigits: 2,
28
+ maximumFractionDigits: 2
29
+ }).format(numeric);
30
+ }
31
+ return new Intl.NumberFormat(locale ?? void 0, {
32
+ style: "decimal",
33
+ minimumFractionDigits: 0,
34
+ maximumFractionDigits: 2
35
+ }).format(numeric);
36
+ } catch {
37
+ return String(numeric);
38
+ }
39
+ }
40
+ export {
41
+ formatAmount,
42
+ openNativeDatePicker,
43
+ readString,
44
+ toDateInputValue
45
+ };
46
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/sales/widgets/dashboard/shared.ts"],
4
+ "sourcesContent": ["import type React from 'react'\n\nexport function readString(value: unknown): string | null {\n return typeof value === 'string' ? value : null\n}\n\nexport function toDateInputValue(value: string | null | undefined): string {\n if (!value) return ''\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) return ''\n const year = String(parsed.getFullYear())\n const month = String(parsed.getMonth() + 1).padStart(2, '0')\n const day = String(parsed.getDate()).padStart(2, '0')\n return `${year}-${month}-${day}`\n}\n\nexport function openNativeDatePicker(event: React.SyntheticEvent<HTMLInputElement>) {\n const input = event.currentTarget\n if (typeof input.showPicker === 'function') {\n input.showPicker()\n }\n}\n\nexport function formatAmount(value: string, currency: string | null, locale?: string): string {\n const numeric = Number(value)\n if (!Number.isFinite(numeric)) return '--'\n try {\n if (currency && currency.trim().length > 0) {\n return new Intl.NumberFormat(locale ?? undefined, {\n style: 'currency',\n currency,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(numeric)\n }\n return new Intl.NumberFormat(locale ?? undefined, {\n style: 'decimal',\n minimumFractionDigits: 0,\n maximumFractionDigits: 2,\n }).format(numeric)\n } catch {\n return String(numeric)\n }\n}\n"],
5
+ "mappings": "AAEO,SAAS,WAAW,OAA+B;AACxD,SAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;AAEO,SAAS,iBAAiB,OAA0C;AACzE,MAAI,CAAC,MAAO,QAAO;AACnB,QAAM,SAAS,IAAI,KAAK,KAAK;AAC7B,MAAI,OAAO,MAAM,OAAO,QAAQ,CAAC,EAAG,QAAO;AAC3C,QAAM,OAAO,OAAO,OAAO,YAAY,CAAC;AACxC,QAAM,QAAQ,OAAO,OAAO,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAC3D,QAAM,MAAM,OAAO,OAAO,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,SAAO,GAAG,IAAI,IAAI,KAAK,IAAI,GAAG;AAChC;AAEO,SAAS,qBAAqB,OAA+C;AAClF,QAAM,QAAQ,MAAM;AACpB,MAAI,OAAO,MAAM,eAAe,YAAY;AAC1C,UAAM,WAAW;AAAA,EACnB;AACF;AAEO,SAAS,aAAa,OAAe,UAAyB,QAAyB;AAC5F,QAAM,UAAU,OAAO,KAAK;AAC5B,MAAI,CAAC,OAAO,SAAS,OAAO,EAAG,QAAO;AACtC,MAAI;AACF,QAAI,YAAY,SAAS,KAAK,EAAE,SAAS,GAAG;AAC1C,aAAO,IAAI,KAAK,aAAa,UAAU,QAAW;AAAA,QAChD,OAAO;AAAA,QACP;AAAA,QACA,uBAAuB;AAAA,QACvB,uBAAuB;AAAA,MACzB,CAAC,EAAE,OAAO,OAAO;AAAA,IACnB;AACA,WAAO,IAAI,KAAK,aAAa,UAAU,QAAW;AAAA,MAChD,OAAO;AAAA,MACP,uBAAuB;AAAA,MACvB,uBAAuB;AAAA,IACzB,CAAC,EAAE,OAAO,OAAO;AAAA,EACnB,QAAQ;AACN,WAAO,OAAO,OAAO;AAAA,EACvB;AACF;",
6
+ "names": []
7
+ }