@open-mercato/core 0.8.1-develop.7221.1.ece7ca874e → 0.8.1-develop.7225.1.73190a4a95

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 (87) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.js +166 -0
  3. package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.js.map +7 -0
  4. package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.meta.js +16 -0
  5. package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.meta.js.map +7 -0
  6. package/dist/modules/catalog/backend/catalog/prices/create/page.js +68 -0
  7. package/dist/modules/catalog/backend/catalog/prices/create/page.js.map +7 -0
  8. package/dist/modules/catalog/backend/catalog/prices/create/page.meta.js +25 -0
  9. package/dist/modules/catalog/backend/catalog/prices/create/page.meta.js.map +7 -0
  10. package/dist/modules/catalog/backend/catalog/prices/page.js +11 -0
  11. package/dist/modules/catalog/backend/catalog/prices/page.js.map +7 -0
  12. package/dist/modules/catalog/backend/catalog/prices/page.meta.js +16 -0
  13. package/dist/modules/catalog/backend/catalog/prices/page.meta.js.map +7 -0
  14. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +201 -0
  15. package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +7 -0
  16. package/dist/modules/catalog/components/prices/PricesDataTable.js +273 -0
  17. package/dist/modules/catalog/components/prices/PricesDataTable.js.map +7 -0
  18. package/dist/modules/catalog/components/prices/normalizePriceRecord.js +43 -0
  19. package/dist/modules/catalog/components/prices/normalizePriceRecord.js.map +7 -0
  20. package/dist/modules/catalog/components/prices/priceFormFields.js +195 -0
  21. package/dist/modules/catalog/components/prices/priceFormFields.js.map +7 -0
  22. package/dist/modules/catalog/components/prices/useBatchLabels.js +42 -0
  23. package/dist/modules/catalog/components/prices/useBatchLabels.js.map +7 -0
  24. package/dist/modules/catalog/data/validators.js +21 -2
  25. package/dist/modules/catalog/data/validators.js.map +2 -2
  26. package/dist/modules/catalog/extension-points.js +2 -1
  27. package/dist/modules/catalog/extension-points.js.map +2 -2
  28. package/dist/modules/catalog/lib/pricing.js +37 -8
  29. package/dist/modules/catalog/lib/pricing.js.map +2 -2
  30. package/dist/modules/communication_channels/backend/profile/communication-channels/ConnectChannelMenu.js +88 -0
  31. package/dist/modules/communication_channels/backend/profile/communication-channels/ConnectChannelMenu.js.map +7 -0
  32. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +6 -12
  33. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
  34. package/dist/modules/communication_channels/commands/connect-credential-channel.js +7 -2
  35. package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
  36. package/dist/modules/communication_channels/commands/ingest-inbound-message.js +11 -2
  37. package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
  38. package/dist/modules/communication_channels/extension-points.js +1 -1
  39. package/dist/modules/communication_channels/extension-points.js.map +1 -1
  40. package/dist/modules/communication_channels/lib/connect-channel.js +33 -0
  41. package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
  42. package/dist/modules/messages/api/route.js +5 -1
  43. package/dist/modules/messages/api/route.js.map +2 -2
  44. package/dist/modules/messages/commands/messages.js +3 -2
  45. package/dist/modules/messages/commands/messages.js.map +2 -2
  46. package/dist/modules/messages/data/validators.js +12 -2
  47. package/dist/modules/messages/data/validators.js.map +2 -2
  48. package/dist/modules/messages/subscribers/message-notification.js +4 -1
  49. package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
  50. package/package.json +7 -7
  51. package/src/modules/catalog/AGENTS.md +6 -2
  52. package/src/modules/catalog/backend/catalog/prices/[id]/edit/page.meta.ts +12 -0
  53. package/src/modules/catalog/backend/catalog/prices/[id]/edit/page.tsx +198 -0
  54. package/src/modules/catalog/backend/catalog/prices/create/page.meta.ts +23 -0
  55. package/src/modules/catalog/backend/catalog/prices/create/page.tsx +70 -0
  56. package/src/modules/catalog/backend/catalog/prices/page.meta.ts +12 -0
  57. package/src/modules/catalog/backend/catalog/prices/page.tsx +14 -0
  58. package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +243 -0
  59. package/src/modules/catalog/components/prices/PricesDataTable.tsx +320 -0
  60. package/src/modules/catalog/components/prices/normalizePriceRecord.ts +71 -0
  61. package/src/modules/catalog/components/prices/priceFormFields.tsx +228 -0
  62. package/src/modules/catalog/components/prices/useBatchLabels.ts +57 -0
  63. package/src/modules/catalog/data/validators.ts +43 -2
  64. package/src/modules/catalog/extension-points.ts +1 -0
  65. package/src/modules/catalog/i18n/de.json +72 -0
  66. package/src/modules/catalog/i18n/en.json +72 -0
  67. package/src/modules/catalog/i18n/es.json +72 -0
  68. package/src/modules/catalog/i18n/ko.json +72 -0
  69. package/src/modules/catalog/i18n/pl.json +72 -0
  70. package/src/modules/catalog/lib/pricing.ts +103 -9
  71. package/src/modules/communication_channels/backend/profile/communication-channels/ConnectChannelMenu.tsx +120 -0
  72. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +9 -10
  73. package/src/modules/communication_channels/commands/connect-credential-channel.ts +9 -0
  74. package/src/modules/communication_channels/commands/ingest-inbound-message.ts +17 -2
  75. package/src/modules/communication_channels/extension-points.ts +1 -1
  76. package/src/modules/communication_channels/i18n/de.json +2 -1
  77. package/src/modules/communication_channels/i18n/en.json +2 -1
  78. package/src/modules/communication_channels/i18n/es.json +2 -1
  79. package/src/modules/communication_channels/i18n/ko.json +2 -1
  80. package/src/modules/communication_channels/i18n/pl.json +2 -1
  81. package/src/modules/communication_channels/lib/adapter.ts +8 -0
  82. package/src/modules/communication_channels/lib/connect-channel.ts +49 -0
  83. package/src/modules/messages/api/route.ts +8 -2
  84. package/src/modules/messages/commands/messages.ts +17 -2
  85. package/src/modules/messages/data/validators.ts +26 -11
  86. package/src/modules/messages/subscribers/message-notification.ts +17 -5
  87. package/src/modules/sales/AGENTS.md +2 -3
@@ -0,0 +1,273 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import Link from "next/link";
5
+ import { useQuery, useQueryClient } from "@tanstack/react-query";
6
+ import { extensionPoints } from "@open-mercato/core/modules/catalog/extension-points";
7
+ import { DataTable } from "@open-mercato/ui/backend/DataTable";
8
+ import { ListEmptyState } from "@open-mercato/ui/backend/filters/ListEmptyState";
9
+ import { RowActions } from "@open-mercato/ui/backend/RowActions";
10
+ import { Button } from "@open-mercato/ui/primitives/button";
11
+ import { Tag } from "@open-mercato/ui/primitives/tag";
12
+ import { apiCall, apiCallOrThrow, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
13
+ import { flash } from "@open-mercato/ui/backend/FlashMessages";
14
+ import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
15
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
16
+ import { useConfirmDialog } from "@open-mercato/ui/backend/confirm-dialog";
17
+ import { useBatchLabels } from "./useBatchLabels.js";
18
+ import { normalizePriceRecord } from "./normalizePriceRecord.js";
19
+ const PAGE_SIZE = 50;
20
+ const KIND_TAG_VARIANT = {
21
+ custom: "brand",
22
+ promotion: "warning",
23
+ tier: "info",
24
+ regular: "neutral"
25
+ };
26
+ function formatAmount(row) {
27
+ const amount = row.unitPriceNet ?? row.unitPriceGross;
28
+ if (amount == null) return "\u2014";
29
+ return row.currencyCode ? `${amount} ${row.currencyCode}` : amount;
30
+ }
31
+ function formatQuantityRange(row, t) {
32
+ if (row.maxQuantity != null) {
33
+ return `${row.minQuantity}\u2013${row.maxQuantity}`;
34
+ }
35
+ return t("catalog.prices.list.quantity.min", "{{min}}+", { min: row.minQuantity });
36
+ }
37
+ function PricesDataTable({ productId } = {}) {
38
+ const t = useT();
39
+ const { confirm, ConfirmDialogElement } = useConfirmDialog();
40
+ const queryClient = useQueryClient();
41
+ const scopeVersion = useOrganizationScopeVersion();
42
+ const [page, setPage] = React.useState(1);
43
+ const [search, setSearch] = React.useState("");
44
+ const [canManage, setCanManage] = React.useState(false);
45
+ React.useEffect(() => {
46
+ let cancelled = false;
47
+ async function load() {
48
+ try {
49
+ const call = await apiCall("/api/auth/feature-check", {
50
+ method: "POST",
51
+ headers: { "content-type": "application/json" },
52
+ body: JSON.stringify({ features: ["catalog.pricing.manage"] })
53
+ });
54
+ if (!cancelled) {
55
+ const granted = Array.isArray(call.result?.granted) ? call.result.granted : [];
56
+ setCanManage(call.result?.ok === true || granted.includes("catalog.pricing.manage"));
57
+ }
58
+ } catch {
59
+ if (!cancelled) setCanManage(false);
60
+ }
61
+ }
62
+ load();
63
+ return () => {
64
+ cancelled = true;
65
+ };
66
+ }, []);
67
+ const queryParams = React.useMemo(() => {
68
+ const params = new URLSearchParams();
69
+ params.set("page", String(page));
70
+ params.set("pageSize", String(PAGE_SIZE));
71
+ if (productId) params.set("productId", productId);
72
+ if (search) params.set("search", search);
73
+ return params.toString();
74
+ }, [page, productId, search]);
75
+ const { data, isLoading } = useQuery({
76
+ queryKey: ["catalog-prices", queryParams, scopeVersion],
77
+ queryFn: async () => {
78
+ const payload = await readApiResultOrThrow(
79
+ `/api/catalog/prices?${queryParams}`,
80
+ void 0,
81
+ { errorMessage: t("catalog.prices.list.error.load", "Failed to load price rules") }
82
+ );
83
+ const rawItems = Array.isArray(payload.items) ? payload.items : [];
84
+ return {
85
+ items: rawItems.map(normalizePriceRecord),
86
+ total: typeof payload.total === "number" ? payload.total : 0,
87
+ page: typeof payload.page === "number" ? payload.page : 1,
88
+ pageSize: typeof payload.pageSize === "number" ? payload.pageSize : PAGE_SIZE,
89
+ totalPages: typeof payload.totalPages === "number" ? payload.totalPages : 1,
90
+ totalIsCapped: payload.totalIsCapped === true
91
+ };
92
+ }
93
+ });
94
+ const rows = data?.items ?? [];
95
+ const total = data?.total ?? 0;
96
+ const totalPages = data?.totalPages ?? 0;
97
+ const totalIsCapped = data?.totalIsCapped === true;
98
+ const productIds = React.useMemo(() => rows.map((row) => row.variantId ?? row.productId).filter((id) => !!id), [rows]);
99
+ const mapProduct = React.useCallback((item) => {
100
+ const id = typeof item.id === "string" ? item.id : "";
101
+ if (!id) return null;
102
+ const label = typeof item.title === "string" ? item.title : typeof item.name === "string" ? item.name : id;
103
+ return { id, label };
104
+ }, []);
105
+ const productLabels = useBatchLabels("/api/catalog/products", rows.filter((r) => !r.variantId).map((r) => r.productId ?? ""), mapProduct);
106
+ const variantLabels = useBatchLabels("/api/catalog/variants", rows.filter((r) => r.variantId).map((r) => r.variantId ?? ""), mapProduct);
107
+ const customerIds = React.useMemo(() => rows.map((row) => row.customerId).filter((id) => !!id), [rows]);
108
+ const mapCustomer = React.useCallback((item) => {
109
+ const id = typeof item.id === "string" ? item.id : "";
110
+ if (!id) return null;
111
+ const label = typeof item.displayName === "string" ? item.displayName : typeof item.display_name === "string" ? item.display_name : id;
112
+ return { id, label };
113
+ }, []);
114
+ const customerLabels = useBatchLabels("/api/customers/people", customerIds, mapCustomer);
115
+ const channelIds = React.useMemo(() => rows.map((row) => row.channelId).filter((id) => !!id), [rows]);
116
+ const mapChannel = React.useCallback((item) => {
117
+ const id = typeof item.id === "string" ? item.id : "";
118
+ if (!id) return null;
119
+ const label = typeof item.name === "string" ? item.name : id;
120
+ return { id, label };
121
+ }, []);
122
+ const channelLabels = useBatchLabels("/api/sales/channels", channelIds, mapChannel);
123
+ const handleDelete = React.useCallback(async (row) => {
124
+ const confirmed = await confirm({
125
+ title: t("catalog.prices.list.confirmDelete", "Delete this price rule?"),
126
+ variant: "destructive"
127
+ });
128
+ if (!confirmed) return;
129
+ try {
130
+ await apiCallOrThrow(
131
+ `/api/catalog/prices?id=${encodeURIComponent(row.id)}`,
132
+ { method: "DELETE" },
133
+ { errorMessage: t("catalog.prices.list.error.delete", "Failed to delete price rule") }
134
+ );
135
+ await queryClient.invalidateQueries({ queryKey: ["catalog-prices"] });
136
+ flash(t("catalog.prices.flash.deleted", "Price rule deleted"), "success");
137
+ } catch (err) {
138
+ const fallback = t("catalog.prices.list.error.delete", "Failed to delete price rule");
139
+ const message = err instanceof Error ? err.message : fallback;
140
+ flash(message, "error");
141
+ }
142
+ }, [confirm, queryClient, t]);
143
+ const columns = React.useMemo(() => [
144
+ {
145
+ id: "product",
146
+ header: t("catalog.prices.list.columns.product", "Product"),
147
+ meta: { priority: 1 },
148
+ cell: ({ row }) => {
149
+ const price = row.original;
150
+ const label = price.variantId ? variantLabels[price.variantId] ?? price.variantId : price.productId ? productLabels[price.productId] ?? price.productId : "\u2014";
151
+ return /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: label });
152
+ }
153
+ },
154
+ {
155
+ id: "kind",
156
+ header: t("catalog.prices.list.columns.kind", "Kind"),
157
+ meta: { priority: 2 },
158
+ cell: ({ row }) => {
159
+ const kind = row.original.kind || "regular";
160
+ return /* @__PURE__ */ jsx(Tag, { variant: KIND_TAG_VARIANT[kind] ?? "neutral", children: kind });
161
+ }
162
+ },
163
+ {
164
+ id: "amount",
165
+ header: t("catalog.prices.list.columns.amount", "Amount"),
166
+ meta: { priority: 1 },
167
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: formatAmount(row.original) })
168
+ },
169
+ {
170
+ id: "scope",
171
+ header: t("catalog.prices.list.columns.scope", "Scope"),
172
+ meta: { priority: 3 },
173
+ cell: ({ row }) => {
174
+ const price = row.original;
175
+ const chips = [];
176
+ if (price.customerId) {
177
+ chips.push(
178
+ /* @__PURE__ */ jsx(Tag, { variant: "info", dot: true, children: customerLabels[price.customerId] ?? t("catalog.prices.list.scope.customer", "Customer") }, "customer")
179
+ );
180
+ }
181
+ if (price.customerGroupId) {
182
+ chips.push(
183
+ /* @__PURE__ */ jsx(Tag, { variant: "info", dot: true, children: t("catalog.prices.list.scope.customerGroup", "Customer group") }, "customerGroup")
184
+ );
185
+ }
186
+ if (price.channelId) {
187
+ chips.push(
188
+ /* @__PURE__ */ jsx(Tag, { variant: "neutral", dot: true, children: channelLabels[price.channelId] ?? t("catalog.prices.list.scope.channel", "Channel") }, "channel")
189
+ );
190
+ }
191
+ if (!chips.length) return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "\u2014" });
192
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-1", children: chips });
193
+ }
194
+ },
195
+ {
196
+ id: "quantity",
197
+ header: t("catalog.prices.list.columns.quantity", "Quantity"),
198
+ meta: { priority: 4 },
199
+ cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-sm", children: formatQuantityRange(row.original, t) })
200
+ },
201
+ {
202
+ id: "validity",
203
+ header: t("catalog.prices.list.columns.validity", "Validity"),
204
+ meta: { priority: 5 },
205
+ cell: ({ row }) => {
206
+ const price = row.original;
207
+ const isExpired = Boolean(price.endsAt && new Date(price.endsAt) < /* @__PURE__ */ new Date());
208
+ if (!price.startsAt && !price.endsAt) {
209
+ return /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("catalog.prices.list.validity.always", "Always") });
210
+ }
211
+ const from = price.startsAt ? new Date(price.startsAt).toLocaleDateString() : null;
212
+ const until = price.endsAt ? new Date(price.endsAt).toLocaleDateString() : null;
213
+ const label = from && until ? t("catalog.prices.list.validity.range", "{{from}} \u2013 {{until}}", { from, until }) : from ? t("catalog.prices.list.validity.from", "From {{date}}", { date: from }) : t("catalog.prices.list.validity.until", "Until {{date}}", { date: until ?? "" });
214
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
215
+ /* @__PURE__ */ jsx("span", { className: "text-sm", children: label }),
216
+ isExpired ? /* @__PURE__ */ jsx(Tag, { variant: "error", dot: true, children: t("catalog.prices.list.badge.expired", "Expired") }) : null
217
+ ] });
218
+ }
219
+ }
220
+ ], [t, variantLabels, productLabels, customerLabels, channelLabels]);
221
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
222
+ /* @__PURE__ */ jsx(
223
+ DataTable,
224
+ {
225
+ title: t("catalog.prices.list.title", "Price rules"),
226
+ titleHeadingLevel: 1,
227
+ actions: canManage ? /* @__PURE__ */ jsx(Button, { asChild: true, children: /* @__PURE__ */ jsx(Link, { href: "/backend/catalog/prices/create", children: t("catalog.prices.list.actions.create", "Create") }) }) : void 0,
228
+ columns,
229
+ data: rows,
230
+ emptyState: /* @__PURE__ */ jsx(
231
+ ListEmptyState,
232
+ {
233
+ entityName: t("catalog.prices.list.title", "Price rules"),
234
+ description: t("catalog.prices.list.empty", "No price rules yet \u2014 add one to price this product beyond its base price."),
235
+ createHref: canManage ? "/backend/catalog/prices/create" : void 0,
236
+ createLabel: canManage ? t("catalog.prices.list.actions.create", "Create") : void 0
237
+ }
238
+ ),
239
+ searchValue: search,
240
+ searchPlaceholder: t("catalog.prices.list.searchPlaceholder", "Search price rules"),
241
+ onSearchChange: (value) => {
242
+ setSearch(value);
243
+ setPage(1);
244
+ },
245
+ sortable: false,
246
+ perspective: { tableId: extensionPoints.hosts.pricesTable.tableId },
247
+ rowActions: (row) => canManage ? /* @__PURE__ */ jsx(
248
+ RowActions,
249
+ {
250
+ items: [
251
+ { id: "edit", label: t("catalog.prices.list.actions.edit", "Edit"), href: `/backend/catalog/prices/${row.id}/edit` },
252
+ { id: "delete", label: t("catalog.prices.list.actions.delete", "Delete"), destructive: true, onSelect: () => handleDelete(row) }
253
+ ]
254
+ }
255
+ ) : null,
256
+ pagination: {
257
+ page,
258
+ pageSize: PAGE_SIZE,
259
+ total,
260
+ totalPages,
261
+ totalIsCapped,
262
+ onPageChange: setPage
263
+ },
264
+ isLoading
265
+ }
266
+ ),
267
+ ConfirmDialogElement
268
+ ] });
269
+ }
270
+ export {
271
+ PricesDataTable as default
272
+ };
273
+ //# sourceMappingURL=PricesDataTable.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/catalog/components/prices/PricesDataTable.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { useQuery, useQueryClient } from '@tanstack/react-query'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { extensionPoints } from '@open-mercato/core/modules/catalog/extension-points'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { ListEmptyState } from '@open-mercato/ui/backend/filters/ListEmptyState'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { apiCall, apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { useBatchLabels } from './useBatchLabels'\nimport { normalizePriceRecord, type NormalizedPriceRecord } from './normalizePriceRecord'\n\ntype PriceRow = NormalizedPriceRecord\n\ntype PricesResponse = {\n items: PriceRow[]\n total: number\n page: number\n pageSize: number\n totalPages: number\n totalIsCapped?: boolean\n}\n\nconst PAGE_SIZE = 50\n\nconst KIND_TAG_VARIANT: Record<string, 'brand' | 'warning' | 'info' | 'neutral'> = {\n custom: 'brand',\n promotion: 'warning',\n tier: 'info',\n regular: 'neutral',\n}\n\nfunction formatAmount(row: PriceRow): string {\n const amount = row.unitPriceNet ?? row.unitPriceGross\n if (amount == null) return '\u2014'\n return row.currencyCode ? `${amount} ${row.currencyCode}` : amount\n}\n\nfunction formatQuantityRange(row: PriceRow, t: TranslateFn): string {\n if (row.maxQuantity != null) {\n return `${row.minQuantity}\u2013${row.maxQuantity}`\n }\n return t('catalog.prices.list.quantity.min', '{{min}}+', { min: row.minQuantity })\n}\n\nexport default function PricesDataTable({ productId }: { productId?: string } = {}) {\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const queryClient = useQueryClient()\n const scopeVersion = useOrganizationScopeVersion()\n const [page, setPage] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [canManage, setCanManage] = React.useState(false)\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n try {\n const call = await apiCall<{ granted?: string[]; ok?: boolean }>('/api/auth/feature-check', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ features: ['catalog.pricing.manage'] }),\n })\n if (!cancelled) {\n const granted = Array.isArray(call.result?.granted) ? call.result.granted : []\n setCanManage(call.result?.ok === true || granted.includes('catalog.pricing.manage'))\n }\n } catch {\n if (!cancelled) setCanManage(false)\n }\n }\n load()\n return () => {\n cancelled = true\n }\n }, [])\n\n const queryParams = React.useMemo(() => {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', String(PAGE_SIZE))\n if (productId) params.set('productId', productId)\n if (search) params.set('search', search)\n return params.toString()\n }, [page, productId, search])\n\n const { data, isLoading } = useQuery<PricesResponse>({\n queryKey: ['catalog-prices', queryParams, scopeVersion],\n queryFn: async () => {\n const payload = await readApiResultOrThrow<{\n items?: Array<Record<string, unknown>>\n total?: number\n page?: number\n pageSize?: number\n totalPages?: number\n totalIsCapped?: boolean\n }>(\n `/api/catalog/prices?${queryParams}`,\n undefined,\n { errorMessage: t('catalog.prices.list.error.load', 'Failed to load price rules') },\n )\n const rawItems = Array.isArray(payload.items) ? payload.items : []\n return {\n items: rawItems.map(normalizePriceRecord),\n total: typeof payload.total === 'number' ? payload.total : 0,\n page: typeof payload.page === 'number' ? payload.page : 1,\n pageSize: typeof payload.pageSize === 'number' ? payload.pageSize : PAGE_SIZE,\n totalPages: typeof payload.totalPages === 'number' ? payload.totalPages : 1,\n totalIsCapped: payload.totalIsCapped === true,\n }\n },\n })\n\n const rows = data?.items ?? []\n const total = data?.total ?? 0\n const totalPages = data?.totalPages ?? 0\n const totalIsCapped = data?.totalIsCapped === true\n\n const productIds = React.useMemo(() => rows.map((row) => row.variantId ?? row.productId).filter((id): id is string => !!id), [rows])\n const mapProduct = React.useCallback((item: Record<string, unknown>) => {\n const id = typeof item.id === 'string' ? item.id : ''\n if (!id) return null\n const label = typeof item.title === 'string' ? item.title : (typeof item.name === 'string' ? item.name : id)\n return { id, label }\n }, [])\n const productLabels = useBatchLabels('/api/catalog/products', rows.filter((r) => !r.variantId).map((r) => r.productId ?? ''), mapProduct)\n const variantLabels = useBatchLabels('/api/catalog/variants', rows.filter((r) => r.variantId).map((r) => r.variantId ?? ''), mapProduct)\n\n const customerIds = React.useMemo(() => rows.map((row) => row.customerId).filter((id): id is string => !!id), [rows])\n const mapCustomer = React.useCallback((item: Record<string, unknown>) => {\n const id = typeof item.id === 'string' ? item.id : ''\n if (!id) return null\n const label = typeof item.displayName === 'string' ? item.displayName : (typeof item.display_name === 'string' ? item.display_name : id)\n return { id, label }\n }, [])\n const customerLabels = useBatchLabels('/api/customers/people', customerIds, mapCustomer)\n\n const channelIds = React.useMemo(() => rows.map((row) => row.channelId).filter((id): id is string => !!id), [rows])\n const mapChannel = React.useCallback((item: Record<string, unknown>) => {\n const id = typeof item.id === 'string' ? item.id : ''\n if (!id) return null\n const label = typeof item.name === 'string' ? item.name : id\n return { id, label }\n }, [])\n const channelLabels = useBatchLabels('/api/sales/channels', channelIds, mapChannel)\n\n const handleDelete = React.useCallback(async (row: PriceRow) => {\n const confirmed = await confirm({\n title: t('catalog.prices.list.confirmDelete', 'Delete this price rule?'),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n // optimistic-lock-exempt: delete-only mutation \u2014 no field-level lost-update\n await apiCallOrThrow(\n `/api/catalog/prices?id=${encodeURIComponent(row.id)}`,\n { method: 'DELETE' },\n { errorMessage: t('catalog.prices.list.error.delete', 'Failed to delete price rule') },\n )\n await queryClient.invalidateQueries({ queryKey: ['catalog-prices'] })\n flash(t('catalog.prices.flash.deleted', 'Price rule deleted'), 'success')\n } catch (err: unknown) {\n const fallback = t('catalog.prices.list.error.delete', 'Failed to delete price rule')\n const message = err instanceof Error ? err.message : fallback\n flash(message, 'error')\n }\n }, [confirm, queryClient, t])\n\n const columns = React.useMemo<ColumnDef<PriceRow>[]>(() => [\n {\n id: 'product',\n header: t('catalog.prices.list.columns.product', 'Product'),\n meta: { priority: 1 },\n cell: ({ row }) => {\n const price = row.original\n const label = price.variantId\n ? (variantLabels[price.variantId] ?? price.variantId)\n : (price.productId ? (productLabels[price.productId] ?? price.productId) : '\u2014')\n return <span className=\"text-sm font-medium text-foreground\">{label}</span>\n },\n },\n {\n id: 'kind',\n header: t('catalog.prices.list.columns.kind', 'Kind'),\n meta: { priority: 2 },\n cell: ({ row }) => {\n const kind = row.original.kind || 'regular'\n return <Tag variant={KIND_TAG_VARIANT[kind] ?? 'neutral'}>{kind}</Tag>\n },\n },\n {\n id: 'amount',\n header: t('catalog.prices.list.columns.amount', 'Amount'),\n meta: { priority: 1 },\n cell: ({ row }) => <span className=\"text-sm\">{formatAmount(row.original)}</span>,\n },\n {\n id: 'scope',\n header: t('catalog.prices.list.columns.scope', 'Scope'),\n meta: { priority: 3 },\n cell: ({ row }) => {\n const price = row.original\n const chips: React.ReactNode[] = []\n if (price.customerId) {\n chips.push(\n <Tag key=\"customer\" variant=\"info\" dot>\n {customerLabels[price.customerId] ?? t('catalog.prices.list.scope.customer', 'Customer')}\n </Tag>,\n )\n }\n if (price.customerGroupId) {\n chips.push(\n <Tag key=\"customerGroup\" variant=\"info\" dot>\n {t('catalog.prices.list.scope.customerGroup', 'Customer group')}\n </Tag>,\n )\n }\n if (price.channelId) {\n chips.push(\n <Tag key=\"channel\" variant=\"neutral\" dot>\n {channelLabels[price.channelId] ?? t('catalog.prices.list.scope.channel', 'Channel')}\n </Tag>,\n )\n }\n if (!chips.length) return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n return <div className=\"flex flex-wrap gap-1\">{chips}</div>\n },\n },\n {\n id: 'quantity',\n header: t('catalog.prices.list.columns.quantity', 'Quantity'),\n meta: { priority: 4 },\n cell: ({ row }) => <span className=\"text-sm\">{formatQuantityRange(row.original, t)}</span>,\n },\n {\n id: 'validity',\n header: t('catalog.prices.list.columns.validity', 'Validity'),\n meta: { priority: 5 },\n cell: ({ row }) => {\n const price = row.original\n const isExpired = Boolean(price.endsAt && new Date(price.endsAt) < new Date())\n if (!price.startsAt && !price.endsAt) {\n return <span className=\"text-sm text-muted-foreground\">{t('catalog.prices.list.validity.always', 'Always')}</span>\n }\n const from = price.startsAt ? new Date(price.startsAt).toLocaleDateString() : null\n const until = price.endsAt ? new Date(price.endsAt).toLocaleDateString() : null\n const label = from && until\n ? t('catalog.prices.list.validity.range', '{{from}} \u2013 {{until}}', { from, until })\n : from\n ? t('catalog.prices.list.validity.from', 'From {{date}}', { date: from })\n : t('catalog.prices.list.validity.until', 'Until {{date}}', { date: until ?? '' })\n return (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm\">{label}</span>\n {isExpired ? <Tag variant=\"error\" dot>{t('catalog.prices.list.badge.expired', 'Expired')}</Tag> : null}\n </div>\n )\n },\n },\n ], [t, variantLabels, productLabels, customerLabels, channelLabels])\n\n return (\n <>\n <DataTable\n title={t('catalog.prices.list.title', 'Price rules')}\n titleHeadingLevel={1}\n actions={canManage ? (\n <Button asChild>\n <Link href=\"/backend/catalog/prices/create\">\n {t('catalog.prices.list.actions.create', 'Create')}\n </Link>\n </Button>\n ) : undefined}\n columns={columns}\n data={rows}\n emptyState={(\n <ListEmptyState\n entityName={t('catalog.prices.list.title', 'Price rules')}\n description={t('catalog.prices.list.empty', 'No price rules yet \u2014 add one to price this product beyond its base price.')}\n createHref={canManage ? '/backend/catalog/prices/create' : undefined}\n createLabel={canManage ? t('catalog.prices.list.actions.create', 'Create') : undefined}\n />\n )}\n searchValue={search}\n searchPlaceholder={t('catalog.prices.list.searchPlaceholder', 'Search price rules')}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n sortable={false}\n perspective={{ tableId: extensionPoints.hosts.pricesTable.tableId }}\n rowActions={(row) => (\n canManage ? (\n <RowActions\n items={[\n { id: 'edit', label: t('catalog.prices.list.actions.edit', 'Edit'), href: `/backend/catalog/prices/${row.id}/edit` },\n { id: 'delete', label: t('catalog.prices.list.actions.delete', 'Delete'), destructive: true, onSelect: () => handleDelete(row) },\n ]}\n />\n ) : null\n )}\n pagination={{\n page,\n pageSize: PAGE_SIZE,\n total,\n totalPages,\n totalIsCapped,\n onPageChange: setPage,\n }}\n isLoading={isLoading}\n />\n {ConfirmDialogElement}\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,43 @@
1
+ function str(record, camelKey, snakeKey) {
2
+ const value = record[camelKey] ?? record[snakeKey];
3
+ return typeof value === "string" && value.length ? value : null;
4
+ }
5
+ function num(record, camelKey, snakeKey, fallback) {
6
+ const value = record[camelKey] ?? record[snakeKey];
7
+ const parsed = typeof value === "number" ? value : typeof value === "string" ? Number(value) : NaN;
8
+ return Number.isFinite(parsed) ? parsed : fallback;
9
+ }
10
+ function numOrNull(record, camelKey, snakeKey) {
11
+ const value = record[camelKey] ?? record[snakeKey];
12
+ if (value == null) return null;
13
+ const parsed = typeof value === "number" ? value : Number(value);
14
+ return Number.isFinite(parsed) ? parsed : null;
15
+ }
16
+ function normalizePriceRecord(record) {
17
+ return {
18
+ id: str(record, "id", "id") ?? "",
19
+ productId: str(record, "productId", "product_id"),
20
+ variantId: str(record, "variantId", "variant_id"),
21
+ priceKindId: str(record, "priceKindId", "price_kind_id"),
22
+ currencyCode: str(record, "currencyCode", "currency_code"),
23
+ kind: str(record, "kind", "kind") ?? "regular",
24
+ unitPriceNet: str(record, "unitPriceNet", "unit_price_net"),
25
+ unitPriceGross: str(record, "unitPriceGross", "unit_price_gross"),
26
+ taxRate: str(record, "taxRate", "tax_rate"),
27
+ minQuantity: num(record, "minQuantity", "min_quantity", 1),
28
+ maxQuantity: numOrNull(record, "maxQuantity", "max_quantity"),
29
+ startsAt: str(record, "startsAt", "starts_at"),
30
+ endsAt: str(record, "endsAt", "ends_at"),
31
+ customerId: str(record, "customerId", "customer_id"),
32
+ customerGroupId: str(record, "customerGroupId", "customer_group_id"),
33
+ channelId: str(record, "channelId", "channel_id"),
34
+ offerId: str(record, "offerId", "offer_id"),
35
+ userId: str(record, "userId", "user_id"),
36
+ userGroupId: str(record, "userGroupId", "user_group_id"),
37
+ updatedAt: str(record, "updatedAt", "updated_at")
38
+ };
39
+ }
40
+ export {
41
+ normalizePriceRecord
42
+ };
43
+ //# sourceMappingURL=normalizePriceRecord.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/catalog/components/prices/normalizePriceRecord.ts"],
4
+ "sourcesContent": ["// `/api/catalog/prices` list/detail responses serialize snake_case column\n// names (verified against the live route by\n// `__integration__/TC-CAT-CRUDFORM-002.spec.ts`: `variant_id`,\n// `unit_price_gross`, `min_quantity`, ...) \u2014 this normalizes either shape\n// into one camelCase record so UI code never has to guess which one it got.\n\nexport type NormalizedPriceRecord = {\n id: string\n productId: string | null\n variantId: string | null\n priceKindId: string | null\n currencyCode: string | null\n kind: string\n unitPriceNet: string | null\n unitPriceGross: string | null\n taxRate: string | null\n minQuantity: number\n maxQuantity: number | null\n startsAt: string | null\n endsAt: string | null\n customerId: string | null\n customerGroupId: string | null\n channelId: string | null\n offerId: string | null\n userId: string | null\n userGroupId: string | null\n updatedAt: string | null\n}\n\nfunction str(record: Record<string, unknown>, camelKey: string, snakeKey: string): string | null {\n const value = record[camelKey] ?? record[snakeKey]\n return typeof value === 'string' && value.length ? value : null\n}\n\nfunction num(record: Record<string, unknown>, camelKey: string, snakeKey: string, fallback: number): number {\n const value = record[camelKey] ?? record[snakeKey]\n const parsed = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN\n return Number.isFinite(parsed) ? parsed : fallback\n}\n\nfunction numOrNull(record: Record<string, unknown>, camelKey: string, snakeKey: string): number | null {\n const value = record[camelKey] ?? record[snakeKey]\n if (value == null) return null\n const parsed = typeof value === 'number' ? value : Number(value)\n return Number.isFinite(parsed) ? parsed : null\n}\n\nexport function normalizePriceRecord(record: Record<string, unknown>): NormalizedPriceRecord {\n return {\n id: str(record, 'id', 'id') ?? '',\n productId: str(record, 'productId', 'product_id'),\n variantId: str(record, 'variantId', 'variant_id'),\n priceKindId: str(record, 'priceKindId', 'price_kind_id'),\n currencyCode: str(record, 'currencyCode', 'currency_code'),\n kind: str(record, 'kind', 'kind') ?? 'regular',\n unitPriceNet: str(record, 'unitPriceNet', 'unit_price_net'),\n unitPriceGross: str(record, 'unitPriceGross', 'unit_price_gross'),\n taxRate: str(record, 'taxRate', 'tax_rate'),\n minQuantity: num(record, 'minQuantity', 'min_quantity', 1),\n maxQuantity: numOrNull(record, 'maxQuantity', 'max_quantity'),\n startsAt: str(record, 'startsAt', 'starts_at'),\n endsAt: str(record, 'endsAt', 'ends_at'),\n customerId: str(record, 'customerId', 'customer_id'),\n customerGroupId: str(record, 'customerGroupId', 'customer_group_id'),\n channelId: str(record, 'channelId', 'channel_id'),\n offerId: str(record, 'offerId', 'offer_id'),\n userId: str(record, 'userId', 'user_id'),\n userGroupId: str(record, 'userGroupId', 'user_group_id'),\n updatedAt: str(record, 'updatedAt', 'updated_at'),\n }\n}\n"],
5
+ "mappings": "AA6BA,SAAS,IAAI,QAAiC,UAAkB,UAAiC;AAC/F,QAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,QAAQ;AACjD,SAAO,OAAO,UAAU,YAAY,MAAM,SAAS,QAAQ;AAC7D;AAEA,SAAS,IAAI,QAAiC,UAAkB,UAAkB,UAA0B;AAC1G,QAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,QAAQ;AACjD,QAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,UAAU,WAAW,OAAO,KAAK,IAAI;AAC/F,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEA,SAAS,UAAU,QAAiC,UAAkB,UAAiC;AACrG,QAAM,QAAQ,OAAO,QAAQ,KAAK,OAAO,QAAQ;AACjD,MAAI,SAAS,KAAM,QAAO;AAC1B,QAAM,SAAS,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;AAC/D,SAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;AAEO,SAAS,qBAAqB,QAAwD;AAC3F,SAAO;AAAA,IACL,IAAI,IAAI,QAAQ,MAAM,IAAI,KAAK;AAAA,IAC/B,WAAW,IAAI,QAAQ,aAAa,YAAY;AAAA,IAChD,WAAW,IAAI,QAAQ,aAAa,YAAY;AAAA,IAChD,aAAa,IAAI,QAAQ,eAAe,eAAe;AAAA,IACvD,cAAc,IAAI,QAAQ,gBAAgB,eAAe;AAAA,IACzD,MAAM,IAAI,QAAQ,QAAQ,MAAM,KAAK;AAAA,IACrC,cAAc,IAAI,QAAQ,gBAAgB,gBAAgB;AAAA,IAC1D,gBAAgB,IAAI,QAAQ,kBAAkB,kBAAkB;AAAA,IAChE,SAAS,IAAI,QAAQ,WAAW,UAAU;AAAA,IAC1C,aAAa,IAAI,QAAQ,eAAe,gBAAgB,CAAC;AAAA,IACzD,aAAa,UAAU,QAAQ,eAAe,cAAc;AAAA,IAC5D,UAAU,IAAI,QAAQ,YAAY,WAAW;AAAA,IAC7C,QAAQ,IAAI,QAAQ,UAAU,SAAS;AAAA,IACvC,YAAY,IAAI,QAAQ,cAAc,aAAa;AAAA,IACnD,iBAAiB,IAAI,QAAQ,mBAAmB,mBAAmB;AAAA,IACnE,WAAW,IAAI,QAAQ,aAAa,YAAY;AAAA,IAChD,SAAS,IAAI,QAAQ,WAAW,UAAU;AAAA,IAC1C,QAAQ,IAAI,QAAQ,UAAU,SAAS;AAAA,IACvC,aAAa,IAAI,QAAQ,eAAe,eAAe;AAAA,IACvD,WAAW,IAAI,QAAQ,aAAa,YAAY;AAAA,EAClD;AACF;",
6
+ "names": []
7
+ }
@@ -0,0 +1,195 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
+ import {
6
+ PriceChannelSelect,
7
+ PriceCurrencySelect,
8
+ PriceCustomerSelect,
9
+ PricePriceKindSelect,
10
+ PriceProductSelect,
11
+ PriceVariantSelect
12
+ } from "./PriceScopeSelectors.js";
13
+ const PRICE_FORM_INITIAL_VALUES = {
14
+ productId: "",
15
+ variantId: "",
16
+ priceKindId: "",
17
+ currencyCode: "",
18
+ unitPriceNet: "",
19
+ unitPriceGross: "",
20
+ taxRate: "",
21
+ minQuantity: 1,
22
+ maxQuantity: void 0,
23
+ startsAt: "",
24
+ endsAt: "",
25
+ customerId: "",
26
+ customerGroupId: "",
27
+ channelId: "",
28
+ offerId: "",
29
+ userId: "",
30
+ userGroupId: ""
31
+ };
32
+ function usePriceFormFields() {
33
+ const t = useT();
34
+ const fields = React.useMemo(() => [
35
+ {
36
+ id: "productId",
37
+ label: t("catalog.prices.form.field.product", "Product"),
38
+ type: "custom",
39
+ component: ({ value, setValue }) => /* @__PURE__ */ jsx(PriceProductSelect, { value: typeof value === "string" ? value : "", onChange: setValue })
40
+ },
41
+ {
42
+ id: "variantId",
43
+ label: t("catalog.prices.form.field.variant", "Variant"),
44
+ type: "custom",
45
+ component: ({ value, setValue, values }) => /* @__PURE__ */ jsx(
46
+ PriceVariantSelect,
47
+ {
48
+ productId: typeof values?.productId === "string" ? values.productId : "",
49
+ value: typeof value === "string" ? value : "",
50
+ onChange: setValue
51
+ }
52
+ )
53
+ },
54
+ {
55
+ id: "priceKindId",
56
+ label: t("catalog.prices.form.field.priceKind", "Price kind"),
57
+ type: "custom",
58
+ required: true,
59
+ component: ({ value, setValue }) => /* @__PURE__ */ jsx(PricePriceKindSelect, { value: typeof value === "string" ? value : "", onChange: setValue })
60
+ },
61
+ {
62
+ id: "currencyCode",
63
+ label: t("catalog.prices.form.field.currency", "Currency"),
64
+ type: "custom",
65
+ required: true,
66
+ component: ({ value, setValue }) => /* @__PURE__ */ jsx(PriceCurrencySelect, { value: typeof value === "string" ? value : "", onChange: setValue })
67
+ },
68
+ {
69
+ id: "unitPriceNet",
70
+ label: t("catalog.prices.form.field.unitPriceNet", "Unit price (net)"),
71
+ type: "text",
72
+ layout: "half"
73
+ },
74
+ {
75
+ id: "unitPriceGross",
76
+ label: t("catalog.prices.form.field.unitPriceGross", "Unit price (gross)"),
77
+ type: "text",
78
+ layout: "half"
79
+ },
80
+ {
81
+ id: "taxRate",
82
+ label: t("catalog.prices.form.field.taxRate", "Tax rate"),
83
+ type: "text",
84
+ layout: "half"
85
+ },
86
+ {
87
+ id: "minQuantity",
88
+ label: t("catalog.prices.form.field.minQuantity", "Minimum quantity"),
89
+ type: "number",
90
+ layout: "half"
91
+ },
92
+ {
93
+ id: "maxQuantity",
94
+ label: t("catalog.prices.form.field.maxQuantity", "Maximum quantity"),
95
+ type: "number",
96
+ layout: "half",
97
+ description: t("catalog.prices.form.field.maxQuantityHint", 'Leave empty for "and above"')
98
+ },
99
+ {
100
+ id: "startsAt",
101
+ label: t("catalog.prices.form.field.startsAt", "Starts at"),
102
+ type: "datepicker",
103
+ layout: "half"
104
+ },
105
+ {
106
+ id: "endsAt",
107
+ label: t("catalog.prices.form.field.endsAt", "Ends at"),
108
+ type: "datepicker",
109
+ layout: "half"
110
+ },
111
+ {
112
+ id: "customerId",
113
+ label: t("catalog.prices.form.field.customer", "Customer"),
114
+ type: "custom",
115
+ component: ({ value, setValue }) => /* @__PURE__ */ jsx(PriceCustomerSelect, { value: typeof value === "string" ? value : "", onChange: setValue })
116
+ },
117
+ {
118
+ id: "customerGroupId",
119
+ label: t("catalog.prices.form.field.customerGroupId", "Customer group ID"),
120
+ type: "text",
121
+ description: t(
122
+ "catalog.prices.form.field.customerGroupIdHint",
123
+ "Raw customer-group identifier \u2014 no customer-group directory yet."
124
+ )
125
+ },
126
+ {
127
+ id: "channelId",
128
+ label: t("catalog.prices.form.field.channel", "Channel"),
129
+ type: "custom",
130
+ component: ({ value, setValue }) => /* @__PURE__ */ jsx(PriceChannelSelect, { value: typeof value === "string" ? value : "", onChange: setValue })
131
+ },
132
+ {
133
+ id: "offerId",
134
+ label: t("catalog.prices.form.field.offer", "Offer"),
135
+ type: "text",
136
+ layout: "half"
137
+ },
138
+ {
139
+ id: "userId",
140
+ label: t("catalog.prices.form.field.userId", "User ID"),
141
+ type: "text",
142
+ layout: "half"
143
+ },
144
+ {
145
+ id: "userGroupId",
146
+ label: t("catalog.prices.form.field.userGroupId", "User group ID"),
147
+ type: "text",
148
+ layout: "half"
149
+ }
150
+ ], [t]);
151
+ const groups = React.useMemo(() => [
152
+ {
153
+ id: "product",
154
+ title: t("catalog.prices.form.group.product", "Product"),
155
+ column: 1,
156
+ fields: ["productId", "variantId"]
157
+ },
158
+ {
159
+ id: "pricing",
160
+ title: t("catalog.prices.form.group.pricing", "Pricing"),
161
+ column: 1,
162
+ fields: ["priceKindId", "currencyCode", "unitPriceNet", "unitPriceGross", "taxRate"]
163
+ },
164
+ {
165
+ id: "quantity",
166
+ title: t("catalog.prices.form.group.quantity", "Quantity tier"),
167
+ column: 1,
168
+ fields: ["minQuantity", "maxQuantity"]
169
+ },
170
+ {
171
+ id: "validity",
172
+ title: t("catalog.prices.form.group.validity", "Validity window"),
173
+ column: 1,
174
+ fields: ["startsAt", "endsAt"]
175
+ },
176
+ {
177
+ id: "scope",
178
+ title: t("catalog.prices.form.group.scope", "Scope"),
179
+ column: 2,
180
+ fields: ["customerId", "customerGroupId", "channelId"]
181
+ },
182
+ {
183
+ id: "advanced",
184
+ title: t("catalog.prices.form.group.advanced", "Advanced scope"),
185
+ column: 2,
186
+ fields: ["offerId", "userId", "userGroupId"]
187
+ }
188
+ ], [t]);
189
+ return { fields, groups };
190
+ }
191
+ export {
192
+ PRICE_FORM_INITIAL_VALUES,
193
+ usePriceFormFields
194
+ };
195
+ //# sourceMappingURL=priceFormFields.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../../../src/modules/catalog/components/prices/priceFormFields.tsx"],
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { CrudField, CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport {\n PriceChannelSelect,\n PriceCurrencySelect,\n PriceCustomerSelect,\n PricePriceKindSelect,\n PriceProductSelect,\n PriceVariantSelect,\n} from './PriceScopeSelectors'\n\nexport type PriceFormValues = {\n id?: string\n productId?: string\n variantId?: string\n priceKindId?: string\n currencyCode?: string\n unitPriceNet?: string\n unitPriceGross?: string\n taxRate?: string\n minQuantity?: number\n maxQuantity?: number\n startsAt?: string\n endsAt?: string\n customerId?: string\n customerGroupId?: string\n channelId?: string\n offerId?: string\n userId?: string\n userGroupId?: string\n updatedAt?: string | null\n}\n\nexport const PRICE_FORM_INITIAL_VALUES: PriceFormValues = {\n productId: '',\n variantId: '',\n priceKindId: '',\n currencyCode: '',\n unitPriceNet: '',\n unitPriceGross: '',\n taxRate: '',\n minQuantity: 1,\n maxQuantity: undefined,\n startsAt: '',\n endsAt: '',\n customerId: '',\n customerGroupId: '',\n channelId: '',\n offerId: '',\n userId: '',\n userGroupId: '',\n}\n\n/** Shared field/group config for the price-rule create and edit CrudForms. */\nexport function usePriceFormFields(): { fields: CrudField[]; groups: CrudFormGroup[] } {\n const t = useT()\n\n const fields = React.useMemo<CrudField[]>(() => [\n {\n id: 'productId',\n label: t('catalog.prices.form.field.product', 'Product'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <PriceProductSelect value={typeof value === 'string' ? value : ''} onChange={setValue} />\n ),\n },\n {\n id: 'variantId',\n label: t('catalog.prices.form.field.variant', 'Variant'),\n type: 'custom',\n component: ({ value, setValue, values }) => (\n <PriceVariantSelect\n productId={typeof values?.productId === 'string' ? values.productId : ''}\n value={typeof value === 'string' ? value : ''}\n onChange={setValue}\n />\n ),\n },\n {\n id: 'priceKindId',\n label: t('catalog.prices.form.field.priceKind', 'Price kind'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => (\n <PricePriceKindSelect value={typeof value === 'string' ? value : ''} onChange={setValue} />\n ),\n },\n {\n id: 'currencyCode',\n label: t('catalog.prices.form.field.currency', 'Currency'),\n type: 'custom',\n required: true,\n component: ({ value, setValue }) => (\n <PriceCurrencySelect value={typeof value === 'string' ? value : ''} onChange={setValue} />\n ),\n },\n {\n id: 'unitPriceNet',\n label: t('catalog.prices.form.field.unitPriceNet', 'Unit price (net)'),\n type: 'text',\n layout: 'half',\n },\n {\n id: 'unitPriceGross',\n label: t('catalog.prices.form.field.unitPriceGross', 'Unit price (gross)'),\n type: 'text',\n layout: 'half',\n },\n {\n id: 'taxRate',\n label: t('catalog.prices.form.field.taxRate', 'Tax rate'),\n type: 'text',\n layout: 'half',\n },\n {\n id: 'minQuantity',\n label: t('catalog.prices.form.field.minQuantity', 'Minimum quantity'),\n type: 'number',\n layout: 'half',\n },\n {\n id: 'maxQuantity',\n label: t('catalog.prices.form.field.maxQuantity', 'Maximum quantity'),\n type: 'number',\n layout: 'half',\n description: t('catalog.prices.form.field.maxQuantityHint', 'Leave empty for \"and above\"'),\n },\n {\n id: 'startsAt',\n label: t('catalog.prices.form.field.startsAt', 'Starts at'),\n type: 'datepicker',\n layout: 'half',\n },\n {\n id: 'endsAt',\n label: t('catalog.prices.form.field.endsAt', 'Ends at'),\n type: 'datepicker',\n layout: 'half',\n },\n {\n id: 'customerId',\n label: t('catalog.prices.form.field.customer', 'Customer'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <PriceCustomerSelect value={typeof value === 'string' ? value : ''} onChange={setValue} />\n ),\n },\n {\n id: 'customerGroupId',\n label: t('catalog.prices.form.field.customerGroupId', 'Customer group ID'),\n type: 'text',\n description: t(\n 'catalog.prices.form.field.customerGroupIdHint',\n 'Raw customer-group identifier \u2014 no customer-group directory yet.',\n ),\n },\n {\n id: 'channelId',\n label: t('catalog.prices.form.field.channel', 'Channel'),\n type: 'custom',\n component: ({ value, setValue }) => (\n <PriceChannelSelect value={typeof value === 'string' ? value : ''} onChange={setValue} />\n ),\n },\n {\n id: 'offerId',\n label: t('catalog.prices.form.field.offer', 'Offer'),\n type: 'text',\n layout: 'half',\n },\n {\n id: 'userId',\n label: t('catalog.prices.form.field.userId', 'User ID'),\n type: 'text',\n layout: 'half',\n },\n {\n id: 'userGroupId',\n label: t('catalog.prices.form.field.userGroupId', 'User group ID'),\n type: 'text',\n layout: 'half',\n },\n ], [t])\n\n const groups = React.useMemo<CrudFormGroup[]>(() => [\n {\n id: 'product',\n title: t('catalog.prices.form.group.product', 'Product'),\n column: 1,\n fields: ['productId', 'variantId'],\n },\n {\n id: 'pricing',\n title: t('catalog.prices.form.group.pricing', 'Pricing'),\n column: 1,\n fields: ['priceKindId', 'currencyCode', 'unitPriceNet', 'unitPriceGross', 'taxRate'],\n },\n {\n id: 'quantity',\n title: t('catalog.prices.form.group.quantity', 'Quantity tier'),\n column: 1,\n fields: ['minQuantity', 'maxQuantity'],\n },\n {\n id: 'validity',\n title: t('catalog.prices.form.group.validity', 'Validity window'),\n column: 1,\n fields: ['startsAt', 'endsAt'],\n },\n {\n id: 'scope',\n title: t('catalog.prices.form.group.scope', 'Scope'),\n column: 2,\n fields: ['customerId', 'customerGroupId', 'channelId'],\n },\n {\n id: 'advanced',\n title: t('catalog.prices.form.group.advanced', 'Advanced scope'),\n column: 2,\n fields: ['offerId', 'userId', 'userGroupId'],\n },\n ], [t])\n\n return { fields, groups }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }