@dainamite/cpq 0.6.0 → 0.7.0
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/CHANGELOG.md +16 -0
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
- package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
- package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
- package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +154 -178
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
- package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
- package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +69 -66
- package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
- package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
- package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
- package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
- package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
- package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
- package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
- package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
- package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
- package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +6 -3
- package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +129 -94
- package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
- package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
- package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
- package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
- package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
- package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
- package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
- package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
- package/dist/modules/cpq/components/ArcLineDiff.js +2 -2
- package/dist/modules/cpq/components/ArcLineDiff.js.map +2 -2
- package/dist/modules/cpq/components/ChargeBreakdownTable.js +4 -10
- package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +2 -2
- package/dist/modules/cpq/components/PriceWaterfall.js +3 -1
- package/dist/modules/cpq/components/PriceWaterfall.js.map +2 -2
- package/dist/modules/cpq/components/PricingSummaryCard.js +1 -6
- package/dist/modules/cpq/components/PricingSummaryCard.js.map +2 -2
- package/dist/modules/cpq/components/StatusPath.js +5 -3
- package/dist/modules/cpq/components/StatusPath.js.map +2 -2
- package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
- package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
- package/dist/modules/cpq/i18n/de.json +1220 -0
- package/dist/modules/cpq/i18n/en.json +1220 -0
- package/dist/modules/cpq/i18n/es.json +1220 -0
- package/dist/modules/cpq/i18n/pl.json +1220 -0
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
- package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
- package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
- package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
- package/dist/modules/cpq/widgets/injection-table.js +2 -2
- package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
- package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
- package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
- package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
- package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
- package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
- package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
- package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
- package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
- package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
- package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
- package/dist/modules/cpq/workflows/steps/index.js +12 -0
- package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
- package/package.json +1 -1
- package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
- package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
- package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
- package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
- package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
- package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +2 -2
- package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +134 -112
- package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
- package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
- package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +66 -43
- package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
- package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
- package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
- package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
- package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
- package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
- package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
- package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
- package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
- package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +3 -3
- package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +134 -89
- package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
- package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
- package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
- package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
- package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
- package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
- package/src/modules/cpq/components/ArcLineDiff.tsx +2 -2
- package/src/modules/cpq/components/ChargeBreakdownTable.tsx +4 -2
- package/src/modules/cpq/components/PriceWaterfall.tsx +4 -2
- package/src/modules/cpq/components/PricingSummaryCard.tsx +1 -1
- package/src/modules/cpq/components/StatusPath.tsx +7 -4
- package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
- package/src/modules/cpq/i18n/de.json +1220 -0
- package/src/modules/cpq/i18n/en.json +1220 -0
- package/src/modules/cpq/i18n/es.json +1220 -0
- package/src/modules/cpq/i18n/pl.json +1220 -0
- package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
- package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
- package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
- package/src/modules/cpq/widgets/injection-table.ts +8 -2
- package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
- package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
- package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
- package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
- package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
- package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
- package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
- package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
- package/src/modules/cpq/workflows/steps/index.ts +12 -0
- package/src/modules/cpq/workflows/types.ts +4 -0
|
@@ -8,7 +8,7 @@ import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
|
8
8
|
import { CpqListView, useCpqListData } from "../../../components/CpqListView.js";
|
|
9
9
|
import {
|
|
10
10
|
PriceRuleForm,
|
|
11
|
-
|
|
11
|
+
buildRuleTypeLabels,
|
|
12
12
|
emptyPriceRuleForm,
|
|
13
13
|
priceRuleFormToPayload
|
|
14
14
|
} from "./PriceRuleForm.js";
|
|
@@ -48,17 +48,18 @@ function PriceRulesPage() {
|
|
|
48
48
|
fetch("/api/cpq/product-offerings?pageSize=100").then((r) => r.json()).then((d) => setOfferings(d.items ?? [])).catch(() => {
|
|
49
49
|
});
|
|
50
50
|
}, []);
|
|
51
|
+
const ruleTypeLabels = React.useMemo(() => buildRuleTypeLabels(t), [t]);
|
|
51
52
|
const ruleTypeOptions = React.useMemo(
|
|
52
|
-
() => Object.entries(
|
|
53
|
-
[]
|
|
53
|
+
() => Object.entries(ruleTypeLabels).map(([value, label]) => ({ value, label })),
|
|
54
|
+
[ruleTypeLabels]
|
|
54
55
|
);
|
|
55
56
|
const chargeTypeOptions = React.useMemo(
|
|
56
57
|
() => [
|
|
57
|
-
{ value: "nrc", label: "NRC" },
|
|
58
|
-
{ value: "mrc", label: "MRC" },
|
|
59
|
-
{ value: "usage", label: "Usage" }
|
|
58
|
+
{ value: "nrc", label: t("cpq.priceRules.chargeTypes.nrc", "NRC") },
|
|
59
|
+
{ value: "mrc", label: t("cpq.priceRules.chargeTypes.mrc", "MRC") },
|
|
60
|
+
{ value: "usage", label: t("cpq.priceRules.chargeTypes.usage", "Usage") }
|
|
60
61
|
],
|
|
61
|
-
[]
|
|
62
|
+
[t]
|
|
62
63
|
);
|
|
63
64
|
const filters = React.useMemo(
|
|
64
65
|
() => [
|
|
@@ -104,16 +105,18 @@ function PriceRulesPage() {
|
|
|
104
105
|
});
|
|
105
106
|
if (!res.ok) {
|
|
106
107
|
const err = await res.json();
|
|
107
|
-
throw new Error(
|
|
108
|
+
throw new Error(
|
|
109
|
+
err.details ? JSON.stringify(err.details) : err.error ?? t("cpq.priceRules.errors.createFailed", "Failed to create")
|
|
110
|
+
);
|
|
108
111
|
}
|
|
109
112
|
setShowForm(false);
|
|
110
113
|
data.reload();
|
|
111
114
|
} catch (err) {
|
|
112
|
-
setError(err instanceof Error ? err.message : "Unknown error");
|
|
115
|
+
setError(err instanceof Error ? err.message : t("cpq.priceRules.errors.unknown", "Unknown error"));
|
|
113
116
|
} finally {
|
|
114
117
|
setSaving(false);
|
|
115
118
|
}
|
|
116
|
-
}, [data, form]);
|
|
119
|
+
}, [data, form, t]);
|
|
117
120
|
const columns = React.useMemo(
|
|
118
121
|
() => [
|
|
119
122
|
{
|
|
@@ -134,7 +137,7 @@ function PriceRulesPage() {
|
|
|
134
137
|
{
|
|
135
138
|
accessorKey: "ruleType",
|
|
136
139
|
header: t("cpq.priceRules.table.type", "Type"),
|
|
137
|
-
cell: ({ row }) => /* @__PURE__ */ jsx("span", { children:
|
|
140
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { children: ruleTypeLabels[row.original.ruleType] ?? row.original.ruleType })
|
|
138
141
|
},
|
|
139
142
|
{
|
|
140
143
|
id: "value",
|
|
@@ -144,7 +147,7 @@ function PriceRulesPage() {
|
|
|
144
147
|
{
|
|
145
148
|
id: "scope",
|
|
146
149
|
header: t("cpq.priceRules.table.scope", "Scope"),
|
|
147
|
-
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs", children: row.original.productOfferingId ? row.original.productOfferingName ?? "Product-scoped" : "Global" })
|
|
150
|
+
cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-xs", children: row.original.productOfferingId ? row.original.productOfferingName ?? t("cpq.priceRules.scope.productScoped", "Product-scoped") : t("cpq.priceRules.scope.global", "Global") })
|
|
148
151
|
},
|
|
149
152
|
{
|
|
150
153
|
id: "filter",
|
|
@@ -166,10 +169,10 @@ function PriceRulesPage() {
|
|
|
166
169
|
{
|
|
167
170
|
accessorKey: "isActive",
|
|
168
171
|
header: t("cpq.priceRules.table.status", "Status"),
|
|
169
|
-
cell: ({ row }) => /* @__PURE__ */ jsx(Tag, { variant: row.original.isActive ? "success" : "neutral", dot: true, children: row.original.isActive ? "Active" : "Inactive" })
|
|
172
|
+
cell: ({ row }) => /* @__PURE__ */ jsx(Tag, { variant: row.original.isActive ? "success" : "neutral", dot: true, children: row.original.isActive ? t("cpq.common.active", "Active") : t("cpq.common.inactive", "Inactive") })
|
|
170
173
|
}
|
|
171
174
|
],
|
|
172
|
-
[t]
|
|
175
|
+
[ruleTypeLabels, t]
|
|
173
176
|
);
|
|
174
177
|
return /* @__PURE__ */ jsx(
|
|
175
178
|
CpqListView,
|
|
@@ -201,7 +204,7 @@ function PriceRulesPage() {
|
|
|
201
204
|
onCancel: () => setShowForm(false)
|
|
202
205
|
}
|
|
203
206
|
) : null,
|
|
204
|
-
emptyState: /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: 'No price rules found. Click "Create Rule" to add one.' })
|
|
207
|
+
emptyState: /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.priceRules.empty", 'No price rules found. Click "Create Rule" to add one.') })
|
|
205
208
|
}
|
|
206
209
|
);
|
|
207
210
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../src/modules/cpq/backend/cpq/price-rules/page.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n PriceRuleForm,\n
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n PriceRuleForm,\n buildRuleTypeLabels,\n emptyPriceRuleForm,\n priceRuleFormToPayload,\n type PriceRule,\n type PriceRuleFormData,\n type ProductOffering,\n} from './PriceRuleForm'\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.ruleType === 'string' && values.ruleType) {\n params.set('ruleType', values.ruleType)\n }\n if (typeof values.chargeTypeFilter === 'string' && values.chargeTypeFilter) {\n params.set('chargeTypeFilter', values.chargeTypeFilter)\n }\n if (values.globalOnly === true) params.set('globalOnly', 'true')\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nfunction formatValue(rule: PriceRule) {\n if (rule.ruleType.includes('percent')) return `${rule.value}%`\n if (rule.ruleType === 'price_override') return `= ${rule.value}`\n return `${rule.value}`\n}\n\nexport default function PriceRulesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PriceRule>({\n endpoint: '/api/cpq/price-rules',\n pageSize: PAGE_SIZE,\n initialSorting: [{ id: 'sortOrder', desc: false }],\n buildFilterParams,\n loadErrorMessage: t('cpq.priceRules.list.error.load', 'Failed to load price rules'),\n })\n\n const [showForm, setShowForm] = React.useState(false)\n const [form, setForm] = React.useState<PriceRuleFormData>(emptyPriceRuleForm)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [offerings, setOfferings] = React.useState<ProductOffering[]>([])\n\n React.useEffect(() => {\n fetch('/api/cpq/product-offerings?pageSize=100')\n .then((r) => r.json())\n .then((d) => setOfferings(d.items ?? []))\n .catch(() => {})\n }, [])\n\n const ruleTypeLabels = React.useMemo(() => buildRuleTypeLabels(t), [t])\n\n const ruleTypeOptions = React.useMemo(\n () => Object.entries(ruleTypeLabels).map(([value, label]) => ({ value, label })),\n [ruleTypeLabels],\n )\n\n const chargeTypeOptions = React.useMemo(\n () => [\n { value: 'nrc', label: t('cpq.priceRules.chargeTypes.nrc', 'NRC') },\n { value: 'mrc', label: t('cpq.priceRules.chargeTypes.mrc', 'MRC') },\n { value: 'usage', label: t('cpq.priceRules.chargeTypes.usage', 'Usage') },\n ],\n [t],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'ruleType',\n label: t('cpq.priceRules.filters.ruleType', 'Rule Type'),\n type: 'select',\n options: ruleTypeOptions,\n },\n {\n id: 'chargeTypeFilter',\n label: t('cpq.priceRules.filters.chargeType', 'Charge Type'),\n type: 'select',\n options: chargeTypeOptions,\n },\n {\n id: 'globalOnly',\n label: t('cpq.priceRules.filters.globalOnly', 'Global only'),\n type: 'checkbox',\n },\n {\n id: 'isActive',\n label: t('cpq.priceRules.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [chargeTypeOptions, ruleTypeOptions, t],\n )\n\n const openCreate = React.useCallback(() => {\n setForm(emptyPriceRuleForm)\n setError(null)\n setShowForm(true)\n }, [])\n\n const handleSave = React.useCallback(async () => {\n setSaving(true)\n setError(null)\n try {\n const payload = priceRuleFormToPayload(form)\n const res = await fetch('/api/cpq/price-rules', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const err = await res.json()\n throw new Error(\n err.details\n ? JSON.stringify(err.details)\n : err.error ?? t('cpq.priceRules.errors.createFailed', 'Failed to create'),\n )\n }\n setShowForm(false)\n data.reload()\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : t('cpq.priceRules.errors.unknown', 'Unknown error'))\n } finally {\n setSaving(false)\n }\n }, [data, form, t])\n\n const columns = React.useMemo<ColumnDef<PriceRule>[]>(\n () => [\n {\n accessorKey: 'sortOrder',\n header: t('cpq.priceRules.table.order', 'Order'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.sortOrder}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.priceRules.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.priceRules.table.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'ruleType',\n header: t('cpq.priceRules.table.type', 'Type'),\n cell: ({ row }) => (\n <span>{ruleTypeLabels[row.original.ruleType] ?? row.original.ruleType}</span>\n ),\n },\n {\n id: 'value',\n header: t('cpq.priceRules.table.value', 'Value'),\n cell: ({ row }) => <span className=\"font-mono\">{formatValue(row.original)}</span>,\n },\n {\n id: 'scope',\n header: t('cpq.priceRules.table.scope', 'Scope'),\n cell: ({ row }) => (\n <span className=\"text-xs\">\n {row.original.productOfferingId\n ? row.original.productOfferingName ??\n t('cpq.priceRules.scope.productScoped', 'Product-scoped')\n : t('cpq.priceRules.scope.global', 'Global')}\n </span>\n ),\n },\n {\n id: 'filter',\n header: t('cpq.priceRules.table.filter', 'Filter'),\n cell: ({ row }) => {\n const rule = row.original\n const parts: string[] = []\n if (rule.chargeTypeFilter) parts.push(rule.chargeTypeFilter)\n if (rule.chargeCodeFilter) parts.push(rule.chargeCodeFilter)\n const cond = rule.applicabilityCondition as\n | { attribute?: string; operator?: string; value?: string }\n | null\n if (cond?.attribute) {\n const op = cond.operator === 'neq' ? '\u2260' : '='\n parts.push(`${cond.attribute} ${op} ${cond.value ?? '\"\"'}`)\n }\n return (\n <span className=\"text-xs text-muted-foreground\">\n {parts.length > 0 ? parts.join(' / ') : '\u2014'}\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.priceRules.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive\n ? t('cpq.common.active', 'Active')\n : t('cpq.common.inactive', 'Inactive')}\n </Tag>\n ),\n },\n ],\n [ruleTypeLabels, t],\n )\n\n return (\n <CpqListView<PriceRule>\n title={t('cpq.priceRules.list.title', 'Price Rules')}\n tableId=\"cpq.price-rules.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.priceRules.search.placeholder', 'Search price rules...')}\n actions={\n <Button type=\"button\" onClick={openCreate}>\n {t('cpq.priceRules.create', 'Create Rule')}\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/price-rules',\n entityName: t('cpq.priceRules.entityName', 'price rule'),\n editHref: (row) => `/backend/cpq/price-rules/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/price-rules/${row.id}`)}\n toolbarContent={\n showForm ? (\n <PriceRuleForm\n editingId={null}\n form={form}\n onFormChange={setForm}\n offerings={offerings}\n saving={saving}\n error={error}\n onSave={handleSave}\n onCancel={() => setShowForm(false)}\n />\n ) : null\n }\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.priceRules.empty', 'No price rules found. Click \"Create Rule\" to add one.')}\n </div>\n }\n />\n )\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -12,11 +12,13 @@ import {
|
|
|
12
12
|
SelectValue
|
|
13
13
|
} from "@open-mercato/ui/primitives/select";
|
|
14
14
|
import { Trash2 } from "lucide-react";
|
|
15
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
15
16
|
function EntriesEditor({
|
|
16
17
|
table,
|
|
17
18
|
initialEntries,
|
|
18
19
|
onSaved
|
|
19
20
|
}) {
|
|
21
|
+
const t = useT();
|
|
20
22
|
const [drafts, setDrafts] = React.useState(
|
|
21
23
|
() => initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id }))
|
|
22
24
|
);
|
|
@@ -87,7 +89,7 @@ function EntriesEditor({
|
|
|
87
89
|
});
|
|
88
90
|
if (!res.ok) {
|
|
89
91
|
const body = await res.json().catch(() => ({}));
|
|
90
|
-
return { ok: false, error: body.error ?? "Failed to create entries" };
|
|
92
|
+
return { ok: false, error: body.error ?? t("cpq.pricing.entries.errors.createFailed", "Failed to create entries") };
|
|
91
93
|
}
|
|
92
94
|
}
|
|
93
95
|
const mods = drafts.filter((d) => d._state === "modified");
|
|
@@ -107,7 +109,7 @@ function EntriesEditor({
|
|
|
107
109
|
});
|
|
108
110
|
if (!res.ok) {
|
|
109
111
|
const body = await res.json().catch(() => ({}));
|
|
110
|
-
return { ok: false, error: body.error ??
|
|
112
|
+
return { ok: false, error: body.error ?? t("cpq.pricing.entries.errors.updateFailed", "Failed to update entry {id}", { id: d.id }) };
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
const dels = drafts.filter((d) => d._state === "deleted");
|
|
@@ -119,12 +121,12 @@ function EntriesEditor({
|
|
|
119
121
|
});
|
|
120
122
|
if (!res.ok) {
|
|
121
123
|
const body = await res.json().catch(() => ({}));
|
|
122
|
-
return { ok: false, error: body.error ??
|
|
124
|
+
return { ok: false, error: body.error ?? t("cpq.pricing.entries.errors.deleteFailed", "Failed to delete entry {id}", { id: d.id }) };
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
return { ok: true };
|
|
126
128
|
} catch (err) {
|
|
127
|
-
return { ok: false, error: err instanceof Error ? err.message : "Failed to save entries" };
|
|
129
|
+
return { ok: false, error: err instanceof Error ? err.message : t("cpq.pricing.entries.errors.saveFailed", "Failed to save entries") };
|
|
128
130
|
} finally {
|
|
129
131
|
setSaving(false);
|
|
130
132
|
}
|
|
@@ -140,30 +142,29 @@ function EntriesEditor({
|
|
|
140
142
|
};
|
|
141
143
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
142
144
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
143
|
-
/* @__PURE__ */
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
")"
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => addRow(false), children: "+ Add Row" }),
|
|
150
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => addRow(true), children: "+ Add Tier" }),
|
|
151
|
-
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: handleSave, disabled: saving || !dirty, children: saving ? "Saving..." : "Save" }),
|
|
152
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: cancelEdit, disabled: saving, children: "Cancel" })
|
|
145
|
+
/* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: t("cpq.pricing.entries.title", "Entries ({count})", { count: visibleDrafts.length }) }),
|
|
146
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: mode === "view" ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setMode("edit"), children: t("cpq.pricing.entries.edit", "Edit Entries") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
147
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => addRow(false), children: t("cpq.pricing.entries.addRow", "+ Add Row") }),
|
|
148
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: () => addRow(true), children: t("cpq.pricing.entries.addTier", "+ Add Tier") }),
|
|
149
|
+
/* @__PURE__ */ jsx(Button, { type: "button", size: "sm", onClick: handleSave, disabled: saving || !dirty, children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.common.save", "Save") }),
|
|
150
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", size: "sm", onClick: cancelEdit, disabled: saving, children: t("cpq.common.cancel", "Cancel") })
|
|
153
151
|
] }) })
|
|
154
152
|
] }),
|
|
155
153
|
error && /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: error }),
|
|
156
|
-
visibleDrafts.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: mode === "edit" ?
|
|
154
|
+
visibleDrafts.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: mode === "edit" ? t(
|
|
155
|
+
"cpq.pricing.entries.emptyEditing",
|
|
156
|
+
'No entries yet \u2014 use "+ Add Row" (dimensions lookup) or "+ Add Tier" (range lookup).'
|
|
157
|
+
) : t("cpq.pricing.entries.empty", "No entries in this pricing table.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border overflow-x-auto", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
|
|
157
158
|
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
|
|
158
159
|
table.dimensions.map((dim) => /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: dim.label }, dim.key)),
|
|
159
160
|
hasTieredEntries && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
160
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: "Tier #" }),
|
|
161
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: "Range From" }),
|
|
162
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: "Range To" })
|
|
161
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: t("cpq.pricing.entries.tierNumber", "Tier #") }),
|
|
162
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: t("cpq.pricing.entries.rangeFrom", "Range From") }),
|
|
163
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium whitespace-nowrap", children: t("cpq.pricing.entries.rangeTo", "Range To") })
|
|
163
164
|
] }),
|
|
164
165
|
table.priceColumns.map((col) => /* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-right font-medium whitespace-nowrap", children: col.label }, col.key)),
|
|
165
|
-
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: "Currency" }),
|
|
166
|
-
mode === "edit" && /* @__PURE__ */ jsx("th", { className: "px-3 py-3 w-8", "aria-label": "Actions" })
|
|
166
|
+
/* @__PURE__ */ jsx("th", { className: "px-3 py-3 text-left font-medium", children: t("cpq.pricing.entries.currency", "Currency") }),
|
|
167
|
+
mode === "edit" && /* @__PURE__ */ jsx("th", { className: "px-3 py-3 w-8", "aria-label": t("cpq.pricing.entries.actions", "Actions") })
|
|
167
168
|
] }) }),
|
|
168
169
|
/* @__PURE__ */ jsx("tbody", { children: visibleDrafts.map((entry) => /* @__PURE__ */ jsx(
|
|
169
170
|
EntryRow,
|
|
@@ -188,6 +189,7 @@ function EntryRow({
|
|
|
188
189
|
onChange,
|
|
189
190
|
onDelete
|
|
190
191
|
}) {
|
|
192
|
+
const t = useT();
|
|
191
193
|
const readOnly = mode === "view";
|
|
192
194
|
const rowBg = entry._state === "new" ? "bg-success/5" : entry._state === "modified" ? "bg-warning/5" : "";
|
|
193
195
|
return /* @__PURE__ */ jsxs("tr", { className: `border-b hover:bg-muted/30 ${rowBg}`, children: [
|
|
@@ -263,7 +265,7 @@ function EntryRow({
|
|
|
263
265
|
type: "button",
|
|
264
266
|
onClick: onDelete,
|
|
265
267
|
className: "text-destructive hover:bg-destructive/10 rounded p-1",
|
|
266
|
-
"aria-label": "Delete row",
|
|
268
|
+
"aria-label": t("cpq.pricing.entries.deleteRow", "Delete row"),
|
|
267
269
|
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
268
270
|
}
|
|
269
271
|
) })
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../../../src/modules/cpq/backend/cpq/pricing/%5Bid%5D/EntriesEditor.tsx"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Trash2 } from 'lucide-react'\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type Dimension = { key: string; label: string }\nexport type PriceColumn = { key: string; label: string }\n\nexport type PricingTableShape = {\n id: string\n dimensions: Dimension[]\n priceColumns: PriceColumn[]\n currencyCodeList: string[]\n}\n\nexport type Entry = {\n id: string\n dimensionValues: Record<string, string>\n tierNumber: number | null\n rangeFrom: string | null\n rangeTo: string | null\n currencyCode: string\n prices: Record<string, number>\n}\n\ntype DraftEntry = Entry & {\n // null = unchanged, 'new' = added locally, 'modified' = field edited,\n // 'deleted' = pending delete (hidden from main grid but kept for save diff).\n _state: null | 'new' | 'modified' | 'deleted'\n _localId: string\n}\n\ntype SaveResult = { ok: true } | { ok: false; error: string }\n\n// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function EntriesEditor({\n table,\n initialEntries,\n onSaved,\n}: {\n table: PricingTableShape\n initialEntries: Entry[]\n onSaved: (entries: Entry[]) => void\n}) {\n const [drafts, setDrafts] = React.useState<DraftEntry[]>(() =>\n initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })),\n )\n const [mode, setMode] = React.useState<'view' | 'edit'>('view')\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n // Sync external changes (e.g. parent reloads after config save).\n React.useEffect(() => {\n setDrafts(initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })))\n }, [initialEntries])\n\n const hasTieredEntries = drafts.some((d) => d.tierNumber != null && d._state !== 'deleted')\n const visibleDrafts = drafts.filter((d) => d._state !== 'deleted')\n const dirty = drafts.some((d) => d._state !== null)\n\n // \u2500\u2500\u2500 Mutation helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const newLocalId = React.useCallback(() => `new-${Math.random().toString(36).slice(2, 10)}`, [])\n\n const addRow = (asTiered: boolean) => {\n const seed: DraftEntry = {\n id: '',\n _localId: newLocalId(),\n _state: 'new',\n dimensionValues: Object.fromEntries(table.dimensions.map((d) => [d.key, ''])),\n tierNumber: asTiered ? (drafts.filter((d) => d.tierNumber != null).length + 1) : null,\n rangeFrom: asTiered ? '0' : null,\n rangeTo: asTiered ? null : null,\n currencyCode: table.currencyCodeList[0] ?? 'USD',\n prices: Object.fromEntries(table.priceColumns.map((c) => [c.key, 0])),\n }\n setDrafts((prev) => [...prev, seed])\n }\n\n const updateRow = (localId: string, patch: Partial<DraftEntry>) => {\n setDrafts((prev) =>\n prev.map((d) =>\n d._localId === localId\n ? { ...d, ...patch, _state: d._state === 'new' ? 'new' : 'modified' }\n : d,\n ),\n )\n }\n\n const deleteRow = (localId: string) => {\n setDrafts((prev) =>\n prev.flatMap((d) => {\n if (d._localId !== localId) return [d]\n // New (not yet persisted) \u2192 drop entirely. Persisted \u2192 mark for delete.\n if (d._state === 'new') return []\n return [{ ...d, _state: 'deleted' }]\n }),\n )\n }\n\n const cancelEdit = () => {\n // Revert to initial \u2014 drop any local mutations.\n setDrafts(initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })))\n setError(null)\n setMode('view')\n }\n\n // \u2500\u2500\u2500 Save (diff against initialEntries) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const save = async (): Promise<SaveResult> => {\n setSaving(true)\n setError(null)\n try {\n // POST new entries (batch \u2014 route accepts arrays)\n const news = drafts.filter((d) => d._state === 'new')\n if (news.length > 0) {\n const payload = news.map((d) => ({\n pricingTableId: table.id,\n dimensionValues: d.dimensionValues,\n tierNumber: d.tierNumber ?? undefined,\n rangeFrom: d.rangeFrom != null ? Number(d.rangeFrom) : undefined,\n rangeTo: d.rangeTo != null ? Number(d.rangeTo) : undefined,\n currencyCode: d.currencyCode,\n prices: d.prices,\n }))\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? 'Failed to create entries' }\n }\n }\n\n // PUT modified entries (one-by-one, route doesn't batch updates)\n const mods = drafts.filter((d) => d._state === 'modified')\n for (const d of mods) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: d.id,\n dimensionValues: d.dimensionValues,\n tierNumber: d.tierNumber ?? null,\n rangeFrom: d.rangeFrom != null ? Number(d.rangeFrom) : null,\n rangeTo: d.rangeTo != null ? Number(d.rangeTo) : null,\n currencyCode: d.currencyCode,\n prices: d.prices,\n }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? `Failed to update entry ${d.id}` }\n }\n }\n\n // DELETE removed entries\n const dels = drafts.filter((d) => d._state === 'deleted')\n for (const d of dels) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: d.id }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? `Failed to delete entry ${d.id}` }\n }\n }\n\n return { ok: true }\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : 'Failed to save entries' }\n } finally {\n setSaving(false)\n }\n }\n\n const handleSave = async () => {\n const r = await save()\n if (!r.ok) {\n setError(r.error)\n return\n }\n // Reload from server via parent (which re-fetches the pricing table\n // and pushes a fresh `initialEntries` down \u2014 useEffect resets drafts).\n onSaved([])\n setMode('view')\n }\n\n // \u2500\u2500\u2500 Render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">\n Entries ({visibleDrafts.length})\n </h2>\n <div className=\"flex items-center gap-2\">\n {mode === 'view' ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setMode('edit')}>\n Edit Entries\n </Button>\n ) : (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => addRow(false)}>\n + Add Row\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => addRow(true)}>\n + Add Tier\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleSave} disabled={saving || !dirty}>\n {saving ? 'Saving...' : 'Save'}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={cancelEdit} disabled={saving}>\n Cancel\n </Button>\n </>\n )}\n </div>\n </div>\n\n {error && <Alert variant=\"destructive\">{error}</Alert>}\n\n {visibleDrafts.length === 0 ? (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {mode === 'edit'\n ? 'No entries yet \u2014 use \"+ Add Row\" (dimensions lookup) or \"+ Add Tier\" (range lookup).'\n : 'No entries in this pricing table.'}\n </div>\n ) : (\n <div className=\"rounded-lg border overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n {table.dimensions.map((dim) => (\n <th key={dim.key} className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">\n {dim.label}\n </th>\n ))}\n {/* Tier / range columns sit between the dimension lookups and the\n prices they qualify, so the row reads \"what \u2192 how much\". */}\n {hasTieredEntries && (\n <>\n <th className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">Tier #</th>\n <th className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">Range From</th>\n <th className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">Range To</th>\n </>\n )}\n {table.priceColumns.map((col) => (\n <th key={col.key} className=\"px-3 py-3 text-right font-medium whitespace-nowrap\">\n {col.label}\n </th>\n ))}\n <th className=\"px-3 py-3 text-left font-medium\">Currency</th>\n {mode === 'edit' && <th className=\"px-3 py-3 w-8\" aria-label=\"Actions\" />}\n </tr>\n </thead>\n <tbody>\n {visibleDrafts.map((entry) => (\n <EntryRow\n key={entry._localId}\n entry={entry}\n table={table}\n hasTieredEntries={hasTieredEntries}\n mode={mode}\n onChange={(patch) => updateRow(entry._localId, patch)}\n onDelete={() => deleteRow(entry._localId)}\n />\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Row sub-component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction EntryRow({\n entry,\n table,\n hasTieredEntries,\n mode,\n onChange,\n onDelete,\n}: {\n entry: DraftEntry\n table: PricingTableShape\n hasTieredEntries: boolean\n mode: 'view' | 'edit'\n onChange: (patch: Partial<DraftEntry>) => void\n onDelete: () => void\n}) {\n const readOnly = mode === 'view'\n const rowBg = entry._state === 'new'\n ? 'bg-success/5'\n : entry._state === 'modified'\n ? 'bg-warning/5'\n : ''\n\n return (\n <tr className={`border-b hover:bg-muted/30 ${rowBg}`}>\n {table.dimensions.map((dim) => (\n <td key={dim.key} className=\"px-3 py-2\">\n {readOnly ? (\n entry.dimensionValues?.[dim.key] ?? '\u2014'\n ) : (\n <Input\n value={entry.dimensionValues?.[dim.key] ?? ''}\n onChange={(e) =>\n onChange({\n dimensionValues: { ...entry.dimensionValues, [dim.key]: e.target.value },\n })\n }\n className=\"w-28 h-8\"\n />\n )}\n </td>\n ))}\n {hasTieredEntries && (\n <>\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.tierNumber ?? '\u2014'\n ) : (\n <Input\n type=\"number\"\n value={entry.tierNumber ?? ''}\n onChange={(e) => onChange({ tierNumber: e.target.value === '' ? null : Number(e.target.value) })}\n className=\"w-20 h-8\"\n />\n )}\n </td>\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.rangeFrom ?? '\u2014'\n ) : (\n <Input\n type=\"number\"\n value={entry.rangeFrom ?? ''}\n onChange={(e) => onChange({ rangeFrom: e.target.value === '' ? null : e.target.value })}\n className=\"w-24 h-8\"\n />\n )}\n </td>\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.rangeTo ?? '\u2014'\n ) : (\n <Input\n type=\"number\"\n value={entry.rangeTo ?? ''}\n onChange={(e) => onChange({ rangeTo: e.target.value === '' ? null : e.target.value })}\n placeholder=\"\u221E\"\n className=\"w-24 h-8\"\n />\n )}\n </td>\n </>\n )}\n {table.priceColumns.map((col) => (\n <td key={col.key} className=\"px-3 py-2 text-right font-mono\">\n {readOnly ? (\n entry.prices?.[col.key] != null ? Number(entry.prices[col.key]).toFixed(2) : '\u2014'\n ) : (\n <Input\n type=\"number\"\n step=\"0.01\"\n value={entry.prices?.[col.key] ?? ''}\n onChange={(e) =>\n onChange({\n prices: {\n ...entry.prices,\n [col.key]: e.target.value === '' ? 0 : Number(e.target.value),\n },\n })\n }\n className=\"w-24 h-8 text-right\"\n />\n )}\n </td>\n ))}\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.currencyCode\n ) : (\n <Select\n value={entry.currencyCode}\n onValueChange={(value) => onChange({ currencyCode: value })}\n >\n <SelectTrigger className=\"w-24 h-8\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {table.currencyCodeList.map((c) => (\n <SelectItem key={c} value={c}>{c}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </td>\n {mode === 'edit' && (\n <td className=\"px-3 py-2\">\n <button\n type=\"button\"\n onClick={onDelete}\n className=\"text-destructive hover:bg-destructive/10 rounded p-1\"\n aria-label=\"Delete row\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </td>\n )}\n </tr>\n )\n}\n\n"],
|
|
5
|
-
"mappings": ";
|
|
4
|
+
"sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { Trash2 } from 'lucide-react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport type Dimension = { key: string; label: string }\nexport type PriceColumn = { key: string; label: string }\n\nexport type PricingTableShape = {\n id: string\n dimensions: Dimension[]\n priceColumns: PriceColumn[]\n currencyCodeList: string[]\n}\n\nexport type Entry = {\n id: string\n dimensionValues: Record<string, string>\n tierNumber: number | null\n rangeFrom: string | null\n rangeTo: string | null\n currencyCode: string\n prices: Record<string, number>\n}\n\ntype DraftEntry = Entry & {\n // null = unchanged, 'new' = added locally, 'modified' = field edited,\n // 'deleted' = pending delete (hidden from main grid but kept for save diff).\n _state: null | 'new' | 'modified' | 'deleted'\n _localId: string\n}\n\ntype SaveResult = { ok: true } | { ok: false; error: string }\n\n// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function EntriesEditor({\n table,\n initialEntries,\n onSaved,\n}: {\n table: PricingTableShape\n initialEntries: Entry[]\n onSaved: (entries: Entry[]) => void\n}) {\n const t = useT()\n const [drafts, setDrafts] = React.useState<DraftEntry[]>(() =>\n initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })),\n )\n const [mode, setMode] = React.useState<'view' | 'edit'>('view')\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n // Sync external changes (e.g. parent reloads after config save).\n React.useEffect(() => {\n setDrafts(initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })))\n }, [initialEntries])\n\n const hasTieredEntries = drafts.some((d) => d.tierNumber != null && d._state !== 'deleted')\n const visibleDrafts = drafts.filter((d) => d._state !== 'deleted')\n const dirty = drafts.some((d) => d._state !== null)\n\n // \u2500\u2500\u2500 Mutation helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const newLocalId = React.useCallback(() => `new-${Math.random().toString(36).slice(2, 10)}`, [])\n\n const addRow = (asTiered: boolean) => {\n const seed: DraftEntry = {\n id: '',\n _localId: newLocalId(),\n _state: 'new',\n dimensionValues: Object.fromEntries(table.dimensions.map((d) => [d.key, ''])),\n tierNumber: asTiered ? (drafts.filter((d) => d.tierNumber != null).length + 1) : null,\n rangeFrom: asTiered ? '0' : null,\n rangeTo: asTiered ? null : null,\n currencyCode: table.currencyCodeList[0] ?? 'USD',\n prices: Object.fromEntries(table.priceColumns.map((c) => [c.key, 0])),\n }\n setDrafts((prev) => [...prev, seed])\n }\n\n const updateRow = (localId: string, patch: Partial<DraftEntry>) => {\n setDrafts((prev) =>\n prev.map((d) =>\n d._localId === localId\n ? { ...d, ...patch, _state: d._state === 'new' ? 'new' : 'modified' }\n : d,\n ),\n )\n }\n\n const deleteRow = (localId: string) => {\n setDrafts((prev) =>\n prev.flatMap((d) => {\n if (d._localId !== localId) return [d]\n // New (not yet persisted) \u2192 drop entirely. Persisted \u2192 mark for delete.\n if (d._state === 'new') return []\n return [{ ...d, _state: 'deleted' }]\n }),\n )\n }\n\n const cancelEdit = () => {\n // Revert to initial \u2014 drop any local mutations.\n setDrafts(initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })))\n setError(null)\n setMode('view')\n }\n\n // \u2500\u2500\u2500 Save (diff against initialEntries) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const save = async (): Promise<SaveResult> => {\n setSaving(true)\n setError(null)\n try {\n // POST new entries (batch \u2014 route accepts arrays)\n const news = drafts.filter((d) => d._state === 'new')\n if (news.length > 0) {\n const payload = news.map((d) => ({\n pricingTableId: table.id,\n dimensionValues: d.dimensionValues,\n tierNumber: d.tierNumber ?? undefined,\n rangeFrom: d.rangeFrom != null ? Number(d.rangeFrom) : undefined,\n rangeTo: d.rangeTo != null ? Number(d.rangeTo) : undefined,\n currencyCode: d.currencyCode,\n prices: d.prices,\n }))\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.createFailed', 'Failed to create entries') }\n }\n }\n\n // PUT modified entries (one-by-one, route doesn't batch updates)\n const mods = drafts.filter((d) => d._state === 'modified')\n for (const d of mods) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n id: d.id,\n dimensionValues: d.dimensionValues,\n tierNumber: d.tierNumber ?? null,\n rangeFrom: d.rangeFrom != null ? Number(d.rangeFrom) : null,\n rangeTo: d.rangeTo != null ? Number(d.rangeTo) : null,\n currencyCode: d.currencyCode,\n prices: d.prices,\n }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.updateFailed', 'Failed to update entry {id}', { id: d.id }) }\n }\n }\n\n // DELETE removed entries\n const dels = drafts.filter((d) => d._state === 'deleted')\n for (const d of dels) {\n const res = await fetch('/api/cpq/pricing-table-entries', {\n method: 'DELETE',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ id: d.id }),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.deleteFailed', 'Failed to delete entry {id}', { id: d.id }) }\n }\n }\n\n return { ok: true }\n } catch (err) {\n return { ok: false, error: err instanceof Error ? err.message : t('cpq.pricing.entries.errors.saveFailed', 'Failed to save entries') }\n } finally {\n setSaving(false)\n }\n }\n\n const handleSave = async () => {\n const r = await save()\n if (!r.ok) {\n setError(r.error)\n return\n }\n // Reload from server via parent (which re-fetches the pricing table\n // and pushes a fresh `initialEntries` down \u2014 useEffect resets drafts).\n onSaved([])\n setMode('view')\n }\n\n // \u2500\u2500\u2500 Render \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">\n {t('cpq.pricing.entries.title', 'Entries ({count})', { count: visibleDrafts.length })}\n </h2>\n <div className=\"flex items-center gap-2\">\n {mode === 'view' ? (\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => setMode('edit')}>\n {t('cpq.pricing.entries.edit', 'Edit Entries')}\n </Button>\n ) : (\n <>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => addRow(false)}>\n {t('cpq.pricing.entries.addRow', '+ Add Row')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={() => addRow(true)}>\n {t('cpq.pricing.entries.addTier', '+ Add Tier')}\n </Button>\n <Button type=\"button\" size=\"sm\" onClick={handleSave} disabled={saving || !dirty}>\n {saving ? t('cpq.common.saving', 'Saving...') : t('cpq.common.save', 'Save')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={cancelEdit} disabled={saving}>\n {t('cpq.common.cancel', 'Cancel')}\n </Button>\n </>\n )}\n </div>\n </div>\n\n {error && <Alert variant=\"destructive\">{error}</Alert>}\n\n {visibleDrafts.length === 0 ? (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {mode === 'edit'\n ? t(\n 'cpq.pricing.entries.emptyEditing',\n 'No entries yet \u2014 use \"+ Add Row\" (dimensions lookup) or \"+ Add Tier\" (range lookup).',\n )\n : t('cpq.pricing.entries.empty', 'No entries in this pricing table.')}\n </div>\n ) : (\n <div className=\"rounded-lg border overflow-x-auto\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/50\">\n {table.dimensions.map((dim) => (\n <th key={dim.key} className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">\n {dim.label}\n </th>\n ))}\n {/* Tier / range columns sit between the dimension lookups and the\n prices they qualify, so the row reads \"what \u2192 how much\". */}\n {hasTieredEntries && (\n <>\n <th className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">{t('cpq.pricing.entries.tierNumber', 'Tier #')}</th>\n <th className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">{t('cpq.pricing.entries.rangeFrom', 'Range From')}</th>\n <th className=\"px-3 py-3 text-left font-medium whitespace-nowrap\">{t('cpq.pricing.entries.rangeTo', 'Range To')}</th>\n </>\n )}\n {table.priceColumns.map((col) => (\n <th key={col.key} className=\"px-3 py-3 text-right font-medium whitespace-nowrap\">\n {col.label}\n </th>\n ))}\n <th className=\"px-3 py-3 text-left font-medium\">{t('cpq.pricing.entries.currency', 'Currency')}</th>\n {mode === 'edit' && <th className=\"px-3 py-3 w-8\" aria-label={t('cpq.pricing.entries.actions', 'Actions')} />}\n </tr>\n </thead>\n <tbody>\n {visibleDrafts.map((entry) => (\n <EntryRow\n key={entry._localId}\n entry={entry}\n table={table}\n hasTieredEntries={hasTieredEntries}\n mode={mode}\n onChange={(patch) => updateRow(entry._localId, patch)}\n onDelete={() => deleteRow(entry._localId)}\n />\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Row sub-component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction EntryRow({\n entry,\n table,\n hasTieredEntries,\n mode,\n onChange,\n onDelete,\n}: {\n entry: DraftEntry\n table: PricingTableShape\n hasTieredEntries: boolean\n mode: 'view' | 'edit'\n onChange: (patch: Partial<DraftEntry>) => void\n onDelete: () => void\n}) {\n const t = useT()\n const readOnly = mode === 'view'\n const rowBg = entry._state === 'new'\n ? 'bg-success/5'\n : entry._state === 'modified'\n ? 'bg-warning/5'\n : ''\n\n return (\n <tr className={`border-b hover:bg-muted/30 ${rowBg}`}>\n {table.dimensions.map((dim) => (\n <td key={dim.key} className=\"px-3 py-2\">\n {readOnly ? (\n entry.dimensionValues?.[dim.key] ?? '\u2014'\n ) : (\n <Input\n value={entry.dimensionValues?.[dim.key] ?? ''}\n onChange={(e) =>\n onChange({\n dimensionValues: { ...entry.dimensionValues, [dim.key]: e.target.value },\n })\n }\n className=\"w-28 h-8\"\n />\n )}\n </td>\n ))}\n {hasTieredEntries && (\n <>\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.tierNumber ?? '\u2014'\n ) : (\n <Input\n type=\"number\"\n value={entry.tierNumber ?? ''}\n onChange={(e) => onChange({ tierNumber: e.target.value === '' ? null : Number(e.target.value) })}\n className=\"w-20 h-8\"\n />\n )}\n </td>\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.rangeFrom ?? '\u2014'\n ) : (\n <Input\n type=\"number\"\n value={entry.rangeFrom ?? ''}\n onChange={(e) => onChange({ rangeFrom: e.target.value === '' ? null : e.target.value })}\n className=\"w-24 h-8\"\n />\n )}\n </td>\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.rangeTo ?? '\u2014'\n ) : (\n <Input\n type=\"number\"\n value={entry.rangeTo ?? ''}\n onChange={(e) => onChange({ rangeTo: e.target.value === '' ? null : e.target.value })}\n placeholder=\"\u221E\"\n className=\"w-24 h-8\"\n />\n )}\n </td>\n </>\n )}\n {table.priceColumns.map((col) => (\n <td key={col.key} className=\"px-3 py-2 text-right font-mono\">\n {readOnly ? (\n entry.prices?.[col.key] != null ? Number(entry.prices[col.key]).toFixed(2) : '\u2014'\n ) : (\n <Input\n type=\"number\"\n step=\"0.01\"\n value={entry.prices?.[col.key] ?? ''}\n onChange={(e) =>\n onChange({\n prices: {\n ...entry.prices,\n [col.key]: e.target.value === '' ? 0 : Number(e.target.value),\n },\n })\n }\n className=\"w-24 h-8 text-right\"\n />\n )}\n </td>\n ))}\n <td className=\"px-3 py-2\">\n {readOnly ? (\n entry.currencyCode\n ) : (\n <Select\n value={entry.currencyCode}\n onValueChange={(value) => onChange({ currencyCode: value })}\n >\n <SelectTrigger className=\"w-24 h-8\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {table.currencyCodeList.map((c) => (\n <SelectItem key={c} value={c}>{c}</SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </td>\n {mode === 'edit' && (\n <td className=\"px-3 py-2\">\n <button\n type=\"button\"\n onClick={onDelete}\n className=\"text-destructive hover:bg-destructive/10 rounded p-1\"\n aria-label={t('cpq.pricing.entries.deleteRow', 'Delete row')}\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </td>\n )}\n </tr>\n )\n}\n\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -3,94 +3,66 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { Alert } from "@open-mercato/ui/primitives/alert";
|
|
4
4
|
import { Button } from "@open-mercato/ui/primitives/button";
|
|
5
5
|
import { Tag } from "@open-mercato/ui/primitives/tag";
|
|
6
|
+
import { useT } from "@open-mercato/shared/lib/i18n/context";
|
|
6
7
|
function ImportPreview({
|
|
7
8
|
diff,
|
|
8
9
|
saving,
|
|
9
10
|
onConfirm,
|
|
10
11
|
onCancel
|
|
11
12
|
}) {
|
|
13
|
+
const t = useT();
|
|
12
14
|
const total = diff.inserts.length + diff.updates.length + diff.deletes.length;
|
|
13
15
|
return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-5 space-y-4", children: [
|
|
14
16
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
15
|
-
/* @__PURE__ */ jsx("h3", { className: "font-semibold", children: "Import preview" }),
|
|
17
|
+
/* @__PURE__ */ jsx("h3", { className: "font-semibold", children: t("cpq.pricing.import.preview.title", "Import preview") }),
|
|
16
18
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 items-center text-sm", children: [
|
|
17
|
-
/* @__PURE__ */
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/* @__PURE__ */ jsxs(Tag, { variant: "warning", dot: true, children: [
|
|
22
|
-
diff.updates.length,
|
|
23
|
-
" updated"
|
|
24
|
-
] }),
|
|
25
|
-
/* @__PURE__ */ jsxs(Tag, { variant: "error", dot: true, children: [
|
|
26
|
-
diff.deletes.length,
|
|
27
|
-
" deleted"
|
|
28
|
-
] }),
|
|
29
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
|
|
30
|
-
"(",
|
|
31
|
-
diff.unchanged.length,
|
|
32
|
-
" unchanged)"
|
|
33
|
-
] })
|
|
19
|
+
/* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: t("cpq.pricing.import.preview.newCount", "{count} new", { count: diff.inserts.length }) }),
|
|
20
|
+
/* @__PURE__ */ jsx(Tag, { variant: "warning", dot: true, children: t("cpq.pricing.import.preview.updatedCount", "{count} updated", { count: diff.updates.length }) }),
|
|
21
|
+
/* @__PURE__ */ jsx(Tag, { variant: "error", dot: true, children: t("cpq.pricing.import.preview.deletedCount", "{count} deleted", { count: diff.deletes.length }) }),
|
|
22
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: t("cpq.pricing.import.preview.unchangedCount", "({count} unchanged)", { count: diff.unchanged.length }) })
|
|
34
23
|
] })
|
|
35
24
|
] }),
|
|
36
25
|
diff.warnings.length > 0 && /* @__PURE__ */ jsxs(Alert, { variant: "default", children: [
|
|
37
|
-
/* @__PURE__ */ jsx("div", { className: "font-medium mb-1", children: "Warnings" }),
|
|
26
|
+
/* @__PURE__ */ jsx("div", { className: "font-medium mb-1", children: t("cpq.pricing.import.preview.warnings", "Warnings") }),
|
|
38
27
|
/* @__PURE__ */ jsx("ul", { className: "list-disc pl-5 text-xs space-y-0.5", children: diff.warnings.map((w, i) => /* @__PURE__ */ jsx("li", { children: w }, i)) })
|
|
39
28
|
] }),
|
|
40
|
-
total === 0 ? /* @__PURE__ */ jsx(Alert, { children: "The file contains no changes \u2014 every row matches an existing entry." }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs", children: [
|
|
29
|
+
total === 0 ? /* @__PURE__ */ jsx(Alert, { children: t("cpq.pricing.import.preview.noChanges", "The file contains no changes \u2014 every row matches an existing entry.") }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2 text-xs", children: [
|
|
41
30
|
diff.inserts.length > 0 && /* @__PURE__ */ jsxs("details", { className: "rounded border p-2", children: [
|
|
42
|
-
/* @__PURE__ */
|
|
43
|
-
|
|
44
|
-
" new entries will be created"
|
|
45
|
-
] }),
|
|
46
|
-
/* @__PURE__ */ jsx("ul", { className: "mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto", children: diff.inserts.map((r) => /* @__PURE__ */ jsxs("li", { children: [
|
|
47
|
-
"Row ",
|
|
48
|
-
r.rowNumber,
|
|
49
|
-
": ",
|
|
50
|
-
summarizeRow(r)
|
|
51
|
-
] }, r.rowNumber)) })
|
|
31
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium text-success", children: t("cpq.pricing.import.preview.insertsSummary", "{count} new entries will be created", { count: diff.inserts.length }) }),
|
|
32
|
+
/* @__PURE__ */ jsx("ul", { className: "mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto", children: diff.inserts.map((r) => /* @__PURE__ */ jsx("li", { children: t("cpq.pricing.import.preview.insertRow", "Row {row}: {summary}", { row: r.rowNumber, summary: summarizeRow(r, t) }) }, r.rowNumber)) })
|
|
52
33
|
] }),
|
|
53
34
|
diff.updates.length > 0 && /* @__PURE__ */ jsxs("details", { className: "rounded border p-2", children: [
|
|
54
|
-
/* @__PURE__ */
|
|
55
|
-
diff.updates.length,
|
|
56
|
-
" entries will be updated"
|
|
57
|
-
] }),
|
|
35
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium text-warning", children: t("cpq.pricing.import.preview.updatesSummary", "{count} entries will be updated", { count: diff.updates.length }) }),
|
|
58
36
|
/* @__PURE__ */ jsx("ul", { className: "mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto", children: diff.updates.map((u) => /* @__PURE__ */ jsxs("li", { children: [
|
|
59
|
-
"Row ",
|
|
60
|
-
u.after.rowNumber,
|
|
61
|
-
" (id ",
|
|
37
|
+
t("cpq.pricing.import.preview.updateRowPrefix", "Row {row} (id ", { row: u.after.rowNumber }),
|
|
62
38
|
/* @__PURE__ */ jsxs("code", { className: "font-mono", children: [
|
|
63
39
|
u.before.id.slice(0, 8),
|
|
64
40
|
"\u2026"
|
|
65
41
|
] }),
|
|
66
|
-
"): ",
|
|
67
|
-
summarizeRow(u.after)
|
|
42
|
+
t("cpq.pricing.import.preview.updateRowSuffix", "): {summary}", { summary: summarizeRow(u.after, t) })
|
|
68
43
|
] }, u.before.id)) })
|
|
69
44
|
] }),
|
|
70
45
|
diff.deletes.length > 0 && /* @__PURE__ */ jsxs("details", { className: "rounded border p-2", children: [
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
-
diff.deletes.length,
|
|
73
|
-
" entries will be deleted"
|
|
74
|
-
] }),
|
|
46
|
+
/* @__PURE__ */ jsx("summary", { className: "cursor-pointer font-medium text-destructive", children: t("cpq.pricing.import.preview.deletesSummary", "{count} entries will be deleted", { count: diff.deletes.length }) }),
|
|
75
47
|
/* @__PURE__ */ jsx("ul", { className: "mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto", children: diff.deletes.map((e) => /* @__PURE__ */ jsxs("li", { children: [
|
|
76
48
|
/* @__PURE__ */ jsxs("code", { className: "font-mono", children: [
|
|
77
49
|
e.id.slice(0, 8),
|
|
78
50
|
"\u2026"
|
|
79
51
|
] }),
|
|
80
52
|
": ",
|
|
81
|
-
summarizeEntry(e)
|
|
53
|
+
summarizeEntry(e, t)
|
|
82
54
|
] }, e.id)) })
|
|
83
55
|
] })
|
|
84
56
|
] }),
|
|
85
57
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-2 border-t", children: [
|
|
86
|
-
/* @__PURE__ */ jsx(Button, { type: "button", onClick: onConfirm, disabled: saving || total === 0, children: saving ? "Applying..." :
|
|
87
|
-
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: saving, children: "Cancel" })
|
|
58
|
+
/* @__PURE__ */ jsx(Button, { type: "button", onClick: onConfirm, disabled: saving || total === 0, children: saving ? t("cpq.pricing.import.preview.applying", "Applying...") : t("cpq.pricing.import.preview.apply", "Apply changes ({count})", { count: total }) }),
|
|
59
|
+
/* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: onCancel, disabled: saving, children: t("cpq.common.cancel", "Cancel") })
|
|
88
60
|
] })
|
|
89
61
|
] });
|
|
90
62
|
}
|
|
91
|
-
function summarizeRow(r) {
|
|
63
|
+
function summarizeRow(r, t) {
|
|
92
64
|
const bits = [];
|
|
93
|
-
if (r.tierNumber != null) bits.push(
|
|
65
|
+
if (r.tierNumber != null) bits.push(t("cpq.pricing.import.preview.tier", "tier {tier}", { tier: r.tierNumber }));
|
|
94
66
|
if (r.rangeFrom != null || r.rangeTo != null) bits.push(`[${r.rangeFrom ?? "0"}..${r.rangeTo ?? "\u221E"}]`);
|
|
95
67
|
for (const [k, v] of Object.entries(r.dimensionValues)) if (v) bits.push(`${k}=${v}`);
|
|
96
68
|
bits.push(r.currencyCode);
|
|
@@ -98,9 +70,9 @@ function summarizeRow(r) {
|
|
|
98
70
|
if (priceBits.length > 0) bits.push(`{${priceBits.join(", ")}}`);
|
|
99
71
|
return bits.join(" \xB7 ");
|
|
100
72
|
}
|
|
101
|
-
function summarizeEntry(e) {
|
|
73
|
+
function summarizeEntry(e, t) {
|
|
102
74
|
const bits = [];
|
|
103
|
-
if (e.tierNumber != null) bits.push(
|
|
75
|
+
if (e.tierNumber != null) bits.push(t("cpq.pricing.import.preview.tier", "tier {tier}", { tier: e.tierNumber }));
|
|
104
76
|
if (e.rangeFrom != null || e.rangeTo != null) bits.push(`[${e.rangeFrom ?? "0"}..${e.rangeTo ?? "\u221E"}]`);
|
|
105
77
|
for (const [k, v] of Object.entries(e.dimensionValues)) if (v) bits.push(`${k}=${v}`);
|
|
106
78
|
bits.push(e.currencyCode);
|