@dainamite/cpq 0.3.0 → 0.5.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 (63) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/MIGRATION.md +30 -0
  3. package/dist/modules/cpq/api/_helpers/handleArcError.js +2 -2
  4. package/dist/modules/cpq/api/_helpers/handleArcError.js.map +2 -2
  5. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +2 -2
  6. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +2 -2
  7. package/dist/modules/cpq/api/orders/[orderId]/route.js +2 -2
  8. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +2 -2
  9. package/dist/modules/cpq/api/orders/[orderId]/status/route.js +2 -2
  10. package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +2 -2
  11. package/dist/modules/cpq/api/orders/route.js +2 -2
  12. package/dist/modules/cpq/api/orders/route.js.map +2 -2
  13. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +2 -2
  14. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +2 -2
  15. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +3 -3
  16. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +2 -2
  17. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +2 -2
  18. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +2 -2
  19. package/dist/modules/cpq/api/quotes/[quoteId]/route.js +2 -2
  20. package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +2 -2
  21. package/dist/modules/cpq/api/quotes/route.js +2 -2
  22. package/dist/modules/cpq/api/quotes/route.js.map +2 -2
  23. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +1 -1
  24. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +1 -1
  25. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +1 -1
  26. package/dist/modules/cpq/backend/cpq/orders/page.js.map +1 -1
  27. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +1 -1
  28. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +1 -1
  29. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +2 -2
  30. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +1 -1
  31. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +1 -1
  32. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +1 -1
  33. package/dist/modules/cpq/components/CpqListView.js.map +2 -2
  34. package/dist/modules/cpq/services/cpqOrderService.js +9 -2
  35. package/dist/modules/cpq/services/cpqOrderService.js.map +2 -2
  36. package/dist/modules/cpq/services/cpqQuotingService.js +9 -2
  37. package/dist/modules/cpq/services/cpqQuotingService.js.map +2 -2
  38. package/package.json +9 -9
  39. package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +8 -5
  40. package/src/modules/cpq/api/_helpers/handleArcError.ts +12 -12
  41. package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +2 -2
  42. package/src/modules/cpq/api/orders/[orderId]/route.ts +2 -2
  43. package/src/modules/cpq/api/orders/[orderId]/status/route.ts +2 -2
  44. package/src/modules/cpq/api/orders/route.ts +2 -2
  45. package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +2 -2
  46. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +3 -3
  47. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +2 -2
  48. package/src/modules/cpq/api/quotes/[quoteId]/route.ts +2 -2
  49. package/src/modules/cpq/api/quotes/route.ts +2 -2
  50. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +1 -1
  51. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +1 -1
  52. package/src/modules/cpq/backend/cpq/offerings/page.tsx +1 -1
  53. package/src/modules/cpq/backend/cpq/orders/page.tsx +1 -1
  54. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +1 -1
  55. package/src/modules/cpq/backend/cpq/pricing/page.tsx +1 -1
  56. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +4 -3
  57. package/src/modules/cpq/backend/cpq/quotes/page.tsx +1 -1
  58. package/src/modules/cpq/backend/cpq/specifications/page.tsx +1 -1
  59. package/src/modules/cpq/backend/cpq/wizards/page.tsx +1 -1
  60. package/src/modules/cpq/components/CpqListView.tsx +2 -1
  61. package/src/modules/cpq/services/__tests__/errorBrands.test.ts +72 -0
  62. package/src/modules/cpq/services/cpqOrderService.ts +10 -0
  63. package/src/modules/cpq/services/cpqQuotingService.ts +14 -0
@@ -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 { ColumnDef } from '@tanstack/react-table'\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"],
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
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -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 { ColumnDef } from '@tanstack/react-table'\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"],
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
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/offerings/page.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 { ColumnDef } from '@tanstack/react-table'\nimport { type BulkAction } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport { NumberInput } from '../../../components/NumberInput'\nimport {\n chargeTypeMap,\n formatStatusLabel,\n lifecycleStatusMap,\n type ChargeType,\n type LifecycleStatus,\n} from '../../../components/statusMaps'\n\ntype Charge = {\n id: string\n code: string\n name: string\n description: string | null\n chargeType: string\n pricingMethod: string\n fixedPrice: string | null\n currencyCode: string | null\n isActive: boolean\n}\n\ntype Offering = {\n id: string\n specId: string\n code: string\n name: string\n description: string | null\n offeringType: string\n designTimeValues: Record<string, unknown>\n lifecycleStatus: string\n isActive: boolean\n createdAt: string\n charges?: Charge[]\n}\n\nconst PAGE_SIZE = 50\n\nfunction ChargePopover({ charge }: { charge: Charge }) {\n const [open, setOpen] = React.useState(false)\n const variant = chargeTypeMap[charge.chargeType as ChargeType] ?? 'neutral'\n return (\n <span\n className=\"relative inline-flex\"\n onMouseEnter={() => setOpen(true)}\n onMouseLeave={() => setOpen(false)}\n >\n <Tag variant={variant} className=\"cursor-default px-2 text-[10px]\">\n {charge.chargeType.toUpperCase()}\n </Tag>\n {open && (\n <div className=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50 w-56 rounded-lg border bg-popover p-3 shadow-md text-popover-foreground text-xs space-y-1.5\">\n <div className=\"font-medium text-sm\">{charge.name}</div>\n <div className=\"text-muted-foreground font-mono\">{charge.code}</div>\n {charge.description && <div className=\"text-muted-foreground\">{charge.description}</div>}\n <div className=\"flex items-center justify-between pt-1 border-t\">\n <span>Pricing: {charge.pricingMethod}</span>\n {charge.fixedPrice != null && (\n <span className=\"font-medium\">\n {charge.currencyCode ?? 'USD'} {charge.fixedPrice}\n </span>\n )}\n </div>\n {!charge.isActive && <Tag variant=\"warning\" dot>Inactive</Tag>}\n <div className=\"absolute left-1/2 -translate-x-1/2 top-full w-2 h-2 bg-popover border-b border-r rotate-45 -mt-1\" />\n </div>\n )}\n </span>\n )\n}\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.lifecycleStatus === 'string' && values.lifecycleStatus) {\n params.set('lifecycleStatus', values.lifecycleStatus)\n }\n if (typeof values.offeringType === 'string' && values.offeringType) {\n params.set('offeringType', values.offeringType)\n }\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function OfferingsListPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Offering>({\n endpoint: '/api/cpq/product-offerings',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.offerings.list.error.load', 'Failed to load offerings'),\n })\n\n // Bulk charge creation is restricted to `flat` pricing \u2014 per_unit/tiered\n // need a pricing table + a numeric quantity attribute that lives on the\n // offering's spec, which differs between selected offerings. We push\n // users to the per-offering detail page for those methods.\n const [bulkChargeOpen, setBulkChargeOpen] = React.useState(false)\n const [bulkChargeTargets, setBulkChargeTargets] = React.useState<Offering[]>([])\n const [chargeForm, setChargeForm] = React.useState({\n code: '',\n name: '',\n description: '',\n chargeType: 'mrc',\n })\n const [chargePrices, setChargePrices] = React.useState<Record<string, string>>({})\n const [chargeCurrency, setChargeCurrency] = React.useState('USD')\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const lifecycleOptions = React.useMemo(\n () => [\n { value: 'draft', label: t('cpq.offerings.lifecycle.draft', 'Draft') },\n { value: 'active', label: t('cpq.offerings.lifecycle.active', 'Active') },\n { value: 'deprecated', label: t('cpq.offerings.lifecycle.deprecated', 'Deprecated') },\n ],\n [t],\n )\n\n const offeringTypeOptions = React.useMemo(\n () => [\n { value: 'simple', label: t('cpq.offerings.type.simple', 'Simple') },\n { value: 'bundle', label: t('cpq.offerings.type.bundle', 'Bundle') },\n ],\n [t],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'lifecycleStatus',\n label: t('cpq.offerings.filters.lifecycleStatus', 'Lifecycle Status'),\n type: 'select',\n options: lifecycleOptions,\n },\n {\n id: 'offeringType',\n label: t('cpq.offerings.filters.offeringType', 'Type'),\n type: 'select',\n options: offeringTypeOptions,\n },\n {\n id: 'isActive',\n label: t('cpq.offerings.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [lifecycleOptions, offeringTypeOptions, t],\n )\n\n const openBulkChargeForm = React.useCallback(\n (targets: Offering[]) => {\n setBulkChargeTargets(targets)\n setChargeForm({ code: '', name: '', description: '', chargeType: 'mrc' })\n setChargePrices({})\n setChargeCurrency('USD')\n setError(null)\n setBulkChargeOpen(true)\n },\n [],\n )\n\n const saveChargesForSelected = React.useCallback(async () => {\n const targets = bulkChargeTargets\n if (!targets.length || !chargeForm.code || !chargeForm.name) return\n setSaving(true)\n setError(null)\n try {\n for (const offering of targets) {\n const chargeCode = targets.length > 1 ? `${offering.code}--${chargeForm.code}` : chargeForm.code\n const payload: Record<string, unknown> = {\n offeringId: offering.id,\n code: chargeCode,\n name: chargeForm.name,\n description: chargeForm.description || null,\n chargeType: chargeForm.chargeType,\n pricingMethod: 'flat',\n fixedPrice: chargePrices[offering.id] || null,\n currencyCode: chargeCurrency,\n isActive: true,\n sortOrder: 0,\n }\n const res = await fetch('/api/cpq/product-charges', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({} as { error?: string }))\n setError(body.error ?? `Failed to save charge for ${offering.name}`)\n setSaving(false)\n return\n }\n }\n flash(t('cpq.offerings.flash.chargesCreated', 'Charges created'), 'success')\n setBulkChargeOpen(false)\n setBulkChargeTargets([])\n data.reload()\n } catch {\n setError('Failed to save charges')\n } finally {\n setSaving(false)\n }\n }, [\n bulkChargeTargets,\n chargeCurrency,\n chargeForm,\n chargePrices,\n data,\n t,\n ])\n\n const columns = React.useMemo<ColumnDef<Offering>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.offerings.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.offerings.name', 'Name'),\n cell: ({ row }) => (\n <span className=\"font-medium\">\n {row.original.name}\n {row.original.offeringType === 'bundle' && (\n <Tag variant=\"brand\" className=\"ml-2 px-2 text-[10px]\">\n bundle\n </Tag>\n )}\n </span>\n ),\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.offerings.isActive', 'Is Active?'),\n cell: ({ row }) => <Checkbox checked={row.original.isActive} disabled />,\n },\n {\n accessorKey: 'lifecycleStatus',\n header: t('cpq.offerings.lifecycleStatus', 'Lifecycle Status'),\n cell: ({ row }) => (\n <Tag variant={lifecycleStatusMap[row.original.lifecycleStatus as LifecycleStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.lifecycleStatus)}\n </Tag>\n ),\n },\n {\n id: 'charges',\n header: t('cpq.offerings.charges', 'Charges'),\n cell: ({ row }) => {\n const charges = row.original.charges ?? []\n if (charges.length === 0) return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n return (\n <div className=\"flex flex-wrap gap-1\" onClick={(e) => e.stopPropagation()}>\n {charges.map((ch) => (\n <ChargePopover key={ch.id} charge={ch} />\n ))}\n </div>\n )\n },\n },\n {\n accessorKey: 'description',\n header: t('cpq.offerings.description', 'Description'),\n cell: ({ row }) => (\n <span className=\"text-muted-foreground\">{row.original.description ?? '\u2014'}</span>\n ),\n meta: { truncate: true, maxWidth: 320 },\n },\n ],\n [t],\n )\n\n // Module-specific bulk actions; \"Delete selected\" is appended automatically\n // by CpqListView when `crud` is provided.\n const bulkActions = React.useMemo<BulkAction<Offering>[]>(\n () => [\n {\n id: 'add-charge',\n label: t('cpq.offerings.bulk.addCharge', 'Add Charge'),\n onExecute: (selectedRows) => {\n if (!selectedRows.length) return { ok: false }\n openBulkChargeForm(selectedRows)\n return { ok: true }\n },\n },\n ],\n [openBulkChargeForm, t],\n )\n\n const bulkChargeForm = bulkChargeOpen && bulkChargeTargets.length > 0 && (\n <div className=\"rounded-lg border bg-card p-5 space-y-4\">\n <h4 className=\"font-medium text-sm\">\n {t('cpq.offerings.bulk.formTitle', 'New Flat-Price Charge for')} {bulkChargeTargets.length}{' '}\n {bulkChargeTargets.length > 1\n ? t('cpq.offerings.bulk.offeringsPlural', 'offerings')\n : t('cpq.offerings.bulk.offeringsSingular', 'offering')}\n </h4>\n\n <Alert>\n Bulk creation supports <strong>flat pricing only</strong>. For <em>per unit</em> or <em>tiered</em> pricing,\n open each offering individually \u2014 those methods need a pricing table and a numeric attribute that lives on\n the offering&apos;s spec, which usually differs between offerings.\n </Alert>\n\n <div className=\"grid grid-cols-3 gap-3\">\n <div>\n <label className=\"block text-xs font-medium mb-1\">Code</label>\n <Input\n value={chargeForm.code}\n onChange={(e) => setChargeForm({ ...chargeForm, code: e.target.value })}\n placeholder=\"e.g. mrc-monthly\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium mb-1\">Name</label>\n <Input\n value={chargeForm.name}\n onChange={(e) => setChargeForm({ ...chargeForm, name: e.target.value })}\n placeholder=\"e.g. Monthly Recurring\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium mb-1\">Type</label>\n <Select\n value={chargeForm.chargeType}\n onValueChange={(value) => setChargeForm({ ...chargeForm, chargeType: value })}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"nrc\">NRC (Non-Recurring)</SelectItem>\n <SelectItem value=\"mrc\">MRC (Monthly Recurring)</SelectItem>\n <SelectItem value=\"usage\">Usage</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div>\n <label className=\"block text-xs font-medium mb-1\">Description</label>\n <Input\n value={chargeForm.description}\n onChange={(e) => setChargeForm({ ...chargeForm, description: e.target.value })}\n placeholder=\"User-facing charge description\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <label className=\"text-xs font-medium\">Currency</label>\n <Input\n value={chargeCurrency}\n onChange={(e) => setChargeCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className=\"w-20\"\n />\n </div>\n <div className=\"rounded border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/30\">\n <th className=\"px-3 py-2 text-left font-medium text-xs\">Offering</th>\n <th className=\"px-3 py-2 text-left font-medium text-xs\">Price ({chargeCurrency})</th>\n </tr>\n </thead>\n <tbody>\n {bulkChargeTargets.map((o) => (\n <tr key={o.id} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2 font-medium text-xs\">\n {o.name} <span className=\"text-muted-foreground font-mono\">({o.code})</span>\n </td>\n <td className=\"px-3 py-2\">\n <NumberInput\n value={chargePrices[o.id] === undefined || chargePrices[o.id] === '' ? null : Number(chargePrices[o.id])}\n onChange={(n) =>\n setChargePrices({ ...chargePrices, [o.id]: n == null ? '' : String(n) })\n }\n placeholder=\"0.00\"\n className=\"w-32\"\n />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n\n <div className=\"flex gap-2 pt-1\">\n <Button\n type=\"button\"\n onClick={saveChargesForSelected}\n disabled={saving || !chargeForm.code || !chargeForm.name}\n >\n {saving\n ? 'Saving...'\n : `Create Charge for ${bulkChargeTargets.length} Offering${bulkChargeTargets.length > 1 ? 's' : ''}`}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setBulkChargeOpen(false)}>\n Cancel\n </Button>\n </div>\n </div>\n )\n\n const toolbarContent = (\n <>\n {error && <Alert variant=\"destructive\">{error}</Alert>}\n {bulkChargeForm}\n </>\n )\n\n return (\n <CpqListView<Offering>\n title={t('cpq.offerings.list.title', 'Product Offerings')}\n tableId=\"cpq.offerings.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.offerings.search.placeholder', 'Search offerings...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/offerings/new\">{t('cpq.offerings.add', 'New Offering')}</a>\n </Button>\n }\n bulkActions={bulkActions}\n crud={{\n endpoint: '/api/cpq/product-offerings',\n entityName: t('cpq.offerings.entityName', 'offering'),\n editHref: (row) => `/backend/cpq/offerings/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/offerings/${row.id}`)}\n toolbarContent={toolbarContent}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.offerings.empty',\n 'No product offerings found. Create one to define a sellable variant of a product specification.',\n )}\n </div>\n }\n />\n )\n}\n"],
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 { type BulkAction } from '@open-mercato/ui/backend/DataTable'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Checkbox } from '@open-mercato/ui/primitives/checkbox'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Alert } from '@open-mercato/ui/primitives/alert'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport { NumberInput } from '../../../components/NumberInput'\nimport {\n chargeTypeMap,\n formatStatusLabel,\n lifecycleStatusMap,\n type ChargeType,\n type LifecycleStatus,\n} from '../../../components/statusMaps'\n\ntype Charge = {\n id: string\n code: string\n name: string\n description: string | null\n chargeType: string\n pricingMethod: string\n fixedPrice: string | null\n currencyCode: string | null\n isActive: boolean\n}\n\ntype Offering = {\n id: string\n specId: string\n code: string\n name: string\n description: string | null\n offeringType: string\n designTimeValues: Record<string, unknown>\n lifecycleStatus: string\n isActive: boolean\n createdAt: string\n charges?: Charge[]\n}\n\nconst PAGE_SIZE = 50\n\nfunction ChargePopover({ charge }: { charge: Charge }) {\n const [open, setOpen] = React.useState(false)\n const variant = chargeTypeMap[charge.chargeType as ChargeType] ?? 'neutral'\n return (\n <span\n className=\"relative inline-flex\"\n onMouseEnter={() => setOpen(true)}\n onMouseLeave={() => setOpen(false)}\n >\n <Tag variant={variant} className=\"cursor-default px-2 text-[10px]\">\n {charge.chargeType.toUpperCase()}\n </Tag>\n {open && (\n <div className=\"absolute bottom-full left-1/2 -translate-x-1/2 mb-2 z-50 w-56 rounded-lg border bg-popover p-3 shadow-md text-popover-foreground text-xs space-y-1.5\">\n <div className=\"font-medium text-sm\">{charge.name}</div>\n <div className=\"text-muted-foreground font-mono\">{charge.code}</div>\n {charge.description && <div className=\"text-muted-foreground\">{charge.description}</div>}\n <div className=\"flex items-center justify-between pt-1 border-t\">\n <span>Pricing: {charge.pricingMethod}</span>\n {charge.fixedPrice != null && (\n <span className=\"font-medium\">\n {charge.currencyCode ?? 'USD'} {charge.fixedPrice}\n </span>\n )}\n </div>\n {!charge.isActive && <Tag variant=\"warning\" dot>Inactive</Tag>}\n <div className=\"absolute left-1/2 -translate-x-1/2 top-full w-2 h-2 bg-popover border-b border-r rotate-45 -mt-1\" />\n </div>\n )}\n </span>\n )\n}\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.lifecycleStatus === 'string' && values.lifecycleStatus) {\n params.set('lifecycleStatus', values.lifecycleStatus)\n }\n if (typeof values.offeringType === 'string' && values.offeringType) {\n params.set('offeringType', values.offeringType)\n }\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function OfferingsListPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<Offering>({\n endpoint: '/api/cpq/product-offerings',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.offerings.list.error.load', 'Failed to load offerings'),\n })\n\n // Bulk charge creation is restricted to `flat` pricing \u2014 per_unit/tiered\n // need a pricing table + a numeric quantity attribute that lives on the\n // offering's spec, which differs between selected offerings. We push\n // users to the per-offering detail page for those methods.\n const [bulkChargeOpen, setBulkChargeOpen] = React.useState(false)\n const [bulkChargeTargets, setBulkChargeTargets] = React.useState<Offering[]>([])\n const [chargeForm, setChargeForm] = React.useState({\n code: '',\n name: '',\n description: '',\n chargeType: 'mrc',\n })\n const [chargePrices, setChargePrices] = React.useState<Record<string, string>>({})\n const [chargeCurrency, setChargeCurrency] = React.useState('USD')\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const lifecycleOptions = React.useMemo(\n () => [\n { value: 'draft', label: t('cpq.offerings.lifecycle.draft', 'Draft') },\n { value: 'active', label: t('cpq.offerings.lifecycle.active', 'Active') },\n { value: 'deprecated', label: t('cpq.offerings.lifecycle.deprecated', 'Deprecated') },\n ],\n [t],\n )\n\n const offeringTypeOptions = React.useMemo(\n () => [\n { value: 'simple', label: t('cpq.offerings.type.simple', 'Simple') },\n { value: 'bundle', label: t('cpq.offerings.type.bundle', 'Bundle') },\n ],\n [t],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'lifecycleStatus',\n label: t('cpq.offerings.filters.lifecycleStatus', 'Lifecycle Status'),\n type: 'select',\n options: lifecycleOptions,\n },\n {\n id: 'offeringType',\n label: t('cpq.offerings.filters.offeringType', 'Type'),\n type: 'select',\n options: offeringTypeOptions,\n },\n {\n id: 'isActive',\n label: t('cpq.offerings.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [lifecycleOptions, offeringTypeOptions, t],\n )\n\n const openBulkChargeForm = React.useCallback(\n (targets: Offering[]) => {\n setBulkChargeTargets(targets)\n setChargeForm({ code: '', name: '', description: '', chargeType: 'mrc' })\n setChargePrices({})\n setChargeCurrency('USD')\n setError(null)\n setBulkChargeOpen(true)\n },\n [],\n )\n\n const saveChargesForSelected = React.useCallback(async () => {\n const targets = bulkChargeTargets\n if (!targets.length || !chargeForm.code || !chargeForm.name) return\n setSaving(true)\n setError(null)\n try {\n for (const offering of targets) {\n const chargeCode = targets.length > 1 ? `${offering.code}--${chargeForm.code}` : chargeForm.code\n const payload: Record<string, unknown> = {\n offeringId: offering.id,\n code: chargeCode,\n name: chargeForm.name,\n description: chargeForm.description || null,\n chargeType: chargeForm.chargeType,\n pricingMethod: 'flat',\n fixedPrice: chargePrices[offering.id] || null,\n currencyCode: chargeCurrency,\n isActive: true,\n sortOrder: 0,\n }\n const res = await fetch('/api/cpq/product-charges', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const body = await res.json().catch(() => ({} as { error?: string }))\n setError(body.error ?? `Failed to save charge for ${offering.name}`)\n setSaving(false)\n return\n }\n }\n flash(t('cpq.offerings.flash.chargesCreated', 'Charges created'), 'success')\n setBulkChargeOpen(false)\n setBulkChargeTargets([])\n data.reload()\n } catch {\n setError('Failed to save charges')\n } finally {\n setSaving(false)\n }\n }, [\n bulkChargeTargets,\n chargeCurrency,\n chargeForm,\n chargePrices,\n data,\n t,\n ])\n\n const columns = React.useMemo<ColumnDef<Offering>[]>(\n () => [\n {\n accessorKey: 'code',\n header: t('cpq.offerings.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.offerings.name', 'Name'),\n cell: ({ row }) => (\n <span className=\"font-medium\">\n {row.original.name}\n {row.original.offeringType === 'bundle' && (\n <Tag variant=\"brand\" className=\"ml-2 px-2 text-[10px]\">\n bundle\n </Tag>\n )}\n </span>\n ),\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.offerings.isActive', 'Is Active?'),\n cell: ({ row }) => <Checkbox checked={row.original.isActive} disabled />,\n },\n {\n accessorKey: 'lifecycleStatus',\n header: t('cpq.offerings.lifecycleStatus', 'Lifecycle Status'),\n cell: ({ row }) => (\n <Tag variant={lifecycleStatusMap[row.original.lifecycleStatus as LifecycleStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.lifecycleStatus)}\n </Tag>\n ),\n },\n {\n id: 'charges',\n header: t('cpq.offerings.charges', 'Charges'),\n cell: ({ row }) => {\n const charges = row.original.charges ?? []\n if (charges.length === 0) return <span className=\"text-xs text-muted-foreground\">\u2014</span>\n return (\n <div className=\"flex flex-wrap gap-1\" onClick={(e) => e.stopPropagation()}>\n {charges.map((ch) => (\n <ChargePopover key={ch.id} charge={ch} />\n ))}\n </div>\n )\n },\n },\n {\n accessorKey: 'description',\n header: t('cpq.offerings.description', 'Description'),\n cell: ({ row }) => (\n <span className=\"text-muted-foreground\">{row.original.description ?? '\u2014'}</span>\n ),\n meta: { truncate: true, maxWidth: 320 },\n },\n ],\n [t],\n )\n\n // Module-specific bulk actions; \"Delete selected\" is appended automatically\n // by CpqListView when `crud` is provided.\n const bulkActions = React.useMemo<BulkAction<Offering>[]>(\n () => [\n {\n id: 'add-charge',\n label: t('cpq.offerings.bulk.addCharge', 'Add Charge'),\n onExecute: (selectedRows) => {\n if (!selectedRows.length) return { ok: false }\n openBulkChargeForm(selectedRows)\n return { ok: true }\n },\n },\n ],\n [openBulkChargeForm, t],\n )\n\n const bulkChargeForm = bulkChargeOpen && bulkChargeTargets.length > 0 && (\n <div className=\"rounded-lg border bg-card p-5 space-y-4\">\n <h4 className=\"font-medium text-sm\">\n {t('cpq.offerings.bulk.formTitle', 'New Flat-Price Charge for')} {bulkChargeTargets.length}{' '}\n {bulkChargeTargets.length > 1\n ? t('cpq.offerings.bulk.offeringsPlural', 'offerings')\n : t('cpq.offerings.bulk.offeringsSingular', 'offering')}\n </h4>\n\n <Alert>\n Bulk creation supports <strong>flat pricing only</strong>. For <em>per unit</em> or <em>tiered</em> pricing,\n open each offering individually \u2014 those methods need a pricing table and a numeric attribute that lives on\n the offering&apos;s spec, which usually differs between offerings.\n </Alert>\n\n <div className=\"grid grid-cols-3 gap-3\">\n <div>\n <label className=\"block text-xs font-medium mb-1\">Code</label>\n <Input\n value={chargeForm.code}\n onChange={(e) => setChargeForm({ ...chargeForm, code: e.target.value })}\n placeholder=\"e.g. mrc-monthly\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium mb-1\">Name</label>\n <Input\n value={chargeForm.name}\n onChange={(e) => setChargeForm({ ...chargeForm, name: e.target.value })}\n placeholder=\"e.g. Monthly Recurring\"\n />\n </div>\n <div>\n <label className=\"block text-xs font-medium mb-1\">Type</label>\n <Select\n value={chargeForm.chargeType}\n onValueChange={(value) => setChargeForm({ ...chargeForm, chargeType: value })}\n >\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"nrc\">NRC (Non-Recurring)</SelectItem>\n <SelectItem value=\"mrc\">MRC (Monthly Recurring)</SelectItem>\n <SelectItem value=\"usage\">Usage</SelectItem>\n </SelectContent>\n </Select>\n </div>\n </div>\n\n <div>\n <label className=\"block text-xs font-medium mb-1\">Description</label>\n <Input\n value={chargeForm.description}\n onChange={(e) => setChargeForm({ ...chargeForm, description: e.target.value })}\n placeholder=\"User-facing charge description\"\n />\n </div>\n\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <label className=\"text-xs font-medium\">Currency</label>\n <Input\n value={chargeCurrency}\n onChange={(e) => setChargeCurrency(e.target.value.toUpperCase())}\n maxLength={3}\n className=\"w-20\"\n />\n </div>\n <div className=\"rounded border\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"border-b bg-muted/30\">\n <th className=\"px-3 py-2 text-left font-medium text-xs\">Offering</th>\n <th className=\"px-3 py-2 text-left font-medium text-xs\">Price ({chargeCurrency})</th>\n </tr>\n </thead>\n <tbody>\n {bulkChargeTargets.map((o) => (\n <tr key={o.id} className=\"border-b last:border-0\">\n <td className=\"px-3 py-2 font-medium text-xs\">\n {o.name} <span className=\"text-muted-foreground font-mono\">({o.code})</span>\n </td>\n <td className=\"px-3 py-2\">\n <NumberInput\n value={chargePrices[o.id] === undefined || chargePrices[o.id] === '' ? null : Number(chargePrices[o.id])}\n onChange={(n) =>\n setChargePrices({ ...chargePrices, [o.id]: n == null ? '' : String(n) })\n }\n placeholder=\"0.00\"\n className=\"w-32\"\n />\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n\n <div className=\"flex gap-2 pt-1\">\n <Button\n type=\"button\"\n onClick={saveChargesForSelected}\n disabled={saving || !chargeForm.code || !chargeForm.name}\n >\n {saving\n ? 'Saving...'\n : `Create Charge for ${bulkChargeTargets.length} Offering${bulkChargeTargets.length > 1 ? 's' : ''}`}\n </Button>\n <Button type=\"button\" variant=\"outline\" onClick={() => setBulkChargeOpen(false)}>\n Cancel\n </Button>\n </div>\n </div>\n )\n\n const toolbarContent = (\n <>\n {error && <Alert variant=\"destructive\">{error}</Alert>}\n {bulkChargeForm}\n </>\n )\n\n return (\n <CpqListView<Offering>\n title={t('cpq.offerings.list.title', 'Product Offerings')}\n tableId=\"cpq.offerings.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.offerings.search.placeholder', 'Search offerings...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/offerings/new\">{t('cpq.offerings.add', 'New Offering')}</a>\n </Button>\n }\n bulkActions={bulkActions}\n crud={{\n endpoint: '/api/cpq/product-offerings',\n entityName: t('cpq.offerings.entityName', 'offering'),\n editHref: (row) => `/backend/cpq/offerings/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/offerings/${row.id}`)}\n toolbarContent={toolbarContent}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.offerings.empty',\n 'No product offerings found. Create one to define a sellable variant of a product specification.',\n )}\n </div>\n }\n />\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/orders/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n formatStatusLabel,\n orderCpqStatusMap,\n type OrderCpqStatus,\n} from '../../../components/statusMaps'\n\ntype OrderConfig = {\n id: string\n orderId: string\n orderNumber: string | null\n customerId: string\n cpqStatus: string\n currencyCode: string\n pricingSummary: {\n nrcTotal?: number\n mrcTotal?: number\n } | null\n createdAt: string\n activatedAt: string | null\n}\n\nconst PAGE_SIZE = 50\n\nfunction fmt(amount: number | undefined, currency: string): string {\n if (amount == null || amount === 0) return '\u2014'\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.cpqStatus === 'string' && values.cpqStatus) {\n params.set('cpqStatus', values.cpqStatus)\n }\n if (typeof values.currencyCode === 'string' && values.currencyCode.trim()) {\n params.set('currencyCode', values.currencyCode.trim().toUpperCase())\n }\n}\n\nexport default function CpqOrdersListPage() {\n const router = useRouter()\n const t = useT()\n\n const data = useCpqListData<OrderConfig>({\n endpoint: '/api/cpq/orders',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.orders.list.error.load', 'Failed to load orders'),\n })\n\n const statusOptions = React.useMemo(\n () => [\n { value: 'draft', label: 'Draft' },\n { value: 'pending_activation', label: 'Pending Activation' },\n { value: 'active', label: 'Active' },\n { value: 'fulfilled', label: 'Fulfilled' },\n { value: 'cancelled', label: 'Cancelled' },\n ],\n [],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'cpqStatus',\n label: t('cpq.orders.filters.status', 'Status'),\n type: 'select',\n options: statusOptions,\n },\n {\n id: 'currencyCode',\n label: t('cpq.orders.filters.currency', 'Currency'),\n type: 'text',\n },\n ],\n [statusOptions, t],\n )\n\n const columns = React.useMemo<ColumnDef<OrderConfig>[]>(\n () => [\n {\n id: 'orderNumber',\n header: t('cpq.orders.table.order', 'Order'),\n cell: ({ row }) => (\n <span className=\"text-sm font-medium text-primary\">\n {row.original.orderNumber ?? `${row.original.orderId?.slice(0, 8)}\u2026`}\n </span>\n ),\n },\n {\n accessorKey: 'cpqStatus',\n header: t('cpq.orders.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={orderCpqStatusMap[row.original.cpqStatus as OrderCpqStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.cpqStatus)}\n </Tag>\n ),\n },\n {\n accessorKey: 'currencyCode',\n header: t('cpq.orders.table.currency', 'Currency'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.currencyCode}</span>,\n },\n {\n id: 'nrcTotal',\n header: t('cpq.orders.table.nrcTotal', 'NRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.nrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n id: 'mrcTotal',\n header: t('cpq.orders.table.mrcTotal', 'MRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.mrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('cpq.orders.table.createdAt', 'Created'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {new Date(row.original.createdAt).toLocaleDateString()}\n </span>\n ),\n },\n {\n accessorKey: 'activatedAt',\n header: t('cpq.orders.table.activatedAt', 'Activated'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {row.original.activatedAt ? new Date(row.original.activatedAt).toLocaleDateString() : '\u2014'}\n </span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<OrderConfig>\n title={t('cpq.orders.list.title', 'CPQ Orders')}\n tableId=\"cpq.orders.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.orders.search.placeholder', 'Search by order / customer id...')}\n onRowClick={(row) => router.push(`/backend/cpq/orders/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'open',\n label: t('cpq.orders.actions.open', 'Open'),\n href: `/backend/cpq/orders/${row.id}`,\n },\n ]}\n />\n )}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.orders.empty',\n 'No CPQ orders found. Convert an accepted quote to create an order.',\n )}\n </div>\n }\n />\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n formatStatusLabel,\n orderCpqStatusMap,\n type OrderCpqStatus,\n} from '../../../components/statusMaps'\n\ntype OrderConfig = {\n id: string\n orderId: string\n orderNumber: string | null\n customerId: string\n cpqStatus: string\n currencyCode: string\n pricingSummary: {\n nrcTotal?: number\n mrcTotal?: number\n } | null\n createdAt: string\n activatedAt: string | null\n}\n\nconst PAGE_SIZE = 50\n\nfunction fmt(amount: number | undefined, currency: string): string {\n if (amount == null || amount === 0) return '\u2014'\n return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount)\n}\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.cpqStatus === 'string' && values.cpqStatus) {\n params.set('cpqStatus', values.cpqStatus)\n }\n if (typeof values.currencyCode === 'string' && values.currencyCode.trim()) {\n params.set('currencyCode', values.currencyCode.trim().toUpperCase())\n }\n}\n\nexport default function CpqOrdersListPage() {\n const router = useRouter()\n const t = useT()\n\n const data = useCpqListData<OrderConfig>({\n endpoint: '/api/cpq/orders',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.orders.list.error.load', 'Failed to load orders'),\n })\n\n const statusOptions = React.useMemo(\n () => [\n { value: 'draft', label: 'Draft' },\n { value: 'pending_activation', label: 'Pending Activation' },\n { value: 'active', label: 'Active' },\n { value: 'fulfilled', label: 'Fulfilled' },\n { value: 'cancelled', label: 'Cancelled' },\n ],\n [],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'cpqStatus',\n label: t('cpq.orders.filters.status', 'Status'),\n type: 'select',\n options: statusOptions,\n },\n {\n id: 'currencyCode',\n label: t('cpq.orders.filters.currency', 'Currency'),\n type: 'text',\n },\n ],\n [statusOptions, t],\n )\n\n const columns = React.useMemo<ColumnDef<OrderConfig>[]>(\n () => [\n {\n id: 'orderNumber',\n header: t('cpq.orders.table.order', 'Order'),\n cell: ({ row }) => (\n <span className=\"text-sm font-medium text-primary\">\n {row.original.orderNumber ?? `${row.original.orderId?.slice(0, 8)}\u2026`}\n </span>\n ),\n },\n {\n accessorKey: 'cpqStatus',\n header: t('cpq.orders.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={orderCpqStatusMap[row.original.cpqStatus as OrderCpqStatus] ?? 'neutral'} dot>\n {formatStatusLabel(row.original.cpqStatus)}\n </Tag>\n ),\n },\n {\n accessorKey: 'currencyCode',\n header: t('cpq.orders.table.currency', 'Currency'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.currencyCode}</span>,\n },\n {\n id: 'nrcTotal',\n header: t('cpq.orders.table.nrcTotal', 'NRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.nrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n id: 'mrcTotal',\n header: t('cpq.orders.table.mrcTotal', 'MRC Total'),\n cell: ({ row }) => (\n <span className=\"font-mono text-xs\">\n {fmt(row.original.pricingSummary?.mrcTotal, row.original.currencyCode)}\n </span>\n ),\n },\n {\n accessorKey: 'createdAt',\n header: t('cpq.orders.table.createdAt', 'Created'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {new Date(row.original.createdAt).toLocaleDateString()}\n </span>\n ),\n },\n {\n accessorKey: 'activatedAt',\n header: t('cpq.orders.table.activatedAt', 'Activated'),\n cell: ({ row }) => (\n <span className=\"text-xs text-muted-foreground\">\n {row.original.activatedAt ? new Date(row.original.activatedAt).toLocaleDateString() : '\u2014'}\n </span>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<OrderConfig>\n title={t('cpq.orders.list.title', 'CPQ Orders')}\n tableId=\"cpq.orders.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.orders.search.placeholder', 'Search by order / customer id...')}\n onRowClick={(row) => router.push(`/backend/cpq/orders/${row.id}`)}\n rowActions={(row) => (\n <RowActions\n items={[\n {\n id: 'open',\n label: t('cpq.orders.actions.open', 'Open'),\n href: `/backend/cpq/orders/${row.id}`,\n },\n ]}\n />\n )}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t(\n 'cpq.orders.empty',\n 'No CPQ orders found. Convert an accepted quote to create an order.',\n )}\n </div>\n }\n />\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/price-rules/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n PriceRuleForm,\n RULE_TYPE_LABELS,\n emptyPriceRuleForm,\n priceRuleFormToPayload,\n type PriceRule,\n type PriceRuleFormData,\n type ProductOffering,\n} from './PriceRuleForm'\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.ruleType === 'string' && values.ruleType) {\n params.set('ruleType', values.ruleType)\n }\n if (typeof values.chargeTypeFilter === 'string' && values.chargeTypeFilter) {\n params.set('chargeTypeFilter', values.chargeTypeFilter)\n }\n if (values.globalOnly === true) params.set('globalOnly', 'true')\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nfunction formatValue(rule: PriceRule) {\n if (rule.ruleType.includes('percent')) return `${rule.value}%`\n if (rule.ruleType === 'price_override') return `= ${rule.value}`\n return `${rule.value}`\n}\n\nexport default function PriceRulesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PriceRule>({\n endpoint: '/api/cpq/price-rules',\n pageSize: PAGE_SIZE,\n initialSorting: [{ id: 'sortOrder', desc: false }],\n buildFilterParams,\n loadErrorMessage: t('cpq.priceRules.list.error.load', 'Failed to load price rules'),\n })\n\n const [showForm, setShowForm] = React.useState(false)\n const [form, setForm] = React.useState<PriceRuleFormData>(emptyPriceRuleForm)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [offerings, setOfferings] = React.useState<ProductOffering[]>([])\n\n React.useEffect(() => {\n fetch('/api/cpq/product-offerings?pageSize=100')\n .then((r) => r.json())\n .then((d) => setOfferings(d.items ?? []))\n .catch(() => {})\n }, [])\n\n const ruleTypeOptions = React.useMemo(\n () => Object.entries(RULE_TYPE_LABELS).map(([value, label]) => ({ value, label })),\n [],\n )\n\n const chargeTypeOptions = React.useMemo(\n () => [\n { value: 'nrc', label: 'NRC' },\n { value: 'mrc', label: 'MRC' },\n { value: 'usage', label: 'Usage' },\n ],\n [],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'ruleType',\n label: t('cpq.priceRules.filters.ruleType', 'Rule Type'),\n type: 'select',\n options: ruleTypeOptions,\n },\n {\n id: 'chargeTypeFilter',\n label: t('cpq.priceRules.filters.chargeType', 'Charge Type'),\n type: 'select',\n options: chargeTypeOptions,\n },\n {\n id: 'globalOnly',\n label: t('cpq.priceRules.filters.globalOnly', 'Global only'),\n type: 'checkbox',\n },\n {\n id: 'isActive',\n label: t('cpq.priceRules.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [chargeTypeOptions, ruleTypeOptions, t],\n )\n\n const openCreate = React.useCallback(() => {\n setForm(emptyPriceRuleForm)\n setError(null)\n setShowForm(true)\n }, [])\n\n const handleSave = React.useCallback(async () => {\n setSaving(true)\n setError(null)\n try {\n const payload = priceRuleFormToPayload(form)\n const res = await fetch('/api/cpq/price-rules', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const err = await res.json()\n throw new Error(err.details ? JSON.stringify(err.details) : err.error ?? 'Failed to create')\n }\n setShowForm(false)\n data.reload()\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Unknown error')\n } finally {\n setSaving(false)\n }\n }, [data, form])\n\n const columns = React.useMemo<ColumnDef<PriceRule>[]>(\n () => [\n {\n accessorKey: 'sortOrder',\n header: t('cpq.priceRules.table.order', 'Order'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.sortOrder}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.priceRules.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.priceRules.table.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'ruleType',\n header: t('cpq.priceRules.table.type', 'Type'),\n cell: ({ row }) => (\n <span>{RULE_TYPE_LABELS[row.original.ruleType] ?? row.original.ruleType}</span>\n ),\n },\n {\n id: 'value',\n header: t('cpq.priceRules.table.value', 'Value'),\n cell: ({ row }) => <span className=\"font-mono\">{formatValue(row.original)}</span>,\n },\n {\n id: 'scope',\n header: t('cpq.priceRules.table.scope', 'Scope'),\n cell: ({ row }) => (\n <span className=\"text-xs\">\n {row.original.productOfferingId\n ? row.original.productOfferingName ?? 'Product-scoped'\n : 'Global'}\n </span>\n ),\n },\n {\n id: 'filter',\n header: t('cpq.priceRules.table.filter', 'Filter'),\n cell: ({ row }) => {\n const rule = row.original\n const parts: string[] = []\n if (rule.chargeTypeFilter) parts.push(rule.chargeTypeFilter)\n if (rule.chargeCodeFilter) parts.push(rule.chargeCodeFilter)\n const cond = rule.applicabilityCondition as\n | { attribute?: string; operator?: string; value?: string }\n | null\n if (cond?.attribute) {\n const op = cond.operator === 'neq' ? '\u2260' : '='\n parts.push(`${cond.attribute} ${op} ${cond.value ?? '\"\"'}`)\n }\n return (\n <span className=\"text-xs text-muted-foreground\">\n {parts.length > 0 ? parts.join(' / ') : '\u2014'}\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.priceRules.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? 'Active' : 'Inactive'}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<PriceRule>\n title={t('cpq.priceRules.list.title', 'Price Rules')}\n tableId=\"cpq.price-rules.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.priceRules.search.placeholder', 'Search price rules...')}\n actions={\n <Button type=\"button\" onClick={openCreate}>\n {t('cpq.priceRules.create', 'Create Rule')}\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/price-rules',\n entityName: t('cpq.priceRules.entityName', 'price rule'),\n editHref: (row) => `/backend/cpq/price-rules/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/price-rules/${row.id}`)}\n toolbarContent={\n showForm ? (\n <PriceRuleForm\n editingId={null}\n form={form}\n onFormChange={setForm}\n offerings={offerings}\n saving={saving}\n error={error}\n onSave={handleSave}\n onCancel={() => setShowForm(false)}\n />\n ) : null\n }\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n No price rules found. Click &quot;Create Rule&quot; to add one.\n </div>\n }\n />\n )\n}\n"],
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport { useRouter } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\nimport {\n PriceRuleForm,\n RULE_TYPE_LABELS,\n emptyPriceRuleForm,\n priceRuleFormToPayload,\n type PriceRule,\n type PriceRuleFormData,\n type ProductOffering,\n} from './PriceRuleForm'\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (typeof values.ruleType === 'string' && values.ruleType) {\n params.set('ruleType', values.ruleType)\n }\n if (typeof values.chargeTypeFilter === 'string' && values.chargeTypeFilter) {\n params.set('chargeTypeFilter', values.chargeTypeFilter)\n }\n if (values.globalOnly === true) params.set('globalOnly', 'true')\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nfunction formatValue(rule: PriceRule) {\n if (rule.ruleType.includes('percent')) return `${rule.value}%`\n if (rule.ruleType === 'price_override') return `= ${rule.value}`\n return `${rule.value}`\n}\n\nexport default function PriceRulesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PriceRule>({\n endpoint: '/api/cpq/price-rules',\n pageSize: PAGE_SIZE,\n initialSorting: [{ id: 'sortOrder', desc: false }],\n buildFilterParams,\n loadErrorMessage: t('cpq.priceRules.list.error.load', 'Failed to load price rules'),\n })\n\n const [showForm, setShowForm] = React.useState(false)\n const [form, setForm] = React.useState<PriceRuleFormData>(emptyPriceRuleForm)\n const [saving, setSaving] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n const [offerings, setOfferings] = React.useState<ProductOffering[]>([])\n\n React.useEffect(() => {\n fetch('/api/cpq/product-offerings?pageSize=100')\n .then((r) => r.json())\n .then((d) => setOfferings(d.items ?? []))\n .catch(() => {})\n }, [])\n\n const ruleTypeOptions = React.useMemo(\n () => Object.entries(RULE_TYPE_LABELS).map(([value, label]) => ({ value, label })),\n [],\n )\n\n const chargeTypeOptions = React.useMemo(\n () => [\n { value: 'nrc', label: 'NRC' },\n { value: 'mrc', label: 'MRC' },\n { value: 'usage', label: 'Usage' },\n ],\n [],\n )\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'ruleType',\n label: t('cpq.priceRules.filters.ruleType', 'Rule Type'),\n type: 'select',\n options: ruleTypeOptions,\n },\n {\n id: 'chargeTypeFilter',\n label: t('cpq.priceRules.filters.chargeType', 'Charge Type'),\n type: 'select',\n options: chargeTypeOptions,\n },\n {\n id: 'globalOnly',\n label: t('cpq.priceRules.filters.globalOnly', 'Global only'),\n type: 'checkbox',\n },\n {\n id: 'isActive',\n label: t('cpq.priceRules.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [chargeTypeOptions, ruleTypeOptions, t],\n )\n\n const openCreate = React.useCallback(() => {\n setForm(emptyPriceRuleForm)\n setError(null)\n setShowForm(true)\n }, [])\n\n const handleSave = React.useCallback(async () => {\n setSaving(true)\n setError(null)\n try {\n const payload = priceRuleFormToPayload(form)\n const res = await fetch('/api/cpq/price-rules', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n })\n if (!res.ok) {\n const err = await res.json()\n throw new Error(err.details ? JSON.stringify(err.details) : err.error ?? 'Failed to create')\n }\n setShowForm(false)\n data.reload()\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Unknown error')\n } finally {\n setSaving(false)\n }\n }, [data, form])\n\n const columns = React.useMemo<ColumnDef<PriceRule>[]>(\n () => [\n {\n accessorKey: 'sortOrder',\n header: t('cpq.priceRules.table.order', 'Order'),\n cell: ({ row }) => <span className=\"text-muted-foreground\">{row.original.sortOrder}</span>,\n },\n {\n accessorKey: 'name',\n header: t('cpq.priceRules.table.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.priceRules.table.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n accessorKey: 'ruleType',\n header: t('cpq.priceRules.table.type', 'Type'),\n cell: ({ row }) => (\n <span>{RULE_TYPE_LABELS[row.original.ruleType] ?? row.original.ruleType}</span>\n ),\n },\n {\n id: 'value',\n header: t('cpq.priceRules.table.value', 'Value'),\n cell: ({ row }) => <span className=\"font-mono\">{formatValue(row.original)}</span>,\n },\n {\n id: 'scope',\n header: t('cpq.priceRules.table.scope', 'Scope'),\n cell: ({ row }) => (\n <span className=\"text-xs\">\n {row.original.productOfferingId\n ? row.original.productOfferingName ?? 'Product-scoped'\n : 'Global'}\n </span>\n ),\n },\n {\n id: 'filter',\n header: t('cpq.priceRules.table.filter', 'Filter'),\n cell: ({ row }) => {\n const rule = row.original\n const parts: string[] = []\n if (rule.chargeTypeFilter) parts.push(rule.chargeTypeFilter)\n if (rule.chargeCodeFilter) parts.push(rule.chargeCodeFilter)\n const cond = rule.applicabilityCondition as\n | { attribute?: string; operator?: string; value?: string }\n | null\n if (cond?.attribute) {\n const op = cond.operator === 'neq' ? '\u2260' : '='\n parts.push(`${cond.attribute} ${op} ${cond.value ?? '\"\"'}`)\n }\n return (\n <span className=\"text-xs text-muted-foreground\">\n {parts.length > 0 ? parts.join(' / ') : '\u2014'}\n </span>\n )\n },\n meta: { truncate: true, maxWidth: 240 },\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.priceRules.table.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? 'Active' : 'Inactive'}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<PriceRule>\n title={t('cpq.priceRules.list.title', 'Price Rules')}\n tableId=\"cpq.price-rules.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.priceRules.search.placeholder', 'Search price rules...')}\n actions={\n <Button type=\"button\" onClick={openCreate}>\n {t('cpq.priceRules.create', 'Create Rule')}\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/price-rules',\n entityName: t('cpq.priceRules.entityName', 'price rule'),\n editHref: (row) => `/backend/cpq/price-rules/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/price-rules/${row.id}`)}\n toolbarContent={\n showForm ? (\n <PriceRuleForm\n editingId={null}\n form={form}\n onFormChange={setForm}\n offerings={offerings}\n saving={saving}\n error={error}\n onSave={handleSave}\n onCancel={() => setShowForm(false)}\n />\n ) : null\n }\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n No price rules found. Click &quot;Create Rule&quot; to add one.\n </div>\n }\n />\n )\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/cpq/backend/cpq/pricing/page.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 { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\n\ntype PricingTable = {\n id: string\n code: string\n name: string\n priceColumns: Array<{ key: string; label: string }>\n currencyCodeList: string[]\n isActive: boolean\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function PricingTablesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PricingTable>({\n endpoint: '/api/cpq/pricing-tables',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.pricing.list.error.load', 'Failed to load pricing tables'),\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'isActive',\n label: t('cpq.pricing.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<PricingTable>[]>(\n () => [\n {\n accessorKey: 'name',\n header: t('cpq.pricing.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.pricing.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n id: 'priceColumns',\n header: t('cpq.pricing.priceColumns', 'Price Columns'),\n cell: ({ row }) => <span>{row.original.priceColumns?.length ?? 0}</span>,\n },\n {\n id: 'currencies',\n header: t('cpq.pricing.currencies', 'Currencies'),\n cell: ({ row }) => <span>{row.original.currencyCodeList?.join(', ') ?? '\u2014'}</span>,\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.pricing.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<PricingTable>\n title={t('cpq.pricing.list.title', 'Pricing Tables')}\n tableId=\"cpq.pricing.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.pricing.search.placeholder', 'Search pricing tables...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/pricing/new\">{t('cpq.pricing.add', 'New Pricing Table')}</a>\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/pricing-tables',\n entityName: t('cpq.pricing.entityName', 'pricing table'),\n editHref: (row) => `/backend/cpq/pricing/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/pricing/${row.id}`)}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.pricing.empty', 'No pricing tables found.')}\n </div>\n }\n />\n )\n}\n"],
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 { Button } from '@open-mercato/ui/primitives/button'\nimport { Tag } from '@open-mercato/ui/primitives/tag'\nimport type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'\nimport { CpqListView, useCpqListData } from '../../../components/CpqListView'\n\ntype PricingTable = {\n id: string\n code: string\n name: string\n priceColumns: Array<{ key: string; label: string }>\n currencyCodeList: string[]\n isActive: boolean\n}\n\nconst PAGE_SIZE = 50\n\nfunction buildFilterParams(values: FilterValues, params: URLSearchParams) {\n if (values.isActive === true) params.set('isActive', 'true')\n if (values.isActive === false) params.set('isActive', 'false')\n}\n\nexport default function PricingTablesPage() {\n const t = useT()\n const router = useRouter()\n\n const data = useCpqListData<PricingTable>({\n endpoint: '/api/cpq/pricing-tables',\n pageSize: PAGE_SIZE,\n buildFilterParams,\n loadErrorMessage: t('cpq.pricing.list.error.load', 'Failed to load pricing tables'),\n })\n\n const filters = React.useMemo<FilterDef[]>(\n () => [\n {\n id: 'isActive',\n label: t('cpq.pricing.filters.isActive', 'Active'),\n type: 'checkbox',\n },\n ],\n [t],\n )\n\n const columns = React.useMemo<ColumnDef<PricingTable>[]>(\n () => [\n {\n accessorKey: 'name',\n header: t('cpq.pricing.name', 'Name'),\n cell: ({ row }) => <span className=\"font-medium\">{row.original.name}</span>,\n },\n {\n accessorKey: 'code',\n header: t('cpq.pricing.code', 'Code'),\n cell: ({ row }) => <span className=\"font-mono text-xs\">{row.original.code}</span>,\n },\n {\n id: 'priceColumns',\n header: t('cpq.pricing.priceColumns', 'Price Columns'),\n cell: ({ row }) => <span>{row.original.priceColumns?.length ?? 0}</span>,\n },\n {\n id: 'currencies',\n header: t('cpq.pricing.currencies', 'Currencies'),\n cell: ({ row }) => <span>{row.original.currencyCodeList?.join(', ') ?? '\u2014'}</span>,\n },\n {\n accessorKey: 'isActive',\n header: t('cpq.pricing.status', 'Status'),\n cell: ({ row }) => (\n <Tag variant={row.original.isActive ? 'success' : 'neutral'} dot>\n {row.original.isActive ? t('common.active', 'Active') : t('common.inactive', 'Inactive')}\n </Tag>\n ),\n },\n ],\n [t],\n )\n\n return (\n <CpqListView<PricingTable>\n title={t('cpq.pricing.list.title', 'Pricing Tables')}\n tableId=\"cpq.pricing.list\"\n data={data}\n columns={columns}\n filters={filters}\n pageSize={PAGE_SIZE}\n searchPlaceholder={t('cpq.pricing.search.placeholder', 'Search pricing tables...')}\n actions={\n <Button asChild>\n <a href=\"/backend/cpq/pricing/new\">{t('cpq.pricing.add', 'New Pricing Table')}</a>\n </Button>\n }\n crud={{\n endpoint: '/api/cpq/pricing-tables',\n entityName: t('cpq.pricing.entityName', 'pricing table'),\n editHref: (row) => `/backend/cpq/pricing/${row.id}`,\n }}\n onRowClick={(row) => router.push(`/backend/cpq/pricing/${row.id}`)}\n emptyState={\n <div className=\"rounded-lg border bg-card p-6 text-center text-sm text-muted-foreground\">\n {t('cpq.pricing.empty', 'No pricing tables found.')}\n </div>\n }\n />\n )\n}\n"],
5
5
  "mappings": ";AAqD2B;AApD3B,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAE1B,SAAS,cAAc;AACvB,SAAS,WAAW;AAEpB,SAAS,aAAa,sBAAsB;AAW5C,MAAM,YAAY;AAElB,SAAS,kBAAkB,QAAsB,QAAyB;AACxE,MAAI,OAAO,aAAa,KAAM,QAAO,IAAI,YAAY,MAAM;AAC3D,MAAI,OAAO,aAAa,MAAO,QAAO,IAAI,YAAY,OAAO;AAC/D;AAEe,SAAR,oBAAqC;AAC1C,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AAEzB,QAAM,OAAO,eAA6B;AAAA,IACxC,UAAU;AAAA,IACV,UAAU;AAAA,IACV;AAAA,IACA,kBAAkB,EAAE,+BAA+B,+BAA+B;AAAA,EACpF,CAAC;AAED,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,IAAI;AAAA,QACJ,OAAO,EAAE,gCAAgC,QAAQ;AAAA,QACjD,MAAM;AAAA,MACR;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,QAAM,UAAU,MAAM;AAAA,IACpB,MAAM;AAAA,MACJ;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,oBAAoB,MAAM;AAAA,QACpC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,eAAe,cAAI,SAAS,MAAK;AAAA,MACtE;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,oBAAoB,MAAM;AAAA,QACpC,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAK,WAAU,qBAAqB,cAAI,SAAS,MAAK;AAAA,MAC5E;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,4BAA4B,eAAe;AAAA,QACrD,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,cAAc,UAAU,GAAE;AAAA,MACnE;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,QAAQ,EAAE,0BAA0B,YAAY;AAAA,QAChD,MAAM,CAAC,EAAE,IAAI,MAAM,oBAAC,UAAM,cAAI,SAAS,kBAAkB,KAAK,IAAI,KAAK,UAAI;AAAA,MAC7E;AAAA,MACA;AAAA,QACE,aAAa;AAAA,QACb,QAAQ,EAAE,sBAAsB,QAAQ;AAAA,QACxC,MAAM,CAAC,EAAE,IAAI,MACX,oBAAC,OAAI,SAAS,IAAI,SAAS,WAAW,YAAY,WAAW,KAAG,MAC7D,cAAI,SAAS,WAAW,EAAE,iBAAiB,QAAQ,IAAI,EAAE,mBAAmB,UAAU,GACzF;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAC,CAAC;AAAA,EACJ;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,EAAE,0BAA0B,gBAAgB;AAAA,MACnD,SAAQ;AAAA,MACR;AAAA,MACA;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,mBAAmB,EAAE,kCAAkC,0BAA0B;AAAA,MACjF,SACE,oBAAC,UAAO,SAAO,MACb,8BAAC,OAAE,MAAK,4BAA4B,YAAE,mBAAmB,mBAAmB,GAAE,GAChF;AAAA,MAEF,MAAM;AAAA,QACJ,UAAU;AAAA,QACV,YAAY,EAAE,0BAA0B,eAAe;AAAA,QACvD,UAAU,CAAC,QAAQ,wBAAwB,IAAI,EAAE;AAAA,MACnD;AAAA,MACA,YAAY,CAAC,QAAQ,OAAO,KAAK,wBAAwB,IAAI,EAAE,EAAE;AAAA,MACjE,YACE,oBAAC,SAAI,WAAU,2EACZ,YAAE,qBAAqB,0BAA0B,GACpD;AAAA;AAAA,EAEJ;AAEJ;",
6
6
  "names": []
7
7
  }