@dainamite/cpq 0.5.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 (368) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/MIGRATION.md +74 -0
  3. package/dist/modules/cpq/acl.js +1 -0
  4. package/dist/modules/cpq/acl.js.map +2 -2
  5. package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js +45 -0
  6. package/dist/modules/cpq/api/_helpers/adjustmentCommandRoute.js.map +7 -0
  7. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js +23 -0
  8. package/dist/modules/cpq/api/_helpers/quoteMutationGuard.js.map +7 -0
  9. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js +19 -0
  10. package/dist/modules/cpq/api/_helpers/quoteRouteOpenApi.js.map +7 -0
  11. package/dist/modules/cpq/api/orders/[orderId]/route.js +17 -1
  12. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
  13. package/dist/modules/cpq/api/orders/route.js +47 -2
  14. package/dist/modules/cpq/api/orders/route.js.map +2 -2
  15. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js +54 -0
  16. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.js.map +7 -0
  17. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +42 -2
  18. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
  19. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -2
  20. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +2 -2
  21. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js +47 -0
  22. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.js.map +7 -0
  23. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js +30 -0
  24. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.js.map +7 -0
  25. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js +47 -0
  26. package/dist/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.js.map +7 -0
  27. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +32 -2
  28. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
  29. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +33 -2
  30. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +2 -2
  31. package/dist/modules/cpq/api/quotes/price/route.js +43 -3
  32. package/dist/modules/cpq/api/quotes/price/route.js.map +2 -2
  33. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
  34. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
  35. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
  36. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
  37. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
  38. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
  39. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
  40. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
  41. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +3 -3
  42. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
  43. package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
  44. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
  45. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +174 -184
  46. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  47. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
  48. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
  49. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
  50. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  51. package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
  52. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
  53. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +140 -280
  54. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  55. package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
  56. package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
  57. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +1 -1
  58. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +1 -1
  59. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
  60. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
  61. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
  62. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
  63. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
  64. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
  65. package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
  66. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
  67. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
  68. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  69. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
  70. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
  71. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
  72. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
  73. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
  74. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
  75. package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
  76. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
  77. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
  78. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
  79. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +208 -0
  80. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +7 -0
  81. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js +79 -0
  82. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.js.map +7 -0
  83. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js +53 -0
  84. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.js.map +7 -0
  85. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js +107 -0
  86. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.js.map +7 -0
  87. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js +130 -0
  88. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.js.map +7 -0
  89. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +269 -311
  90. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +3 -3
  91. package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
  92. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
  93. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +1 -1
  94. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +1 -1
  95. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
  96. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
  97. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
  98. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  99. package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
  100. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
  101. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
  102. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
  103. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
  104. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
  105. package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
  106. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
  107. package/dist/modules/cpq/ce.js +8 -0
  108. package/dist/modules/cpq/ce.js.map +2 -2
  109. package/dist/modules/cpq/cli.js +34 -1
  110. package/dist/modules/cpq/cli.js.map +2 -2
  111. package/dist/modules/cpq/commands/price-adjustments.js +107 -0
  112. package/dist/modules/cpq/commands/price-adjustments.js.map +7 -0
  113. package/dist/modules/cpq/components/ArcLineDiff.js +53 -0
  114. package/dist/modules/cpq/components/ArcLineDiff.js.map +7 -0
  115. package/dist/modules/cpq/components/ChargeBreakdownTable.js +148 -0
  116. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +7 -0
  117. package/dist/modules/cpq/components/PriceWaterfall.js +68 -0
  118. package/dist/modules/cpq/components/PriceWaterfall.js.map +7 -0
  119. package/dist/modules/cpq/components/PricingSummaryCard.js +112 -0
  120. package/dist/modules/cpq/components/PricingSummaryCard.js.map +7 -0
  121. package/dist/modules/cpq/components/StatusPath.js +5 -3
  122. package/dist/modules/cpq/components/StatusPath.js.map +2 -2
  123. package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
  124. package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
  125. package/dist/modules/cpq/components/useGrantedFeatures.js +27 -0
  126. package/dist/modules/cpq/components/useGrantedFeatures.js.map +7 -0
  127. package/dist/modules/cpq/data/entities.js +97 -0
  128. package/dist/modules/cpq/data/entities.js.map +2 -2
  129. package/dist/modules/cpq/data/price-adjustments.js +70 -0
  130. package/dist/modules/cpq/data/price-adjustments.js.map +7 -0
  131. package/dist/modules/cpq/data/validators.js +4 -0
  132. package/dist/modules/cpq/data/validators.js.map +2 -2
  133. package/dist/modules/cpq/di.js +6 -0
  134. package/dist/modules/cpq/di.js.map +2 -2
  135. package/dist/modules/cpq/events.js +47 -0
  136. package/dist/modules/cpq/events.js.map +2 -2
  137. package/dist/modules/cpq/i18n/de.json +1220 -0
  138. package/dist/modules/cpq/i18n/en.json +1220 -0
  139. package/dist/modules/cpq/i18n/es.json +1220 -0
  140. package/dist/modules/cpq/i18n/pl.json +1220 -0
  141. package/dist/modules/cpq/index.js +1 -1
  142. package/dist/modules/cpq/index.js.map +2 -2
  143. package/dist/modules/cpq/lib/auditActor.js +10 -0
  144. package/dist/modules/cpq/lib/auditActor.js.map +7 -0
  145. package/dist/modules/cpq/lib/formatMoney.js +7 -0
  146. package/dist/modules/cpq/lib/formatMoney.js.map +7 -0
  147. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js +60 -0
  148. package/dist/modules/cpq/lib/priceAdjustmentDefaults.js.map +7 -0
  149. package/dist/modules/cpq/lib/priceAdjustmentView.js +169 -0
  150. package/dist/modules/cpq/lib/priceAdjustmentView.js.map +7 -0
  151. package/dist/modules/cpq/lib/quotePermissions.js +13 -0
  152. package/dist/modules/cpq/lib/quotePermissions.js.map +7 -0
  153. package/dist/modules/cpq/lib/quoteSummaryView.js +28 -0
  154. package/dist/modules/cpq/lib/quoteSummaryView.js.map +7 -0
  155. package/dist/modules/cpq/migrations/Migration20261110213052.js +22 -0
  156. package/dist/modules/cpq/migrations/Migration20261110213052.js.map +7 -0
  157. package/dist/modules/cpq/services/arcLineRemoval.js +53 -0
  158. package/dist/modules/cpq/services/arcLineRemoval.js.map +7 -0
  159. package/dist/modules/cpq/services/arcPriceFreeze.js +79 -0
  160. package/dist/modules/cpq/services/arcPriceFreeze.js.map +7 -0
  161. package/dist/modules/cpq/services/cpqOrderService.js +25 -28
  162. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  163. package/dist/modules/cpq/services/cpqPricingService.js +7 -0
  164. package/dist/modules/cpq/services/cpqPricingService.js.map +2 -2
  165. package/dist/modules/cpq/services/cpqQuotingService.js +330 -109
  166. package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
  167. package/dist/modules/cpq/services/orderPricingSummary.js +26 -0
  168. package/dist/modules/cpq/services/orderPricingSummary.js.map +7 -0
  169. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js +264 -0
  170. package/dist/modules/cpq/services/priceAdjustments/adjustmentService.js.map +7 -0
  171. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js +25 -0
  172. package/dist/modules/cpq/services/priceAdjustments/chargeWaterfall.js.map +7 -0
  173. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js +38 -0
  174. package/dist/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.js.map +7 -0
  175. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js +104 -0
  176. package/dist/modules/cpq/services/priceAdjustments/pricingSnapshot.js.map +7 -0
  177. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js +102 -0
  178. package/dist/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.js.map +7 -0
  179. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js +52 -0
  180. package/dist/modules/cpq/services/priceAdjustments/quotePricingEvents.js.map +7 -0
  181. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js +37 -0
  182. package/dist/modules/cpq/services/priceAdjustments/quotePricingWrite.js.map +7 -0
  183. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js +107 -0
  184. package/dist/modules/cpq/services/priceAdjustments/resolveManualAdjustments.js.map +7 -0
  185. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js +30 -0
  186. package/dist/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.js.map +7 -0
  187. package/dist/modules/cpq/services/types.js.map +2 -2
  188. package/dist/modules/cpq/setup.js +6 -0
  189. package/dist/modules/cpq/setup.js.map +2 -2
  190. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
  191. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
  192. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
  193. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
  194. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
  195. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
  196. package/dist/modules/cpq/widgets/injection-table.js +2 -2
  197. package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
  198. package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
  199. package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
  200. package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
  201. package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
  202. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
  203. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
  204. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
  205. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
  206. package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
  207. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
  208. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
  209. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
  210. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
  211. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
  212. package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
  213. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
  214. package/dist/modules/cpq/workflows/steps/index.js +12 -0
  215. package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
  216. package/package.json +1 -1
  217. package/src/modules/cpq/__integration__/TC-CPQ-ARC-012.spec.ts +134 -0
  218. package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
  219. package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
  220. package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
  221. package/src/modules/cpq/acl.ts +1 -0
  222. package/src/modules/cpq/api/__tests__/adjustmentRoutes.test.ts +79 -0
  223. package/src/modules/cpq/api/__tests__/quoteWriteGuards.test.ts +100 -0
  224. package/src/modules/cpq/api/_helpers/__tests__/quoteMutationGuard.test.ts +48 -0
  225. package/src/modules/cpq/api/_helpers/adjustmentCommandRoute.ts +45 -0
  226. package/src/modules/cpq/api/_helpers/quoteMutationGuard.ts +38 -0
  227. package/src/modules/cpq/api/_helpers/quoteRouteOpenApi.ts +19 -0
  228. package/src/modules/cpq/api/orders/[orderId]/route.ts +18 -0
  229. package/src/modules/cpq/api/orders/route.ts +51 -1
  230. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/restore/route.ts +56 -0
  231. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +48 -1
  232. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +31 -1
  233. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/[adjustmentId]/route.ts +39 -0
  234. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/preview/route.ts +24 -0
  235. package/src/modules/cpq/api/quotes/[quoteId]/price-adjustments/route.ts +40 -0
  236. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/__tests__/route.test.ts +95 -0
  237. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +40 -1
  238. package/src/modules/cpq/api/quotes/[quoteId]/status/__tests__/route.test.ts +74 -0
  239. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +37 -1
  240. package/src/modules/cpq/api/quotes/price/__tests__/route.test.ts +37 -0
  241. package/src/modules/cpq/api/quotes/price/route.ts +28 -2
  242. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
  243. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
  244. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
  245. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
  246. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +3 -3
  247. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
  248. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
  249. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +164 -120
  250. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
  251. package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
  252. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +122 -308
  253. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +1 -1
  254. package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
  255. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
  256. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
  257. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
  258. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
  259. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
  260. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
  261. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
  262. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
  263. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  264. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
  265. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +260 -0
  266. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentsPanel.tsx +124 -0
  267. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/__tests__/PriceAdjustmentsPanel.test.tsx +76 -0
  268. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/arcLineRemoval.tsx +76 -0
  269. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/priceAdjustmentContext.tsx +133 -0
  270. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/usePriceAdjustments.tsx +165 -0
  271. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +304 -348
  272. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +1 -1
  273. package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
  274. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
  275. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
  276. package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
  277. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
  278. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
  279. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  280. package/src/modules/cpq/ce.ts +8 -0
  281. package/src/modules/cpq/cli.ts +48 -1
  282. package/src/modules/cpq/commands/__tests__/price-adjustments.test.ts +54 -0
  283. package/src/modules/cpq/commands/price-adjustments.ts +90 -0
  284. package/src/modules/cpq/components/ArcLineDiff.tsx +87 -0
  285. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +203 -0
  286. package/src/modules/cpq/components/PriceWaterfall.tsx +96 -0
  287. package/src/modules/cpq/components/PricingSummaryCard.tsx +128 -0
  288. package/src/modules/cpq/components/StatusPath.tsx +7 -4
  289. package/src/modules/cpq/components/__tests__/PriceWaterfall.test.tsx +38 -0
  290. package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
  291. package/src/modules/cpq/components/useGrantedFeatures.ts +35 -0
  292. package/src/modules/cpq/data/__tests__/price-adjustment-migration.test.ts +11 -0
  293. package/src/modules/cpq/data/__tests__/price-adjustments.test.ts +26 -0
  294. package/src/modules/cpq/data/entities.ts +83 -0
  295. package/src/modules/cpq/data/price-adjustments.ts +67 -0
  296. package/src/modules/cpq/data/validators.ts +3 -0
  297. package/src/modules/cpq/di.ts +6 -0
  298. package/src/modules/cpq/events.ts +53 -2
  299. package/src/modules/cpq/i18n/de.json +1220 -0
  300. package/src/modules/cpq/i18n/en.json +1220 -0
  301. package/src/modules/cpq/i18n/es.json +1220 -0
  302. package/src/modules/cpq/i18n/pl.json +1220 -0
  303. package/src/modules/cpq/index.ts +1 -1
  304. package/src/modules/cpq/lib/__tests__/auditActor.test.ts +12 -0
  305. package/src/modules/cpq/lib/__tests__/priceAdjustmentDefaults.test.ts +37 -0
  306. package/src/modules/cpq/lib/__tests__/priceAdjustmentView.test.ts +152 -0
  307. package/src/modules/cpq/lib/__tests__/quotePermissions.test.ts +30 -0
  308. package/src/modules/cpq/lib/__tests__/quoteSummaryView.test.ts +56 -0
  309. package/src/modules/cpq/lib/auditActor.ts +13 -0
  310. package/src/modules/cpq/lib/formatMoney.ts +4 -0
  311. package/src/modules/cpq/lib/priceAdjustmentDefaults.ts +36 -0
  312. package/src/modules/cpq/lib/priceAdjustmentView.ts +259 -0
  313. package/src/modules/cpq/lib/quotePermissions.ts +22 -0
  314. package/src/modules/cpq/lib/quoteSummaryView.ts +51 -0
  315. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +3298 -2423
  316. package/src/modules/cpq/migrations/Migration20261110213052.ts +22 -0
  317. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +9 -2
  318. package/src/modules/cpq/services/__tests__/adjustmentService.test.ts +183 -0
  319. package/src/modules/cpq/services/__tests__/arcLineRemoval.test.ts +109 -0
  320. package/src/modules/cpq/services/__tests__/arcPriceFreeze.test.ts +141 -0
  321. package/src/modules/cpq/services/__tests__/chargeWaterfall.test.ts +28 -0
  322. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +35 -2
  323. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +57 -0
  324. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +237 -0
  325. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +58 -3
  326. package/src/modules/cpq/services/__tests__/orderPricingSummary.test.ts +50 -0
  327. package/src/modules/cpq/services/__tests__/pricingSnapshot.test.ts +62 -0
  328. package/src/modules/cpq/services/__tests__/quoteAdjustmentLifecycle.test.ts +35 -0
  329. package/src/modules/cpq/services/__tests__/quoteAdjustmentPricing.test.ts +65 -0
  330. package/src/modules/cpq/services/__tests__/quoteCloneAdjustments.test.ts +45 -0
  331. package/src/modules/cpq/services/__tests__/quoteDetachTargetAdjustments.test.ts +54 -0
  332. package/src/modules/cpq/services/__tests__/quoteEventsService.test.ts +72 -0
  333. package/src/modules/cpq/services/__tests__/quotePricingEvents.test.ts +126 -0
  334. package/src/modules/cpq/services/__tests__/quotePricingIsolation.test.ts +52 -0
  335. package/src/modules/cpq/services/__tests__/quotePricingWrite.test.ts +74 -0
  336. package/src/modules/cpq/services/__tests__/resolveManualAdjustments.test.ts +90 -0
  337. package/src/modules/cpq/services/__tests__/storedAdjustmentFailure.test.ts +9 -0
  338. package/src/modules/cpq/services/arcLineRemoval.ts +87 -0
  339. package/src/modules/cpq/services/arcPriceFreeze.ts +165 -0
  340. package/src/modules/cpq/services/cpqOrderService.ts +22 -28
  341. package/src/modules/cpq/services/cpqPricingService.ts +8 -0
  342. package/src/modules/cpq/services/cpqQuotingService.ts +374 -125
  343. package/src/modules/cpq/services/orderPricingSummary.ts +47 -0
  344. package/src/modules/cpq/services/priceAdjustments/adjustmentService.ts +249 -0
  345. package/src/modules/cpq/services/priceAdjustments/chargeWaterfall.ts +28 -0
  346. package/src/modules/cpq/services/priceAdjustments/loadQuoteAdjustments.ts +25 -0
  347. package/src/modules/cpq/services/priceAdjustments/pricingSnapshot.ts +86 -0
  348. package/src/modules/cpq/services/priceAdjustments/quoteAdjustmentLifecycle.ts +68 -0
  349. package/src/modules/cpq/services/priceAdjustments/quotePricingEvents.ts +94 -0
  350. package/src/modules/cpq/services/priceAdjustments/quotePricingWrite.ts +51 -0
  351. package/src/modules/cpq/services/priceAdjustments/resolveManualAdjustments.ts +116 -0
  352. package/src/modules/cpq/services/priceAdjustments/storedAdjustmentFailure.ts +31 -0
  353. package/src/modules/cpq/services/types.ts +34 -1
  354. package/src/modules/cpq/setup.ts +7 -1
  355. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
  356. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
  357. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
  358. package/src/modules/cpq/widgets/injection-table.ts +8 -2
  359. package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
  360. package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
  361. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
  362. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
  363. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
  364. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
  365. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
  366. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
  367. package/src/modules/cpq/workflows/steps/index.ts +12 -0
  368. package/src/modules/cpq/workflows/types.ts +4 -0
@@ -1,15 +1,27 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
5
  import { useRouter, useParams, useSearchParams } from "next/navigation";
5
6
  import ArcQuoteConfigurator from "./_components/ArcQuoteConfigurator.js";
6
7
  import { StatusPath } from "../../../../components/StatusPath.js";
7
- import { ArrowRight, ExternalLink, Plus } from "lucide-react";
8
+ import { ArrowRight, ExternalLink, Plus, RotateCcw } from "lucide-react";
8
9
  import { Alert, AlertTitle, AlertDescription } from "@open-mercato/ui/primitives/alert";
9
10
  import { Button } from "@open-mercato/ui/primitives/button";
10
11
  import { Tag } from "@open-mercato/ui/primitives/tag";
11
12
  import { FormHeader } from "@open-mercato/ui/backend/forms";
12
13
  import { NumberInput } from "../../../../components/NumberInput.js";
14
+ import { ChargeBreakdownTable } from "../../../../components/ChargeBreakdownTable.js";
15
+ import { PricingSummaryCard } from "../../../../components/PricingSummaryCard.js";
16
+ import { ArcLineDiff, LineActionBadge } from "../../../../components/ArcLineDiff.js";
17
+ import { ADJUST_PRICE_FEATURE } from "../../../../lib/priceAdjustmentView.js";
18
+ import { arcRecurringDelta } from "../../../../lib/quoteSummaryView.js";
19
+ import { usePriceAdjustments } from "./_components/usePriceAdjustments.js";
20
+ import { PriceAdjustmentsPanel } from "./_components/PriceAdjustmentsPanel.js";
21
+ import { ArcLineRemovalProvider, useArcLineRemoval, useArcLineRemovalUi } from "./_components/arcLineRemoval.js";
22
+ import { AdjustChargeAction, DiscountLineAction, ManualAdjustmentActions, PriceAdjustmentProvider, usePriceAdjustmentUi } from "./_components/priceAdjustmentContext.js";
23
+ import { useGrantedFeatures } from "../../../../components/useGrantedFeatures.js";
24
+ import { canRecalculateQuote, QUOTE_MANAGE_FEATURE } from "../../../../lib/quotePermissions.js";
13
25
  import {
14
26
  formatStatusLabel,
15
27
  quoteCpqStatusMap
@@ -17,24 +29,24 @@ import {
17
29
  function fmt(amount, currency) {
18
30
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(amount);
19
31
  }
20
- const RULE_TYPE_LABELS = {
21
- discount_percent: "% discount",
22
- discount_absolute: "fixed discount",
23
- surcharge_percent: "% surcharge",
24
- surcharge_absolute: "fixed surcharge",
25
- price_override: "override"
26
- };
27
- function fmtRuleValue(adj, currency) {
28
- if (adj.ruleType === "discount_percent" || adj.ruleType === "surcharge_percent") return `${adj.value}%`;
29
- if (currency) return fmt(adj.value, currency);
30
- return String(adj.value);
31
- }
32
- async function apiJson(url, init) {
32
+ async function apiJson(t, url, init) {
33
33
  const res = await fetch(url, init);
34
34
  const json = await res.json();
35
- if (!res.ok) throw new Error(json?.error ?? `Request failed (${res.status})`);
35
+ if (!res.ok) throw new Error(json?.error ?? t("cpq.common.requestFailed", "Request failed ({status})", { status: res.status }));
36
36
  return json;
37
37
  }
38
+ function quoteTypeLabel(t, quoteType) {
39
+ switch (quoteType) {
40
+ case "amend":
41
+ return t("cpq.quotes.type.amend", "amend");
42
+ case "renew":
43
+ return t("cpq.quotes.type.renew", "renew");
44
+ case "cancel":
45
+ return t("cpq.quotes.type.cancel", "cancel");
46
+ default:
47
+ return quoteType;
48
+ }
49
+ }
38
50
  function MetaCard({ label, children }) {
39
51
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
40
52
  /* @__PURE__ */ jsx("div", { className: "text-[10px] font-medium uppercase tracking-wide text-muted-foreground", children: label }),
@@ -42,6 +54,7 @@ function MetaCard({ label, children }) {
42
54
  ] });
43
55
  }
44
56
  function MoreActionsDropdown({ items }) {
57
+ const t = useT();
45
58
  const [open, setOpen] = React.useState(false);
46
59
  const ref = React.useRef(null);
47
60
  React.useEffect(() => {
@@ -55,7 +68,7 @@ function MoreActionsDropdown({ items }) {
55
68
  if (items.length === 0) return null;
56
69
  return /* @__PURE__ */ jsxs("div", { className: "relative", ref, children: [
57
70
  /* @__PURE__ */ jsxs(Button, { type: "button", variant: "outline", onClick: () => setOpen((v) => !v), children: [
58
- "Actions",
71
+ t("cpq.quotes.detail.actions", "Actions"),
59
72
  /* @__PURE__ */ jsx("svg", { className: "h-3 w-3 ml-1", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M19.5 8.25l-7.5 7.5-7.5-7.5" }) })
60
73
  ] }),
61
74
  open && /* @__PURE__ */ jsx("div", { className: "absolute right-0 top-full mt-1 z-50 min-w-[200px] rounded-md border bg-popover shadow-md py-1", children: items.map((item) => {
@@ -81,25 +94,28 @@ function MoreActionsDropdown({ items }) {
81
94
  ] });
82
95
  }
83
96
  function buildMoreActions({
97
+ t,
84
98
  recalculate,
99
+ canRecalculate,
85
100
  submitting,
86
101
  deletable,
87
102
  onDelete,
88
103
  deleting
89
104
  }) {
90
- const items = [
91
- {
105
+ const items = [];
106
+ if (canRecalculate) {
107
+ items.push({
92
108
  id: "recalculate",
93
- label: "Recalculate",
109
+ label: t("cpq.quotes.detail.recalculate", "Recalculate"),
94
110
  onSelect: recalculate,
95
111
  loading: submitting,
96
112
  disabled: submitting
97
- }
98
- ];
113
+ });
114
+ }
99
115
  if (deletable) {
100
116
  items.push({
101
117
  id: "delete",
102
- label: "Delete",
118
+ label: t("cpq.common.delete", "Delete"),
103
119
  onSelect: onDelete,
104
120
  disabled: deleting,
105
121
  loading: deleting
@@ -113,7 +129,9 @@ function CpqQuoteDetailPage(props) {
113
129
  const searchParams = useSearchParams();
114
130
  const cpqConfigId = props.params?.id ?? urlParams?.id;
115
131
  const isNew = cpqConfigId === "new";
132
+ const t = useT();
116
133
  const [cpqQuote, setCpqQuote] = React.useState(null);
134
+ const grantedFeatures = useGrantedFeatures([QUOTE_MANAGE_FEATURE, ADJUST_PRICE_FEATURE]);
117
135
  const [loading, setLoading] = React.useState(true);
118
136
  const [error, setError] = React.useState(null);
119
137
  const [submitting, setSubmitting] = React.useState(false);
@@ -156,12 +174,26 @@ function CpqQuoteDetailPage(props) {
156
174
  if (!cpqConfigId || isNew) return;
157
175
  void loadArcTargets();
158
176
  }, [cpqConfigId, isNew, loadArcTargets]);
177
+ const reloadQuote = React.useCallback(async () => {
178
+ try {
179
+ setCpqQuote(await apiJson(t, `/api/cpq/quotes/${cpqConfigId}`));
180
+ } catch (err) {
181
+ setError(err.message);
182
+ }
183
+ }, [cpqConfigId, t]);
184
+ const priceAdjustments = usePriceAdjustments({
185
+ quote: isNew ? null : cpqQuote,
186
+ quoteType: arcQuoteType,
187
+ canAdjust: grantedFeatures.includes(QUOTE_MANAGE_FEATURE) && grantedFeatures.includes(ADJUST_PRICE_FEATURE),
188
+ onQuote: setCpqQuote,
189
+ reloadQuote
190
+ });
159
191
  React.useEffect(() => {
160
192
  if (isNew) {
161
193
  const quoteId = searchParams.get("quoteId");
162
194
  const customerId = searchParams.get("customerId");
163
195
  if (!customerId) {
164
- setError("customerId is required to create a CPQ quote");
196
+ setError(t("cpq.quotes.detail.customerIdRequired", "customerId is required to create a CPQ quote"));
165
197
  setLoading(false);
166
198
  return;
167
199
  }
@@ -169,7 +201,7 @@ function CpqQuoteDetailPage(props) {
169
201
  try {
170
202
  const body = { customerId };
171
203
  if (quoteId) body.quoteId = quoteId;
172
- const result = await apiJson("/api/cpq/quotes", {
204
+ const result = await apiJson(t, "/api/cpq/quotes", {
173
205
  method: "POST",
174
206
  headers: { "Content-Type": "application/json" },
175
207
  body: JSON.stringify(body)
@@ -186,7 +218,7 @@ function CpqQuoteDetailPage(props) {
186
218
  let cancelled = false;
187
219
  async function load() {
188
220
  try {
189
- const result = await apiJson(`/api/cpq/quotes/${cpqConfigId}`);
221
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqConfigId}`);
190
222
  if (!cancelled) setCpqQuote(result);
191
223
  } catch (err) {
192
224
  if (!cancelled) setError(err.message);
@@ -198,11 +230,11 @@ function CpqQuoteDetailPage(props) {
198
230
  return () => {
199
231
  cancelled = true;
200
232
  };
201
- }, [cpqConfigId, isNew, searchParams, router]);
233
+ }, [cpqConfigId, isNew, searchParams, router, t]);
202
234
  const loadOfferings = async () => {
203
235
  setOfferingsLoading(true);
204
236
  try {
205
- const result = await apiJson("/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
237
+ const result = await apiJson(t, "/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
206
238
  setOfferings(result.items ?? []);
207
239
  } catch {
208
240
  setOfferings([]);
@@ -214,7 +246,7 @@ function CpqQuoteDetailPage(props) {
214
246
  try {
215
247
  const params = new URLSearchParams({ offeringId });
216
248
  if (Object.keys(currentConfig).length > 0) params.set("configuration", JSON.stringify(currentConfig));
217
- const result = await apiJson(`/api/cpq/quotes/attributes?${params}`);
249
+ const result = await apiJson(t, `/api/cpq/quotes/attributes?${params}`);
218
250
  setAttributes(result.attributes ?? []);
219
251
  } catch {
220
252
  setAttributes([]);
@@ -231,7 +263,7 @@ function CpqQuoteDetailPage(props) {
231
263
  const body = { offeringId: selectedOffering.id, configuration: config, quantity };
232
264
  if (addingToParentLineId) body.parentLineId = addingToParentLineId;
233
265
  if (tid) body.targetSubscriptionId = tid;
234
- lastResult = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items`, {
266
+ lastResult = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/items`, {
235
267
  method: "POST",
236
268
  headers: { "Content-Type": "application/json" },
237
269
  body: JSON.stringify(body)
@@ -251,7 +283,7 @@ function CpqQuoteDetailPage(props) {
251
283
  try {
252
284
  setSubmitting(true);
253
285
  setError(null);
254
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, {
286
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, {
255
287
  method: "PUT",
256
288
  headers: { "Content-Type": "application/json" },
257
289
  body: JSON.stringify({ configuration: config, quantity })
@@ -265,22 +297,19 @@ function CpqQuoteDetailPage(props) {
265
297
  setSubmitting(false);
266
298
  }
267
299
  };
268
- const removeItem = async (lineId) => {
269
- if (!cpqQuote) return;
270
- try {
271
- setError(null);
272
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/items/${lineId}`, { method: "DELETE" });
273
- setCpqQuote(result);
274
- } catch (err) {
275
- setError(err.message);
276
- }
277
- };
300
+ const lineRemoval = useArcLineRemoval({
301
+ quote: isNew ? null : cpqQuote,
302
+ quoteType: arcQuoteType,
303
+ onQuote: setCpqQuote,
304
+ onError: setError
305
+ });
306
+ const removeItem = lineRemoval.remove;
278
307
  const recalculate = async () => {
279
308
  if (!cpqQuote) return;
280
309
  try {
281
310
  setSubmitting(true);
282
311
  setError(null);
283
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/recalculate`, {
312
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/recalculate`, {
284
313
  method: "POST",
285
314
  headers: { "Content-Type": "application/json" },
286
315
  body: JSON.stringify({ save: true })
@@ -297,7 +326,7 @@ function CpqQuoteDetailPage(props) {
297
326
  try {
298
327
  setConverting(true);
299
328
  setError(null);
300
- const result = await apiJson("/api/cpq/orders", {
329
+ const result = await apiJson(t, "/api/cpq/orders", {
301
330
  method: "POST",
302
331
  headers: { "Content-Type": "application/json" },
303
332
  body: JSON.stringify({ quoteId: cpqQuote.id })
@@ -314,7 +343,7 @@ function CpqQuoteDetailPage(props) {
314
343
  try {
315
344
  setTransitioning(true);
316
345
  setError(null);
317
- const result = await apiJson(`/api/cpq/quotes/${cpqQuote.id}/status`, {
346
+ const result = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}/status`, {
318
347
  method: "POST",
319
348
  headers: { "Content-Type": "application/json" },
320
349
  body: JSON.stringify({ targetStatus })
@@ -332,7 +361,7 @@ function CpqQuoteDetailPage(props) {
332
361
  setDeleting(true);
333
362
  setError(null);
334
363
  setShowDeleteConfirm(false);
335
- await apiJson(`/api/cpq/quotes/${cpqQuote.id}`, { method: "DELETE" });
364
+ await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}`, { method: "DELETE" });
336
365
  router.push("/backend/cpq/quotes");
337
366
  } catch (err) {
338
367
  setError(err.message);
@@ -350,7 +379,7 @@ function CpqQuoteDetailPage(props) {
350
379
  };
351
380
  const startAddOffering = () => {
352
381
  resetConfigState();
353
- setAddingTargetSubIds(arcTargets.map((t) => t.subscriptionId));
382
+ setAddingTargetSubIds(arcTargets.map((t2) => t2.subscriptionId));
354
383
  loadOfferings();
355
384
  setView("add-offering");
356
385
  };
@@ -366,7 +395,7 @@ function CpqQuoteDetailPage(props) {
366
395
  setAddingTargetSubIds(inherited ? [inherited] : []);
367
396
  if (offeringId) {
368
397
  try {
369
- const result = await apiJson("/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
398
+ const result = await apiJson(t, "/api/cpq/product-offerings?pageSize=100&lifecycleStatus=active");
370
399
  const match = (result.items ?? []).find((o) => o.id === offeringId);
371
400
  if (match) {
372
401
  selectOffering(match);
@@ -402,24 +431,28 @@ function CpqQuoteDetailPage(props) {
402
431
  if (loading) {
403
432
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-12 justify-center text-sm text-muted-foreground", children: [
404
433
  /* @__PURE__ */ jsx(Spinner, {}),
405
- " Loading\u2026"
434
+ " ",
435
+ t("cpq.quotes.detail.loading", "Loading\u2026")
406
436
  ] });
407
437
  }
408
438
  if (!cpqQuote) {
409
439
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
410
- /* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/quotes"), className: "text-sm text-muted-foreground hover:text-foreground", children: "\u2190 Back to Quotes" }),
440
+ /* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/quotes"), className: "text-sm text-muted-foreground hover:text-foreground", children: t("cpq.quotes.detail.backToQuotes", "\u2190 Back to Quotes") }),
411
441
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
412
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "Quote not found." })
442
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.quotes.detail.notFound", "Quote not found.") })
413
443
  ] });
414
444
  }
415
445
  const { pricingSummary: summary, currencyCode: currency } = cpqQuote;
446
+ const arcDelta = arcQuoteType === "amend" || arcQuoteType === "renew" ? arcRecurringDelta(cpqQuote.lines) : null;
416
447
  const arcEditable = ["new", "incomplete", "ready"].includes(cpqQuote.cpqStatus);
417
- const isMergeRenew = arcQuoteType === "renew" && arcTargets.length >= 2 && arcTargets.every((t) => t.mergeAction === "absorb");
448
+ const isMergeRenew = arcQuoteType === "renew" && arcTargets.length >= 2 && arcTargets.every((t2) => t2.mergeAction === "absorb");
418
449
  const arcTargetCodeMap = /* @__PURE__ */ new Map();
419
- for (const t of arcTargets) {
420
- if (t.subscription?.code) arcTargetCodeMap.set(t.subscriptionId, t.subscription.code);
450
+ for (const t2 of arcTargets) {
451
+ if (t2.subscription?.code) arcTargetCodeMap.set(t2.subscriptionId, t2.subscription.code);
421
452
  }
422
- return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
453
+ return /* @__PURE__ */ jsx(PriceAdjustmentProvider, { value: priceAdjustments.ui, children: /* @__PURE__ */ jsx(ArcLineRemovalProvider, { value: lineRemoval.ui, children: /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
454
+ priceAdjustments.elements,
455
+ lineRemoval.ConfirmDialogElement,
423
456
  arcDrawerOpen && /* @__PURE__ */ jsx(
424
457
  ArcQuoteConfigurator,
425
458
  {
@@ -434,7 +467,7 @@ function CpqQuoteDetailPage(props) {
434
467
  onChanged: async () => {
435
468
  await loadArcTargets();
436
469
  try {
437
- const fresh = await apiJson(`/api/cpq/quotes/${cpqQuote.id}`);
470
+ const fresh = await apiJson(t, `/api/cpq/quotes/${cpqQuote.id}`);
438
471
  setCpqQuote(fresh);
439
472
  } catch {
440
473
  }
@@ -446,13 +479,13 @@ function CpqQuoteDetailPage(props) {
446
479
  {
447
480
  mode: "detail",
448
481
  backHref: "/backend/cpq/quotes",
449
- backLabel: "Back to CPQ Quotes",
450
- entityTypeLabel: "CPQ Quote",
451
- title: cpqQuote.quoteNumber || cpqQuote.customerName || "Untitled",
482
+ backLabel: t("cpq.quotes.detail.backLabel", "Back to CPQ Quotes"),
483
+ entityTypeLabel: t("cpq.quotes.detail.entityType", "CPQ Quote"),
484
+ title: cpqQuote.quoteNumber || cpqQuote.customerName || t("cpq.quotes.detail.untitled", "Untitled"),
452
485
  statusBadge: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
453
486
  /* @__PURE__ */ jsx(Tag, { variant: quoteCpqStatusMap[cpqQuote.cpqStatus] ?? "neutral", dot: true, children: formatStatusLabel(cpqQuote.cpqStatus) }),
454
- arcQuoteType !== "new" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "uppercase", children: arcQuoteType }),
455
- cpqQuote.convertedOrderId && /* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: "Converted" })
487
+ arcQuoteType !== "new" && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "uppercase", children: quoteTypeLabel(t, arcQuoteType) }),
488
+ cpqQuote.convertedOrderId && /* @__PURE__ */ jsx(Tag, { variant: "success", dot: true, children: t("cpq.quotes.detail.converted", "Converted") })
456
489
  ] }),
457
490
  actionsContent: /* @__PURE__ */ jsxs(Fragment, { children: [
458
491
  /* @__PURE__ */ jsxs(
@@ -463,7 +496,7 @@ function CpqQuoteDetailPage(props) {
463
496
  onClick: () => router.push(`/backend/sales/quotes/${cpqQuote.quoteId}`),
464
497
  children: [
465
498
  /* @__PURE__ */ jsx(ExternalLink, { className: "h-3.5 w-3.5" }),
466
- "View Sales Quote"
499
+ t("cpq.quotes.detail.viewSalesQuote", "View Sales Quote")
467
500
  ]
468
501
  }
469
502
  ),
@@ -474,27 +507,29 @@ function CpqQuoteDetailPage(props) {
474
507
  onClick: () => router.push(`/backend/cpq/orders/${cpqQuote.convertedOrderId}`),
475
508
  children: [
476
509
  /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
477
- "Go to order"
510
+ t("cpq.quotes.detail.goToOrder", "Go to order")
478
511
  ]
479
512
  }
480
513
  ) : view !== "summary" ? /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => {
481
514
  resetConfigState();
482
515
  setView("summary");
483
- }, children: "Cancel" }) : /* @__PURE__ */ jsxs(Fragment, { children: [
516
+ }, children: t("cpq.common.cancel", "Cancel") }) : /* @__PURE__ */ jsxs(Fragment, { children: [
484
517
  /* @__PURE__ */ jsxs(Button, { type: "button", onClick: startAddOffering, children: [
485
518
  /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
486
- "Add Offering"
519
+ t("cpq.quotes.detail.addOffering", "Add Offering")
487
520
  ] }),
488
- arcEditable && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setArcDrawerOpen(true), children: "Modify Subscription" }),
521
+ arcEditable && /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setArcDrawerOpen(true), children: t("cpq.quotes.detail.modifySubscription", "Modify Subscription") }),
489
522
  cpqQuote.cpqStatus === "accepted" && /* @__PURE__ */ jsxs(Button, { type: "button", onClick: convertToOrder, disabled: converting, children: [
490
523
  converting ? /* @__PURE__ */ jsx(Spinner, {}) : /* @__PURE__ */ jsx(ArrowRight, { className: "h-4 w-4" }),
491
- "Convert to Order"
524
+ t("cpq.quotes.detail.convertToOrder", "Convert to Order")
492
525
  ] }),
493
526
  /* @__PURE__ */ jsx(
494
527
  MoreActionsDropdown,
495
528
  {
496
529
  items: buildMoreActions({
530
+ t,
497
531
  recalculate,
532
+ canRecalculate: canRecalculateQuote(grantedFeatures, cpqQuote.cpqStatus),
498
533
  submitting,
499
534
  deletable: !["accepted", "rejected", "cancelled"].includes(cpqQuote.cpqStatus),
500
535
  onDelete: () => setShowDeleteConfirm(true),
@@ -516,89 +551,69 @@ function CpqQuoteDetailPage(props) {
516
551
  statusMap: quoteCpqStatusMap,
517
552
  onTransition: transitionStatus,
518
553
  disabled: transitioning,
519
- ariaLabel: "Quote status path"
554
+ ariaLabel: t("cpq.quotes.detail.statusPath", "Quote status path")
520
555
  }
521
556
  ),
522
557
  transitioning && /* @__PURE__ */ jsx(Spinner, {})
523
558
  ] }),
524
559
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error, onDismiss: () => setError(null) }),
525
560
  arcQuoteType !== "new" && arcTargets.length > 0 && /* @__PURE__ */ jsx("div", { className: "rounded-md border border-brand-violet/30 bg-brand-violet/10 px-4 py-3 text-sm text-brand-violet", children: /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
526
- /* @__PURE__ */ jsx("span", { className: "font-medium uppercase", children: arcQuoteType }),
561
+ /* @__PURE__ */ jsx("span", { className: "font-medium uppercase", children: quoteTypeLabel(t, arcQuoteType) }),
527
562
  " \u2022 ",
528
- arcTargets.length,
529
- " target",
530
- arcTargets.length === 1 ? "" : "s",
531
- ":",
563
+ arcTargets.length === 1 ? t("cpq.quotes.detail.arcTargets.one", "{count} target:", { count: arcTargets.length }) : t("cpq.quotes.detail.arcTargets.other", "{count} targets:", { count: arcTargets.length }),
532
564
  " ",
533
- arcTargets.map((t, i) => /* @__PURE__ */ jsxs("span", { children: [
565
+ arcTargets.map((t2, i) => /* @__PURE__ */ jsxs("span", { children: [
534
566
  i > 0 && ", ",
535
567
  /* @__PURE__ */ jsx(
536
568
  "button",
537
569
  {
538
- onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${t.subscriptionId}`),
570
+ onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${t2.subscriptionId}`),
539
571
  className: "text-brand-violet hover:underline",
540
- children: t.subscription?.code ?? t.subscriptionId.slice(0, 8)
572
+ children: t2.subscription?.code ?? t2.subscriptionId.slice(0, 8)
541
573
  }
542
574
  )
543
- ] }, t.id)),
544
- isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: "\u2022 Merging into a new contract" })
575
+ ] }, t2.id)),
576
+ isMergeRenew && /* @__PURE__ */ jsx("span", { className: "ml-2 text-status-warning-text font-medium", children: t("cpq.quotes.detail.mergingRenew", "\u2022 Merging into a new contract") })
545
577
  ] }) }) }),
546
578
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3", children: [
547
- /* @__PURE__ */ jsx(MetaCard, { label: "Customer", children: cpqQuote.customerName ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.customerName }) : /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
579
+ /* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.customer", "Customer"), children: cpqQuote.customerName ? /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.customerName }) : /* @__PURE__ */ jsxs("span", { className: "font-mono text-xs text-muted-foreground", children: [
548
580
  cpqQuote.customerId.slice(0, 8),
549
581
  "\u2026"
550
582
  ] }) }),
551
- /* @__PURE__ */ jsx(MetaCard, { label: "Version", children: /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
583
+ /* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.version", "Version"), children: /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
552
584
  "v",
553
585
  cpqQuote.version
554
586
  ] }) }),
555
- /* @__PURE__ */ jsx(MetaCard, { label: "Currency", children: /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency }) })
587
+ /* @__PURE__ */ jsx(MetaCard, { label: t("cpq.quotes.detail.meta.currency", "Currency"), children: /* @__PURE__ */ jsx("span", { className: "font-medium", children: currency }) })
556
588
  ] }),
557
589
  showDeleteConfirm && /* @__PURE__ */ jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", onClick: () => setShowDeleteConfirm(false), children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm rounded-lg border bg-card shadow-xl", onClick: (e) => e.stopPropagation(), children: [
558
- /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Delete Quote" }) }),
559
- /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Are you sure you want to delete this quote? This will soft-delete the quote and all its line items." }) }),
590
+ /* @__PURE__ */ jsx("div", { className: "px-4 py-3 border-b", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.quotes.detail.deleteConfirm.title", "Delete Quote") }) }),
591
+ /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.quotes.detail.deleteConfirm.text", "Are you sure you want to delete this quote? This will soft-delete the quote and all its line items.") }) }),
560
592
  /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2 px-4 py-3 border-t", children: [
561
- /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setShowDeleteConfirm(false), children: "Cancel" }),
593
+ /* @__PURE__ */ jsx(Button, { type: "button", variant: "outline", onClick: () => setShowDeleteConfirm(false), children: t("cpq.common.cancel", "Cancel") }),
562
594
  /* @__PURE__ */ jsxs(Button, { type: "button", variant: "destructive", onClick: deleteQuote, disabled: deleting, children: [
563
595
  deleting ? /* @__PURE__ */ jsx(Spinner, {}) : null,
564
- " Delete"
596
+ " ",
597
+ t("cpq.common.delete", "Delete")
565
598
  ] })
566
599
  ] })
567
600
  ] }) }),
568
601
  view === "summary" && /* @__PURE__ */ jsxs(Fragment, { children: [
569
- /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
570
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-3", children: "Pricing Summary" }),
571
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-4", children: [
572
- /* @__PURE__ */ jsxs("div", { children: [
573
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Non-Recurring (NRC)" }),
574
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.nrcTotal, currency) })
575
- ] }),
576
- /* @__PURE__ */ jsxs("div", { children: [
577
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Monthly Recurring (MRC)" }),
578
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.mrcTotal, currency) })
579
- ] }),
580
- summary.usageTotalEstimated != null && /* @__PURE__ */ jsxs("div", { children: [
581
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Usage (est.)" }),
582
- /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(summary.usageTotalEstimated, currency) })
583
- ] }),
584
- summary.usageCharges.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
585
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Usage Charges" }),
586
- summary.usageCharges.map((uc, i) => /* @__PURE__ */ jsxs("p", { className: "text-xs mt-0.5", children: [
587
- uc.chargeName,
588
- ": ",
589
- fmt(uc.unitPrice, currency),
590
- "/unit"
591
- ] }, i))
592
- ] })
593
- ] })
594
- ] }),
602
+ /* @__PURE__ */ jsx(PricingSummaryCard, { summary, currency, arcDelta }),
595
603
  !cpqQuote.validationResult.valid && /* @__PURE__ */ jsxs(Alert, { variant: "warning", children: [
596
- /* @__PURE__ */ jsx(AlertTitle, { children: "Validation Issues" }),
604
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("cpq.quotes.detail.validationIssues", "Validation Issues") }),
597
605
  /* @__PURE__ */ jsx(AlertDescription, { children: /* @__PURE__ */ jsx("span", { className: "text-xs space-y-0.5 block", children: cpqQuote.validationResult.errors.map((e, i) => /* @__PURE__ */ jsxs("span", { className: "block", children: [
598
606
  "\u2014 ",
599
607
  e.message
600
608
  ] }, i)) }) })
601
609
  ] }),
610
+ arcQuoteType === "amend" && cpqQuote.lines.some((l) => l.action !== "cancel" && l.configuration?._arcRepriced === true) && /* @__PURE__ */ jsxs(Alert, { status: "information", style: "lighter", children: [
611
+ /* @__PURE__ */ jsx(AlertTitle, { children: t("cpq.quotes.arc.repricedTitle", "Some existing items use the current catalog price") }),
612
+ /* @__PURE__ */ jsx(AlertDescription, { children: t(
613
+ "cpq.quotes.arc.repricedDescription",
614
+ "Reconfigured lines were re-priced at today's catalog price. Negotiated prices from the subscription are not carried over to reconfigured lines yet."
615
+ ) })
616
+ ] }),
602
617
  /* @__PURE__ */ jsx(
603
618
  QuoteLineTree,
604
619
  {
@@ -615,12 +630,24 @@ function CpqQuoteDetailPage(props) {
615
630
  pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === chargeIdx ? null : { lineIdx, chargeIdx }
616
631
  )
617
632
  }
633
+ ),
634
+ /* @__PURE__ */ jsx(
635
+ PriceAdjustmentsPanel,
636
+ {
637
+ quoteId: cpqQuote.id,
638
+ items: priceAdjustments.items,
639
+ total: priceAdjustments.total,
640
+ lines: cpqQuote.lines,
641
+ currency,
642
+ errorsById: priceAdjustments.errorsById
643
+ }
618
644
  )
619
645
  ] }),
620
646
  view === "add-offering" && /* @__PURE__ */ jsxs(Fragment, { children: [
621
647
  addingToParentLineId && /* @__PURE__ */ jsxs(Alert, { variant: "info", children: [
622
- "Adding component to bundle: ",
623
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.lines.find((l) => l.lineId === addingToParentLineId)?.offeringName ?? "Bundle" })
648
+ t("cpq.quotes.detail.addingComponentTo", "Adding component to bundle:"),
649
+ " ",
650
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: cpqQuote.lines.find((l) => l.lineId === addingToParentLineId)?.offeringName ?? t("cpq.quotes.detail.bundleFallback", "Bundle") })
624
651
  ] }),
625
652
  /* @__PURE__ */ jsx(
626
653
  OfferingBrowser,
@@ -636,14 +663,14 @@ function CpqQuoteDetailPage(props) {
636
663
  view === "configure" && selectedOffering && /* @__PURE__ */ jsx(
637
664
  ConfigurePanel,
638
665
  {
639
- title: addingToParentLineId ? `Add Component: ${selectedOffering.name}` : `Configure: ${selectedOffering.name}`,
666
+ title: addingToParentLineId ? t("cpq.quotes.detail.configure.addComponentTitle", "Add Component: {name}", { name: selectedOffering.name }) : t("cpq.quotes.detail.configure.configureTitle", "Configure: {name}", { name: selectedOffering.name }),
640
667
  attributes,
641
668
  config,
642
669
  quantity,
643
- arcTargetOptions: arcTargets.length >= 2 && !addingToParentLineId ? arcTargets.map((t) => ({
644
- subscriptionId: t.subscriptionId,
645
- code: t.subscription?.code ?? t.subscriptionId.slice(0, 8),
646
- name: t.subscription?.name ?? null
670
+ arcTargetOptions: arcTargets.length >= 2 && !addingToParentLineId ? arcTargets.map((t2) => ({
671
+ subscriptionId: t2.subscriptionId,
672
+ code: t2.subscription?.code ?? t2.subscriptionId.slice(0, 8),
673
+ name: t2.subscription?.name ?? null
647
674
  })) : null,
648
675
  arcSelectedTargetIds: addingTargetSubIds,
649
676
  onArcTargetToggle: (subId) => {
@@ -658,14 +685,16 @@ function CpqQuoteDetailPage(props) {
658
685
  },
659
686
  onQuantityChange: setQuantity,
660
687
  onSubmit: addItem,
661
- submitLabel: "Add to Quote",
688
+ submitLabel: t("cpq.quotes.detail.configure.addToQuote", "Add to Quote"),
662
689
  submitting
663
690
  }
664
691
  ),
665
692
  view === "edit" && editingLineId && /* @__PURE__ */ jsx(
666
693
  ConfigurePanel,
667
694
  {
668
- title: `Edit: ${cpqQuote.lines.find((l) => l.lineId === editingLineId)?.offeringName ?? "Line"}`,
695
+ title: t("cpq.quotes.detail.configure.editTitle", "Edit: {name}", {
696
+ name: cpqQuote.lines.find((l) => l.lineId === editingLineId)?.offeringName ?? t("cpq.quotes.detail.configure.lineFallback", "Line")
697
+ }),
669
698
  attributes,
670
699
  config,
671
700
  quantity,
@@ -681,13 +710,14 @@ function CpqQuoteDetailPage(props) {
681
710
  },
682
711
  onQuantityChange: setQuantity,
683
712
  onSubmit: () => updateItem(editingLineId),
684
- submitLabel: "Update Line",
713
+ submitLabel: t("cpq.quotes.detail.configure.updateLine", "Update Line"),
685
714
  submitting
686
715
  }
687
716
  )
688
- ] });
717
+ ] }) }) });
689
718
  }
690
719
  function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
720
+ const t = useT();
691
721
  const [bundleTrees, setBundleTrees] = React.useState(/* @__PURE__ */ new Map());
692
722
  const [loadingTrees, setLoadingTrees] = React.useState(/* @__PURE__ */ new Set());
693
723
  const rootLines = lines.filter((l) => !l.parentLineId);
@@ -703,7 +733,7 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
703
733
  if (bundleTrees.has(offeringId) || loadingTrees.has(offeringId)) return;
704
734
  setLoadingTrees((prev) => new Set(prev).add(offeringId));
705
735
  try {
706
- const result = await apiJson(`/api/cpq/bundle-tree?offeringId=${offeringId}`);
736
+ const result = await apiJson(t, `/api/cpq/bundle-tree?offeringId=${offeringId}`);
707
737
  setBundleTrees((prev) => new Map(prev).set(offeringId, result.tree));
708
738
  } catch {
709
739
  } finally {
@@ -713,10 +743,10 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
713
743
  return n;
714
744
  });
715
745
  }
716
- }, [bundleTrees, loadingTrees]);
746
+ }, [bundleTrees, loadingTrees, t]);
717
747
  return /* @__PURE__ */ jsxs("div", { children: [
718
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: "Line Items" }),
719
- lines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: 'No items configured yet. Click "Add Offering" to start.' }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: rootLines.map((line) => {
748
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: t("cpq.quotes.detail.lineItems", "Line Items") }),
749
+ lines.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: t("cpq.quotes.detail.noLines", 'No items configured yet. Click "Add Offering" to start.') }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: rootLines.map((line) => {
720
750
  const lineIdx = lines.indexOf(line);
721
751
  const isBundle = line.offeringType === "bundle";
722
752
  const children = childrenByParent.get(line.lineId) ?? [];
@@ -770,10 +800,12 @@ function QuoteLineTree({ lines, currency, expandedLines, pricingDetail, targetCo
770
800
  ] });
771
801
  }
772
802
  function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency, expandedLines, pricingDetail, parentLineId, targetCodes, onToggleLine, onEditLine, onRemoveLine, onAddComponent, onTogglePricingDetail }) {
803
+ const t = useT();
773
804
  if (treeLoading) {
774
805
  return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/5 px-8 py-3", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
775
806
  /* @__PURE__ */ jsx(Spinner, {}),
776
- " Loading bundle structure\u2026"
807
+ " ",
808
+ t("cpq.quotes.bundle.loadingStructure", "Loading bundle structure\u2026")
777
809
  ] }) });
778
810
  }
779
811
  if (!bundleTree || bundleTree.slots.length === 0) {
@@ -814,12 +846,13 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
814
846
  return /* @__PURE__ */ jsxs("div", { className: "border-b last:border-b-0", children: [
815
847
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-6 py-2 bg-muted/20", children: [
816
848
  /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground uppercase tracking-wide", children: slot.name }),
817
- isRequired ? /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "text-[10px] px-1.5", children: "required" }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "text-[10px] px-1.5", children: "optional" }),
849
+ isRequired ? /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "text-[10px] px-1.5", children: t("cpq.quotes.bundle.slotRequired", "required") }) : /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "text-[10px] px-1.5", children: t("cpq.quotes.bundle.slotOptional", "optional") }),
818
850
  /* @__PURE__ */ jsxs("span", { className: "text-[10px] text-muted-foreground", children: [
819
851
  filledCount,
820
852
  "/",
821
853
  slot.cardinalityMax ?? "\u221E",
822
- slot.cardinalityMin > 0 && ` (min ${slot.cardinalityMin})`
854
+ slot.cardinalityMin > 0 && " ",
855
+ slot.cardinalityMin > 0 && t("cpq.quotes.bundle.slotMin", "(min {min})", { min: slot.cardinalityMin })
823
856
  ] }),
824
857
  /* @__PURE__ */ jsx("div", { className: "flex-1" }),
825
858
  /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1", children: slot.components.map((comp) => {
@@ -830,7 +863,7 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
830
863
  onClick: () => !isFilled && canAdd && onAddComponent(parentLineId, comp.childOffering.offeringId),
831
864
  disabled: isFilled || !canAdd,
832
865
  className: `inline-flex items-center gap-1 rounded border px-2 py-1 text-[11px] font-medium transition-colors ${isFilled ? "border-status-success-border bg-status-success-bg text-status-success-text cursor-default" : canAdd ? "border-border hover:border-primary hover:bg-primary/5 hover:text-primary cursor-pointer" : "border-border text-muted-foreground opacity-50 cursor-not-allowed"}`,
833
- title: isFilled ? "Already added" : canAdd ? `Add ${comp.childOffering.offeringName}` : "Slot is full",
866
+ title: isFilled ? t("cpq.quotes.bundle.alreadyAdded", "Already added") : canAdd ? t("cpq.quotes.bundle.addComponent", "Add {name}", { name: comp.childOffering.offeringName }) : t("cpq.quotes.bundle.slotFull", "Slot is full"),
834
867
  children: [
835
868
  isFilled ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M4.5 12.75l6 6 9-13.5" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }),
836
869
  comp.childOffering.offeringName
@@ -889,229 +922,149 @@ function BundleSlotPanel({ bundleTree, treeLoading, children, allLines, currency
889
922
  ] });
890
923
  }
891
924
  function QuoteLineRow({ line, lineIdx, currency, isExpanded, pricingDetail, isBundle, childCount, indent, targetCodes, onToggle, onEdit, onRemove, onAddComponent, onTogglePricingDetail }) {
925
+ const t = useT();
926
+ const adjustmentUi = usePriceAdjustmentUi();
927
+ const removalUi = useArcLineRemovalUi();
928
+ const isRemoved = removalUi?.isRemoved(line) ?? false;
892
929
  const paddingLeft = indent > 0 ? `${indent * 2 + 1}rem` : void 0;
930
+ const chargeActions = {
931
+ reasonLabels: adjustmentUi?.reasonLabels,
932
+ renderChargeActions: (charge) => /* @__PURE__ */ jsx(AdjustChargeAction, { line, charge }),
933
+ renderManualActions: (adjustmentId) => /* @__PURE__ */ jsx(ManualAdjustmentActions, { adjustmentId })
934
+ };
893
935
  return /* @__PURE__ */ jsxs("div", { children: [
894
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors", style: paddingLeft ? { paddingLeft } : void 0, children: [
936
+ /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors ${isRemoved ? "bg-muted/20" : ""}`, style: paddingLeft ? { paddingLeft } : void 0, "data-testid": "cpq-quote-line", "data-removed": isRemoved ? "true" : void 0, children: [
895
937
  /* @__PURE__ */ jsx("button", { onClick: onToggle, className: "flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:text-foreground", children: /* @__PURE__ */ jsx("svg", { className: `h-4 w-4 transition-transform ${isExpanded ? "rotate-90" : ""}`, fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M8.25 4.5l7.5 7.5-7.5 7.5" }) }) }),
896
938
  /* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
897
939
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
898
940
  indent > 0 && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "\u2514" }),
899
- isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: "bundle" }),
900
- /* @__PURE__ */ jsx("span", { className: "font-medium text-sm", children: line.offeringName }),
941
+ isBundle && /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "px-1.5 text-xs", children: t("cpq.lines.bundle", "bundle") }),
942
+ /* @__PURE__ */ jsx("span", { className: `font-medium text-sm ${isRemoved ? "text-muted-foreground line-through" : ""}`, children: line.offeringName }),
901
943
  line.quantity > 1 && /* @__PURE__ */ jsxs("span", { className: "rounded bg-muted px-1.5 py-0.5 text-xs font-medium", children: [
902
944
  "\xD7",
903
945
  line.quantity
904
946
  ] }),
905
- /* @__PURE__ */ jsx(ActionBadge, { action: line.action }),
947
+ /* @__PURE__ */ jsx(
948
+ LineActionBadge,
949
+ {
950
+ action: line.action,
951
+ label: isRemoved ? t("cpq.quotes.arc.lineRemoved", "remove") : void 0,
952
+ title: isRemoved ? t("cpq.quotes.arc.lineRemovedHint", "This item is cancelled on the subscription when the order is activated") : void 0
953
+ }
954
+ ),
955
+ !isRemoved && line.configuration?._arcPriceFrozen === true && /* @__PURE__ */ jsx(
956
+ Tag,
957
+ {
958
+ variant: "neutral",
959
+ className: "px-1.5 text-xs",
960
+ title: t(
961
+ "cpq.quotes.arc.priceKeptHint",
962
+ "Price kept from the subscription \u2014 it changes only if you reconfigure this line"
963
+ ),
964
+ children: t("cpq.quotes.arc.priceKept", "price kept")
965
+ }
966
+ ),
906
967
  targetCodes.size >= 2 && line.targetSubscriptionId && targetCodes.get(line.targetSubscriptionId) && /* @__PURE__ */ jsxs(
907
968
  Tag,
908
969
  {
909
970
  variant: "brand",
910
971
  className: "px-1.5 text-xs",
911
- title: `Acts on subscription ${targetCodes.get(line.targetSubscriptionId)}`,
972
+ title: t("cpq.quotes.detail.actsOnSubscription", "Acts on subscription {code}", { code: targetCodes.get(line.targetSubscriptionId) ?? "" }),
912
973
  children: [
913
974
  "\u2192 ",
914
975
  targetCodes.get(line.targetSubscriptionId)
915
976
  ]
916
977
  }
917
978
  ),
918
- line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: "configured" }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: "incomplete" }),
919
- isBundle && /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
920
- childCount,
921
- " component",
922
- childCount !== 1 ? "s" : ""
923
- ] })
979
+ isRemoved ? null : line.isConfigured ? /* @__PURE__ */ jsx(Tag, { variant: "success", className: "px-1.5 text-xs", children: t("cpq.quotes.detail.lineConfigured", "configured") }) : /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "px-1.5 text-xs", children: t("cpq.quotes.detail.lineIncomplete", "incomplete") }),
980
+ isBundle && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: childCount !== 1 ? t("cpq.lines.componentCount.other", "{count} components", { count: childCount }) : t("cpq.lines.componentCount.one", "{count} component", { count: childCount }) })
924
981
  ] }),
925
982
  line.validationErrors && line.validationErrors.length > 0 && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-0.5", children: line.validationErrors[0].message })
926
983
  ] }),
927
984
  /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right text-xs space-y-0.5", children: [
985
+ isRemoved && line.arcSource && line.arcSource.mrcAmount > 0 && /* @__PURE__ */ jsxs("div", { children: [
986
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
987
+ t("cpq.lines.mrc", "MRC"),
988
+ " "
989
+ ] }),
990
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground line-through", children: fmt(line.arcSource.mrcAmount, currency) })
991
+ ] }),
928
992
  line.nrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
929
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "NRC " }),
993
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
994
+ t("cpq.lines.nrc", "NRC"),
995
+ " "
996
+ ] }),
930
997
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.nrcTotal, currency) })
931
998
  ] }),
932
999
  line.mrcTotal > 0 && /* @__PURE__ */ jsxs("div", { children: [
933
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "MRC " }),
1000
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1001
+ t("cpq.lines.mrc", "MRC"),
1002
+ " "
1003
+ ] }),
934
1004
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(line.mrcTotal, currency) })
935
1005
  ] })
936
1006
  ] }),
937
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
938
- onAddComponent && /* @__PURE__ */ jsx("button", { onClick: onAddComponent, className: "rounded p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors", title: "Add Component", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }) }),
939
- /* @__PURE__ */ jsx("button", { onClick: onEdit, className: "rounded p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors", title: "Edit", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" }) }) }),
940
- /* @__PURE__ */ jsx("button", { onClick: onRemove, className: "rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors", title: "Remove", children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }) })
1007
+ isRemoved ? /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 shrink-0", children: /* @__PURE__ */ jsxs(
1008
+ Button,
1009
+ {
1010
+ type: "button",
1011
+ variant: "ghost",
1012
+ size: "sm",
1013
+ onClick: () => removalUi?.restore(line.lineId),
1014
+ title: t("cpq.quotes.arc.restoreHint", "Keep this item on the subscription"),
1015
+ "data-testid": "cpq-line-restore",
1016
+ children: [
1017
+ /* @__PURE__ */ jsx(RotateCcw, { className: "h-4 w-4" }),
1018
+ t("cpq.quotes.arc.restore", "Restore")
1019
+ ]
1020
+ }
1021
+ ) }) : /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 shrink-0", children: [
1022
+ /* @__PURE__ */ jsx(DiscountLineAction, { line }),
1023
+ onAddComponent && /* @__PURE__ */ jsx("button", { onClick: onAddComponent, className: "rounded p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/5 transition-colors", title: t("cpq.quotes.detail.addComponent", "Add Component"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4.5v15m7.5-7.5h-15" }) }) }),
1024
+ /* @__PURE__ */ jsx("button", { onClick: onEdit, className: "rounded p-1.5 text-muted-foreground hover:text-foreground hover:bg-muted transition-colors", title: t("cpq.common.edit", "Edit"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10" }) }) }),
1025
+ /* @__PURE__ */ jsx("button", { onClick: onRemove, className: "rounded p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 transition-colors", title: t("cpq.quotes.detail.removeLine", "Remove"), children: /* @__PURE__ */ jsx("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", strokeWidth: "1.5", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0" }) }) })
941
1026
  ] })
942
1027
  ] }),
943
- isExpanded && line.arcSource && /* @__PURE__ */ jsx(ArcLineDiff, { line, currency }),
944
- isExpanded && !isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail }),
945
- isExpanded && isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail }),
1028
+ isExpanded && !isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
1029
+ isExpanded && isBundle && line.charges.length > 0 && /* @__PURE__ */ jsx(ChargeBreakdown, { line, lineIdx, currency, pricingDetail, onTogglePricingDetail, ...chargeActions }),
1030
+ isExpanded && line.arcSource && /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsx(ArcLineDiff, { line, currency }) }),
946
1031
  isExpanded && line.charges.length === 0 && !line.arcSource && /* @__PURE__ */ jsx(LineSummaryFallback, { line, currency })
947
1032
  ] });
948
1033
  }
949
- function ArcLineDiff({ line, currency }) {
950
- const src = line.arcSource;
951
- if (!src) return null;
952
- const isCancel = line.action === "cancel";
953
- const beforeMrc = src.mrcAmount;
954
- const beforeNrc = src.nrcAmount;
955
- const beforeQty = src.quantity;
956
- const afterMrc = isCancel ? 0 : line.mrcTotal;
957
- const afterNrc = isCancel ? 0 : line.nrcTotal;
958
- const afterQty = isCancel ? 0 : line.quantity;
959
- const fmtVal = (v, asCurrency) => asCurrency ? fmt(v, currency) : String(v);
960
- const rows = [
961
- { label: "Quantity", before: beforeQty, after: afterQty, asCurrency: false },
962
- { label: "MRC", before: beforeMrc, after: afterMrc, asCurrency: true },
963
- { label: "NRC", before: beforeNrc, after: afterNrc, asCurrency: true }
964
- ];
965
- return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/10 px-12 py-2 space-y-1.5", children: [
966
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground", children: "Change Preview" }),
967
- /* @__PURE__ */ jsx("div", { className: "rounded border bg-background overflow-hidden", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
968
- /* @__PURE__ */ jsx("thead", { className: "bg-muted/40", children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
969
- /* @__PURE__ */ jsx("th", { className: "text-left px-3 py-1.5 font-medium" }),
970
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "Before" }),
971
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "After" }),
972
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-1.5 font-medium", children: "\u0394" })
973
- ] }) }),
974
- /* @__PURE__ */ jsx("tbody", { children: rows.map((r) => {
975
- const delta = r.after - r.before;
976
- const showDelta = delta !== 0;
977
- return /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/40", children: [
978
- /* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-muted-foreground", children: r.label }),
979
- /* @__PURE__ */ jsx("td", { className: "px-3 py-1 text-right font-mono", children: fmtVal(r.before, r.asCurrency) }),
980
- /* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono ${isCancel ? "text-muted-foreground line-through" : ""}`, children: fmtVal(r.after, r.asCurrency) }),
981
- /* @__PURE__ */ jsx("td", { className: `px-3 py-1 text-right font-mono font-medium ${showDelta ? delta > 0 ? "text-status-success-text" : "text-destructive" : "text-muted-foreground"}`, children: showDelta ? `${delta > 0 ? "+" : ""}${fmtVal(delta, r.asCurrency)}` : "\u2014" })
982
- ] }, r.label);
983
- }) })
984
- ] }) }),
985
- isCancel && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive italic", children: "Cancellation \u2014 item will be terminated on activation." })
986
- ] });
987
- }
988
- function ActionBadge({ action }) {
989
- const variantByAction = {
990
- add: "info",
991
- modify: "warning",
992
- cancel: "error"
993
- };
994
- return /* @__PURE__ */ jsx(Tag, { variant: variantByAction[action], className: "px-1.5 text-xs", children: action });
995
- }
996
1034
  function LineSummaryFallback({ line, currency }) {
1035
+ const t = useT();
997
1036
  const hasTotals = line.nrcTotal > 0 || line.mrcTotal > 0;
998
1037
  const mirroredFromItemId = line.configuration?._arcMirroredFromItemId ?? null;
999
1038
  return /* @__PURE__ */ jsxs("div", { className: "border-t bg-muted/10 px-12 py-2 text-xs text-muted-foreground space-y-1", children: [
1000
1039
  /* @__PURE__ */ jsxs("div", { children: [
1001
- "No detailed charge breakdown available for this line.",
1002
- mirroredFromItemId && " Mirrored from existing subscription item."
1040
+ t("cpq.quotes.detail.noBreakdown", "No detailed charge breakdown available for this line."),
1041
+ mirroredFromItemId && " ",
1042
+ mirroredFromItemId && t("cpq.orders.detail.mirroredItem", "Mirrored from existing subscription item.")
1003
1043
  ] }),
1004
1044
  hasTotals && /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
1005
1045
  line.nrcTotal > 0 && /* @__PURE__ */ jsxs("span", { children: [
1006
- "NRC ",
1046
+ t("cpq.lines.nrc", "NRC"),
1047
+ " ",
1007
1048
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground", children: fmt(line.nrcTotal, currency) })
1008
1049
  ] }),
1009
1050
  line.mrcTotal > 0 && /* @__PURE__ */ jsxs("span", { children: [
1010
- "MRC ",
1051
+ t("cpq.lines.mrc", "MRC"),
1052
+ " ",
1011
1053
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium text-foreground", children: fmt(line.mrcTotal, currency) })
1012
1054
  ] })
1013
1055
  ] })
1014
1056
  ] });
1015
1057
  }
1016
- function ChargeBreakdown({ line, lineIdx, currency, pricingDetail, onTogglePricingDetail }) {
1017
- return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
1018
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
1019
- /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Charge" }),
1020
- /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Type" }),
1021
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Unit Price" }),
1022
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Qty" }),
1023
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Total" }),
1024
- /* @__PURE__ */ jsx("th", { className: "w-8" })
1025
- ] }) }),
1026
- /* @__PURE__ */ jsx("tbody", { children: line.charges.map((c, i) => {
1027
- const hasDetail = !!(c.breakdown?.tiers?.length || c.adjustments?.length);
1028
- const isDetailOpen = pricingDetail?.lineIdx === lineIdx && pricingDetail?.chargeIdx === i;
1029
- return /* @__PURE__ */ jsxs(React.Fragment, { children: [
1030
- /* @__PURE__ */ jsxs("tr", { className: "border-t border-dashed border-border/50", children: [
1031
- /* @__PURE__ */ jsx("td", { className: "py-1", children: c.chargeName }),
1032
- /* @__PURE__ */ jsx("td", { className: "py-1 uppercase", children: c.chargeType }),
1033
- /* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: fmt(c.unitPrice, currency) }),
1034
- /* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.quantity ?? "\u2014" }),
1035
- /* @__PURE__ */ jsx("td", { className: "py-1 text-right font-mono", children: c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? "\u2014" }),
1036
- /* @__PURE__ */ jsx("td", { className: "py-1 text-center", children: hasDetail && /* @__PURE__ */ jsx(
1037
- "button",
1038
- {
1039
- onClick: () => onTogglePricingDetail(lineIdx, i),
1040
- className: "inline-flex items-center justify-center rounded p-0.5 hover:bg-accent text-muted-foreground hover:text-foreground transition-colors",
1041
- title: "Show pricing breakdown",
1042
- children: /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", strokeWidth: 1.5, stroke: "currentColor", className: "w-3.5 h-3.5", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z" }) })
1043
- }
1044
- ) })
1045
- ] }),
1046
- isDetailOpen && /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: 6, className: "pb-3 pt-1", children: /* @__PURE__ */ jsxs("div", { className: "rounded border bg-background p-3 space-y-3 text-xs", children: [
1047
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1048
- /* @__PURE__ */ jsxs("span", { className: "font-semibold text-sm", children: [
1049
- "Pricing Details \u2014 ",
1050
- c.chargeName
1051
- ] }),
1052
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: c.pricingMethod ?? "flat" })
1053
- ] }),
1054
- c.breakdown?.tiers && c.breakdown.tiers.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1055
- /* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: "Tier Breakdown" }),
1056
- /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
1057
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
1058
- /* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Tier" }),
1059
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Range" }),
1060
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Price/Unit" }),
1061
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Qty" }),
1062
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Subtotal" })
1063
- ] }) }),
1064
- /* @__PURE__ */ jsxs("tbody", { children: [
1065
- c.breakdown.tiers.map((t) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
1066
- /* @__PURE__ */ jsx("td", { className: "py-0.5", children: t.tier }),
1067
- /* @__PURE__ */ jsxs("td", { className: "py-0.5 text-right font-mono", children: [
1068
- t.from,
1069
- "\u2013",
1070
- t.to
1071
- ] }),
1072
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(t.pricePerUnit, currency) }),
1073
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: t.quantity }),
1074
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(t.subtotal, currency) })
1075
- ] }, t.tier)),
1076
- /* @__PURE__ */ jsxs("tr", { className: "border-t font-medium", children: [
1077
- /* @__PURE__ */ jsx("td", { colSpan: 4, className: "py-0.5 text-right", children: "Tiered Total" }),
1078
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(c.breakdown.tiers.reduce((s, t) => s + t.subtotal, 0), currency) })
1079
- ] })
1080
- ] })
1081
- ] })
1082
- ] }),
1083
- c.adjustments && c.adjustments.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1084
- /* @__PURE__ */ jsx("div", { className: "font-medium text-muted-foreground mb-1", children: "Applied Price Rules" }),
1085
- /* @__PURE__ */ jsxs("table", { className: "w-full", children: [
1086
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
1087
- /* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Rule" }),
1088
- /* @__PURE__ */ jsx("th", { className: "text-left py-0.5 font-medium", children: "Type" }),
1089
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Value" }),
1090
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Before" }),
1091
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "After" }),
1092
- /* @__PURE__ */ jsx("th", { className: "text-right py-0.5 font-medium", children: "Delta" })
1093
- ] }) }),
1094
- /* @__PURE__ */ jsx("tbody", { children: c.adjustments.map((adj, ai) => /* @__PURE__ */ jsxs("tr", { className: "border-t border-border/30", children: [
1095
- /* @__PURE__ */ jsx("td", { className: "py-0.5", children: adj.ruleName }),
1096
- /* @__PURE__ */ jsx("td", { className: "py-0.5", children: RULE_TYPE_LABELS[adj.ruleType] ?? adj.ruleType }),
1097
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmtRuleValue(adj, currency) }),
1098
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(adj.unitPriceBefore, currency) }),
1099
- /* @__PURE__ */ jsx("td", { className: "py-0.5 text-right font-mono", children: fmt(adj.unitPriceAfter, currency) }),
1100
- /* @__PURE__ */ jsxs("td", { className: `py-0.5 text-right font-mono ${adj.delta < 0 ? "text-destructive" : adj.delta > 0 ? "text-status-success-text" : ""}`, children: [
1101
- adj.delta > 0 ? "+" : "",
1102
- fmt(adj.delta, currency)
1103
- ] })
1104
- ] }, ai)) })
1105
- ] })
1106
- ] }),
1107
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-1 border-t text-sm font-semibold", children: [
1108
- /* @__PURE__ */ jsx("span", { children: "Final Total" }),
1109
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: c.totalPrice != null ? fmt(c.totalPrice, currency) : "\u2014" })
1110
- ] })
1111
- ] }) }) })
1112
- ] }, i);
1113
- }) })
1114
- ] }) });
1058
+ function ChargeBreakdown({ line, lineIdx, pricingDetail, onTogglePricingDetail, ...tableProps }) {
1059
+ return /* @__PURE__ */ jsx("div", { className: "border-t bg-muted/10 px-12 py-2", children: /* @__PURE__ */ jsx(
1060
+ ChargeBreakdownTable,
1061
+ {
1062
+ charges: line.charges,
1063
+ openDetailIndex: pricingDetail?.lineIdx === lineIdx ? pricingDetail.chargeIdx : null,
1064
+ onToggleDetail: (chargeIdx) => onTogglePricingDetail(lineIdx, chargeIdx),
1065
+ ...tableProps
1066
+ }
1067
+ ) });
1115
1068
  }
1116
1069
  function Spinner() {
1117
1070
  return /* @__PURE__ */ jsxs("svg", { className: "h-4 w-4 animate-spin", viewBox: "0 0 24 24", fill: "none", children: [
@@ -1120,6 +1073,7 @@ function Spinner() {
1120
1073
  ] });
1121
1074
  }
1122
1075
  function ErrorBanner({ message, onDismiss }) {
1076
+ const t = useT();
1123
1077
  return /* @__PURE__ */ jsxs(Alert, { variant: "destructive", className: "flex items-start gap-2", children: [
1124
1078
  /* @__PURE__ */ jsx("span", { className: "flex-1", children: message }),
1125
1079
  onDismiss && /* @__PURE__ */ jsx(
@@ -1127,27 +1081,28 @@ function ErrorBanner({ message, onDismiss }) {
1127
1081
  {
1128
1082
  onClick: onDismiss,
1129
1083
  className: "text-destructive/70 hover:text-destructive text-sm font-bold",
1130
- "aria-label": "Dismiss",
1084
+ "aria-label": t("cpq.common.dismiss", "Dismiss"),
1131
1085
  children: "\xD7"
1132
1086
  }
1133
1087
  )
1134
1088
  ] });
1135
1089
  }
1136
1090
  function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect }) {
1091
+ const t = useT();
1137
1092
  const filtered = offerings.filter((o) => {
1138
1093
  if (!search.trim()) return true;
1139
1094
  const q = search.toLowerCase();
1140
1095
  return o.name.toLowerCase().includes(q) || o.code.toLowerCase().includes(q) || (o.description ?? "").toLowerCase().includes(q);
1141
1096
  });
1142
1097
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1143
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium px-1", children: "Select Product Offering" }),
1098
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium px-1", children: t("cpq.quotes.offeringBrowser.title", "Select Product Offering") }),
1144
1099
  /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1145
1100
  /* @__PURE__ */ jsx("svg", { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground", fill: "none", viewBox: "0 0 24 24", strokeWidth: "2", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z" }) }),
1146
1101
  /* @__PURE__ */ jsx(
1147
1102
  "input",
1148
1103
  {
1149
1104
  type: "text",
1150
- placeholder: "Search offerings\u2026",
1105
+ placeholder: t("cpq.quotes.offeringBrowser.searchPlaceholder", "Search offerings\u2026"),
1151
1106
  value: search,
1152
1107
  onChange: (e) => onSearchChange(e.target.value),
1153
1108
  className: "w-full rounded-md border bg-background pl-10 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50"
@@ -1156,8 +1111,9 @@ function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect
1156
1111
  ] }),
1157
1112
  loading ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-8 justify-center text-sm text-muted-foreground", children: [
1158
1113
  /* @__PURE__ */ jsx(Spinner, {}),
1159
- " Loading offerings\u2026"
1160
- ] }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: search ? "No offerings match your search." : "No active offerings available." }) : /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((offering) => /* @__PURE__ */ jsxs(
1114
+ " ",
1115
+ t("cpq.quotes.offeringBrowser.loading", "Loading offerings\u2026")
1116
+ ] }) : filtered.length === 0 ? /* @__PURE__ */ jsx("div", { className: "py-8 text-center text-sm text-muted-foreground", children: search ? t("cpq.quotes.offeringBrowser.noMatch", "No offerings match your search.") : t("cpq.quotes.offeringBrowser.empty", "No active offerings available.") }) : /* @__PURE__ */ jsx("div", { className: "grid gap-2 sm:grid-cols-2 lg:grid-cols-3", children: filtered.map((offering) => /* @__PURE__ */ jsxs(
1161
1117
  "button",
1162
1118
  {
1163
1119
  onClick: () => onSelect(offering),
@@ -1173,13 +1129,14 @@ function OfferingBrowser({ offerings, loading, search, onSearchChange, onSelect
1173
1129
  ] });
1174
1130
  }
1175
1131
  function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions, arcSelectedTargetIds, onArcTargetToggle, onConfigChange, onQuantityChange, onSubmit, submitLabel, submitting }) {
1132
+ const t = useT();
1176
1133
  const noTargetsPicked = arcTargetOptions !== null && arcTargetOptions.length > 0 && arcSelectedTargetIds.length === 0;
1177
1134
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card", children: [
1178
1135
  /* @__PURE__ */ jsx("div", { className: "border-b px-4 py-3", children: /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: title }) }),
1179
1136
  /* @__PURE__ */ jsxs("div", { className: "p-4 space-y-4", children: [
1180
1137
  arcTargetOptions && arcTargetOptions.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
1181
1138
  /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium mb-1", children: [
1182
- "Apply to subscription(s)",
1139
+ t("cpq.quotes.detail.configure.applyToSubscriptions", "Apply to subscription(s)"),
1183
1140
  /* @__PURE__ */ jsx("span", { className: "text-destructive ml-1", children: "*" })
1184
1141
  ] }),
1185
1142
  /* @__PURE__ */ jsx("div", { className: "space-y-1.5 rounded-md border bg-background px-3 py-2", children: arcTargetOptions.map((opt) => {
@@ -1208,11 +1165,11 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
1208
1165
  opt.subscriptionId
1209
1166
  );
1210
1167
  }) }),
1211
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: "Pick one or more \u2014 a separate line will be created on each selected subscription." }),
1212
- noTargetsPicked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1", children: "Select at least one target." })
1168
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: t("cpq.quotes.detail.configure.applyToSubscriptionsHint", "Pick one or more \u2014 a separate line will be created on each selected subscription.") }),
1169
+ noTargetsPicked && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive mt-1", children: t("cpq.quotes.detail.configure.selectTarget", "Select at least one target.") })
1213
1170
  ] }),
1214
1171
  /* @__PURE__ */ jsxs("div", { children: [
1215
- /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: "Quantity" }),
1172
+ /* @__PURE__ */ jsx("label", { className: "block text-sm font-medium mb-1", children: t("cpq.quotes.detail.configure.quantity", "Quantity") }),
1216
1173
  /* @__PURE__ */ jsx(
1217
1174
  NumberInput,
1218
1175
  {
@@ -1224,7 +1181,7 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
1224
1181
  }
1225
1182
  )
1226
1183
  ] }),
1227
- attributes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "No configurable attributes for this offering." }) : attributes.map((attr) => /* @__PURE__ */ jsx(AttributeField, { attribute: attr, value: config[attr.code], onChange: (v) => onConfigChange(attr.code, v) }, attr.code))
1184
+ attributes.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.quotes.detail.configure.noAttributes", "No configurable attributes for this offering.") }) : attributes.map((attr) => /* @__PURE__ */ jsx(AttributeField, { attribute: attr, value: config[attr.code], onChange: (v) => onConfigChange(attr.code, v) }, attr.code))
1228
1185
  ] }),
1229
1186
  /* @__PURE__ */ jsx("div", { className: "border-t px-4 py-3 flex justify-end", children: /* @__PURE__ */ jsxs(
1230
1187
  "button",
@@ -1242,6 +1199,7 @@ function ConfigurePanel({ title, attributes, config, quantity, arcTargetOptions,
1242
1199
  ] });
1243
1200
  }
1244
1201
  function AttributeField({ attribute, value, onChange }) {
1202
+ const t = useT();
1245
1203
  const { code, name, attributeType, isRequired, options, constraints, helpText, defaultValue } = attribute;
1246
1204
  const currentValue = value ?? defaultValue ?? "";
1247
1205
  return /* @__PURE__ */ jsxs("div", { children: [
@@ -1256,13 +1214,13 @@ function AttributeField({ attribute, value, onChange }) {
1256
1214
  onChange: (e) => onChange(e.target.value),
1257
1215
  className: "w-full rounded-md border bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary/50",
1258
1216
  children: [
1259
- /* @__PURE__ */ jsx("option", { value: "", children: "\u2014 Select \u2014" }),
1217
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.quotes.detail.configure.selectPlaceholder", "\u2014 Select \u2014") }),
1260
1218
  options.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
1261
1219
  ]
1262
1220
  }
1263
1221
  ) : attributeType === "boolean" ? /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2", children: [
1264
1222
  /* @__PURE__ */ jsx("input", { type: "checkbox", checked: Boolean(currentValue), onChange: (e) => onChange(e.target.checked), className: "rounded border-border" }),
1265
- /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: helpText ?? "Enable" })
1223
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: helpText ?? t("cpq.quotes.detail.configure.enable", "Enable") })
1266
1224
  ] }) : attributeType === "number" ? /* @__PURE__ */ jsx(
1267
1225
  NumberInput,
1268
1226
  {