@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
|
@@ -114,12 +114,12 @@ export default function SpecificationsListPage() {
|
|
|
114
114
|
{row.original.name}
|
|
115
115
|
{row.original.specType === 'bundle' && (
|
|
116
116
|
<Tag variant="brand" className="ml-2 px-2 text-[10px]">
|
|
117
|
-
bundle
|
|
117
|
+
{t('cpq.specifications.list.tag.bundle', 'bundle')}
|
|
118
118
|
</Tag>
|
|
119
119
|
)}
|
|
120
120
|
{row.original.isAssetizable && (
|
|
121
121
|
<Tag variant="warning" className="ml-1 px-2 text-[10px]">
|
|
122
|
-
asset
|
|
122
|
+
{t('cpq.specifications.list.tag.asset', 'asset')}
|
|
123
123
|
</Tag>
|
|
124
124
|
)}
|
|
125
125
|
</span>
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { useRouter } from 'next/navigation'
|
|
4
4
|
import { Alert } from '@open-mercato/ui/primitives/alert'
|
|
5
|
+
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
5
6
|
import { RowActions } from '@open-mercato/ui/backend/RowActions'
|
|
6
7
|
import { Tag } from '@open-mercato/ui/primitives/tag'
|
|
7
8
|
import type {
|
|
@@ -11,9 +12,9 @@ import type {
|
|
|
11
12
|
WizardApplicability,
|
|
12
13
|
} from '../../../../../workflows/types'
|
|
13
14
|
|
|
14
|
-
async function apiJson<T>(url: string): Promise<T> {
|
|
15
|
+
async function apiJson<T>(url: string, t: TranslateFn): Promise<T> {
|
|
15
16
|
const res = await fetch(url)
|
|
16
|
-
if (!res.ok) throw new Error(
|
|
17
|
+
if (!res.ok) throw new Error(t('cpq.wizards.runner.errors.api', 'API error {status}', { status: res.status }))
|
|
17
18
|
return res.json() as Promise<T>
|
|
18
19
|
}
|
|
19
20
|
|
|
@@ -43,7 +44,8 @@ function KV({ label, children }: { label: string; children: React.ReactNode }) {
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
function JsonBlock({ data }: { data: unknown }) {
|
|
46
|
-
|
|
47
|
+
const t = useT()
|
|
48
|
+
if (data == null) return <span className="text-xs text-muted-foreground">{t('cpq.wizards.detail.none', 'none')}</span>
|
|
47
49
|
return (
|
|
48
50
|
<pre className="rounded-md bg-muted/50 p-3 text-xs overflow-x-auto max-h-48">
|
|
49
51
|
{JSON.stringify(data, null, 2)}
|
|
@@ -51,20 +53,24 @@ function JsonBlock({ data }: { data: unknown }) {
|
|
|
51
53
|
)
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
function TransitionRow({
|
|
56
|
+
function TransitionRow({ transition, index }: { transition: WizardTransition; index: number }) {
|
|
57
|
+
const t = useT()
|
|
55
58
|
return (
|
|
56
59
|
<div className="flex items-start gap-3 text-xs">
|
|
57
60
|
<span className="font-mono text-muted-foreground w-4 shrink-0">{index + 1}.</span>
|
|
58
61
|
<div className="space-y-0.5">
|
|
59
62
|
<div className="flex items-center gap-2">
|
|
60
|
-
<span className="font-medium"
|
|
61
|
-
{
|
|
62
|
-
{
|
|
63
|
+
<span className="font-medium">{'->'} {transition.targetStepId}</span>
|
|
64
|
+
{transition.isDefault && <Badge variant="muted">{t('cpq.wizards.detail.transitionDefault', 'default')}</Badge>}
|
|
65
|
+
{transition.label && <span className="text-muted-foreground">({transition.label})</span>}
|
|
63
66
|
</div>
|
|
64
|
-
{
|
|
67
|
+
{transition.condition && (
|
|
65
68
|
<div className="font-mono text-muted-foreground">
|
|
66
|
-
|
|
67
|
-
|
|
69
|
+
{t('cpq.wizards.detail.transitionWhen', 'when {field} {operator}', {
|
|
70
|
+
field: transition.condition.field,
|
|
71
|
+
operator: transition.condition.operator,
|
|
72
|
+
})}
|
|
73
|
+
{transition.condition.value !== undefined ? ` ${JSON.stringify(transition.condition.value)}` : ''}
|
|
68
74
|
</div>
|
|
69
75
|
)}
|
|
70
76
|
</div>
|
|
@@ -73,6 +79,7 @@ function TransitionRow({ t, index }: { t: WizardTransition; index: number }) {
|
|
|
73
79
|
}
|
|
74
80
|
|
|
75
81
|
function StepCard({ step, index }: { step: WizardStepDefinition; index: number }) {
|
|
82
|
+
const t = useT()
|
|
76
83
|
const [open, setOpen] = React.useState(false)
|
|
77
84
|
const configKeys = Object.keys(step.config)
|
|
78
85
|
|
|
@@ -90,7 +97,7 @@ function StepCard({ step, index }: { step: WizardStepDefinition; index: number }
|
|
|
90
97
|
<div className="flex items-center gap-2">
|
|
91
98
|
<span className="text-sm font-medium truncate">{step.title}</span>
|
|
92
99
|
<Badge>{step.type}</Badge>
|
|
93
|
-
{step.isOptional && <Badge variant="muted">optional</Badge>}
|
|
100
|
+
{step.isOptional && <Badge variant="muted">{t('cpq.wizards.detail.optional', 'optional')}</Badge>}
|
|
94
101
|
</div>
|
|
95
102
|
{step.description && (
|
|
96
103
|
<p className="text-xs text-muted-foreground truncate mt-0.5">{step.description}</p>
|
|
@@ -102,13 +109,13 @@ function StepCard({ step, index }: { step: WizardStepDefinition; index: number }
|
|
|
102
109
|
{open && (
|
|
103
110
|
<div className="border-t px-4 py-3 space-y-4">
|
|
104
111
|
<div className="grid grid-cols-2 gap-4">
|
|
105
|
-
<KV label=
|
|
106
|
-
<KV label=
|
|
112
|
+
<KV label={t('cpq.wizards.detail.stepId', 'Step ID')}><span className="font-mono text-xs">{step.stepId}</span></KV>
|
|
113
|
+
<KV label={t('cpq.wizards.detail.type', 'Type')}><span className="font-mono text-xs">{step.type}</span></KV>
|
|
107
114
|
</div>
|
|
108
115
|
|
|
109
116
|
{configKeys.length > 0 && (
|
|
110
117
|
<div className="space-y-1">
|
|
111
|
-
<span className="text-xs font-medium text-muted-foreground">Config</span>
|
|
118
|
+
<span className="text-xs font-medium text-muted-foreground">{t('cpq.wizards.detail.config', 'Config')}</span>
|
|
112
119
|
<JsonBlock data={step.config} />
|
|
113
120
|
</div>
|
|
114
121
|
)}
|
|
@@ -116,23 +123,23 @@ function StepCard({ step, index }: { step: WizardStepDefinition; index: number }
|
|
|
116
123
|
{step.transitions.length > 0 && (
|
|
117
124
|
<div className="space-y-2">
|
|
118
125
|
<span className="text-xs font-medium text-muted-foreground">
|
|
119
|
-
Transitions ({step.transitions.length})
|
|
126
|
+
{t('cpq.wizards.detail.transitions', 'Transitions ({count})', { count: step.transitions.length })}
|
|
120
127
|
</span>
|
|
121
128
|
<div className="space-y-1.5">
|
|
122
|
-
{step.transitions.map((
|
|
123
|
-
<TransitionRow key={`${
|
|
129
|
+
{step.transitions.map((transition, i) => (
|
|
130
|
+
<TransitionRow key={`${transition.targetStepId}-${i}`} transition={transition} index={i} />
|
|
124
131
|
))}
|
|
125
132
|
</div>
|
|
126
133
|
</div>
|
|
127
134
|
)}
|
|
128
135
|
|
|
129
136
|
{step.transitions.length === 0 && (
|
|
130
|
-
<div className="text-xs text-muted-foreground">No transitions (terminal step)</div>
|
|
137
|
+
<div className="text-xs text-muted-foreground">{t('cpq.wizards.detail.noTransitions', 'No transitions (terminal step)')}</div>
|
|
131
138
|
)}
|
|
132
139
|
|
|
133
140
|
{step.validationRules && step.validationRules.length > 0 && (
|
|
134
141
|
<div className="space-y-1">
|
|
135
|
-
<span className="text-xs font-medium text-muted-foreground">Validation Rules</span>
|
|
142
|
+
<span className="text-xs font-medium text-muted-foreground">{t('cpq.wizards.detail.validationRules', 'Validation Rules')}</span>
|
|
136
143
|
<JsonBlock data={step.validationRules} />
|
|
137
144
|
</div>
|
|
138
145
|
)}
|
|
@@ -143,16 +150,17 @@ function StepCard({ step, index }: { step: WizardStepDefinition; index: number }
|
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
function ApplicabilityPanel({ applicability }: { applicability: WizardApplicability | null }) {
|
|
153
|
+
const t = useT()
|
|
146
154
|
if (!applicability) {
|
|
147
|
-
return <span className="text-sm text-muted-foreground">No applicability constraints</span>
|
|
155
|
+
return <span className="text-sm text-muted-foreground">{t('cpq.wizards.detail.noApplicability', 'No applicability constraints')}</span>
|
|
148
156
|
}
|
|
149
157
|
return (
|
|
150
158
|
<div className="grid grid-cols-2 gap-4">
|
|
151
159
|
{applicability.priority != null && (
|
|
152
|
-
<KV label=
|
|
160
|
+
<KV label={t('cpq.wizards.detail.priority', 'Priority')}>{applicability.priority}</KV>
|
|
153
161
|
)}
|
|
154
162
|
{applicability.specCodes?.length ? (
|
|
155
|
-
<KV label=
|
|
163
|
+
<KV label={t('cpq.wizards.detail.specCodes', 'Spec Codes')}>
|
|
156
164
|
<div className="flex flex-wrap gap-1">
|
|
157
165
|
{applicability.specCodes.map((c) => (
|
|
158
166
|
<span key={c} className="font-mono text-xs rounded bg-muted px-1.5 py-0.5">{c}</span>
|
|
@@ -161,7 +169,7 @@ function ApplicabilityPanel({ applicability }: { applicability: WizardApplicabil
|
|
|
161
169
|
</KV>
|
|
162
170
|
) : null}
|
|
163
171
|
{applicability.offeringCodes?.length ? (
|
|
164
|
-
<KV label=
|
|
172
|
+
<KV label={t('cpq.wizards.detail.offeringCodes', 'Offering Codes')}>
|
|
165
173
|
<div className="flex flex-wrap gap-1">
|
|
166
174
|
{applicability.offeringCodes.map((c) => (
|
|
167
175
|
<span key={c} className="font-mono text-xs rounded bg-muted px-1.5 py-0.5">{c}</span>
|
|
@@ -170,7 +178,7 @@ function ApplicabilityPanel({ applicability }: { applicability: WizardApplicabil
|
|
|
170
178
|
</KV>
|
|
171
179
|
) : null}
|
|
172
180
|
{applicability.customerSegments?.length ? (
|
|
173
|
-
<KV label=
|
|
181
|
+
<KV label={t('cpq.wizards.detail.customerSegments', 'Customer Segments')}>
|
|
174
182
|
<div className="flex flex-wrap gap-1">
|
|
175
183
|
{applicability.customerSegments.map((c) => (
|
|
176
184
|
<span key={c} className="font-mono text-xs rounded bg-muted px-1.5 py-0.5">{c}</span>
|
|
@@ -185,6 +193,7 @@ function ApplicabilityPanel({ applicability }: { applicability: WizardApplicabil
|
|
|
185
193
|
// ─── Main Page ───────────────────────────────────────────────────
|
|
186
194
|
|
|
187
195
|
export default function CpqWizardDetailPage({ params }: { params: Record<string, string | string[]> }) {
|
|
196
|
+
const t = useT()
|
|
188
197
|
const router = useRouter()
|
|
189
198
|
const definitionCode = params.definitionCode as string
|
|
190
199
|
|
|
@@ -200,19 +209,20 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
200
209
|
try {
|
|
201
210
|
const def = await apiJson<WizardDefinitionResult>(
|
|
202
211
|
`/api/cpq/wizards?code=${encodeURIComponent(definitionCode)}`,
|
|
212
|
+
t,
|
|
203
213
|
)
|
|
204
214
|
setDefinition(def)
|
|
205
215
|
} catch (err) {
|
|
206
|
-
setError(err instanceof Error ? err.message : 'Failed to load wizard')
|
|
216
|
+
setError(err instanceof Error ? err.message : t('cpq.wizards.runner.errors.load', 'Failed to load wizard'))
|
|
207
217
|
} finally {
|
|
208
218
|
setLoading(false)
|
|
209
219
|
}
|
|
210
220
|
}
|
|
211
221
|
load()
|
|
212
|
-
}, [definitionCode])
|
|
222
|
+
}, [definitionCode, t])
|
|
213
223
|
|
|
214
224
|
if (loading) {
|
|
215
|
-
return <div className="text-sm text-muted-foreground">Loading
|
|
225
|
+
return <div className="text-sm text-muted-foreground">{t('cpq.common.loading', 'Loading...')}</div>
|
|
216
226
|
}
|
|
217
227
|
|
|
218
228
|
if (error) {
|
|
@@ -220,7 +230,7 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
220
230
|
<div className="space-y-4">
|
|
221
231
|
<Alert variant="destructive">{error}</Alert>
|
|
222
232
|
<button type="button" onClick={() => router.push('/backend/cpq/wizards')} className="text-sm text-muted-foreground hover:text-foreground">
|
|
223
|
-
|
|
233
|
+
← {t('cpq.wizards.runner.backToList', 'Back to Wizards')}
|
|
224
234
|
</button>
|
|
225
235
|
</div>
|
|
226
236
|
)
|
|
@@ -229,7 +239,7 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
229
239
|
if (!definition) {
|
|
230
240
|
return (
|
|
231
241
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
232
|
-
Wizard not found.
|
|
242
|
+
{t('cpq.wizards.runner.notFound', 'Wizard not found.')}
|
|
233
243
|
</div>
|
|
234
244
|
)
|
|
235
245
|
}
|
|
@@ -243,7 +253,7 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
243
253
|
onClick={() => router.push('/backend/cpq/wizards')}
|
|
244
254
|
className="text-sm text-muted-foreground hover:text-foreground"
|
|
245
255
|
>
|
|
246
|
-
|
|
256
|
+
← {t('cpq.wizards.runner.allWizards', 'All Wizards')}
|
|
247
257
|
</button>
|
|
248
258
|
</div>
|
|
249
259
|
|
|
@@ -261,7 +271,7 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
261
271
|
items={[
|
|
262
272
|
{
|
|
263
273
|
id: 'preview',
|
|
264
|
-
label: 'Preview',
|
|
274
|
+
label: t('cpq.wizards.actions.preview', 'Preview'),
|
|
265
275
|
onSelect: () => router.push(`/backend/cpq/wizards/${definitionCode}`),
|
|
266
276
|
},
|
|
267
277
|
]}
|
|
@@ -270,15 +280,15 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
270
280
|
</div>
|
|
271
281
|
|
|
272
282
|
{/* General Info */}
|
|
273
|
-
<Section title=
|
|
283
|
+
<Section title={t('cpq.wizards.detail.sections.general', 'General')}>
|
|
274
284
|
<div className="rounded-lg border p-4">
|
|
275
285
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-4">
|
|
276
|
-
<KV label=
|
|
277
|
-
<KV label=
|
|
278
|
-
<KV label=
|
|
279
|
-
<KV label=
|
|
286
|
+
<KV label={t('cpq.wizards.detail.code', 'Code')}><span className="font-mono text-xs">{definition.code}</span></KV>
|
|
287
|
+
<KV label={t('cpq.wizards.detail.version', 'Version')}>v{definition.version}</KV>
|
|
288
|
+
<KV label={t('cpq.wizards.detail.surface', 'Surface')}>{definition.surface}</KV>
|
|
289
|
+
<KV label={t('cpq.wizards.detail.active', 'Active')}>
|
|
280
290
|
<Badge variant={definition.isActive ? 'success' : 'muted'}>
|
|
281
|
-
{definition.isActive ? 'Yes' : 'No'}
|
|
291
|
+
{definition.isActive ? t('cpq.common.yes', 'Yes') : t('cpq.common.no', 'No')}
|
|
282
292
|
</Badge>
|
|
283
293
|
</KV>
|
|
284
294
|
</div>
|
|
@@ -286,7 +296,7 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
286
296
|
</Section>
|
|
287
297
|
|
|
288
298
|
{/* Applicability */}
|
|
289
|
-
<Section title=
|
|
299
|
+
<Section title={t('cpq.wizards.detail.sections.applicability', 'Applicability')}>
|
|
290
300
|
<div className="rounded-lg border p-4">
|
|
291
301
|
<ApplicabilityPanel applicability={definition.applicability} />
|
|
292
302
|
</div>
|
|
@@ -294,13 +304,13 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
294
304
|
|
|
295
305
|
{/* Params Schema */}
|
|
296
306
|
{definition.paramsSchema && Object.keys(definition.paramsSchema).length > 0 && (
|
|
297
|
-
<Section title=
|
|
307
|
+
<Section title={t('cpq.wizards.detail.sections.paramsSchema', 'Parameters Schema')}>
|
|
298
308
|
<JsonBlock data={definition.paramsSchema} />
|
|
299
309
|
</Section>
|
|
300
310
|
)}
|
|
301
311
|
|
|
302
312
|
{/* Steps */}
|
|
303
|
-
<Section title={
|
|
313
|
+
<Section title={t('cpq.wizards.detail.sections.steps', 'Steps ({count})', { count: definition.steps.length })}>
|
|
304
314
|
<div className="space-y-2">
|
|
305
315
|
{definition.steps.map((step, i) => (
|
|
306
316
|
<StepCard key={step.stepId} step={step} index={i} />
|
|
@@ -310,7 +320,7 @@ export default function CpqWizardDetailPage({ params }: { params: Record<string,
|
|
|
310
320
|
|
|
311
321
|
{/* Metadata */}
|
|
312
322
|
{definition.metadata && Object.keys(definition.metadata).length > 0 && (
|
|
313
|
-
<Section title=
|
|
323
|
+
<Section title={t('cpq.wizards.detail.sections.metadata', 'Metadata')}>
|
|
314
324
|
<JsonBlock data={definition.metadata} />
|
|
315
325
|
</Section>
|
|
316
326
|
)}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { useRouter, useSearchParams } from 'next/navigation'
|
|
4
4
|
import { Alert } from '@open-mercato/ui/primitives/alert'
|
|
5
|
+
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
5
6
|
import { WizardRunner } from '../../../../workflows/WizardRunner'
|
|
6
7
|
import { registerBuiltInStepTypes } from '../../../../workflows/steps'
|
|
7
8
|
import type { WizardDefinitionResult } from '../../../../workflows/types'
|
|
@@ -9,9 +10,9 @@ import type { WizardDefinitionResult } from '../../../../workflows/types'
|
|
|
9
10
|
// Register built-in step types once on module load
|
|
10
11
|
registerBuiltInStepTypes()
|
|
11
12
|
|
|
12
|
-
async function apiJson<T>(url: string): Promise<T> {
|
|
13
|
+
async function apiJson<T>(url: string, t: TranslateFn): Promise<T> {
|
|
13
14
|
const res = await fetch(url)
|
|
14
|
-
if (!res.ok) throw new Error(
|
|
15
|
+
if (!res.ok) throw new Error(t('cpq.wizards.runner.errors.api', 'API error {status}', { status: res.status }))
|
|
15
16
|
return res.json() as Promise<T>
|
|
16
17
|
}
|
|
17
18
|
|
|
@@ -30,6 +31,7 @@ function ParamsForm({
|
|
|
30
31
|
schema: ParamsSchema
|
|
31
32
|
onStart: (params: Record<string, string>) => void
|
|
32
33
|
}) {
|
|
34
|
+
const t = useT()
|
|
33
35
|
const properties = schema.properties ?? {}
|
|
34
36
|
const requiredFields = schema.required ?? []
|
|
35
37
|
const fieldNames = Object.keys(properties)
|
|
@@ -45,8 +47,8 @@ function ParamsForm({
|
|
|
45
47
|
return (
|
|
46
48
|
<div className="space-y-4">
|
|
47
49
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-1">
|
|
48
|
-
<h4 className="text-sm font-medium">This wizard requires input parameters</h4>
|
|
49
|
-
<p className="text-xs text-muted-foreground">Fill in the required fields before starting
|
|
50
|
+
<h4 className="text-sm font-medium">{t('cpq.wizards.runner.params.title', 'This wizard requires input parameters')}</h4>
|
|
51
|
+
<p className="text-xs text-muted-foreground">{t('cpq.wizards.runner.params.hint', 'Fill in the required fields before starting.')}</p>
|
|
50
52
|
</div>
|
|
51
53
|
|
|
52
54
|
<div className="space-y-3">
|
|
@@ -67,7 +69,7 @@ function ParamsForm({
|
|
|
67
69
|
className="w-full rounded-md border px-3 py-2 text-sm font-mono"
|
|
68
70
|
/>
|
|
69
71
|
{prop?.format && (
|
|
70
|
-
<p className="text-xs text-muted-foreground mt-0.5">Format: {prop.format}</p>
|
|
72
|
+
<p className="text-xs text-muted-foreground mt-0.5">{t('cpq.wizards.runner.params.format', 'Format: {format}', { format: prop.format })}</p>
|
|
71
73
|
)}
|
|
72
74
|
</div>
|
|
73
75
|
)
|
|
@@ -80,7 +82,7 @@ function ParamsForm({
|
|
|
80
82
|
disabled={!isValid}
|
|
81
83
|
className="inline-flex items-center justify-center rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
82
84
|
>
|
|
83
|
-
Preview Wizard
|
|
85
|
+
{t('cpq.wizards.runner.params.start', 'Preview Wizard')}
|
|
84
86
|
</button>
|
|
85
87
|
</div>
|
|
86
88
|
)
|
|
@@ -89,6 +91,7 @@ function ParamsForm({
|
|
|
89
91
|
// ─── Main page ──────────────────────────────────────────────────
|
|
90
92
|
|
|
91
93
|
export default function CpqWizardRunnerPage({ params }: { params: Record<string, string | string[]> }) {
|
|
94
|
+
const t = useT()
|
|
92
95
|
const router = useRouter()
|
|
93
96
|
const searchParams = useSearchParams()
|
|
94
97
|
const definitionCode = params.definitionCode as string
|
|
@@ -114,7 +117,7 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
114
117
|
React.useEffect(() => {
|
|
115
118
|
async function load() {
|
|
116
119
|
if (!definitionCode || definitionCode === 'undefined') {
|
|
117
|
-
setError('Invalid wizard definition code')
|
|
120
|
+
setError(t('cpq.wizards.runner.errors.invalidCode', 'Invalid wizard definition code'))
|
|
118
121
|
setLoading(false)
|
|
119
122
|
return
|
|
120
123
|
}
|
|
@@ -123,6 +126,7 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
123
126
|
try {
|
|
124
127
|
const def = await apiJson<WizardDefinitionResult>(
|
|
125
128
|
`/api/cpq/wizards?code=${encodeURIComponent(definitionCode)}`,
|
|
129
|
+
t,
|
|
126
130
|
)
|
|
127
131
|
setDefinition(def)
|
|
128
132
|
|
|
@@ -133,13 +137,13 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
133
137
|
setStarted(true)
|
|
134
138
|
}
|
|
135
139
|
} catch (err) {
|
|
136
|
-
setError(err instanceof Error ? err.message : 'Failed to load wizard')
|
|
140
|
+
setError(err instanceof Error ? err.message : t('cpq.wizards.runner.errors.load', 'Failed to load wizard'))
|
|
137
141
|
} finally {
|
|
138
142
|
setLoading(false)
|
|
139
143
|
}
|
|
140
144
|
}
|
|
141
145
|
load()
|
|
142
|
-
}, [definitionCode, queryParams])
|
|
146
|
+
}, [definitionCode, queryParams, t])
|
|
143
147
|
|
|
144
148
|
const handleComplete = React.useCallback((stepData: Record<string, Record<string, unknown>>) => {
|
|
145
149
|
// Check for orderId first (e.g. purchasing wizard creates an order)
|
|
@@ -164,7 +168,7 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
164
168
|
}, [])
|
|
165
169
|
|
|
166
170
|
if (loading) {
|
|
167
|
-
return <div className="text-sm text-muted-foreground">Loading wizard
|
|
171
|
+
return <div className="text-sm text-muted-foreground">{t('cpq.wizards.runner.loading', 'Loading wizard...')}</div>
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
if (error) {
|
|
@@ -176,7 +180,7 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
176
180
|
onClick={() => router.push('/backend/cpq/wizards')}
|
|
177
181
|
className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted"
|
|
178
182
|
>
|
|
179
|
-
Back to Wizards
|
|
183
|
+
{t('cpq.wizards.runner.backToList', 'Back to Wizards')}
|
|
180
184
|
</button>
|
|
181
185
|
</div>
|
|
182
186
|
)
|
|
@@ -185,7 +189,7 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
185
189
|
if (!definition) {
|
|
186
190
|
return (
|
|
187
191
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
188
|
-
Wizard not found.
|
|
192
|
+
{t('cpq.wizards.runner.notFound', 'Wizard not found.')}
|
|
189
193
|
</div>
|
|
190
194
|
)
|
|
191
195
|
}
|
|
@@ -202,7 +206,7 @@ export default function CpqWizardRunnerPage({ params }: { params: Record<string,
|
|
|
202
206
|
onClick={() => router.push('/backend/cpq/wizards')}
|
|
203
207
|
className="text-sm text-muted-foreground hover:text-foreground"
|
|
204
208
|
>
|
|
205
|
-
|
|
209
|
+
← {t('cpq.wizards.runner.allWizards', 'All Wizards')}
|
|
206
210
|
</button>
|
|
207
211
|
</div>
|
|
208
212
|
|
|
@@ -95,7 +95,7 @@ export default function CpqWizardsPage() {
|
|
|
95
95
|
header: t('cpq.wizards.table.active', 'Active'),
|
|
96
96
|
cell: ({ row }) => (
|
|
97
97
|
<Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>
|
|
98
|
-
{row.original.isActive ? 'Yes' : 'No'}
|
|
98
|
+
{row.original.isActive ? t('cpq.common.yes', 'Yes') : t('cpq.common.no', 'No')}
|
|
99
99
|
</Tag>
|
|
100
100
|
),
|
|
101
101
|
},
|
|
@@ -39,8 +39,8 @@ export function ArcLineDiff({ line, currency }: { line: ArcDiffLine; currency: s
|
|
|
39
39
|
const isCancel = line.action === 'cancel'
|
|
40
40
|
const rows: Array<{ label: string; before: number; after: number; asCurrency: boolean }> = [
|
|
41
41
|
{ label: t('cpq.arc.diff.quantity', 'Quantity'), before: src.quantity, after: isCancel ? 0 : line.quantity, asCurrency: false },
|
|
42
|
-
{ label: 'MRC', before: src.mrcAmount, after: isCancel ? 0 : line.mrcTotal, asCurrency: true },
|
|
43
|
-
{ label: 'NRC', before: src.nrcAmount, after: isCancel ? 0 : line.nrcTotal, asCurrency: true },
|
|
42
|
+
{ label: t('cpq.arc.diff.mrc', 'MRC'), before: src.mrcAmount, after: isCancel ? 0 : line.mrcTotal, asCurrency: true },
|
|
43
|
+
{ label: t('cpq.arc.diff.nrc', 'NRC'), before: src.nrcAmount, after: isCancel ? 0 : line.nrcTotal, asCurrency: true },
|
|
44
44
|
]
|
|
45
45
|
const fmtVal = (value: number, asCurrency: boolean) => (asCurrency ? formatMoney(value, currency) : String(value))
|
|
46
46
|
|
|
@@ -94,7 +94,9 @@ export function ChargeBreakdownTable<C extends ChargeRowView>({
|
|
|
94
94
|
<span>{c.chargeName}</span>
|
|
95
95
|
{errors.length > 0 && (
|
|
96
96
|
<Tag variant="warning" className="ml-1.5 px-1.5 text-xs" data-testid="cpq-adjustment-invalid">
|
|
97
|
-
{t('cpq.priceAdjustments.
|
|
97
|
+
{t('cpq.priceAdjustments.invalidReason', 'Invalid: {reason}', {
|
|
98
|
+
reason: t(`cpq.priceAdjustments.errors.${errors[0].code}`, ADJUSTMENT_ERROR_FALLBACKS[errors[0].code] ?? errors[0].code),
|
|
99
|
+
})}
|
|
98
100
|
</Tag>
|
|
99
101
|
)}
|
|
100
102
|
</td>
|
|
@@ -149,7 +151,7 @@ function ChargePricingDetail({ charge: c, currency, reasonLabels, renderManualAc
|
|
|
149
151
|
return (
|
|
150
152
|
<div className="rounded border bg-background p-3 space-y-3 text-xs">
|
|
151
153
|
<div className="flex items-center justify-between">
|
|
152
|
-
<span className="font-semibold text-sm">{t('cpq.charges.
|
|
154
|
+
<span className="font-semibold text-sm">{t('cpq.charges.pricingDetailsFor', 'Pricing Details — {name}', { name: c.chargeName })}</span>
|
|
153
155
|
<span className="text-muted-foreground">{c.pricingMethod ?? 'flat'}</span>
|
|
154
156
|
</div>
|
|
155
157
|
|
|
@@ -73,8 +73,10 @@ export function PriceWaterfall({ charge, currency, reasonLabels, renderManualAct
|
|
|
73
73
|
)
|
|
74
74
|
}
|
|
75
75
|
const source = row.kind === 'rule'
|
|
76
|
-
?
|
|
77
|
-
:
|
|
76
|
+
? t('cpq.priceAdjustments.waterfall.ruleSource', 'Price rule: {name}', { name: row.label })
|
|
77
|
+
: t('cpq.priceAdjustments.waterfall.manualSource', 'Negotiated: {reason}', {
|
|
78
|
+
reason: reasonLabels?.[row.reasonCode ?? ''] ?? row.reasonCode ?? row.label,
|
|
79
|
+
})
|
|
78
80
|
return (
|
|
79
81
|
<tr key={`${row.kind}-${index}`} className="border-t border-border/30">
|
|
80
82
|
<td className="py-0.5 pl-3 text-muted-foreground">{t(`cpq.priceAdjustments.types.${row.ruleType}`, TYPE_FALLBACKS[row.ruleType] ?? row.ruleType)}</td>
|
|
@@ -94,7 +94,7 @@ export function PricingSummaryCard({ summary, currency, arcDelta }: {
|
|
|
94
94
|
<div>
|
|
95
95
|
<p className="text-xs text-muted-foreground">{t('cpq.quotes.summary.usageCharges', 'Usage Charges')}</p>
|
|
96
96
|
{usageCharges.map((uc, i) => (
|
|
97
|
-
<p key={i} className="text-xs mt-0.5">{uc.chargeName
|
|
97
|
+
<p key={i} className="text-xs mt-0.5">{t('cpq.quotes.summary.usageChargeLine', '{name}: {price}/unit', { name: uc.chargeName, price: formatMoney(uc.unitPrice, currency) })}</p>
|
|
98
98
|
))}
|
|
99
99
|
</div>
|
|
100
100
|
)}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import * as React from 'react'
|
|
3
3
|
import { ChevronRight } from 'lucide-react'
|
|
4
4
|
import { Tag } from '@open-mercato/ui/primitives/tag'
|
|
5
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
5
6
|
import type { TagMap } from '@open-mercato/ui/primitives/tag'
|
|
6
7
|
import { formatStatusLabel } from './statusMaps'
|
|
7
8
|
|
|
@@ -30,7 +31,7 @@ export function StatusPath({
|
|
|
30
31
|
onTransition,
|
|
31
32
|
allowedTransitions,
|
|
32
33
|
disabled,
|
|
33
|
-
ariaLabel
|
|
34
|
+
ariaLabel,
|
|
34
35
|
}: {
|
|
35
36
|
current: string
|
|
36
37
|
/** Happy-path statuses in order. */
|
|
@@ -39,15 +40,17 @@ export function StatusPath({
|
|
|
39
40
|
terminals?: string[]
|
|
40
41
|
/** Status → Tag variant. Missing entries fall back to "neutral". */
|
|
41
42
|
statusMap: Record<string, TagVariant>
|
|
42
|
-
/** Optional human labels (overrides `formatStatusLabel`). */
|
|
43
|
+
/** Optional human labels, already translated by the caller (overrides `formatStatusLabel`). */
|
|
43
44
|
labels?: Record<string, string>
|
|
44
45
|
/** Click handler. Omit for read-only path. */
|
|
45
46
|
onTransition?: (target: string) => void
|
|
46
47
|
/** If set, only these targets are clickable. Else any non-current. */
|
|
47
48
|
allowedTransitions?: string[]
|
|
48
49
|
disabled?: boolean
|
|
50
|
+
/** Translated accessible name; defaults to "Status path". */
|
|
49
51
|
ariaLabel?: string
|
|
50
52
|
}) {
|
|
53
|
+
const t = useT()
|
|
51
54
|
// Build the displayed sequence. Aliases can swap into the happy
|
|
52
55
|
// path slot (e.g. quote's `pre_approved` shown as `approved`). We
|
|
53
56
|
// don't handle aliasing here — consumers can swap entries in
|
|
@@ -61,7 +64,7 @@ export function StatusPath({
|
|
|
61
64
|
const interactiveMode = typeof onTransition === 'function'
|
|
62
65
|
|
|
63
66
|
return (
|
|
64
|
-
<nav aria-label={ariaLabel} className="flex items-center gap-1.5 flex-wrap text-xs">
|
|
67
|
+
<nav aria-label={ariaLabel ?? t('cpq.statusPath.ariaLabel', 'Status path')} className="flex items-center gap-1.5 flex-wrap text-xs">
|
|
65
68
|
{sequence.map((status, idx) => {
|
|
66
69
|
const reached = currentIdx >= 0 && idx <= currentIdx
|
|
67
70
|
const isCurrent = status === current
|
|
@@ -91,7 +94,7 @@ export function StatusPath({
|
|
|
91
94
|
type="button"
|
|
92
95
|
onClick={() => onTransition!(status)}
|
|
93
96
|
aria-current={isCurrent ? 'step' : undefined}
|
|
94
|
-
title={
|
|
97
|
+
title={t('cpq.statusPath.transitionTo', 'Transition to {status}', { status: label })}
|
|
95
98
|
className={wrapperClass}
|
|
96
99
|
>
|
|
97
100
|
<Tag variant={variant} dot={isCurrent} className={tagClass}>
|