@dainamite/cpq 0.2.2

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 (504) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION.md +236 -0
  4. package/README.md +108 -0
  5. package/dist/modules/cpq/acl.js +30 -0
  6. package/dist/modules/cpq/acl.js.map +7 -0
  7. package/dist/modules/cpq/api/_helpers/handleArcError.js +37 -0
  8. package/dist/modules/cpq/api/_helpers/handleArcError.js.map +7 -0
  9. package/dist/modules/cpq/api/attribute-options/route.js +47 -0
  10. package/dist/modules/cpq/api/attribute-options/route.js.map +7 -0
  11. package/dist/modules/cpq/api/bundle-slots/route.js +101 -0
  12. package/dist/modules/cpq/api/bundle-slots/route.js.map +7 -0
  13. package/dist/modules/cpq/api/bundle-tree/route.js +28 -0
  14. package/dist/modules/cpq/api/bundle-tree/route.js.map +7 -0
  15. package/dist/modules/cpq/api/context.js +29 -0
  16. package/dist/modules/cpq/api/context.js.map +7 -0
  17. package/dist/modules/cpq/api/inventory/assets/route.js +113 -0
  18. package/dist/modules/cpq/api/inventory/assets/route.js.map +7 -0
  19. package/dist/modules/cpq/api/inventory/assets/status/route.js +35 -0
  20. package/dist/modules/cpq/api/inventory/assets/status/route.js.map +7 -0
  21. package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js +30 -0
  22. package/dist/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.js.map +7 -0
  23. package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js +31 -0
  24. package/dist/modules/cpq/api/inventory/customer/[customerId]/route.js.map +7 -0
  25. package/dist/modules/cpq/api/inventory/subscription-items/route.js +99 -0
  26. package/dist/modules/cpq/api/inventory/subscription-items/route.js.map +7 -0
  27. package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js +57 -0
  28. package/dist/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.js.map +7 -0
  29. package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js +62 -0
  30. package/dist/modules/cpq/api/inventory/subscriptions/expiring/route.js.map +7 -0
  31. package/dist/modules/cpq/api/inventory/subscriptions/route.js +116 -0
  32. package/dist/modules/cpq/api/inventory/subscriptions/route.js.map +7 -0
  33. package/dist/modules/cpq/api/inventory/subscriptions/status/route.js +35 -0
  34. package/dist/modules/cpq/api/inventory/subscriptions/status/route.js.map +7 -0
  35. package/dist/modules/cpq/api/offering-components/route.js +91 -0
  36. package/dist/modules/cpq/api/offering-components/route.js.map +7 -0
  37. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js +39 -0
  38. package/dist/modules/cpq/api/orders/[orderId]/activate/route.js.map +7 -0
  39. package/dist/modules/cpq/api/orders/[orderId]/route.js +28 -0
  40. package/dist/modules/cpq/api/orders/[orderId]/route.js.map +7 -0
  41. package/dist/modules/cpq/api/orders/[orderId]/status/route.js +38 -0
  42. package/dist/modules/cpq/api/orders/[orderId]/status/route.js.map +7 -0
  43. package/dist/modules/cpq/api/orders/route.js +71 -0
  44. package/dist/modules/cpq/api/orders/route.js.map +7 -0
  45. package/dist/modules/cpq/api/price-rules/route.js +180 -0
  46. package/dist/modules/cpq/api/price-rules/route.js.map +7 -0
  47. package/dist/modules/cpq/api/pricing-table-entries/route.js +142 -0
  48. package/dist/modules/cpq/api/pricing-table-entries/route.js.map +7 -0
  49. package/dist/modules/cpq/api/pricing-tables/route.js +179 -0
  50. package/dist/modules/cpq/api/pricing-tables/route.js.map +7 -0
  51. package/dist/modules/cpq/api/product-attributes/route.js +160 -0
  52. package/dist/modules/cpq/api/product-attributes/route.js.map +7 -0
  53. package/dist/modules/cpq/api/product-charges/route.js +166 -0
  54. package/dist/modules/cpq/api/product-charges/route.js.map +7 -0
  55. package/dist/modules/cpq/api/product-offerings/route.js +294 -0
  56. package/dist/modules/cpq/api/product-offerings/route.js.map +7 -0
  57. package/dist/modules/cpq/api/product-relationships/route.js +144 -0
  58. package/dist/modules/cpq/api/product-relationships/route.js.map +7 -0
  59. package/dist/modules/cpq/api/product-specifications/route.js +292 -0
  60. package/dist/modules/cpq/api/product-specifications/route.js.map +7 -0
  61. package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js +71 -0
  62. package/dist/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.js.map +7 -0
  63. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js +29 -0
  64. package/dist/modules/cpq/api/quotes/[quoteId]/clone/route.js.map +7 -0
  65. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js +54 -0
  66. package/dist/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.js.map +7 -0
  67. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js +27 -0
  68. package/dist/modules/cpq/api/quotes/[quoteId]/items/route.js.map +7 -0
  69. package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js +72 -0
  70. package/dist/modules/cpq/api/quotes/[quoteId]/merge-meta/route.js.map +7 -0
  71. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js +35 -0
  72. package/dist/modules/cpq/api/quotes/[quoteId]/recalculate/route.js.map +7 -0
  73. package/dist/modules/cpq/api/quotes/[quoteId]/route.js +102 -0
  74. package/dist/modules/cpq/api/quotes/[quoteId]/route.js.map +7 -0
  75. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js +27 -0
  76. package/dist/modules/cpq/api/quotes/[quoteId]/status/route.js.map +7 -0
  77. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js +79 -0
  78. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.js.map +7 -0
  79. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js +95 -0
  80. package/dist/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.js.map +7 -0
  81. package/dist/modules/cpq/api/quotes/attributes/route.js +52 -0
  82. package/dist/modules/cpq/api/quotes/attributes/route.js.map +7 -0
  83. package/dist/modules/cpq/api/quotes/from-subscription/route.js +72 -0
  84. package/dist/modules/cpq/api/quotes/from-subscription/route.js.map +7 -0
  85. package/dist/modules/cpq/api/quotes/price/route.js +69 -0
  86. package/dist/modules/cpq/api/quotes/price/route.js.map +7 -0
  87. package/dist/modules/cpq/api/quotes/route.js +97 -0
  88. package/dist/modules/cpq/api/quotes/route.js.map +7 -0
  89. package/dist/modules/cpq/api/quotes/validate-relationships/route.js +86 -0
  90. package/dist/modules/cpq/api/quotes/validate-relationships/route.js.map +7 -0
  91. package/dist/modules/cpq/api/resolveQuotingService.js +7 -0
  92. package/dist/modules/cpq/api/resolveQuotingService.js.map +7 -0
  93. package/dist/modules/cpq/api/wizards/resolve/route.js +38 -0
  94. package/dist/modules/cpq/api/wizards/resolve/route.js.map +7 -0
  95. package/dist/modules/cpq/api/wizards/route.js +111 -0
  96. package/dist/modules/cpq/api/wizards/route.js.map +7 -0
  97. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js +136 -0
  98. package/dist/modules/cpq/backend/cpq/inventory/AssetsTable.js.map +7 -0
  99. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js +185 -0
  100. package/dist/modules/cpq/backend/cpq/inventory/SubscriptionsTable.js.map +7 -0
  101. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js +343 -0
  102. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.js.map +7 -0
  103. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js +13 -0
  104. package/dist/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.js.map +7 -0
  105. package/dist/modules/cpq/backend/cpq/inventory/page.js +35 -0
  106. package/dist/modules/cpq/backend/cpq/inventory/page.js.map +7 -0
  107. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js +18 -0
  108. package/dist/modules/cpq/backend/cpq/inventory/page.meta.js.map +7 -0
  109. package/dist/modules/cpq/backend/cpq/inventory/shared.js +20 -0
  110. package/dist/modules/cpq/backend/cpq/inventory/shared.js.map +7 -0
  111. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js +794 -0
  112. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.js.map +7 -0
  113. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js +13 -0
  114. package/dist/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.js.map +7 -0
  115. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js +917 -0
  116. package/dist/modules/cpq/backend/cpq/offerings/[id]/page.js.map +7 -0
  117. package/dist/modules/cpq/backend/cpq/offerings/page.js +405 -0
  118. package/dist/modules/cpq/backend/cpq/offerings/page.js.map +7 -0
  119. package/dist/modules/cpq/backend/cpq/offerings/page.meta.js +18 -0
  120. package/dist/modules/cpq/backend/cpq/offerings/page.meta.js.map +7 -0
  121. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js +677 -0
  122. package/dist/modules/cpq/backend/cpq/orders/[id]/page.js.map +7 -0
  123. package/dist/modules/cpq/backend/cpq/orders/page.js +134 -0
  124. package/dist/modules/cpq/backend/cpq/orders/page.js.map +7 -0
  125. package/dist/modules/cpq/backend/cpq/orders/page.meta.js +18 -0
  126. package/dist/modules/cpq/backend/cpq/orders/page.meta.js.map +7 -0
  127. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js +272 -0
  128. package/dist/modules/cpq/backend/cpq/price-rules/PriceRuleForm.js.map +7 -0
  129. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js +407 -0
  130. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.js.map +7 -0
  131. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js +13 -0
  132. package/dist/modules/cpq/backend/cpq/price-rules/[id]/page.meta.js.map +7 -0
  133. package/dist/modules/cpq/backend/cpq/price-rules/page.js +211 -0
  134. package/dist/modules/cpq/backend/cpq/price-rules/page.js.map +7 -0
  135. package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js +18 -0
  136. package/dist/modules/cpq/backend/cpq/price-rules/page.meta.js.map +7 -0
  137. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js +275 -0
  138. package/dist/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.js.map +7 -0
  139. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js +112 -0
  140. package/dist/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.js.map +7 -0
  141. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js +492 -0
  142. package/dist/modules/cpq/backend/cpq/pricing/[id]/page.js.map +7 -0
  143. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js +50 -0
  144. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.js.map +7 -0
  145. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js +249 -0
  146. package/dist/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.js.map +7 -0
  147. package/dist/modules/cpq/backend/cpq/pricing/page.js +87 -0
  148. package/dist/modules/cpq/backend/cpq/pricing/page.js.map +7 -0
  149. package/dist/modules/cpq/backend/cpq/pricing/page.meta.js +18 -0
  150. package/dist/modules/cpq/backend/cpq/pricing/page.meta.js.map +7 -0
  151. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js +652 -0
  152. package/dist/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.js.map +7 -0
  153. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js +1289 -0
  154. package/dist/modules/cpq/backend/cpq/quotes/[id]/page.js.map +7 -0
  155. package/dist/modules/cpq/backend/cpq/quotes/page.js +283 -0
  156. package/dist/modules/cpq/backend/cpq/quotes/page.js.map +7 -0
  157. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js +18 -0
  158. package/dist/modules/cpq/backend/cpq/quotes/page.meta.js.map +7 -0
  159. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js +214 -0
  160. package/dist/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.js.map +7 -0
  161. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js +1399 -0
  162. package/dist/modules/cpq/backend/cpq/specifications/[id]/page.js.map +7 -0
  163. package/dist/modules/cpq/backend/cpq/specifications/page.js +148 -0
  164. package/dist/modules/cpq/backend/cpq/specifications/page.js.map +7 -0
  165. package/dist/modules/cpq/backend/cpq/specifications/page.meta.js +18 -0
  166. package/dist/modules/cpq/backend/cpq/specifications/page.meta.js.map +7 -0
  167. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js +204 -0
  168. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.js.map +7 -0
  169. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js +191 -0
  170. package/dist/modules/cpq/backend/cpq/wizards/[definitionCode]/page.js.map +7 -0
  171. package/dist/modules/cpq/backend/cpq/wizards/page.js +119 -0
  172. package/dist/modules/cpq/backend/cpq/wizards/page.js.map +7 -0
  173. package/dist/modules/cpq/backend/cpq/wizards/page.meta.js +18 -0
  174. package/dist/modules/cpq/backend/cpq/wizards/page.meta.js.map +7 -0
  175. package/dist/modules/cpq/ce.js +151 -0
  176. package/dist/modules/cpq/ce.js.map +7 -0
  177. package/dist/modules/cpq/cli.js +11 -0
  178. package/dist/modules/cpq/cli.js.map +7 -0
  179. package/dist/modules/cpq/components/CpqListView.js +265 -0
  180. package/dist/modules/cpq/components/CpqListView.js.map +7 -0
  181. package/dist/modules/cpq/components/NumberInput.js +95 -0
  182. package/dist/modules/cpq/components/NumberInput.js.map +7 -0
  183. package/dist/modules/cpq/components/StatusPath.js +60 -0
  184. package/dist/modules/cpq/components/StatusPath.js.map +7 -0
  185. package/dist/modules/cpq/components/statusMaps.js +66 -0
  186. package/dist/modules/cpq/components/statusMaps.js.map +7 -0
  187. package/dist/modules/cpq/components/useCpqRowActions.js +125 -0
  188. package/dist/modules/cpq/components/useCpqRowActions.js.map +7 -0
  189. package/dist/modules/cpq/data/entities.js +1556 -0
  190. package/dist/modules/cpq/data/entities.js.map +7 -0
  191. package/dist/modules/cpq/data/validators.js +575 -0
  192. package/dist/modules/cpq/data/validators.js.map +7 -0
  193. package/dist/modules/cpq/di.js +57 -0
  194. package/dist/modules/cpq/di.js.map +7 -0
  195. package/dist/modules/cpq/events.js +50 -0
  196. package/dist/modules/cpq/events.js.map +7 -0
  197. package/dist/modules/cpq/index.js +16 -0
  198. package/dist/modules/cpq/index.js.map +7 -0
  199. package/dist/modules/cpq/lib/seeds/api.js +32 -0
  200. package/dist/modules/cpq/lib/seeds/api.js.map +7 -0
  201. package/dist/modules/cpq/lib/seeds/tenant-provisioning.js +158 -0
  202. package/dist/modules/cpq/lib/seeds/tenant-provisioning.js.map +7 -0
  203. package/dist/modules/cpq/lib/seeds.js +6 -0
  204. package/dist/modules/cpq/lib/seeds.js.map +7 -0
  205. package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js +40 -0
  206. package/dist/modules/cpq/migrations/Migration20260220220000_cpq.js.map +7 -0
  207. package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js +54 -0
  208. package/dist/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.js.map +7 -0
  209. package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js +15 -0
  210. package/dist/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.js.map +7 -0
  211. package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js +62 -0
  212. package/dist/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.js.map +7 -0
  213. package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js +56 -0
  214. package/dist/modules/cpq/migrations/Migration20260402000000_cpq_workflows.js.map +7 -0
  215. package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js +26 -0
  216. package/dist/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.js.map +7 -0
  217. package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js +37 -0
  218. package/dist/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.js.map +7 -0
  219. package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js +35 -0
  220. package/dist/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.js.map +7 -0
  221. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js +21 -0
  222. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.js.map +7 -0
  223. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js +46 -0
  224. package/dist/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.js.map +7 -0
  225. package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js +118 -0
  226. package/dist/modules/cpq/migrations/Migration20260410100000_cpq_inventory.js.map +7 -0
  227. package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js +63 -0
  228. package/dist/modules/cpq/migrations/Migration20260411000000_cpq_orders.js.map +7 -0
  229. package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js +28 -0
  230. package/dist/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.js.map +7 -0
  231. package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js +36 -0
  232. package/dist/modules/cpq/migrations/Migration20260504064621_cpq_arc.js.map +7 -0
  233. package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js +14 -0
  234. package/dist/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.js.map +7 -0
  235. package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js +14 -0
  236. package/dist/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.js.map +7 -0
  237. package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js +58 -0
  238. package/dist/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.js.map +7 -0
  239. package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js +29 -0
  240. package/dist/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.js.map +7 -0
  241. package/dist/modules/cpq/services/cpqBundleService.js +447 -0
  242. package/dist/modules/cpq/services/cpqBundleService.js.map +7 -0
  243. package/dist/modules/cpq/services/cpqInventoryService.js +1371 -0
  244. package/dist/modules/cpq/services/cpqInventoryService.js.map +7 -0
  245. package/dist/modules/cpq/services/cpqOrderService.js +901 -0
  246. package/dist/modules/cpq/services/cpqOrderService.js.map +7 -0
  247. package/dist/modules/cpq/services/cpqPricingService.js +289 -0
  248. package/dist/modules/cpq/services/cpqPricingService.js.map +7 -0
  249. package/dist/modules/cpq/services/cpqProductService.js +204 -0
  250. package/dist/modules/cpq/services/cpqProductService.js.map +7 -0
  251. package/dist/modules/cpq/services/cpqQuotingService.js +1867 -0
  252. package/dist/modules/cpq/services/cpqQuotingService.js.map +7 -0
  253. package/dist/modules/cpq/services/cpqValidationService.js +234 -0
  254. package/dist/modules/cpq/services/cpqValidationService.js.map +7 -0
  255. package/dist/modules/cpq/services/cpqWizardService.js +137 -0
  256. package/dist/modules/cpq/services/cpqWizardService.js.map +7 -0
  257. package/dist/modules/cpq/services/types.js +114 -0
  258. package/dist/modules/cpq/services/types.js.map +7 -0
  259. package/dist/modules/cpq/setup.js +49 -0
  260. package/dist/modules/cpq/setup.js.map +7 -0
  261. package/dist/modules/cpq/subscribers/subscription-amended-log.js +18 -0
  262. package/dist/modules/cpq/subscribers/subscription-amended-log.js.map +7 -0
  263. package/dist/modules/cpq/subscribers/subscription-cancelled-log.js +19 -0
  264. package/dist/modules/cpq/subscribers/subscription-cancelled-log.js.map +7 -0
  265. package/dist/modules/cpq/subscribers/subscription-merged-log.js +20 -0
  266. package/dist/modules/cpq/subscribers/subscription-merged-log.js.map +7 -0
  267. package/dist/modules/cpq/subscribers/subscription-renewed-log.js +18 -0
  268. package/dist/modules/cpq/subscribers/subscription-renewed-log.js.map +7 -0
  269. package/dist/modules/cpq/subscribers/subscription-superseded-log.js +18 -0
  270. package/dist/modules/cpq/subscribers/subscription-superseded-log.js.map +7 -0
  271. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js +192 -0
  272. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.client.js.map +7 -0
  273. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js +17 -0
  274. package/dist/modules/cpq/widgets/injection/customer-inventory/widget.js.map +7 -0
  275. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js +79 -0
  276. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.client.js.map +7 -0
  277. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js +17 -0
  278. package/dist/modules/cpq/widgets/injection/order-cpq-tab/widget.js.map +7 -0
  279. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js +181 -0
  280. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.client.js.map +7 -0
  281. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js +17 -0
  282. package/dist/modules/cpq/widgets/injection/quote-configurator/widget.js.map +7 -0
  283. package/dist/modules/cpq/widgets/injection-table.js +44 -0
  284. package/dist/modules/cpq/widgets/injection-table.js.map +7 -0
  285. package/dist/modules/cpq/workflows/WizardRunner.js +113 -0
  286. package/dist/modules/cpq/workflows/WizardRunner.js.map +7 -0
  287. package/dist/modules/cpq/workflows/WizardTrigger.js +116 -0
  288. package/dist/modules/cpq/workflows/WizardTrigger.js.map +7 -0
  289. package/dist/modules/cpq/workflows/engine.js +188 -0
  290. package/dist/modules/cpq/workflows/engine.js.map +7 -0
  291. package/dist/modules/cpq/workflows/registry.js +16 -0
  292. package/dist/modules/cpq/workflows/registry.js.map +7 -0
  293. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js +119 -0
  294. package/dist/modules/cpq/workflows/steps/CustomerSelectStep.js.map +7 -0
  295. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js +24 -0
  296. package/dist/modules/cpq/workflows/steps/InventorySelectStep.js.map +7 -0
  297. package/dist/modules/cpq/workflows/steps/ItemListStep.js +115 -0
  298. package/dist/modules/cpq/workflows/steps/ItemListStep.js.map +7 -0
  299. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js +327 -0
  300. package/dist/modules/cpq/workflows/steps/OfferingSelectStep.js.map +7 -0
  301. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js +231 -0
  302. package/dist/modules/cpq/workflows/steps/ProductConfigureStep.js.map +7 -0
  303. package/dist/modules/cpq/workflows/steps/ReviewStep.js +152 -0
  304. package/dist/modules/cpq/workflows/steps/ReviewStep.js.map +7 -0
  305. package/dist/modules/cpq/workflows/steps/index.js +49 -0
  306. package/dist/modules/cpq/workflows/steps/index.js.map +7 -0
  307. package/dist/modules/cpq/workflows/types.js +1 -0
  308. package/dist/modules/cpq/workflows/types.js.map +7 -0
  309. package/package.json +150 -0
  310. package/src/modules/cpq/__integration__/TC-CPQ-001.spec.ts +177 -0
  311. package/src/modules/cpq/__integration__/TC-CPQ-002.spec.ts +123 -0
  312. package/src/modules/cpq/__integration__/TC-CPQ-003.spec.ts +115 -0
  313. package/src/modules/cpq/__integration__/TC-CPQ-004.spec.ts +184 -0
  314. package/src/modules/cpq/__integration__/TC-CPQ-005.spec.ts +43 -0
  315. package/src/modules/cpq/__integration__/TC-CPQ-006.spec.ts +87 -0
  316. package/src/modules/cpq/__integration__/TC-CPQ-007.spec.ts +79 -0
  317. package/src/modules/cpq/__integration__/TC-CPQ-008.spec.ts +55 -0
  318. package/src/modules/cpq/__integration__/TC-CPQ-009.spec.ts +45 -0
  319. package/src/modules/cpq/__integration__/TC-CPQ-010.spec.ts +54 -0
  320. package/src/modules/cpq/__integration__/TC-CPQ-011.spec.ts +24 -0
  321. package/src/modules/cpq/__integration__/TC-CPQ-012.spec.ts +62 -0
  322. package/src/modules/cpq/__integration__/TC-CPQ-ARC-001.spec.ts +52 -0
  323. package/src/modules/cpq/__integration__/TC-CPQ-ARC-002.spec.ts +65 -0
  324. package/src/modules/cpq/__integration__/TC-CPQ-ARC-003.spec.ts +107 -0
  325. package/src/modules/cpq/__integration__/TC-CPQ-ARC-004.spec.ts +74 -0
  326. package/src/modules/cpq/__integration__/TC-CPQ-ARC-005.spec.ts +86 -0
  327. package/src/modules/cpq/__integration__/TC-CPQ-ARC-006.spec.ts +79 -0
  328. package/src/modules/cpq/__integration__/TC-CPQ-ARC-007.spec.ts +63 -0
  329. package/src/modules/cpq/__integration__/TC-CPQ-ARC-008.spec.ts +62 -0
  330. package/src/modules/cpq/__integration__/TC-CPQ-ARC-009.spec.ts +62 -0
  331. package/src/modules/cpq/__integration__/TC-CPQ-ARC-010.spec.ts +59 -0
  332. package/src/modules/cpq/__integration__/TC-CPQ-ARC-011.spec.ts +128 -0
  333. package/src/modules/cpq/__integration__/_helpers/cpqArcFixtures.ts +208 -0
  334. package/src/modules/cpq/__tests__/package-isolation.test.ts +232 -0
  335. package/src/modules/cpq/acl.ts +26 -0
  336. package/src/modules/cpq/api/_helpers/__tests__/handleArcError.test.ts +117 -0
  337. package/src/modules/cpq/api/_helpers/handleArcError.ts +81 -0
  338. package/src/modules/cpq/api/attribute-options/route.ts +55 -0
  339. package/src/modules/cpq/api/bundle-slots/route.ts +112 -0
  340. package/src/modules/cpq/api/bundle-tree/route.ts +31 -0
  341. package/src/modules/cpq/api/context.ts +39 -0
  342. package/src/modules/cpq/api/inventory/assets/route.ts +123 -0
  343. package/src/modules/cpq/api/inventory/assets/status/route.ts +34 -0
  344. package/src/modules/cpq/api/inventory/customer/[customerId]/parent-items/route.ts +30 -0
  345. package/src/modules/cpq/api/inventory/customer/[customerId]/route.ts +31 -0
  346. package/src/modules/cpq/api/inventory/subscription-items/route.ts +107 -0
  347. package/src/modules/cpq/api/inventory/subscriptions/[subscriptionId]/change-log/route.ts +64 -0
  348. package/src/modules/cpq/api/inventory/subscriptions/expiring/route.ts +66 -0
  349. package/src/modules/cpq/api/inventory/subscriptions/route.ts +126 -0
  350. package/src/modules/cpq/api/inventory/subscriptions/status/route.ts +34 -0
  351. package/src/modules/cpq/api/offering-components/route.ts +103 -0
  352. package/src/modules/cpq/api/orders/[orderId]/activate/route.ts +48 -0
  353. package/src/modules/cpq/api/orders/[orderId]/route.ts +28 -0
  354. package/src/modules/cpq/api/orders/[orderId]/status/route.ts +38 -0
  355. package/src/modules/cpq/api/orders/route.ts +77 -0
  356. package/src/modules/cpq/api/price-rules/route.ts +210 -0
  357. package/src/modules/cpq/api/pricing-table-entries/route.ts +159 -0
  358. package/src/modules/cpq/api/pricing-tables/route.ts +202 -0
  359. package/src/modules/cpq/api/product-attributes/route.ts +175 -0
  360. package/src/modules/cpq/api/product-charges/route.ts +181 -0
  361. package/src/modules/cpq/api/product-offerings/route.ts +329 -0
  362. package/src/modules/cpq/api/product-relationships/route.ts +156 -0
  363. package/src/modules/cpq/api/product-specifications/route.ts +325 -0
  364. package/src/modules/cpq/api/quotes/[quoteId]/cancel-meta/route.ts +77 -0
  365. package/src/modules/cpq/api/quotes/[quoteId]/clone/route.ts +28 -0
  366. package/src/modules/cpq/api/quotes/[quoteId]/items/[lineId]/route.ts +55 -0
  367. package/src/modules/cpq/api/quotes/[quoteId]/items/route.ts +26 -0
  368. package/src/modules/cpq/api/quotes/[quoteId]/merge-meta/route.ts +78 -0
  369. package/src/modules/cpq/api/quotes/[quoteId]/recalculate/route.ts +34 -0
  370. package/src/modules/cpq/api/quotes/[quoteId]/route.ts +109 -0
  371. package/src/modules/cpq/api/quotes/[quoteId]/status/route.ts +26 -0
  372. package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/[targetId]/route.ts +91 -0
  373. package/src/modules/cpq/api/quotes/[quoteId]/target-subscriptions/route.ts +107 -0
  374. package/src/modules/cpq/api/quotes/attributes/route.ts +61 -0
  375. package/src/modules/cpq/api/quotes/from-subscription/route.ts +85 -0
  376. package/src/modules/cpq/api/quotes/price/route.ts +81 -0
  377. package/src/modules/cpq/api/quotes/route.ts +114 -0
  378. package/src/modules/cpq/api/quotes/validate-relationships/route.ts +100 -0
  379. package/src/modules/cpq/api/resolveQuotingService.ts +11 -0
  380. package/src/modules/cpq/api/wizards/resolve/route.ts +39 -0
  381. package/src/modules/cpq/api/wizards/route.ts +127 -0
  382. package/src/modules/cpq/backend/cpq/inventory/AssetsTable.tsx +166 -0
  383. package/src/modules/cpq/backend/cpq/inventory/SubscriptionsTable.tsx +251 -0
  384. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.meta.ts +9 -0
  385. package/src/modules/cpq/backend/cpq/inventory/assets/[id]/page.tsx +440 -0
  386. package/src/modules/cpq/backend/cpq/inventory/page.meta.ts +16 -0
  387. package/src/modules/cpq/backend/cpq/inventory/page.tsx +49 -0
  388. package/src/modules/cpq/backend/cpq/inventory/shared.tsx +32 -0
  389. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.meta.ts +9 -0
  390. package/src/modules/cpq/backend/cpq/inventory/subscriptions/[id]/page.tsx +1085 -0
  391. package/src/modules/cpq/backend/cpq/offerings/[id]/page.tsx +1165 -0
  392. package/src/modules/cpq/backend/cpq/offerings/page.meta.ts +16 -0
  393. package/src/modules/cpq/backend/cpq/offerings/page.tsx +468 -0
  394. package/src/modules/cpq/backend/cpq/orders/[id]/page.tsx +921 -0
  395. package/src/modules/cpq/backend/cpq/orders/page.meta.ts +16 -0
  396. package/src/modules/cpq/backend/cpq/orders/page.tsx +182 -0
  397. package/src/modules/cpq/backend/cpq/price-rules/PriceRuleForm.tsx +310 -0
  398. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.meta.ts +9 -0
  399. package/src/modules/cpq/backend/cpq/price-rules/[id]/page.tsx +472 -0
  400. package/src/modules/cpq/backend/cpq/price-rules/page.meta.ts +16 -0
  401. package/src/modules/cpq/backend/cpq/price-rules/page.tsx +253 -0
  402. package/src/modules/cpq/backend/cpq/pricing/[id]/EntriesEditor.tsx +431 -0
  403. package/src/modules/cpq/backend/cpq/pricing/[id]/ImportPreview.tsx +122 -0
  404. package/src/modules/cpq/backend/cpq/pricing/[id]/page.tsx +588 -0
  405. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxExport.ts +90 -0
  406. package/src/modules/cpq/backend/cpq/pricing/[id]/xlsxImport.ts +338 -0
  407. package/src/modules/cpq/backend/cpq/pricing/page.meta.ts +16 -0
  408. package/src/modules/cpq/backend/cpq/pricing/page.tsx +111 -0
  409. package/src/modules/cpq/backend/cpq/quotes/[id]/_components/ArcQuoteConfigurator.tsx +737 -0
  410. package/src/modules/cpq/backend/cpq/quotes/[id]/page.tsx +1721 -0
  411. package/src/modules/cpq/backend/cpq/quotes/page.meta.ts +16 -0
  412. package/src/modules/cpq/backend/cpq/quotes/page.tsx +370 -0
  413. package/src/modules/cpq/backend/cpq/specifications/[id]/DependsOnRulesEditor.tsx +300 -0
  414. package/src/modules/cpq/backend/cpq/specifications/[id]/page.tsx +1722 -0
  415. package/src/modules/cpq/backend/cpq/specifications/page.meta.ts +16 -0
  416. package/src/modules/cpq/backend/cpq/specifications/page.tsx +191 -0
  417. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/detail/page.tsx +319 -0
  418. package/src/modules/cpq/backend/cpq/wizards/[definitionCode]/page.tsx +234 -0
  419. package/src/modules/cpq/backend/cpq/wizards/page.meta.ts +16 -0
  420. package/src/modules/cpq/backend/cpq/wizards/page.tsx +141 -0
  421. package/src/modules/cpq/ce.ts +150 -0
  422. package/src/modules/cpq/cli.ts +10 -0
  423. package/src/modules/cpq/components/CpqListView.tsx +431 -0
  424. package/src/modules/cpq/components/NumberInput.tsx +166 -0
  425. package/src/modules/cpq/components/StatusPath.tsx +116 -0
  426. package/src/modules/cpq/components/statusMaps.ts +148 -0
  427. package/src/modules/cpq/components/useCpqRowActions.tsx +183 -0
  428. package/src/modules/cpq/data/__tests__/validators.arc.test.ts +271 -0
  429. package/src/modules/cpq/data/__tests__/validators.test.ts +782 -0
  430. package/src/modules/cpq/data/entities.ts +1467 -0
  431. package/src/modules/cpq/data/validators.ts +663 -0
  432. package/src/modules/cpq/di.ts +70 -0
  433. package/src/modules/cpq/events.ts +62 -0
  434. package/src/modules/cpq/index.ts +15 -0
  435. package/src/modules/cpq/lib/seeds/__tests__/api.test.ts +86 -0
  436. package/src/modules/cpq/lib/seeds/__tests__/tenant-provisioning.test.ts +349 -0
  437. package/src/modules/cpq/lib/seeds/api.ts +119 -0
  438. package/src/modules/cpq/lib/seeds/tenant-provisioning.ts +239 -0
  439. package/src/modules/cpq/lib/seeds.ts +18 -0
  440. package/src/modules/cpq/migrations/.snapshot-open-mercato.json +7473 -0
  441. package/src/modules/cpq/migrations/Migration20260220220000_cpq.ts +51 -0
  442. package/src/modules/cpq/migrations/Migration20260330000000_cpq_product_domain.ts +70 -0
  443. package/src/modules/cpq/migrations/Migration20260331000000_cpq_charge_unique_per_offering.ts +15 -0
  444. package/src/modules/cpq/migrations/Migration20260401000000_cpq_quoting_v2.ts +73 -0
  445. package/src/modules/cpq/migrations/Migration20260402000000_cpq_workflows.ts +59 -0
  446. package/src/modules/cpq/migrations/Migration20260407000000_cpq_pricing_tables_v2.ts +30 -0
  447. package/src/modules/cpq/migrations/Migration20260407100000_cpq_price_rules.ts +36 -0
  448. package/src/modules/cpq/migrations/Migration20260408000000_cpq_price_rules.ts +33 -0
  449. package/src/modules/cpq/migrations/Migration20260410000000_cpq_drop_datacentres_inventory.ts +22 -0
  450. package/src/modules/cpq/migrations/Migration20260410000000_cpq_wizard_rename.ts +56 -0
  451. package/src/modules/cpq/migrations/Migration20260410100000_cpq_inventory.ts +118 -0
  452. package/src/modules/cpq/migrations/Migration20260411000000_cpq_orders.ts +62 -0
  453. package/src/modules/cpq/migrations/Migration20260412000000_cpq_bundled_offerings.ts +33 -0
  454. package/src/modules/cpq/migrations/Migration20260504064621_cpq_arc.ts +68 -0
  455. package/src/modules/cpq/migrations/Migration20260514000000_cpq_spec_merge_retired_deprecated.ts +30 -0
  456. package/src/modules/cpq/migrations/Migration20260514100000_cpq_offering_merge_retired_deprecated.ts +27 -0
  457. package/src/modules/cpq/migrations/Migration20260514110000_cpq_list_search_indexes.ts +82 -0
  458. package/src/modules/cpq/migrations/Migration20260515000000_cpq_inventory_subscriptions_search_indexes.ts +52 -0
  459. package/src/modules/cpq/services/__tests__/_helpers/mockEm.ts +59 -0
  460. package/src/modules/cpq/services/__tests__/cpqBundleService.test.ts +661 -0
  461. package/src/modules/cpq/services/__tests__/cpqInventoryService.arc.test.ts +357 -0
  462. package/src/modules/cpq/services/__tests__/cpqInventoryService.test.ts +718 -0
  463. package/src/modules/cpq/services/__tests__/cpqOrderService.test.ts +603 -0
  464. package/src/modules/cpq/services/__tests__/cpqPricingService.test.ts +1012 -0
  465. package/src/modules/cpq/services/__tests__/cpqProductService.test.ts +603 -0
  466. package/src/modules/cpq/services/__tests__/cpqQuotingService.arc.test.ts +143 -0
  467. package/src/modules/cpq/services/__tests__/cpqQuotingService.test.ts +658 -0
  468. package/src/modules/cpq/services/__tests__/cpqValidationService.test.ts +811 -0
  469. package/src/modules/cpq/services/__tests__/cpqWizardService.test.ts +389 -0
  470. package/src/modules/cpq/services/__tests__/types.test.ts +359 -0
  471. package/src/modules/cpq/services/cpqBundleService.ts +671 -0
  472. package/src/modules/cpq/services/cpqInventoryService.ts +1929 -0
  473. package/src/modules/cpq/services/cpqOrderService.ts +1230 -0
  474. package/src/modules/cpq/services/cpqPricingService.ts +412 -0
  475. package/src/modules/cpq/services/cpqProductService.ts +311 -0
  476. package/src/modules/cpq/services/cpqQuotingService.ts +2638 -0
  477. package/src/modules/cpq/services/cpqValidationService.ts +323 -0
  478. package/src/modules/cpq/services/cpqWizardService.ts +204 -0
  479. package/src/modules/cpq/services/types.ts +303 -0
  480. package/src/modules/cpq/setup.ts +63 -0
  481. package/src/modules/cpq/subscribers/subscription-amended-log.ts +20 -0
  482. package/src/modules/cpq/subscribers/subscription-cancelled-log.ts +19 -0
  483. package/src/modules/cpq/subscribers/subscription-merged-log.ts +22 -0
  484. package/src/modules/cpq/subscribers/subscription-renewed-log.ts +18 -0
  485. package/src/modules/cpq/subscribers/subscription-superseded-log.ts +19 -0
  486. package/src/modules/cpq/widgets/injection/customer-inventory/widget.client.tsx +339 -0
  487. package/src/modules/cpq/widgets/injection/customer-inventory/widget.ts +16 -0
  488. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.client.tsx +96 -0
  489. package/src/modules/cpq/widgets/injection/order-cpq-tab/widget.ts +16 -0
  490. package/src/modules/cpq/widgets/injection/quote-configurator/widget.client.tsx +265 -0
  491. package/src/modules/cpq/widgets/injection/quote-configurator/widget.ts +16 -0
  492. package/src/modules/cpq/widgets/injection-table.ts +42 -0
  493. package/src/modules/cpq/workflows/WizardRunner.tsx +158 -0
  494. package/src/modules/cpq/workflows/WizardTrigger.tsx +142 -0
  495. package/src/modules/cpq/workflows/engine.tsx +278 -0
  496. package/src/modules/cpq/workflows/registry.ts +15 -0
  497. package/src/modules/cpq/workflows/steps/CustomerSelectStep.tsx +136 -0
  498. package/src/modules/cpq/workflows/steps/InventorySelectStep.tsx +34 -0
  499. package/src/modules/cpq/workflows/steps/ItemListStep.tsx +146 -0
  500. package/src/modules/cpq/workflows/steps/OfferingSelectStep.tsx +438 -0
  501. package/src/modules/cpq/workflows/steps/ProductConfigureStep.tsx +287 -0
  502. package/src/modules/cpq/workflows/steps/ReviewStep.tsx +219 -0
  503. package/src/modules/cpq/workflows/steps/index.ts +51 -0
  504. package/src/modules/cpq/workflows/types.ts +102 -0
@@ -0,0 +1,431 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import type { ColumnDef, SortingState } from '@tanstack/react-table'
4
+ import { DataTable, type BulkAction } from '@open-mercato/ui/backend/DataTable'
5
+ import { Page, PageBody } from '@open-mercato/ui/backend/Page'
6
+ import type { FilterDef, FilterValues } from '@open-mercato/ui/backend/FilterBar'
7
+ import { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'
8
+ import { apiCall } from '@open-mercato/ui/backend/utils/apiCall'
9
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
11
+ import { useCpqRowActions, type UseCpqRowActionsOptions } from './useCpqRowActions'
12
+
13
+ // ─── Types ───────────────────────────────────────────────────────
14
+
15
+ type PaginatedResponse<T> = {
16
+ items?: T[]
17
+ total?: number
18
+ totalPages?: number
19
+ }
20
+
21
+ export type CpqListData<T> = {
22
+ rows: T[]
23
+ total: number
24
+ totalPages: number
25
+ page: number
26
+ isLoading: boolean
27
+ /** Last successful fetch duration in milliseconds. Surfaces in the
28
+ * pagination footer as `... in 1.5s`, matching OM's standard list views. */
29
+ durationMs: number | null
30
+ search: string
31
+ sorting: SortingState
32
+ filterValues: FilterValues
33
+ setPage: (page: number) => void
34
+ setSearch: (value: string) => void
35
+ setSorting: React.Dispatch<React.SetStateAction<SortingState>>
36
+ setFilterValues: (values: FilterValues) => void
37
+ clearFilters: () => void
38
+ reload: () => void
39
+ queryString: string
40
+ }
41
+
42
+ export type UseCpqListDataOptions = {
43
+ /** API endpoint without query string, e.g. `/api/cpq/product-offerings` */
44
+ endpoint: string
45
+ /** Default 50 — keep at or below 100. */
46
+ pageSize?: number
47
+ /** Initial sort state. Default `[{ id: 'createdAt', desc: true }]`. */
48
+ initialSorting?: SortingState
49
+ /** Map filter values to URL params (only call set on the params arg). */
50
+ buildFilterParams?: (values: FilterValues, params: URLSearchParams) => void
51
+ /** Override the flash message shown when the load fails. */
52
+ loadErrorMessage?: string
53
+ }
54
+
55
+ /**
56
+ * Server-side paginated list state for CPQ list views.
57
+ *
58
+ * Designed for 100k+ records:
59
+ * - search / sort / filter all go to the server
60
+ * - default pageSize stays at or below 100
61
+ * - debouncing is handled by FilterBar's search input (1s)
62
+ */
63
+ export function useCpqListData<T extends { id: string }>(
64
+ options: UseCpqListDataOptions,
65
+ ): CpqListData<T> {
66
+ const {
67
+ endpoint,
68
+ pageSize = 50,
69
+ initialSorting = [{ id: 'createdAt', desc: true }],
70
+ buildFilterParams,
71
+ loadErrorMessage,
72
+ } = options
73
+ const t = useT()
74
+
75
+ const [rows, setRows] = React.useState<T[]>([])
76
+ const [total, setTotal] = React.useState(0)
77
+ const [totalPages, setTotalPages] = React.useState(1)
78
+ const [page, setPageState] = React.useState(1)
79
+ const [isLoading, setIsLoading] = React.useState(true)
80
+ const [durationMs, setDurationMs] = React.useState<number | null>(null)
81
+ const [reloadToken, setReloadToken] = React.useState(0)
82
+ const [search, setSearchState] = React.useState('')
83
+ const [sorting, setSorting] = React.useState<SortingState>(initialSorting)
84
+ const [filterValues, setFilterValuesState] = React.useState<FilterValues>({})
85
+
86
+ const queryString = React.useMemo(() => {
87
+ const params = new URLSearchParams()
88
+ params.set('page', String(page))
89
+ params.set('pageSize', String(pageSize))
90
+ if (search.trim()) params.set('search', search.trim())
91
+ const sort = sorting[0]
92
+ if (sort?.id) {
93
+ params.set('sortField', sort.id)
94
+ params.set('sortDir', sort.desc ? 'desc' : 'asc')
95
+ }
96
+ if (buildFilterParams) buildFilterParams(filterValues, params)
97
+ return params.toString()
98
+ }, [buildFilterParams, filterValues, page, pageSize, search, sorting])
99
+
100
+ React.useEffect(() => {
101
+ let cancelled = false
102
+ async function load() {
103
+ setIsLoading(true)
104
+ // Use `performance.now()` so the wall-clock change during the request
105
+ // (NTP, DST, manual clock skew) can never produce a negative duration.
106
+ const startedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()
107
+ try {
108
+ const fallback: PaginatedResponse<T> = { items: [], total: 0, totalPages: 1 }
109
+ const call = await apiCall<PaginatedResponse<T>>(
110
+ `${endpoint}?${queryString}`,
111
+ undefined,
112
+ { fallback },
113
+ )
114
+ if (cancelled) return
115
+ if (!call.ok) {
116
+ flash(loadErrorMessage ?? t('cpq.list.error.load', 'Failed to load'), 'error')
117
+ return
118
+ }
119
+ const payload = call.result ?? fallback
120
+ const items = Array.isArray(payload.items) ? payload.items : []
121
+ setRows(items)
122
+ setTotal(typeof payload.total === 'number' ? payload.total : items.length)
123
+ setTotalPages(typeof payload.totalPages === 'number' ? payload.totalPages : 1)
124
+ const finishedAt = typeof performance !== 'undefined' ? performance.now() : Date.now()
125
+ setDurationMs(Math.max(0, finishedAt - startedAt))
126
+ } finally {
127
+ if (!cancelled) setIsLoading(false)
128
+ }
129
+ }
130
+ load()
131
+ return () => {
132
+ cancelled = true
133
+ }
134
+ }, [endpoint, loadErrorMessage, queryString, reloadToken, t])
135
+
136
+ const setSearch = React.useCallback((value: string) => {
137
+ setSearchState(value)
138
+ setPageState(1)
139
+ }, [])
140
+
141
+ const setFilterValues = React.useCallback((values: FilterValues) => {
142
+ setFilterValuesState(values)
143
+ setPageState(1)
144
+ }, [])
145
+
146
+ const clearFilters = React.useCallback(() => {
147
+ setFilterValuesState({})
148
+ setPageState(1)
149
+ }, [])
150
+
151
+ const reload = React.useCallback(() => {
152
+ setReloadToken((token) => token + 1)
153
+ }, [])
154
+
155
+ return {
156
+ rows,
157
+ total,
158
+ totalPages,
159
+ page,
160
+ isLoading,
161
+ durationMs,
162
+ search,
163
+ sorting,
164
+ filterValues,
165
+ setPage: setPageState,
166
+ setSearch,
167
+ setSorting,
168
+ setFilterValues,
169
+ clearFilters,
170
+ reload,
171
+ queryString,
172
+ }
173
+ }
174
+
175
+ // ─── Component ───────────────────────────────────────────────────
176
+
177
+ /**
178
+ * Declarative shortcut for the standard CRUD-list row actions.
179
+ *
180
+ * When provided, CpqListView:
181
+ * - registers `useCpqRowActions` internally
182
+ * - renders Edit + Delete in every row's `⋯` menu (with optional
183
+ * `extraRowItems` per row prepended / appended)
184
+ * - adds a "Delete selected" bulk action (after any `bulkActions` you
185
+ * pass explicitly, so module-specific bulk ops come first)
186
+ * - mounts the confirm-dialog portal in the page footer
187
+ *
188
+ * Pass `rowActions` / `bulkActions` directly instead when the page needs
189
+ * fully custom semantics (e.g. Quotes / Orders have no DELETE endpoint).
190
+ */
191
+ export type CpqListViewCrudConfig<T extends { id: string }> = Omit<
192
+ UseCpqRowActionsOptions<T>,
193
+ 'onReload'
194
+ > & {
195
+ /**
196
+ * Extra items added to each row's actions menu.
197
+ * `prepend` renders before Edit, `append` between Edit and Delete.
198
+ */
199
+ extraRowItems?: (row: T) => { prepend?: RowActionItem[]; append?: RowActionItem[] }
200
+ }
201
+
202
+ export type CpqListViewProps<T extends { id: string }> = {
203
+ /** Page + table title */
204
+ title: React.ReactNode
205
+ /** Stable id for perspectives (e.g. `cpq.offerings.list`) */
206
+ tableId: string
207
+ /** Stable id for DataTable's selection state (defaults to tableId) */
208
+ selectionScopeKey?: string
209
+ /** List state — typically built from `useCpqListData` in the parent */
210
+ data: CpqListData<T>
211
+ /** Table columns */
212
+ columns: ColumnDef<T>[]
213
+ /** Filter definitions for the FilterBar overlay */
214
+ filters?: FilterDef[]
215
+ /** Search input placeholder */
216
+ searchPlaceholder?: string
217
+ /** Header actions (e.g. Create button) */
218
+ actions?: React.ReactNode
219
+ /** Bulk actions toolbar shown when rows are selected */
220
+ bulkActions?: BulkAction<T>[]
221
+ /** Per-row action menu */
222
+ rowActions?: (row: T) => React.ReactNode
223
+ /**
224
+ * Declarative CRUD config. When set, CpqListView produces the standard
225
+ * `⋯ → Edit / Delete` menu, a "Delete selected" bulk action, and mounts
226
+ * the confirm-dialog portal — replacing the ~30 lines of boilerplate
227
+ * each list page used to repeat. Override `rowActions` / `bulkActions`
228
+ * to take full control instead.
229
+ */
230
+ crud?: CpqListViewCrudConfig<T>
231
+ /** Click handler for whole row (typically navigates to detail) */
232
+ onRowClick?: (row: T) => void
233
+ /** Custom empty state */
234
+ emptyState?: React.ReactNode
235
+ /** Pagination size (must match `useCpqListData` pageSize) */
236
+ pageSize?: number
237
+ /**
238
+ * Custom content rendered ABOVE the DataTable inside the same Page wrapper.
239
+ * Use for inline forms, customer pickers, tab switchers, alerts.
240
+ */
241
+ toolbarContent?: React.ReactNode
242
+ /**
243
+ * Custom content rendered BELOW the DataTable (e.g. confirm dialog portals).
244
+ */
245
+ footerContent?: React.ReactNode
246
+ }
247
+
248
+ /**
249
+ * Generic CPQ list view. Wraps DataTable + FilterBar + Page in the
250
+ * Open Mercato standard, with stronger row-hover affordance so users
251
+ * always know what's clickable.
252
+ */
253
+ export function CpqListView<T extends { id: string }>({
254
+ title,
255
+ tableId,
256
+ selectionScopeKey,
257
+ data,
258
+ columns,
259
+ filters,
260
+ searchPlaceholder,
261
+ actions,
262
+ bulkActions,
263
+ rowActions,
264
+ crud,
265
+ onRowClick,
266
+ emptyState,
267
+ pageSize = 50,
268
+ toolbarContent,
269
+ footerContent,
270
+ }: CpqListViewProps<T>) {
271
+ const t = useT()
272
+
273
+ // Wire `crud` into RowActions + bulkActions + footer confirm dialog so the
274
+ // call-site stays a single declarative config block.
275
+ // The hook is always called (Rules of Hooks); the helpers below decide
276
+ // whether to actually use its output based on the presence of `crud`.
277
+ const crudActions = useCpqRowActions<T>({
278
+ endpoint: crud?.endpoint ?? '',
279
+ entityName: crud?.entityName ?? 'item',
280
+ editHref: crud?.editHref ?? (() => '#'),
281
+ onReload: data.reload,
282
+ canDelete: crud?.canDelete,
283
+ })
284
+
285
+ const resolvedRowActions = React.useMemo(() => {
286
+ if (rowActions) return rowActions
287
+ if (!crud) return undefined
288
+ return (row: T) => (
289
+ <RowActions items={crudActions.buildItems(row, crud.extraRowItems?.(row))} />
290
+ )
291
+ }, [crud, crudActions, rowActions])
292
+
293
+ const resolvedBulkActions = React.useMemo<BulkAction<T>[] | undefined>(() => {
294
+ if (!crud) return bulkActions
295
+ const deleteAction: BulkAction<T> = {
296
+ id: 'delete',
297
+ label: t('cpq.list.bulk.deleteSelected', 'Delete selected'),
298
+ destructive: true,
299
+ onExecute: crudActions.bulkDelete,
300
+ }
301
+ return bulkActions ? [...bulkActions, deleteAction] : [deleteAction]
302
+ }, [bulkActions, crud, crudActions.bulkDelete, t])
303
+
304
+ const resolvedFooter = crud ? (
305
+ <>
306
+ {footerContent}
307
+ {crudActions.ConfirmDialogElement}
308
+ </>
309
+ ) : (
310
+ footerContent
311
+ )
312
+
313
+ return (
314
+ <Page>
315
+ <PageBody className="cpq-list-view space-y-6">
316
+ <CpqInteractionStyles />
317
+ {toolbarContent}
318
+ <DataTable<T>
319
+ title={title}
320
+ actions={actions}
321
+ refreshButton={{
322
+ label: t('cpq.list.actions.refresh', 'Refresh'),
323
+ onRefresh: data.reload,
324
+ isRefreshing: data.isLoading,
325
+ }}
326
+ columns={columns}
327
+ data={data.rows}
328
+ searchValue={data.search}
329
+ onSearchChange={data.setSearch}
330
+ searchPlaceholder={searchPlaceholder ?? t('cpq.list.search.placeholder', 'Search...')}
331
+ filters={filters}
332
+ filterValues={data.filterValues}
333
+ onFiltersApply={data.setFilterValues}
334
+ onFiltersClear={data.clearFilters}
335
+ sorting={data.sorting}
336
+ onSortingChange={data.setSorting}
337
+ bulkActions={resolvedBulkActions}
338
+ selectionScopeKey={selectionScopeKey ?? tableId}
339
+ onRowClick={onRowClick}
340
+ rowActions={resolvedRowActions}
341
+ perspective={{ tableId }}
342
+ columnChooser={{ auto: true }}
343
+ pagination={{
344
+ page: data.page,
345
+ pageSize,
346
+ total: data.total,
347
+ totalPages: data.totalPages,
348
+ onPageChange: data.setPage,
349
+ durationMs: data.durationMs,
350
+ }}
351
+ isLoading={data.isLoading}
352
+ emptyState={emptyState}
353
+ />
354
+ {resolvedFooter}
355
+ </PageBody>
356
+ </Page>
357
+ )
358
+ }
359
+
360
+ // ─── Shared interaction styles ───────────────────────────────────
361
+
362
+ /**
363
+ * Inline style block applied inside `.cpq-list-view` (and any `.cpq-page`)
364
+ * wrappers. Strengthens the OM hover affordance so users can clearly tell
365
+ * which row their mouse is on, and forces `cursor: pointer` on every
366
+ * clickable element inside CPQ pages.
367
+ *
368
+ * Kept inline to avoid coupling the package to a global stylesheet.
369
+ */
370
+ export function CpqInteractionStyles() {
371
+ return (
372
+ <style>{`
373
+ .cpq-list-view tbody tr.cursor-pointer,
374
+ .cpq-page tbody tr.cursor-pointer {
375
+ cursor: pointer;
376
+ }
377
+ .cpq-list-view tbody tr.cursor-pointer:hover,
378
+ .cpq-page tbody tr.cursor-pointer:hover {
379
+ background-color: rgba(0, 0, 0, 0.045);
380
+ }
381
+ .dark .cpq-list-view tbody tr.cursor-pointer:hover,
382
+ .dark .cpq-page tbody tr.cursor-pointer:hover {
383
+ background-color: rgba(255, 255, 255, 0.06);
384
+ }
385
+ .cpq-list-view button:not(:disabled),
386
+ .cpq-list-view [role="button"]:not([aria-disabled="true"]),
387
+ .cpq-list-view a[href],
388
+ .cpq-list-view [data-clickable="true"],
389
+ .cpq-page button:not(:disabled),
390
+ .cpq-page [role="button"]:not([aria-disabled="true"]),
391
+ .cpq-page a[href],
392
+ .cpq-page [data-clickable="true"] {
393
+ cursor: pointer;
394
+ }
395
+ /* Row actions \`⋯\` trigger: OM's hover:bg-accent is barely visible in
396
+ the light theme. Make hover (and the open state) clearly distinct so
397
+ users see what they're aiming at. */
398
+ .cpq-list-view [aria-haspopup="menu"]:hover,
399
+ .cpq-page [aria-haspopup="menu"]:hover,
400
+ .cpq-list-view [aria-haspopup="menu"][aria-expanded="true"],
401
+ .cpq-page [aria-haspopup="menu"][aria-expanded="true"] {
402
+ background-color: rgba(0, 0, 0, 0.08);
403
+ }
404
+ .dark .cpq-list-view [aria-haspopup="menu"]:hover,
405
+ .dark .cpq-page [aria-haspopup="menu"]:hover,
406
+ .dark .cpq-list-view [aria-haspopup="menu"][aria-expanded="true"],
407
+ .dark .cpq-page [aria-haspopup="menu"][aria-expanded="true"] {
408
+ background-color: rgba(255, 255, 255, 0.1);
409
+ }
410
+ /* Items inside the row-actions dropdown menu — strengthen hover so the
411
+ item under the pointer is unmistakable. The menu portal renders at the
412
+ document body, so we can't scope it to .cpq-list-view; targeting the
413
+ role attribute is enough. */
414
+ [role="menu"] [role="menuitem"]:hover,
415
+ [role="menu"] [role="menuitem"]:focus-visible {
416
+ background-color: rgba(0, 0, 0, 0.06);
417
+ }
418
+ .dark [role="menu"] [role="menuitem"]:hover,
419
+ .dark [role="menu"] [role="menuitem"]:focus-visible {
420
+ background-color: rgba(255, 255, 255, 0.08);
421
+ }
422
+ /* Align padding between <a href> menu items (OM's px-2) and the <button>
423
+ variant used for onSelect items (OM's Button size=sm = px-3). Without
424
+ this the Delete label visibly indents past the Edit label. */
425
+ [role="menu"] button[role="menuitem"] {
426
+ padding-left: 0.5rem;
427
+ padding-right: 0.5rem;
428
+ }
429
+ `}</style>
430
+ )
431
+ }
@@ -0,0 +1,166 @@
1
+ "use client"
2
+ import * as React from 'react'
3
+ import { Input, type InputProps } from '@open-mercato/ui/primitives/input'
4
+
5
+ // ─── Why this exists ─────────────────────────────────────────────
6
+ //
7
+ // Plain `<input type="number" value={state}>` is hostile when `state === 0`:
8
+ // the field renders "0", the caret lands AFTER it, and typing "5" produces
9
+ // "05" which most browsers normalise back to `5` — the user typed five but
10
+ // the field swallowed the digit they wanted to keep. Same trap when editing
11
+ // any single-digit existing value.
12
+ //
13
+ // NumberInput fixes this with two layered behaviours:
14
+ //
15
+ // 1. **Select on focus.** Tabbing or clicking into the field selects the
16
+ // whole text, so the next keystroke replaces the existing value
17
+ // instead of prepending to it. This single change removes ~95 % of the
18
+ // friction without changing the controlled-component contract.
19
+ //
20
+ // 2. **Internal string state.** The input is rendered with a string we
21
+ // own, not `String(numericState)`. That lets intermediate edits like
22
+ // `"-"`, `"1."`, or empty stay visible while the parent only ever sees
23
+ // a parsed `number | null`. Parent updates (programmatic reset after
24
+ // save, form initial load) still flow in via the effect below.
25
+ //
26
+ // It also drops `type="number"` in favour of `type="text"` +
27
+ // `inputMode={'numeric' | 'decimal'}` so we get the right mobile keyboard
28
+ // without the spinner buttons and the silent step/min/max coercion that
29
+ // HTML number inputs apply on blur.
30
+
31
+ export type NumberInputProps = Omit<
32
+ InputProps,
33
+ 'value' | 'onChange' | 'type' | 'inputMode' | 'defaultValue'
34
+ > & {
35
+ /**
36
+ * Numeric value. `null`/`undefined` renders the field empty — that's how
37
+ * callers signal "no value yet" without colliding with `0` as a real
38
+ * default.
39
+ */
40
+ value: number | null | undefined
41
+ /** Called with the parsed number, or `null` when the field is cleared. */
42
+ onChange: (value: number | null) => void
43
+ /** Selects the input contents on focus. Default true. */
44
+ selectOnFocus?: boolean
45
+ /** Restrict input to integers (rejects `.`, `e`, etc). Default false. */
46
+ integer?: boolean
47
+ /** Optional minimum (enforced on commit, not while typing). */
48
+ min?: number
49
+ /** Optional maximum (enforced on commit, not while typing). */
50
+ max?: number
51
+ }
52
+
53
+ function isIntermediateInput(raw: string, integer: boolean): boolean {
54
+ // Allow the user to type these without us replacing the field mid-edit.
55
+ // - empty / lone `-` → not parseable yet
56
+ // - trailing `.` for decimals → not parseable yet
57
+ // - trailing `e` / `e-` → scientific notation in progress
58
+ if (raw === '' || raw === '-') return true
59
+ if (!integer && (raw.endsWith('.') || raw.endsWith('e') || raw.endsWith('e-'))) return true
60
+ return false
61
+ }
62
+
63
+ export const NumberInput = React.forwardRef<HTMLInputElement, NumberInputProps>(
64
+ function NumberInput(
65
+ {
66
+ value,
67
+ onChange,
68
+ selectOnFocus = true,
69
+ integer = false,
70
+ min,
71
+ max,
72
+ onFocus,
73
+ onBlur,
74
+ ...rest
75
+ },
76
+ ref,
77
+ ) {
78
+ const [text, setText] = React.useState<string>(value == null ? '' : String(value))
79
+ const lastEmittedRef = React.useRef<number | null | undefined>(value)
80
+
81
+ // Sync external updates (form reset after save, initial load, etc.) into
82
+ // the local text state — but only when the parent's value actually
83
+ // diverges from what we last emitted, so we don't clobber intermediate
84
+ // text like "1." while the user is mid-edit.
85
+ React.useEffect(() => {
86
+ if (lastEmittedRef.current === value) return
87
+ setText(value == null ? '' : String(value))
88
+ lastEmittedRef.current = value
89
+ }, [value])
90
+
91
+ const clamp = React.useCallback(
92
+ (n: number): number => {
93
+ let result = n
94
+ if (typeof min === 'number' && result < min) result = min
95
+ if (typeof max === 'number' && result > max) result = max
96
+ return result
97
+ },
98
+ [max, min],
99
+ )
100
+
101
+ const handleChange = React.useCallback(
102
+ (e: React.ChangeEvent<HTMLInputElement>) => {
103
+ const raw = e.currentTarget.value
104
+
105
+ // Strip characters that are never valid in our numeric formats; this
106
+ // lets us keep `type="text"` while still rejecting alphabetic input.
107
+ const allowed = integer ? /[^\d-]/g : /[^\d.\-eE]/g
108
+ const cleaned = raw.replace(allowed, '')
109
+
110
+ if (isIntermediateInput(cleaned, integer)) {
111
+ setText(cleaned)
112
+ // Treat intermediates as "no committed value yet" — emit null so
113
+ // the parent doesn't store a half-typed number. The full value
114
+ // commits on the next valid keystroke or on blur.
115
+ if (lastEmittedRef.current !== null) {
116
+ lastEmittedRef.current = null
117
+ onChange(null)
118
+ }
119
+ return
120
+ }
121
+
122
+ const parsed = integer ? parseInt(cleaned, 10) : parseFloat(cleaned)
123
+ if (Number.isNaN(parsed)) return // Reject — keep previous state.
124
+
125
+ setText(cleaned)
126
+ const next = clamp(parsed)
127
+ if (lastEmittedRef.current !== next) {
128
+ lastEmittedRef.current = next
129
+ onChange(next)
130
+ }
131
+ },
132
+ [clamp, integer, onChange],
133
+ )
134
+
135
+ const handleBlur = React.useCallback(
136
+ (e: React.FocusEvent<HTMLInputElement>) => {
137
+ // On blur, normalise the displayed text to match the committed value
138
+ // (e.g. "01" → "1", "1." → "1"). Empty stays empty.
139
+ if (lastEmittedRef.current == null) {
140
+ if (text !== '') setText('')
141
+ } else {
142
+ const normalised = String(lastEmittedRef.current)
143
+ if (text !== normalised) setText(normalised)
144
+ }
145
+ onBlur?.(e)
146
+ },
147
+ [onBlur, text],
148
+ )
149
+
150
+ return (
151
+ <Input
152
+ ref={ref}
153
+ {...rest}
154
+ type="text"
155
+ inputMode={integer ? 'numeric' : 'decimal'}
156
+ value={text}
157
+ onFocus={(e) => {
158
+ if (selectOnFocus) e.currentTarget.select()
159
+ onFocus?.(e)
160
+ }}
161
+ onChange={handleChange}
162
+ onBlur={handleBlur}
163
+ />
164
+ )
165
+ },
166
+ )