@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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
|
|
6
7
|
type Customer = {
|
|
@@ -11,6 +12,7 @@ type Customer = {
|
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps }: WizardStepProps) {
|
|
15
|
+
const t = useT()
|
|
14
16
|
const [customers, setCustomers] = useState<Customer[]>([])
|
|
15
17
|
const [search, setSearch] = useState('')
|
|
16
18
|
const [selected, setSelected] = useState<Customer | null>(null)
|
|
@@ -59,7 +61,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
59
61
|
<div className="flex gap-2">
|
|
60
62
|
<input
|
|
61
63
|
type="text"
|
|
62
|
-
placeholder=
|
|
64
|
+
placeholder={t('cpq.wizard.steps.customerSelect.searchPlaceholder', 'Search customers...')}
|
|
63
65
|
value={search}
|
|
64
66
|
onChange={(e) => setSearch(e.target.value)}
|
|
65
67
|
onKeyDown={(e) => e.key === 'Enter' && handleSearch()}
|
|
@@ -71,21 +73,23 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
71
73
|
disabled={loading}
|
|
72
74
|
className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50"
|
|
73
75
|
>
|
|
74
|
-
{loading
|
|
76
|
+
{loading
|
|
77
|
+
? t('cpq.wizard.steps.customerSelect.searching', 'Searching...')
|
|
78
|
+
: t('cpq.wizard.steps.customerSelect.search', 'Search')}
|
|
75
79
|
</button>
|
|
76
80
|
</div>
|
|
77
81
|
|
|
78
82
|
{selected && (
|
|
79
83
|
<div className="rounded-md border-2 border-primary bg-primary/5 p-3 flex items-center justify-between">
|
|
80
84
|
<span className="text-sm">
|
|
81
|
-
<strong>Selected
|
|
85
|
+
<strong>{t('cpq.wizard.steps.customerSelect.selected', 'Selected:')}</strong> {selected.name}
|
|
82
86
|
</span>
|
|
83
87
|
<button
|
|
84
88
|
type="button"
|
|
85
89
|
onClick={() => setSelected(null)}
|
|
86
90
|
className="text-xs text-muted-foreground hover:text-foreground underline"
|
|
87
91
|
>
|
|
88
|
-
Clear
|
|
92
|
+
{t('cpq.wizard.steps.customerSelect.clear', 'Clear')}
|
|
89
93
|
</button>
|
|
90
94
|
</div>
|
|
91
95
|
)}
|
|
@@ -107,7 +111,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
107
111
|
))}
|
|
108
112
|
{customers.length === 0 && !loading && (
|
|
109
113
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
110
|
-
No customers found.
|
|
114
|
+
{t('cpq.wizard.steps.customerSelect.empty', 'No customers found.')}
|
|
111
115
|
</div>
|
|
112
116
|
)}
|
|
113
117
|
</div>
|
|
@@ -115,7 +119,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
115
119
|
<div className="flex gap-2 justify-end pt-2">
|
|
116
120
|
{completedSteps.length > 0 && (
|
|
117
121
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
118
|
-
Back
|
|
122
|
+
{t('cpq.common.back', 'Back')}
|
|
119
123
|
</button>
|
|
120
124
|
)}
|
|
121
125
|
<button
|
|
@@ -128,7 +132,7 @@ export function CustomerSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
128
132
|
disabled={!selected}
|
|
129
133
|
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"
|
|
130
134
|
>
|
|
131
|
-
Next
|
|
135
|
+
{t('cpq.wizard.next', 'Next')}
|
|
132
136
|
</button>
|
|
133
137
|
</div>
|
|
134
138
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -8,17 +9,20 @@ import type { WizardStepProps } from '../types'
|
|
|
8
9
|
* Implementation deferred until the inventory/subscription domain exists.
|
|
9
10
|
*/
|
|
10
11
|
export function InventorySelectStep({ onBack, onSkip, completedSteps }: WizardStepProps) {
|
|
12
|
+
const t = useT()
|
|
11
13
|
return (
|
|
12
14
|
<div className="rounded-lg border bg-card p-6 text-center space-y-3">
|
|
13
|
-
<h3 className="text-lg font-semibold">Inventory Selection</h3>
|
|
15
|
+
<h3 className="text-lg font-semibold">{t('cpq.wizard.steps.inventorySelect.title', 'Inventory Selection')}</h3>
|
|
14
16
|
<p className="text-sm text-muted-foreground">
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
{t(
|
|
18
|
+
'cpq.wizard.steps.inventorySelect.notImplemented',
|
|
19
|
+
'This step type is not yet implemented. It will allow selecting existing customer inventory items for modification or renewal once the inventory domain is available.',
|
|
20
|
+
)}
|
|
17
21
|
</p>
|
|
18
22
|
<div className="flex gap-2 justify-center pt-2">
|
|
19
23
|
{completedSteps.length > 0 && (
|
|
20
24
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
21
|
-
Back
|
|
25
|
+
{t('cpq.common.back', 'Back')}
|
|
22
26
|
</button>
|
|
23
27
|
)}
|
|
24
28
|
<button
|
|
@@ -26,7 +30,7 @@ export function InventorySelectStep({ onBack, onSkip, completedSteps }: WizardSt
|
|
|
26
30
|
onClick={onSkip}
|
|
27
31
|
className="inline-flex items-center justify-center rounded-md border border-primary px-4 py-2 text-sm font-medium text-primary hover:bg-primary/10"
|
|
28
32
|
>
|
|
29
|
-
Skip
|
|
33
|
+
{t('cpq.wizard.skip', 'Skip')}
|
|
30
34
|
</button>
|
|
31
35
|
</div>
|
|
32
36
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
import { Button } from '@open-mercato/ui/primitives/button'
|
|
6
7
|
import { Tag } from '@open-mercato/ui/primitives/tag'
|
|
@@ -21,6 +22,7 @@ function fmt(n: number, currency = 'USD') {
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, quoteId }: WizardStepProps) {
|
|
25
|
+
const t = useT()
|
|
24
26
|
const [lines, setLines] = useState<QuoteLine[]>([])
|
|
25
27
|
const [loading, setLoading] = useState(true)
|
|
26
28
|
|
|
@@ -67,19 +69,23 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
67
69
|
<div className="space-y-4">
|
|
68
70
|
<div className="flex items-center justify-between">
|
|
69
71
|
<span className="text-xs text-muted-foreground">
|
|
70
|
-
{lines.length
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
{lines.length !== 1
|
|
73
|
+
? t('cpq.wizard.steps.itemList.count.other', '{count} items', { count: lines.length })
|
|
74
|
+
: t('cpq.wizard.steps.itemList.count.one', '{count} item', { count: lines.length })}
|
|
75
|
+
{minItems > 0 && ' '}
|
|
76
|
+
{minItems > 0 && t('cpq.wizard.steps.itemList.min', '(min: {min})', { min: minItems })}
|
|
77
|
+
{maxItems < Infinity && ' '}
|
|
78
|
+
{maxItems < Infinity && t('cpq.wizard.steps.itemList.max', '(max: {max})', { max: maxItems })}
|
|
73
79
|
</span>
|
|
74
80
|
{canAddMore && (
|
|
75
81
|
<span className="text-xs text-muted-foreground">
|
|
76
|
-
Use the offering browser to add items to the quote, then return here.
|
|
82
|
+
{t('cpq.wizard.steps.itemList.hint', 'Use the offering browser to add items to the quote, then return here.')}
|
|
77
83
|
</span>
|
|
78
84
|
)}
|
|
79
85
|
</div>
|
|
80
86
|
|
|
81
87
|
{loading ? (
|
|
82
|
-
<div className="text-sm text-muted-foreground">Loading items
|
|
88
|
+
<div className="text-sm text-muted-foreground">{t('cpq.wizard.steps.itemList.loading', 'Loading items...')}</div>
|
|
83
89
|
) : (
|
|
84
90
|
<div className="space-y-2">
|
|
85
91
|
{lines.map((line) => (
|
|
@@ -92,14 +98,14 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
92
98
|
{line.quantity > 1 && <span className="ml-2 text-xs text-muted-foreground">x{line.quantity}</span>}
|
|
93
99
|
{!line.isConfigured && (
|
|
94
100
|
<Tag variant="warning" className="ml-2">
|
|
95
|
-
Needs configuration
|
|
101
|
+
{t('cpq.wizard.steps.itemList.needsConfiguration', 'Needs configuration')}
|
|
96
102
|
</Tag>
|
|
97
103
|
)}
|
|
98
104
|
</div>
|
|
99
105
|
<div className="flex items-center gap-3">
|
|
100
106
|
{showPricing && (
|
|
101
107
|
<span className="text-xs">
|
|
102
|
-
{
|
|
108
|
+
{t('cpq.wizard.linePricing', '{nrc} NRC / {mrc} MRC', { nrc: fmt(line.nrcTotal), mrc: fmt(line.mrcTotal) })}
|
|
103
109
|
</span>
|
|
104
110
|
)}
|
|
105
111
|
<Button
|
|
@@ -108,14 +114,14 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
108
114
|
size="sm"
|
|
109
115
|
onClick={() => handleRemove(line.lineId)}
|
|
110
116
|
>
|
|
111
|
-
Remove
|
|
117
|
+
{t('cpq.wizard.remove', 'Remove')}
|
|
112
118
|
</Button>
|
|
113
119
|
</div>
|
|
114
120
|
</div>
|
|
115
121
|
))}
|
|
116
122
|
{lines.length === 0 && (
|
|
117
123
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
118
|
-
No items added yet.
|
|
124
|
+
{t('cpq.wizard.steps.itemList.empty', 'No items added yet.')}
|
|
119
125
|
</div>
|
|
120
126
|
)}
|
|
121
127
|
</div>
|
|
@@ -124,11 +130,11 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
124
130
|
<div className="flex gap-2 justify-end pt-2">
|
|
125
131
|
{completedSteps.length > 0 && (
|
|
126
132
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
127
|
-
Back
|
|
133
|
+
{t('cpq.common.back', 'Back')}
|
|
128
134
|
</button>
|
|
129
135
|
)}
|
|
130
136
|
<button type="button" onClick={loadLines} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
131
|
-
Refresh
|
|
137
|
+
{t('cpq.wizard.steps.itemList.refresh', 'Refresh')}
|
|
132
138
|
</button>
|
|
133
139
|
<button
|
|
134
140
|
type="button"
|
|
@@ -138,7 +144,7 @@ export function ItemListStep({ config, onComplete, onBack, stepData, completedSt
|
|
|
138
144
|
disabled={!canProceed}
|
|
139
145
|
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"
|
|
140
146
|
>
|
|
141
|
-
Next
|
|
147
|
+
{t('cpq.wizard.next', 'Next')}
|
|
142
148
|
</button>
|
|
143
149
|
</div>
|
|
144
150
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
import { NumberInput } from '../../components/NumberInput'
|
|
6
7
|
|
|
@@ -43,6 +44,7 @@ function ConfigPanel({
|
|
|
43
44
|
onChange: (code: string, value: unknown) => void
|
|
44
45
|
onRemove: () => void
|
|
45
46
|
}) {
|
|
47
|
+
const t = useT()
|
|
46
48
|
const [attributes, setAttributes] = useState<ConstrainedAttribute[]>([])
|
|
47
49
|
const [loading, setLoading] = useState(true)
|
|
48
50
|
const [configVersion, setConfigVersion] = useState(0)
|
|
@@ -89,7 +91,7 @@ function ConfigPanel({
|
|
|
89
91
|
if (attr.attributeType === 'select' || attr.attributeType === 'enum') {
|
|
90
92
|
return (
|
|
91
93
|
<select value={String(value)} onChange={(e) => handleChange(attr.code, e.target.value)} className="w-full rounded-md border px-3 py-2 text-sm">
|
|
92
|
-
<option value="">Select
|
|
94
|
+
<option value="">{t('cpq.wizard.selectPlaceholder', 'Select...')}</option>
|
|
93
95
|
{attr.options?.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}
|
|
94
96
|
</select>
|
|
95
97
|
)
|
|
@@ -117,12 +119,12 @@ function ConfigPanel({
|
|
|
117
119
|
<div className="rounded-lg border p-4 space-y-3">
|
|
118
120
|
<div className="flex items-center justify-between border-b pb-2">
|
|
119
121
|
<h4 className="text-sm font-medium">{offering.name}</h4>
|
|
120
|
-
<button type="button" onClick={onRemove} className="text-xs text-muted-foreground hover:text-foreground">Remove</button>
|
|
122
|
+
<button type="button" onClick={onRemove} className="text-xs text-muted-foreground hover:text-foreground">{t('cpq.wizard.remove', 'Remove')}</button>
|
|
121
123
|
</div>
|
|
122
124
|
{loading ? (
|
|
123
|
-
<div className="text-sm text-muted-foreground">Loading attributes
|
|
125
|
+
<div className="text-sm text-muted-foreground">{t('cpq.wizard.loadingAttributes', 'Loading attributes...')}</div>
|
|
124
126
|
) : attributes.length === 0 ? (
|
|
125
|
-
<div className="text-sm text-muted-foreground">No configurable attributes
|
|
127
|
+
<div className="text-sm text-muted-foreground">{t('cpq.wizard.steps.offeringSelect.noAttributes', 'No configurable attributes.')}</div>
|
|
126
128
|
) : (
|
|
127
129
|
<div className="space-y-3">
|
|
128
130
|
{attributes.map((attr) => (
|
|
@@ -150,6 +152,7 @@ function BandwidthIndicator({
|
|
|
150
152
|
portCapacityMb: number
|
|
151
153
|
items: SelectedItem[]
|
|
152
154
|
}) {
|
|
155
|
+
const t = useT()
|
|
153
156
|
const totalBandwidth = items.reduce((sum, item) => {
|
|
154
157
|
const bw = Number(item.configuration.bandwidth_mb) || 0
|
|
155
158
|
return sum + bw
|
|
@@ -161,9 +164,9 @@ function BandwidthIndicator({
|
|
|
161
164
|
return (
|
|
162
165
|
<div className="rounded-lg border p-3 space-y-2">
|
|
163
166
|
<div className="flex items-center justify-between text-xs">
|
|
164
|
-
<span className="font-medium">Bandwidth Utilization</span>
|
|
167
|
+
<span className="font-medium">{t('cpq.wizard.steps.offeringSelect.bandwidth.title', 'Bandwidth Utilization')}</span>
|
|
165
168
|
<span className={overCapacity ? 'text-destructive font-medium' : 'text-muted-foreground'}>
|
|
166
|
-
{
|
|
169
|
+
{t('cpq.wizard.steps.offeringSelect.bandwidth.usage', '{used} / {capacity} MB', { used: totalBandwidth, capacity: portCapacityMb })}
|
|
167
170
|
</span>
|
|
168
171
|
</div>
|
|
169
172
|
<div className="h-2 rounded-full bg-muted overflow-hidden">
|
|
@@ -174,7 +177,7 @@ function BandwidthIndicator({
|
|
|
174
177
|
</div>
|
|
175
178
|
{overCapacity && (
|
|
176
179
|
<p className="text-xs text-destructive">
|
|
177
|
-
Total bandwidth exceeds port capacity by {totalBandwidth - portCapacityMb}
|
|
180
|
+
{t('cpq.wizard.steps.offeringSelect.bandwidth.exceeded', 'Total bandwidth exceeds port capacity by {excess} MB', { excess: totalBandwidth - portCapacityMb })}
|
|
178
181
|
</p>
|
|
179
182
|
)}
|
|
180
183
|
</div>
|
|
@@ -186,6 +189,7 @@ function BandwidthIndicator({
|
|
|
186
189
|
const PORT_SIZE_MB: Record<string, number> = { '1G': 1000, '10G': 10000, '100G': 100000 }
|
|
187
190
|
|
|
188
191
|
export function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }: WizardStepProps) {
|
|
192
|
+
const t = useT()
|
|
189
193
|
const [offerings, setOfferings] = useState<Offering[]>([])
|
|
190
194
|
const [selectedItems, setSelectedItems] = useState<SelectedItem[]>([])
|
|
191
195
|
const [loading, setLoading] = useState(true)
|
|
@@ -336,7 +340,7 @@ export function OfferingSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
336
340
|
}, [selectedItems, enableConfiguration, quoteId, resolvedCustomerId, onComplete])
|
|
337
341
|
|
|
338
342
|
if (loading) {
|
|
339
|
-
return <div className="text-sm text-muted-foreground">Loading offerings
|
|
343
|
+
return <div className="text-sm text-muted-foreground">{t('cpq.wizard.steps.offeringSelect.loading', 'Loading offerings...')}</div>
|
|
340
344
|
}
|
|
341
345
|
|
|
342
346
|
// Offerings not yet added
|
|
@@ -368,18 +372,18 @@ export function OfferingSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
368
372
|
>
|
|
369
373
|
<div className="flex items-center justify-between">
|
|
370
374
|
<h4 className="text-sm font-medium">{o.name}</h4>
|
|
371
|
-
{isAdded && <span className="text-xs text-primary font-medium">Added</span>}
|
|
375
|
+
{isAdded && <span className="text-xs text-primary font-medium">{t('cpq.wizard.steps.offeringSelect.added', 'Added')}</span>}
|
|
372
376
|
</div>
|
|
373
377
|
<p className="text-xs text-muted-foreground mt-1">{o.description || o.code}</p>
|
|
374
378
|
{!isAdded && (
|
|
375
|
-
<span className="text-xs text-primary mt-2 inline-block"
|
|
379
|
+
<span className="text-xs text-primary mt-2 inline-block">{t('cpq.wizard.steps.offeringSelect.add', '+ Add')}</span>
|
|
376
380
|
)}
|
|
377
381
|
</div>
|
|
378
382
|
)
|
|
379
383
|
})}
|
|
380
384
|
{offerings.length === 0 && (
|
|
381
385
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
382
|
-
No offerings available.
|
|
386
|
+
{t('cpq.wizard.steps.offeringSelect.empty', 'No offerings available.')}
|
|
383
387
|
</div>
|
|
384
388
|
)}
|
|
385
389
|
</div>
|
|
@@ -392,7 +396,7 @@ export function OfferingSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
392
396
|
{/* Configuration panels for selected items */}
|
|
393
397
|
{enableConfiguration && selectedItems.length > 0 && (
|
|
394
398
|
<div className="space-y-3">
|
|
395
|
-
<h4 className="text-sm font-medium">Selected Services ({selectedItems.length})</h4>
|
|
399
|
+
<h4 className="text-sm font-medium">{t('cpq.wizard.steps.offeringSelect.selectedServices', 'Selected Services ({count})', { count: selectedItems.length })}</h4>
|
|
396
400
|
{selectedItems.map((item, index) => (
|
|
397
401
|
<ConfigPanel
|
|
398
402
|
key={`${item.offering.id}-${index}`}
|
|
@@ -408,11 +412,11 @@ export function OfferingSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
408
412
|
{/* No configuration — just show selected list */}
|
|
409
413
|
{!enableConfiguration && selectedItems.length > 0 && (
|
|
410
414
|
<div className="space-y-2">
|
|
411
|
-
<h4 className="text-sm font-medium">Selected ({selectedItems.length})</h4>
|
|
415
|
+
<h4 className="text-sm font-medium">{t('cpq.wizard.steps.offeringSelect.selected', 'Selected ({count})', { count: selectedItems.length })}</h4>
|
|
412
416
|
{selectedItems.map((item, index) => (
|
|
413
417
|
<div key={`${item.offering.id}-${index}`} className="flex items-center justify-between rounded-md border p-3">
|
|
414
418
|
<span className="text-sm font-medium">{item.offering.name}</span>
|
|
415
|
-
<button type="button" onClick={() => removeItem(index)} className="text-xs text-muted-foreground hover:text-foreground">Remove</button>
|
|
419
|
+
<button type="button" onClick={() => removeItem(index)} className="text-xs text-muted-foreground hover:text-foreground">{t('cpq.wizard.remove', 'Remove')}</button>
|
|
416
420
|
</div>
|
|
417
421
|
))}
|
|
418
422
|
</div>
|
|
@@ -421,7 +425,7 @@ export function OfferingSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
421
425
|
<div className="flex gap-2 justify-end pt-2">
|
|
422
426
|
{completedSteps.length > 0 && (
|
|
423
427
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
424
|
-
Back
|
|
428
|
+
{t('cpq.common.back', 'Back')}
|
|
425
429
|
</button>
|
|
426
430
|
)}
|
|
427
431
|
<button
|
|
@@ -430,7 +434,7 @@ export function OfferingSelectStep({ config, onComplete, onBack, stepData, curre
|
|
|
430
434
|
disabled={selectedItems.length === 0 || saving}
|
|
431
435
|
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"
|
|
432
436
|
>
|
|
433
|
-
{saving ? 'Saving...' : 'Next'}
|
|
437
|
+
{saving ? t('cpq.common.saving', 'Saving...') : t('cpq.wizard.next', 'Next')}
|
|
434
438
|
</button>
|
|
435
439
|
</div>
|
|
436
440
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
import { NumberInput } from '../../components/NumberInput'
|
|
6
7
|
|
|
@@ -34,6 +35,7 @@ function resolveOfferingId(config: Record<string, unknown>, stepData: Record<str
|
|
|
34
35
|
}
|
|
35
36
|
|
|
36
37
|
export function ProductConfigureStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }: WizardStepProps) {
|
|
38
|
+
const t = useT()
|
|
37
39
|
const [attributes, setAttributes] = useState<ConstrainedAttribute[]>([])
|
|
38
40
|
const [values, setValues] = useState<Record<string, unknown>>({})
|
|
39
41
|
const [loading, setLoading] = useState(true)
|
|
@@ -181,7 +183,7 @@ export function ProductConfigureStep({ config, onComplete, onBack, stepData, cur
|
|
|
181
183
|
disabled={isReadonly}
|
|
182
184
|
className="w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50"
|
|
183
185
|
>
|
|
184
|
-
<option value="">Select
|
|
186
|
+
<option value="">{t('cpq.wizard.selectPlaceholder', 'Select...')}</option>
|
|
185
187
|
{attr.options?.map((opt) => (
|
|
186
188
|
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
187
189
|
))}
|
|
@@ -226,13 +228,13 @@ export function ProductConfigureStep({ config, onComplete, onBack, stepData, cur
|
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
if (loading) {
|
|
229
|
-
return <div className="text-sm text-muted-foreground">Loading attributes
|
|
231
|
+
return <div className="text-sm text-muted-foreground">{t('cpq.wizard.loadingAttributes', 'Loading attributes...')}</div>
|
|
230
232
|
}
|
|
231
233
|
|
|
232
234
|
if (!offeringId) {
|
|
233
235
|
return (
|
|
234
236
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
235
|
-
No offering selected. Please go back and select an offering.
|
|
237
|
+
{t('cpq.wizard.steps.productConfigure.noOffering', 'No offering selected. Please go back and select an offering.')}
|
|
236
238
|
</div>
|
|
237
239
|
)
|
|
238
240
|
}
|
|
@@ -264,13 +266,13 @@ export function ProductConfigureStep({ config, onComplete, onBack, stepData, cur
|
|
|
264
266
|
return renderGroup(group.label, groupAttrs)
|
|
265
267
|
})
|
|
266
268
|
) : (
|
|
267
|
-
renderGroup('Configuration', attributes)
|
|
269
|
+
renderGroup(t('cpq.lines.configuration', 'Configuration'), attributes)
|
|
268
270
|
)}
|
|
269
271
|
|
|
270
272
|
<div className="flex gap-2 justify-end pt-2">
|
|
271
273
|
{completedSteps.length > 0 && (
|
|
272
274
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
273
|
-
Back
|
|
275
|
+
{t('cpq.common.back', 'Back')}
|
|
274
276
|
</button>
|
|
275
277
|
)}
|
|
276
278
|
<button
|
|
@@ -279,7 +281,7 @@ export function ProductConfigureStep({ config, onComplete, onBack, stepData, cur
|
|
|
279
281
|
disabled={saving}
|
|
280
282
|
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"
|
|
281
283
|
>
|
|
282
|
-
{saving ? 'Saving...' : 'Next'}
|
|
284
|
+
{saving ? t('cpq.common.saving', 'Saving...') : t('cpq.wizard.next', 'Next')}
|
|
283
285
|
</button>
|
|
284
286
|
</div>
|
|
285
287
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
3
|
import React, { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
4
5
|
import type { WizardStepProps } from '../types'
|
|
5
6
|
|
|
6
7
|
type ResolvedCharge = {
|
|
@@ -47,7 +48,21 @@ function fmt(n: number, currency = 'USD') {
|
|
|
47
48
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(n)
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
function chargeTypeLabel(t: TranslateFn, chargeType: string): string {
|
|
52
|
+
switch (chargeType) {
|
|
53
|
+
case 'nrc':
|
|
54
|
+
return t('cpq.lines.chargeType.nrc', 'nrc')
|
|
55
|
+
case 'mrc':
|
|
56
|
+
return t('cpq.lines.chargeType.mrc', 'mrc')
|
|
57
|
+
case 'usage':
|
|
58
|
+
return t('cpq.lines.chargeType.usage', 'usage')
|
|
59
|
+
default:
|
|
60
|
+
return chargeType
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId }: WizardStepProps) {
|
|
65
|
+
const t = useT()
|
|
51
66
|
const [quote, setQuote] = useState<QuoteData | null>(null)
|
|
52
67
|
const [loading, setLoading] = useState(true)
|
|
53
68
|
const [submitting, setSubmitting] = useState(false)
|
|
@@ -55,7 +70,7 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
55
70
|
const showChargeBreakdown = config.showChargeBreakdown !== false
|
|
56
71
|
const showAdjustments = config.showAdjustments !== false
|
|
57
72
|
const submitAction = (config.submitAction as string) ?? 'save'
|
|
58
|
-
const submitLabel = (config.submitLabel as string) ?? 'Complete'
|
|
73
|
+
const submitLabel = (config.submitLabel as string) ?? t('cpq.wizard.steps.review.complete', 'Complete')
|
|
59
74
|
|
|
60
75
|
const loadQuote = useCallback(async () => {
|
|
61
76
|
if (!quoteId) { setLoading(false); return }
|
|
@@ -102,13 +117,13 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
102
117
|
}, [quoteId, submitAction, quote, onComplete])
|
|
103
118
|
|
|
104
119
|
if (loading) {
|
|
105
|
-
return <div className="text-sm text-muted-foreground">Loading quote summary
|
|
120
|
+
return <div className="text-sm text-muted-foreground">{t('cpq.wizard.steps.review.loading', 'Loading quote summary...')}</div>
|
|
106
121
|
}
|
|
107
122
|
|
|
108
123
|
if (!quote) {
|
|
109
124
|
return (
|
|
110
125
|
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">
|
|
111
|
-
No quote data available. Please go back and configure items.
|
|
126
|
+
{t('cpq.wizard.steps.review.noData', 'No quote data available. Please go back and configure items.')}
|
|
112
127
|
</div>
|
|
113
128
|
)
|
|
114
129
|
}
|
|
@@ -119,25 +134,25 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
119
134
|
<div className="space-y-5">
|
|
120
135
|
{/* Quote summary */}
|
|
121
136
|
<div className="rounded-lg border bg-muted/30 p-4 space-y-3">
|
|
122
|
-
<h4 className="text-sm font-medium">Quote Summary</h4>
|
|
137
|
+
<h4 className="text-sm font-medium">{t('cpq.wizard.steps.review.summaryTitle', 'Quote Summary')}</h4>
|
|
123
138
|
<div className="grid grid-cols-2 gap-4">
|
|
124
139
|
<div>
|
|
125
|
-
<span className="text-xs text-muted-foreground">NRC Total</span>
|
|
140
|
+
<span className="text-xs text-muted-foreground">{t('cpq.wizard.steps.review.nrcTotal', 'NRC Total')}</span>
|
|
126
141
|
<p className="text-lg font-semibold">{fmt(pricingSummary.nrcTotal, pricingSummary.currencyCode)}</p>
|
|
127
142
|
</div>
|
|
128
143
|
<div>
|
|
129
|
-
<span className="text-xs text-muted-foreground">MRC Total</span>
|
|
144
|
+
<span className="text-xs text-muted-foreground">{t('cpq.wizard.steps.review.mrcTotal', 'MRC Total')}</span>
|
|
130
145
|
<p className="text-lg font-semibold">{fmt(pricingSummary.mrcTotal, pricingSummary.currencyCode)}</p>
|
|
131
146
|
</div>
|
|
132
147
|
{pricingSummary.discountTotal > 0 && (
|
|
133
148
|
<div>
|
|
134
|
-
<span className="text-xs text-muted-foreground">Discounts</span>
|
|
149
|
+
<span className="text-xs text-muted-foreground">{t('cpq.wizard.steps.review.discounts', 'Discounts')}</span>
|
|
135
150
|
<p className="text-sm">-{fmt(pricingSummary.discountTotal, pricingSummary.currencyCode)}</p>
|
|
136
151
|
</div>
|
|
137
152
|
)}
|
|
138
153
|
{pricingSummary.surchargeTotal > 0 && (
|
|
139
154
|
<div>
|
|
140
|
-
<span className="text-xs text-muted-foreground">Surcharges</span>
|
|
155
|
+
<span className="text-xs text-muted-foreground">{t('cpq.wizard.steps.review.surcharges', 'Surcharges')}</span>
|
|
141
156
|
<p className="text-sm">+{fmt(pricingSummary.surchargeTotal, pricingSummary.currencyCode)}</p>
|
|
142
157
|
</div>
|
|
143
158
|
)}
|
|
@@ -146,18 +161,18 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
146
161
|
|
|
147
162
|
{/* Line items */}
|
|
148
163
|
<div className="space-y-3">
|
|
149
|
-
<h4 className="text-sm font-medium">Items ({lines.length})</h4>
|
|
164
|
+
<h4 className="text-sm font-medium">{t('cpq.wizard.steps.review.items', 'Items ({count})', { count: lines.length })}</h4>
|
|
150
165
|
<div className="space-y-2">
|
|
151
166
|
{lines.map((line) => (
|
|
152
167
|
<div key={line.lineId} className="rounded-lg border p-3 space-y-2">
|
|
153
168
|
<div className="flex items-center justify-between">
|
|
154
169
|
<strong className="text-sm">{line.offeringName}</strong>
|
|
155
170
|
<span className="text-sm">
|
|
156
|
-
{fmt(line.nrcTotal, pricingSummary.currencyCode)
|
|
171
|
+
{t('cpq.wizard.linePricing', '{nrc} NRC / {mrc} MRC', { nrc: fmt(line.nrcTotal, pricingSummary.currencyCode), mrc: fmt(line.mrcTotal, pricingSummary.currencyCode) })}
|
|
157
172
|
</span>
|
|
158
173
|
</div>
|
|
159
174
|
{line.quantity > 1 && (
|
|
160
|
-
<span className="text-xs text-muted-foreground">Quantity: {line.quantity}</span>
|
|
175
|
+
<span className="text-xs text-muted-foreground">{t('cpq.wizard.steps.review.quantity', 'Quantity: {quantity}', { quantity: line.quantity })}</span>
|
|
161
176
|
)}
|
|
162
177
|
|
|
163
178
|
{showChargeBreakdown && line.charges?.length > 0 && (
|
|
@@ -165,9 +180,9 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
165
180
|
<table className="w-full text-xs">
|
|
166
181
|
<thead>
|
|
167
182
|
<tr className="border-b bg-muted/30">
|
|
168
|
-
<th className="px-3 py-1.5 text-left font-medium">Charge</th>
|
|
169
|
-
<th className="px-3 py-1.5 text-left font-medium">Type</th>
|
|
170
|
-
<th className="px-3 py-1.5 text-right font-medium">Price</th>
|
|
183
|
+
<th className="px-3 py-1.5 text-left font-medium">{t('cpq.wizard.steps.review.charges.charge', 'Charge')}</th>
|
|
184
|
+
<th className="px-3 py-1.5 text-left font-medium">{t('cpq.wizard.steps.review.charges.type', 'Type')}</th>
|
|
185
|
+
<th className="px-3 py-1.5 text-right font-medium">{t('cpq.wizard.steps.review.charges.price', 'Price')}</th>
|
|
171
186
|
</tr>
|
|
172
187
|
</thead>
|
|
173
188
|
<tbody>
|
|
@@ -175,9 +190,9 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
175
190
|
<React.Fragment key={charge.chargeCode}>
|
|
176
191
|
<tr className="border-b last:border-0">
|
|
177
192
|
<td className="px-3 py-1.5">{charge.chargeName}</td>
|
|
178
|
-
<td className="px-3 py-1.5 uppercase">{charge.chargeType}</td>
|
|
193
|
+
<td className="px-3 py-1.5 uppercase">{chargeTypeLabel(t, charge.chargeType)}</td>
|
|
179
194
|
<td className="px-3 py-1.5 text-right">
|
|
180
|
-
{charge.totalPrice != null ? fmt(charge.totalPrice, charge.currencyCode) :
|
|
195
|
+
{charge.totalPrice != null ? fmt(charge.totalPrice, charge.currencyCode) : t('cpq.lines.perUnit', '{price}/unit', { price: fmt(charge.unitPrice, charge.currencyCode) })}
|
|
181
196
|
</td>
|
|
182
197
|
</tr>
|
|
183
198
|
{showAdjustments && charge.adjustments?.map((adj) => (
|
|
@@ -202,7 +217,7 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
202
217
|
<div className="flex gap-2 justify-end pt-2">
|
|
203
218
|
{completedSteps.length > 0 && (
|
|
204
219
|
<button type="button" onClick={onBack} className="inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted">
|
|
205
|
-
Back
|
|
220
|
+
{t('cpq.common.back', 'Back')}
|
|
206
221
|
</button>
|
|
207
222
|
)}
|
|
208
223
|
<button
|
|
@@ -211,7 +226,7 @@ export function ReviewStep({ config, onComplete, onBack, completedSteps, quoteId
|
|
|
211
226
|
disabled={submitting}
|
|
212
227
|
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"
|
|
213
228
|
>
|
|
214
|
-
{submitting ? 'Submitting...' : submitLabel}
|
|
229
|
+
{submitting ? t('cpq.wizard.steps.review.submitting', 'Submitting...') : submitLabel}
|
|
215
230
|
</button>
|
|
216
231
|
</div>
|
|
217
232
|
</div>
|
|
@@ -9,6 +9,8 @@ import { InventorySelectStep } from './InventorySelectStep'
|
|
|
9
9
|
export function registerBuiltInStepTypes() {
|
|
10
10
|
registerStepType({
|
|
11
11
|
type: 'customer_select',
|
|
12
|
+
labelKey: 'cpq.wizard.steps.customerSelect.label',
|
|
13
|
+
descriptionKey: 'cpq.wizard.steps.customerSelect.description',
|
|
12
14
|
label: 'Customer Selection',
|
|
13
15
|
description: 'Search and select a customer for the quote.',
|
|
14
16
|
component: CustomerSelectStep,
|
|
@@ -16,6 +18,8 @@ export function registerBuiltInStepTypes() {
|
|
|
16
18
|
|
|
17
19
|
registerStepType({
|
|
18
20
|
type: 'offering_select',
|
|
21
|
+
labelKey: 'cpq.wizard.steps.offeringSelect.label',
|
|
22
|
+
descriptionKey: 'cpq.wizard.steps.offeringSelect.description',
|
|
19
23
|
label: 'Offering Selection',
|
|
20
24
|
description: 'Browse and select a product offering from the catalog.',
|
|
21
25
|
component: OfferingSelectStep,
|
|
@@ -23,6 +27,8 @@ export function registerBuiltInStepTypes() {
|
|
|
23
27
|
|
|
24
28
|
registerStepType({
|
|
25
29
|
type: 'product_configure',
|
|
30
|
+
labelKey: 'cpq.wizard.steps.productConfigure.label',
|
|
31
|
+
descriptionKey: 'cpq.wizard.steps.productConfigure.description',
|
|
26
32
|
label: 'Product Configuration',
|
|
27
33
|
description: 'Configure run-time attributes for the selected offering.',
|
|
28
34
|
component: ProductConfigureStep,
|
|
@@ -30,6 +36,8 @@ export function registerBuiltInStepTypes() {
|
|
|
30
36
|
|
|
31
37
|
registerStepType({
|
|
32
38
|
type: 'item_list',
|
|
39
|
+
labelKey: 'cpq.wizard.steps.itemList.label',
|
|
40
|
+
descriptionKey: 'cpq.wizard.steps.itemList.description',
|
|
33
41
|
label: 'Item List',
|
|
34
42
|
description: 'View and manage child items on the quote.',
|
|
35
43
|
component: ItemListStep,
|
|
@@ -37,6 +45,8 @@ export function registerBuiltInStepTypes() {
|
|
|
37
45
|
|
|
38
46
|
registerStepType({
|
|
39
47
|
type: 'review',
|
|
48
|
+
labelKey: 'cpq.wizard.steps.review.label',
|
|
49
|
+
descriptionKey: 'cpq.wizard.steps.review.description',
|
|
40
50
|
label: 'Review & Submit',
|
|
41
51
|
description: 'Review the quote summary and submit.',
|
|
42
52
|
component: ReviewStep,
|
|
@@ -44,6 +54,8 @@ export function registerBuiltInStepTypes() {
|
|
|
44
54
|
|
|
45
55
|
registerStepType({
|
|
46
56
|
type: 'inventory_select',
|
|
57
|
+
labelKey: 'cpq.wizard.steps.inventorySelect.label',
|
|
58
|
+
descriptionKey: 'cpq.wizard.steps.inventorySelect.description',
|
|
47
59
|
label: 'Inventory Selection',
|
|
48
60
|
description: 'Select existing customer inventory items (stub — pending inventory domain).',
|
|
49
61
|
component: InventorySelectStep,
|
|
@@ -97,6 +97,10 @@ export interface WizardStepProps {
|
|
|
97
97
|
export interface WizardStepType {
|
|
98
98
|
type: string
|
|
99
99
|
label: string
|
|
100
|
+
/** i18n key for `label`; the English `label` is the fallback. */
|
|
101
|
+
labelKey?: string
|
|
100
102
|
description: string
|
|
103
|
+
/** i18n key for `description`; the English `description` is the fallback. */
|
|
104
|
+
descriptionKey?: string
|
|
101
105
|
component: React.ComponentType<WizardStepProps>
|
|
102
106
|
}
|