@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
|
@@ -57,6 +57,7 @@ type Offering = {
|
|
|
57
57
|
const PAGE_SIZE = 50
|
|
58
58
|
|
|
59
59
|
function ChargePopover({ charge }: { charge: Charge }) {
|
|
60
|
+
const t = useT()
|
|
60
61
|
const [open, setOpen] = React.useState(false)
|
|
61
62
|
const variant = chargeTypeMap[charge.chargeType as ChargeType] ?? 'neutral'
|
|
62
63
|
return (
|
|
@@ -74,14 +75,14 @@ function ChargePopover({ charge }: { charge: Charge }) {
|
|
|
74
75
|
<div className="text-muted-foreground font-mono">{charge.code}</div>
|
|
75
76
|
{charge.description && <div className="text-muted-foreground">{charge.description}</div>}
|
|
76
77
|
<div className="flex items-center justify-between pt-1 border-t">
|
|
77
|
-
<span>Pricing: {charge.pricingMethod}</span>
|
|
78
|
+
<span>{t('cpq.offerings.list.chargePopover.pricing', 'Pricing: {method}', { method: charge.pricingMethod })}</span>
|
|
78
79
|
{charge.fixedPrice != null && (
|
|
79
80
|
<span className="font-medium">
|
|
80
81
|
{charge.currencyCode ?? 'USD'} {charge.fixedPrice}
|
|
81
82
|
</span>
|
|
82
83
|
)}
|
|
83
84
|
</div>
|
|
84
|
-
{!charge.isActive && <Tag variant="warning" dot>Inactive</Tag>}
|
|
85
|
+
{!charge.isActive && <Tag variant="warning" dot>{t('cpq.common.inactive', 'Inactive')}</Tag>}
|
|
85
86
|
<div className="absolute left-1/2 -translate-x-1/2 top-full w-2 h-2 bg-popover border-b border-r rotate-45 -mt-1" />
|
|
86
87
|
</div>
|
|
87
88
|
)}
|
|
@@ -207,7 +208,7 @@ export default function OfferingsListPage() {
|
|
|
207
208
|
})
|
|
208
209
|
if (!res.ok) {
|
|
209
210
|
const body = await res.json().catch(() => ({} as { error?: string }))
|
|
210
|
-
setError(body.error ??
|
|
211
|
+
setError(body.error ?? t('cpq.offerings.bulk.error.saveChargeFor', 'Failed to save charge for {name}', { name: offering.name }))
|
|
211
212
|
setSaving(false)
|
|
212
213
|
return
|
|
213
214
|
}
|
|
@@ -217,7 +218,7 @@ export default function OfferingsListPage() {
|
|
|
217
218
|
setBulkChargeTargets([])
|
|
218
219
|
data.reload()
|
|
219
220
|
} catch {
|
|
220
|
-
setError('Failed to save charges')
|
|
221
|
+
setError(t('cpq.offerings.bulk.error.saveCharges', 'Failed to save charges'))
|
|
221
222
|
} finally {
|
|
222
223
|
setSaving(false)
|
|
223
224
|
}
|
|
@@ -245,7 +246,7 @@ export default function OfferingsListPage() {
|
|
|
245
246
|
{row.original.name}
|
|
246
247
|
{row.original.offeringType === 'bundle' && (
|
|
247
248
|
<Tag variant="brand" className="ml-2 px-2 text-[10px]">
|
|
248
|
-
bundle
|
|
249
|
+
{t('cpq.offerings.list.tag.bundle', 'bundle')}
|
|
249
250
|
</Tag>
|
|
250
251
|
)}
|
|
251
252
|
</span>
|
|
@@ -319,30 +320,37 @@ export default function OfferingsListPage() {
|
|
|
319
320
|
</h4>
|
|
320
321
|
|
|
321
322
|
<Alert>
|
|
322
|
-
Bulk creation supports
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
{t('cpq.offerings.bulk.notice.supports', 'Bulk creation supports')}{' '}
|
|
324
|
+
<strong>{t('cpq.offerings.bulk.notice.flatOnly', 'flat pricing only')}</strong>
|
|
325
|
+
{t('cpq.offerings.bulk.notice.for', '. For')}{' '}
|
|
326
|
+
<em>{t('cpq.offerings.bulk.notice.perUnit', 'per unit')}</em>{' '}
|
|
327
|
+
{t('cpq.offerings.bulk.notice.or', 'or')}{' '}
|
|
328
|
+
<em>{t('cpq.offerings.bulk.notice.tiered', 'tiered')}</em>{' '}
|
|
329
|
+
{t(
|
|
330
|
+
'cpq.offerings.bulk.notice.rest',
|
|
331
|
+
"pricing, open each offering individually — those methods need a pricing table and a numeric attribute that lives on the offering's spec, which usually differs between offerings.",
|
|
332
|
+
)}
|
|
325
333
|
</Alert>
|
|
326
334
|
|
|
327
335
|
<div className="grid grid-cols-3 gap-3">
|
|
328
336
|
<div>
|
|
329
|
-
<label className="block text-xs font-medium mb-1">Code</label>
|
|
337
|
+
<label className="block text-xs font-medium mb-1">{t('cpq.offerings.bulk.fields.code', 'Code')}</label>
|
|
330
338
|
<Input
|
|
331
339
|
value={chargeForm.code}
|
|
332
340
|
onChange={(e) => setChargeForm({ ...chargeForm, code: e.target.value })}
|
|
333
|
-
placeholder=
|
|
341
|
+
placeholder={t('cpq.offerings.bulk.fields.codePlaceholder', 'e.g. mrc-monthly')}
|
|
334
342
|
/>
|
|
335
343
|
</div>
|
|
336
344
|
<div>
|
|
337
|
-
<label className="block text-xs font-medium mb-1">Name</label>
|
|
345
|
+
<label className="block text-xs font-medium mb-1">{t('cpq.offerings.bulk.fields.name', 'Name')}</label>
|
|
338
346
|
<Input
|
|
339
347
|
value={chargeForm.name}
|
|
340
348
|
onChange={(e) => setChargeForm({ ...chargeForm, name: e.target.value })}
|
|
341
|
-
placeholder=
|
|
349
|
+
placeholder={t('cpq.offerings.bulk.fields.namePlaceholder', 'e.g. Monthly Recurring')}
|
|
342
350
|
/>
|
|
343
351
|
</div>
|
|
344
352
|
<div>
|
|
345
|
-
<label className="block text-xs font-medium mb-1">Type</label>
|
|
353
|
+
<label className="block text-xs font-medium mb-1">{t('cpq.offerings.bulk.fields.type', 'Type')}</label>
|
|
346
354
|
<Select
|
|
347
355
|
value={chargeForm.chargeType}
|
|
348
356
|
onValueChange={(value) => setChargeForm({ ...chargeForm, chargeType: value })}
|
|
@@ -351,26 +359,26 @@ export default function OfferingsListPage() {
|
|
|
351
359
|
<SelectValue />
|
|
352
360
|
</SelectTrigger>
|
|
353
361
|
<SelectContent>
|
|
354
|
-
<SelectItem value="nrc">NRC (Non-Recurring)</SelectItem>
|
|
355
|
-
<SelectItem value="mrc">MRC (Monthly Recurring)</SelectItem>
|
|
356
|
-
<SelectItem value="usage">Usage</SelectItem>
|
|
362
|
+
<SelectItem value="nrc">{t('cpq.offerings.chargeType.nrc', 'NRC (Non-Recurring)')}</SelectItem>
|
|
363
|
+
<SelectItem value="mrc">{t('cpq.offerings.chargeType.mrc', 'MRC (Monthly Recurring)')}</SelectItem>
|
|
364
|
+
<SelectItem value="usage">{t('cpq.offerings.chargeType.usage', 'Usage')}</SelectItem>
|
|
357
365
|
</SelectContent>
|
|
358
366
|
</Select>
|
|
359
367
|
</div>
|
|
360
368
|
</div>
|
|
361
369
|
|
|
362
370
|
<div>
|
|
363
|
-
<label className="block text-xs font-medium mb-1">Description</label>
|
|
371
|
+
<label className="block text-xs font-medium mb-1">{t('cpq.offerings.bulk.fields.description', 'Description')}</label>
|
|
364
372
|
<Input
|
|
365
373
|
value={chargeForm.description}
|
|
366
374
|
onChange={(e) => setChargeForm({ ...chargeForm, description: e.target.value })}
|
|
367
|
-
placeholder=
|
|
375
|
+
placeholder={t('cpq.offerings.bulk.fields.descriptionPlaceholder', 'User-facing charge description')}
|
|
368
376
|
/>
|
|
369
377
|
</div>
|
|
370
378
|
|
|
371
379
|
<div className="space-y-2">
|
|
372
380
|
<div className="flex items-center gap-3">
|
|
373
|
-
<label className="text-xs font-medium">Currency</label>
|
|
381
|
+
<label className="text-xs font-medium">{t('cpq.offerings.bulk.fields.currency', 'Currency')}</label>
|
|
374
382
|
<Input
|
|
375
383
|
value={chargeCurrency}
|
|
376
384
|
onChange={(e) => setChargeCurrency(e.target.value.toUpperCase())}
|
|
@@ -382,8 +390,8 @@ export default function OfferingsListPage() {
|
|
|
382
390
|
<table className="w-full text-sm">
|
|
383
391
|
<thead>
|
|
384
392
|
<tr className="border-b bg-muted/30">
|
|
385
|
-
<th className="px-3 py-2 text-left font-medium text-xs">Offering</th>
|
|
386
|
-
<th className="px-3 py-2 text-left font-medium text-xs">Price ({chargeCurrency})</th>
|
|
393
|
+
<th className="px-3 py-2 text-left font-medium text-xs">{t('cpq.offerings.bulk.table.offering', 'Offering')}</th>
|
|
394
|
+
<th className="px-3 py-2 text-left font-medium text-xs">{t('cpq.offerings.bulk.table.price', 'Price ({currency})', { currency: chargeCurrency })}</th>
|
|
387
395
|
</tr>
|
|
388
396
|
</thead>
|
|
389
397
|
<tbody>
|
|
@@ -416,11 +424,13 @@ export default function OfferingsListPage() {
|
|
|
416
424
|
disabled={saving || !chargeForm.code || !chargeForm.name}
|
|
417
425
|
>
|
|
418
426
|
{saving
|
|
419
|
-
? 'Saving...'
|
|
420
|
-
:
|
|
427
|
+
? t('cpq.common.saving', 'Saving...')
|
|
428
|
+
: bulkChargeTargets.length > 1
|
|
429
|
+
? t('cpq.offerings.bulk.submitPlural', 'Create Charge for {count} Offerings', { count: bulkChargeTargets.length })
|
|
430
|
+
: t('cpq.offerings.bulk.submitSingular', 'Create Charge for {count} Offering', { count: bulkChargeTargets.length })}
|
|
421
431
|
</Button>
|
|
422
432
|
<Button type="button" variant="outline" onClick={() => setBulkChargeOpen(false)}>
|
|
423
|
-
Cancel
|
|
433
|
+
{t('cpq.common.cancel', 'Cancel')}
|
|
424
434
|
</Button>
|
|
425
435
|
</div>
|
|
426
436
|
</div>
|
|
@@ -17,7 +17,7 @@ import { PricingSummaryCard, type PricingSummaryView } from '../../../../compone
|
|
|
17
17
|
import { ArcLineDiff, LineActionBadge } from '../../../../components/ArcLineDiff'
|
|
18
18
|
import { arcRecurringDelta } from '../../../../lib/quoteSummaryView'
|
|
19
19
|
import { isArcCancelledLine } from '../../../../services/arcLineRemoval'
|
|
20
|
-
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
20
|
+
import { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'
|
|
21
21
|
|
|
22
22
|
// ─── Types ───────────────────────────────────────────────────────
|
|
23
23
|
|
|
@@ -95,6 +95,19 @@ const ARC_BADGE_VARIANTS: Record<string, 'brand' | 'success' | 'error'> = {
|
|
|
95
95
|
cancel: 'error',
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function quoteTypeLabel(t: TranslateFn, quoteType: string): string {
|
|
99
|
+
switch (quoteType) {
|
|
100
|
+
case 'amend':
|
|
101
|
+
return t('cpq.quotes.type.amend', 'amend')
|
|
102
|
+
case 'renew':
|
|
103
|
+
return t('cpq.quotes.type.renew', 'renew')
|
|
104
|
+
case 'cancel':
|
|
105
|
+
return t('cpq.quotes.type.cancel', 'cancel')
|
|
106
|
+
default:
|
|
107
|
+
return quoteType
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
98
111
|
|
|
99
112
|
// ─── Constants ───────────────────────────────────────────────────
|
|
100
113
|
|
|
@@ -102,10 +115,10 @@ function fmt(amount: number, currency: string): string {
|
|
|
102
115
|
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)
|
|
103
116
|
}
|
|
104
117
|
|
|
105
|
-
async function apiJson<T>(url: string, init?: RequestInit): Promise<T> {
|
|
118
|
+
async function apiJson<T>(t: TranslateFn, url: string, init?: RequestInit): Promise<T> {
|
|
106
119
|
const res = await fetch(url, init)
|
|
107
120
|
const json = await res.json()
|
|
108
|
-
if (!res.ok) throw new Error(json?.error ??
|
|
121
|
+
if (!res.ok) throw new Error(json?.error ?? t('cpq.common.requestFailed', 'Request failed ({status})', { status: res.status }))
|
|
109
122
|
return json as T
|
|
110
123
|
}
|
|
111
124
|
|
|
@@ -113,6 +126,7 @@ async function apiJson<T>(url: string, init?: RequestInit): Promise<T> {
|
|
|
113
126
|
|
|
114
127
|
export default function CpqOrderDetailPage(props: { params?: { id?: string } }) {
|
|
115
128
|
const router = useRouter()
|
|
129
|
+
const t = useT()
|
|
116
130
|
const urlParams = useParams()
|
|
117
131
|
const cpqOrderId = (props.params?.id ?? urlParams?.id) as string
|
|
118
132
|
|
|
@@ -151,8 +165,8 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
151
165
|
.then(async (d) => {
|
|
152
166
|
const targets: Array<{ subscriptionId: string }> = d?.items ?? []
|
|
153
167
|
const fetched = await Promise.all(
|
|
154
|
-
targets.map((
|
|
155
|
-
fetch(`/api/cpq/inventory/subscriptions?id=${
|
|
168
|
+
targets.map((target) =>
|
|
169
|
+
fetch(`/api/cpq/inventory/subscriptions?id=${target.subscriptionId}`)
|
|
156
170
|
.then((r) => (r.ok ? r.json() : null))
|
|
157
171
|
.catch(() => null),
|
|
158
172
|
),
|
|
@@ -181,7 +195,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
181
195
|
let cancelled = false
|
|
182
196
|
async function load() {
|
|
183
197
|
try {
|
|
184
|
-
const result = await apiJson<OrderResult>(`/api/cpq/orders/${cpqOrderId}`)
|
|
198
|
+
const result = await apiJson<OrderResult>(t, `/api/cpq/orders/${cpqOrderId}`)
|
|
185
199
|
if (!cancelled) {
|
|
186
200
|
setOrder(result)
|
|
187
201
|
fetch(`/api/customers/companies?id=${result.customerId}&pageSize=1`)
|
|
@@ -204,14 +218,14 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
204
218
|
}
|
|
205
219
|
load()
|
|
206
220
|
return () => { cancelled = true }
|
|
207
|
-
}, [cpqOrderId, fetchLinkedSubscriptions])
|
|
221
|
+
}, [cpqOrderId, fetchLinkedSubscriptions, t])
|
|
208
222
|
|
|
209
223
|
const activateOrder = async () => {
|
|
210
224
|
if (!order) return
|
|
211
225
|
try {
|
|
212
226
|
setActivating(true)
|
|
213
227
|
setError(null)
|
|
214
|
-
const result = await apiJson<OrderResult>(`/api/cpq/orders/${order.id}/activate`, {
|
|
228
|
+
const result = await apiJson<OrderResult>(t, `/api/cpq/orders/${order.id}/activate`, {
|
|
215
229
|
method: 'POST',
|
|
216
230
|
headers: { 'Content-Type': 'application/json' },
|
|
217
231
|
body: JSON.stringify({}),
|
|
@@ -229,7 +243,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
229
243
|
if (!order) return
|
|
230
244
|
try {
|
|
231
245
|
setError(null)
|
|
232
|
-
const result = await apiJson<OrderResult>(`/api/cpq/orders/${order.id}/status`, {
|
|
246
|
+
const result = await apiJson<OrderResult>(t, `/api/cpq/orders/${order.id}/status`, {
|
|
233
247
|
method: 'POST',
|
|
234
248
|
headers: { 'Content-Type': 'application/json' },
|
|
235
249
|
body: JSON.stringify({ targetStatus: 'cancelled' }),
|
|
@@ -249,7 +263,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
249
263
|
if (loading) {
|
|
250
264
|
return (
|
|
251
265
|
<div className="flex items-center gap-2 py-12 justify-center text-sm text-muted-foreground">
|
|
252
|
-
<Spinner /> Loading…
|
|
266
|
+
<Spinner /> {t('cpq.orders.detail.loading', 'Loading…')}
|
|
253
267
|
</div>
|
|
254
268
|
)
|
|
255
269
|
}
|
|
@@ -257,9 +271,9 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
257
271
|
if (!order) {
|
|
258
272
|
return (
|
|
259
273
|
<div className="space-y-4">
|
|
260
|
-
<button onClick={() => router.push('/backend/cpq/orders')} className="text-sm text-muted-foreground hover:text-foreground"
|
|
274
|
+
<button onClick={() => router.push('/backend/cpq/orders')} className="text-sm text-muted-foreground hover:text-foreground">{t('cpq.orders.detail.backToOrders', '← Back to Orders')}</button>
|
|
261
275
|
{error && <ErrorBanner message={error} />}
|
|
262
|
-
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">Order not found
|
|
276
|
+
<div className="rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground">{t('cpq.orders.detail.notFound', 'Order not found.')}</div>
|
|
263
277
|
</div>
|
|
264
278
|
)
|
|
265
279
|
}
|
|
@@ -286,8 +300,8 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
286
300
|
<FormHeader
|
|
287
301
|
mode="detail"
|
|
288
302
|
backHref="/backend/cpq/orders"
|
|
289
|
-
backLabel=
|
|
290
|
-
entityTypeLabel=
|
|
303
|
+
backLabel={t('cpq.orders.detail.backLabel', 'Back to CPQ Orders')}
|
|
304
|
+
entityTypeLabel={t('cpq.orders.detail.entityType', 'CPQ Order')}
|
|
291
305
|
title={order.orderNumber || order.orderId.slice(0, 8)}
|
|
292
306
|
statusBadge={
|
|
293
307
|
<div className="flex flex-wrap items-center gap-2 pt-1">
|
|
@@ -296,7 +310,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
296
310
|
</Tag>
|
|
297
311
|
{order.quoteType && order.quoteType !== 'new' && (
|
|
298
312
|
<Tag variant={ARC_BADGE_VARIANTS[order.quoteType] ?? 'neutral'} className="uppercase">
|
|
299
|
-
{order.quoteType}
|
|
313
|
+
{quoteTypeLabel(t, order.quoteType)}
|
|
300
314
|
</Tag>
|
|
301
315
|
)}
|
|
302
316
|
</div>
|
|
@@ -309,7 +323,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
309
323
|
onClick={() => router.push(`/backend/sales/orders/${order.orderId}`)}
|
|
310
324
|
>
|
|
311
325
|
<ExternalLink className="h-3.5 w-3.5" />
|
|
312
|
-
View Sales Order
|
|
326
|
+
{t('cpq.orders.detail.viewSalesOrder', 'View Sales Order')}
|
|
313
327
|
</Button>
|
|
314
328
|
{order.sourceQuoteId && (
|
|
315
329
|
<Button
|
|
@@ -317,19 +331,19 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
317
331
|
variant="outline"
|
|
318
332
|
onClick={() => router.push(`/backend/cpq/quotes/${order.sourceQuoteId}`)}
|
|
319
333
|
>
|
|
320
|
-
Source Quote
|
|
334
|
+
{t('cpq.orders.detail.sourceQuote', 'Source Quote')}
|
|
321
335
|
</Button>
|
|
322
336
|
)}
|
|
323
337
|
{canActivate && (
|
|
324
338
|
<Button type="button" onClick={activateOrder} disabled={activating}>
|
|
325
339
|
{activating ? <Spinner /> : <Play className="h-4 w-4" />}
|
|
326
|
-
Activate
|
|
340
|
+
{t('cpq.orders.detail.activate', 'Activate')}
|
|
327
341
|
</Button>
|
|
328
342
|
)}
|
|
329
343
|
{canCancel && (
|
|
330
344
|
<Button type="button" variant="destructive-outline" onClick={cancelOrder}>
|
|
331
345
|
<X className="h-4 w-4" />
|
|
332
|
-
Cancel
|
|
346
|
+
{t('cpq.common.cancel', 'Cancel')}
|
|
333
347
|
</Button>
|
|
334
348
|
)}
|
|
335
349
|
</>
|
|
@@ -348,7 +362,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
348
362
|
path={['draft', 'pending_activation', 'active', 'fulfilled']}
|
|
349
363
|
terminals={['cancelled']}
|
|
350
364
|
statusMap={orderCpqStatusMap}
|
|
351
|
-
ariaLabel=
|
|
365
|
+
ariaLabel={t('cpq.orders.detail.statusPath', 'Order status path')}
|
|
352
366
|
/>
|
|
353
367
|
</div>
|
|
354
368
|
|
|
@@ -368,7 +382,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
368
382
|
<div className="flex-1 min-w-0 space-y-1">
|
|
369
383
|
<div className="flex items-center gap-2 flex-wrap">
|
|
370
384
|
<span className="text-[10px] font-medium uppercase tracking-wide text-muted-foreground">
|
|
371
|
-
Linked Subscription
|
|
385
|
+
{t('cpq.orders.detail.linkedSubscription', 'Linked Subscription')}
|
|
372
386
|
</span>
|
|
373
387
|
<Tag
|
|
374
388
|
variant={sub.status === 'active' ? 'success' : 'info'}
|
|
@@ -384,7 +398,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
384
398
|
</div>
|
|
385
399
|
{order.activatedAt && (
|
|
386
400
|
<div className="text-xs text-muted-foreground">
|
|
387
|
-
Activated on {new Date(order.activatedAt).toLocaleString()}
|
|
401
|
+
{t('cpq.orders.detail.activatedOn', 'Activated on {date}', { date: new Date(order.activatedAt).toLocaleString() })}
|
|
388
402
|
</div>
|
|
389
403
|
)}
|
|
390
404
|
</div>
|
|
@@ -394,7 +408,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
394
408
|
onClick={() => router.push(`/backend/cpq/inventory/subscriptions/${sub.id}`)}
|
|
395
409
|
>
|
|
396
410
|
<ArrowRight className="h-3.5 w-3.5" />
|
|
397
|
-
Open
|
|
411
|
+
{t('cpq.orders.actions.open', 'Open')}
|
|
398
412
|
</Button>
|
|
399
413
|
</div>
|
|
400
414
|
))}
|
|
@@ -403,7 +417,7 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
403
417
|
|
|
404
418
|
{/* Meta cards — secondary reference info, matches the quote layout. */}
|
|
405
419
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
406
|
-
<MetaCard label=
|
|
420
|
+
<MetaCard label={t('cpq.orders.detail.meta.customer', 'Customer')}>
|
|
407
421
|
{customerName ? (
|
|
408
422
|
<span className="font-medium">{customerName}</span>
|
|
409
423
|
) : (
|
|
@@ -412,10 +426,10 @@ export default function CpqOrderDetailPage(props: { params?: { id?: string } })
|
|
|
412
426
|
</span>
|
|
413
427
|
)}
|
|
414
428
|
</MetaCard>
|
|
415
|
-
<MetaCard label=
|
|
429
|
+
<MetaCard label={t('cpq.orders.detail.meta.created', 'Created')}>
|
|
416
430
|
<span className="font-medium">{new Date(order.createdAt).toLocaleDateString()}</span>
|
|
417
431
|
</MetaCard>
|
|
418
|
-
<MetaCard label=
|
|
432
|
+
<MetaCard label={t('cpq.orders.detail.meta.currency', 'Currency')}>
|
|
419
433
|
<span className="font-medium">{currency}</span>
|
|
420
434
|
</MetaCard>
|
|
421
435
|
</div>
|
|
@@ -472,6 +486,7 @@ function OrderLineTree({
|
|
|
472
486
|
onToggleLine: (id: string) => void
|
|
473
487
|
onTogglePricingDetail: (lineIdx: number, chargeIdx: number) => void
|
|
474
488
|
}) {
|
|
489
|
+
const t = useT()
|
|
475
490
|
const rootLines = lines.filter((l) => !l.parentLineId)
|
|
476
491
|
const childrenByParent = new Map<string, OrderLineResult[]>()
|
|
477
492
|
for (const line of lines) {
|
|
@@ -489,10 +504,10 @@ function OrderLineTree({
|
|
|
489
504
|
|
|
490
505
|
return (
|
|
491
506
|
<div>
|
|
492
|
-
<h3 className="text-sm font-medium mb-2 px-1">Line Items ({lines.length})</h3>
|
|
507
|
+
<h3 className="text-sm font-medium mb-2 px-1">{t('cpq.orders.detail.lineItems', 'Line Items ({count})', { count: lines.length })}</h3>
|
|
493
508
|
{lines.length === 0 ? (
|
|
494
509
|
<div className="rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground">
|
|
495
|
-
No line items.
|
|
510
|
+
{t('cpq.orders.detail.noLineItems', 'No line items.')}
|
|
496
511
|
</div>
|
|
497
512
|
) : (
|
|
498
513
|
<div className="rounded-lg border bg-card divide-y">
|
|
@@ -519,7 +534,7 @@ function OrderLineTree({
|
|
|
519
534
|
{isBundle && isExpanded && children.length > 0 && (
|
|
520
535
|
<div className="border-t border-dashed border-brand-violet/30 bg-brand-violet/5">
|
|
521
536
|
<div className="px-6 py-1.5">
|
|
522
|
-
<span className="text-[10px] font-medium text-brand-violet uppercase tracking-wider">Bundle Components ({children.length})</span>
|
|
537
|
+
<span className="text-[10px] font-medium text-brand-violet uppercase tracking-wider">{t('cpq.lines.bundleComponents', 'Bundle Components ({count})', { count: children.length })}</span>
|
|
523
538
|
</div>
|
|
524
539
|
{children.map((child) => {
|
|
525
540
|
const childIdx = indexOf.get(child.lineId) ?? 0
|
|
@@ -582,7 +597,7 @@ function OrderLineRow({ line, lineIdx, quoteType, currency, isExpanded, isBundle
|
|
|
582
597
|
<div className="flex items-center gap-2">
|
|
583
598
|
{indent > 0 && <span className="text-muted-foreground text-xs">└</span>}
|
|
584
599
|
{isBundle && (
|
|
585
|
-
<Tag variant="brand" className="px-1.5 text-xs">bundle</Tag>
|
|
600
|
+
<Tag variant="brand" className="px-1.5 text-xs">{t('cpq.lines.bundle', 'bundle')}</Tag>
|
|
586
601
|
)}
|
|
587
602
|
<span className={`font-medium text-sm ${isRemoved ? 'text-muted-foreground line-through' : ''}`}>{line.offeringName}</span>
|
|
588
603
|
{line.quantity > 1 && <span className="rounded bg-muted px-1.5 py-0.5 text-xs font-medium">×{line.quantity}</span>}
|
|
@@ -591,19 +606,25 @@ function OrderLineRow({ line, lineIdx, quoteType, currency, isExpanded, isBundle
|
|
|
591
606
|
label={isRemoved ? t('cpq.quotes.arc.lineRemoved', 'remove') : undefined}
|
|
592
607
|
title={isRemoved ? t('cpq.quotes.arc.lineRemovedHint', 'This item is cancelled on the subscription when the order is activated') : undefined}
|
|
593
608
|
/>
|
|
594
|
-
{isBundle &&
|
|
609
|
+
{isBundle && (
|
|
610
|
+
<span className="text-xs text-muted-foreground">
|
|
611
|
+
{childCount !== 1
|
|
612
|
+
? t('cpq.lines.componentCount.other', '{count} components', { count: childCount })
|
|
613
|
+
: t('cpq.lines.componentCount.one', '{count} component', { count: childCount })}
|
|
614
|
+
</span>
|
|
615
|
+
)}
|
|
595
616
|
</div>
|
|
596
617
|
</div>
|
|
597
618
|
|
|
598
619
|
<div className="shrink-0 text-right text-xs space-y-0.5">
|
|
599
620
|
{isRemoved && line.arcSource && line.arcSource.mrcAmount > 0 && (
|
|
600
621
|
<div>
|
|
601
|
-
<span className="text-muted-foreground">MRC </span>
|
|
622
|
+
<span className="text-muted-foreground">{t('cpq.lines.mrc', 'MRC')} </span>
|
|
602
623
|
<span className="font-mono text-muted-foreground line-through">{fmt(line.arcSource.mrcAmount, currency)}</span>
|
|
603
624
|
</div>
|
|
604
625
|
)}
|
|
605
|
-
{line.nrcTotal > 0 && <div><span className="text-muted-foreground">NRC </span><span className="font-mono font-medium">{fmt(line.nrcTotal, currency)}</span></div>}
|
|
606
|
-
{line.mrcTotal > 0 && <div><span className="text-muted-foreground">MRC </span><span className="font-mono font-medium">{fmt(line.mrcTotal, currency)}</span></div>}
|
|
626
|
+
{line.nrcTotal > 0 && <div><span className="text-muted-foreground">{t('cpq.lines.nrc', 'NRC')} </span><span className="font-mono font-medium">{fmt(line.nrcTotal, currency)}</span></div>}
|
|
627
|
+
{line.mrcTotal > 0 && <div><span className="text-muted-foreground">{t('cpq.lines.mrc', 'MRC')} </span><span className="font-mono font-medium">{fmt(line.mrcTotal, currency)}</span></div>}
|
|
607
628
|
</div>
|
|
608
629
|
</div>
|
|
609
630
|
|
|
@@ -627,16 +648,17 @@ function OrderLineRow({ line, lineIdx, quoteType, currency, isExpanded, isBundle
|
|
|
627
648
|
{isEmpty ? (
|
|
628
649
|
<div className="text-xs text-muted-foreground space-y-1">
|
|
629
650
|
<div>
|
|
630
|
-
No detailed breakdown for this line.
|
|
631
|
-
{mirroredFromItemId && '
|
|
651
|
+
{t('cpq.orders.detail.noBreakdown', 'No detailed breakdown for this line.')}
|
|
652
|
+
{mirroredFromItemId && ' '}
|
|
653
|
+
{mirroredFromItemId && t('cpq.orders.detail.mirroredItem', 'Mirrored from existing subscription item.')}
|
|
632
654
|
</div>
|
|
633
655
|
{(line.nrcTotal > 0 || line.mrcTotal > 0) && (
|
|
634
656
|
<div className="flex gap-4">
|
|
635
657
|
{line.nrcTotal > 0 && (
|
|
636
|
-
<span>NRC <span className="font-mono font-medium text-foreground">{fmt(line.nrcTotal, currency)}</span></span>
|
|
658
|
+
<span>{t('cpq.lines.nrc', 'NRC')} <span className="font-mono font-medium text-foreground">{fmt(line.nrcTotal, currency)}</span></span>
|
|
637
659
|
)}
|
|
638
660
|
{line.mrcTotal > 0 && (
|
|
639
|
-
<span>MRC <span className="font-mono font-medium text-foreground">{fmt(line.mrcTotal, currency)}</span></span>
|
|
661
|
+
<span>{t('cpq.lines.mrc', 'MRC')} <span className="font-mono font-medium text-foreground">{fmt(line.mrcTotal, currency)}</span></span>
|
|
640
662
|
)}
|
|
641
663
|
</div>
|
|
642
664
|
)}
|
|
@@ -645,7 +667,7 @@ function OrderLineRow({ line, lineIdx, quoteType, currency, isExpanded, isBundle
|
|
|
645
667
|
<>
|
|
646
668
|
{hasConfig && (
|
|
647
669
|
<div>
|
|
648
|
-
<p className="text-xs font-medium text-muted-foreground mb-1">Configuration</p>
|
|
670
|
+
<p className="text-xs font-medium text-muted-foreground mb-1">{t('cpq.lines.configuration', 'Configuration')}</p>
|
|
649
671
|
<div className="grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1">
|
|
650
672
|
{visibleConfigEntries.map(([key, value]) => (
|
|
651
673
|
<div key={key} className="text-xs">
|
|
@@ -668,9 +690,9 @@ function OrderLineRow({ line, lineIdx, quoteType, currency, isExpanded, isBundle
|
|
|
668
690
|
|
|
669
691
|
{hasTerm && (
|
|
670
692
|
<div className="flex gap-4 text-xs text-muted-foreground">
|
|
671
|
-
{line.startDate && <span>Start: {new Date(line.startDate).toLocaleDateString()}</span>}
|
|
672
|
-
{line.termMonths && <span>Term: {line.termMonths}
|
|
673
|
-
{line.endDate && <span>End: {new Date(line.endDate).toLocaleDateString()}</span>}
|
|
693
|
+
{line.startDate && <span>{t('cpq.lines.term.start', 'Start: {date}', { date: new Date(line.startDate).toLocaleDateString() })}</span>}
|
|
694
|
+
{line.termMonths && <span>{t('cpq.lines.term.months', 'Term: {months}mo', { months: line.termMonths })}</span>}
|
|
695
|
+
{line.endDate && <span>{t('cpq.lines.term.end', 'End: {date}', { date: new Date(line.endDate).toLocaleDateString() })}</span>}
|
|
674
696
|
</div>
|
|
675
697
|
)}
|
|
676
698
|
|
|
@@ -695,6 +717,7 @@ function Spinner() {
|
|
|
695
717
|
}
|
|
696
718
|
|
|
697
719
|
function ErrorBanner({ message, onDismiss }: { message: string; onDismiss?: () => void }) {
|
|
720
|
+
const t = useT()
|
|
698
721
|
return (
|
|
699
722
|
<Alert variant="destructive" className="flex items-start gap-2">
|
|
700
723
|
<span className="flex-1">{message}</span>
|
|
@@ -702,7 +725,7 @@ function ErrorBanner({ message, onDismiss }: { message: string; onDismiss?: () =
|
|
|
702
725
|
<button
|
|
703
726
|
onClick={onDismiss}
|
|
704
727
|
className="text-destructive/70 hover:text-destructive text-sm font-bold"
|
|
705
|
-
aria-label=
|
|
728
|
+
aria-label={t('cpq.common.dismiss', 'Dismiss')}
|
|
706
729
|
>
|
|
707
730
|
×
|
|
708
731
|
</button>
|
|
@@ -57,13 +57,13 @@ export default function CpqOrdersListPage() {
|
|
|
57
57
|
|
|
58
58
|
const statusOptions = React.useMemo(
|
|
59
59
|
() => [
|
|
60
|
-
{ value: 'draft', label: 'Draft' },
|
|
61
|
-
{ value: 'pending_activation', label: 'Pending Activation' },
|
|
62
|
-
{ value: 'active', label: 'Active' },
|
|
63
|
-
{ value: 'fulfilled', label: 'Fulfilled' },
|
|
64
|
-
{ value: 'cancelled', label: 'Cancelled' },
|
|
60
|
+
{ value: 'draft', label: t('cpq.orders.status.draft', 'Draft') },
|
|
61
|
+
{ value: 'pending_activation', label: t('cpq.orders.status.pending_activation', 'Pending Activation') },
|
|
62
|
+
{ value: 'active', label: t('cpq.orders.status.active', 'Active') },
|
|
63
|
+
{ value: 'fulfilled', label: t('cpq.orders.status.fulfilled', 'Fulfilled') },
|
|
64
|
+
{ value: 'cancelled', label: t('cpq.orders.status.cancelled', 'Cancelled') },
|
|
65
65
|
],
|
|
66
|
-
[],
|
|
66
|
+
[t],
|
|
67
67
|
)
|
|
68
68
|
|
|
69
69
|
const filters = React.useMemo<FilterDef[]>(
|