@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.
Files changed (164) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
  3. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
  4. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
  5. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
  6. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
  7. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
  8. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
  9. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
  10. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +2 -2
  11. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
  12. package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
  13. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
  14. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +154 -178
  15. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  16. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
  17. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
  18. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
  19. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  20. package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
  21. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
  22. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +69 -66
  23. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  24. package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
  25. package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
  26. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
  27. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
  28. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
  29. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
  30. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
  31. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
  32. package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
  33. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
  34. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
  35. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  36. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
  37. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
  38. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
  39. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
  40. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
  41. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
  42. package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
  43. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
  44. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
  45. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
  46. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +6 -3
  47. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +2 -2
  48. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +129 -94
  49. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +2 -2
  50. package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
  51. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
  52. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
  53. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
  54. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
  55. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  56. package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
  57. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
  58. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
  59. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
  60. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
  61. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
  62. package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
  63. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
  64. package/dist/modules/cpq/components/ArcLineDiff.js +2 -2
  65. package/dist/modules/cpq/components/ArcLineDiff.js.map +2 -2
  66. package/dist/modules/cpq/components/ChargeBreakdownTable.js +4 -10
  67. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +2 -2
  68. package/dist/modules/cpq/components/PriceWaterfall.js +3 -1
  69. package/dist/modules/cpq/components/PriceWaterfall.js.map +2 -2
  70. package/dist/modules/cpq/components/PricingSummaryCard.js +1 -6
  71. package/dist/modules/cpq/components/PricingSummaryCard.js.map +2 -2
  72. package/dist/modules/cpq/components/StatusPath.js +5 -3
  73. package/dist/modules/cpq/components/StatusPath.js.map +2 -2
  74. package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
  75. package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
  76. package/dist/modules/cpq/i18n/de.json +1220 -0
  77. package/dist/modules/cpq/i18n/en.json +1220 -0
  78. package/dist/modules/cpq/i18n/es.json +1220 -0
  79. package/dist/modules/cpq/i18n/pl.json +1220 -0
  80. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
  81. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
  82. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
  83. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
  84. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
  85. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
  86. package/dist/modules/cpq/widgets/injection-table.js +2 -2
  87. package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
  88. package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
  89. package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
  90. package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
  91. package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
  92. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
  93. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
  94. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
  95. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
  96. package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
  97. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
  98. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
  99. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
  100. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
  101. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
  102. package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
  103. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
  104. package/dist/modules/cpq/workflows/steps/index.js +12 -0
  105. package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
  106. package/package.json +1 -1
  107. package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
  108. package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
  109. package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
  110. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
  111. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
  112. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
  113. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
  114. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +2 -2
  115. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
  116. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
  117. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +134 -112
  118. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
  119. package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
  120. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +66 -43
  121. package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
  122. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
  123. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
  124. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
  125. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
  126. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
  127. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
  128. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
  129. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
  130. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  131. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
  132. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +3 -3
  133. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +134 -89
  134. package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
  135. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
  136. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
  137. package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
  138. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
  139. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
  140. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  141. package/src/modules/cpq/components/ArcLineDiff.tsx +2 -2
  142. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +4 -2
  143. package/src/modules/cpq/components/PriceWaterfall.tsx +4 -2
  144. package/src/modules/cpq/components/PricingSummaryCard.tsx +1 -1
  145. package/src/modules/cpq/components/StatusPath.tsx +7 -4
  146. package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
  147. package/src/modules/cpq/i18n/de.json +1220 -0
  148. package/src/modules/cpq/i18n/en.json +1220 -0
  149. package/src/modules/cpq/i18n/es.json +1220 -0
  150. package/src/modules/cpq/i18n/pl.json +1220 -0
  151. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
  152. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
  153. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
  154. package/src/modules/cpq/widgets/injection-table.ts +8 -2
  155. package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
  156. package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
  157. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
  158. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
  159. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
  160. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
  161. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
  162. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
  163. package/src/modules/cpq/workflows/steps/index.ts +12 -0
  164. package/src/modules/cpq/workflows/types.ts +4 -0
@@ -70,8 +70,8 @@ type CustomerPickerProps = {
70
70
  error: string | null
71
71
  }
72
72
 
73
- function getCustomerLabel(c: Customer) {
74
- return c.display_name || c.displayName || c.name || c.companyName || 'Unnamed'
73
+ function getCustomerLabel(c: Customer, unnamedLabel: string) {
74
+ return c.display_name || c.displayName || c.name || c.companyName || unnamedLabel
75
75
  }
76
76
 
77
77
  function getCustomerEmail(c: Customer) {
@@ -79,6 +79,8 @@ function getCustomerEmail(c: Customer) {
79
79
  }
80
80
 
81
81
  function CustomerPickerModal({ open, onClose, onSelect, creating, error }: CustomerPickerProps) {
82
+ const t = useT()
83
+ const unnamedLabel = t('cpq.quotes.customerPicker.unnamed', 'Unnamed')
82
84
  const [customers, setCustomers] = React.useState<Customer[]>([])
83
85
  const [customersLoading, setCustomersLoading] = React.useState(false)
84
86
  const [search, setSearch] = React.useState('')
@@ -110,7 +112,7 @@ function CustomerPickerModal({ open, onClose, onSelect, creating, error }: Custo
110
112
  if (!search.trim()) return true
111
113
  const q = search.toLowerCase()
112
114
  return (
113
- getCustomerLabel(c).toLowerCase().includes(q) ||
115
+ getCustomerLabel(c, unnamedLabel).toLowerCase().includes(q) ||
114
116
  getCustomerEmail(c).toLowerCase().includes(q) ||
115
117
  c.id.toLowerCase().includes(q)
116
118
  )
@@ -126,14 +128,16 @@ function CustomerPickerModal({ open, onClose, onSelect, creating, error }: Custo
126
128
  onClick={(e) => e.stopPropagation()}
127
129
  >
128
130
  <div className="flex items-center justify-between border-b px-4 py-3">
129
- <h3 className="text-sm font-medium">Select Customer for New Quote</h3>
131
+ <h3 className="text-sm font-medium">
132
+ {t('cpq.quotes.customerPicker.title', 'Select Customer for New Quote')}
133
+ </h3>
130
134
  <button
131
135
  type="button"
132
136
  onClick={onClose}
133
137
  disabled={creating}
134
138
  className="text-muted-foreground hover:text-foreground text-lg font-bold leading-none cursor-pointer disabled:cursor-not-allowed"
135
139
  >
136
- &times;
140
+ {'\u00d7'}
137
141
  </button>
138
142
  </div>
139
143
  <div className="p-4 space-y-3">
@@ -141,7 +145,7 @@ function CustomerPickerModal({ open, onClose, onSelect, creating, error }: Custo
141
145
  <div className="relative">
142
146
  <SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
143
147
  <Input
144
- placeholder="Search customers…"
148
+ placeholder={t('cpq.quotes.customerPicker.searchPlaceholder', 'Search customers…')}
145
149
  value={search}
146
150
  onChange={(e) => setSearch(e.target.value)}
147
151
  autoFocus
@@ -151,11 +155,13 @@ function CustomerPickerModal({ open, onClose, onSelect, creating, error }: Custo
151
155
  <div className="max-h-72 overflow-y-auto space-y-1">
152
156
  {customersLoading ? (
153
157
  <div className="py-8 text-center text-sm text-muted-foreground">
154
- Loading customers…
158
+ {t('cpq.quotes.customerPicker.loading', 'Loading customers…')}
155
159
  </div>
156
160
  ) : filtered.length === 0 ? (
157
161
  <div className="py-8 text-center text-sm text-muted-foreground">
158
- {search ? 'No customers match your search.' : 'No customers found.'}
162
+ {search
163
+ ? t('cpq.quotes.customerPicker.noMatch', 'No customers match your search.')
164
+ : t('cpq.quotes.customerPicker.empty', 'No customers found.')}
159
165
  </div>
160
166
  ) : (
161
167
  filtered.map((c) => (
@@ -166,7 +172,7 @@ function CustomerPickerModal({ open, onClose, onSelect, creating, error }: Custo
166
172
  disabled={creating}
167
173
  className="w-full rounded-md border px-3 py-2.5 text-left hover:border-primary hover:bg-primary/5 transition-colors disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed"
168
174
  >
169
- <p className="text-sm font-medium">{getCustomerLabel(c)}</p>
175
+ <p className="text-sm font-medium">{getCustomerLabel(c, unnamedLabel)}</p>
170
176
  {getCustomerEmail(c) && (
171
177
  <p className="text-xs text-muted-foreground mt-0.5">{getCustomerEmail(c)}</p>
172
178
  )}
@@ -200,17 +206,17 @@ export default function CpqQuotesListPage() {
200
206
 
201
207
  const statusOptions = React.useMemo(
202
208
  () => [
203
- { value: 'new', label: 'New' },
204
- { value: 'incomplete', label: 'Incomplete' },
205
- { value: 'ready', label: 'Ready' },
206
- { value: 'in_approval', label: 'In Approval' },
207
- { value: 'approved', label: 'Approved' },
208
- { value: 'with_customer', label: 'With Customer' },
209
- { value: 'accepted', label: 'Accepted' },
210
- { value: 'rejected', label: 'Rejected' },
211
- { value: 'cancelled', label: 'Cancelled' },
209
+ { value: 'new', label: t('cpq.quotes.status.new', 'New') },
210
+ { value: 'incomplete', label: t('cpq.quotes.status.incomplete', 'Incomplete') },
211
+ { value: 'ready', label: t('cpq.quotes.status.ready', 'Ready') },
212
+ { value: 'in_approval', label: t('cpq.quotes.status.in_approval', 'In Approval') },
213
+ { value: 'approved', label: t('cpq.quotes.status.approved', 'Approved') },
214
+ { value: 'with_customer', label: t('cpq.quotes.status.with_customer', 'With Customer') },
215
+ { value: 'accepted', label: t('cpq.quotes.status.accepted', 'Accepted') },
216
+ { value: 'rejected', label: t('cpq.quotes.status.rejected', 'Rejected') },
217
+ { value: 'cancelled', label: t('cpq.quotes.status.cancelled', 'Cancelled') },
212
218
  ],
213
- [],
219
+ [t],
214
220
  )
215
221
 
216
222
  const filters = React.useMemo<FilterDef[]>(
@@ -245,7 +251,9 @@ export default function CpqQuotesListPage() {
245
251
  setShowCustomerPicker(false)
246
252
  router.push(`/backend/cpq/quotes/${body.id}`)
247
253
  } else {
248
- setCreateError(body.error ?? `Failed to create quote (${res.status})`)
254
+ setCreateError(
255
+ body.error ?? t('cpq.quotes.create.error', 'Failed to create quote ({status})', { status: res.status }),
256
+ )
249
257
  }
250
258
  } catch (err) {
251
259
  setCreateError((err as Error).message)
@@ -253,7 +261,7 @@ export default function CpqQuotesListPage() {
253
261
  setCreating(false)
254
262
  }
255
263
  },
256
- [router],
264
+ [router, t],
257
265
  )
258
266
 
259
267
  const columns = React.useMemo<ColumnDef<QuoteConfig>[]>(
@@ -5,6 +5,7 @@ import { Button } from '@open-mercato/ui/primitives/button'
5
5
  import { Input } from '@open-mercato/ui/primitives/input'
6
6
  import { Checkbox } from '@open-mercato/ui/primitives/checkbox'
7
7
  import { Tag } from '@open-mercato/ui/primitives/tag'
8
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
8
9
  import type { DependsOnEffect, DependsOnRule } from './page'
9
10
 
10
11
  // ─── Helpers ─────────────────────────────────────────────────────
@@ -68,6 +69,7 @@ export function DependsOnRulesEditor({
68
69
  parentOptions,
69
70
  childOptions,
70
71
  }: DependsOnRulesEditorProps) {
72
+ const t = useT()
71
73
  const updateRule = (index: number, patch: Partial<DependsOnRule>) => {
72
74
  const next = rules.map((r, i) => (i === index ? { ...r, ...patch } : r))
73
75
  onChange(next)
@@ -85,11 +87,11 @@ export function DependsOnRulesEditor({
85
87
  return (
86
88
  <div className="space-y-2">
87
89
  <p className="text-xs text-muted-foreground">
88
- No rules yet. Add at least one to make this dependency effective.
90
+ {t('cpq.specifications.dependsOn.empty', 'No rules yet. Add at least one to make this dependency effective.')}
89
91
  </p>
90
92
  <Button type="button" variant="outline" size="sm" onClick={addRule}>
91
93
  <Plus className="h-3.5 w-3.5" />
92
- Add rule
94
+ {t('cpq.specifications.dependsOn.addRule', 'Add rule')}
93
95
  </Button>
94
96
  </div>
95
97
  )
@@ -113,7 +115,7 @@ export function DependsOnRulesEditor({
113
115
  </div>
114
116
  <Button type="button" variant="outline" size="sm" onClick={addRule}>
115
117
  <Plus className="h-3.5 w-3.5" />
116
- Add rule
118
+ {t('cpq.specifications.dependsOn.addRule', 'Add rule')}
117
119
  </Button>
118
120
  </div>
119
121
  )
@@ -140,11 +142,12 @@ function RuleRow({
140
142
  onThenChange,
141
143
  onRemove,
142
144
  }: RuleRowProps) {
145
+ const t = useT()
143
146
  return (
144
147
  <div className="grid grid-cols-[1fr_auto_1fr_auto] items-start gap-2 rounded-md border bg-muted/20 p-2">
145
148
  <div>
146
149
  <label className="block text-[10px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
147
- When parent =
150
+ {t('cpq.specifications.dependsOn.whenParent', 'When parent =')}
148
151
  </label>
149
152
  {parentOptions && parentOptions.length > 0 ? (
150
153
  <select
@@ -152,7 +155,7 @@ function RuleRow({
152
155
  onChange={(e) => onWhenChange(e.target.value)}
153
156
  className="w-full rounded-md border bg-background px-2 py-1.5 text-sm"
154
157
  >
155
- <option value="">— Select value —</option>
158
+ <option value="">{t('cpq.specifications.dependsOn.selectValue', '— Select value —')}</option>
156
159
  {parentOptions.map((opt) => (
157
160
  <option key={opt.value} value={opt.value}>
158
161
  {opt.label}
@@ -163,7 +166,7 @@ function RuleRow({
163
166
  <Input
164
167
  value={rule.when}
165
168
  onChange={(e) => onWhenChange(e.target.value)}
166
- placeholder="Parent value"
169
+ placeholder={t('cpq.specifications.dependsOn.parentValuePlaceholder', 'Parent value')}
167
170
  />
168
171
  )}
169
172
  </div>
@@ -178,7 +181,7 @@ function RuleRow({
178
181
  type="button"
179
182
  onClick={onRemove}
180
183
  className="mt-5 self-start rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors"
181
- title="Remove rule"
184
+ title={t('cpq.specifications.dependsOn.removeRule', 'Remove rule')}
182
185
  >
183
186
  <X className="h-4 w-4" />
184
187
  </button>
@@ -196,19 +199,20 @@ type ThenInputProps = {
196
199
  }
197
200
 
198
201
  function ThenInput({ effect, value, childOptions, onChange }: ThenInputProps) {
202
+ const t = useT()
199
203
  if (effect === 'toggle_required') {
200
204
  const isRequired = value === true
201
205
  return (
202
206
  <div>
203
207
  <label className="block text-[10px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
204
- Then required is
208
+ {t('cpq.specifications.dependsOn.thenRequiredIs', 'Then required is')}
205
209
  </label>
206
210
  <label className="flex items-center gap-2 rounded-md border bg-background px-2 py-1.5 text-sm cursor-pointer">
207
211
  <Checkbox
208
212
  checked={isRequired}
209
213
  onCheckedChange={(checked) => onChange(checked === true)}
210
214
  />
211
- <span>{isRequired ? 'Required' : 'Optional'}</span>
215
+ <span>{isRequired ? t('cpq.specifications.dependsOn.required', 'Required') : t('cpq.specifications.dependsOn.optional', 'Optional')}</span>
212
216
  </label>
213
217
  </div>
214
218
  )
@@ -218,7 +222,7 @@ function ThenInput({ effect, value, childOptions, onChange }: ThenInputProps) {
218
222
  return (
219
223
  <div>
220
224
  <label className="block text-[10px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
221
- Then set value to
225
+ {t('cpq.specifications.dependsOn.thenSetValueTo', 'Then set value to')}
222
226
  </label>
223
227
  {childOptions && childOptions.length > 0 ? (
224
228
  <select
@@ -226,7 +230,7 @@ function ThenInput({ effect, value, childOptions, onChange }: ThenInputProps) {
226
230
  onChange={(e) => onChange(e.target.value)}
227
231
  className="w-full rounded-md border bg-background px-2 py-1.5 text-sm"
228
232
  >
229
- <option value="">— Select value —</option>
233
+ <option value="">{t('cpq.specifications.dependsOn.selectValue', '— Select value —')}</option>
230
234
  {childOptions.map((opt) => (
231
235
  <option key={opt.value} value={opt.value}>
232
236
  {opt.label}
@@ -237,7 +241,7 @@ function ThenInput({ effect, value, childOptions, onChange }: ThenInputProps) {
237
241
  <Input
238
242
  value={typeof value === 'string' ? value : ''}
239
243
  onChange={(e) => onChange(e.target.value)}
240
- placeholder="Value"
244
+ placeholder={t('cpq.specifications.dependsOn.valuePlaceholder', 'Value')}
241
245
  />
242
246
  )}
243
247
  </div>
@@ -257,7 +261,7 @@ function ThenInput({ effect, value, childOptions, onChange }: ThenInputProps) {
257
261
  return (
258
262
  <div>
259
263
  <label className="block text-[10px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
260
- Then allow only
264
+ {t('cpq.specifications.dependsOn.thenAllowOnly', 'Then allow only')}
261
265
  </label>
262
266
  <div className="flex flex-wrap gap-1.5 rounded-md border bg-background px-2 py-1.5 text-sm min-h-[34px]">
263
267
  {childOptions.map((opt) => {
@@ -288,12 +292,12 @@ function ThenInput({ effect, value, childOptions, onChange }: ThenInputProps) {
288
292
  return (
289
293
  <div>
290
294
  <label className="block text-[10px] font-medium uppercase tracking-wide text-muted-foreground mb-0.5">
291
- Then allow only (comma-separated)
295
+ {t('cpq.specifications.dependsOn.thenAllowOnlyCsv', 'Then allow only (comma-separated)')}
292
296
  </label>
293
297
  <Input
294
298
  value={thenAsCsv(value)}
295
299
  onChange={(e) => onChange(csvAsArray(e.target.value))}
296
- placeholder="value1, value2, value3"
300
+ placeholder={t('cpq.specifications.dependsOn.csvPlaceholder', 'value1, value2, value3')}
297
301
  />
298
302
  </div>
299
303
  )