@open-mercato/core 0.8.1-develop.7221.1.ece7ca874e → 0.8.1-develop.7227.1.9248c14599
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.js +166 -0
- package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.meta.js +16 -0
- package/dist/modules/catalog/backend/catalog/prices/[id]/edit/page.meta.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/prices/create/page.js +68 -0
- package/dist/modules/catalog/backend/catalog/prices/create/page.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/prices/create/page.meta.js +25 -0
- package/dist/modules/catalog/backend/catalog/prices/create/page.meta.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/prices/page.js +11 -0
- package/dist/modules/catalog/backend/catalog/prices/page.js.map +7 -0
- package/dist/modules/catalog/backend/catalog/prices/page.meta.js +16 -0
- package/dist/modules/catalog/backend/catalog/prices/page.meta.js.map +7 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js +201 -0
- package/dist/modules/catalog/components/prices/PriceScopeSelectors.js.map +7 -0
- package/dist/modules/catalog/components/prices/PricesDataTable.js +273 -0
- package/dist/modules/catalog/components/prices/PricesDataTable.js.map +7 -0
- package/dist/modules/catalog/components/prices/normalizePriceRecord.js +43 -0
- package/dist/modules/catalog/components/prices/normalizePriceRecord.js.map +7 -0
- package/dist/modules/catalog/components/prices/priceFormFields.js +195 -0
- package/dist/modules/catalog/components/prices/priceFormFields.js.map +7 -0
- package/dist/modules/catalog/components/prices/useBatchLabels.js +42 -0
- package/dist/modules/catalog/components/prices/useBatchLabels.js.map +7 -0
- package/dist/modules/catalog/data/validators.js +21 -2
- package/dist/modules/catalog/data/validators.js.map +2 -2
- package/dist/modules/catalog/extension-points.js +2 -1
- package/dist/modules/catalog/extension-points.js.map +2 -2
- package/dist/modules/catalog/lib/pricing.js +37 -8
- package/dist/modules/catalog/lib/pricing.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js +5 -1
- package/dist/modules/communication_channels/api/post/channels/[id]/import-history/route.js.map +2 -2
- package/dist/modules/communication_channels/backend/profile/communication-channels/ConnectChannelMenu.js +88 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/ConnectChannelMenu.js.map +7 -0
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +17 -19
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- package/dist/modules/communication_channels/commands/connect-credential-channel.js +7 -2
- package/dist/modules/communication_channels/commands/connect-credential-channel.js.map +2 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js +11 -2
- package/dist/modules/communication_channels/commands/ingest-inbound-message.js.map +2 -2
- package/dist/modules/communication_channels/commands/queue-import-history.js +40 -7
- package/dist/modules/communication_channels/commands/queue-import-history.js.map +2 -2
- package/dist/modules/communication_channels/extension-points.js +1 -1
- package/dist/modules/communication_channels/extension-points.js.map +1 -1
- package/dist/modules/communication_channels/lib/connect-channel.js +33 -0
- package/dist/modules/communication_channels/lib/connect-channel.js.map +2 -2
- package/dist/modules/communication_channels/lib/import-history-limits.js +69 -0
- package/dist/modules/communication_channels/lib/import-history-limits.js.map +7 -0
- package/dist/modules/communication_channels/workers/channel-import-history.js +36 -2
- package/dist/modules/communication_channels/workers/channel-import-history.js.map +2 -2
- package/dist/modules/messages/api/route.js +5 -1
- package/dist/modules/messages/api/route.js.map +2 -2
- package/dist/modules/messages/commands/messages.js +3 -2
- package/dist/modules/messages/commands/messages.js.map +2 -2
- package/dist/modules/messages/data/validators.js +12 -2
- package/dist/modules/messages/data/validators.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +4 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/query_index/lib/engine.js +19 -1
- package/dist/modules/query_index/lib/engine.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/catalog/AGENTS.md +6 -2
- package/src/modules/catalog/backend/catalog/prices/[id]/edit/page.meta.ts +12 -0
- package/src/modules/catalog/backend/catalog/prices/[id]/edit/page.tsx +198 -0
- package/src/modules/catalog/backend/catalog/prices/create/page.meta.ts +23 -0
- package/src/modules/catalog/backend/catalog/prices/create/page.tsx +70 -0
- package/src/modules/catalog/backend/catalog/prices/page.meta.ts +12 -0
- package/src/modules/catalog/backend/catalog/prices/page.tsx +14 -0
- package/src/modules/catalog/components/prices/PriceScopeSelectors.tsx +243 -0
- package/src/modules/catalog/components/prices/PricesDataTable.tsx +320 -0
- package/src/modules/catalog/components/prices/normalizePriceRecord.ts +71 -0
- package/src/modules/catalog/components/prices/priceFormFields.tsx +228 -0
- package/src/modules/catalog/components/prices/useBatchLabels.ts +57 -0
- package/src/modules/catalog/data/validators.ts +43 -2
- package/src/modules/catalog/extension-points.ts +1 -0
- package/src/modules/catalog/i18n/de.json +72 -0
- package/src/modules/catalog/i18n/en.json +72 -0
- package/src/modules/catalog/i18n/es.json +72 -0
- package/src/modules/catalog/i18n/ko.json +72 -0
- package/src/modules/catalog/i18n/pl.json +72 -0
- package/src/modules/catalog/lib/pricing.ts +103 -9
- package/src/modules/communication_channels/api/post/channels/[id]/import-history/route.ts +6 -1
- package/src/modules/communication_channels/backend/profile/communication-channels/ConnectChannelMenu.tsx +120 -0
- package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +20 -17
- package/src/modules/communication_channels/commands/connect-credential-channel.ts +9 -0
- package/src/modules/communication_channels/commands/ingest-inbound-message.ts +17 -2
- package/src/modules/communication_channels/commands/queue-import-history.ts +59 -11
- package/src/modules/communication_channels/extension-points.ts +1 -1
- package/src/modules/communication_channels/i18n/de.json +4 -3
- package/src/modules/communication_channels/i18n/en.json +4 -3
- package/src/modules/communication_channels/i18n/es.json +4 -3
- package/src/modules/communication_channels/i18n/ko.json +4 -3
- package/src/modules/communication_channels/i18n/pl.json +4 -3
- package/src/modules/communication_channels/lib/adapter.ts +16 -2
- package/src/modules/communication_channels/lib/connect-channel.ts +49 -0
- package/src/modules/communication_channels/lib/import-history-limits.ts +109 -0
- package/src/modules/communication_channels/workers/channel-import-history.ts +46 -5
- package/src/modules/messages/api/route.ts +8 -2
- package/src/modules/messages/commands/messages.ts +17 -2
- package/src/modules/messages/data/validators.ts +26 -11
- package/src/modules/messages/subscribers/message-notification.ts +17 -5
- package/src/modules/query_index/lib/engine.ts +23 -1
- 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
|
+
}
|