@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @dainamite/cpq
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - d86651d: CPQ UI in English, Spanish, German and Polish.
8
+
9
+ - **Dictionaries.** New `i18n/{en,es,de,pl}.json` (1218 keys). CPQ previously shipped no dictionaries, so every screen rendered English in every locale.
10
+ - **Every UI string is translatable.** Hard-coded labels in quotes, orders, the quote wizard, specifications, offerings, inventory, price rules, pricing tables, wizard definitions, shared components and the injected widgets now go through `t()`. English text is unchanged.
11
+ - **Own generic labels.** `cpq.common.*` replaces borrowed `common.*` keys that only some unrelated modules shipped (`common.inactive` existed nowhere).
12
+ - **Sales tabs translated.** The quote / order tabs on sales documents use i18n keys as `groupLabel`. The customer "Inventory" tab stays English (legacy customer pages render `groupLabel` verbatim).
13
+ - **Row-action messages are noun-free** (`Delete selected items ({count})?`), because a spliced entity noun can't be grammatical in ES / DE / PL. `useCpqRowActions({ entityName })` is now optional and deprecated.
14
+ - **API change for direct importers:** `parsePricingTableXlsx` / `commitImportDiff` take a required `t` argument, and `StatusPath`'s `ariaLabel` is optional.
15
+ - **Guardrails:** tests fail on a missing or orphaned key, a dropped `{placeholder}`, or a new hard-coded UI string.
16
+
17
+ No migration. After upgrading run `yarn generate` so the app picks up the dictionaries.
18
+
3
19
  ## 0.6.0
4
20
 
5
21
  ### Minor Changes
@@ -7,15 +7,9 @@ import { DataTable } from "@open-mercato/ui/backend/DataTable";
7
7
  import { RowActions } from "@open-mercato/ui/backend/RowActions";
8
8
  import { useCpqListData } from "../../../components/CpqListView.js";
9
9
  import { useCpqRowActions } from "../../../components/useCpqRowActions.js";
10
- import { StatusBadge, formatCurrency } from "./shared.js";
10
+ import { StatusBadge, buildInventoryStatusOptions, formatCurrency } from "./shared.js";
11
11
  const PAGE_SIZE = 50;
12
- const ASSET_STATUS_OPTIONS = [
13
- { value: "pending", label: "Pending" },
14
- { value: "delivered", label: "Delivered" },
15
- { value: "active", label: "Active" },
16
- { value: "returned", label: "Returned" },
17
- { value: "cancelled", label: "Cancelled" }
18
- ];
12
+ const ASSET_STATUSES = ["pending", "delivered", "active", "returned", "cancelled"];
19
13
  function buildFilterParams(values, params) {
20
14
  if (typeof values.status === "string" && values.status) {
21
15
  params.set("status", values.status);
@@ -46,7 +40,7 @@ function AssetsTable() {
46
40
  id: "status",
47
41
  label: t("cpq.inventory.filters.status", "Status"),
48
42
  type: "select",
49
- options: [...ASSET_STATUS_OPTIONS]
43
+ options: buildInventoryStatusOptions(t, ASSET_STATUSES)
50
44
  },
51
45
  {
52
46
  id: "assetType",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useCpqListData } from '../../../components/CpqListView'\nimport { useCpqRowActions } from '../../../components/useCpqRowActions'\nimport { StatusBadge, formatCurrency } from './shared'\n\ntype Asset = {\n id: string\n code: string\n name: string\n status: string\n assetType: string\n purchasePrice: number\n currencyCode: string\n createdAt: string\n}\n\nconst PAGE_SIZE = 50\n\nconst ASSET_STATUS_OPTIONS = [\n { value: 'pending', label: 'Pending' },\n { value: 'delivered', label: 'Delivered' },\n { value: 'active', label: 'Active' },\n { value: 'returned', label: 'Returned' },\n { value: 'cancelled', label: 'Cancelled' },\n] as const\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.status === 'string' && values.status) {\n params.set('status', values.status)\n }\n if (typeof values.assetType === 'string' && values.assetType.trim()) {\n params.set('assetType', values.assetType.trim())\n }\n}\n\nexport function AssetsTable() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Asset>({\n endpoint: '/api/cpq/inventory/assets',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.inventory.assets.error.load', 'Failed to load assets'),\n })\n\n // Assets follow a `delivered \u2192 active \u2192 returned` lifecycle; deletion only\n // makes sense before the asset is provisioned, so gate by status.\n const rowActionsApi = useCpqRowActions<Asset>({\n endpoint: '/api/cpq/inventory/assets',\n entityName: t('cpq.inventory.assets.entityName', 'asset'),\n editHref: (row) => `/backend/cpq/inventory/assets/${row.id}`,\n onReload: data.reload,\n canDelete: (row) => row.status === 'pending' || row.status === 'cancelled',\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('cpq.inventory.filters.status', 'Status'),\n type: 'select',\n options: [...ASSET_STATUS_OPTIONS],\n },\n {\n id: 'assetType',\n label: t('cpq.inventory.filters.assetType', 'Asset Type'),\n type: 'text',\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<Asset>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.inventory.assets.table.code', 'Code'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.inventory.assets.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium text-primary\">{row.original.name}</span>,\n },\n {\n accessorKey: 'assetType',\n header: t('cpq.inventory.assets.table.type', 'Type'),\n },\n {\n accessorKey: 'status',\n header: t('cpq.inventory.assets.table.status', 'Status'),\n cell: ({ row }) => <StatusBadge status={row.original.status} />,\n },\n {\n accessorKey: 'purchasePrice',\n header: t('cpq.inventory.assets.table.price', 'Price'),\n cell: ({ row }) => (\n <span className=\"font-mono\">\n {formatCurrency(row.original.purchasePrice, row.original.currencyCode || 'USD')}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('cpq.inventory.assets.table.created', 'Created'),\n cell: ({ row }) => (\n <span>\n {row.original.createdAt ? new Date(row.original.createdAt).toLocaleDateString() : '\u2014'}\n </span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <>\n {rowActionsApi.ConfirmDialogElement}\n <DataTable<Asset>\n title={t('cpq.inventory.assets', 'Assets')}\n refreshButton={{\n label: t('cpq.inventory.actions.refresh', 'Refresh'),\n onRefresh: data.reload,\n isRefreshing: data.isLoading,\n }}\n columns={columns}\n data={data.rows}\n searchValue={data.search}\n onSearchChange={data.setSearch}\n searchPlaceholder={t('cpq.inventory.assets.search.placeholder', 'Search assets...')}\n filters={filters}\n filterValues={data.filterValues}\n onFiltersApply={data.setFilterValues}\n onFiltersClear={data.clearFilters}\n sorting={data.sorting}\n onSortingChange={data.setSorting}\n onRowClick={(row) => router.push(`/backend/cpq/inventory/assets/${row.id}`)}\n rowActions={(row) => <RowActions items={rowActionsApi.buildItems(row)} />}\n perspective={{ tableId: 'cpq.inventory.assets.list' }}\n columnChooser={{ auto: true }}\n pagination={{\n page: data.page,\n pageSize: PAGE_SIZE,\n total: data.total,\n totalPages: data.totalPages,\n onPageChange: data.setPage,\n durationMs: data.durationMs,\n }}\n isLoading={data.isLoading}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.inventory.assets.empty', 'No assets found.')}\n </div>\n }\n />\n </>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { useCpqListData } from '../../../components/CpqListView'\nimport { useCpqRowActions } from '../../../components/useCpqRowActions'\nimport { StatusBadge, buildInventoryStatusOptions, formatCurrency } from './shared'\n\ntype Asset = {\n id: string\n code: string\n name: string\n status: string\n assetType: string\n purchasePrice: number\n currencyCode: string\n createdAt: string\n}\n\nconst PAGE_SIZE = 50\n\nconst ASSET_STATUSES = ['pending', 'delivered', 'active', 'returned', 'cancelled'] as const\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.status === 'string' && values.status) {\n params.set('status', values.status)\n }\n if (typeof values.assetType === 'string' && values.assetType.trim()) {\n params.set('assetType', values.assetType.trim())\n }\n}\n\nexport function AssetsTable() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Asset>({\n endpoint: '/api/cpq/inventory/assets',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.inventory.assets.error.load', 'Failed to load assets'),\n })\n\n // Assets follow a `delivered \u2192 active \u2192 returned` lifecycle; deletion only\n // makes sense before the asset is provisioned, so gate by status.\n const rowActionsApi = useCpqRowActions<Asset>({\n endpoint: '/api/cpq/inventory/assets',\n entityName: t('cpq.inventory.assets.entityName', 'asset'),\n editHref: (row) => `/backend/cpq/inventory/assets/${row.id}`,\n onReload: data.reload,\n canDelete: (row) => row.status === 'pending' || row.status === 'cancelled',\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('cpq.inventory.filters.status', 'Status'),\n type: 'select',\n options: buildInventoryStatusOptions(t, ASSET_STATUSES),\n },\n {\n id: 'assetType',\n label: t('cpq.inventory.filters.assetType', 'Asset Type'),\n type: 'text',\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<Asset>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.inventory.assets.table.code', 'Code'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.inventory.assets.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium text-primary\">{row.original.name}</span>,\n },\n {\n accessorKey: 'assetType',\n header: t('cpq.inventory.assets.table.type', 'Type'),\n },\n {\n accessorKey: 'status',\n header: t('cpq.inventory.assets.table.status', 'Status'),\n cell: ({ row }) => <StatusBadge status={row.original.status} />,\n },\n {\n accessorKey: 'purchasePrice',\n header: t('cpq.inventory.assets.table.price', 'Price'),\n cell: ({ row }) => (\n <span className=\"font-mono\">\n {formatCurrency(row.original.purchasePrice, row.original.currencyCode || 'USD')}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('cpq.inventory.assets.table.created', 'Created'),\n cell: ({ row }) => (\n <span>\n {row.original.createdAt ? new Date(row.original.createdAt).toLocaleDateString() : '\u2014'}\n </span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <>\n {rowActionsApi.ConfirmDialogElement}\n <DataTable<Asset>\n title={t('cpq.inventory.assets', 'Assets')}\n refreshButton={{\n label: t('cpq.inventory.actions.refresh', 'Refresh'),\n onRefresh: data.reload,\n isRefreshing: data.isLoading,\n }}\n columns={columns}\n data={data.rows}\n searchValue={data.search}\n onSearchChange={data.setSearch}\n searchPlaceholder={t('cpq.inventory.assets.search.placeholder', 'Search assets...')}\n filters={filters}\n filterValues={data.filterValues}\n onFiltersApply={data.setFilterValues}\n onFiltersClear={data.clearFilters}\n sorting={data.sorting}\n onSortingChange={data.setSorting}\n onRowClick={(row) => router.push(`/backend/cpq/inventory/assets/${row.id}`)}\n rowActions={(row) => <RowActions items={rowActionsApi.buildItems(row)} />}\n perspective={{ tableId: 'cpq.inventory.assets.list' }}\n columnChooser={{ auto: true }}\n pagination={{\n page: data.page,\n pageSize: PAGE_SIZE,\n total: data.total,\n totalPages: data.totalPages,\n onPageChange: data.setPage,\n durationMs: data.durationMs,\n }}\n isLoading={data.isLoading}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.inventory.assets.empty', 'No assets found.')}\n </div>\n }\n />\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -14,24 +14,19 @@ import {
14
14
  } from "@open-mercato/ui/primitives/select";
15
15
  import { useCpqListData } from "../../../components/CpqListView.js";
16
16
  import { useCpqRowActions } from "../../../components/useCpqRowActions.js";
17
- import { StatusBadge, formatCurrency } from "./shared.js";
18
- const EXPIRING_WINDOWS = [
19
- { value: "all", label: "All subscriptions" },
20
- { value: "7", label: "Expiring in 7 days" },
21
- { value: "14", label: "Expiring in 14 days" },
22
- { value: "30", label: "Expiring in 30 days" },
23
- { value: "60", label: "Expiring in 60 days" },
24
- { value: "90", label: "Expiring in 90 days" }
25
- ];
17
+ import { StatusBadge, buildInventoryStatusOptions, formatCurrency } from "./shared.js";
18
+ function buildExpiringWindows(t) {
19
+ return [
20
+ { value: "all", label: t("cpq.inventory.subscriptions.expiring.all", "All subscriptions") },
21
+ { value: "7", label: t("cpq.inventory.subscriptions.expiring.in7Days", "Expiring in 7 days") },
22
+ { value: "14", label: t("cpq.inventory.subscriptions.expiring.in14Days", "Expiring in 14 days") },
23
+ { value: "30", label: t("cpq.inventory.subscriptions.expiring.in30Days", "Expiring in 30 days") },
24
+ { value: "60", label: t("cpq.inventory.subscriptions.expiring.in60Days", "Expiring in 60 days") },
25
+ { value: "90", label: t("cpq.inventory.subscriptions.expiring.in90Days", "Expiring in 90 days") }
26
+ ];
27
+ }
26
28
  const PAGE_SIZE = 50;
27
- const SUBSCRIPTION_STATUS_OPTIONS = [
28
- { value: "pending", label: "Pending" },
29
- { value: "active", label: "Active" },
30
- { value: "suspended", label: "Suspended" },
31
- { value: "terminated", label: "Terminated" },
32
- { value: "expired", label: "Expired" },
33
- { value: "cancelled", label: "Cancelled" }
34
- ];
29
+ const SUBSCRIPTION_STATUSES = ["pending", "active", "suspended", "terminated", "expired", "cancelled"];
35
30
  function buildFilterParams(values, params) {
36
31
  if (typeof values.status === "string" && values.status) {
37
32
  params.set("status", values.status);
@@ -65,7 +60,7 @@ function SubscriptionsTable() {
65
60
  id: "status",
66
61
  label: t("cpq.inventory.filters.status", "Status"),
67
62
  type: "select",
68
- options: [...SUBSCRIPTION_STATUS_OPTIONS]
63
+ options: buildInventoryStatusOptions(t, SUBSCRIPTION_STATUSES)
69
64
  },
70
65
  {
71
66
  id: "billingCycle",
@@ -78,11 +73,11 @@ function SubscriptionsTable() {
78
73
  type: "select",
79
74
  // Pick from common renewal-watch windows. Backend caps at 365.
80
75
  options: [
81
- { value: "7", label: "7 days" },
82
- { value: "14", label: "14 days" },
83
- { value: "30", label: "30 days" },
84
- { value: "60", label: "60 days" },
85
- { value: "90", label: "90 days" }
76
+ { value: "7", label: t("cpq.inventory.filters.days7", "7 days") },
77
+ { value: "14", label: t("cpq.inventory.filters.days14", "14 days") },
78
+ { value: "30", label: t("cpq.inventory.filters.days30", "30 days") },
79
+ { value: "60", label: t("cpq.inventory.filters.days60", "60 days") },
80
+ { value: "90", label: t("cpq.inventory.filters.days90", "90 days") }
86
81
  ]
87
82
  }
88
83
  ],
@@ -134,9 +129,10 @@ function SubscriptionsTable() {
134
129
  else delete next.expiringWithinDays;
135
130
  data.setFilterValues(next);
136
131
  };
132
+ const expiringWindows = React.useMemo(() => buildExpiringWindows(t), [t]);
137
133
  const expiringPicker = /* @__PURE__ */ jsxs(Select, { value: currentExpiring, onValueChange: onExpiringChange, children: [
138
- /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9 w-[200px]", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: "All subscriptions" }) }),
139
- /* @__PURE__ */ jsx(SelectContent, { children: EXPIRING_WINDOWS.map((w) => /* @__PURE__ */ jsx(SelectItem, { value: w.value, children: w.label }, w.value)) })
134
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-9 w-[200px]", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("cpq.inventory.subscriptions.expiring.all", "All subscriptions") }) }),
135
+ /* @__PURE__ */ jsx(SelectContent, { children: expiringWindows.map((w) => /* @__PURE__ */ jsx(SelectItem, { value: w.value, children: w.label }, w.value)) })
140
136
  ] });
141
137
  return /* @__PURE__ */ jsxs(Fragment, { children: [
142
138
  rowActionsApi.ConfirmDialogElement,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { useCpqListData } from '../../../components/CpqListView'\nimport { useCpqRowActions } from '../../../components/useCpqRowActions'\nimport { StatusBadge, formatCurrency } from './shared'\n\n// `all` is a sentinel value because Radix Select disallows empty strings\n// as option values. We map it back to \"no filter\" before writing to the\n// query.\nconst EXPIRING_WINDOWS = [\n { value: 'all', label: 'All subscriptions' },\n { value: '7', label: 'Expiring in 7 days' },\n { value: '14', label: 'Expiring in 14 days' },\n { value: '30', label: 'Expiring in 30 days' },\n { value: '60', label: 'Expiring in 60 days' },\n { value: '90', label: 'Expiring in 90 days' },\n]\n\ntype Subscription = {\n id: string\n code: string\n name: string\n status: string\n mrcAmount: number\n nrcAmount: number\n billingCycle: string\n currencyCode: string\n startDate: string\n currentTermEnd: string | null\n}\n\nconst PAGE_SIZE = 50\n\nconst SUBSCRIPTION_STATUS_OPTIONS = [\n { value: 'pending', label: 'Pending' },\n { value: 'active', label: 'Active' },\n { value: 'suspended', label: 'Suspended' },\n { value: 'terminated', label: 'Terminated' },\n { value: 'expired', label: 'Expired' },\n { value: 'cancelled', label: 'Cancelled' },\n] as const\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.status === 'string' && values.status) {\n params.set('status', values.status)\n }\n if (typeof values.billingCycle === 'string' && values.billingCycle.trim()) {\n params.set('billingCycle', values.billingCycle.trim())\n }\n // Expiring window: replaces the standalone /expiring page. Save as a\n // perspective (\"Expiring 30d\", \"Expiring 90d\", \u2026) for quick recall.\n if (typeof values.expiringWithinDays === 'string' && values.expiringWithinDays) {\n params.set('expiringWithinDays', values.expiringWithinDays)\n }\n}\n\nexport function SubscriptionsTable() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Subscription>({\n endpoint: '/api/cpq/inventory/subscriptions',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.inventory.subscriptions.error.load', 'Failed to load subscriptions'),\n })\n\n // Subscriptions are lifecycle-managed; deleting an active one usually means\n // \"terminate\", which the API rejects. Allow deletion only from non-active\n // states so the row-actions menu doesn't dangle a useless \"Delete\" item.\n const rowActionsApi = useCpqRowActions<Subscription>({\n endpoint: '/api/cpq/inventory/subscriptions',\n entityName: t('cpq.inventory.subscriptions.entityName', 'subscription'),\n editHref: (row) => `/backend/cpq/inventory/subscriptions/${row.id}`,\n onReload: data.reload,\n canDelete: (row) => row.status !== 'active',\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('cpq.inventory.filters.status', 'Status'),\n type: 'select',\n options: [...SUBSCRIPTION_STATUS_OPTIONS],\n },\n {\n id: 'billingCycle',\n label: t('cpq.inventory.filters.billingCycle', 'Billing Cycle'),\n type: 'text',\n },\n {\n id: 'expiringWithinDays',\n label: t('cpq.inventory.filters.expiringWithinDays', 'Expiring within (days)'),\n type: 'select',\n // Pick from common renewal-watch windows. Backend caps at 365.\n options: [\n { value: '7', label: '7 days' },\n { value: '14', label: '14 days' },\n { value: '30', label: '30 days' },\n { value: '60', label: '60 days' },\n { value: '90', label: '90 days' },\n ],\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<Subscription>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.inventory.subscriptions.table.code', 'Code'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.inventory.subscriptions.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium text-primary\">{row.original.name}</span>,\n },\n {\n accessorKey: 'status',\n header: t('cpq.inventory.subscriptions.table.status', 'Status'),\n cell: ({ row }) => <StatusBadge status={row.original.status} />,\n },\n {\n id: 'mrc',\n header: t('cpq.inventory.subscriptions.table.mrc', 'MRC'),\n cell: ({ row }) => (\n <span className=\"font-mono\">\n {formatCurrency(row.original.mrcAmount, row.original.currencyCode || 'USD')}\n </span>\n ),\n },\n {\n accessorKey: 'billingCycle',\n header: t('cpq.inventory.subscriptions.table.billing', 'Billing'),\n },\n {\n accessorKey: 'startDate',\n header: t('cpq.inventory.subscriptions.table.startDate', 'Start Date'),\n cell: ({ row }) => (\n <span>\n {row.original.startDate ? new Date(row.original.startDate).toLocaleDateString() : '\u2014'}\n </span>\n ),\n },\n {\n accessorKey: 'currentTermEnd',\n header: t('cpq.inventory.subscriptions.table.termEnd', 'Term End'),\n cell: ({ row }) => (\n <span>\n {row.original.currentTermEnd\n ? new Date(row.original.currentTermEnd).toLocaleDateString()\n : '\u2014'}\n </span>\n ),\n },\n ],\n [t],\n )\n\n // One-click \"Expiring within\" picker that writes into the same filter\n // key the FilterBar uses (`expiringWithinDays`). Operators get instant\n // access without opening the filter overlay, and the value still rides\n // into perspectives because it's stored in `data.filterValues`.\n const currentExpiring = typeof data.filterValues.expiringWithinDays === 'string' && data.filterValues.expiringWithinDays\n ? data.filterValues.expiringWithinDays\n : 'all'\n const onExpiringChange = (value: string) => {\n const next: FilterValues = { ...data.filterValues }\n if (value && value !== 'all') next.expiringWithinDays = value\n else delete next.expiringWithinDays\n data.setFilterValues(next)\n }\n\n // Render the picker inline with the DataTable's title-row actions\n // (right next to Refresh / kebab menu) so it sits flush with the\n // card chrome instead of floating outside.\n const expiringPicker = (\n <Select value={currentExpiring} onValueChange={onExpiringChange}>\n <SelectTrigger className=\"h-9 w-[200px]\">\n <SelectValue placeholder=\"All subscriptions\" />\n </SelectTrigger>\n <SelectContent>\n {EXPIRING_WINDOWS.map((w) => (\n <SelectItem key={w.value} value={w.value}>\n {w.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n\n return (\n <>\n {rowActionsApi.ConfirmDialogElement}\n <DataTable<Subscription>\n title={t('cpq.inventory.subscriptions', 'Subscriptions')}\n actions={expiringPicker}\n refreshButton={{\n label: t('cpq.inventory.actions.refresh', 'Refresh'),\n onRefresh: data.reload,\n isRefreshing: data.isLoading,\n }}\n columns={columns}\n data={data.rows}\n searchValue={data.search}\n onSearchChange={data.setSearch}\n searchPlaceholder={t('cpq.inventory.subscriptions.search.placeholder', 'Search subscriptions...')}\n filters={filters}\n filterValues={data.filterValues}\n onFiltersApply={data.setFilterValues}\n onFiltersClear={data.clearFilters}\n sorting={data.sorting}\n onSortingChange={data.setSorting}\n onRowClick={(row) => router.push(`/backend/cpq/inventory/subscriptions/${row.id}`)}\n rowActions={(row) => <RowActions items={rowActionsApi.buildItems(row)} />}\n perspective={{ tableId: 'cpq.inventory.subscriptions.list' }}\n columnChooser={{ auto: true }}\n pagination={{\n page: data.page,\n pageSize: PAGE_SIZE,\n total: data.total,\n totalPages: data.totalPages,\n onPageChange: data.setPage,\n durationMs: data.durationMs,\n }}\n isLoading={data.isLoading}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.inventory.subscriptions.empty', 'No subscriptions found.')}\n </div>\n }\n />\n </>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useT, type TranslateFn } from '@open-mercato/shared/lib/i18n/context'\nimport { useRouter } from 'next/navigation'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport { useCpqListData } from '../../../components/CpqListView'\nimport { useCpqRowActions } from '../../../components/useCpqRowActions'\nimport { StatusBadge, buildInventoryStatusOptions, formatCurrency } from './shared'\n\n// `all` is a sentinel value because Radix Select disallows empty strings\n// as option values. We map it back to \"no filter\" before writing to the\n// query.\nfunction buildExpiringWindows(t: TranslateFn): { value: string; label: string }[] {\n return [\n { value: 'all', label: t('cpq.inventory.subscriptions.expiring.all', 'All subscriptions') },\n { value: '7', label: t('cpq.inventory.subscriptions.expiring.in7Days', 'Expiring in 7 days') },\n { value: '14', label: t('cpq.inventory.subscriptions.expiring.in14Days', 'Expiring in 14 days') },\n { value: '30', label: t('cpq.inventory.subscriptions.expiring.in30Days', 'Expiring in 30 days') },\n { value: '60', label: t('cpq.inventory.subscriptions.expiring.in60Days', 'Expiring in 60 days') },\n { value: '90', label: t('cpq.inventory.subscriptions.expiring.in90Days', 'Expiring in 90 days') },\n ]\n}\n\ntype Subscription = {\n id: string\n code: string\n name: string\n status: string\n mrcAmount: number\n nrcAmount: number\n billingCycle: string\n currencyCode: string\n startDate: string\n currentTermEnd: string | null\n}\n\nconst PAGE_SIZE = 50\n\nconst SUBSCRIPTION_STATUSES = ['pending', 'active', 'suspended', 'terminated', 'expired', 'cancelled'] as const\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.status === 'string' && values.status) {\n params.set('status', values.status)\n }\n if (typeof values.billingCycle === 'string' && values.billingCycle.trim()) {\n params.set('billingCycle', values.billingCycle.trim())\n }\n // Expiring window: replaces the standalone /expiring page. Save as a\n // perspective (\"Expiring 30d\", \"Expiring 90d\", \u2026) for quick recall.\n if (typeof values.expiringWithinDays === 'string' && values.expiringWithinDays) {\n params.set('expiringWithinDays', values.expiringWithinDays)\n }\n}\n\nexport function SubscriptionsTable() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Subscription>({\n endpoint: '/api/cpq/inventory/subscriptions',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.inventory.subscriptions.error.load', 'Failed to load subscriptions'),\n })\n\n // Subscriptions are lifecycle-managed; deleting an active one usually means\n // \"terminate\", which the API rejects. Allow deletion only from non-active\n // states so the row-actions menu doesn't dangle a useless \"Delete\" item.\n const rowActionsApi = useCpqRowActions<Subscription>({\n endpoint: '/api/cpq/inventory/subscriptions',\n entityName: t('cpq.inventory.subscriptions.entityName', 'subscription'),\n editHref: (row) => `/backend/cpq/inventory/subscriptions/${row.id}`,\n onReload: data.reload,\n canDelete: (row) => row.status !== 'active',\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'status',\n label: t('cpq.inventory.filters.status', 'Status'),\n type: 'select',\n options: buildInventoryStatusOptions(t, SUBSCRIPTION_STATUSES),\n },\n {\n id: 'billingCycle',\n label: t('cpq.inventory.filters.billingCycle', 'Billing Cycle'),\n type: 'text',\n },\n {\n id: 'expiringWithinDays',\n label: t('cpq.inventory.filters.expiringWithinDays', 'Expiring within (days)'),\n type: 'select',\n // Pick from common renewal-watch windows. Backend caps at 365.\n options: [\n { value: '7', label: t('cpq.inventory.filters.days7', '7 days') },\n { value: '14', label: t('cpq.inventory.filters.days14', '14 days') },\n { value: '30', label: t('cpq.inventory.filters.days30', '30 days') },\n { value: '60', label: t('cpq.inventory.filters.days60', '60 days') },\n { value: '90', label: t('cpq.inventory.filters.days90', '90 days') },\n ],\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<Subscription>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.inventory.subscriptions.table.code', 'Code'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.inventory.subscriptions.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium text-primary\">{row.original.name}</span>,\n },\n {\n accessorKey: 'status',\n header: t('cpq.inventory.subscriptions.table.status', 'Status'),\n cell: ({ row }) => <StatusBadge status={row.original.status} />,\n },\n {\n id: 'mrc',\n header: t('cpq.inventory.subscriptions.table.mrc', 'MRC'),\n cell: ({ row }) => (\n <span className=\"font-mono\">\n {formatCurrency(row.original.mrcAmount, row.original.currencyCode || 'USD')}\n </span>\n ),\n },\n {\n accessorKey: 'billingCycle',\n header: t('cpq.inventory.subscriptions.table.billing', 'Billing'),\n },\n {\n accessorKey: 'startDate',\n header: t('cpq.inventory.subscriptions.table.startDate', 'Start Date'),\n cell: ({ row }) => (\n <span>\n {row.original.startDate ? new Date(row.original.startDate).toLocaleDateString() : '\u2014'}\n </span>\n ),\n },\n {\n accessorKey: 'currentTermEnd',\n header: t('cpq.inventory.subscriptions.table.termEnd', 'Term End'),\n cell: ({ row }) => (\n <span>\n {row.original.currentTermEnd\n ? new Date(row.original.currentTermEnd).toLocaleDateString()\n : '\u2014'}\n </span>\n ),\n },\n ],\n [t],\n )\n\n // One-click \"Expiring within\" picker that writes into the same filter\n // key the FilterBar uses (`expiringWithinDays`). Operators get instant\n // access without opening the filter overlay, and the value still rides\n // into perspectives because it's stored in `data.filterValues`.\n const currentExpiring = typeof data.filterValues.expiringWithinDays === 'string' && data.filterValues.expiringWithinDays\n ? data.filterValues.expiringWithinDays\n : 'all'\n const onExpiringChange = (value: string) => {\n const next: FilterValues = { ...data.filterValues }\n if (value && value !== 'all') next.expiringWithinDays = value\n else delete next.expiringWithinDays\n data.setFilterValues(next)\n }\n\n // Render the picker inline with the DataTable's title-row actions\n // (right next to Refresh / kebab menu) so it sits flush with the\n // card chrome instead of floating outside.\n const expiringWindows = React.useMemo(() => buildExpiringWindows(t), [t])\n const expiringPicker = (\n <Select value={currentExpiring} onValueChange={onExpiringChange}>\n <SelectTrigger className=\"h-9 w-[200px]\">\n <SelectValue placeholder={t('cpq.inventory.subscriptions.expiring.all', 'All subscriptions')} />\n </SelectTrigger>\n <SelectContent>\n {expiringWindows.map((w) => (\n <SelectItem key={w.value} value={w.value}>\n {w.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )\n\n return (\n <>\n {rowActionsApi.ConfirmDialogElement}\n <DataTable<Subscription>\n title={t('cpq.inventory.subscriptions', 'Subscriptions')}\n actions={expiringPicker}\n refreshButton={{\n label: t('cpq.inventory.actions.refresh', 'Refresh'),\n onRefresh: data.reload,\n isRefreshing: data.isLoading,\n }}\n columns={columns}\n data={data.rows}\n searchValue={data.search}\n onSearchChange={data.setSearch}\n searchPlaceholder={t('cpq.inventory.subscriptions.search.placeholder', 'Search subscriptions...')}\n filters={filters}\n filterValues={data.filterValues}\n onFiltersApply={data.setFilterValues}\n onFiltersClear={data.clearFilters}\n sorting={data.sorting}\n onSortingChange={data.setSorting}\n onRowClick={(row) => router.push(`/backend/cpq/inventory/subscriptions/${row.id}`)}\n rowActions={(row) => <RowActions items={rowActionsApi.buildItems(row)} />}\n perspective={{ tableId: 'cpq.inventory.subscriptions.list' }}\n columnChooser={{ auto: true }}\n pagination={{\n page: data.page,\n pageSize: PAGE_SIZE,\n total: data.total,\n totalPages: data.totalPages,\n onPageChange: data.setPage,\n durationMs: data.durationMs,\n }}\n isLoading={data.isLoading}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.inventory.subscriptions.empty', 'No subscriptions found.')}\n </div>\n }\n />\n </>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -2,14 +2,15 @@
2
2
  import { 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 { Tag } from "@open-mercato/ui/primitives/tag";
7
8
  import { FormHeader } from "@open-mercato/ui/backend/forms";
8
9
  import {
9
- assetStatusMap,
10
- formatStatusLabel
10
+ assetStatusMap
11
11
  } from "../../../../../components/statusMaps.js";
12
12
  import { StatusPath } from "../../../../../components/StatusPath.js";
13
+ import { buildInventoryStatusLabels, inventoryStatusLabel } from "../../shared.js";
13
14
  const ALLOWED_TRANSITIONS = {
14
15
  pending: ["delivered", "active", "cancelled"],
15
16
  delivered: ["active", "returned"],
@@ -17,17 +18,12 @@ const ALLOWED_TRANSITIONS = {
17
18
  returned: [],
18
19
  cancelled: []
19
20
  };
20
- const STATUS_LABELS = {
21
- pending: "Pending",
22
- delivered: "Delivered",
23
- active: "Active",
24
- returned: "Returned",
25
- cancelled: "Cancelled"
26
- };
21
+ const ASSET_STATUSES = ["pending", "delivered", "active", "returned", "cancelled"];
27
22
  function fmt(amount, currency) {
28
23
  return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(Number(amount));
29
24
  }
30
25
  function AssetDetailPage(props) {
26
+ const t = useT();
31
27
  const router = useRouter();
32
28
  const urlParams = useParams();
33
29
  const assetId = props.params?.id ?? urlParams?.id;
@@ -53,7 +49,7 @@ function AssetDetailPage(props) {
53
49
  body: JSON.stringify({ id: asset.id, targetStatus })
54
50
  });
55
51
  const data = await res.json();
56
- if (!res.ok) throw new Error(data?.error ?? "Status transition failed");
52
+ if (!res.ok) throw new Error(data?.error ?? t("cpq.inventory.assets.detail.error.transitionFailed", "Status transition failed"));
57
53
  setAsset((prev) => prev ? { ...prev, ...data } : prev);
58
54
  } catch (err) {
59
55
  setError(err.message);
@@ -66,7 +62,7 @@ function AssetDetailPage(props) {
66
62
  async function load() {
67
63
  try {
68
64
  const res = await fetch(`/api/cpq/inventory/assets?id=${assetId}`);
69
- if (!res.ok) throw new Error("Asset not found");
65
+ if (!res.ok) throw new Error(t("cpq.inventory.assets.detail.error.notFound", "Asset not found"));
70
66
  const data = await res.json();
71
67
  if (!cancelled) {
72
68
  setAsset(data);
@@ -128,33 +124,38 @@ function AssetDetailPage(props) {
128
124
  return () => {
129
125
  cancelled = true;
130
126
  };
131
- }, [assetId]);
127
+ }, [assetId, t]);
132
128
  if (loading) {
133
129
  return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 py-12 justify-center text-sm text-muted-foreground", children: [
134
130
  /* @__PURE__ */ jsx(Spinner, {}),
135
- " Loading\u2026"
131
+ " ",
132
+ t("cpq.inventory.assets.detail.loading", "Loading\u2026")
136
133
  ] });
137
134
  }
138
135
  if (!asset) {
139
136
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
140
- /* @__PURE__ */ jsx("button", { onClick: () => router.push("/backend/cpq/inventory"), className: "text-sm text-muted-foreground hover:text-foreground", children: "\u2190 Back" }),
137
+ /* @__PURE__ */ jsxs("button", { onClick: () => router.push("/backend/cpq/inventory"), className: "text-sm text-muted-foreground hover:text-foreground", children: [
138
+ "\u2190 ",
139
+ t("cpq.common.back", "Back")
140
+ ] }),
141
141
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
142
- /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: "Asset not found." })
142
+ /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground", children: t("cpq.inventory.assets.detail.notFound", "Asset not found.") })
143
143
  ] });
144
144
  }
145
145
  const currency = asset.currencyCode || "USD";
146
146
  const statusTransitions = ALLOWED_TRANSITIONS[asset.status] ?? [];
147
+ const statusLabels = buildInventoryStatusLabels(t, ASSET_STATUSES);
147
148
  return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
148
149
  /* @__PURE__ */ jsx(
149
150
  FormHeader,
150
151
  {
151
152
  mode: "detail",
152
153
  backHref: "/backend/cpq/inventory",
153
- backLabel: "Back to Customer Inventory",
154
- entityTypeLabel: "Asset",
154
+ backLabel: t("cpq.inventory.detail.backToInventory", "Back to Customer Inventory"),
155
+ entityTypeLabel: t("cpq.inventory.assets.detail.entityType", "Asset"),
155
156
  title: asset.name,
156
157
  statusBadge: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 pt-1", children: [
157
- /* @__PURE__ */ jsx(Tag, { variant: assetStatusMap[asset.status] ?? "neutral", dot: true, children: STATUS_LABELS[asset.status] ?? formatStatusLabel(asset.status) }),
158
+ /* @__PURE__ */ jsx(Tag, { variant: assetStatusMap[asset.status] ?? "neutral", dot: true, children: inventoryStatusLabel(t, asset.status) }),
158
159
  /* @__PURE__ */ jsx(Tag, { variant: "brand", className: "capitalize", children: asset.assetType.replace(/_/g, " ") }),
159
160
  transitioning && /* @__PURE__ */ jsx(Spinner, {})
160
161
  ] })
@@ -168,11 +169,11 @@ function AssetDetailPage(props) {
168
169
  path: ["pending", "delivered", "active", "returned"],
169
170
  terminals: ["cancelled"],
170
171
  statusMap: assetStatusMap,
171
- labels: STATUS_LABELS,
172
+ labels: statusLabels,
172
173
  onTransition: transitionStatus,
173
174
  allowedTransitions: statusTransitions,
174
175
  disabled: transitioning,
175
- ariaLabel: "Asset status path"
176
+ ariaLabel: t("cpq.inventory.assets.detail.statusPath", "Asset status path")
176
177
  }
177
178
  ),
178
179
  transitioning && /* @__PURE__ */ jsx(Spinner, {})
@@ -180,34 +181,34 @@ function AssetDetailPage(props) {
180
181
  error && /* @__PURE__ */ jsx(ErrorBanner, { message: error }),
181
182
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 gap-4", children: [
182
183
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
183
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Asset Details" }),
184
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.inventory.assets.detail.sections.details", "Asset Details") }),
184
185
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4 gap-y-2", children: [
185
- /* @__PURE__ */ jsx(Field, { label: "Code", value: asset.code, mono: true }),
186
- /* @__PURE__ */ jsx(Field, { label: "Status", value: asset.status }),
187
- /* @__PURE__ */ jsx(Field, { label: "Asset Type", value: asset.assetType.replace(/_/g, " ") }),
188
- /* @__PURE__ */ jsx(Field, { label: "Currency", value: currency }),
189
- /* @__PURE__ */ jsx(Field, { label: "Quantity", value: String(asset.quantity) }),
190
- asset.purchaseDate && /* @__PURE__ */ jsx(Field, { label: "Purchase Date", value: new Date(asset.purchaseDate).toLocaleDateString() }),
191
- asset.deliveryDate && /* @__PURE__ */ jsx(Field, { label: "Delivery Date", value: new Date(asset.deliveryDate).toLocaleDateString() })
186
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.code", "Code"), value: asset.code, mono: true }),
187
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.status", "Status"), value: asset.status }),
188
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.assetType", "Asset Type"), value: asset.assetType.replace(/_/g, " ") }),
189
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.currency", "Currency"), value: currency }),
190
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.quantity", "Quantity"), value: String(asset.quantity) }),
191
+ asset.purchaseDate && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.purchaseDate", "Purchase Date"), value: new Date(asset.purchaseDate).toLocaleDateString() }),
192
+ asset.deliveryDate && /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.deliveryDate", "Delivery Date"), value: new Date(asset.deliveryDate).toLocaleDateString() })
192
193
  ] }),
193
194
  asset.description && /* @__PURE__ */ jsxs("div", { className: "pt-1", children: [
194
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Description" }),
195
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.assets.detail.fields.description", "Description") }),
195
196
  /* @__PURE__ */ jsx("p", { className: "text-sm", children: asset.description })
196
197
  ] })
197
198
  ] }),
198
199
  /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4 space-y-3", children: [
199
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: "Pricing & References" }),
200
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium", children: t("cpq.inventory.assets.detail.sections.pricing", "Pricing & References") }),
200
201
  /* @__PURE__ */ jsxs("div", { children: [
201
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: "Purchase Price" }),
202
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("cpq.inventory.assets.detail.fields.purchasePrice", "Purchase Price") }),
202
203
  /* @__PURE__ */ jsx("p", { className: "text-lg font-semibold font-mono", children: fmt(asset.purchasePrice, currency) })
203
204
  ] }),
204
205
  /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-2 gap-x-4 gap-y-2 pt-1", children: [
205
- /* @__PURE__ */ jsx(Field, { label: "Customer", value: customerName ?? `${asset.customerId.slice(0, 12)}\u2026` }),
206
- /* @__PURE__ */ jsx(Field, { label: "Created", value: new Date(asset.createdAt).toLocaleString() }),
206
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.customer", "Customer"), value: customerName ?? `${asset.customerId.slice(0, 12)}\u2026` }),
207
+ /* @__PURE__ */ jsx(Field, { label: t("cpq.inventory.assets.detail.fields.created", "Created"), value: new Date(asset.createdAt).toLocaleString() }),
207
208
  asset.subscriptionId && /* @__PURE__ */ jsx(
208
209
  RefField,
209
210
  {
210
- label: "Linked Subscription",
211
+ label: t("cpq.inventory.assets.detail.fields.linkedSubscription", "Linked Subscription"),
211
212
  name: subscriptionName ?? void 0,
212
213
  fallback: asset.subscriptionId,
213
214
  onClick: () => router.push(`/backend/cpq/inventory/subscriptions/${asset.subscriptionId}`)
@@ -216,7 +217,7 @@ function AssetDetailPage(props) {
216
217
  asset.offeringId && /* @__PURE__ */ jsx(
217
218
  RefField,
218
219
  {
219
- label: "Offering",
220
+ label: t("cpq.inventory.assets.detail.fields.offering", "Offering"),
220
221
  name: offeringName ?? void 0,
221
222
  fallback: asset.offeringId,
222
223
  onClick: () => router.push(`/backend/cpq/offerings/${asset.offeringId}`)
@@ -225,7 +226,7 @@ function AssetDetailPage(props) {
225
226
  asset.specId && /* @__PURE__ */ jsx(
226
227
  RefField,
227
228
  {
228
- label: "Specification",
229
+ label: t("cpq.inventory.assets.detail.fields.specification", "Specification"),
229
230
  name: specName ?? void 0,
230
231
  fallback: asset.specId,
231
232
  onClick: () => router.push(`/backend/cpq/specifications/${asset.specId}`)
@@ -234,7 +235,7 @@ function AssetDetailPage(props) {
234
235
  asset.productId && /* @__PURE__ */ jsx(
235
236
  RefField,
236
237
  {
237
- label: "Product",
238
+ label: t("cpq.inventory.assets.detail.fields.product", "Product"),
238
239
  name: productName ?? void 0,
239
240
  fallback: asset.productId,
240
241
  onClick: () => router.push(`/backend/catalog/products/${asset.productId}`)
@@ -243,7 +244,7 @@ function AssetDetailPage(props) {
243
244
  asset.sourceQuoteId && /* @__PURE__ */ jsx(
244
245
  RefField,
245
246
  {
246
- label: "Source Quote",
247
+ label: t("cpq.inventory.assets.detail.fields.sourceQuote", "Source Quote"),
247
248
  name: sourceQuoteNumber ?? void 0,
248
249
  fallback: asset.sourceQuoteId,
249
250
  onClick: () => router.push(`/backend/cpq/quotes/${asset.sourceQuoteId}`)
@@ -252,7 +253,7 @@ function AssetDetailPage(props) {
252
253
  asset.sourceOrderId && /* @__PURE__ */ jsx(
253
254
  RefField,
254
255
  {
255
- label: "Source Order",
256
+ label: t("cpq.inventory.assets.detail.fields.sourceOrder", "Source Order"),
256
257
  name: sourceOrderNumber ?? void 0,
257
258
  fallback: asset.sourceOrderId,
258
259
  onClick: () => router.push(`/backend/cpq/orders/${asset.sourceOrderId}`)
@@ -262,7 +263,7 @@ function AssetDetailPage(props) {
262
263
  ] })
263
264
  ] }),
264
265
  asset.configuration && Object.keys(asset.configuration).length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
265
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: "Configuration" }),
266
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: t("cpq.inventory.assets.detail.sections.configuration", "Configuration") }),
266
267
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1", children: Object.entries(asset.configuration).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
267
268
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
268
269
  key,
@@ -272,14 +273,14 @@ function AssetDetailPage(props) {
272
273
  ] }, key)) })
273
274
  ] }),
274
275
  asset.charges && asset.charges.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
275
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: "Charges" }),
276
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: t("cpq.inventory.assets.detail.sections.charges", "Charges") }),
276
277
  /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
277
278
  /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b bg-muted/50 text-muted-foreground", children: [
278
- /* @__PURE__ */ jsx("th", { className: "text-left px-3 py-2 font-medium", children: "Charge" }),
279
- /* @__PURE__ */ jsx("th", { className: "text-left px-3 py-2 font-medium", children: "Type" }),
280
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-2 font-medium", children: "Unit Price" }),
281
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-2 font-medium", children: "Qty" }),
282
- /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-2 font-medium", children: "Total" })
279
+ /* @__PURE__ */ jsx("th", { className: "text-left px-3 py-2 font-medium", children: t("cpq.inventory.assets.detail.charges.charge", "Charge") }),
280
+ /* @__PURE__ */ jsx("th", { className: "text-left px-3 py-2 font-medium", children: t("cpq.inventory.assets.detail.charges.type", "Type") }),
281
+ /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-2 font-medium", children: t("cpq.inventory.assets.detail.charges.unitPrice", "Unit Price") }),
282
+ /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-2 font-medium", children: t("cpq.inventory.assets.detail.charges.qty", "Qty") }),
283
+ /* @__PURE__ */ jsx("th", { className: "text-right px-3 py-2 font-medium", children: t("cpq.inventory.assets.detail.charges.total", "Total") })
283
284
  ] }) }),
284
285
  /* @__PURE__ */ jsx("tbody", { children: asset.charges.map((c, i) => /* @__PURE__ */ jsxs("tr", { className: "border-b", children: [
285
286
  /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: c.chargeName ?? "\u2014" }),
@@ -291,7 +292,7 @@ function AssetDetailPage(props) {
291
292
  ] })
292
293
  ] }),
293
294
  asset.metadata && Object.keys(asset.metadata).length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
294
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: "Metadata" }),
295
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-medium mb-2", children: t("cpq.inventory.assets.detail.sections.metadata", "Metadata") }),
295
296
  /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 gap-x-4 gap-y-1", children: Object.entries(asset.metadata).map(([key, value]) => /* @__PURE__ */ jsxs("div", { className: "text-xs", children: [
296
297
  /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
297
298
  key,