@dainamite/cpq 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +3 -9
  3. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +2 -2
  4. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +21 -25
  5. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +2 -2
  6. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +48 -47
  7. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +2 -2
  8. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +1 -1
  9. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +2 -2
  10. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +2 -2
  11. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +2 -2
  12. package/dist/modules/cpq/backend/cpq/inventory/shared.js +37 -2
  13. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +2 -2
  14. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +154 -178
  15. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +2 -2
  16. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +1 -1
  17. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +2 -2
  18. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +168 -143
  19. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +2 -2
  20. package/dist/modules/cpq/backend/cpq/offerings/page.js +36 -34
  21. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +2 -2
  22. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +69 -66
  23. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +2 -2
  24. package/dist/modules/cpq/backend/cpq/orders/page.js +6 -6
  25. package/dist/modules/cpq/backend/cpq/orders/page.js.map +2 -2
  26. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +41 -36
  27. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +2 -2
  28. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +76 -72
  29. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +2 -2
  30. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +1 -1
  31. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +2 -2
  32. package/dist/modules/cpq/backend/cpq/price-rules/page.js +18 -15
  33. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +2 -2
  34. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +23 -21
  35. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +2 -2
  36. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +22 -50
  37. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +2 -2
  38. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +74 -52
  39. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +2 -2
  40. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +20 -17
  41. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +2 -2
  42. package/dist/modules/cpq/backend/cpq/pricing/page.js +1 -1
  43. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +2 -2
  44. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +115 -123
  45. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +2 -2
  46. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js +6 -3
  47. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.js.map +2 -2
  48. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +129 -94
  49. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +2 -2
  50. package/dist/modules/cpq/backend/cpq/quotes/page.js +23 -19
  51. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +2 -2
  52. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +19 -15
  53. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +2 -2
  54. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +171 -178
  55. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +2 -2
  56. package/dist/modules/cpq/backend/cpq/specifications/page.js +2 -2
  57. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +2 -2
  58. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +60 -50
  59. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +2 -2
  60. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +22 -18
  61. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +2 -2
  62. package/dist/modules/cpq/backend/cpq/wizards/page.js +1 -1
  63. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +2 -2
  64. package/dist/modules/cpq/components/ArcLineDiff.js +2 -2
  65. package/dist/modules/cpq/components/ArcLineDiff.js.map +2 -2
  66. package/dist/modules/cpq/components/ChargeBreakdownTable.js +4 -10
  67. package/dist/modules/cpq/components/ChargeBreakdownTable.js.map +2 -2
  68. package/dist/modules/cpq/components/PriceWaterfall.js +3 -1
  69. package/dist/modules/cpq/components/PriceWaterfall.js.map +2 -2
  70. package/dist/modules/cpq/components/PricingSummaryCard.js +1 -6
  71. package/dist/modules/cpq/components/PricingSummaryCard.js.map +2 -2
  72. package/dist/modules/cpq/components/StatusPath.js +5 -3
  73. package/dist/modules/cpq/components/StatusPath.js.map +2 -2
  74. package/dist/modules/cpq/components/useCpqRowActions.js +10 -22
  75. package/dist/modules/cpq/components/useCpqRowActions.js.map +2 -2
  76. package/dist/modules/cpq/i18n/de.json +1220 -0
  77. package/dist/modules/cpq/i18n/en.json +1220 -0
  78. package/dist/modules/cpq/i18n/es.json +1220 -0
  79. package/dist/modules/cpq/i18n/pl.json +1220 -0
  80. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +39 -31
  81. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +2 -2
  82. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +13 -5
  83. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +2 -2
  84. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +41 -21
  85. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +2 -2
  86. package/dist/modules/cpq/widgets/injection-table.js +2 -2
  87. package/dist/modules/cpq/widgets/injection-table.js.map +2 -2
  88. package/dist/modules/cpq/workflows/WizardRunner.js +10 -8
  89. package/dist/modules/cpq/workflows/WizardRunner.js.map +2 -2
  90. package/dist/modules/cpq/workflows/WizardTrigger.js +15 -9
  91. package/dist/modules/cpq/workflows/WizardTrigger.js.map +2 -2
  92. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +9 -7
  93. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +2 -2
  94. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +9 -4
  95. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +2 -2
  96. package/dist/modules/cpq/workflows/steps/ItemListStep.js +16 -19
  97. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +2 -2
  98. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +19 -32
  99. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +2 -2
  100. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +8 -6
  101. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +2 -2
  102. package/dist/modules/cpq/workflows/steps/ReviewStep.js +32 -30
  103. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +2 -2
  104. package/dist/modules/cpq/workflows/steps/index.js +12 -0
  105. package/dist/modules/cpq/workflows/steps/index.js.map +2 -2
  106. package/package.json +1 -1
  107. package/src/modules/cpq/__tests__/i18n/dictionaries.test.ts +62 -0
  108. package/src/modules/cpq/__tests__/i18n/hardcodedStrings.test.ts +35 -0
  109. package/src/modules/cpq/__tests__/i18n/sourceScan.ts +109 -0
  110. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +3 -9
  111. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +22 -26
  112. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +1 -1
  113. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +43 -46
  114. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +2 -2
  115. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +37 -1
  116. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +1 -1
  117. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +134 -112
  118. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +169 -130
  119. package/src/modules/cpq/backend/cpq/offerings/page.tsx +34 -24
  120. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +66 -43
  121. package/src/modules/cpq/backend/cpq/orders/page.tsx +6 -6
  122. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +48 -34
  123. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +1 -1
  124. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +76 -70
  125. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +25 -16
  126. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +24 -18
  127. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +29 -19
  128. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +95 -47
  129. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +23 -15
  130. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  131. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +111 -88
  132. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/PriceAdjustmentDialog.tsx +3 -3
  133. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +134 -89
  134. package/src/modules/cpq/backend/cpq/quotes/page.tsx +29 -21
  135. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +19 -15
  136. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +171 -171
  137. package/src/modules/cpq/backend/cpq/specifications/page.tsx +2 -2
  138. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +51 -41
  139. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +17 -13
  140. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  141. package/src/modules/cpq/components/ArcLineDiff.tsx +2 -2
  142. package/src/modules/cpq/components/ChargeBreakdownTable.tsx +4 -2
  143. package/src/modules/cpq/components/PriceWaterfall.tsx +4 -2
  144. package/src/modules/cpq/components/PricingSummaryCard.tsx +1 -1
  145. package/src/modules/cpq/components/StatusPath.tsx +7 -4
  146. package/src/modules/cpq/components/useCpqRowActions.tsx +17 -25
  147. package/src/modules/cpq/i18n/de.json +1220 -0
  148. package/src/modules/cpq/i18n/en.json +1220 -0
  149. package/src/modules/cpq/i18n/es.json +1220 -0
  150. package/src/modules/cpq/i18n/pl.json +1220 -0
  151. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +38 -31
  152. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +7 -5
  153. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +36 -21
  154. package/src/modules/cpq/widgets/injection-table.ts +8 -2
  155. package/src/modules/cpq/workflows/WizardRunner.tsx +8 -5
  156. package/src/modules/cpq/workflows/WizardTrigger.tsx +15 -9
  157. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +11 -7
  158. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +9 -5
  159. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +18 -12
  160. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +20 -16
  161. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +8 -6
  162. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +33 -18
  163. package/src/modules/cpq/workflows/steps/index.ts +12 -0
  164. package/src/modules/cpq/workflows/types.ts +4 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useRouter } from 'next/navigation'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport {\n formatStatusLabel,\n quoteCpqStatusMap,\n type QuoteCpqStatus,\n} from '../../../components/statusMaps'\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ResolvedCharge = {\n chargeCode: string\n chargeName: string\n chargeType: 'nrc' | 'mrc' | 'usage'\n unitPrice: number\n quantity: number | null\n totalPrice: number | null\n currencyCode: string\n note?: string | null\n}\n\ntype QuoteLine = {\n lineId: string\n offeringId: string | null\n offeringName: string\n quantity: number\n nrcTotal: number\n mrcTotal: number\n charges: ResolvedCharge[]\n isConfigured: boolean\n validationErrors: Array<{ message: string }> | null\n}\n\ntype QuoteResult = {\n id: string\n quoteId: string\n quoteNumber: string\n customerId: string\n cpqStatus: string\n version: number\n currencyCode: string\n pricingSummary: {\n nrcTotal: number\n mrcTotal: number\n usageCharges: Array<{ chargeCode: string; chargeName: string; unitPrice: number; note: string | null }>\n usageTotalEstimated: number | null\n currencyCode: string\n }\n lines: QuoteLine[]\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction fmt(amount: number, currency: string): string {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\n// \u2500\u2500\u2500 Main Widget (read-only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default function QuoteConfiguratorWidget({ data }: InjectionWidgetComponentProps) {\n const router = useRouter()\n const record = data as Record<string, unknown> | null\n const quoteId = (record?.id as string) ?? null\n\n const [cpqQuote, setCpqQuote] = React.useState<QuoteResult | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [expandedLines, setExpandedLines] = React.useState<Set<string>>(new Set())\n\n React.useEffect(() => {\n if (!quoteId) { setLoading(false); return }\n let cancelled = false\n async function load() {\n try {\n const res = await fetch(`/api/cpq/quotes/${quoteId}`)\n if (res.ok && !cancelled) setCpqQuote(await res.json())\n } catch { /* no CPQ config */ }\n finally { if (!cancelled) setLoading(false) }\n }\n load()\n return () => { cancelled = true }\n }, [quoteId])\n\n if (!quoteId) return <div className=\"p-4 text-sm text-muted-foreground\">No quote context available.</div>\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 p-6 text-sm text-muted-foreground\">\n <Spinner /> Loading CPQ configuration\u2026\n </div>\n )\n }\n\n // \u2500\u2500\u2500 No CPQ config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n if (!cpqQuote) {\n const custId = (record?.customerEntityId as string) ?? (record?.customer_entity_id as string) ?? ''\n return (\n <div className=\"flex flex-col items-center justify-center py-12 px-4\">\n <div className=\"rounded-full bg-primary/10 p-3 mb-3\">\n <svg className=\"h-6 w-6 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"1.5\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n </div>\n <p className=\"text-sm text-muted-foreground mb-4\">This quote has no CPQ configuration yet.</p>\n <button\n onClick={() => router.push(`/backend/cpq/quotes/new?quoteId=${quoteId}&customerId=${custId}`)}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors\"\n >\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"2\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" />\n </svg>\n Create CPQ Quote\n </button>\n {!custId && <p className=\"mt-2 text-xs text-status-warning-text\">Assign a customer to this quote first.</p>}\n </div>\n )\n }\n\n // \u2500\u2500\u2500 Read-only summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const { pricingSummary: summary, currencyCode: currency } = cpqQuote\n const toggleLine = (id: string) => {\n setExpandedLines((prev) => {\n const next = new Set(prev)\n next.has(id) ? next.delete(id) : next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-1\">\n <div className=\"flex items-center gap-3\">\n <Tag variant={quoteCpqStatusMap[cpqQuote.cpqStatus as QuoteCpqStatus] ?? 'neutral'} dot>\n {formatStatusLabel(cpqQuote.cpqStatus)}\n </Tag>\n <span className=\"text-xs text-muted-foreground\">v{cpqQuote.version}</span>\n <span className=\"text-xs text-muted-foreground\">{currency}</span>\n </div>\n <button\n onClick={() => router.push(`/backend/cpq/quotes/${cpqQuote.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors\"\n >\n Open CPQ Configurator\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"2\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </button>\n </div>\n\n {/* Lines */}\n {cpqQuote.lines.length === 0 ? (\n <div className=\"rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground\">\n No items configured yet.\n </div>\n ) : (\n <div className=\"rounded-lg border bg-card divide-y\">\n {cpqQuote.lines.map((line) => {\n const isExpanded = expandedLines.has(line.lineId)\n return (\n <div key={line.lineId}>\n <div\n className=\"flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors cursor-pointer\"\n onClick={() => toggleLine(line.lineId)}\n >\n <button className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground\">\n <svg className={`h-4 w-4 transition-transform ${isExpanded ? 'rotate-90' : ''}`} fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"2\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-sm\">{line.offeringName}</span>\n {line.quantity > 1 && <span className=\"rounded bg-muted px-1.5 py-0.5 text-xs font-medium\">\u00D7{line.quantity}</span>}\n {line.isConfigured ? (\n <Tag variant=\"success\" className=\"px-1.5 text-xs\">configured</Tag>\n ) : (\n <Tag variant=\"warning\" className=\"px-1.5 text-xs\">incomplete</Tag>\n )}\n </div>\n {line.validationErrors && line.validationErrors.length > 0 && (\n <p className=\"text-xs text-destructive mt-0.5\">{line.validationErrors[0].message}</p>\n )}\n </div>\n <div className=\"shrink-0 text-right text-xs space-y-0.5\">\n {line.nrcTotal > 0 && <div><span className=\"text-muted-foreground\">NRC </span><span className=\"font-mono font-medium\">{fmt(line.nrcTotal, currency)}</span></div>}\n {line.mrcTotal > 0 && <div><span className=\"text-muted-foreground\">MRC </span><span className=\"font-mono font-medium\">{fmt(line.mrcTotal, currency)}</span></div>}\n </div>\n </div>\n\n {/* Charge breakdown */}\n {isExpanded && line.charges.length > 0 && (\n <div className=\"border-t bg-muted/10 px-12 py-2\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-1 font-medium\">Charge</th>\n <th className=\"text-left py-1 font-medium\">Type</th>\n <th className=\"text-right py-1 font-medium\">Unit Price</th>\n <th className=\"text-right py-1 font-medium\">Qty</th>\n <th className=\"text-right py-1 font-medium\">Total</th>\n </tr>\n </thead>\n <tbody>\n {line.charges.map((c, i) => (\n <tr key={i} className=\"border-t border-dashed border-border/50\">\n <td className=\"py-1\">{c.chargeName}</td>\n <td className=\"py-1 uppercase\">{c.chargeType}</td>\n <td className=\"py-1 text-right font-mono\">{fmt(c.unitPrice, currency)}</td>\n <td className=\"py-1 text-right font-mono\">{c.quantity ?? '\u2014'}</td>\n <td className=\"py-1 text-right font-mono\">{c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? '\u2014'}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n )\n })}\n </div>\n )}\n\n {/* Pricing Summary */}\n {cpqQuote.lines.length > 0 && (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"grid grid-cols-2 sm:grid-cols-3 gap-4\">\n <div>\n <p className=\"text-xs text-muted-foreground\">Non-Recurring (NRC)</p>\n <p className=\"text-lg font-semibold font-mono\">{fmt(summary.nrcTotal, currency)}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">Monthly Recurring (MRC)</p>\n <p className=\"text-lg font-semibold font-mono\">{fmt(summary.mrcTotal, currency)}</p>\n </div>\n {summary.usageCharges.length > 0 && (\n <div>\n <p className=\"text-xs text-muted-foreground\">Usage Charges</p>\n <div className=\"mt-1 space-y-0.5\">\n {summary.usageCharges.map((uc, i) => (\n <p key={i} className=\"text-xs\">{uc.chargeName}: {fmt(uc.unitPrice, currency)}/unit</p>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction Spinner() {\n return (\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport type { InjectionWidgetComponentProps } from '@open-mercato/shared/modules/widgets/injection'\nimport { useRouter } from 'next/navigation'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport {\n formatStatusLabel,\n quoteCpqStatusMap,\n type QuoteCpqStatus,\n} from '../../../components/statusMaps'\n\n// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ntype ResolvedCharge = {\n chargeCode: string\n chargeName: string\n chargeType: 'nrc' | 'mrc' | 'usage'\n unitPrice: number\n quantity: number | null\n totalPrice: number | null\n currencyCode: string\n note?: string | null\n}\n\ntype QuoteLine = {\n lineId: string\n offeringId: string | null\n offeringName: string\n quantity: number\n nrcTotal: number\n mrcTotal: number\n charges: ResolvedCharge[]\n isConfigured: boolean\n validationErrors: Array<{ message: string }> | null\n}\n\ntype QuoteResult = {\n id: string\n quoteId: string\n quoteNumber: string\n customerId: string\n cpqStatus: string\n version: number\n currencyCode: string\n pricingSummary: {\n nrcTotal: number\n mrcTotal: number\n usageCharges: Array<{ chargeCode: string; chargeName: string; unitPrice: number; note: string | null }>\n usageTotalEstimated: number | null\n currencyCode: string\n }\n lines: QuoteLine[]\n}\n\n// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction fmt(amount: number, currency: string): string {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nfunction chargeTypeLabel(t: TranslateFn, chargeType: ResolvedCharge['chargeType']): string {\n switch (chargeType) {\n case 'nrc':\n return t('cpq.lines.chargeType.nrc', 'nrc')\n case 'mrc':\n return t('cpq.lines.chargeType.mrc', 'mrc')\n case 'usage':\n return t('cpq.lines.chargeType.usage', 'usage')\n default:\n return chargeType\n }\n}\n\n// \u2500\u2500\u2500 Main Widget (read-only) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport default function QuoteConfiguratorWidget({ data }: InjectionWidgetComponentProps) {\n const router = useRouter()\n const t = useT()\n const record = data as Record<string, unknown> | null\n const quoteId = (record?.id as string) ?? null\n\n const [cpqQuote, setCpqQuote] = React.useState<QuoteResult | null>(null)\n const [loading, setLoading] = React.useState(true)\n const [expandedLines, setExpandedLines] = React.useState<Set<string>>(new Set())\n\n React.useEffect(() => {\n if (!quoteId) { setLoading(false); return }\n let cancelled = false\n async function load() {\n try {\n const res = await fetch(`/api/cpq/quotes/${quoteId}`)\n if (res.ok && !cancelled) setCpqQuote(await res.json())\n } catch { /* no CPQ config */ }\n finally { if (!cancelled) setLoading(false) }\n }\n load()\n return () => { cancelled = true }\n }, [quoteId])\n\n if (!quoteId) return <div className=\"p-4 text-sm text-muted-foreground\">{t('cpq.widgets.quoteConfigurator.noContext', 'No quote context available.')}</div>\n\n if (loading) {\n return (\n <div className=\"flex items-center gap-2 p-6 text-sm text-muted-foreground\">\n <Spinner /> {t('cpq.widgets.quoteConfigurator.loading', 'Loading CPQ configuration\u2026')}\n </div>\n )\n }\n\n // \u2500\u2500\u2500 No CPQ config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n if (!cpqQuote) {\n const custId = (record?.customerEntityId as string) ?? (record?.customer_entity_id as string) ?? ''\n return (\n <div className=\"flex flex-col items-center justify-center py-12 px-4\">\n <div className=\"rounded-full bg-primary/10 p-3 mb-3\">\n <svg className=\"h-6 w-6 text-primary\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"1.5\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 010 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 010-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28z\" />\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M15 12a3 3 0 11-6 0 3 3 0 016 0z\" />\n </svg>\n </div>\n <p className=\"text-sm text-muted-foreground mb-4\">{t('cpq.widgets.quoteConfigurator.noConfig', 'This quote has no CPQ configuration yet.')}</p>\n <button\n onClick={() => router.push(`/backend/cpq/quotes/new?quoteId=${quoteId}&customerId=${custId}`)}\n className=\"inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors\"\n >\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"2\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 4.5v15m7.5-7.5h-15\" />\n </svg>\n {t('cpq.widgets.quoteConfigurator.create', 'Create CPQ Quote')}\n </button>\n {!custId && <p className=\"mt-2 text-xs text-status-warning-text\">{t('cpq.widgets.quoteConfigurator.assignCustomer', 'Assign a customer to this quote first.')}</p>}\n </div>\n )\n }\n\n // \u2500\u2500\u2500 Read-only summary \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n const { pricingSummary: summary, currencyCode: currency } = cpqQuote\n const toggleLine = (id: string) => {\n setExpandedLines((prev) => {\n const next = new Set(prev)\n next.has(id) ? next.delete(id) : next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"space-y-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-1\">\n <div className=\"flex items-center gap-3\">\n <Tag variant={quoteCpqStatusMap[cpqQuote.cpqStatus as QuoteCpqStatus] ?? 'neutral'} dot>\n {formatStatusLabel(cpqQuote.cpqStatus)}\n </Tag>\n <span className=\"text-xs text-muted-foreground\">v{cpqQuote.version}</span>\n <span className=\"text-xs text-muted-foreground\">{currency}</span>\n </div>\n <button\n onClick={() => router.push(`/backend/cpq/quotes/${cpqQuote.id}`)}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground shadow-sm hover:bg-primary/90 transition-colors\"\n >\n {t('cpq.widgets.quoteConfigurator.open', 'Open CPQ Configurator')}\n <svg className=\"h-4 w-4\" fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"2\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25\" />\n </svg>\n </button>\n </div>\n\n {/* Lines */}\n {cpqQuote.lines.length === 0 ? (\n <div className=\"rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground\">\n {t('cpq.widgets.quoteConfigurator.noLines', 'No items configured yet.')}\n </div>\n ) : (\n <div className=\"rounded-lg border bg-card divide-y\">\n {cpqQuote.lines.map((line) => {\n const isExpanded = expandedLines.has(line.lineId)\n return (\n <div key={line.lineId}>\n <div\n className=\"flex items-center gap-3 px-4 py-3 hover:bg-muted/30 transition-colors cursor-pointer\"\n onClick={() => toggleLine(line.lineId)}\n >\n <button className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground\">\n <svg className={`h-4 w-4 transition-transform ${isExpanded ? 'rotate-90' : ''}`} fill=\"none\" viewBox=\"0 0 24 24\" strokeWidth=\"2\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8.25 4.5l7.5 7.5-7.5 7.5\" />\n </svg>\n </button>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <span className=\"font-medium text-sm\">{line.offeringName}</span>\n {line.quantity > 1 && <span className=\"rounded bg-muted px-1.5 py-0.5 text-xs font-medium\">\u00D7{line.quantity}</span>}\n {line.isConfigured ? (\n <Tag variant=\"success\" className=\"px-1.5 text-xs\">{t('cpq.quotes.detail.lineConfigured', 'configured')}</Tag>\n ) : (\n <Tag variant=\"warning\" className=\"px-1.5 text-xs\">{t('cpq.quotes.detail.lineIncomplete', 'incomplete')}</Tag>\n )}\n </div>\n {line.validationErrors && line.validationErrors.length > 0 && (\n <p className=\"text-xs text-destructive mt-0.5\">{line.validationErrors[0].message}</p>\n )}\n </div>\n <div className=\"shrink-0 text-right text-xs space-y-0.5\">\n {line.nrcTotal > 0 && <div><span className=\"text-muted-foreground\">{t('cpq.lines.nrc', 'NRC')} </span><span className=\"font-mono font-medium\">{fmt(line.nrcTotal, currency)}</span></div>}\n {line.mrcTotal > 0 && <div><span className=\"text-muted-foreground\">{t('cpq.lines.mrc', 'MRC')} </span><span className=\"font-mono font-medium\">{fmt(line.mrcTotal, currency)}</span></div>}\n </div>\n </div>\n\n {/* Charge breakdown */}\n {isExpanded && line.charges.length > 0 && (\n <div className=\"border-t bg-muted/10 px-12 py-2\">\n <table className=\"w-full text-xs\">\n <thead>\n <tr className=\"text-muted-foreground\">\n <th className=\"text-left py-1 font-medium\">{t('cpq.widgets.quoteConfigurator.charges.charge', 'Charge')}</th>\n <th className=\"text-left py-1 font-medium\">{t('cpq.widgets.quoteConfigurator.charges.type', 'Type')}</th>\n <th className=\"text-right py-1 font-medium\">{t('cpq.widgets.quoteConfigurator.charges.unitPrice', 'Unit Price')}</th>\n <th className=\"text-right py-1 font-medium\">{t('cpq.widgets.quoteConfigurator.charges.qty', 'Qty')}</th>\n <th className=\"text-right py-1 font-medium\">{t('cpq.widgets.quoteConfigurator.charges.total', 'Total')}</th>\n </tr>\n </thead>\n <tbody>\n {line.charges.map((c, i) => (\n <tr key={i} className=\"border-t border-dashed border-border/50\">\n <td className=\"py-1\">{c.chargeName}</td>\n <td className=\"py-1 uppercase\">{chargeTypeLabel(t, c.chargeType)}</td>\n <td className=\"py-1 text-right font-mono\">{fmt(c.unitPrice, currency)}</td>\n <td className=\"py-1 text-right font-mono\">{c.quantity ?? '\u2014'}</td>\n <td className=\"py-1 text-right font-mono\">{c.totalPrice != null ? fmt(c.totalPrice, currency) : c.note ?? '\u2014'}</td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </div>\n )\n })}\n </div>\n )}\n\n {/* Pricing Summary */}\n {cpqQuote.lines.length > 0 && (\n <div className=\"rounded-lg border bg-card p-4\">\n <div className=\"grid grid-cols-2 sm:grid-cols-3 gap-4\">\n <div>\n <p className=\"text-xs text-muted-foreground\">{t('cpq.quotes.summary.nrc', 'Non-Recurring (NRC)')}</p>\n <p className=\"text-lg font-semibold font-mono\">{fmt(summary.nrcTotal, currency)}</p>\n </div>\n <div>\n <p className=\"text-xs text-muted-foreground\">{t('cpq.quotes.summary.mrc', 'Monthly Recurring (MRC)')}</p>\n <p className=\"text-lg font-semibold font-mono\">{fmt(summary.mrcTotal, currency)}</p>\n </div>\n {summary.usageCharges.length > 0 && (\n <div>\n <p className=\"text-xs text-muted-foreground\">{t('cpq.quotes.summary.usageCharges', 'Usage Charges')}</p>\n <div className=\"mt-1 space-y-0.5\">\n {summary.usageCharges.map((uc, i) => (\n <p key={i} className=\"text-xs\">{uc.chargeName}: {t('cpq.lines.perUnit', '{price}/unit', { price: fmt(uc.unitPrice, currency) })}</p>\n ))}\n </div>\n </div>\n )}\n </div>\n </div>\n )}\n </div>\n )\n}\n\nfunction Spinner() {\n return (\n <svg className=\"h-4 w-4 animate-spin\" viewBox=\"0 0 24 24\" fill=\"none\">\n <circle className=\"opacity-25\" cx=\"12\" cy=\"12\" r=\"10\" stroke=\"currentColor\" strokeWidth=\"4\" />\n <path className=\"opacity-75\" fill=\"currentColor\" d=\"M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z\" />\n </svg>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@ const injectionTable = {
3
3
  {
4
4
  widgetId: "cpq.injection.quote-configurator",
5
5
  kind: "tab",
6
- groupLabel: "CPQ Configuration",
6
+ groupLabel: "cpq.widgets.quoteConfigurator.tab",
7
7
  groupDescription: "Configure, price, and manage quote offerings",
8
8
  priority: 70
9
9
  }
@@ -12,7 +12,7 @@ const injectionTable = {
12
12
  {
13
13
  widgetId: "cpq.injection.order-cpq-tab",
14
14
  kind: "tab",
15
- groupLabel: "CPQ Order",
15
+ groupLabel: "cpq.widgets.orderCpqTab.tab",
16
16
  groupDescription: "View the CPQ order associated with this sales order",
17
17
  priority: 60
18
18
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/cpq/widgets/injection-table.ts"],
4
- "sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\nexport const injectionTable: ModuleInjectionTable = {\n 'sales.document.detail.quote:tabs': [\n {\n widgetId: 'cpq.injection.quote-configurator',\n kind: 'tab',\n groupLabel: 'CPQ Configuration',\n groupDescription: 'Configure, price, and manage quote offerings',\n priority: 70,\n },\n ],\n 'sales.document.detail.order:tabs': [\n {\n widgetId: 'cpq.injection.order-cpq-tab',\n kind: 'tab',\n groupLabel: 'CPQ Order',\n groupDescription: 'View the CPQ order associated with this sales order',\n priority: 60,\n },\n ],\n 'detail:customers.company:tabs': [\n {\n widgetId: 'cpq.injection.customer-inventory',\n kind: 'tab',\n groupLabel: 'Inventory',\n groupDescription: 'Products and services purchased by this customer',\n priority: 60,\n },\n ],\n 'detail:customers.person:tabs': [\n {\n widgetId: 'cpq.injection.customer-inventory',\n kind: 'tab',\n groupLabel: 'Inventory',\n groupDescription: 'Products and services purchased by this customer',\n priority: 60,\n },\n ],\n}\n\nexport default injectionTable\n"],
5
- "mappings": "AAEO,MAAM,iBAAuC;AAAA,EAClD,oCAAoC;AAAA,IAClC;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,oCAAoC;AAAA,IAClC;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,iCAAiC;AAAA,IAC/B;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,gCAAgC;AAAA,IAC9B;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
4
+ "sourcesContent": ["import type { ModuleInjectionTable } from '@open-mercato/shared/modules/widgets/injection'\n\n// Tab labels: the sales document page translates `groupLabel` as an i18n key\n// (`t(groupLabel, metadata.title)`), so the sales tabs use CPQ keys. The\n// customers detail pages (legacy v1) render `groupLabel` verbatim, so the\n// customer-inventory tab keeps plain English until the host translates it\n// (spec 2026-11-15-cpq-multi-language, Risks).\n\nexport const injectionTable: ModuleInjectionTable = {\n 'sales.document.detail.quote:tabs': [\n {\n widgetId: 'cpq.injection.quote-configurator',\n kind: 'tab',\n groupLabel: 'cpq.widgets.quoteConfigurator.tab',\n groupDescription: 'Configure, price, and manage quote offerings',\n priority: 70,\n },\n ],\n 'sales.document.detail.order:tabs': [\n {\n widgetId: 'cpq.injection.order-cpq-tab',\n kind: 'tab',\n groupLabel: 'cpq.widgets.orderCpqTab.tab',\n groupDescription: 'View the CPQ order associated with this sales order',\n priority: 60,\n },\n ],\n 'detail:customers.company:tabs': [\n {\n widgetId: 'cpq.injection.customer-inventory',\n kind: 'tab',\n groupLabel: 'Inventory',\n groupDescription: 'Products and services purchased by this customer',\n priority: 60,\n },\n ],\n 'detail:customers.person:tabs': [\n {\n widgetId: 'cpq.injection.customer-inventory',\n kind: 'tab',\n groupLabel: 'Inventory',\n groupDescription: 'Products and services purchased by this customer',\n priority: 60,\n },\n ],\n}\n\nexport default injectionTable\n"],
5
+ "mappings": "AAQO,MAAM,iBAAuC;AAAA,EAClD,oCAAoC;AAAA,IAClC;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,oCAAoC;AAAA,IAClC;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,iCAAiC;AAAA,IAC/B;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,gCAAgC;AAAA,IAC9B;AAAA,MACE,UAAU;AAAA,MACV,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,UAAU;AAAA,IACZ;AAAA,EACF;AACF;AAEA,IAAO,0BAAQ;",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,11 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import React from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
5
  import { WizardEngineProvider, useWizardEngine } from "./engine.js";
5
6
  import { getStepType } from "./registry.js";
6
7
  function StepRenderer({ scope }) {
8
+ const t = useT();
7
9
  const { state, completeStep, goBack, skipStep } = useWizardEngine();
8
10
  const { currentStep, stepData, completedSteps, currentStepId, params, quoteId, customerId } = state;
9
11
  const stepType = getStepType(currentStep.type);
@@ -24,17 +26,19 @@ function StepRenderer({ scope }) {
24
26
  if (!stepType) {
25
27
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6 text-center space-y-3", children: [
26
28
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-destructive", children: [
27
- "Unknown step type: ",
29
+ t("cpq.wizard.runner.unknownStepType", "Unknown step type:"),
30
+ " ",
28
31
  /* @__PURE__ */ jsx("strong", { children: currentStep.type })
29
32
  ] }),
30
33
  /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
31
- "This step type is not registered. Register it via ",
34
+ t("cpq.wizard.runner.notRegistered", "This step type is not registered. Register it via"),
35
+ " ",
32
36
  /* @__PURE__ */ jsx("code", { className: "font-mono text-xs bg-muted px-1 py-0.5 rounded", children: "registerStepType()" }),
33
37
  "."
34
38
  ] }),
35
39
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-center pt-2", children: [
36
- state.canGoBack && /* @__PURE__ */ jsx("button", { type: "button", onClick: goBack, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: "Back" }),
37
- state.canSkip && /* @__PURE__ */ jsx("button", { type: "button", onClick: skipStep, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: "Skip" })
40
+ state.canGoBack && /* @__PURE__ */ jsx("button", { type: "button", onClick: goBack, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: t("cpq.common.back", "Back") }),
41
+ state.canSkip && /* @__PURE__ */ jsx("button", { type: "button", onClick: skipStep, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: t("cpq.wizard.skip", "Skip") })
38
42
  ] })
39
43
  ] });
40
44
  }
@@ -68,13 +72,11 @@ function WizardProgress() {
68
72
  ] });
69
73
  }
70
74
  function WizardHeader() {
75
+ const t = useT();
71
76
  const { state } = useWizardEngine();
72
77
  return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between", children: /* @__PURE__ */ jsxs("div", { children: [
73
78
  /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold", children: state.definition.name }),
74
- /* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground", children: [
75
- "Step: ",
76
- state.currentStep.title
77
- ] })
79
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("cpq.wizard.runner.currentStep", "Step: {title}", { title: state.currentStep.title }) })
78
80
  ] }) });
79
81
  }
80
82
  function WizardRunner({
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/cpq/workflows/WizardRunner.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { WizardEngineProvider, useWizardEngine } from './engine'\nimport { getStepType } from './registry'\nimport type { WizardDefinitionResult, WizardStepProps } from './types'\nimport type { TenantScope } from '../services/types'\n\n// \u2500\u2500\u2500 Step renderer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction StepRenderer({ scope }: { scope: TenantScope }) {\n const { state, completeStep, goBack, skipStep } = useWizardEngine()\n const { currentStep, stepData, completedSteps, currentStepId, params, quoteId, customerId } = state\n\n const stepType = getStepType(currentStep.type)\n\n const stepProps: WizardStepProps = {\n stepData,\n completedSteps,\n currentStepId,\n stepDefinition: currentStep,\n config: currentStep.config,\n params,\n onComplete: completeStep,\n onBack: goBack,\n onSkip: skipStep,\n quoteId,\n customerId,\n scope,\n }\n\n if (!stepType) {\n return (\n <div className=\"rounded-lg border bg-card p-6 text-center space-y-3\">\n <p className=\"text-sm text-destructive\">\n Unknown step type: <strong>{currentStep.type}</strong>\n </p>\n <p className=\"text-sm text-muted-foreground\">This step type is not registered. Register it via <code className=\"font-mono text-xs bg-muted px-1 py-0.5 rounded\">registerStepType()</code>.</p>\n <div className=\"flex gap-2 justify-center pt-2\">\n {state.canGoBack && (\n <button type=\"button\" onClick={goBack} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n Back\n </button>\n )}\n {state.canSkip && (\n <button type=\"button\" onClick={skipStep} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n Skip\n </button>\n )}\n </div>\n </div>\n )\n }\n\n const Component = stepType.component\n return <Component {...stepProps} />\n}\n\n// \u2500\u2500\u2500 Progress bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction WizardProgress() {\n const { state } = useWizardEngine()\n const { definition, completedSteps, currentStepId, completionPercent } = state\n const steps = definition.steps\n\n return (\n <div className=\"flex items-center gap-1 py-2\">\n {steps.map((step, idx) => {\n const isCompleted = completedSteps.includes(step.stepId)\n const isCurrent = step.stepId === currentStepId\n return (\n <div key={step.stepId} className=\"flex items-center gap-1\">\n <div\n className={`w-7 h-7 rounded-full flex items-center justify-center text-xs ${\n isCompleted\n ? 'bg-primary text-primary-foreground font-medium'\n : isCurrent\n ? 'bg-primary text-primary-foreground font-bold'\n : 'bg-muted text-muted-foreground'\n }`}\n title={step.title}\n >\n {isCompleted ? '\\u2713' : idx + 1}\n </div>\n {idx < steps.length - 1 && (\n <div className={`w-6 h-0.5 ${isCompleted ? 'bg-primary' : 'bg-muted'}`} />\n )}\n </div>\n )\n })}\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {completionPercent}%\n </span>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction WizardHeader() {\n const { state } = useWizardEngine()\n\n return (\n <div className=\"flex items-center justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">{state.definition.name}</h2>\n <p className=\"text-sm text-muted-foreground\">\n Step: {state.currentStep.title}\n </p>\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Main Runner component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface WizardRunnerProps {\n definition: WizardDefinitionResult\n params?: Record<string, unknown>\n initialQuoteId?: string | null\n initialCustomerId?: string | null\n scope: TenantScope\n onComplete?: (stepData: Record<string, Record<string, unknown>>) => void\n}\n\nexport function WizardRunner({\n definition,\n params,\n initialQuoteId,\n initialCustomerId,\n scope,\n onComplete,\n}: WizardRunnerProps) {\n const handleComplete = React.useCallback(\n (stepData: Record<string, Record<string, unknown>>) => {\n onComplete?.(stepData)\n },\n [onComplete],\n )\n\n return (\n <WizardEngineProvider\n definition={definition}\n params={params}\n initialQuoteId={initialQuoteId}\n initialCustomerId={initialCustomerId}\n onComplete={handleComplete}\n >\n <div className=\"space-y-4\">\n <WizardHeader />\n <WizardProgress />\n <div className=\"min-h-[200px]\">\n <StepRenderer scope={scope} />\n </div>\n </div>\n </WizardEngineProvider>\n )\n}\n"],
5
- "mappings": ";AAkCQ,SACqB,KADrB;AAhCR,OAAO,WAAW;AAClB,SAAS,sBAAsB,uBAAuB;AACtD,SAAS,mBAAmB;AAM5B,SAAS,aAAa,EAAE,MAAM,GAA2B;AACvD,QAAM,EAAE,OAAO,cAAc,QAAQ,SAAS,IAAI,gBAAgB;AAClE,QAAM,EAAE,aAAa,UAAU,gBAAgB,eAAe,QAAQ,SAAS,WAAW,IAAI;AAE9F,QAAM,WAAW,YAAY,YAAY,IAAI;AAE7C,QAAM,YAA6B;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB,QAAQ,YAAY;AAAA,IACpB;AAAA,IACA,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,MAAI,CAAC,UAAU;AACb,WACE,qBAAC,SAAI,WAAU,uDACb;AAAA,2BAAC,OAAE,WAAU,4BAA2B;AAAA;AAAA,QACnB,oBAAC,YAAQ,sBAAY,MAAK;AAAA,SAC/C;AAAA,MACA,qBAAC,OAAE,WAAU,iCAAgC;AAAA;AAAA,QAAkD,oBAAC,UAAK,WAAU,kDAAiD,gCAAkB;AAAA,QAAO;AAAA,SAAC;AAAA,MAC1L,qBAAC,SAAI,WAAU,kCACZ;AAAA,cAAM,aACL,oBAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,WAAU,0GAAyG,kBAE1J;AAAA,QAED,MAAM,WACL,oBAAC,YAAO,MAAK,UAAS,SAAS,UAAU,WAAU,0GAAyG,kBAE5J;AAAA,SAEJ;AAAA,OACF;AAAA,EAEJ;AAEA,QAAM,YAAY,SAAS;AAC3B,SAAO,oBAAC,aAAW,GAAG,WAAW;AACnC;AAIA,SAAS,iBAAiB;AACxB,QAAM,EAAE,MAAM,IAAI,gBAAgB;AAClC,QAAM,EAAE,YAAY,gBAAgB,eAAe,kBAAkB,IAAI;AACzE,QAAM,QAAQ,WAAW;AAEzB,SACE,qBAAC,SAAI,WAAU,gCACZ;AAAA,UAAM,IAAI,CAAC,MAAM,QAAQ;AACxB,YAAM,cAAc,eAAe,SAAS,KAAK,MAAM;AACvD,YAAM,YAAY,KAAK,WAAW;AAClC,aACE,qBAAC,SAAsB,WAAU,2BAC/B;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW,iEACT,cACI,mDACA,YACE,iDACA,gCACR;AAAA,YACA,OAAO,KAAK;AAAA,YAEX,wBAAc,WAAW,MAAM;AAAA;AAAA,QAClC;AAAA,QACC,MAAM,MAAM,SAAS,KACpB,oBAAC,SAAI,WAAW,aAAa,cAAc,eAAe,UAAU,IAAI;AAAA,WAdlE,KAAK,MAgBf;AAAA,IAEJ,CAAC;AAAA,IACD,qBAAC,UAAK,WAAU,sCACb;AAAA;AAAA,MAAkB;AAAA,OACrB;AAAA,KACF;AAEJ;AAIA,SAAS,eAAe;AACtB,QAAM,EAAE,MAAM,IAAI,gBAAgB;AAElC,SACE,oBAAC,SAAI,WAAU,qCACb,+BAAC,SACC;AAAA,wBAAC,QAAG,WAAU,yBAAyB,gBAAM,WAAW,MAAK;AAAA,IAC7D,qBAAC,OAAE,WAAU,iCAAgC;AAAA;AAAA,MACpC,MAAM,YAAY;AAAA,OAC3B;AAAA,KACF,GACF;AAEJ;AAaO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,iBAAiB,MAAM;AAAA,IAC3B,CAAC,aAAsD;AACrD,mBAAa,QAAQ;AAAA,IACvB;AAAA,IACA,CAAC,UAAU;AAAA,EACb;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,YAAY;AAAA,MAEZ,+BAAC,SAAI,WAAU,aACb;AAAA,4BAAC,gBAAa;AAAA,QACd,oBAAC,kBAAe;AAAA,QAChB,oBAAC,SAAI,WAAU,iBACb,8BAAC,gBAAa,OAAc,GAC9B;AAAA,SACF;AAAA;AAAA,EACF;AAEJ;",
4
+ "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { WizardEngineProvider, useWizardEngine } from './engine'\nimport { getStepType } from './registry'\nimport type { WizardDefinitionResult, WizardStepProps } from './types'\nimport type { TenantScope } from '../services/types'\n\n// \u2500\u2500\u2500 Step renderer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction StepRenderer({ scope }: { scope: TenantScope }) {\n const t = useT()\n const { state, completeStep, goBack, skipStep } = useWizardEngine()\n const { currentStep, stepData, completedSteps, currentStepId, params, quoteId, customerId } = state\n\n const stepType = getStepType(currentStep.type)\n\n const stepProps: WizardStepProps = {\n stepData,\n completedSteps,\n currentStepId,\n stepDefinition: currentStep,\n config: currentStep.config,\n params,\n onComplete: completeStep,\n onBack: goBack,\n onSkip: skipStep,\n quoteId,\n customerId,\n scope,\n }\n\n if (!stepType) {\n return (\n <div className=\"rounded-lg border bg-card p-6 text-center space-y-3\">\n <p className=\"text-sm text-destructive\">\n {t('cpq.wizard.runner.unknownStepType', 'Unknown step type:')} <strong>{currentStep.type}</strong>\n </p>\n <p className=\"text-sm text-muted-foreground\">{t('cpq.wizard.runner.notRegistered', 'This step type is not registered. Register it via')} <code className=\"font-mono text-xs bg-muted px-1 py-0.5 rounded\">{'registerStepType()'}</code>.</p>\n <div className=\"flex gap-2 justify-center pt-2\">\n {state.canGoBack && (\n <button type=\"button\" onClick={goBack} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n {t('cpq.common.back', 'Back')}\n </button>\n )}\n {state.canSkip && (\n <button type=\"button\" onClick={skipStep} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n {t('cpq.wizard.skip', 'Skip')}\n </button>\n )}\n </div>\n </div>\n )\n }\n\n const Component = stepType.component\n return <Component {...stepProps} />\n}\n\n// \u2500\u2500\u2500 Progress bar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction WizardProgress() {\n const { state } = useWizardEngine()\n const { definition, completedSteps, currentStepId, completionPercent } = state\n const steps = definition.steps\n\n return (\n <div className=\"flex items-center gap-1 py-2\">\n {steps.map((step, idx) => {\n const isCompleted = completedSteps.includes(step.stepId)\n const isCurrent = step.stepId === currentStepId\n return (\n <div key={step.stepId} className=\"flex items-center gap-1\">\n <div\n className={`w-7 h-7 rounded-full flex items-center justify-center text-xs ${\n isCompleted\n ? 'bg-primary text-primary-foreground font-medium'\n : isCurrent\n ? 'bg-primary text-primary-foreground font-bold'\n : 'bg-muted text-muted-foreground'\n }`}\n title={step.title}\n >\n {isCompleted ? '\\u2713' : idx + 1}\n </div>\n {idx < steps.length - 1 && (\n <div className={`w-6 h-0.5 ${isCompleted ? 'bg-primary' : 'bg-muted'}`} />\n )}\n </div>\n )\n })}\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {completionPercent}%\n </span>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Header \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction WizardHeader() {\n const t = useT()\n const { state } = useWizardEngine()\n\n return (\n <div className=\"flex items-center justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">{state.definition.name}</h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('cpq.wizard.runner.currentStep', 'Step: {title}', { title: state.currentStep.title })}\n </p>\n </div>\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Main Runner component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface WizardRunnerProps {\n definition: WizardDefinitionResult\n params?: Record<string, unknown>\n initialQuoteId?: string | null\n initialCustomerId?: string | null\n scope: TenantScope\n onComplete?: (stepData: Record<string, Record<string, unknown>>) => void\n}\n\nexport function WizardRunner({\n definition,\n params,\n initialQuoteId,\n initialCustomerId,\n scope,\n onComplete,\n}: WizardRunnerProps) {\n const handleComplete = React.useCallback(\n (stepData: Record<string, Record<string, unknown>>) => {\n onComplete?.(stepData)\n },\n [onComplete],\n )\n\n return (\n <WizardEngineProvider\n definition={definition}\n params={params}\n initialQuoteId={initialQuoteId}\n initialCustomerId={initialCustomerId}\n onComplete={handleComplete}\n >\n <div className=\"space-y-4\">\n <WizardHeader />\n <WizardProgress />\n <div className=\"min-h-[200px]\">\n <StepRenderer scope={scope} />\n </div>\n </div>\n </WizardEngineProvider>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,11 +2,12 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useCallback, useState } from "react";
4
4
  import { useRouter } from "next/navigation";
5
- async function apiJson(url) {
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
6
+ async function apiJson(t, url) {
6
7
  const res = await fetch(url);
7
8
  if (!res.ok) {
8
9
  const body = await res.json().catch(() => ({}));
9
- throw new Error(body.error ?? `API error ${res.status}`);
10
+ throw new Error(body.error ?? t("cpq.wizard.trigger.apiError", "API error {status}", { status: res.status }));
10
11
  }
11
12
  return res.json();
12
13
  }
@@ -15,13 +16,16 @@ function WizardTrigger({
15
16
  offeringCode,
16
17
  params: wizardParams = {},
17
18
  surface = "backend",
18
- label = "Start Wizard",
19
+ label,
19
20
  variant = "button",
20
21
  className,
21
22
  basePath,
22
23
  onStart
23
24
  }) {
24
25
  const router = useRouter();
26
+ const t = useT();
27
+ const buttonLabel = label ?? t("cpq.wizard.trigger.start", "Start Wizard");
28
+ const startingLabel = t("cpq.wizard.trigger.starting", "Starting...");
25
29
  const [loading, setLoading] = useState(false);
26
30
  const [error, setError] = useState(null);
27
31
  const handleClick = useCallback(async () => {
@@ -31,17 +35,19 @@ function WizardTrigger({
31
35
  let definition = null;
32
36
  if (wizardCode) {
33
37
  definition = await apiJson(
38
+ t,
34
39
  `/api/cpq/wizards?code=${encodeURIComponent(wizardCode)}`
35
40
  );
36
41
  } else {
37
42
  const resolveParams = new URLSearchParams({ surface });
38
43
  if (offeringCode) resolveParams.set("offeringCode", offeringCode);
39
44
  definition = await apiJson(
45
+ t,
40
46
  `/api/cpq/wizards/resolve?${resolveParams}`
41
47
  );
42
48
  }
43
49
  if (!definition) {
44
- setError("No matching wizard found");
50
+ setError(t("cpq.wizard.trigger.notFound", "No matching wizard found"));
45
51
  return;
46
52
  }
47
53
  if (onStart) {
@@ -56,11 +62,11 @@ function WizardTrigger({
56
62
  router.push(`${path}/${definition.code}${qs ? `?${qs}` : ""}`);
57
63
  }
58
64
  } catch (err) {
59
- setError(err instanceof Error ? err.message : "Failed to start wizard");
65
+ setError(err instanceof Error ? err.message : t("cpq.wizard.trigger.failed", "Failed to start wizard"));
60
66
  } finally {
61
67
  setLoading(false);
62
68
  }
63
- }, [wizardCode, offeringCode, wizardParams, surface, basePath, onStart, router]);
69
+ }, [wizardCode, offeringCode, wizardParams, surface, basePath, onStart, router, t]);
64
70
  if (variant === "link") {
65
71
  return /* @__PURE__ */ jsxs(
66
72
  "a",
@@ -72,7 +78,7 @@ function WizardTrigger({
72
78
  handleClick();
73
79
  },
74
80
  children: [
75
- loading ? "Starting..." : label,
81
+ loading ? startingLabel : buttonLabel,
76
82
  error && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-destructive", children: error })
77
83
  ]
78
84
  }
@@ -90,7 +96,7 @@ function WizardTrigger({
90
96
  if (e.key === "Enter") handleClick();
91
97
  },
92
98
  children: [
93
- /* @__PURE__ */ jsx("p", { className: "font-semibold text-sm", children: label }),
99
+ /* @__PURE__ */ jsx("p", { className: "font-semibold text-sm", children: buttonLabel }),
94
100
  error && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-destructive", children: error })
95
101
  ]
96
102
  }
@@ -104,7 +110,7 @@ function WizardTrigger({
104
110
  onClick: handleClick,
105
111
  disabled: loading,
106
112
  children: [
107
- loading ? "Starting..." : label,
113
+ loading ? startingLabel : buttonLabel,
108
114
  error && /* @__PURE__ */ jsx("span", { className: "ml-2 text-xs text-destructive/70", children: error })
109
115
  ]
110
116
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/cpq/workflows/WizardTrigger.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React, { useCallback, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport type { WizardDefinitionResult } from './types'\n\n// \u2500\u2500\u2500 API helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function apiJson<T>(url: string): Promise<T> {\n const res = await fetch(url)\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n throw new Error(body.error ?? `API error ${res.status}`)\n }\n return res.json() as Promise<T>\n}\n\n// \u2500\u2500\u2500 Props \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface WizardTriggerProps {\n /** Explicit wizard definition code. If set, uses this wizard directly. */\n wizardCode?: string\n /** Offering code for wizard resolution (when no explicit wizard). */\n offeringCode?: string\n /** Launch params passed to the wizard engine. */\n params?: Record<string, unknown>\n /** Surface context. */\n surface?: 'backend' | 'portal'\n /** Button label. */\n label?: string\n /** Render variant. */\n variant?: 'button' | 'link' | 'card'\n /** Additional CSS class. */\n className?: string\n /** Override the navigation target base path. */\n basePath?: string\n /** Called after wizard is resolved (instead of navigating). */\n onStart?: (definition: WizardDefinitionResult, params: Record<string, unknown>) => void\n}\n\nexport function WizardTrigger({\n wizardCode,\n offeringCode,\n params: wizardParams = {},\n surface = 'backend',\n label = 'Start Wizard',\n variant = 'button',\n className,\n basePath,\n onStart,\n}: WizardTriggerProps) {\n const router = useRouter()\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const handleClick = useCallback(async () => {\n setLoading(true)\n setError(null)\n\n try {\n // Resolve definition\n let definition: WizardDefinitionResult | null = null\n\n if (wizardCode) {\n definition = await apiJson<WizardDefinitionResult>(\n `/api/cpq/wizards?code=${encodeURIComponent(wizardCode)}`,\n )\n } else {\n const resolveParams = new URLSearchParams({ surface })\n if (offeringCode) resolveParams.set('offeringCode', offeringCode)\n definition = await apiJson<WizardDefinitionResult>(\n `/api/cpq/wizards/resolve?${resolveParams}`,\n )\n }\n\n if (!definition) {\n setError('No matching wizard found')\n return\n }\n\n if (onStart) {\n onStart(definition, wizardParams)\n } else {\n const path = basePath ?? (surface === 'portal' ? '/wizards' : '/backend/cpq/wizards')\n const queryParams = new URLSearchParams()\n if (Object.keys(wizardParams).length > 0) {\n queryParams.set('params', JSON.stringify(wizardParams))\n }\n const qs = queryParams.toString()\n router.push(`${path}/${definition.code}${qs ? `?${qs}` : ''}`)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to start wizard')\n } finally {\n setLoading(false)\n }\n }, [wizardCode, offeringCode, wizardParams, surface, basePath, onStart, router])\n\n if (variant === 'link') {\n return (\n <a\n href=\"#\"\n className={className ?? 'text-sm text-primary hover:underline'}\n onClick={(e) => {\n e.preventDefault()\n handleClick()\n }}\n >\n {loading ? 'Starting...' : label}\n {error && <span className=\"ml-2 text-xs text-destructive\">{error}</span>}\n </a>\n )\n }\n\n if (variant === 'card') {\n return (\n <div\n className={className ?? 'rounded-lg border p-4 cursor-pointer hover:shadow-sm transition-shadow'}\n onClick={handleClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => { if (e.key === 'Enter') handleClick() }}\n >\n <p className=\"font-semibold text-sm\">{label}</p>\n {error && <p className=\"mt-1 text-xs text-destructive\">{error}</p>}\n </div>\n )\n }\n\n // Default: button\n return (\n <button\n type=\"button\"\n className={className ?? 'inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50'}\n onClick={handleClick}\n disabled={loading}\n >\n {loading ? 'Starting...' : label}\n {error && <span className=\"ml-2 text-xs text-destructive/70\">{error}</span>}\n </button>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport React, { useCallback, useState } from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport type { WizardDefinitionResult } from './types'\n\n// \u2500\u2500\u2500 API helper \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function apiJson<T>(t: TranslateFn, url: string): Promise<T> {\n const res = await fetch(url)\n if (!res.ok) {\n const body = await res.json().catch(() => ({}))\n throw new Error(body.error ?? t('cpq.wizard.trigger.apiError', 'API error {status}', { status: res.status }))\n }\n return res.json() as Promise<T>\n}\n\n// \u2500\u2500\u2500 Props \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface WizardTriggerProps {\n /** Explicit wizard definition code. If set, uses this wizard directly. */\n wizardCode?: string\n /** Offering code for wizard resolution (when no explicit wizard). */\n offeringCode?: string\n /** Launch params passed to the wizard engine. */\n params?: Record<string, unknown>\n /** Surface context. */\n surface?: 'backend' | 'portal'\n /** Button label. */\n label?: string\n /** Render variant. */\n variant?: 'button' | 'link' | 'card'\n /** Additional CSS class. */\n className?: string\n /** Override the navigation target base path. */\n basePath?: string\n /** Called after wizard is resolved (instead of navigating). */\n onStart?: (definition: WizardDefinitionResult, params: Record<string, unknown>) => void\n}\n\nexport function WizardTrigger({\n wizardCode,\n offeringCode,\n params: wizardParams = {},\n surface = 'backend',\n label,\n variant = 'button',\n className,\n basePath,\n onStart,\n}: WizardTriggerProps) {\n const router = useRouter()\n const t = useT()\n const buttonLabel = label ?? t('cpq.wizard.trigger.start', 'Start Wizard')\n const startingLabel = t('cpq.wizard.trigger.starting', 'Starting...')\n const [loading, setLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const handleClick = useCallback(async () => {\n setLoading(true)\n setError(null)\n\n try {\n // Resolve definition\n let definition: WizardDefinitionResult | null = null\n\n if (wizardCode) {\n definition = await apiJson<WizardDefinitionResult>(\n t,\n `/api/cpq/wizards?code=${encodeURIComponent(wizardCode)}`,\n )\n } else {\n const resolveParams = new URLSearchParams({ surface })\n if (offeringCode) resolveParams.set('offeringCode', offeringCode)\n definition = await apiJson<WizardDefinitionResult>(\n t,\n `/api/cpq/wizards/resolve?${resolveParams}`,\n )\n }\n\n if (!definition) {\n setError(t('cpq.wizard.trigger.notFound', 'No matching wizard found'))\n return\n }\n\n if (onStart) {\n onStart(definition, wizardParams)\n } else {\n const path = basePath ?? (surface === 'portal' ? '/wizards' : '/backend/cpq/wizards')\n const queryParams = new URLSearchParams()\n if (Object.keys(wizardParams).length > 0) {\n queryParams.set('params', JSON.stringify(wizardParams))\n }\n const qs = queryParams.toString()\n router.push(`${path}/${definition.code}${qs ? `?${qs}` : ''}`)\n }\n } catch (err) {\n setError(err instanceof Error ? err.message : t('cpq.wizard.trigger.failed', 'Failed to start wizard'))\n } finally {\n setLoading(false)\n }\n }, [wizardCode, offeringCode, wizardParams, surface, basePath, onStart, router, t])\n\n if (variant === 'link') {\n return (\n <a\n href=\"#\"\n className={className ?? 'text-sm text-primary hover:underline'}\n onClick={(e) => {\n e.preventDefault()\n handleClick()\n }}\n >\n {loading ? startingLabel : buttonLabel}\n {error && <span className=\"ml-2 text-xs text-destructive\">{error}</span>}\n </a>\n )\n }\n\n if (variant === 'card') {\n return (\n <div\n className={className ?? 'rounded-lg border p-4 cursor-pointer hover:shadow-sm transition-shadow'}\n onClick={handleClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => { if (e.key === 'Enter') handleClick() }}\n >\n <p className=\"font-semibold text-sm\">{buttonLabel}</p>\n {error && <p className=\"mt-1 text-xs text-destructive\">{error}</p>}\n </div>\n )\n }\n\n // Default: button\n return (\n <button\n type=\"button\"\n className={className ?? 'inline-flex items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50'}\n onClick={handleClick}\n disabled={loading}\n >\n {loading ? startingLabel : buttonLabel}\n {error && <span className=\"ml-2 text-xs text-destructive/70\">{error}</span>}\n </button>\n )\n}\n"],
5
+ "mappings": ";AA0GM,SASY,KATZ;AAxGN,SAAgB,aAAa,gBAAgB;AAC7C,SAAS,iBAAiB;AAC1B,SAAS,YAA8B;AAKvC,eAAe,QAAW,GAAgB,KAAyB;AACjE,QAAM,MAAM,MAAM,MAAM,GAAG;AAC3B,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC,EAAE;AAC9C,UAAM,IAAI,MAAM,KAAK,SAAS,EAAE,+BAA+B,sBAAsB,EAAE,QAAQ,IAAI,OAAO,CAAC,CAAC;AAAA,EAC9G;AACA,SAAO,IAAI,KAAK;AAClB;AAyBO,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,QAAQ,eAAe,CAAC;AAAA,EACxB,UAAU;AAAA,EACV;AAAA,EACA,UAAU;AAAA,EACV;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,SAAS,UAAU;AACzB,QAAM,IAAI,KAAK;AACf,QAAM,cAAc,SAAS,EAAE,4BAA4B,cAAc;AACzE,QAAM,gBAAgB,EAAE,+BAA+B,aAAa;AACpE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,cAAc,YAAY,YAAY;AAC1C,eAAW,IAAI;AACf,aAAS,IAAI;AAEb,QAAI;AAEF,UAAI,aAA4C;AAEhD,UAAI,YAAY;AACd,qBAAa,MAAM;AAAA,UACjB;AAAA,UACA,yBAAyB,mBAAmB,UAAU,CAAC;AAAA,QACzD;AAAA,MACF,OAAO;AACL,cAAM,gBAAgB,IAAI,gBAAgB,EAAE,QAAQ,CAAC;AACrD,YAAI,aAAc,eAAc,IAAI,gBAAgB,YAAY;AAChE,qBAAa,MAAM;AAAA,UACjB;AAAA,UACA,4BAA4B,aAAa;AAAA,QAC3C;AAAA,MACF;AAEA,UAAI,CAAC,YAAY;AACf,iBAAS,EAAE,+BAA+B,0BAA0B,CAAC;AACrE;AAAA,MACF;AAEA,UAAI,SAAS;AACX,gBAAQ,YAAY,YAAY;AAAA,MAClC,OAAO;AACL,cAAM,OAAO,aAAa,YAAY,WAAW,aAAa;AAC9D,cAAM,cAAc,IAAI,gBAAgB;AACxC,YAAI,OAAO,KAAK,YAAY,EAAE,SAAS,GAAG;AACxC,sBAAY,IAAI,UAAU,KAAK,UAAU,YAAY,CAAC;AAAA,QACxD;AACA,cAAM,KAAK,YAAY,SAAS;AAChC,eAAO,KAAK,GAAG,IAAI,IAAI,WAAW,IAAI,GAAG,KAAK,IAAI,EAAE,KAAK,EAAE,EAAE;AAAA,MAC/D;AAAA,IACF,SAAS,KAAK;AACZ,eAAS,eAAe,QAAQ,IAAI,UAAU,EAAE,6BAA6B,wBAAwB,CAAC;AAAA,IACxG,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,YAAY,cAAc,cAAc,SAAS,UAAU,SAAS,QAAQ,CAAC,CAAC;AAElF,MAAI,YAAY,QAAQ;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,WAAW,aAAa;AAAA,QACxB,SAAS,CAAC,MAAM;AACd,YAAE,eAAe;AACjB,sBAAY;AAAA,QACd;AAAA,QAEC;AAAA,oBAAU,gBAAgB;AAAA,UAC1B,SAAS,oBAAC,UAAK,WAAU,iCAAiC,iBAAM;AAAA;AAAA;AAAA,IACnE;AAAA,EAEJ;AAEA,MAAI,YAAY,QAAQ;AACtB,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,aAAa;AAAA,QACxB,SAAS;AAAA,QACT,MAAK;AAAA,QACL,UAAU;AAAA,QACV,WAAW,CAAC,MAAM;AAAE,cAAI,EAAE,QAAQ,QAAS,aAAY;AAAA,QAAE;AAAA,QAEzD;AAAA,8BAAC,OAAE,WAAU,yBAAyB,uBAAY;AAAA,UACjD,SAAS,oBAAC,OAAE,WAAU,iCAAiC,iBAAM;AAAA;AAAA;AAAA,IAChE;AAAA,EAEJ;AAGA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,WAAW,aAAa;AAAA,MACxB,SAAS;AAAA,MACT,UAAU;AAAA,MAET;AAAA,kBAAU,gBAAgB;AAAA,QAC1B,SAAS,oBAAC,UAAK,WAAU,oCAAoC,iBAAM;AAAA;AAAA;AAAA,EACtE;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useCallback } from "react";
4
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
4
5
  function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps }) {
6
+ const t = useT();
5
7
  const [customers, setCustomers] = useState([]);
6
8
  const [search, setSearch] = useState("");
7
9
  const [selected, setSelected] = useState(null);
@@ -45,7 +47,7 @@ function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepI
45
47
  "input",
46
48
  {
47
49
  type: "text",
48
- placeholder: "Search customers...",
50
+ placeholder: t("cpq.wizard.steps.customerSelect.searchPlaceholder", "Search customers..."),
49
51
  value: search,
50
52
  onChange: (e) => setSearch(e.target.value),
51
53
  onKeyDown: (e) => e.key === "Enter" && handleSearch(),
@@ -59,13 +61,13 @@ function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepI
59
61
  onClick: handleSearch,
60
62
  disabled: loading,
61
63
  className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50",
62
- children: loading ? "Searching..." : "Search"
64
+ children: loading ? t("cpq.wizard.steps.customerSelect.searching", "Searching...") : t("cpq.wizard.steps.customerSelect.search", "Search")
63
65
  }
64
66
  )
65
67
  ] }),
66
68
  selected && /* @__PURE__ */ jsxs("div", { className: "rounded-md border-2 border-primary bg-primary/5 p-3 flex items-center justify-between", children: [
67
69
  /* @__PURE__ */ jsxs("span", { className: "text-sm", children: [
68
- /* @__PURE__ */ jsx("strong", { children: "Selected:" }),
70
+ /* @__PURE__ */ jsx("strong", { children: t("cpq.wizard.steps.customerSelect.selected", "Selected:") }),
69
71
  " ",
70
72
  selected.name
71
73
  ] }),
@@ -75,7 +77,7 @@ function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepI
75
77
  type: "button",
76
78
  onClick: () => setSelected(null),
77
79
  className: "text-xs text-muted-foreground hover:text-foreground underline",
78
- children: "Clear"
80
+ children: t("cpq.wizard.steps.customerSelect.clear", "Clear")
79
81
  }
80
82
  )
81
83
  ] }),
@@ -92,10 +94,10 @@ function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepI
92
94
  },
93
95
  c.id
94
96
  )),
95
- customers.length === 0 && !loading && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No customers found." })
97
+ customers.length === 0 && !loading && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.wizard.steps.customerSelect.empty", "No customers found.") })
96
98
  ] }),
97
99
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
98
- completedSteps.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onBack, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: "Back" }),
100
+ completedSteps.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onBack, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: t("cpq.common.back", "Back") }),
99
101
  /* @__PURE__ */ jsx(
100
102
  "button",
101
103
  {
@@ -107,7 +109,7 @@ function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepI
107
109
  },
108
110
  disabled: !selected,
109
111
  className: "inline-flex items-center justify-center rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed",
110
- children: "Next"
112
+ children: t("cpq.wizard.next", "Next")
111
113
  }
112
114
  )
113
115
  ] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/workflows/steps/CustomerSelectStep.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React, { useState, useEffect, useCallback } from 'react'\nimport type { WizardStepProps } from '../types'\n\ntype Customer = {\n id: string\n name: string\n email?: string\n segment?: string\n}\n\nexport function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps }: WizardStepProps) {\n const [customers, setCustomers] = useState<Customer[]>([])\n const [search, setSearch] = useState('')\n const [selected, setSelected] = useState<Customer | null>(null)\n const [loading, setLoading] = useState(false)\n\n // Restore previous selection if returning to this step\n const previousData = stepData[currentStepId] as { customerId?: string; customerName?: string } | undefined\n useEffect(() => {\n if (previousData?.customerId && previousData?.customerName) {\n setSelected({ id: previousData.customerId, name: previousData.customerName })\n }\n }, [previousData?.customerId, previousData?.customerName])\n\n const segmentFilter = (config.segmentFilter as string[]) ?? []\n\n const handleSearch = useCallback(async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n if (search) params.set('search', search)\n const res = await fetch(`/api/customers/companies?${params}`)\n if (res.ok) {\n const data = await res.json()\n let items = ((data.items ?? data) as Array<Record<string, unknown>>).map((c) => ({\n id: c.id as string,\n name: (c.display_name ?? c.displayName ?? c.name ?? '') as string,\n email: (c.primary_email ?? c.email ?? '') as string,\n segment: (c.lifecycle_stage ?? c.segment ?? '') as string,\n })) as Customer[]\n if (segmentFilter.length > 0) {\n items = items.filter((c) => c.segment && segmentFilter.includes(c.segment))\n }\n setCustomers(items)\n }\n } finally {\n setLoading(false)\n }\n }, [search, segmentFilter])\n\n useEffect(() => {\n handleSearch()\n }, []) // Load initial list on mount\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n placeholder=\"Search customers...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={(e) => e.key === 'Enter' && handleSearch()}\n className=\"flex-1 rounded-md border px-3 py-2 text-sm\"\n />\n <button\n type=\"button\"\n onClick={handleSearch}\n disabled={loading}\n className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50\"\n >\n {loading ? 'Searching...' : 'Search'}\n </button>\n </div>\n\n {selected && (\n <div className=\"rounded-md border-2 border-primary bg-primary/5 p-3 flex items-center justify-between\">\n <span className=\"text-sm\">\n <strong>Selected:</strong> {selected.name}\n </span>\n <button\n type=\"button\"\n onClick={() => setSelected(null)}\n className=\"text-xs text-muted-foreground hover:text-foreground underline\"\n >\n Clear\n </button>\n </div>\n )}\n\n <div className=\"flex flex-col gap-1 max-h-[300px] overflow-y-auto\">\n {customers.map((c) => (\n <div\n key={c.id}\n onClick={() => setSelected(c)}\n className={`rounded-md border p-3 cursor-pointer transition-colors ${\n selected?.id === c.id\n ? 'border-primary bg-primary/5'\n : 'hover:bg-muted/30'\n }`}\n >\n <span className=\"font-medium text-sm\">{c.name}</span>\n {c.email && <span className=\"ml-2 text-xs text-muted-foreground\">{c.email}</span>}\n </div>\n ))}\n {customers.length === 0 && !loading && (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n No customers found.\n </div>\n )}\n </div>\n\n <div className=\"flex gap-2 justify-end pt-2\">\n {completedSteps.length > 0 && (\n <button type=\"button\" onClick={onBack} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n Back\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => {\n if (selected) {\n onComplete({ customerId: selected.id, customerName: selected.name })\n }\n }}\n disabled={!selected}\n className=\"inline-flex items-center justify-center rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Next\n </button>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport React, { useState, useEffect, useCallback } from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { WizardStepProps } from '../types'\n\ntype Customer = {\n id: string\n name: string\n email?: string\n segment?: string\n}\n\nexport function CustomerSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps }: WizardStepProps) {\n const t = useT()\n const [customers, setCustomers] = useState<Customer[]>([])\n const [search, setSearch] = useState('')\n const [selected, setSelected] = useState<Customer | null>(null)\n const [loading, setLoading] = useState(false)\n\n // Restore previous selection if returning to this step\n const previousData = stepData[currentStepId] as { customerId?: string; customerName?: string } | undefined\n useEffect(() => {\n if (previousData?.customerId && previousData?.customerName) {\n setSelected({ id: previousData.customerId, name: previousData.customerName })\n }\n }, [previousData?.customerId, previousData?.customerName])\n\n const segmentFilter = (config.segmentFilter as string[]) ?? []\n\n const handleSearch = useCallback(async () => {\n setLoading(true)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '20' })\n if (search) params.set('search', search)\n const res = await fetch(`/api/customers/companies?${params}`)\n if (res.ok) {\n const data = await res.json()\n let items = ((data.items ?? data) as Array<Record<string, unknown>>).map((c) => ({\n id: c.id as string,\n name: (c.display_name ?? c.displayName ?? c.name ?? '') as string,\n email: (c.primary_email ?? c.email ?? '') as string,\n segment: (c.lifecycle_stage ?? c.segment ?? '') as string,\n })) as Customer[]\n if (segmentFilter.length > 0) {\n items = items.filter((c) => c.segment && segmentFilter.includes(c.segment))\n }\n setCustomers(items)\n }\n } finally {\n setLoading(false)\n }\n }, [search, segmentFilter])\n\n useEffect(() => {\n handleSearch()\n }, []) // Load initial list on mount\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n placeholder={t('cpq.wizard.steps.customerSelect.searchPlaceholder', 'Search customers...')}\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n onKeyDown={(e) => e.key === 'Enter' && handleSearch()}\n className=\"flex-1 rounded-md border px-3 py-2 text-sm\"\n />\n <button\n type=\"button\"\n onClick={handleSearch}\n disabled={loading}\n className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted disabled:opacity-50\"\n >\n {loading\n ? t('cpq.wizard.steps.customerSelect.searching', 'Searching...')\n : t('cpq.wizard.steps.customerSelect.search', 'Search')}\n </button>\n </div>\n\n {selected && (\n <div className=\"rounded-md border-2 border-primary bg-primary/5 p-3 flex items-center justify-between\">\n <span className=\"text-sm\">\n <strong>{t('cpq.wizard.steps.customerSelect.selected', 'Selected:')}</strong> {selected.name}\n </span>\n <button\n type=\"button\"\n onClick={() => setSelected(null)}\n className=\"text-xs text-muted-foreground hover:text-foreground underline\"\n >\n {t('cpq.wizard.steps.customerSelect.clear', 'Clear')}\n </button>\n </div>\n )}\n\n <div className=\"flex flex-col gap-1 max-h-[300px] overflow-y-auto\">\n {customers.map((c) => (\n <div\n key={c.id}\n onClick={() => setSelected(c)}\n className={`rounded-md border p-3 cursor-pointer transition-colors ${\n selected?.id === c.id\n ? 'border-primary bg-primary/5'\n : 'hover:bg-muted/30'\n }`}\n >\n <span className=\"font-medium text-sm\">{c.name}</span>\n {c.email && <span className=\"ml-2 text-xs text-muted-foreground\">{c.email}</span>}\n </div>\n ))}\n {customers.length === 0 && !loading && (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.wizard.steps.customerSelect.empty', 'No customers found.')}\n </div>\n )}\n </div>\n\n <div className=\"flex gap-2 justify-end pt-2\">\n {completedSteps.length > 0 && (\n <button type=\"button\" onClick={onBack} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n {t('cpq.common.back', 'Back')}\n </button>\n )}\n <button\n type=\"button\"\n onClick={() => {\n if (selected) {\n onComplete({ customerId: selected.id, customerName: selected.name })\n }\n }}\n disabled={!selected}\n className=\"inline-flex items-center justify-center rounded-md bg-primary px-5 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n {t('cpq.wizard.next', 'Next')}\n </button>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,18 +1,23 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
3
4
  function InventorySelectStep({ onBack, onSkip, completedSteps }) {
5
+ const t = useT();
4
6
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-6 text-center space-y-3", children: [
5
- /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: "Inventory Selection" }),
6
- /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "This step type is not yet implemented. It will allow selecting existing customer inventory items for modification or renewal once the inventory domain is available." }),
7
+ /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: t("cpq.wizard.steps.inventorySelect.title", "Inventory Selection") }),
8
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t(
9
+ "cpq.wizard.steps.inventorySelect.notImplemented",
10
+ "This step type is not yet implemented. It will allow selecting existing customer inventory items for modification or renewal once the inventory domain is available."
11
+ ) }),
7
12
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-center pt-2", children: [
8
- completedSteps.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onBack, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: "Back" }),
13
+ completedSteps.length > 0 && /* @__PURE__ */ jsx("button", { type: "button", onClick: onBack, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: t("cpq.common.back", "Back") }),
9
14
  /* @__PURE__ */ jsx(
10
15
  "button",
11
16
  {
12
17
  type: "button",
13
18
  onClick: onSkip,
14
19
  className: "inline-flex items-center justify-center rounded-md border border-primary px-4 py-2 text-sm font-medium text-primary hover:bg-primary/10",
15
- children: "Skip"
20
+ children: t("cpq.wizard.skip", "Skip")
16
21
  }
17
22
  )
18
23
  ] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/workflows/steps/InventorySelectStep.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport type { WizardStepProps } from '../types'\n\n/**\n * Stub step type for inventory/subscription item selection.\n * Implementation deferred until the inventory/subscription domain exists.\n */\nexport function InventorySelectStep({ onBack, onSkip, completedSteps }: WizardStepProps) {\n return (\n <div className=\"rounded-lg border bg-card p-6 text-center space-y-3\">\n <h3 className=\"text-lg font-semibold\">Inventory Selection</h3>\n <p className=\"text-sm text-muted-foreground\">\n This step type is not yet implemented. It will allow selecting existing customer\n inventory items for modification or renewal once the inventory domain is available.\n </p>\n <div className=\"flex gap-2 justify-center pt-2\">\n {completedSteps.length > 0 && (\n <button type=\"button\" onClick={onBack} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n Back\n </button>\n )}\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"inline-flex items-center justify-center rounded-md border border-primary px-4 py-2 text-sm font-medium text-primary hover:bg-primary/10\"\n >\n Skip\n </button>\n </div>\n </div>\n )\n}\n"],
5
- "mappings": ";AAYM,cAKA,YALA;AAHC,SAAS,oBAAoB,EAAE,QAAQ,QAAQ,eAAe,GAAoB;AACvF,SACE,qBAAC,SAAI,WAAU,uDACb;AAAA,wBAAC,QAAG,WAAU,yBAAwB,iCAAmB;AAAA,IACzD,oBAAC,OAAE,WAAU,iCAAgC,kLAG7C;AAAA,IACA,qBAAC,SAAI,WAAU,kCACZ;AAAA,qBAAe,SAAS,KACvB,oBAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,WAAU,0GAAyG,kBAE1J;AAAA,MAEF;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;",
4
+ "sourcesContent": ["'use client'\n\nimport React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { WizardStepProps } from '../types'\n\n/**\n * Stub step type for inventory/subscription item selection.\n * Implementation deferred until the inventory/subscription domain exists.\n */\nexport function InventorySelectStep({ onBack, onSkip, completedSteps }: WizardStepProps) {\n const t = useT()\n return (\n <div className=\"rounded-lg border bg-card p-6 text-center space-y-3\">\n <h3 className=\"text-lg font-semibold\">{t('cpq.wizard.steps.inventorySelect.title', 'Inventory Selection')}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {t(\n 'cpq.wizard.steps.inventorySelect.notImplemented',\n 'This step type is not yet implemented. It will allow selecting existing customer inventory items for modification or renewal once the inventory domain is available.',\n )}\n </p>\n <div className=\"flex gap-2 justify-center pt-2\">\n {completedSteps.length > 0 && (\n <button type=\"button\" onClick={onBack} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n {t('cpq.common.back', 'Back')}\n </button>\n )}\n <button\n type=\"button\"\n onClick={onSkip}\n className=\"inline-flex items-center justify-center rounded-md border border-primary px-4 py-2 text-sm font-medium text-primary hover:bg-primary/10\"\n >\n {t('cpq.wizard.skip', 'Skip')}\n </button>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAcM,cAOA,YAPA;AAXN,SAAS,YAAY;AAOd,SAAS,oBAAoB,EAAE,QAAQ,QAAQ,eAAe,GAAoB;AACvF,QAAM,IAAI,KAAK;AACf,SACE,qBAAC,SAAI,WAAU,uDACb;AAAA,wBAAC,QAAG,WAAU,yBAAyB,YAAE,0CAA0C,qBAAqB,GAAE;AAAA,IAC1G,oBAAC,OAAE,WAAU,iCACV;AAAA,MACC;AAAA,MACA;AAAA,IACF,GACF;AAAA,IACA,qBAAC,SAAI,WAAU,kCACZ;AAAA,qBAAe,SAAS,KACvB,oBAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,WAAU,0GAC9C,YAAE,mBAAmB,MAAM,GAC9B;AAAA,MAEF;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UAET,YAAE,mBAAmB,MAAM;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }