@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
|
@@ -9,7 +9,7 @@ import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar
|
|
|
9
9
|
import { CpqListView, useCpqListData } from '../../../components/CpqListView'
|
|
10
10
|
import {
|
|
11
11
|
PriceRuleForm,
|
|
12
|
-
|
|
12
|
+
buildRuleTypeLabels,
|
|
13
13
|
emptyPriceRuleForm,
|
|
14
14
|
priceRuleFormToPayload,
|
|
15
15
|
type PriceRule,
|
|
@@ -62,18 +62,20 @@ export default function PriceRulesPage() {
|
|
|
62
62
|
.catch(() => {})
|
|
63
63
|
}, [])
|
|
64
64
|
|
|
65
|
+
const ruleTypeLabels = React.useMemo(() => buildRuleTypeLabels(t), [t])
|
|
66
|
+
|
|
65
67
|
const ruleTypeOptions = React.useMemo(
|
|
66
|
-
() => Object.entries(
|
|
67
|
-
[],
|
|
68
|
+
() => Object.entries(ruleTypeLabels).map(([value, label]) => ({ value, label })),
|
|
69
|
+
[ruleTypeLabels],
|
|
68
70
|
)
|
|
69
71
|
|
|
70
72
|
const chargeTypeOptions = React.useMemo(
|
|
71
73
|
() => [
|
|
72
|
-
{ value: 'nrc', label: 'NRC' },
|
|
73
|
-
{ value: 'mrc', label: 'MRC' },
|
|
74
|
-
{ value: 'usage', label: 'Usage' },
|
|
74
|
+
{ value: 'nrc', label: t('cpq.priceRules.chargeTypes.nrc', 'NRC') },
|
|
75
|
+
{ value: 'mrc', label: t('cpq.priceRules.chargeTypes.mrc', 'MRC') },
|
|
76
|
+
{ value: 'usage', label: t('cpq.priceRules.chargeTypes.usage', 'Usage') },
|
|
75
77
|
],
|
|
76
|
-
[],
|
|
78
|
+
[t],
|
|
77
79
|
)
|
|
78
80
|
|
|
79
81
|
const filters = React.useMemo<FilterDef[]>(
|
|
@@ -122,16 +124,20 @@ export default function PriceRulesPage() {
|
|
|
122
124
|
})
|
|
123
125
|
if (!res.ok) {
|
|
124
126
|
const err = await res.json()
|
|
125
|
-
throw new Error(
|
|
127
|
+
throw new Error(
|
|
128
|
+
err.details
|
|
129
|
+
? JSON.stringify(err.details)
|
|
130
|
+
: err.error ?? t('cpq.priceRules.errors.createFailed', 'Failed to create'),
|
|
131
|
+
)
|
|
126
132
|
}
|
|
127
133
|
setShowForm(false)
|
|
128
134
|
data.reload()
|
|
129
135
|
} catch (err: unknown) {
|
|
130
|
-
setError(err instanceof Error ? err.message : 'Unknown error')
|
|
136
|
+
setError(err instanceof Error ? err.message : t('cpq.priceRules.errors.unknown', 'Unknown error'))
|
|
131
137
|
} finally {
|
|
132
138
|
setSaving(false)
|
|
133
139
|
}
|
|
134
|
-
}, [data, form])
|
|
140
|
+
}, [data, form, t])
|
|
135
141
|
|
|
136
142
|
const columns = React.useMemo<ColumnDef<PriceRule>[]>(
|
|
137
143
|
() => [
|
|
@@ -154,7 +160,7 @@ export default function PriceRulesPage() {
|
|
|
154
160
|
accessorKey: 'ruleType',
|
|
155
161
|
header: t('cpq.priceRules.table.type', 'Type'),
|
|
156
162
|
cell: ({ row }) => (
|
|
157
|
-
<span>{
|
|
163
|
+
<span>{ruleTypeLabels[row.original.ruleType] ?? row.original.ruleType}</span>
|
|
158
164
|
),
|
|
159
165
|
},
|
|
160
166
|
{
|
|
@@ -168,8 +174,9 @@ export default function PriceRulesPage() {
|
|
|
168
174
|
cell: ({ row }) => (
|
|
169
175
|
<span className="text-xs">
|
|
170
176
|
{row.original.productOfferingId
|
|
171
|
-
? row.original.productOfferingName ??
|
|
172
|
-
|
|
177
|
+
? row.original.productOfferingName ??
|
|
178
|
+
t('cpq.priceRules.scope.productScoped', 'Product-scoped')
|
|
179
|
+
: t('cpq.priceRules.scope.global', 'Global')}
|
|
173
180
|
</span>
|
|
174
181
|
),
|
|
175
182
|
},
|
|
@@ -201,12 +208,14 @@ export default function PriceRulesPage() {
|
|
|
201
208
|
header: t('cpq.priceRules.table.status', 'Status'),
|
|
202
209
|
cell: ({ row }) => (
|
|
203
210
|
<Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>
|
|
204
|
-
{row.original.isActive
|
|
211
|
+
{row.original.isActive
|
|
212
|
+
? t('cpq.common.active', 'Active')
|
|
213
|
+
: t('cpq.common.inactive', 'Inactive')}
|
|
205
214
|
</Tag>
|
|
206
215
|
),
|
|
207
216
|
},
|
|
208
217
|
],
|
|
209
|
-
[t],
|
|
218
|
+
[ruleTypeLabels, t],
|
|
210
219
|
)
|
|
211
220
|
|
|
212
221
|
return (
|
|
@@ -245,7 +254,7 @@ export default function PriceRulesPage() {
|
|
|
245
254
|
}
|
|
246
255
|
emptyState={
|
|
247
256
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
248
|
-
No price rules found. Click
|
|
257
|
+
{t('cpq.priceRules.empty', 'No price rules found. Click "Create Rule" to add one.')}
|
|
249
258
|
</div>
|
|
250
259
|
}
|
|
251
260
|
/>
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
SelectValue,
|
|
12
12
|
} from '@open-mercato/ui/primitives/select'
|
|
13
13
|
import { Trash2 } from 'lucide-react'
|
|
14
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
14
15
|
|
|
15
16
|
// ─── Types ──────────────────────────────────────────────────────
|
|
16
17
|
|
|
@@ -54,6 +55,7 @@ export function EntriesEditor({
|
|
|
54
55
|
initialEntries: Entry[]
|
|
55
56
|
onSaved: (entries: Entry[]) => void
|
|
56
57
|
}) {
|
|
58
|
+
const t = useT()
|
|
57
59
|
const [drafts, setDrafts] = React.useState<DraftEntry[]>(() =>
|
|
58
60
|
initialEntries.map((e) => ({ ...e, _state: null, _localId: e.id })),
|
|
59
61
|
)
|
|
@@ -142,7 +144,7 @@ export function EntriesEditor({
|
|
|
142
144
|
})
|
|
143
145
|
if (!res.ok) {
|
|
144
146
|
const body = await res.json().catch(() => ({}))
|
|
145
|
-
return { ok: false, error: body.error ?? 'Failed to create entries' }
|
|
147
|
+
return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.createFailed', 'Failed to create entries') }
|
|
146
148
|
}
|
|
147
149
|
}
|
|
148
150
|
|
|
@@ -164,7 +166,7 @@ export function EntriesEditor({
|
|
|
164
166
|
})
|
|
165
167
|
if (!res.ok) {
|
|
166
168
|
const body = await res.json().catch(() => ({}))
|
|
167
|
-
return { ok: false, error: body.error ??
|
|
169
|
+
return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.updateFailed', 'Failed to update entry {id}', { id: d.id }) }
|
|
168
170
|
}
|
|
169
171
|
}
|
|
170
172
|
|
|
@@ -178,13 +180,13 @@ export function EntriesEditor({
|
|
|
178
180
|
})
|
|
179
181
|
if (!res.ok) {
|
|
180
182
|
const body = await res.json().catch(() => ({}))
|
|
181
|
-
return { ok: false, error: body.error ??
|
|
183
|
+
return { ok: false, error: body.error ?? t('cpq.pricing.entries.errors.deleteFailed', 'Failed to delete entry {id}', { id: d.id }) }
|
|
182
184
|
}
|
|
183
185
|
}
|
|
184
186
|
|
|
185
187
|
return { ok: true }
|
|
186
188
|
} catch (err) {
|
|
187
|
-
return { ok: false, error: err instanceof Error ? err.message : 'Failed to save entries' }
|
|
189
|
+
return { ok: false, error: err instanceof Error ? err.message : t('cpq.pricing.entries.errors.saveFailed', 'Failed to save entries') }
|
|
188
190
|
} finally {
|
|
189
191
|
setSaving(false)
|
|
190
192
|
}
|
|
@@ -208,26 +210,26 @@ export function EntriesEditor({
|
|
|
208
210
|
<div className="space-y-3">
|
|
209
211
|
<div className="flex items-center justify-between">
|
|
210
212
|
<h2 className="text-lg font-semibold">
|
|
211
|
-
Entries ({visibleDrafts.length})
|
|
213
|
+
{t('cpq.pricing.entries.title', 'Entries ({count})', { count: visibleDrafts.length })}
|
|
212
214
|
</h2>
|
|
213
215
|
<div className="flex items-center gap-2">
|
|
214
216
|
{mode === 'view' ? (
|
|
215
217
|
<Button type="button" variant="outline" size="sm" onClick={() => setMode('edit')}>
|
|
216
|
-
Edit Entries
|
|
218
|
+
{t('cpq.pricing.entries.edit', 'Edit Entries')}
|
|
217
219
|
</Button>
|
|
218
220
|
) : (
|
|
219
221
|
<>
|
|
220
222
|
<Button type="button" variant="outline" size="sm" onClick={() => addRow(false)}>
|
|
221
|
-
+ Add Row
|
|
223
|
+
{t('cpq.pricing.entries.addRow', '+ Add Row')}
|
|
222
224
|
</Button>
|
|
223
225
|
<Button type="button" variant="outline" size="sm" onClick={() => addRow(true)}>
|
|
224
|
-
+ Add Tier
|
|
226
|
+
{t('cpq.pricing.entries.addTier', '+ Add Tier')}
|
|
225
227
|
</Button>
|
|
226
228
|
<Button type="button" size="sm" onClick={handleSave} disabled={saving || !dirty}>
|
|
227
|
-
{saving ? 'Saving...' : 'Save'}
|
|
229
|
+
{saving ? t('cpq.common.saving', 'Saving...') : t('cpq.common.save', 'Save')}
|
|
228
230
|
</Button>
|
|
229
231
|
<Button type="button" variant="outline" size="sm" onClick={cancelEdit} disabled={saving}>
|
|
230
|
-
Cancel
|
|
232
|
+
{t('cpq.common.cancel', 'Cancel')}
|
|
231
233
|
</Button>
|
|
232
234
|
</>
|
|
233
235
|
)}
|
|
@@ -239,8 +241,11 @@ export function EntriesEditor({
|
|
|
239
241
|
{visibleDrafts.length === 0 ? (
|
|
240
242
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
241
243
|
{mode === 'edit'
|
|
242
|
-
?
|
|
243
|
-
|
|
244
|
+
? t(
|
|
245
|
+
'cpq.pricing.entries.emptyEditing',
|
|
246
|
+
'No entries yet — use "+ Add Row" (dimensions lookup) or "+ Add Tier" (range lookup).',
|
|
247
|
+
)
|
|
248
|
+
: t('cpq.pricing.entries.empty', 'No entries in this pricing table.')}
|
|
244
249
|
</div>
|
|
245
250
|
) : (
|
|
246
251
|
<div className="rounded-lg border overflow-x-auto">
|
|
@@ -256,9 +261,9 @@ export function EntriesEditor({
|
|
|
256
261
|
prices they qualify, so the row reads "what → how much". */}
|
|
257
262
|
{hasTieredEntries && (
|
|
258
263
|
<>
|
|
259
|
-
<th className="px-3 py-3 text-left font-medium whitespace-nowrap">Tier
|
|
260
|
-
<th className="px-3 py-3 text-left font-medium whitespace-nowrap">Range From</th>
|
|
261
|
-
<th className="px-3 py-3 text-left font-medium whitespace-nowrap">Range To</th>
|
|
264
|
+
<th className="px-3 py-3 text-left font-medium whitespace-nowrap">{t('cpq.pricing.entries.tierNumber', 'Tier #')}</th>
|
|
265
|
+
<th className="px-3 py-3 text-left font-medium whitespace-nowrap">{t('cpq.pricing.entries.rangeFrom', 'Range From')}</th>
|
|
266
|
+
<th className="px-3 py-3 text-left font-medium whitespace-nowrap">{t('cpq.pricing.entries.rangeTo', 'Range To')}</th>
|
|
262
267
|
</>
|
|
263
268
|
)}
|
|
264
269
|
{table.priceColumns.map((col) => (
|
|
@@ -266,8 +271,8 @@ export function EntriesEditor({
|
|
|
266
271
|
{col.label}
|
|
267
272
|
</th>
|
|
268
273
|
))}
|
|
269
|
-
<th className="px-3 py-3 text-left font-medium">Currency</th>
|
|
270
|
-
{mode === 'edit' && <th className="px-3 py-3 w-8" aria-label=
|
|
274
|
+
<th className="px-3 py-3 text-left font-medium">{t('cpq.pricing.entries.currency', 'Currency')}</th>
|
|
275
|
+
{mode === 'edit' && <th className="px-3 py-3 w-8" aria-label={t('cpq.pricing.entries.actions', 'Actions')} />}
|
|
271
276
|
</tr>
|
|
272
277
|
</thead>
|
|
273
278
|
<tbody>
|
|
@@ -307,6 +312,7 @@ function EntryRow({
|
|
|
307
312
|
onChange: (patch: Partial<DraftEntry>) => void
|
|
308
313
|
onDelete: () => void
|
|
309
314
|
}) {
|
|
315
|
+
const t = useT()
|
|
310
316
|
const readOnly = mode === 'view'
|
|
311
317
|
const rowBg = entry._state === 'new'
|
|
312
318
|
? 'bg-success/5'
|
|
@@ -421,7 +427,7 @@ function EntryRow({
|
|
|
421
427
|
type="button"
|
|
422
428
|
onClick={onDelete}
|
|
423
429
|
className="text-destructive hover:bg-destructive/10 rounded p-1"
|
|
424
|
-
aria-label=
|
|
430
|
+
aria-label={t('cpq.pricing.entries.deleteRow', 'Delete row')}
|
|
425
431
|
>
|
|
426
432
|
<Trash2 className="h-4 w-4" />
|
|
427
433
|
</button>
|
|
@@ -3,6 +3,7 @@ import * as React from 'react'
|
|
|
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, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
6
7
|
import type { ImportDiff } from './xlsxImport'
|
|
7
8
|
|
|
8
9
|
export function ImportPreview({
|
|
@@ -16,22 +17,23 @@ export function ImportPreview({
|
|
|
16
17
|
onConfirm: () => void
|
|
17
18
|
onCancel: () => void
|
|
18
19
|
}) {
|
|
20
|
+
const t = useT()
|
|
19
21
|
const total = diff.inserts.length + diff.updates.length + diff.deletes.length
|
|
20
22
|
return (
|
|
21
23
|
<div className="rounded-lg border bg-card p-5 space-y-4">
|
|
22
24
|
<div className="flex items-center justify-between">
|
|
23
|
-
<h3 className="font-semibold">Import preview</h3>
|
|
25
|
+
<h3 className="font-semibold">{t('cpq.pricing.import.preview.title', 'Import preview')}</h3>
|
|
24
26
|
<div className="flex gap-2 items-center text-sm">
|
|
25
|
-
<Tag variant="success" dot>{diff.inserts.length}
|
|
26
|
-
<Tag variant="warning" dot>{diff.updates.length}
|
|
27
|
-
<Tag variant="error" dot>{diff.deletes.length}
|
|
28
|
-
<span className="text-muted-foreground">({diff.unchanged.length}
|
|
27
|
+
<Tag variant="success" dot>{t('cpq.pricing.import.preview.newCount', '{count} new', { count: diff.inserts.length })}</Tag>
|
|
28
|
+
<Tag variant="warning" dot>{t('cpq.pricing.import.preview.updatedCount', '{count} updated', { count: diff.updates.length })}</Tag>
|
|
29
|
+
<Tag variant="error" dot>{t('cpq.pricing.import.preview.deletedCount', '{count} deleted', { count: diff.deletes.length })}</Tag>
|
|
30
|
+
<span className="text-muted-foreground">{t('cpq.pricing.import.preview.unchangedCount', '({count} unchanged)', { count: diff.unchanged.length })}</span>
|
|
29
31
|
</div>
|
|
30
32
|
</div>
|
|
31
33
|
|
|
32
34
|
{diff.warnings.length > 0 && (
|
|
33
35
|
<Alert variant="default">
|
|
34
|
-
<div className="font-medium mb-1">Warnings</div>
|
|
36
|
+
<div className="font-medium mb-1">{t('cpq.pricing.import.preview.warnings', 'Warnings')}</div>
|
|
35
37
|
<ul className="list-disc pl-5 text-xs space-y-0.5">
|
|
36
38
|
{diff.warnings.map((w, i) => <li key={i}>{w}</li>)}
|
|
37
39
|
</ul>
|
|
@@ -40,19 +42,19 @@ export function ImportPreview({
|
|
|
40
42
|
|
|
41
43
|
{total === 0 ? (
|
|
42
44
|
<Alert>
|
|
43
|
-
The file contains no changes — every row matches an existing entry.
|
|
45
|
+
{t('cpq.pricing.import.preview.noChanges', 'The file contains no changes — every row matches an existing entry.')}
|
|
44
46
|
</Alert>
|
|
45
47
|
) : (
|
|
46
48
|
<div className="space-y-2 text-xs">
|
|
47
49
|
{diff.inserts.length > 0 && (
|
|
48
50
|
<details className="rounded border p-2">
|
|
49
51
|
<summary className="cursor-pointer font-medium text-success">
|
|
50
|
-
{
|
|
52
|
+
{t('cpq.pricing.import.preview.insertsSummary', '{count} new entries will be created', { count: diff.inserts.length })}
|
|
51
53
|
</summary>
|
|
52
54
|
<ul className="mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto">
|
|
53
55
|
{diff.inserts.map((r) => (
|
|
54
56
|
<li key={r.rowNumber}>
|
|
55
|
-
Row {r.rowNumber
|
|
57
|
+
{t('cpq.pricing.import.preview.insertRow', 'Row {row}: {summary}', { row: r.rowNumber, summary: summarizeRow(r, t) })}
|
|
56
58
|
</li>
|
|
57
59
|
))}
|
|
58
60
|
</ul>
|
|
@@ -61,12 +63,15 @@ export function ImportPreview({
|
|
|
61
63
|
{diff.updates.length > 0 && (
|
|
62
64
|
<details className="rounded border p-2">
|
|
63
65
|
<summary className="cursor-pointer font-medium text-warning">
|
|
64
|
-
{
|
|
66
|
+
{t('cpq.pricing.import.preview.updatesSummary', '{count} entries will be updated', { count: diff.updates.length })}
|
|
65
67
|
</summary>
|
|
66
68
|
<ul className="mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto">
|
|
67
69
|
{diff.updates.map((u) => (
|
|
68
70
|
<li key={u.before.id}>
|
|
69
|
-
|
|
71
|
+
{/* The truncated id renders as <code> between prefix and suffix. */}
|
|
72
|
+
{t('cpq.pricing.import.preview.updateRowPrefix', 'Row {row} (id ', { row: u.after.rowNumber })}
|
|
73
|
+
<code className="font-mono">{u.before.id.slice(0, 8)}…</code>
|
|
74
|
+
{t('cpq.pricing.import.preview.updateRowSuffix', '): {summary}', { summary: summarizeRow(u.after, t) })}
|
|
70
75
|
</li>
|
|
71
76
|
))}
|
|
72
77
|
</ul>
|
|
@@ -75,12 +80,12 @@ export function ImportPreview({
|
|
|
75
80
|
{diff.deletes.length > 0 && (
|
|
76
81
|
<details className="rounded border p-2">
|
|
77
82
|
<summary className="cursor-pointer font-medium text-destructive">
|
|
78
|
-
{
|
|
83
|
+
{t('cpq.pricing.import.preview.deletesSummary', '{count} entries will be deleted', { count: diff.deletes.length })}
|
|
79
84
|
</summary>
|
|
80
85
|
<ul className="mt-2 pl-5 list-disc space-y-0.5 max-h-40 overflow-y-auto">
|
|
81
86
|
{diff.deletes.map((e) => (
|
|
82
87
|
<li key={e.id}>
|
|
83
|
-
<code className="font-mono">{e.id.slice(0, 8)}…</code>: {summarizeEntry(e)}
|
|
88
|
+
<code className="font-mono">{e.id.slice(0, 8)}…</code>: {summarizeEntry(e, t)}
|
|
84
89
|
</li>
|
|
85
90
|
))}
|
|
86
91
|
</ul>
|
|
@@ -91,19 +96,24 @@ export function ImportPreview({
|
|
|
91
96
|
|
|
92
97
|
<div className="flex gap-2 pt-2 border-t">
|
|
93
98
|
<Button type="button" onClick={onConfirm} disabled={saving || total === 0}>
|
|
94
|
-
{saving
|
|
99
|
+
{saving
|
|
100
|
+
? t('cpq.pricing.import.preview.applying', 'Applying...')
|
|
101
|
+
: t('cpq.pricing.import.preview.apply', 'Apply changes ({count})', { count: total })}
|
|
95
102
|
</Button>
|
|
96
103
|
<Button type="button" variant="outline" onClick={onCancel} disabled={saving}>
|
|
97
|
-
Cancel
|
|
104
|
+
{t('cpq.common.cancel', 'Cancel')}
|
|
98
105
|
</Button>
|
|
99
106
|
</div>
|
|
100
107
|
</div>
|
|
101
108
|
)
|
|
102
109
|
}
|
|
103
110
|
|
|
104
|
-
function summarizeRow(
|
|
111
|
+
function summarizeRow(
|
|
112
|
+
r: ImportDiff['inserts'][number] | ImportDiff['updates'][number]['after'],
|
|
113
|
+
t: TranslateFn,
|
|
114
|
+
): string {
|
|
105
115
|
const bits: string[] = []
|
|
106
|
-
if (r.tierNumber != null) bits.push(
|
|
116
|
+
if (r.tierNumber != null) bits.push(t('cpq.pricing.import.preview.tier', 'tier {tier}', { tier: r.tierNumber }))
|
|
107
117
|
if (r.rangeFrom != null || r.rangeTo != null) bits.push(`[${r.rangeFrom ?? '0'}..${r.rangeTo ?? '∞'}]`)
|
|
108
118
|
for (const [k, v] of Object.entries(r.dimensionValues)) if (v) bits.push(`${k}=${v}`)
|
|
109
119
|
bits.push(r.currencyCode)
|
|
@@ -112,9 +122,9 @@ function summarizeRow(r: ImportDiff['inserts'][number] | ImportDiff['updates'][n
|
|
|
112
122
|
return bits.join(' · ')
|
|
113
123
|
}
|
|
114
124
|
|
|
115
|
-
function summarizeEntry(e: ImportDiff['deletes'][number]): string {
|
|
125
|
+
function summarizeEntry(e: ImportDiff['deletes'][number], t: TranslateFn): string {
|
|
116
126
|
const bits: string[] = []
|
|
117
|
-
if (e.tierNumber != null) bits.push(
|
|
127
|
+
if (e.tierNumber != null) bits.push(t('cpq.pricing.import.preview.tier', 'tier {tier}', { tier: e.tierNumber }))
|
|
118
128
|
if (e.rangeFrom != null || e.rangeTo != null) bits.push(`[${e.rangeFrom ?? '0'}..${e.rangeTo ?? '∞'}]`)
|
|
119
129
|
for (const [k, v] of Object.entries(e.dimensionValues)) if (v) bits.push(`${k}=${v}`)
|
|
120
130
|
bits.push(e.currencyCode)
|