@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
@@ -2,25 +2,36 @@
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useRouter, useParams } from "next/navigation";
5
+ import { useT } from "@open-mercato/shared/lib/i18n/context";
5
6
  import { Alert } from "@open-mercato/ui/primitives/alert";
6
7
  import { Button } from "@open-mercato/ui/primitives/button";
7
8
  import { Tag } from "@open-mercato/ui/primitives/tag";
8
9
  import { FormHeader } from "@open-mercato/ui/backend/forms";
9
10
  import {
10
- formatStatusLabel,
11
11
  subscriptionStatusMap
12
12
  } from "../../../../../components/statusMaps.js";
13
13
  import { StatusPath } from "../../../../../components/StatusPath.js";
14
14
  import { DepthBadge, PriceWaterfall } from "../../../../../components/PriceWaterfall.js";
15
15
  import { parseStoredCharge } from "../../../../../lib/priceAdjustmentView.js";
16
- const CHANGE_TYPE_LABELS = {
17
- created: "Created",
18
- amend: "Amended",
19
- renew: "Renewed",
20
- cancel: "Cancelled",
21
- "merge-result": "Created from merging contracts",
22
- "merge-source": "Merged into a new contract"
23
- };
16
+ import { buildInventoryStatusLabels, inventoryStatusLabel } from "../../shared.js";
17
+ function changeTypeLabel(t, changeType) {
18
+ switch (changeType) {
19
+ case "created":
20
+ return t("cpq.inventory.subscriptions.detail.changeType.created", "Created");
21
+ case "amend":
22
+ return t("cpq.inventory.subscriptions.detail.changeType.amend", "Amended");
23
+ case "renew":
24
+ return t("cpq.inventory.subscriptions.detail.changeType.renew", "Renewed");
25
+ case "cancel":
26
+ return t("cpq.inventory.subscriptions.detail.changeType.cancel", "Cancelled");
27
+ case "merge-result":
28
+ return t("cpq.inventory.subscriptions.detail.changeType.mergeResult", "Created from merging contracts");
29
+ case "merge-source":
30
+ return t("cpq.inventory.subscriptions.detail.changeType.mergeSource", "Merged into a new contract");
31
+ default:
32
+ return changeType;
33
+ }
34
+ }
24
35
  const CHANGE_TYPE_VARIANTS = {
25
36
  created: "neutral",
26
37
  amend: "info",
@@ -36,20 +47,16 @@ const ALLOWED_TRANSITIONS = {
36
47
  terminated: [],
37
48
  expired: []
38
49
  };
39
- const STATUS_LABELS = {
40
- pending: "Pending",
41
- active: "Active",
42
- suspended: "Suspended",
43
- terminated: "Terminated",
44
- expired: "Expired"
45
- };
50
+ const SUBSCRIPTION_STATUSES = ["pending", "active", "suspended", "terminated", "expired"];
46
51
  function fmt(amount, currency) {
47
52
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(Number(amount));
48
53
  }
49
54
  function StatusBadge({ status }) {
50
- return /* @__PURE__ */ jsx(Tag, { variant: subscriptionStatusMap[status] ?? "neutral", dot: true, children: formatStatusLabel(status) });
55
+ const t = useT();
56
+ return /* @__PURE__ */ jsx(Tag, { variant: subscriptionStatusMap[status] ?? "neutral", dot: true, children: inventoryStatusLabel(t, status) });
51
57
  }
52
58
  function SubscriptionDetailPage(props) {
59
+ const t = useT();
53
60
  const router = useRouter();
54
61
  const urlParams = useParams();
55
62
  const subId = props.params?.id ?? urlParams?.id;
@@ -84,16 +91,16 @@ function SubscriptionDetailPage(props) {
84
91
  const body = await res.json().catch(() => ({}));
85
92
  if (body.existingQuoteId) {
86
93
  const ok = window.confirm(
87
- `${body.error ?? "Subscription has another ARC quote in progress."}
88
-
89
- Open the existing quote now?`
94
+ t("cpq.inventory.subscriptions.detail.arc.existingQuoteConfirm", "{error}\n\nOpen the existing quote now?", {
95
+ error: body.error ?? t("cpq.inventory.subscriptions.detail.arc.existingQuoteInProgress", "Subscription has another ARC quote in progress.")
96
+ })
90
97
  );
91
98
  if (ok) {
92
99
  router.push(`/backend/cpq/quotes/${body.existingQuoteId}`);
93
100
  }
94
101
  return;
95
102
  }
96
- alert(`Failed to start ${type} quote: ${body.error ?? res.statusText}`);
103
+ alert(t("cpq.inventory.subscriptions.detail.arc.startFailed", "Failed to start {type} quote: {error}", { type, error: body.error ?? res.statusText }));
97
104
  return;
98
105
  }
99
106
  const data = await res.json();
@@ -102,7 +109,7 @@ Open the existing quote now?`
102
109
  setArcInFlight(null);
103
110
  }
104
111
  },
105
- [router, sub]
112
+ [router, sub, t]
106
113
  );
107
114
  const transitionStatus = async (targetStatus) => {
108
115
  if (!sub) return;
@@ -115,7 +122,7 @@ Open the existing quote now?`
115
122
  body: JSON.stringify({ id: sub.id, targetStatus })
116
123
  });
117
124
  const data = await res.json();
118
- if (!res.ok) throw new Error(data?.error ?? "Status transition failed");
125
+ if (!res.ok) throw new Error(data?.error ?? t("cpq.inventory.subscriptions.detail.error.transitionFailed", "Status transition failed"));
119
126
  const [freshSub, freshAssets] = await Promise.all([
120
127
  fetch(`/api/cpq/inventory/subscriptions?id=${sub.id}`).then((r) => r.ok ? r.json() : null),
121
128
  fetch(`/api/cpq/inventory/assets?subscriptionId=${sub.id}&pageSize=50`).then((r) => r.ok ? r.json() : null).then((r) => r?.items ?? [])
@@ -175,7 +182,7 @@ Open the existing quote now?`
175
182
  async function load() {
176
183
  try {
177
184
  const res = await fetch(`/api/cpq/inventory/subscriptions?id=${subId}`);
178
- if (!res.ok) throw new Error("Subscription not found");
185
+ if (!res.ok) throw new Error(t("cpq.inventory.subscriptions.detail.error.notFound", "Subscription not found"));
179
186
  const data = await res.json();
180
187
  if (!cancelled) {
181
188
  setSub(data);
@@ -240,7 +247,7 @@ Open the existing quote now?`
240
247
  return () => {
241
248
  cancelled = true;
242
249
  };
243
- }, [subId]);
250
+ }, [subId, t]);
244
251
  const toggleItem = (id) => {
245
252
  setExpandedItems((prev) => {
246
253
  const n = new Set(prev);
@@ -251,31 +258,36 @@ Open the existing quote now?`
251
258
  if (loading) {
252
259
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-12 justify-center text-sm text-muted-foreground", children: [
253
260
  /* @__PURE__ */ jsx(Spinner, {}),
254
- " Loading\u2026"
261
+ " ",
262
+ t("cpq.inventory.subscriptions.detail.loading", "Loading\u2026")
255
263
  ] });
256
264
  }
257
265
  if (!sub) {
258
266
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
259
- /* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/inventory"), className: "text-sm text-muted-foreground hover:text-foreground", children: "\u2190 Back" }),
267
+ /* @__PURE__ */ jsxs("button", { onClick: () => router.push("/backend/cpq/inventory"), className: "text-sm text-muted-foreground hover:text-foreground", children: [
268
+ "\u2190 ",
269
+ t("cpq.common.back", "Back")
270
+ ] }),
260
271
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
261
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "Subscription not found." })
272
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.notFound", "Subscription not found.") })
262
273
  ] });
263
274
  }
264
275
  const currency = sub.currencyCode || "USD";
265
276
  const flatItems = flattenItems(sub.items);
266
277
  const canArc = sub.status === "active" || sub.status === "suspended";
267
278
  const statusTransitions = ALLOWED_TRANSITIONS[sub.status] ?? [];
279
+ const statusLabels = buildInventoryStatusLabels(t, SUBSCRIPTION_STATUSES);
268
280
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
269
281
  /* @__PURE__ */ jsx(
270
282
  FormHeader,
271
283
  {
272
284
  mode: "detail",
273
285
  backHref: "/backend/cpq/inventory",
274
- backLabel: "Back to Customer Inventory",
275
- entityTypeLabel: "Subscription",
286
+ backLabel: t("cpq.inventory.detail.backToInventory", "Back to Customer Inventory"),
287
+ entityTypeLabel: t("cpq.inventory.subscriptions.detail.entityType", "Subscription"),
276
288
  title: sub.name,
277
289
  statusBadge: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
278
- /* @__PURE__ */ jsx(Tag, { variant: subscriptionStatusMap[sub.status] ?? "neutral", dot: true, children: STATUS_LABELS[sub.status] ?? formatStatusLabel(sub.status) }),
290
+ /* @__PURE__ */ jsx(Tag, { variant: subscriptionStatusMap[sub.status] ?? "neutral", dot: true, children: inventoryStatusLabel(t, sub.status) }),
279
291
  /* @__PURE__ */ jsx(Tag, { variant: "neutral", className: "capitalize", children: sub.billingCycle }),
280
292
  transitioning && /* @__PURE__ */ jsx(Spinner, {})
281
293
  ] }),
@@ -287,7 +299,7 @@ Open the existing quote now?`
287
299
  variant: "outline",
288
300
  disabled: arcInFlight !== null,
289
301
  onClick: () => startArcQuote("amend"),
290
- children: "Amend"
302
+ children: t("cpq.inventory.subscriptions.detail.arc.amend", "Amend")
291
303
  }
292
304
  ),
293
305
  /* @__PURE__ */ jsx(
@@ -297,7 +309,7 @@ Open the existing quote now?`
297
309
  variant: "outline",
298
310
  disabled: arcInFlight !== null,
299
311
  onClick: () => startArcQuote("renew"),
300
- children: "Renew"
312
+ children: t("cpq.inventory.subscriptions.detail.arc.renew", "Renew")
301
313
  }
302
314
  ),
303
315
  /* @__PURE__ */ jsx(
@@ -307,7 +319,7 @@ Open the existing quote now?`
307
319
  variant: "destructive-outline",
308
320
  disabled: arcInFlight !== null,
309
321
  onClick: () => startArcQuote("cancel"),
310
- children: "Cancel"
322
+ children: t("cpq.inventory.subscriptions.detail.arc.cancel", "Cancel")
311
323
  }
312
324
  )
313
325
  ] }) : null
@@ -320,52 +332,52 @@ Open the existing quote now?`
320
332
  path: ["pending", "active"],
321
333
  terminals: ["suspended", "terminated", "expired"],
322
334
  statusMap: subscriptionStatusMap,
323
- labels: STATUS_LABELS,
335
+ labels: statusLabels,
324
336
  onTransition: transitionStatus,
325
337
  allowedTransitions: statusTransitions,
326
338
  disabled: transitioning,
327
- ariaLabel: "Subscription status path"
339
+ ariaLabel: t("cpq.inventory.subscriptions.detail.statusPath", "Subscription status path")
328
340
  }
329
341
  ) }),
330
342
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
331
343
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
332
344
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
333
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Subscription Details" }),
345
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.inventory.subscriptions.detail.sections.details", "Subscription Details") }),
334
346
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4 gap-y-2", children: [
335
- /* @__PURE__ */ jsx(Field, { label: "Code", value: sub.code, mono: true }),
336
- /* @__PURE__ */ jsx(Field, { label: "Status", value: sub.status }),
337
- /* @__PURE__ */ jsx(Field, { label: "Billing Cycle", value: sub.billingCycle }),
338
- /* @__PURE__ */ jsx(Field, { label: "Currency", value: currency }),
339
- /* @__PURE__ */ jsx(Field, { label: "Auto-Renew", value: sub.autoRenew ? "Yes" : "No" }),
340
- sub.termMonths != null && /* @__PURE__ */ jsx(Field, { label: "Term", value: `${sub.termMonths} months` }),
341
- sub.startDate && /* @__PURE__ */ jsx(Field, { label: "Start Date", value: new Date(sub.startDate).toLocaleDateString() }),
342
- sub.currentTermEnd && /* @__PURE__ */ jsx(Field, { label: "Term End", value: new Date(sub.currentTermEnd).toLocaleDateString() }),
343
- sub.activatedAt && /* @__PURE__ */ jsx(Field, { label: "Activated", value: new Date(sub.activatedAt).toLocaleString() }),
344
- sub.suspendedAt && /* @__PURE__ */ jsx(Field, { label: "Suspended", value: new Date(sub.suspendedAt).toLocaleString() }),
345
- sub.terminatedAt && /* @__PURE__ */ jsx(Field, { label: "Terminated", value: new Date(sub.terminatedAt).toLocaleString() })
347
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.code", "Code"), value: sub.code, mono: true }),
348
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.status", "Status"), value: sub.status }),
349
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.billingCycle", "Billing Cycle"), value: sub.billingCycle }),
350
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.currency", "Currency"), value: currency }),
351
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.autoRenew", "Auto-Renew"), value: sub.autoRenew ? t("cpq.common.yes", "Yes") : t("cpq.common.no", "No") }),
352
+ sub.termMonths != null && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.term", "Term"), value: t("cpq.inventory.subscriptions.detail.fields.termMonths", "{count} months", { count: sub.termMonths }) }),
353
+ sub.startDate && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.startDate", "Start Date"), value: new Date(sub.startDate).toLocaleDateString() }),
354
+ sub.currentTermEnd && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.termEnd", "Term End"), value: new Date(sub.currentTermEnd).toLocaleDateString() }),
355
+ sub.activatedAt && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.activated", "Activated"), value: new Date(sub.activatedAt).toLocaleString() }),
356
+ sub.suspendedAt && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.suspended", "Suspended"), value: new Date(sub.suspendedAt).toLocaleString() }),
357
+ sub.terminatedAt && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.terminated", "Terminated"), value: new Date(sub.terminatedAt).toLocaleString() })
346
358
  ] }),
347
359
  sub.description && /* @__PURE__ */ jsxs("div", { className: "pt-1", children: [
348
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Description" }),
360
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.fields.description", "Description") }),
349
361
  /* @__PURE__ */ jsx("p", { className: "text-sm", children: sub.description })
350
362
  ] })
351
363
  ] }),
352
364
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
353
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Pricing & References" }),
365
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.inventory.subscriptions.detail.sections.pricing", "Pricing & References") }),
354
366
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
355
367
  /* @__PURE__ */ jsxs("div", { children: [
356
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Monthly Recurring (MRC)" }),
368
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.fields.mrc", "Monthly Recurring (MRC)") }),
357
369
  /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(sub.mrcAmount, currency) })
358
370
  ] }),
359
371
  /* @__PURE__ */ jsxs("div", { children: [
360
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Non-Recurring (NRC)" }),
372
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.fields.nrc", "Non-Recurring (NRC)") }),
361
373
  /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(sub.nrcAmount, currency) })
362
374
  ] })
363
375
  ] }),
364
376
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4 gap-y-2 pt-1", children: [
365
- /* @__PURE__ */ jsx(Field, { label: "Customer", value: customerName ?? sub.customerId.slice(0, 12) + "\u2026" }),
366
- /* @__PURE__ */ jsx(Field, { label: "Created", value: new Date(sub.createdAt).toLocaleString() }),
377
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.customer", "Customer"), value: customerName ?? sub.customerId.slice(0, 12) + "\u2026" }),
378
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.created", "Created"), value: new Date(sub.createdAt).toLocaleString() }),
367
379
  sub.sourceQuoteId && /* @__PURE__ */ jsxs("div", { children: [
368
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Source Quote" }),
380
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.fields.sourceQuote", "Source Quote") }),
369
381
  /* @__PURE__ */ jsx(
370
382
  "button",
371
383
  {
@@ -376,7 +388,7 @@ Open the existing quote now?`
376
388
  )
377
389
  ] }),
378
390
  sub.sourceOrderId && /* @__PURE__ */ jsxs("div", { children: [
379
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Source Order" }),
391
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.fields.sourceOrder", "Source Order") }),
380
392
  /* @__PURE__ */ jsx(
381
393
  "button",
382
394
  {
@@ -390,7 +402,7 @@ Open the existing quote now?`
390
402
  ] })
391
403
  ] }),
392
404
  sub.metadata && Object.keys(sub.metadata).length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
393
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: "Metadata" }),
405
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: t("cpq.inventory.subscriptions.detail.sections.metadata", "Metadata") }),
394
406
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1", children: Object.entries(sub.metadata).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
395
407
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
396
408
  key,
@@ -400,12 +412,8 @@ Open the existing quote now?`
400
412
  ] }, key)) })
401
413
  ] }),
402
414
  /* @__PURE__ */ jsxs("div", { children: [
403
- /* @__PURE__ */ jsxs("h3", { className: "text-sm font-medium mb-2 px-1", children: [
404
- "Subscription Items (",
405
- flatItems.length,
406
- ")"
407
- ] }),
408
- flatItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: "No items." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: flatItems.map((item) => {
415
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: t("cpq.inventory.subscriptions.detail.items.title", "Subscription Items ({count})", { count: flatItems.length }) }),
416
+ flatItems.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.items.empty", "No items.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: flatItems.map((item) => {
409
417
  const isExpanded = expandedItems.has(item.id);
410
418
  const indent = item._depth ?? 0;
411
419
  return /* @__PURE__ */ jsxs("div", { children: [
@@ -427,11 +435,17 @@ Open the existing quote now?`
427
435
  ] }) }),
428
436
  /* @__PURE__ */ jsxs("div", { className: "shrink-0 text-right text-xs space-y-0.5", children: [
429
437
  Number(item.mrcAmount) > 0 && /* @__PURE__ */ jsxs("div", { children: [
430
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "MRC " }),
438
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
439
+ t("cpq.inventory.subscriptions.detail.items.mrc", "MRC"),
440
+ " "
441
+ ] }),
431
442
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(item.mrcAmount, item.currencyCode || currency) })
432
443
  ] }),
433
444
  Number(item.nrcAmount) > 0 && /* @__PURE__ */ jsxs("div", { children: [
434
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "NRC " }),
445
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
446
+ t("cpq.inventory.subscriptions.detail.items.nrc", "NRC"),
447
+ " "
448
+ ] }),
435
449
  /* @__PURE__ */ jsx("span", { className: "font-mono font-medium", children: fmt(item.nrcAmount, item.currencyCode || currency) })
436
450
  ] })
437
451
  ] })
@@ -443,7 +457,7 @@ Open the existing quote now?`
443
457
  item.offeringId && /* @__PURE__ */ jsx(
444
458
  RefField,
445
459
  {
446
- label: "Offering",
460
+ label: t("cpq.inventory.subscriptions.detail.fields.offering", "Offering"),
447
461
  name: offeringNames[item.offeringId],
448
462
  fallback: item.offeringId,
449
463
  onClick: () => router.push(`/backend/cpq/offerings/${item.offeringId}`)
@@ -452,7 +466,7 @@ Open the existing quote now?`
452
466
  item.specId && /* @__PURE__ */ jsx(
453
467
  RefField,
454
468
  {
455
- label: "Specification",
469
+ label: t("cpq.inventory.subscriptions.detail.fields.specification", "Specification"),
456
470
  name: specNames[item.specId],
457
471
  fallback: item.specId,
458
472
  onClick: () => router.push(`/backend/cpq/specifications/${item.specId}`)
@@ -461,18 +475,18 @@ Open the existing quote now?`
461
475
  item.productId && /* @__PURE__ */ jsx(
462
476
  RefField,
463
477
  {
464
- label: "Product",
478
+ label: t("cpq.inventory.subscriptions.detail.fields.product", "Product"),
465
479
  name: productNames[item.productId],
466
480
  fallback: item.productId,
467
481
  onClick: () => router.push(`/backend/catalog/products/${item.productId}`)
468
482
  }
469
483
  ),
470
- /* @__PURE__ */ jsx(Field, { label: "Currency", value: item.currencyCode || currency }),
471
- /* @__PURE__ */ jsx(Field, { label: "Quantity", value: String(item.quantity) }),
472
- item.capacityTotal && /* @__PURE__ */ jsx(Field, { label: "Capacity", value: `${item.capacityUsed ?? "0"} / ${item.capacityTotal} ${item.capacityUnit ?? ""}` })
484
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.currency", "Currency"), value: item.currencyCode || currency }),
485
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.quantity", "Quantity"), value: String(item.quantity) }),
486
+ item.capacityTotal && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.subscriptions.detail.fields.capacity", "Capacity"), value: `${item.capacityUsed ?? "0"} / ${item.capacityTotal} ${item.capacityUnit ?? ""}` })
473
487
  ] }),
474
488
  item.configuration && Object.keys(item.configuration).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
475
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Configuration" }),
489
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: t("cpq.inventory.subscriptions.detail.items.configuration", "Configuration") }),
476
490
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1", children: Object.entries(item.configuration).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
477
491
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
478
492
  key,
@@ -482,15 +496,15 @@ Open the existing quote now?`
482
496
  ] }, key)) })
483
497
  ] }),
484
498
  item.charges && item.charges.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
485
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Charges" }),
499
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: t("cpq.inventory.subscriptions.detail.items.charges", "Charges") }),
486
500
  /* @__PURE__ */ jsxs("table", { className: "w-full text-xs", children: [
487
501
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "text-muted-foreground", children: [
488
- /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Charge" }),
489
- /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: "Type" }),
490
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "List" }),
491
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Offer" }),
492
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Qty" }),
493
- /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: "Total" })
502
+ /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: t("cpq.inventory.subscriptions.detail.charges.charge", "Charge") }),
503
+ /* @__PURE__ */ jsx("th", { className: "text-left py-1 font-medium", children: t("cpq.inventory.subscriptions.detail.charges.type", "Type") }),
504
+ /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.inventory.subscriptions.detail.charges.list", "List") }),
505
+ /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.inventory.subscriptions.detail.charges.offer", "Offer") }),
506
+ /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.inventory.subscriptions.detail.charges.qty", "Qty") }),
507
+ /* @__PURE__ */ jsx("th", { className: "text-right py-1 font-medium", children: t("cpq.inventory.subscriptions.detail.charges.total", "Total") })
494
508
  ] }) }),
495
509
  /* @__PURE__ */ jsx("tbody", { children: item.charges.map((raw, i) => {
496
510
  const c = parseStoredCharge(raw);
@@ -513,7 +527,7 @@ Open the existing quote now?`
513
527
  ] })
514
528
  ] }),
515
529
  item.metadata && Object.keys(item.metadata).length > 0 && /* @__PURE__ */ jsxs("div", { children: [
516
- /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: "Metadata" }),
530
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-muted-foreground mb-1", children: t("cpq.inventory.subscriptions.detail.items.metadata", "Metadata") }),
517
531
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1", children: Object.entries(item.metadata).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
518
532
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
519
533
  key,
@@ -523,107 +537,68 @@ Open the existing quote now?`
523
537
  ] }, key)) })
524
538
  ] }),
525
539
  /* @__PURE__ */ jsxs("div", { className: "flex gap-4 text-xs text-muted-foreground", children: [
526
- /* @__PURE__ */ jsxs("span", { children: [
527
- "Created: ",
528
- new Date(item.createdAt).toLocaleString()
529
- ] }),
530
- /* @__PURE__ */ jsxs("span", { children: [
531
- "Updated: ",
532
- new Date(item.updatedAt).toLocaleString()
533
- ] })
540
+ /* @__PURE__ */ jsx("span", { children: t("cpq.inventory.subscriptions.detail.items.createdAt", "Created: {date}", { date: new Date(item.createdAt).toLocaleString() }) }),
541
+ /* @__PURE__ */ jsx("span", { children: t("cpq.inventory.subscriptions.detail.items.updatedAt", "Updated: {date}", { date: new Date(item.updatedAt).toLocaleString() }) })
534
542
  ] })
535
543
  ] })
536
544
  ] }, item.id);
537
545
  }) })
538
546
  ] }),
539
547
  /* @__PURE__ */ jsxs("div", { children: [
540
- /* @__PURE__ */ jsxs("h3", { className: "text-sm font-medium mb-2 px-1", children: [
541
- "Change History (",
542
- changeLog.length,
543
- ")"
544
- ] }),
545
- changeLogLoading ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-6 text-center text-sm text-muted-foreground", children: "Loading\u2026" }) : changeLog.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-6 text-center text-sm text-muted-foreground", children: "No ARC changes yet." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: changeLog.map((row) => /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 space-y-1", children: [
548
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: t("cpq.inventory.subscriptions.detail.changeLog.title", "Change History ({count})", { count: changeLog.length }) }),
549
+ changeLogLoading ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-6 text-center text-sm text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.loading", "Loading\u2026") }) : changeLog.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-6 text-center text-sm text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.empty", "No ARC changes yet.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card divide-y", children: changeLog.map((row) => /* @__PURE__ */ jsxs("div", { className: "px-4 py-3 space-y-1", children: [
546
550
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
547
- /* @__PURE__ */ jsx(Tag, { variant: CHANGE_TYPE_VARIANTS[row.changeType] ?? "neutral", children: CHANGE_TYPE_LABELS[row.changeType] ?? row.changeType }),
551
+ /* @__PURE__ */ jsx(Tag, { variant: CHANGE_TYPE_VARIANTS[row.changeType] ?? "neutral", children: changeTypeLabel(t, row.changeType) }),
548
552
  /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: new Date(row.effectiveAt).toLocaleString() }),
549
- row.sourceQuoteId && /* @__PURE__ */ jsxs(
553
+ row.sourceQuoteId && /* @__PURE__ */ jsx(
550
554
  "button",
551
555
  {
552
556
  onClick: () => router.push(`/backend/cpq/quotes/${row.sourceQuoteId}`),
553
557
  className: "text-xs text-primary hover:underline",
554
- children: [
555
- "Quote ",
556
- logQuoteNumbers[row.sourceQuoteId] ?? `${row.sourceQuoteId.slice(0, 8)}\u2026`
557
- ]
558
+ children: t("cpq.inventory.subscriptions.detail.changeLog.quote", "Quote {number}", { number: logQuoteNumbers[row.sourceQuoteId] ?? `${row.sourceQuoteId.slice(0, 8)}\u2026` })
558
559
  }
559
560
  ),
560
- row.sourceOrderId && /* @__PURE__ */ jsxs(
561
+ row.sourceOrderId && /* @__PURE__ */ jsx(
561
562
  "button",
562
563
  {
563
564
  onClick: () => router.push(`/backend/cpq/orders/${row.sourceOrderId}`),
564
565
  className: "text-xs text-primary hover:underline",
565
- children: [
566
- "Order ",
567
- logOrderNumbers[row.sourceOrderId] ?? `${row.sourceOrderId.slice(0, 8)}\u2026`
568
- ]
566
+ children: t("cpq.inventory.subscriptions.detail.changeLog.order", "Order {number}", { number: logOrderNumbers[row.sourceOrderId] ?? `${row.sourceOrderId.slice(0, 8)}\u2026` })
569
567
  }
570
568
  )
571
569
  ] }),
572
570
  /* @__PURE__ */ jsx(ChangeLogLineDetails, { row, currency }),
573
- row.termChange && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
574
- "Term: ",
575
- String(row.termChange.oldTermEnd ?? "\u2014"),
576
- " \u2192 ",
577
- String(row.termChange.newTermEnd ?? "\u2014")
578
- ] }),
579
- row.reasonCode && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
580
- "Reason: ",
581
- row.reasonCode,
582
- row.reasonText ? ` \u2014 ${row.reasonText}` : ""
583
- ] }),
584
- row.etfAmount && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
585
- "ETF: ",
586
- row.etfAmount,
587
- " ",
588
- row.etfCurrency ?? ""
589
- ] }),
571
+ row.termChange && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.term", "Term: {from} \u2192 {to}", {
572
+ from: String(row.termChange.oldTermEnd ?? "\u2014"),
573
+ to: String(row.termChange.newTermEnd ?? "\u2014")
574
+ }) }),
575
+ row.reasonCode && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: row.reasonText ? t("cpq.inventory.subscriptions.detail.changeLog.reasonWithText", "Reason: {code} \u2014 {text}", { code: row.reasonCode, text: row.reasonText }) : t("cpq.inventory.subscriptions.detail.changeLog.reason", "Reason: {code}", { code: row.reasonCode }) }),
576
+ row.etfAmount && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.etf", "ETF: {amount} {currency}", { amount: row.etfAmount, currency: row.etfCurrency ?? "" }) }),
590
577
  row.mergedIntoSubscriptionId && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
591
- "Merged into",
578
+ t("cpq.inventory.subscriptions.detail.changeLog.mergedInto", "Merged into"),
592
579
  " ",
593
- /* @__PURE__ */ jsxs(
580
+ /* @__PURE__ */ jsx(
594
581
  "button",
595
582
  {
596
583
  onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${row.mergedIntoSubscriptionId}`),
597
584
  className: "text-primary hover:underline",
598
- children: [
599
- "subscription ",
600
- row.mergedIntoSubscriptionId.slice(0, 8),
601
- "\u2026"
602
- ]
585
+ children: t("cpq.inventory.subscriptions.detail.changeLog.mergedIntoSubscription", "subscription {id}\u2026", { id: row.mergedIntoSubscriptionId.slice(0, 8) })
603
586
  }
604
587
  )
605
588
  ] }),
606
- row.mergedFromSubscriptionIds && row.mergedFromSubscriptionIds.length > 0 && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground", children: [
607
- "Merged from ",
608
- row.mergedFromSubscriptionIds.length,
609
- " subscriptions"
610
- ] })
589
+ row.mergedFromSubscriptionIds && row.mergedFromSubscriptionIds.length > 0 && /* @__PURE__ */ jsx("div", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.mergedFrom", "Merged from {count} subscriptions", { count: row.mergedFromSubscriptionIds.length }) })
611
590
  ] }, row.id)) })
612
591
  ] }),
613
592
  /* @__PURE__ */ jsxs("div", { children: [
614
- /* @__PURE__ */ jsxs("h3", { className: "text-sm font-medium mb-2 px-1", children: [
615
- "Linked Assets (",
616
- assets.length,
617
- ")"
618
- ] }),
619
- assets.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: "No assets linked to this subscription." }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
593
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2 px-1", children: t("cpq.inventory.subscriptions.detail.assets.title", "Linked Assets ({count})", { count: assets.length }) }),
594
+ assets.length === 0 ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card py-8 text-center text-sm text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.assets.empty", "No assets linked to this subscription.") }) : /* @__PURE__ */ jsx("div", { className: "rounded-lg border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
620
595
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50", children: [
621
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Code" }),
622
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Name" }),
623
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Type" }),
624
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: "Status" }),
625
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: "Price" }),
626
- /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: "Qty" })
596
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.inventory.subscriptions.detail.assets.table.code", "Code") }),
597
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.inventory.subscriptions.detail.assets.table.name", "Name") }),
598
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.inventory.subscriptions.detail.assets.table.type", "Type") }),
599
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-left font-medium", children: t("cpq.inventory.subscriptions.detail.assets.table.status", "Status") }),
600
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: t("cpq.inventory.subscriptions.detail.assets.table.price", "Price") }),
601
+ /* @__PURE__ */ jsx("th", { className: "px-4 py-3 text-right font-medium", children: t("cpq.inventory.subscriptions.detail.assets.table.qty", "Qty") })
627
602
  ] }) }),
628
603
  /* @__PURE__ */ jsx("tbody", { children: assets.map((a) => /* @__PURE__ */ jsxs("tr", { onClick: () => router.push(`/backend/cpq/inventory/assets/${a.id}`), className: "border-b cursor-pointer hover:bg-muted/30 transition-colors", children: [
629
604
  /* @__PURE__ */ jsx("td", { className: "px-4 py-3 text-muted-foreground", children: a.code }),
@@ -683,6 +658,8 @@ function ErrorBanner({ message }) {
683
658
  return /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: message });
684
659
  }
685
660
  function ChangeLogLineDetails({ row, currency }) {
661
+ const t = useT();
662
+ const itemFallback = t("cpq.inventory.subscriptions.detail.changeLog.item", "Item");
686
663
  const beforeItems = new Map(
687
664
  (row.beforeSnapshot?.items ?? []).map((it) => [it.id, it])
688
665
  );
@@ -704,7 +681,7 @@ function ChangeLogLineDetails({ row, currency }) {
704
681
  derived.push({
705
682
  key: `${itemId}-${i}`,
706
683
  action: lc.action,
707
- name: item?.name ?? (lc.action === "add" ? "New item" : "Item"),
684
+ name: item?.name ?? (lc.action === "add" ? t("cpq.inventory.subscriptions.detail.changeLog.newItem", "New item") : itemFallback),
708
685
  beforeMrc,
709
686
  afterMrc: lc.action === "add" && afterMrc == null && lc.mrcAmount != null ? Number(lc.mrcAmount) : afterMrc,
710
687
  delta: explicitDelta ?? computedDelta
@@ -716,7 +693,7 @@ function ChangeLogLineDetails({ row, currency }) {
716
693
  derived.push({
717
694
  key: it.id,
718
695
  action: "cancel",
719
- name: it.name ?? "Item",
696
+ name: it.name ?? itemFallback,
720
697
  beforeMrc: mrc,
721
698
  afterMrc: 0,
722
699
  delta: -mrc
@@ -728,7 +705,7 @@ function ChangeLogLineDetails({ row, currency }) {
728
705
  derived.push({
729
706
  key: it.id,
730
707
  action: "add",
731
- name: it.name ?? "Item",
708
+ name: it.name ?? itemFallback,
732
709
  beforeMrc: null,
733
710
  afterMrc: mrc,
734
711
  delta: null
@@ -740,7 +717,7 @@ function ChangeLogLineDetails({ row, currency }) {
740
717
  derived.push({
741
718
  key: it.id,
742
719
  action: "cancel",
743
- name: it.name ?? "Item",
720
+ name: it.name ?? itemFallback,
744
721
  beforeMrc: mrc,
745
722
  afterMrc: 0,
746
723
  delta: -mrc
@@ -749,57 +726,56 @@ function ChangeLogLineDetails({ row, currency }) {
749
726
  }
750
727
  if (derived.length === 0) {
751
728
  if (row.changeType === "amend" || row.changeType === "renew") {
752
- return /* @__PURE__ */ jsx("div", { className: "mt-1.5 text-xs text-muted-foreground italic", children: "No item-level changes recorded." });
729
+ return /* @__PURE__ */ jsx("div", { className: "mt-1.5 text-xs text-muted-foreground italic", children: t("cpq.inventory.subscriptions.detail.changeLog.noItemChanges", "No item-level changes recorded.") });
753
730
  }
754
731
  return null;
755
732
  }
756
733
  return /* @__PURE__ */ jsx("div", { className: "mt-1.5 rounded border bg-muted/20 px-3 py-2 space-y-1", children: derived.map((d) => /* @__PURE__ */ jsx(ChangeLogLineRow, { change: d, currency }, d.key)) });
757
734
  }
758
735
  const ACTION_GLYPH = {
759
- add: { sign: "+", color: "text-status-success-text", label: "added" },
760
- modify: { sign: "~", color: "text-status-info-text", label: "modified" },
761
- cancel: { sign: "\u2212", color: "text-destructive", label: "removed" }
736
+ add: { sign: "+", color: "text-status-success-text" },
737
+ modify: { sign: "~", color: "text-status-info-text" },
738
+ cancel: { sign: "\u2212", color: "text-destructive" }
762
739
  };
740
+ function actionGlyphLabel(t, action) {
741
+ switch (action) {
742
+ case "add":
743
+ return t("cpq.inventory.subscriptions.detail.changeLog.action.add", "added");
744
+ case "modify":
745
+ return t("cpq.inventory.subscriptions.detail.changeLog.action.modify", "modified");
746
+ case "cancel":
747
+ return t("cpq.inventory.subscriptions.detail.changeLog.action.cancel", "removed");
748
+ }
749
+ }
763
750
  function ChangeLogLineRow({
764
751
  change,
765
752
  currency
766
753
  }) {
754
+ const t = useT();
767
755
  const glyph = ACTION_GLYPH[change.action];
768
756
  const showPriceChange = change.action === "modify" && change.beforeMrc != null && change.afterMrc != null && change.beforeMrc !== change.afterMrc;
769
757
  const showSinglePrice = !showPriceChange && (change.action === "add" && change.afterMrc != null && change.afterMrc > 0 || change.action === "cancel" && change.beforeMrc != null && change.beforeMrc > 0);
770
758
  const showDelta = change.delta != null && change.delta !== 0;
771
759
  const isNoopModify = change.action === "modify" && !showPriceChange && !showDelta;
772
760
  return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-2 gap-y-0.5 text-xs", children: [
773
- /* @__PURE__ */ jsx("span", { className: `font-bold w-3 text-center ${glyph.color}`, title: glyph.label, children: glyph.sign }),
761
+ /* @__PURE__ */ jsx("span", { className: `font-bold w-3 text-center ${glyph.color}`, title: actionGlyphLabel(t, change.action), children: glyph.sign }),
774
762
  /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: change.name }),
775
- showPriceChange && /* @__PURE__ */ jsxs("span", { className: "font-mono text-muted-foreground", children: [
776
- fmt(change.beforeMrc, currency),
777
- " \u2192 ",
778
- fmt(change.afterMrc, currency),
779
- "/mo"
780
- ] }),
781
- showSinglePrice && change.action === "add" && /* @__PURE__ */ jsxs("span", { className: "font-mono text-muted-foreground", children: [
782
- fmt(change.afterMrc, currency),
783
- "/mo"
784
- ] }),
785
- showSinglePrice && change.action === "cancel" && /* @__PURE__ */ jsxs("span", { className: "font-mono text-muted-foreground", children: [
786
- "was ",
787
- fmt(change.beforeMrc, currency),
788
- "/mo"
789
- ] }),
790
- showDelta && /* @__PURE__ */ jsxs(
763
+ showPriceChange && /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.priceChangePerMonth", "{before} \u2192 {after}/mo", {
764
+ before: fmt(change.beforeMrc, currency),
765
+ after: fmt(change.afterMrc, currency)
766
+ }) }),
767
+ showSinglePrice && change.action === "add" && /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.pricePerMonth", "{price}/mo", { price: fmt(change.afterMrc, currency) }) }),
768
+ showSinglePrice && change.action === "cancel" && /* @__PURE__ */ jsx("span", { className: "font-mono text-muted-foreground", children: t("cpq.inventory.subscriptions.detail.changeLog.wasPricePerMonth", "was {price}/mo", { price: fmt(change.beforeMrc, currency) }) }),
769
+ showDelta && /* @__PURE__ */ jsx(
791
770
  "span",
792
771
  {
793
772
  className: `font-mono font-medium ${change.delta > 0 ? "text-status-success-text" : "text-destructive"}`,
794
- children: [
795
- "(",
796
- change.delta > 0 ? "+" : "",
797
- fmt(change.delta, currency),
798
- "/mo)"
799
- ]
773
+ children: t("cpq.inventory.subscriptions.detail.changeLog.deltaPerMonth", "({delta}/mo)", {
774
+ delta: `${change.delta > 0 ? "+" : ""}${fmt(change.delta, currency)}`
775
+ })
800
776
  }
801
777
  ),
802
- isNoopModify && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground italic", children: "no price change" })
778
+ isNoopModify && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground italic", children: t("cpq.inventory.subscriptions.detail.changeLog.noPriceChange", "no price change") })
803
779
  ] });
804
780
  }
805
781
  export {