@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,12 +1,14 @@
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
  import { Button } from "@open-mercato/ui/primitives/button";
5
6
  import { Tag } from "@open-mercato/ui/primitives/tag";
6
7
  function fmt(n, currency = "USD") {
7
8
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(n);
8
9
  }
9
10
  function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, quoteId }) {
11
+ const t = useT();
10
12
  const [lines, setLines] = useState([]);
11
13
  const [loading, setLoading] = useState(true);
12
14
  const parentLineSource = config.parentLineSource;
@@ -45,15 +47,15 @@ function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, qu
45
47
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
46
48
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
47
49
  /* @__PURE__ */ jsxs("span", { className: "text-xs text-muted-foreground", children: [
48
- lines.length,
49
- " item",
50
- lines.length !== 1 ? "s" : "",
51
- minItems > 0 && ` (min: ${minItems})`,
52
- maxItems < Infinity && ` (max: ${maxItems})`
50
+ lines.length !== 1 ? t("cpq.wizard.steps.itemList.count.other", "{count} items", { count: lines.length }) : t("cpq.wizard.steps.itemList.count.one", "{count} item", { count: lines.length }),
51
+ minItems > 0 && " ",
52
+ minItems > 0 && t("cpq.wizard.steps.itemList.min", "(min: {min})", { min: minItems }),
53
+ maxItems < Infinity && " ",
54
+ maxItems < Infinity && t("cpq.wizard.steps.itemList.max", "(max: {max})", { max: maxItems })
53
55
  ] }),
54
- canAddMore && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: "Use the offering browser to add items to the quote, then return here." })
56
+ canAddMore && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("cpq.wizard.steps.itemList.hint", "Use the offering browser to add items to the quote, then return here.") })
55
57
  ] }),
56
- loading ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading items..." }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
58
+ loading ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("cpq.wizard.steps.itemList.loading", "Loading items...") }) : /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
57
59
  lines.map((line) => /* @__PURE__ */ jsxs(
58
60
  "div",
59
61
  {
@@ -65,15 +67,10 @@ function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, qu
65
67
  "x",
66
68
  line.quantity
67
69
  ] }),
68
- !line.isConfigured && /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "ml-2", children: "Needs configuration" })
70
+ !line.isConfigured && /* @__PURE__ */ jsx(Tag, { variant: "warning", className: "ml-2", children: t("cpq.wizard.steps.itemList.needsConfiguration", "Needs configuration") })
69
71
  ] }),
70
72
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
71
- showPricing && /* @__PURE__ */ jsxs("span", { className: "text-xs", children: [
72
- fmt(line.nrcTotal),
73
- " NRC / ",
74
- fmt(line.mrcTotal),
75
- " MRC"
76
- ] }),
73
+ showPricing && /* @__PURE__ */ jsx("span", { className: "text-xs", children: t("cpq.wizard.linePricing", "{nrc} NRC / {mrc} MRC", { nrc: fmt(line.nrcTotal), mrc: fmt(line.mrcTotal) }) }),
77
74
  /* @__PURE__ */ jsx(
78
75
  Button,
79
76
  {
@@ -81,7 +78,7 @@ function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, qu
81
78
  variant: "destructive-outline",
82
79
  size: "sm",
83
80
  onClick: () => handleRemove(line.lineId),
84
- children: "Remove"
81
+ children: t("cpq.wizard.remove", "Remove")
85
82
  }
86
83
  )
87
84
  ] })
@@ -89,11 +86,11 @@ function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, qu
89
86
  },
90
87
  line.lineId
91
88
  )),
92
- lines.length === 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No items added yet." })
89
+ lines.length === 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.wizard.steps.itemList.empty", "No items added yet.") })
93
90
  ] }),
94
91
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
95
- 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" }),
96
- /* @__PURE__ */ jsx("button", { type: "button", onClick: loadLines, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: "Refresh" }),
92
+ 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") }),
93
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: loadLines, className: "inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted", children: t("cpq.wizard.steps.itemList.refresh", "Refresh") }),
97
94
  /* @__PURE__ */ jsx(
98
95
  "button",
99
96
  {
@@ -103,7 +100,7 @@ function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, qu
103
100
  },
104
101
  disabled: !canProceed,
105
102
  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",
106
- children: "Next"
103
+ children: t("cpq.wizard.next", "Next")
107
104
  }
108
105
  )
109
106
  ] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/workflows/steps/ItemListStep.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React, { useState, useEffect, useCallback } from 'react'\nimport type { WizardStepProps } from '../types'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\n\ntype QuoteLine = {\n lineId: string\n offeringId: string | null\n offeringName: string\n quantity: number\n nrcTotal: number\n mrcTotal: number\n isConfigured: boolean\n parentLineId: string | null\n}\n\nfunction fmt(n: number, currency = 'USD') {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(n)\n}\n\nexport function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, quoteId }: WizardStepProps) {\n const [lines, setLines] = useState<QuoteLine[]>([])\n const [loading, setLoading] = useState(true)\n\n const parentLineSource = config.parentLineSource as string | undefined\n const minItems = (config.minItems as number) ?? 0\n const maxItems = (config.maxItems as number) ?? Infinity\n const showPricing = config.showPricing !== false\n\n const parentLineId = parentLineSource\n ? (stepData[parentLineSource]?.lineId as string | undefined)\n : undefined\n\n const loadLines = useCallback(async () => {\n if (!quoteId) { setLoading(false); return }\n setLoading(true)\n try {\n const res = await fetch(`/api/cpq/quotes/${quoteId}`)\n if (res.ok) {\n const data = await res.json()\n const allLines = (data.lines ?? []) as QuoteLine[]\n // Filter to children of the parent line (if parentLineId set), otherwise show all\n const filtered = parentLineId\n ? allLines.filter((l) => l.parentLineId === parentLineId)\n : allLines\n setLines(filtered)\n }\n } finally {\n setLoading(false)\n }\n }, [quoteId, parentLineId])\n\n useEffect(() => { loadLines() }, [loadLines])\n\n const handleRemove = useCallback(async (lineId: string) => {\n if (!quoteId) return\n await fetch(`/api/cpq/quotes/${quoteId}/items/${lineId}`, { method: 'DELETE' })\n await loadLines()\n }, [quoteId, loadLines])\n\n const canProceed = lines.length >= minItems\n const canAddMore = lines.length < maxItems\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-muted-foreground\">\n {lines.length} item{lines.length !== 1 ? 's' : ''}\n {minItems > 0 && ` (min: ${minItems})`}\n {maxItems < Infinity && ` (max: ${maxItems})`}\n </span>\n {canAddMore && (\n <span className=\"text-xs text-muted-foreground\">\n Use the offering browser to add items to the quote, then return here.\n </span>\n )}\n </div>\n\n {loading ? (\n <div className=\"text-sm text-muted-foreground\">Loading items...</div>\n ) : (\n <div className=\"space-y-2\">\n {lines.map((line) => (\n <div\n key={line.lineId}\n className=\"flex items-center justify-between rounded-lg border p-3\"\n >\n <div>\n <span className=\"text-sm font-medium\">{line.offeringName}</span>\n {line.quantity > 1 && <span className=\"ml-2 text-xs text-muted-foreground\">x{line.quantity}</span>}\n {!line.isConfigured && (\n <Tag variant=\"warning\" className=\"ml-2\">\n Needs configuration\n </Tag>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n {showPricing && (\n <span className=\"text-xs\">\n {fmt(line.nrcTotal)} NRC / {fmt(line.mrcTotal)} MRC\n </span>\n )}\n <Button\n type=\"button\"\n variant=\"destructive-outline\"\n size=\"sm\"\n onClick={() => handleRemove(line.lineId)}\n >\n Remove\n </Button>\n </div>\n </div>\n ))}\n {lines.length === 0 && (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n No items added yet.\n </div>\n )}\n </div>\n )}\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 type=\"button\" onClick={loadLines} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={() => {\n onComplete({ lineCount: lines.length, lineIds: lines.map((l) => l.lineId) })\n }}\n disabled={!canProceed}\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": ";AAoEQ,SAME,KANF;AAlER,SAAgB,UAAU,WAAW,mBAAmB;AAExD,SAAS,cAAc;AACvB,SAAS,WAAW;AAapB,SAAS,IAAI,GAAW,WAAW,OAAO;AACxC,SAAO,IAAI,KAAK,aAAa,SAAS,EAAE,OAAO,YAAY,SAAS,CAAC,EAAE,OAAO,CAAC;AACjF;AAEO,SAAS,aAAa,EAAE,QAAQ,YAAY,QAAQ,UAAU,gBAAgB,QAAQ,GAAoB;AAC/G,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAsB,CAAC,CAAC;AAClD,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAE3C,QAAM,mBAAmB,OAAO;AAChC,QAAM,WAAY,OAAO,YAAuB;AAChD,QAAM,WAAY,OAAO,YAAuB;AAChD,QAAM,cAAc,OAAO,gBAAgB;AAE3C,QAAM,eAAe,mBAChB,SAAS,gBAAgB,GAAG,SAC7B;AAEJ,QAAM,YAAY,YAAY,YAAY;AACxC,QAAI,CAAC,SAAS;AAAE,iBAAW,KAAK;AAAG;AAAA,IAAO;AAC1C,eAAW,IAAI;AACf,QAAI;AACF,YAAM,MAAM,MAAM,MAAM,mBAAmB,OAAO,EAAE;AACpD,UAAI,IAAI,IAAI;AACV,cAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,cAAM,WAAY,KAAK,SAAS,CAAC;AAEjC,cAAM,WAAW,eACb,SAAS,OAAO,CAAC,MAAM,EAAE,iBAAiB,YAAY,IACtD;AACJ,iBAAS,QAAQ;AAAA,MACnB;AAAA,IACF,UAAE;AACA,iBAAW,KAAK;AAAA,IAClB;AAAA,EACF,GAAG,CAAC,SAAS,YAAY,CAAC;AAE1B,YAAU,MAAM;AAAE,cAAU;AAAA,EAAE,GAAG,CAAC,SAAS,CAAC;AAE5C,QAAM,eAAe,YAAY,OAAO,WAAmB;AACzD,QAAI,CAAC,QAAS;AACd,UAAM,MAAM,mBAAmB,OAAO,UAAU,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AAC9E,UAAM,UAAU;AAAA,EAClB,GAAG,CAAC,SAAS,SAAS,CAAC;AAEvB,QAAM,aAAa,MAAM,UAAU;AACnC,QAAM,aAAa,MAAM,SAAS;AAElC,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,yBAAC,SAAI,WAAU,qCACb;AAAA,2BAAC,UAAK,WAAU,iCACb;AAAA,cAAM;AAAA,QAAO;AAAA,QAAM,MAAM,WAAW,IAAI,MAAM;AAAA,QAC9C,WAAW,KAAK,UAAU,QAAQ;AAAA,QAClC,WAAW,YAAY,UAAU,QAAQ;AAAA,SAC5C;AAAA,MACC,cACC,oBAAC,UAAK,WAAU,iCAAgC,mFAEhD;AAAA,OAEJ;AAAA,IAEC,UACC,oBAAC,SAAI,WAAU,iCAAgC,8BAAgB,IAE/D,qBAAC,SAAI,WAAU,aACZ;AAAA,YAAM,IAAI,CAAC,SACV;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UAEV;AAAA,iCAAC,SACC;AAAA,kCAAC,UAAK,WAAU,uBAAuB,eAAK,cAAa;AAAA,cACxD,KAAK,WAAW,KAAK,qBAAC,UAAK,WAAU,sCAAqC;AAAA;AAAA,gBAAE,KAAK;AAAA,iBAAS;AAAA,cAC1F,CAAC,KAAK,gBACL,oBAAC,OAAI,SAAQ,WAAU,WAAU,QAAO,iCAExC;AAAA,eAEJ;AAAA,YACA,qBAAC,SAAI,WAAU,2BACZ;AAAA,6BACC,qBAAC,UAAK,WAAU,WACb;AAAA,oBAAI,KAAK,QAAQ;AAAA,gBAAE;AAAA,gBAAQ,IAAI,KAAK,QAAQ;AAAA,gBAAE;AAAA,iBACjD;AAAA,cAEF;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,SAAQ;AAAA,kBACR,MAAK;AAAA,kBACL,SAAS,MAAM,aAAa,KAAK,MAAM;AAAA,kBACxC;AAAA;AAAA,cAED;AAAA,eACF;AAAA;AAAA;AAAA,QA1BK,KAAK;AAAA,MA2BZ,CACD;AAAA,MACA,MAAM,WAAW,KAChB,oBAAC,SAAI,WAAU,2EAA0E,iCAEzF;AAAA,OAEJ;AAAA,IAGF,qBAAC,SAAI,WAAU,+BACZ;AAAA,qBAAe,SAAS,KACvB,oBAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,WAAU,0GAAyG,kBAE1J;AAAA,MAEF,oBAAC,YAAO,MAAK,UAAS,SAAS,WAAW,WAAU,0GAAyG,qBAE7J;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM;AACb,uBAAW,EAAE,WAAW,MAAM,QAAQ,SAAS,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;AAAA,UAC7E;AAAA,UACA,UAAU,CAAC;AAAA,UACX,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,OACF;AAAA,KACF;AAEJ;",
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'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\n\ntype QuoteLine = {\n lineId: string\n offeringId: string | null\n offeringName: string\n quantity: number\n nrcTotal: number\n mrcTotal: number\n isConfigured: boolean\n parentLineId: string | null\n}\n\nfunction fmt(n: number, currency = 'USD') {\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(n)\n}\n\nexport function ItemListStep({ config, onComplete, onBack, stepData, completedSteps, quoteId }: WizardStepProps) {\n const t = useT()\n const [lines, setLines] = useState<QuoteLine[]>([])\n const [loading, setLoading] = useState(true)\n\n const parentLineSource = config.parentLineSource as string | undefined\n const minItems = (config.minItems as number) ?? 0\n const maxItems = (config.maxItems as number) ?? Infinity\n const showPricing = config.showPricing !== false\n\n const parentLineId = parentLineSource\n ? (stepData[parentLineSource]?.lineId as string | undefined)\n : undefined\n\n const loadLines = useCallback(async () => {\n if (!quoteId) { setLoading(false); return }\n setLoading(true)\n try {\n const res = await fetch(`/api/cpq/quotes/${quoteId}`)\n if (res.ok) {\n const data = await res.json()\n const allLines = (data.lines ?? []) as QuoteLine[]\n // Filter to children of the parent line (if parentLineId set), otherwise show all\n const filtered = parentLineId\n ? allLines.filter((l) => l.parentLineId === parentLineId)\n : allLines\n setLines(filtered)\n }\n } finally {\n setLoading(false)\n }\n }, [quoteId, parentLineId])\n\n useEffect(() => { loadLines() }, [loadLines])\n\n const handleRemove = useCallback(async (lineId: string) => {\n if (!quoteId) return\n await fetch(`/api/cpq/quotes/${quoteId}/items/${lineId}`, { method: 'DELETE' })\n await loadLines()\n }, [quoteId, loadLines])\n\n const canProceed = lines.length >= minItems\n const canAddMore = lines.length < maxItems\n\n return (\n <div className=\"space-y-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-xs text-muted-foreground\">\n {lines.length !== 1\n ? t('cpq.wizard.steps.itemList.count.other', '{count} items', { count: lines.length })\n : t('cpq.wizard.steps.itemList.count.one', '{count} item', { count: lines.length })}\n {minItems > 0 && ' '}\n {minItems > 0 && t('cpq.wizard.steps.itemList.min', '(min: {min})', { min: minItems })}\n {maxItems < Infinity && ' '}\n {maxItems < Infinity && t('cpq.wizard.steps.itemList.max', '(max: {max})', { max: maxItems })}\n </span>\n {canAddMore && (\n <span className=\"text-xs text-muted-foreground\">\n {t('cpq.wizard.steps.itemList.hint', 'Use the offering browser to add items to the quote, then return here.')}\n </span>\n )}\n </div>\n\n {loading ? (\n <div className=\"text-sm text-muted-foreground\">{t('cpq.wizard.steps.itemList.loading', 'Loading items...')}</div>\n ) : (\n <div className=\"space-y-2\">\n {lines.map((line) => (\n <div\n key={line.lineId}\n className=\"flex items-center justify-between rounded-lg border p-3\"\n >\n <div>\n <span className=\"text-sm font-medium\">{line.offeringName}</span>\n {line.quantity > 1 && <span className=\"ml-2 text-xs text-muted-foreground\">x{line.quantity}</span>}\n {!line.isConfigured && (\n <Tag variant=\"warning\" className=\"ml-2\">\n {t('cpq.wizard.steps.itemList.needsConfiguration', 'Needs configuration')}\n </Tag>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n {showPricing && (\n <span className=\"text-xs\">\n {t('cpq.wizard.linePricing', '{nrc} NRC / {mrc} MRC', { nrc: fmt(line.nrcTotal), mrc: fmt(line.mrcTotal) })}\n </span>\n )}\n <Button\n type=\"button\"\n variant=\"destructive-outline\"\n size=\"sm\"\n onClick={() => handleRemove(line.lineId)}\n >\n {t('cpq.wizard.remove', 'Remove')}\n </Button>\n </div>\n </div>\n ))}\n {lines.length === 0 && (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.wizard.steps.itemList.empty', 'No items added yet.')}\n </div>\n )}\n </div>\n )}\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 type=\"button\" onClick={loadLines} className=\"inline-flex items-center justify-center rounded-md border px-4 py-2 text-sm font-medium hover:bg-muted\">\n {t('cpq.wizard.steps.itemList.refresh', 'Refresh')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n onComplete({ lineCount: lines.length, lineIds: lines.map((l) => l.lineId) })\n }}\n disabled={!canProceed}\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,6 +1,7 @@
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
  import { NumberInput } from "../../components/NumberInput.js";
5
6
  function ConfigPanel({
6
7
  offering,
@@ -8,6 +9,7 @@ function ConfigPanel({
8
9
  onChange,
9
10
  onRemove
10
11
  }) {
12
+ const t = useT();
11
13
  const [attributes, setAttributes] = useState([]);
12
14
  const [loading, setLoading] = useState(true);
13
15
  const [configVersion, setConfigVersion] = useState(0);
@@ -49,7 +51,7 @@ function ConfigPanel({
49
51
  const value = configuration[attr.code] ?? "";
50
52
  if (attr.attributeType === "select" || attr.attributeType === "enum") {
51
53
  return /* @__PURE__ */ jsxs("select", { value: String(value), onChange: (e) => handleChange(attr.code, e.target.value), className: "w-full rounded-md border px-3 py-2 text-sm", children: [
52
- /* @__PURE__ */ jsx("option", { value: "", children: "Select..." }),
54
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.wizard.selectPlaceholder", "Select...") }),
53
55
  attr.options?.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
54
56
  ] });
55
57
  }
@@ -73,9 +75,9 @@ function ConfigPanel({
73
75
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-4 space-y-3", children: [
74
76
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b pb-2", children: [
75
77
  /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium", children: offering.name }),
76
- /* @__PURE__ */ jsx("button", { type: "button", onClick: onRemove, className: "text-xs text-muted-foreground hover:text-foreground", children: "Remove" })
78
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onRemove, className: "text-xs text-muted-foreground hover:text-foreground", children: t("cpq.wizard.remove", "Remove") })
77
79
  ] }),
78
- loading ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading attributes..." }) : attributes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "No configurable attributes." }) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: attributes.map((attr) => /* @__PURE__ */ jsxs("div", { children: [
80
+ loading ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("cpq.wizard.loadingAttributes", "Loading attributes...") }) : attributes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("cpq.wizard.steps.offeringSelect.noAttributes", "No configurable attributes.") }) : /* @__PURE__ */ jsx("div", { className: "space-y-3", children: attributes.map((attr) => /* @__PURE__ */ jsxs("div", { children: [
79
81
  /* @__PURE__ */ jsxs("label", { className: "block text-sm font-medium mb-1", children: [
80
82
  attr.name,
81
83
  attr.isRequired && /* @__PURE__ */ jsx("span", { className: "text-destructive ml-0.5", children: "*" })
@@ -89,6 +91,7 @@ function BandwidthIndicator({
89
91
  portCapacityMb,
90
92
  items
91
93
  }) {
94
+ const t = useT();
92
95
  const totalBandwidth = items.reduce((sum, item) => {
93
96
  const bw = Number(item.configuration.bandwidth_mb) || 0;
94
97
  return sum + bw;
@@ -97,13 +100,8 @@ function BandwidthIndicator({
97
100
  const overCapacity = totalBandwidth > portCapacityMb;
98
101
  return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border p-3 space-y-2", children: [
99
102
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
100
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Bandwidth Utilization" }),
101
- /* @__PURE__ */ jsxs("span", { className: overCapacity ? "text-destructive font-medium" : "text-muted-foreground", children: [
102
- totalBandwidth,
103
- " / ",
104
- portCapacityMb,
105
- " MB"
106
- ] })
103
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: t("cpq.wizard.steps.offeringSelect.bandwidth.title", "Bandwidth Utilization") }),
104
+ /* @__PURE__ */ jsx("span", { className: overCapacity ? "text-destructive font-medium" : "text-muted-foreground", children: t("cpq.wizard.steps.offeringSelect.bandwidth.usage", "{used} / {capacity} MB", { used: totalBandwidth, capacity: portCapacityMb }) })
107
105
  ] }),
108
106
  /* @__PURE__ */ jsx("div", { className: "h-2 rounded-full bg-muted overflow-hidden", children: /* @__PURE__ */ jsx(
109
107
  "div",
@@ -112,15 +110,12 @@ function BandwidthIndicator({
112
110
  style: { width: `${Math.min(100, pct)}%` }
113
111
  }
114
112
  ) }),
115
- overCapacity && /* @__PURE__ */ jsxs("p", { className: "text-xs text-destructive", children: [
116
- "Total bandwidth exceeds port capacity by ",
117
- totalBandwidth - portCapacityMb,
118
- " MB"
119
- ] })
113
+ overCapacity && /* @__PURE__ */ jsx("p", { className: "text-xs text-destructive", children: t("cpq.wizard.steps.offeringSelect.bandwidth.exceeded", "Total bandwidth exceeds port capacity by {excess} MB", { excess: totalBandwidth - portCapacityMb }) })
120
114
  ] });
121
115
  }
122
116
  const PORT_SIZE_MB = { "1G": 1e3, "10G": 1e4, "100G": 1e5 };
123
117
  function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }) {
118
+ const t = useT();
124
119
  const [offerings, setOfferings] = useState([]);
125
120
  const [selectedItems, setSelectedItems] = useState([]);
126
121
  const [loading, setLoading] = useState(true);
@@ -243,7 +238,7 @@ function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepI
243
238
  }
244
239
  }, [selectedItems, enableConfiguration, quoteId, resolvedCustomerId, onComplete]);
245
240
  if (loading) {
246
- return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading offerings..." });
241
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("cpq.wizard.steps.offeringSelect.loading", "Loading offerings...") });
247
242
  }
248
243
  const availableOfferings = offerings.filter(
249
244
  (o) => multiSelect ? true : !selectedItems.some((s) => s.offering.id === o.id)
@@ -264,26 +259,22 @@ function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepI
264
259
  children: [
265
260
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
266
261
  /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium", children: o.name }),
267
- isAdded && /* @__PURE__ */ jsx("span", { className: "text-xs text-primary font-medium", children: "Added" })
262
+ isAdded && /* @__PURE__ */ jsx("span", { className: "text-xs text-primary font-medium", children: t("cpq.wizard.steps.offeringSelect.added", "Added") })
268
263
  ] }),
269
264
  /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mt-1", children: o.description || o.code }),
270
- !isAdded && /* @__PURE__ */ jsx("span", { className: "text-xs text-primary mt-2 inline-block", children: "+ Add" })
265
+ !isAdded && /* @__PURE__ */ jsx("span", { className: "text-xs text-primary mt-2 inline-block", children: t("cpq.wizard.steps.offeringSelect.add", "+ Add") })
271
266
  ]
272
267
  },
273
268
  o.id
274
269
  );
275
270
  }),
276
- offerings.length === 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No offerings available." })
271
+ offerings.length === 0 && /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.wizard.steps.offeringSelect.empty", "No offerings available.") })
277
272
  ]
278
273
  }
279
274
  ),
280
275
  portCapacityMb > 0 && selectedItems.length > 0 && /* @__PURE__ */ jsx(BandwidthIndicator, { portCapacityMb, items: selectedItems }),
281
276
  enableConfiguration && selectedItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
282
- /* @__PURE__ */ jsxs("h4", { className: "text-sm font-medium", children: [
283
- "Selected Services (",
284
- selectedItems.length,
285
- ")"
286
- ] }),
277
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium", children: t("cpq.wizard.steps.offeringSelect.selectedServices", "Selected Services ({count})", { count: selectedItems.length }) }),
287
278
  selectedItems.map((item, index) => /* @__PURE__ */ jsx(
288
279
  ConfigPanel,
289
280
  {
@@ -296,18 +287,14 @@ function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepI
296
287
  ))
297
288
  ] }),
298
289
  !enableConfiguration && selectedItems.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
299
- /* @__PURE__ */ jsxs("h4", { className: "text-sm font-medium", children: [
300
- "Selected (",
301
- selectedItems.length,
302
- ")"
303
- ] }),
290
+ /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium", children: t("cpq.wizard.steps.offeringSelect.selected", "Selected ({count})", { count: selectedItems.length }) }),
304
291
  selectedItems.map((item, index) => /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between rounded-md border p-3", children: [
305
292
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: item.offering.name }),
306
- /* @__PURE__ */ jsx("button", { type: "button", onClick: () => removeItem(index), className: "text-xs text-muted-foreground hover:text-foreground", children: "Remove" })
293
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => removeItem(index), className: "text-xs text-muted-foreground hover:text-foreground", children: t("cpq.wizard.remove", "Remove") })
307
294
  ] }, `${item.offering.id}-${index}`))
308
295
  ] }),
309
296
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
310
- 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" }),
297
+ 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") }),
311
298
  /* @__PURE__ */ jsx(
312
299
  "button",
313
300
  {
@@ -315,7 +302,7 @@ function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepI
315
302
  onClick: handleNext,
316
303
  disabled: selectedItems.length === 0 || saving,
317
304
  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",
318
- children: saving ? "Saving..." : "Next"
305
+ children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.wizard.next", "Next")
319
306
  }
320
307
  )
321
308
  ] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/workflows/steps/OfferingSelectStep.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React, { useState, useEffect, useCallback } from 'react'\nimport type { WizardStepProps } from '../types'\nimport { NumberInput } from '../../components/NumberInput'\n\ntype Offering = {\n id: string\n code: string\n name: string\n description: string | null\n specId: string\n isActive: boolean\n}\n\ntype ConstrainedAttribute = {\n code: string\n name: string\n attributeType: string\n isRequired: boolean\n options: Array<{ value: string; label: string }> | null\n constraints: Record<string, unknown> | null\n defaultValue: unknown | null\n helpText: string | null\n}\n\ntype SelectedItem = {\n offering: Offering\n configuration: Record<string, unknown>\n lineId?: string\n}\n\n// \u2500\u2500\u2500 Inline configuration panel for a single offering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ConfigPanel({\n offering,\n configuration,\n onChange,\n onRemove,\n}: {\n offering: Offering\n configuration: Record<string, unknown>\n onChange: (code: string, value: unknown) => void\n onRemove: () => void\n}) {\n const [attributes, setAttributes] = useState<ConstrainedAttribute[]>([])\n const [loading, setLoading] = useState(true)\n const [configVersion, setConfigVersion] = useState(0)\n\n useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n try {\n const params = new URLSearchParams({ offeringId: offering.id })\n if (Object.keys(configuration).length > 0) {\n params.set('configuration', JSON.stringify(configuration))\n }\n const res = await fetch(`/api/cpq/quotes/attributes?${params}`)\n if (res.ok && !cancelled) {\n const data = await res.json()\n const attrs = (data.attributes ?? []) as ConstrainedAttribute[]\n setAttributes(attrs.filter((a: ConstrainedAttribute & { resolutionTime?: string }) => !a.resolutionTime || a.resolutionTime === 'run_time'))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [offering.id, configVersion]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleChange = useCallback((code: string, value: unknown) => {\n onChange(code, value)\n // Check for dependent attributes\n const hasDependents = attributes.some((a) => {\n const dep = a.constraints as Record<string, unknown> | null\n const depOn = dep?.dependsOn as { attributeCode?: string } | undefined\n const dynSrc = dep?.dynamicOptionsSource as { filterByDimension?: Record<string, string> } | undefined\n return depOn?.attributeCode === code ||\n (dynSrc?.filterByDimension && Object.values(dynSrc.filterByDimension).some((v) => v === `$${code}`))\n })\n if (hasDependents) setConfigVersion((v) => v + 1)\n }, [onChange, attributes])\n\n const renderAttribute = (attr: ConstrainedAttribute) => {\n const value = configuration[attr.code] ?? ''\n\n if (attr.attributeType === 'select' || attr.attributeType === 'enum') {\n return (\n <select value={String(value)} onChange={(e) => handleChange(attr.code, e.target.value)} className=\"w-full rounded-md border px-3 py-2 text-sm\">\n <option value=\"\">Select...</option>\n {attr.options?.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}\n </select>\n )\n }\n if (attr.attributeType === 'boolean') {\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <input type=\"checkbox\" checked={Boolean(value)} onChange={(e) => handleChange(attr.code, e.target.checked)} className=\"rounded border\" />\n {attr.name}\n </label>\n )\n }\n if (attr.attributeType === 'number') {\n return (\n <NumberInput\n value={value === '' || value == null ? null : Number(value)}\n onChange={(n) => handleChange(attr.code, n ?? '')}\n />\n )\n }\n return <input type=\"text\" value={String(value)} onChange={(e) => handleChange(attr.code, e.target.value)} className=\"w-full rounded-md border px-3 py-2 text-sm\" />\n }\n\n return (\n <div className=\"rounded-lg border p-4 space-y-3\">\n <div className=\"flex items-center justify-between border-b pb-2\">\n <h4 className=\"text-sm font-medium\">{offering.name}</h4>\n <button type=\"button\" onClick={onRemove} className=\"text-xs text-muted-foreground hover:text-foreground\">Remove</button>\n </div>\n {loading ? (\n <div className=\"text-sm text-muted-foreground\">Loading attributes...</div>\n ) : attributes.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">No configurable attributes.</div>\n ) : (\n <div className=\"space-y-3\">\n {attributes.map((attr) => (\n <div key={attr.code}>\n <label className=\"block text-sm font-medium mb-1\">\n {attr.name}\n {attr.isRequired && <span className=\"text-destructive ml-0.5\">*</span>}\n </label>\n {renderAttribute(attr)}\n {attr.helpText && <p className=\"text-xs text-muted-foreground mt-1\">{attr.helpText}</p>}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Port capacity indicator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 BandwidthIndicator({\n portCapacityMb,\n items,\n}: {\n portCapacityMb: number\n items: SelectedItem[]\n}) {\n const totalBandwidth = items.reduce((sum, item) => {\n const bw = Number(item.configuration.bandwidth_mb) || 0\n return sum + bw\n }, 0)\n\n const pct = portCapacityMb > 0 ? Math.min(100, (totalBandwidth / portCapacityMb) * 100) : 0\n const overCapacity = totalBandwidth > portCapacityMb\n\n return (\n <div className=\"rounded-lg border p-3 space-y-2\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"font-medium\">Bandwidth Utilization</span>\n <span className={overCapacity ? 'text-destructive font-medium' : 'text-muted-foreground'}>\n {totalBandwidth} / {portCapacityMb} MB\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${overCapacity ? 'bg-destructive' : 'bg-primary'}`}\n style={{ width: `${Math.min(100, pct)}%` }}\n />\n </div>\n {overCapacity && (\n <p className=\"text-xs text-destructive\">\n Total bandwidth exceeds port capacity by {totalBandwidth - portCapacityMb} MB\n </p>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Main 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst PORT_SIZE_MB: Record<string, number> = { '1G': 1000, '10G': 10000, '100G': 100000 }\n\nexport function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }: WizardStepProps) {\n const [offerings, setOfferings] = useState<Offering[]>([])\n const [selectedItems, setSelectedItems] = useState<SelectedItem[]>([])\n const [loading, setLoading] = useState(true)\n const [saving, setSaving] = useState(false)\n\n const enableConfiguration = config.enableConfiguration === true\n const multiSelect = config.multiSelect !== false // default true for offering_select\n const offeringFilter = config.offeringFilter as { specCodes?: string[]; offeringCodes?: string[] } | undefined\n const displayMode = (config.displayMode as string) ?? 'grid'\n\n // Resolve port capacity from prior step data\n const portCapacityMb = (() => {\n for (const data of Object.values(stepData)) {\n const cfg = data.configuration as Record<string, unknown> | undefined\n if (cfg?.port_size) return PORT_SIZE_MB[String(cfg.port_size)] ?? 0\n }\n return 0\n })()\n\n // Restore previous selections\n const previousData = stepData[currentStepId] as { items?: SelectedItem[] } | undefined\n\n useEffect(() => {\n async function load() {\n setLoading(true)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const res = await fetch(`/api/cpq/product-offerings?${params}`)\n if (res.ok) {\n const data = await res.json()\n let items = (data.items ?? []) as Offering[]\n if (offeringFilter?.offeringCodes?.length) {\n const codeOrder = offeringFilter.offeringCodes!\n items = items\n .filter((o) => codeOrder.includes(o.code))\n .sort((a, b) => codeOrder.indexOf(a.code) - codeOrder.indexOf(b.code))\n }\n items = items.filter((o) => o.isActive)\n setOfferings(items)\n\n // Restore previous selections\n if (previousData?.items?.length) {\n const restored = previousData.items\n .map((prev) => {\n const o = items.find((i) => i.id === prev.offering.id)\n return o ? { offering: o, configuration: prev.configuration, lineId: prev.lineId } : null\n })\n .filter(Boolean) as SelectedItem[]\n setSelectedItems(restored)\n }\n }\n } finally {\n setLoading(false)\n }\n }\n load()\n }, []) // eslint-disable-line react-hooks/exhaustive-deps\n\n const addItem = useCallback((offering: Offering) => {\n setSelectedItems((prev) => {\n if (!multiSelect) return [{ offering, configuration: {} }]\n return [...prev, { offering, configuration: {} }]\n })\n }, [multiSelect])\n\n const removeItem = useCallback((index: number) => {\n setSelectedItems((prev) => prev.filter((_, i) => i !== index))\n }, [])\n\n const updateItemConfig = useCallback((index: number, code: string, value: unknown) => {\n setSelectedItems((prev) => prev.map((item, i) =>\n i === index ? { ...item, configuration: { ...item.configuration, [code]: value } } : item\n ))\n }, [])\n\n // Resolve customerId from props or stepData\n const resolvedCustomerId = customerId ?? (() => {\n for (const data of Object.values(stepData)) {\n if (data.customerId) return data.customerId as string\n }\n return null\n })()\n\n const handleNext = useCallback(async () => {\n if (selectedItems.length === 0) return\n setSaving(true)\n try {\n let activeQuoteId = quoteId\n const output: Record<string, unknown> = { items: selectedItems }\n\n if (enableConfiguration) {\n // Auto-create quote if needed\n if (!activeQuoteId && resolvedCustomerId) {\n const createRes = await fetch('/api/cpq/quotes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ customerId: resolvedCustomerId }),\n })\n if (createRes.ok) {\n const quoteData = await createRes.json()\n activeQuoteId = quoteData.id\n output.quoteId = activeQuoteId\n }\n }\n\n // Add or update each selected item on the quote\n if (activeQuoteId) {\n for (const item of selectedItems) {\n if (item.lineId) {\n // Update existing line\n await fetch(`/api/cpq/quotes/${activeQuoteId}/items/${item.lineId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ configuration: item.configuration }),\n })\n } else {\n // Create new line\n const res = await fetch(`/api/cpq/quotes/${activeQuoteId}/items`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ offeringId: item.offering.id, configuration: item.configuration }),\n })\n if (res.ok) {\n const data = await res.json()\n const line = data.lines?.find((l: { offeringId: string }) => l.offeringId === item.offering.id)\n item.lineId = line?.lineId\n }\n }\n }\n output.quoteId = activeQuoteId\n }\n }\n\n // Also set single-item compat fields\n if (selectedItems.length === 1) {\n const first = selectedItems[0]\n output.offeringId = first.offering.id\n output.offeringCode = first.offering.code\n output.offeringName = first.offering.name\n output.specId = first.offering.specId\n if (enableConfiguration) output.configuration = first.configuration\n }\n\n onComplete(output)\n } finally {\n setSaving(false)\n }\n }, [selectedItems, enableConfiguration, quoteId, resolvedCustomerId, onComplete])\n\n if (loading) {\n return <div className=\"text-sm text-muted-foreground\">Loading offerings...</div>\n }\n\n // Offerings not yet added\n const availableOfferings = offerings.filter((o) =>\n multiSelect ? true : !selectedItems.some((s) => s.offering.id === o.id)\n )\n\n return (\n <div className=\"space-y-4\">\n {/* Offering cards */}\n <div\n className={\n displayMode === 'grid'\n ? 'grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-3'\n : 'flex flex-col gap-2'\n }\n >\n {availableOfferings.map((o) => {\n const isAdded = selectedItems.some((s) => s.offering.id === o.id)\n return (\n <div\n key={o.id}\n onClick={() => !isAdded && addItem(o)}\n className={`rounded-lg border-2 p-4 transition-colors ${\n isAdded\n ? 'border-primary/30 bg-primary/5 opacity-60 cursor-default'\n : 'border-muted cursor-pointer hover:bg-muted/30'\n }`}\n >\n <div className=\"flex items-center justify-between\">\n <h4 className=\"text-sm font-medium\">{o.name}</h4>\n {isAdded && <span className=\"text-xs text-primary font-medium\">Added</span>}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1\">{o.description || o.code}</p>\n {!isAdded && (\n <span className=\"text-xs text-primary mt-2 inline-block\">+ Add</span>\n )}\n </div>\n )\n })}\n {offerings.length === 0 && (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n No offerings available.\n </div>\n )}\n </div>\n\n {/* Bandwidth indicator */}\n {portCapacityMb > 0 && selectedItems.length > 0 && (\n <BandwidthIndicator portCapacityMb={portCapacityMb} items={selectedItems} />\n )}\n\n {/* Configuration panels for selected items */}\n {enableConfiguration && selectedItems.length > 0 && (\n <div className=\"space-y-3\">\n <h4 className=\"text-sm font-medium\">Selected Services ({selectedItems.length})</h4>\n {selectedItems.map((item, index) => (\n <ConfigPanel\n key={`${item.offering.id}-${index}`}\n offering={item.offering}\n configuration={item.configuration}\n onChange={(code, value) => updateItemConfig(index, code, value)}\n onRemove={() => removeItem(index)}\n />\n ))}\n </div>\n )}\n\n {/* No configuration \u2014 just show selected list */}\n {!enableConfiguration && selectedItems.length > 0 && (\n <div className=\"space-y-2\">\n <h4 className=\"text-sm font-medium\">Selected ({selectedItems.length})</h4>\n {selectedItems.map((item, index) => (\n <div key={`${item.offering.id}-${index}`} className=\"flex items-center justify-between rounded-md border p-3\">\n <span className=\"text-sm font-medium\">{item.offering.name}</span>\n <button type=\"button\" onClick={() => removeItem(index)} className=\"text-xs text-muted-foreground hover:text-foreground\">Remove</button>\n </div>\n ))}\n </div>\n )}\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={handleNext}\n disabled={selectedItems.length === 0 || saving}\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 {saving ? 'Saving...' : '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'\nimport { NumberInput } from '../../components/NumberInput'\n\ntype Offering = {\n id: string\n code: string\n name: string\n description: string | null\n specId: string\n isActive: boolean\n}\n\ntype ConstrainedAttribute = {\n code: string\n name: string\n attributeType: string\n isRequired: boolean\n options: Array<{ value: string; label: string }> | null\n constraints: Record<string, unknown> | null\n defaultValue: unknown | null\n helpText: string | null\n}\n\ntype SelectedItem = {\n offering: Offering\n configuration: Record<string, unknown>\n lineId?: string\n}\n\n// \u2500\u2500\u2500 Inline configuration panel for a single offering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction ConfigPanel({\n offering,\n configuration,\n onChange,\n onRemove,\n}: {\n offering: Offering\n configuration: Record<string, unknown>\n onChange: (code: string, value: unknown) => void\n onRemove: () => void\n}) {\n const t = useT()\n const [attributes, setAttributes] = useState<ConstrainedAttribute[]>([])\n const [loading, setLoading] = useState(true)\n const [configVersion, setConfigVersion] = useState(0)\n\n useEffect(() => {\n let cancelled = false\n async function load() {\n setLoading(true)\n try {\n const params = new URLSearchParams({ offeringId: offering.id })\n if (Object.keys(configuration).length > 0) {\n params.set('configuration', JSON.stringify(configuration))\n }\n const res = await fetch(`/api/cpq/quotes/attributes?${params}`)\n if (res.ok && !cancelled) {\n const data = await res.json()\n const attrs = (data.attributes ?? []) as ConstrainedAttribute[]\n setAttributes(attrs.filter((a: ConstrainedAttribute & { resolutionTime?: string }) => !a.resolutionTime || a.resolutionTime === 'run_time'))\n }\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [offering.id, configVersion]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleChange = useCallback((code: string, value: unknown) => {\n onChange(code, value)\n // Check for dependent attributes\n const hasDependents = attributes.some((a) => {\n const dep = a.constraints as Record<string, unknown> | null\n const depOn = dep?.dependsOn as { attributeCode?: string } | undefined\n const dynSrc = dep?.dynamicOptionsSource as { filterByDimension?: Record<string, string> } | undefined\n return depOn?.attributeCode === code ||\n (dynSrc?.filterByDimension && Object.values(dynSrc.filterByDimension).some((v) => v === `$${code}`))\n })\n if (hasDependents) setConfigVersion((v) => v + 1)\n }, [onChange, attributes])\n\n const renderAttribute = (attr: ConstrainedAttribute) => {\n const value = configuration[attr.code] ?? ''\n\n if (attr.attributeType === 'select' || attr.attributeType === 'enum') {\n return (\n <select value={String(value)} onChange={(e) => handleChange(attr.code, e.target.value)} className=\"w-full rounded-md border px-3 py-2 text-sm\">\n <option value=\"\">{t('cpq.wizard.selectPlaceholder', 'Select...')}</option>\n {attr.options?.map((opt) => <option key={opt.value} value={opt.value}>{opt.label}</option>)}\n </select>\n )\n }\n if (attr.attributeType === 'boolean') {\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <input type=\"checkbox\" checked={Boolean(value)} onChange={(e) => handleChange(attr.code, e.target.checked)} className=\"rounded border\" />\n {attr.name}\n </label>\n )\n }\n if (attr.attributeType === 'number') {\n return (\n <NumberInput\n value={value === '' || value == null ? null : Number(value)}\n onChange={(n) => handleChange(attr.code, n ?? '')}\n />\n )\n }\n return <input type=\"text\" value={String(value)} onChange={(e) => handleChange(attr.code, e.target.value)} className=\"w-full rounded-md border px-3 py-2 text-sm\" />\n }\n\n return (\n <div className=\"rounded-lg border p-4 space-y-3\">\n <div className=\"flex items-center justify-between border-b pb-2\">\n <h4 className=\"text-sm font-medium\">{offering.name}</h4>\n <button type=\"button\" onClick={onRemove} className=\"text-xs text-muted-foreground hover:text-foreground\">{t('cpq.wizard.remove', 'Remove')}</button>\n </div>\n {loading ? (\n <div className=\"text-sm text-muted-foreground\">{t('cpq.wizard.loadingAttributes', 'Loading attributes...')}</div>\n ) : attributes.length === 0 ? (\n <div className=\"text-sm text-muted-foreground\">{t('cpq.wizard.steps.offeringSelect.noAttributes', 'No configurable attributes.')}</div>\n ) : (\n <div className=\"space-y-3\">\n {attributes.map((attr) => (\n <div key={attr.code}>\n <label className=\"block text-sm font-medium mb-1\">\n {attr.name}\n {attr.isRequired && <span className=\"text-destructive ml-0.5\">*</span>}\n </label>\n {renderAttribute(attr)}\n {attr.helpText && <p className=\"text-xs text-muted-foreground mt-1\">{attr.helpText}</p>}\n </div>\n ))}\n </div>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Port capacity indicator \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\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 BandwidthIndicator({\n portCapacityMb,\n items,\n}: {\n portCapacityMb: number\n items: SelectedItem[]\n}) {\n const t = useT()\n const totalBandwidth = items.reduce((sum, item) => {\n const bw = Number(item.configuration.bandwidth_mb) || 0\n return sum + bw\n }, 0)\n\n const pct = portCapacityMb > 0 ? Math.min(100, (totalBandwidth / portCapacityMb) * 100) : 0\n const overCapacity = totalBandwidth > portCapacityMb\n\n return (\n <div className=\"rounded-lg border p-3 space-y-2\">\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"font-medium\">{t('cpq.wizard.steps.offeringSelect.bandwidth.title', 'Bandwidth Utilization')}</span>\n <span className={overCapacity ? 'text-destructive font-medium' : 'text-muted-foreground'}>\n {t('cpq.wizard.steps.offeringSelect.bandwidth.usage', '{used} / {capacity} MB', { used: totalBandwidth, capacity: portCapacityMb })}\n </span>\n </div>\n <div className=\"h-2 rounded-full bg-muted overflow-hidden\">\n <div\n className={`h-full rounded-full transition-all ${overCapacity ? 'bg-destructive' : 'bg-primary'}`}\n style={{ width: `${Math.min(100, pct)}%` }}\n />\n </div>\n {overCapacity && (\n <p className=\"text-xs text-destructive\">\n {t('cpq.wizard.steps.offeringSelect.bandwidth.exceeded', 'Total bandwidth exceeds port capacity by {excess} MB', { excess: totalBandwidth - portCapacityMb })}\n </p>\n )}\n </div>\n )\n}\n\n// \u2500\u2500\u2500 Main 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\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst PORT_SIZE_MB: Record<string, number> = { '1G': 1000, '10G': 10000, '100G': 100000 }\n\nexport function OfferingSelectStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }: WizardStepProps) {\n const t = useT()\n const [offerings, setOfferings] = useState<Offering[]>([])\n const [selectedItems, setSelectedItems] = useState<SelectedItem[]>([])\n const [loading, setLoading] = useState(true)\n const [saving, setSaving] = useState(false)\n\n const enableConfiguration = config.enableConfiguration === true\n const multiSelect = config.multiSelect !== false // default true for offering_select\n const offeringFilter = config.offeringFilter as { specCodes?: string[]; offeringCodes?: string[] } | undefined\n const displayMode = (config.displayMode as string) ?? 'grid'\n\n // Resolve port capacity from prior step data\n const portCapacityMb = (() => {\n for (const data of Object.values(stepData)) {\n const cfg = data.configuration as Record<string, unknown> | undefined\n if (cfg?.port_size) return PORT_SIZE_MB[String(cfg.port_size)] ?? 0\n }\n return 0\n })()\n\n // Restore previous selections\n const previousData = stepData[currentStepId] as { items?: SelectedItem[] } | undefined\n\n useEffect(() => {\n async function load() {\n setLoading(true)\n try {\n const params = new URLSearchParams({ page: '1', pageSize: '100' })\n const res = await fetch(`/api/cpq/product-offerings?${params}`)\n if (res.ok) {\n const data = await res.json()\n let items = (data.items ?? []) as Offering[]\n if (offeringFilter?.offeringCodes?.length) {\n const codeOrder = offeringFilter.offeringCodes!\n items = items\n .filter((o) => codeOrder.includes(o.code))\n .sort((a, b) => codeOrder.indexOf(a.code) - codeOrder.indexOf(b.code))\n }\n items = items.filter((o) => o.isActive)\n setOfferings(items)\n\n // Restore previous selections\n if (previousData?.items?.length) {\n const restored = previousData.items\n .map((prev) => {\n const o = items.find((i) => i.id === prev.offering.id)\n return o ? { offering: o, configuration: prev.configuration, lineId: prev.lineId } : null\n })\n .filter(Boolean) as SelectedItem[]\n setSelectedItems(restored)\n }\n }\n } finally {\n setLoading(false)\n }\n }\n load()\n }, []) // eslint-disable-line react-hooks/exhaustive-deps\n\n const addItem = useCallback((offering: Offering) => {\n setSelectedItems((prev) => {\n if (!multiSelect) return [{ offering, configuration: {} }]\n return [...prev, { offering, configuration: {} }]\n })\n }, [multiSelect])\n\n const removeItem = useCallback((index: number) => {\n setSelectedItems((prev) => prev.filter((_, i) => i !== index))\n }, [])\n\n const updateItemConfig = useCallback((index: number, code: string, value: unknown) => {\n setSelectedItems((prev) => prev.map((item, i) =>\n i === index ? { ...item, configuration: { ...item.configuration, [code]: value } } : item\n ))\n }, [])\n\n // Resolve customerId from props or stepData\n const resolvedCustomerId = customerId ?? (() => {\n for (const data of Object.values(stepData)) {\n if (data.customerId) return data.customerId as string\n }\n return null\n })()\n\n const handleNext = useCallback(async () => {\n if (selectedItems.length === 0) return\n setSaving(true)\n try {\n let activeQuoteId = quoteId\n const output: Record<string, unknown> = { items: selectedItems }\n\n if (enableConfiguration) {\n // Auto-create quote if needed\n if (!activeQuoteId && resolvedCustomerId) {\n const createRes = await fetch('/api/cpq/quotes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ customerId: resolvedCustomerId }),\n })\n if (createRes.ok) {\n const quoteData = await createRes.json()\n activeQuoteId = quoteData.id\n output.quoteId = activeQuoteId\n }\n }\n\n // Add or update each selected item on the quote\n if (activeQuoteId) {\n for (const item of selectedItems) {\n if (item.lineId) {\n // Update existing line\n await fetch(`/api/cpq/quotes/${activeQuoteId}/items/${item.lineId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ configuration: item.configuration }),\n })\n } else {\n // Create new line\n const res = await fetch(`/api/cpq/quotes/${activeQuoteId}/items`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ offeringId: item.offering.id, configuration: item.configuration }),\n })\n if (res.ok) {\n const data = await res.json()\n const line = data.lines?.find((l: { offeringId: string }) => l.offeringId === item.offering.id)\n item.lineId = line?.lineId\n }\n }\n }\n output.quoteId = activeQuoteId\n }\n }\n\n // Also set single-item compat fields\n if (selectedItems.length === 1) {\n const first = selectedItems[0]\n output.offeringId = first.offering.id\n output.offeringCode = first.offering.code\n output.offeringName = first.offering.name\n output.specId = first.offering.specId\n if (enableConfiguration) output.configuration = first.configuration\n }\n\n onComplete(output)\n } finally {\n setSaving(false)\n }\n }, [selectedItems, enableConfiguration, quoteId, resolvedCustomerId, onComplete])\n\n if (loading) {\n return <div className=\"text-sm text-muted-foreground\">{t('cpq.wizard.steps.offeringSelect.loading', 'Loading offerings...')}</div>\n }\n\n // Offerings not yet added\n const availableOfferings = offerings.filter((o) =>\n multiSelect ? true : !selectedItems.some((s) => s.offering.id === o.id)\n )\n\n return (\n <div className=\"space-y-4\">\n {/* Offering cards */}\n <div\n className={\n displayMode === 'grid'\n ? 'grid grid-cols-[repeat(auto-fill,minmax(240px,1fr))] gap-3'\n : 'flex flex-col gap-2'\n }\n >\n {availableOfferings.map((o) => {\n const isAdded = selectedItems.some((s) => s.offering.id === o.id)\n return (\n <div\n key={o.id}\n onClick={() => !isAdded && addItem(o)}\n className={`rounded-lg border-2 p-4 transition-colors ${\n isAdded\n ? 'border-primary/30 bg-primary/5 opacity-60 cursor-default'\n : 'border-muted cursor-pointer hover:bg-muted/30'\n }`}\n >\n <div className=\"flex items-center justify-between\">\n <h4 className=\"text-sm font-medium\">{o.name}</h4>\n {isAdded && <span className=\"text-xs text-primary font-medium\">{t('cpq.wizard.steps.offeringSelect.added', 'Added')}</span>}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1\">{o.description || o.code}</p>\n {!isAdded && (\n <span className=\"text-xs text-primary mt-2 inline-block\">{t('cpq.wizard.steps.offeringSelect.add', '+ Add')}</span>\n )}\n </div>\n )\n })}\n {offerings.length === 0 && (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.wizard.steps.offeringSelect.empty', 'No offerings available.')}\n </div>\n )}\n </div>\n\n {/* Bandwidth indicator */}\n {portCapacityMb > 0 && selectedItems.length > 0 && (\n <BandwidthIndicator portCapacityMb={portCapacityMb} items={selectedItems} />\n )}\n\n {/* Configuration panels for selected items */}\n {enableConfiguration && selectedItems.length > 0 && (\n <div className=\"space-y-3\">\n <h4 className=\"text-sm font-medium\">{t('cpq.wizard.steps.offeringSelect.selectedServices', 'Selected Services ({count})', { count: selectedItems.length })}</h4>\n {selectedItems.map((item, index) => (\n <ConfigPanel\n key={`${item.offering.id}-${index}`}\n offering={item.offering}\n configuration={item.configuration}\n onChange={(code, value) => updateItemConfig(index, code, value)}\n onRemove={() => removeItem(index)}\n />\n ))}\n </div>\n )}\n\n {/* No configuration \u2014 just show selected list */}\n {!enableConfiguration && selectedItems.length > 0 && (\n <div className=\"space-y-2\">\n <h4 className=\"text-sm font-medium\">{t('cpq.wizard.steps.offeringSelect.selected', 'Selected ({count})', { count: selectedItems.length })}</h4>\n {selectedItems.map((item, index) => (\n <div key={`${item.offering.id}-${index}`} className=\"flex items-center justify-between rounded-md border p-3\">\n <span className=\"text-sm font-medium\">{item.offering.name}</span>\n <button type=\"button\" onClick={() => removeItem(index)} className=\"text-xs text-muted-foreground hover:text-foreground\">{t('cpq.wizard.remove', 'Remove')}</button>\n </div>\n ))}\n </div>\n )}\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={handleNext}\n disabled={selectedItems.length === 0 || saving}\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 {saving ? t('cpq.common.saving', 'Saving...') : t('cpq.wizard.next', 'Next')}\n </button>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": ";AA4FQ,SACE,KADF;AA1FR,SAAgB,UAAU,WAAW,mBAAmB;AACxD,SAAS,YAAY;AAErB,SAAS,mBAAmB;AA8B5B,SAAS,YAAY;AAAA,EACnB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAKG;AACD,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,YAAY,aAAa,IAAI,SAAiC,CAAC,CAAC;AACvE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,CAAC;AAEpD,YAAU,MAAM;AACd,QAAI,YAAY;AAChB,mBAAe,OAAO;AACpB,iBAAW,IAAI;AACf,UAAI;AACF,cAAM,SAAS,IAAI,gBAAgB,EAAE,YAAY,SAAS,GAAG,CAAC;AAC9D,YAAI,OAAO,KAAK,aAAa,EAAE,SAAS,GAAG;AACzC,iBAAO,IAAI,iBAAiB,KAAK,UAAU,aAAa,CAAC;AAAA,QAC3D;AACA,cAAM,MAAM,MAAM,MAAM,8BAA8B,MAAM,EAAE;AAC9D,YAAI,IAAI,MAAM,CAAC,WAAW;AACxB,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,gBAAM,QAAS,KAAK,cAAc,CAAC;AACnC,wBAAc,MAAM,OAAO,CAAC,MAA0D,CAAC,EAAE,kBAAkB,EAAE,mBAAmB,UAAU,CAAC;AAAA,QAC7I;AAAA,MACF,UAAE;AACA,YAAI,CAAC,UAAW,YAAW,KAAK;AAAA,MAClC;AAAA,IACF;AACA,SAAK;AACL,WAAO,MAAM;AAAE,kBAAY;AAAA,IAAK;AAAA,EAClC,GAAG,CAAC,SAAS,IAAI,aAAa,CAAC;AAE/B,QAAM,eAAe,YAAY,CAAC,MAAc,UAAmB;AACjE,aAAS,MAAM,KAAK;AAEpB,UAAM,gBAAgB,WAAW,KAAK,CAAC,MAAM;AAC3C,YAAM,MAAM,EAAE;AACd,YAAM,QAAQ,KAAK;AACnB,YAAM,SAAS,KAAK;AACpB,aAAO,OAAO,kBAAkB,QAC7B,QAAQ,qBAAqB,OAAO,OAAO,OAAO,iBAAiB,EAAE,KAAK,CAAC,MAAM,MAAM,IAAI,IAAI,EAAE;AAAA,IACtG,CAAC;AACD,QAAI,cAAe,kBAAiB,CAAC,MAAM,IAAI,CAAC;AAAA,EAClD,GAAG,CAAC,UAAU,UAAU,CAAC;AAEzB,QAAM,kBAAkB,CAAC,SAA+B;AACtD,UAAM,QAAQ,cAAc,KAAK,IAAI,KAAK;AAE1C,QAAI,KAAK,kBAAkB,YAAY,KAAK,kBAAkB,QAAQ;AACpE,aACE,qBAAC,YAAO,OAAO,OAAO,KAAK,GAAG,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,EAAE,OAAO,KAAK,GAAG,WAAU,8CAChG;AAAA,4BAAC,YAAO,OAAM,IAAI,YAAE,gCAAgC,WAAW,GAAE;AAAA,QAChE,KAAK,SAAS,IAAI,CAAC,QAAQ,oBAAC,YAAuB,OAAO,IAAI,OAAQ,cAAI,SAAlC,IAAI,KAAoC,CAAS;AAAA,SAC5F;AAAA,IAEJ;AACA,QAAI,KAAK,kBAAkB,WAAW;AACpC,aACE,qBAAC,WAAM,WAAU,mCACf;AAAA,4BAAC,WAAM,MAAK,YAAW,SAAS,QAAQ,KAAK,GAAG,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,EAAE,OAAO,OAAO,GAAG,WAAU,kBAAiB;AAAA,QACtI,KAAK;AAAA,SACR;AAAA,IAEJ;AACA,QAAI,KAAK,kBAAkB,UAAU;AACnC,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,UAAU,MAAM,SAAS,OAAO,OAAO,OAAO,KAAK;AAAA,UAC1D,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,KAAK,EAAE;AAAA;AAAA,MAClD;AAAA,IAEJ;AACA,WAAO,oBAAC,WAAM,MAAK,QAAO,OAAO,OAAO,KAAK,GAAG,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,EAAE,OAAO,KAAK,GAAG,WAAU,8CAA6C;AAAA,EACnK;AAEA,SACE,qBAAC,SAAI,WAAU,mCACb;AAAA,yBAAC,SAAI,WAAU,mDACb;AAAA,0BAAC,QAAG,WAAU,uBAAuB,mBAAS,MAAK;AAAA,MACnD,oBAAC,YAAO,MAAK,UAAS,SAAS,UAAU,WAAU,uDAAuD,YAAE,qBAAqB,QAAQ,GAAE;AAAA,OAC7I;AAAA,IACC,UACC,oBAAC,SAAI,WAAU,iCAAiC,YAAE,gCAAgC,uBAAuB,GAAE,IACzG,WAAW,WAAW,IACxB,oBAAC,SAAI,WAAU,iCAAiC,YAAE,gDAAgD,6BAA6B,GAAE,IAEjI,oBAAC,SAAI,WAAU,aACZ,qBAAW,IAAI,CAAC,SACf,qBAAC,SACC;AAAA,2BAAC,WAAM,WAAU,kCACd;AAAA,aAAK;AAAA,QACL,KAAK,cAAc,oBAAC,UAAK,WAAU,2BAA0B,eAAC;AAAA,SACjE;AAAA,MACC,gBAAgB,IAAI;AAAA,MACpB,KAAK,YAAY,oBAAC,OAAE,WAAU,sCAAsC,eAAK,UAAS;AAAA,SAN3E,KAAK,IAOf,CACD,GACH;AAAA,KAEJ;AAEJ;AAIA,SAAS,mBAAmB;AAAA,EAC1B;AAAA,EACA;AACF,GAGG;AACD,QAAM,IAAI,KAAK;AACf,QAAM,iBAAiB,MAAM,OAAO,CAAC,KAAK,SAAS;AACjD,UAAM,KAAK,OAAO,KAAK,cAAc,YAAY,KAAK;AACtD,WAAO,MAAM;AAAA,EACf,GAAG,CAAC;AAEJ,QAAM,MAAM,iBAAiB,IAAI,KAAK,IAAI,KAAM,iBAAiB,iBAAkB,GAAG,IAAI;AAC1F,QAAM,eAAe,iBAAiB;AAEtC,SACE,qBAAC,SAAI,WAAU,mCACb;AAAA,yBAAC,SAAI,WAAU,6CACb;AAAA,0BAAC,UAAK,WAAU,eAAe,YAAE,mDAAmD,uBAAuB,GAAE;AAAA,MAC7G,oBAAC,UAAK,WAAW,eAAe,iCAAiC,yBAC9D,YAAE,mDAAmD,0BAA0B,EAAE,MAAM,gBAAgB,UAAU,eAAe,CAAC,GACpI;AAAA,OACF;AAAA,IACA,oBAAC,SAAI,WAAU,6CACb;AAAA,MAAC;AAAA;AAAA,QACC,WAAW,sCAAsC,eAAe,mBAAmB,YAAY;AAAA,QAC/F,OAAO,EAAE,OAAO,GAAG,KAAK,IAAI,KAAK,GAAG,CAAC,IAAI;AAAA;AAAA,IAC3C,GACF;AAAA,IACC,gBACC,oBAAC,OAAE,WAAU,4BACV,YAAE,sDAAsD,wDAAwD,EAAE,QAAQ,iBAAiB,eAAe,CAAC,GAC9J;AAAA,KAEJ;AAEJ;AAIA,MAAM,eAAuC,EAAE,MAAM,KAAM,OAAO,KAAO,QAAQ,IAAO;AAEjF,SAAS,mBAAmB,EAAE,QAAQ,YAAY,QAAQ,UAAU,eAAe,gBAAgB,SAAS,WAAW,GAAoB;AAChJ,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,WAAW,YAAY,IAAI,SAAqB,CAAC,CAAC;AACzD,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAyB,CAAC,CAAC;AACrE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAE1C,QAAM,sBAAsB,OAAO,wBAAwB;AAC3D,QAAM,cAAc,OAAO,gBAAgB;AAC3C,QAAM,iBAAiB,OAAO;AAC9B,QAAM,cAAe,OAAO,eAA0B;AAGtD,QAAM,kBAAkB,MAAM;AAC5B,eAAW,QAAQ,OAAO,OAAO,QAAQ,GAAG;AAC1C,YAAM,MAAM,KAAK;AACjB,UAAI,KAAK,UAAW,QAAO,aAAa,OAAO,IAAI,SAAS,CAAC,KAAK;AAAA,IACpE;AACA,WAAO;AAAA,EACT,GAAG;AAGH,QAAM,eAAe,SAAS,aAAa;AAE3C,YAAU,MAAM;AACd,mBAAe,OAAO;AACpB,iBAAW,IAAI;AACf,UAAI;AACF,cAAM,SAAS,IAAI,gBAAgB,EAAE,MAAM,KAAK,UAAU,MAAM,CAAC;AACjE,cAAM,MAAM,MAAM,MAAM,8BAA8B,MAAM,EAAE;AAC9D,YAAI,IAAI,IAAI;AACV,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,cAAI,QAAS,KAAK,SAAS,CAAC;AAC5B,cAAI,gBAAgB,eAAe,QAAQ;AACzC,kBAAM,YAAY,eAAe;AACjC,oBAAQ,MACL,OAAO,CAAC,MAAM,UAAU,SAAS,EAAE,IAAI,CAAC,EACxC,KAAK,CAAC,GAAG,MAAM,UAAU,QAAQ,EAAE,IAAI,IAAI,UAAU,QAAQ,EAAE,IAAI,CAAC;AAAA,UACzE;AACA,kBAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,QAAQ;AACtC,uBAAa,KAAK;AAGlB,cAAI,cAAc,OAAO,QAAQ;AAC/B,kBAAM,WAAW,aAAa,MAC3B,IAAI,CAAC,SAAS;AACb,oBAAM,IAAI,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,KAAK,SAAS,EAAE;AACrD,qBAAO,IAAI,EAAE,UAAU,GAAG,eAAe,KAAK,eAAe,QAAQ,KAAK,OAAO,IAAI;AAAA,YACvF,CAAC,EACA,OAAO,OAAO;AACjB,6BAAiB,QAAQ;AAAA,UAC3B;AAAA,QACF;AAAA,MACF,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AACA,SAAK;AAAA,EACP,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,YAAY,CAAC,aAAuB;AAClD,qBAAiB,CAAC,SAAS;AACzB,UAAI,CAAC,YAAa,QAAO,CAAC,EAAE,UAAU,eAAe,CAAC,EAAE,CAAC;AACzD,aAAO,CAAC,GAAG,MAAM,EAAE,UAAU,eAAe,CAAC,EAAE,CAAC;AAAA,IAClD,CAAC;AAAA,EACH,GAAG,CAAC,WAAW,CAAC;AAEhB,QAAM,aAAa,YAAY,CAAC,UAAkB;AAChD,qBAAiB,CAAC,SAAS,KAAK,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK,CAAC;AAAA,EAC/D,GAAG,CAAC,CAAC;AAEL,QAAM,mBAAmB,YAAY,CAAC,OAAe,MAAc,UAAmB;AACpF,qBAAiB,CAAC,SAAS,KAAK;AAAA,MAAI,CAAC,MAAM,MACzC,MAAM,QAAQ,EAAE,GAAG,MAAM,eAAe,EAAE,GAAG,KAAK,eAAe,CAAC,IAAI,GAAG,MAAM,EAAE,IAAI;AAAA,IACvF,CAAC;AAAA,EACH,GAAG,CAAC,CAAC;AAGL,QAAM,qBAAqB,eAAe,MAAM;AAC9C,eAAW,QAAQ,OAAO,OAAO,QAAQ,GAAG;AAC1C,UAAI,KAAK,WAAY,QAAO,KAAK;AAAA,IACnC;AACA,WAAO;AAAA,EACT,GAAG;AAEH,QAAM,aAAa,YAAY,YAAY;AACzC,QAAI,cAAc,WAAW,EAAG;AAChC,cAAU,IAAI;AACd,QAAI;AACF,UAAI,gBAAgB;AACpB,YAAM,SAAkC,EAAE,OAAO,cAAc;AAE/D,UAAI,qBAAqB;AAEvB,YAAI,CAAC,iBAAiB,oBAAoB;AACxC,gBAAM,YAAY,MAAM,MAAM,mBAAmB;AAAA,YAC/C,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,mBAAmB,CAAC;AAAA,UACzD,CAAC;AACD,cAAI,UAAU,IAAI;AAChB,kBAAM,YAAY,MAAM,UAAU,KAAK;AACvC,4BAAgB,UAAU;AAC1B,mBAAO,UAAU;AAAA,UACnB;AAAA,QACF;AAGA,YAAI,eAAe;AACjB,qBAAW,QAAQ,eAAe;AAChC,gBAAI,KAAK,QAAQ;AAEf,oBAAM,MAAM,mBAAmB,aAAa,UAAU,KAAK,MAAM,IAAI;AAAA,gBACnE,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,EAAE,eAAe,KAAK,cAAc,CAAC;AAAA,cAC5D,CAAC;AAAA,YACH,OAAO;AAEL,oBAAM,MAAM,MAAM,MAAM,mBAAmB,aAAa,UAAU;AAAA,gBAChE,QAAQ;AAAA,gBACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,gBAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,KAAK,SAAS,IAAI,eAAe,KAAK,cAAc,CAAC;AAAA,cAC1F,CAAC;AACD,kBAAI,IAAI,IAAI;AACV,sBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,sBAAM,OAAO,KAAK,OAAO,KAAK,CAAC,MAA8B,EAAE,eAAe,KAAK,SAAS,EAAE;AAC9F,qBAAK,SAAS,MAAM;AAAA,cACtB;AAAA,YACF;AAAA,UACF;AACA,iBAAO,UAAU;AAAA,QACnB;AAAA,MACF;AAGA,UAAI,cAAc,WAAW,GAAG;AAC9B,cAAM,QAAQ,cAAc,CAAC;AAC7B,eAAO,aAAa,MAAM,SAAS;AACnC,eAAO,eAAe,MAAM,SAAS;AACrC,eAAO,eAAe,MAAM,SAAS;AACrC,eAAO,SAAS,MAAM,SAAS;AAC/B,YAAI,oBAAqB,QAAO,gBAAgB,MAAM;AAAA,MACxD;AAEA,iBAAW,MAAM;AAAA,IACnB,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,eAAe,qBAAqB,SAAS,oBAAoB,UAAU,CAAC;AAEhF,MAAI,SAAS;AACX,WAAO,oBAAC,SAAI,WAAU,iCAAiC,YAAE,2CAA2C,sBAAsB,GAAE;AAAA,EAC9H;AAGA,QAAM,qBAAqB,UAAU;AAAA,IAAO,CAAC,MAC3C,cAAc,OAAO,CAAC,cAAc,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE;AAAA,EACxE;AAEA,SACE,qBAAC,SAAI,WAAU,aAEb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WACE,gBAAgB,SACZ,+DACA;AAAA,QAGL;AAAA,6BAAmB,IAAI,CAAC,MAAM;AAC7B,kBAAM,UAAU,cAAc,KAAK,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,EAAE;AAChE,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,SAAS,MAAM,CAAC,WAAW,QAAQ,CAAC;AAAA,gBACpC,WAAW,6CACT,UACI,6DACA,+CACN;AAAA,gBAEA;AAAA,uCAAC,SAAI,WAAU,qCACb;AAAA,wCAAC,QAAG,WAAU,uBAAuB,YAAE,MAAK;AAAA,oBAC3C,WAAW,oBAAC,UAAK,WAAU,oCAAoC,YAAE,yCAAyC,OAAO,GAAE;AAAA,qBACtH;AAAA,kBACA,oBAAC,OAAE,WAAU,sCAAsC,YAAE,eAAe,EAAE,MAAK;AAAA,kBAC1E,CAAC,WACA,oBAAC,UAAK,WAAU,0CAA0C,YAAE,uCAAuC,OAAO,GAAE;AAAA;AAAA;AAAA,cAdzG,EAAE;AAAA,YAgBT;AAAA,UAEJ,CAAC;AAAA,UACA,UAAU,WAAW,KACpB,oBAAC,SAAI,WAAU,2EACZ,YAAE,yCAAyC,yBAAyB,GACvE;AAAA;AAAA;AAAA,IAEJ;AAAA,IAGC,iBAAiB,KAAK,cAAc,SAAS,KAC5C,oBAAC,sBAAmB,gBAAgC,OAAO,eAAe;AAAA,IAI3E,uBAAuB,cAAc,SAAS,KAC7C,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,QAAG,WAAU,uBAAuB,YAAE,oDAAoD,+BAA+B,EAAE,OAAO,cAAc,OAAO,CAAC,GAAE;AAAA,MAC1J,cAAc,IAAI,CAAC,MAAM,UACxB;AAAA,QAAC;AAAA;AAAA,UAEC,UAAU,KAAK;AAAA,UACf,eAAe,KAAK;AAAA,UACpB,UAAU,CAAC,MAAM,UAAU,iBAAiB,OAAO,MAAM,KAAK;AAAA,UAC9D,UAAU,MAAM,WAAW,KAAK;AAAA;AAAA,QAJ3B,GAAG,KAAK,SAAS,EAAE,IAAI,KAAK;AAAA,MAKnC,CACD;AAAA,OACH;AAAA,IAID,CAAC,uBAAuB,cAAc,SAAS,KAC9C,qBAAC,SAAI,WAAU,aACb;AAAA,0BAAC,QAAG,WAAU,uBAAuB,YAAE,4CAA4C,sBAAsB,EAAE,OAAO,cAAc,OAAO,CAAC,GAAE;AAAA,MACzI,cAAc,IAAI,CAAC,MAAM,UACxB,qBAAC,SAAyC,WAAU,2DAClD;AAAA,4BAAC,UAAK,WAAU,uBAAuB,eAAK,SAAS,MAAK;AAAA,QAC1D,oBAAC,YAAO,MAAK,UAAS,SAAS,MAAM,WAAW,KAAK,GAAG,WAAU,uDAAuD,YAAE,qBAAqB,QAAQ,GAAE;AAAA,WAFlJ,GAAG,KAAK,SAAS,EAAE,IAAI,KAAK,EAGtC,CACD;AAAA,OACH;AAAA,IAGF,qBAAC,SAAI,WAAU,+BACZ;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,UAAU,cAAc,WAAW,KAAK;AAAA,UACxC,WAAU;AAAA,UAET,mBAAS,EAAE,qBAAqB,WAAW,IAAI,EAAE,mBAAmB,MAAM;AAAA;AAAA,MAC7E;AAAA,OACF;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -1,6 +1,7 @@
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
  import { NumberInput } from "../../components/NumberInput.js";
5
6
  function resolveOfferingId(config, stepData) {
6
7
  const source = config.offeringSource;
@@ -13,6 +14,7 @@ function resolveOfferingId(config, stepData) {
13
14
  return null;
14
15
  }
15
16
  function ProductConfigureStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }) {
17
+ const t = useT();
16
18
  const [attributes, setAttributes] = useState([]);
17
19
  const [values, setValues] = useState({});
18
20
  const [loading, setLoading] = useState(true);
@@ -145,7 +147,7 @@ function ProductConfigureStep({ config, onComplete, onBack, stepData, currentSte
145
147
  disabled: isReadonly,
146
148
  className: "w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50",
147
149
  children: [
148
- /* @__PURE__ */ jsx("option", { value: "", children: "Select..." }),
150
+ /* @__PURE__ */ jsx("option", { value: "", children: t("cpq.wizard.selectPlaceholder", "Select...") }),
149
151
  attr.options?.map((opt) => /* @__PURE__ */ jsx("option", { value: opt.value, children: opt.label }, opt.value))
150
152
  ]
151
153
  }
@@ -188,10 +190,10 @@ function ProductConfigureStep({ config, onComplete, onBack, stepData, currentSte
188
190
  );
189
191
  };
190
192
  if (loading) {
191
- return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: "Loading attributes..." });
193
+ return /* @__PURE__ */ jsx("div", { className: "text-sm text-muted-foreground", children: t("cpq.wizard.loadingAttributes", "Loading attributes...") });
192
194
  }
193
195
  if (!offeringId) {
194
- return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "No offering selected. Please go back and select an offering." });
196
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.wizard.steps.productConfigure.noOffering", "No offering selected. Please go back and select an offering.") });
195
197
  }
196
198
  const renderGroup = (label, attrs) => /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
197
199
  /* @__PURE__ */ jsx("h4", { className: "text-sm font-medium border-b pb-2", children: label }),
@@ -209,9 +211,9 @@ function ProductConfigureStep({ config, onComplete, onBack, stepData, currentSte
209
211
  const groupAttrs = attributes.filter((a) => group.attributeCodes.includes(a.code));
210
212
  if (groupAttrs.length === 0) return null;
211
213
  return renderGroup(group.label, groupAttrs);
212
- }) : renderGroup("Configuration", attributes),
214
+ }) : renderGroup(t("cpq.lines.configuration", "Configuration"), attributes),
213
215
  /* @__PURE__ */ jsxs("div", { className: "flex gap-2 justify-end pt-2", children: [
214
- 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" }),
216
+ 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") }),
215
217
  /* @__PURE__ */ jsx(
216
218
  "button",
217
219
  {
@@ -219,7 +221,7 @@ function ProductConfigureStep({ config, onComplete, onBack, stepData, currentSte
219
221
  onClick: handleComplete,
220
222
  disabled: saving,
221
223
  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",
222
- children: saving ? "Saving..." : "Next"
224
+ children: saving ? t("cpq.common.saving", "Saving...") : t("cpq.wizard.next", "Next")
223
225
  }
224
226
  )
225
227
  ] })
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/cpq/workflows/steps/ProductConfigureStep.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport React, { useState, useEffect, useCallback } from 'react'\nimport type { WizardStepProps } from '../types'\nimport { NumberInput } from '../../components/NumberInput'\n\ntype ConstrainedAttribute = {\n code: string\n name: string\n attributeType: string\n isRequired: boolean\n options: Array<{ value: string; label: string }> | null\n constraints: Record<string, unknown> | null\n defaultValue: unknown | null\n helpText: string | null\n}\n\ntype AttributeGroup = {\n label: string\n attributeCodes: string[]\n}\n\nfunction resolveOfferingId(config: Record<string, unknown>, stepData: Record<string, Record<string, unknown>>): string | null {\n // Check explicit source\n const source = config.offeringSource as string | undefined\n if (source && stepData[source]) {\n return (stepData[source].offeringId as string) ?? null\n }\n // Auto-detect: find first step data with offeringId\n for (const data of Object.values(stepData)) {\n if (data.offeringId) return data.offeringId as string\n }\n return null\n}\n\nexport function ProductConfigureStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }: WizardStepProps) {\n const [attributes, setAttributes] = useState<ConstrainedAttribute[]>([])\n const [values, setValues] = useState<Record<string, unknown>>({})\n const [loading, setLoading] = useState(true)\n const [saving, setSaving] = useState(false)\n const [resolvedOfferingId, setResolvedOfferingId] = useState<string | null>(null)\n\n const directOfferingId = resolveOfferingId(config, stepData)\n const offeringCode = config.offeringCode as string | undefined\n const attributeGroups = config.attributeGroups as AttributeGroup[] | undefined\n\n // Resolve offering by code if no direct offeringId\n useEffect(() => {\n if (directOfferingId) {\n setResolvedOfferingId(directOfferingId)\n return\n }\n if (!offeringCode) return\n async function resolve() {\n try {\n const res = await fetch(`/api/cpq/product-offerings?code=${encodeURIComponent(offeringCode!)}`)\n if (res.ok) {\n const data = await res.json()\n const items = data.items ?? data\n if (Array.isArray(items) && items.length > 0) {\n setResolvedOfferingId(items[0].id)\n }\n }\n } catch { /* ignore */ }\n }\n resolve()\n }, [directOfferingId, offeringCode])\n\n const offeringId = resolvedOfferingId\n const hiddenAttributes = (config.hiddenAttributes as string[]) ?? []\n const readonlyAttributes = (config.readonlyAttributes as string[]) ?? []\n\n // Restore previous values\n const previousData = stepData[currentStepId] as { configuration?: Record<string, unknown> } | undefined\n\n useEffect(() => {\n async function loadAttributes() {\n if (!offeringId) { setLoading(false); return }\n setLoading(true)\n try {\n const params = new URLSearchParams({ offeringId })\n if (Object.keys(values).length > 0) {\n params.set('configuration', JSON.stringify(values))\n }\n const res = await fetch(`/api/cpq/quotes/attributes?${params}`)\n if (res.ok) {\n const data = await res.json()\n const attrs = (data.attributes ?? []) as ConstrainedAttribute[]\n setAttributes(attrs.filter((a) => !hiddenAttributes.includes(a.code)))\n\n // Set defaults on first load\n if (Object.keys(values).length === 0) {\n const defaults: Record<string, unknown> = previousData?.configuration ?? {}\n for (const attr of attrs) {\n if (defaults[attr.code] === undefined && attr.defaultValue != null) {\n defaults[attr.code] = attr.defaultValue\n }\n }\n setValues(defaults)\n }\n }\n } finally {\n setLoading(false)\n }\n }\n loadAttributes()\n }, [offeringId]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleChange = useCallback((code: string, value: unknown) => {\n setValues((prev) => ({ ...prev, [code]: value }))\n }, [])\n\n // Resolve customerId from props or stepData\n const resolvedCustomerId = customerId ?? (() => {\n for (const data of Object.values(stepData)) {\n if (data.customerId) return data.customerId as string\n }\n return null\n })()\n\n const handleComplete = useCallback(async () => {\n setSaving(true)\n try {\n let activeQuoteId = quoteId\n\n // Auto-create a quote if none exists yet\n if (!activeQuoteId && resolvedCustomerId && offeringId) {\n const createRes = await fetch('/api/cpq/quotes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ customerId: resolvedCustomerId }),\n })\n if (createRes.ok) {\n const quoteData = await createRes.json()\n activeQuoteId = quoteData.id\n }\n }\n\n if (activeQuoteId && offeringId) {\n // Check if there's already a line for this offering\n const existingLineId = (stepData[currentStepId] as { lineId?: string })?.lineId\n\n if (existingLineId) {\n await fetch(`/api/cpq/quotes/${activeQuoteId}/items/${existingLineId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ configuration: values }),\n })\n onComplete({ configuration: values, offeringId, lineId: existingLineId, quoteId: activeQuoteId })\n } else {\n const res = await fetch(`/api/cpq/quotes/${activeQuoteId}/items`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ offeringId, configuration: values }),\n })\n if (res.ok) {\n const data = await res.json()\n const line = data.lines?.find((l: { offeringId: string }) => l.offeringId === offeringId)\n onComplete({ configuration: values, offeringId, lineId: line?.lineId, quoteId: activeQuoteId })\n } else {\n onComplete({ configuration: values, offeringId, quoteId: activeQuoteId })\n }\n }\n } else {\n onComplete({ configuration: values, offeringId })\n }\n } finally {\n setSaving(false)\n }\n }, [values, quoteId, offeringId, stepData, currentStepId, onComplete, resolvedCustomerId])\n\n const renderAttribute = (attr: ConstrainedAttribute) => {\n const isReadonly = readonlyAttributes.includes(attr.code)\n const value = values[attr.code] ?? ''\n\n if (attr.attributeType === 'select' || attr.attributeType === 'enum') {\n return (\n <select\n value={String(value)}\n onChange={(e) => handleChange(attr.code, e.target.value)}\n disabled={isReadonly}\n className=\"w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50\"\n >\n <option value=\"\">Select...</option>\n {attr.options?.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n )\n }\n\n if (attr.attributeType === 'boolean') {\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={Boolean(value)}\n onChange={(e) => handleChange(attr.code, e.target.checked)}\n disabled={isReadonly}\n className=\"rounded border\"\n />\n {attr.name}\n </label>\n )\n }\n\n if (attr.attributeType === 'number') {\n return (\n <NumberInput\n value={value === '' || value == null ? null : Number(value)}\n onChange={(n) => handleChange(attr.code, n ?? '')}\n disabled={isReadonly}\n />\n )\n }\n\n return (\n <input\n type=\"text\"\n value={String(value)}\n onChange={(e) => handleChange(attr.code, e.target.value)}\n disabled={isReadonly}\n className=\"w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50\"\n />\n )\n }\n\n if (loading) {\n return <div className=\"text-sm text-muted-foreground\">Loading attributes...</div>\n }\n\n if (!offeringId) {\n return (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n No offering selected. Please go back and select an offering.\n </div>\n )\n }\n\n const renderGroup = (label: string, attrs: ConstrainedAttribute[]) => (\n <div key={label} className=\"space-y-3\">\n <h4 className=\"text-sm font-medium border-b pb-2\">{label}</h4>\n <div className=\"space-y-3\">\n {attrs.map((attr) => (\n <div key={attr.code}>\n <label className=\"block text-sm font-medium mb-1\">\n {attr.name}\n {attr.isRequired && <span className=\"text-destructive ml-0.5\">*</span>}\n </label>\n {renderAttribute(attr)}\n {attr.helpText && <p className=\"text-xs text-muted-foreground mt-1\">{attr.helpText}</p>}\n </div>\n ))}\n </div>\n </div>\n )\n\n return (\n <div className=\"space-y-4\">\n {attributeGroups?.length ? (\n attributeGroups.map((group) => {\n const groupAttrs = attributes.filter((a) => group.attributeCodes.includes(a.code))\n if (groupAttrs.length === 0) return null\n return renderGroup(group.label, groupAttrs)\n })\n ) : (\n renderGroup('Configuration', attributes)\n )}\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={handleComplete}\n disabled={saving}\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\"\n >\n {saving ? 'Saving...' : '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'\nimport { NumberInput } from '../../components/NumberInput'\n\ntype ConstrainedAttribute = {\n code: string\n name: string\n attributeType: string\n isRequired: boolean\n options: Array<{ value: string; label: string }> | null\n constraints: Record<string, unknown> | null\n defaultValue: unknown | null\n helpText: string | null\n}\n\ntype AttributeGroup = {\n label: string\n attributeCodes: string[]\n}\n\nfunction resolveOfferingId(config: Record<string, unknown>, stepData: Record<string, Record<string, unknown>>): string | null {\n // Check explicit source\n const source = config.offeringSource as string | undefined\n if (source && stepData[source]) {\n return (stepData[source].offeringId as string) ?? null\n }\n // Auto-detect: find first step data with offeringId\n for (const data of Object.values(stepData)) {\n if (data.offeringId) return data.offeringId as string\n }\n return null\n}\n\nexport function ProductConfigureStep({ config, onComplete, onBack, stepData, currentStepId, completedSteps, quoteId, customerId }: WizardStepProps) {\n const t = useT()\n const [attributes, setAttributes] = useState<ConstrainedAttribute[]>([])\n const [values, setValues] = useState<Record<string, unknown>>({})\n const [loading, setLoading] = useState(true)\n const [saving, setSaving] = useState(false)\n const [resolvedOfferingId, setResolvedOfferingId] = useState<string | null>(null)\n\n const directOfferingId = resolveOfferingId(config, stepData)\n const offeringCode = config.offeringCode as string | undefined\n const attributeGroups = config.attributeGroups as AttributeGroup[] | undefined\n\n // Resolve offering by code if no direct offeringId\n useEffect(() => {\n if (directOfferingId) {\n setResolvedOfferingId(directOfferingId)\n return\n }\n if (!offeringCode) return\n async function resolve() {\n try {\n const res = await fetch(`/api/cpq/product-offerings?code=${encodeURIComponent(offeringCode!)}`)\n if (res.ok) {\n const data = await res.json()\n const items = data.items ?? data\n if (Array.isArray(items) && items.length > 0) {\n setResolvedOfferingId(items[0].id)\n }\n }\n } catch { /* ignore */ }\n }\n resolve()\n }, [directOfferingId, offeringCode])\n\n const offeringId = resolvedOfferingId\n const hiddenAttributes = (config.hiddenAttributes as string[]) ?? []\n const readonlyAttributes = (config.readonlyAttributes as string[]) ?? []\n\n // Restore previous values\n const previousData = stepData[currentStepId] as { configuration?: Record<string, unknown> } | undefined\n\n useEffect(() => {\n async function loadAttributes() {\n if (!offeringId) { setLoading(false); return }\n setLoading(true)\n try {\n const params = new URLSearchParams({ offeringId })\n if (Object.keys(values).length > 0) {\n params.set('configuration', JSON.stringify(values))\n }\n const res = await fetch(`/api/cpq/quotes/attributes?${params}`)\n if (res.ok) {\n const data = await res.json()\n const attrs = (data.attributes ?? []) as ConstrainedAttribute[]\n setAttributes(attrs.filter((a) => !hiddenAttributes.includes(a.code)))\n\n // Set defaults on first load\n if (Object.keys(values).length === 0) {\n const defaults: Record<string, unknown> = previousData?.configuration ?? {}\n for (const attr of attrs) {\n if (defaults[attr.code] === undefined && attr.defaultValue != null) {\n defaults[attr.code] = attr.defaultValue\n }\n }\n setValues(defaults)\n }\n }\n } finally {\n setLoading(false)\n }\n }\n loadAttributes()\n }, [offeringId]) // eslint-disable-line react-hooks/exhaustive-deps\n\n const handleChange = useCallback((code: string, value: unknown) => {\n setValues((prev) => ({ ...prev, [code]: value }))\n }, [])\n\n // Resolve customerId from props or stepData\n const resolvedCustomerId = customerId ?? (() => {\n for (const data of Object.values(stepData)) {\n if (data.customerId) return data.customerId as string\n }\n return null\n })()\n\n const handleComplete = useCallback(async () => {\n setSaving(true)\n try {\n let activeQuoteId = quoteId\n\n // Auto-create a quote if none exists yet\n if (!activeQuoteId && resolvedCustomerId && offeringId) {\n const createRes = await fetch('/api/cpq/quotes', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ customerId: resolvedCustomerId }),\n })\n if (createRes.ok) {\n const quoteData = await createRes.json()\n activeQuoteId = quoteData.id\n }\n }\n\n if (activeQuoteId && offeringId) {\n // Check if there's already a line for this offering\n const existingLineId = (stepData[currentStepId] as { lineId?: string })?.lineId\n\n if (existingLineId) {\n await fetch(`/api/cpq/quotes/${activeQuoteId}/items/${existingLineId}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ configuration: values }),\n })\n onComplete({ configuration: values, offeringId, lineId: existingLineId, quoteId: activeQuoteId })\n } else {\n const res = await fetch(`/api/cpq/quotes/${activeQuoteId}/items`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ offeringId, configuration: values }),\n })\n if (res.ok) {\n const data = await res.json()\n const line = data.lines?.find((l: { offeringId: string }) => l.offeringId === offeringId)\n onComplete({ configuration: values, offeringId, lineId: line?.lineId, quoteId: activeQuoteId })\n } else {\n onComplete({ configuration: values, offeringId, quoteId: activeQuoteId })\n }\n }\n } else {\n onComplete({ configuration: values, offeringId })\n }\n } finally {\n setSaving(false)\n }\n }, [values, quoteId, offeringId, stepData, currentStepId, onComplete, resolvedCustomerId])\n\n const renderAttribute = (attr: ConstrainedAttribute) => {\n const isReadonly = readonlyAttributes.includes(attr.code)\n const value = values[attr.code] ?? ''\n\n if (attr.attributeType === 'select' || attr.attributeType === 'enum') {\n return (\n <select\n value={String(value)}\n onChange={(e) => handleChange(attr.code, e.target.value)}\n disabled={isReadonly}\n className=\"w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50\"\n >\n <option value=\"\">{t('cpq.wizard.selectPlaceholder', 'Select...')}</option>\n {attr.options?.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n )\n }\n\n if (attr.attributeType === 'boolean') {\n return (\n <label className=\"flex items-center gap-2 text-sm\">\n <input\n type=\"checkbox\"\n checked={Boolean(value)}\n onChange={(e) => handleChange(attr.code, e.target.checked)}\n disabled={isReadonly}\n className=\"rounded border\"\n />\n {attr.name}\n </label>\n )\n }\n\n if (attr.attributeType === 'number') {\n return (\n <NumberInput\n value={value === '' || value == null ? null : Number(value)}\n onChange={(n) => handleChange(attr.code, n ?? '')}\n disabled={isReadonly}\n />\n )\n }\n\n return (\n <input\n type=\"text\"\n value={String(value)}\n onChange={(e) => handleChange(attr.code, e.target.value)}\n disabled={isReadonly}\n className=\"w-full rounded-md border px-3 py-2 text-sm disabled:opacity-50\"\n />\n )\n }\n\n if (loading) {\n return <div className=\"text-sm text-muted-foreground\">{t('cpq.wizard.loadingAttributes', 'Loading attributes...')}</div>\n }\n\n if (!offeringId) {\n return (\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.wizard.steps.productConfigure.noOffering', 'No offering selected. Please go back and select an offering.')}\n </div>\n )\n }\n\n const renderGroup = (label: string, attrs: ConstrainedAttribute[]) => (\n <div key={label} className=\"space-y-3\">\n <h4 className=\"text-sm font-medium border-b pb-2\">{label}</h4>\n <div className=\"space-y-3\">\n {attrs.map((attr) => (\n <div key={attr.code}>\n <label className=\"block text-sm font-medium mb-1\">\n {attr.name}\n {attr.isRequired && <span className=\"text-destructive ml-0.5\">*</span>}\n </label>\n {renderAttribute(attr)}\n {attr.helpText && <p className=\"text-xs text-muted-foreground mt-1\">{attr.helpText}</p>}\n </div>\n ))}\n </div>\n </div>\n )\n\n return (\n <div className=\"space-y-4\">\n {attributeGroups?.length ? (\n attributeGroups.map((group) => {\n const groupAttrs = attributes.filter((a) => group.attributeCodes.includes(a.code))\n if (groupAttrs.length === 0) return null\n return renderGroup(group.label, groupAttrs)\n })\n ) : (\n renderGroup(t('cpq.lines.configuration', 'Configuration'), attributes)\n )}\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={handleComplete}\n disabled={saving}\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\"\n >\n {saving ? t('cpq.common.saving', 'Saving...') : t('cpq.wizard.next', 'Next')}\n </button>\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAmLQ,SAME,KANF;AAjLR,SAAgB,UAAU,WAAW,mBAAmB;AACxD,SAAS,YAAY;AAErB,SAAS,mBAAmB;AAkB5B,SAAS,kBAAkB,QAAiC,UAAkE;AAE5H,QAAM,SAAS,OAAO;AACtB,MAAI,UAAU,SAAS,MAAM,GAAG;AAC9B,WAAQ,SAAS,MAAM,EAAE,cAAyB;AAAA,EACpD;AAEA,aAAW,QAAQ,OAAO,OAAO,QAAQ,GAAG;AAC1C,QAAI,KAAK,WAAY,QAAO,KAAK;AAAA,EACnC;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,EAAE,QAAQ,YAAY,QAAQ,UAAU,eAAe,gBAAgB,SAAS,WAAW,GAAoB;AAClJ,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,YAAY,aAAa,IAAI,SAAiC,CAAC,CAAC;AACvE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAkC,CAAC,CAAC;AAChE,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,IAAI;AAC3C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAC1C,QAAM,CAAC,oBAAoB,qBAAqB,IAAI,SAAwB,IAAI;AAEhF,QAAM,mBAAmB,kBAAkB,QAAQ,QAAQ;AAC3D,QAAM,eAAe,OAAO;AAC5B,QAAM,kBAAkB,OAAO;AAG/B,YAAU,MAAM;AACd,QAAI,kBAAkB;AACpB,4BAAsB,gBAAgB;AACtC;AAAA,IACF;AACA,QAAI,CAAC,aAAc;AACnB,mBAAe,UAAU;AACvB,UAAI;AACF,cAAM,MAAM,MAAM,MAAM,mCAAmC,mBAAmB,YAAa,CAAC,EAAE;AAC9F,YAAI,IAAI,IAAI;AACV,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,gBAAM,QAAQ,KAAK,SAAS;AAC5B,cAAI,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,GAAG;AAC5C,kCAAsB,MAAM,CAAC,EAAE,EAAE;AAAA,UACnC;AAAA,QACF;AAAA,MACF,QAAQ;AAAA,MAAe;AAAA,IACzB;AACA,YAAQ;AAAA,EACV,GAAG,CAAC,kBAAkB,YAAY,CAAC;AAEnC,QAAM,aAAa;AACnB,QAAM,mBAAoB,OAAO,oBAAiC,CAAC;AACnE,QAAM,qBAAsB,OAAO,sBAAmC,CAAC;AAGvE,QAAM,eAAe,SAAS,aAAa;AAE3C,YAAU,MAAM;AACd,mBAAe,iBAAiB;AAC9B,UAAI,CAAC,YAAY;AAAE,mBAAW,KAAK;AAAG;AAAA,MAAO;AAC7C,iBAAW,IAAI;AACf,UAAI;AACF,cAAM,SAAS,IAAI,gBAAgB,EAAE,WAAW,CAAC;AACjD,YAAI,OAAO,KAAK,MAAM,EAAE,SAAS,GAAG;AAClC,iBAAO,IAAI,iBAAiB,KAAK,UAAU,MAAM,CAAC;AAAA,QACpD;AACA,cAAM,MAAM,MAAM,MAAM,8BAA8B,MAAM,EAAE;AAC9D,YAAI,IAAI,IAAI;AACV,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,gBAAM,QAAS,KAAK,cAAc,CAAC;AACnC,wBAAc,MAAM,OAAO,CAAC,MAAM,CAAC,iBAAiB,SAAS,EAAE,IAAI,CAAC,CAAC;AAGrE,cAAI,OAAO,KAAK,MAAM,EAAE,WAAW,GAAG;AACpC,kBAAM,WAAoC,cAAc,iBAAiB,CAAC;AAC1E,uBAAW,QAAQ,OAAO;AACxB,kBAAI,SAAS,KAAK,IAAI,MAAM,UAAa,KAAK,gBAAgB,MAAM;AAClE,yBAAS,KAAK,IAAI,IAAI,KAAK;AAAA,cAC7B;AAAA,YACF;AACA,sBAAU,QAAQ;AAAA,UACpB;AAAA,QACF;AAAA,MACF,UAAE;AACA,mBAAW,KAAK;AAAA,MAClB;AAAA,IACF;AACA,mBAAe;AAAA,EACjB,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,eAAe,YAAY,CAAC,MAAc,UAAmB;AACjE,cAAU,CAAC,UAAU,EAAE,GAAG,MAAM,CAAC,IAAI,GAAG,MAAM,EAAE;AAAA,EAClD,GAAG,CAAC,CAAC;AAGL,QAAM,qBAAqB,eAAe,MAAM;AAC9C,eAAW,QAAQ,OAAO,OAAO,QAAQ,GAAG;AAC1C,UAAI,KAAK,WAAY,QAAO,KAAK;AAAA,IACnC;AACA,WAAO;AAAA,EACT,GAAG;AAEH,QAAM,iBAAiB,YAAY,YAAY;AAC7C,cAAU,IAAI;AACd,QAAI;AACF,UAAI,gBAAgB;AAGpB,UAAI,CAAC,iBAAiB,sBAAsB,YAAY;AACtD,cAAM,YAAY,MAAM,MAAM,mBAAmB;AAAA,UAC/C,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,mBAAmB,CAAC;AAAA,QACzD,CAAC;AACD,YAAI,UAAU,IAAI;AAChB,gBAAM,YAAY,MAAM,UAAU,KAAK;AACvC,0BAAgB,UAAU;AAAA,QAC5B;AAAA,MACF;AAEA,UAAI,iBAAiB,YAAY;AAE/B,cAAM,iBAAkB,SAAS,aAAa,GAA2B;AAEzE,YAAI,gBAAgB;AAClB,gBAAM,MAAM,mBAAmB,aAAa,UAAU,cAAc,IAAI;AAAA,YACtE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,eAAe,OAAO,CAAC;AAAA,UAChD,CAAC;AACD,qBAAW,EAAE,eAAe,QAAQ,YAAY,QAAQ,gBAAgB,SAAS,cAAc,CAAC;AAAA,QAClG,OAAO;AACL,gBAAM,MAAM,MAAM,MAAM,mBAAmB,aAAa,UAAU;AAAA,YAChE,QAAQ;AAAA,YACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,YAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,eAAe,OAAO,CAAC;AAAA,UAC5D,CAAC;AACD,cAAI,IAAI,IAAI;AACV,kBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,kBAAM,OAAO,KAAK,OAAO,KAAK,CAAC,MAA8B,EAAE,eAAe,UAAU;AACxF,uBAAW,EAAE,eAAe,QAAQ,YAAY,QAAQ,MAAM,QAAQ,SAAS,cAAc,CAAC;AAAA,UAChG,OAAO;AACL,uBAAW,EAAE,eAAe,QAAQ,YAAY,SAAS,cAAc,CAAC;AAAA,UAC1E;AAAA,QACF;AAAA,MACF,OAAO;AACL,mBAAW,EAAE,eAAe,QAAQ,WAAW,CAAC;AAAA,MAClD;AAAA,IACF,UAAE;AACA,gBAAU,KAAK;AAAA,IACjB;AAAA,EACF,GAAG,CAAC,QAAQ,SAAS,YAAY,UAAU,eAAe,YAAY,kBAAkB,CAAC;AAEzF,QAAM,kBAAkB,CAAC,SAA+B;AACtD,UAAM,aAAa,mBAAmB,SAAS,KAAK,IAAI;AACxD,UAAM,QAAQ,OAAO,KAAK,IAAI,KAAK;AAEnC,QAAI,KAAK,kBAAkB,YAAY,KAAK,kBAAkB,QAAQ;AACpE,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,OAAO,KAAK;AAAA,UACnB,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,EAAE,OAAO,KAAK;AAAA,UACvD,UAAU;AAAA,UACV,WAAU;AAAA,UAEV;AAAA,gCAAC,YAAO,OAAM,IAAI,YAAE,gCAAgC,WAAW,GAAE;AAAA,YAChE,KAAK,SAAS,IAAI,CAAC,QAClB,oBAAC,YAAuB,OAAO,IAAI,OAAQ,cAAI,SAAlC,IAAI,KAAoC,CACtD;AAAA;AAAA;AAAA,MACH;AAAA,IAEJ;AAEA,QAAI,KAAK,kBAAkB,WAAW;AACpC,aACE,qBAAC,WAAM,WAAU,mCACf;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,QAAQ,KAAK;AAAA,YACtB,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,EAAE,OAAO,OAAO;AAAA,YACzD,UAAU;AAAA,YACV,WAAU;AAAA;AAAA,QACZ;AAAA,QACC,KAAK;AAAA,SACR;AAAA,IAEJ;AAEA,QAAI,KAAK,kBAAkB,UAAU;AACnC,aACE;AAAA,QAAC;AAAA;AAAA,UACC,OAAO,UAAU,MAAM,SAAS,OAAO,OAAO,OAAO,KAAK;AAAA,UAC1D,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,KAAK,EAAE;AAAA,UAChD,UAAU;AAAA;AAAA,MACZ;AAAA,IAEJ;AAEA,WACE;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,OAAO,OAAO,KAAK;AAAA,QACnB,UAAU,CAAC,MAAM,aAAa,KAAK,MAAM,EAAE,OAAO,KAAK;AAAA,QACvD,UAAU;AAAA,QACV,WAAU;AAAA;AAAA,IACZ;AAAA,EAEJ;AAEA,MAAI,SAAS;AACX,WAAO,oBAAC,SAAI,WAAU,iCAAiC,YAAE,gCAAgC,uBAAuB,GAAE;AAAA,EACpH;AAEA,MAAI,CAAC,YAAY;AACf,WACE,oBAAC,SAAI,WAAU,2EACZ,YAAE,gDAAgD,8DAA8D,GACnH;AAAA,EAEJ;AAEA,QAAM,cAAc,CAAC,OAAe,UAClC,qBAAC,SAAgB,WAAU,aACzB;AAAA,wBAAC,QAAG,WAAU,qCAAqC,iBAAM;AAAA,IACzD,oBAAC,SAAI,WAAU,aACZ,gBAAM,IAAI,CAAC,SACV,qBAAC,SACC;AAAA,2BAAC,WAAM,WAAU,kCACd;AAAA,aAAK;AAAA,QACL,KAAK,cAAc,oBAAC,UAAK,WAAU,2BAA0B,eAAC;AAAA,SACjE;AAAA,MACC,gBAAgB,IAAI;AAAA,MACpB,KAAK,YAAY,oBAAC,OAAE,WAAU,sCAAsC,eAAK,UAAS;AAAA,SAN3E,KAAK,IAOf,CACD,GACH;AAAA,OAbQ,KAcV;AAGF,SACE,qBAAC,SAAI,WAAU,aACZ;AAAA,qBAAiB,SAChB,gBAAgB,IAAI,CAAC,UAAU;AAC7B,YAAM,aAAa,WAAW,OAAO,CAAC,MAAM,MAAM,eAAe,SAAS,EAAE,IAAI,CAAC;AACjF,UAAI,WAAW,WAAW,EAAG,QAAO;AACpC,aAAO,YAAY,MAAM,OAAO,UAAU;AAAA,IAC5C,CAAC,IAED,YAAY,EAAE,2BAA2B,eAAe,GAAG,UAAU;AAAA,IAGvE,qBAAC,SAAI,WAAU,+BACZ;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,UAAU;AAAA,UACV,WAAU;AAAA,UAET,mBAAS,EAAE,qBAAqB,WAAW,IAAI,EAAE,mBAAmB,MAAM;AAAA;AAAA,MAC7E;AAAA,OACF;AAAA,KACF;AAEJ;",
6
6
  "names": []
7
7
  }