@open-mercato/core 0.6.6-develop.6551.1.1511bfa890 → 0.6.6

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 (304) hide show
  1. package/.turbo/turbo-build.log +2 -2
  2. package/dist/generated/entities.ids.generated.js +0 -1
  3. package/dist/generated/entities.ids.generated.js.map +2 -2
  4. package/dist/generated/entity-fields-registry.js +0 -12
  5. package/dist/generated/entity-fields-registry.js.map +2 -2
  6. package/dist/modules/api_keys/api/keys/route.js +13 -30
  7. package/dist/modules/api_keys/api/keys/route.js.map +2 -2
  8. package/dist/modules/api_keys/cli.js +1 -2
  9. package/dist/modules/api_keys/cli.js.map +2 -2
  10. package/dist/modules/attachments/api/file/[id]/route.js +7 -9
  11. package/dist/modules/attachments/api/file/[id]/route.js.map +2 -2
  12. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js +7 -9
  13. package/dist/modules/attachments/api/image/[id]/[[...slug]]/route.js.map +2 -2
  14. package/dist/modules/attachments/api/route.js +17 -21
  15. package/dist/modules/attachments/api/route.js.map +2 -2
  16. package/dist/modules/attachments/components/AttachmentContentPreview.js +35 -6
  17. package/dist/modules/attachments/components/AttachmentContentPreview.js.map +2 -2
  18. package/dist/modules/audit_logs/backend/audit-logs/page.js +24 -14
  19. package/dist/modules/audit_logs/backend/audit-logs/page.js.map +2 -2
  20. package/dist/modules/auth/api/users/route.js +2 -6
  21. package/dist/modules/auth/api/users/route.js.map +2 -2
  22. package/dist/modules/auth/cli.js +2 -3
  23. package/dist/modules/auth/cli.js.map +2 -2
  24. package/dist/modules/configs/lib/upgrade-actions.js +0 -22
  25. package/dist/modules/configs/lib/upgrade-actions.js.map +2 -2
  26. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +0 -1
  27. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  28. package/dist/modules/customer_accounts/api/admin/roles/[id].js +3 -17
  29. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  30. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js +1 -6
  31. package/dist/modules/customer_accounts/api/portal/users/[id]/roles.js.map +2 -2
  32. package/dist/modules/customer_accounts/api/portal/users-invite.js +1 -6
  33. package/dist/modules/customer_accounts/api/portal/users-invite.js.map +2 -2
  34. package/dist/modules/customer_accounts/services/customerInvitationService.js +0 -1
  35. package/dist/modules/customer_accounts/services/customerInvitationService.js.map +2 -2
  36. package/dist/modules/customer_accounts/services/customerRbacService.js +1 -6
  37. package/dist/modules/customer_accounts/services/customerRbacService.js.map +2 -2
  38. package/dist/modules/customer_accounts/widgets/injection-table.js +2 -0
  39. package/dist/modules/customer_accounts/widgets/injection-table.js.map +2 -2
  40. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +1 -7
  41. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  42. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js +2 -2
  43. package/dist/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.js.map +2 -2
  44. package/dist/modules/customers/components/detail/CompanyDetailTabs.js +43 -37
  45. package/dist/modules/customers/components/detail/CompanyDetailTabs.js.map +2 -2
  46. package/dist/modules/customers/components/detail/DealDetailTabs.js +35 -23
  47. package/dist/modules/customers/components/detail/DealDetailTabs.js.map +2 -2
  48. package/dist/modules/customers/components/detail/DetailTabsLayout.js +21 -6
  49. package/dist/modules/customers/components/detail/DetailTabsLayout.js.map +2 -2
  50. package/dist/modules/customers/components/detail/MobilePersonDetail.js +32 -26
  51. package/dist/modules/customers/components/detail/MobilePersonDetail.js.map +2 -2
  52. package/dist/modules/customers/components/detail/PersonDetailTabs.js +39 -29
  53. package/dist/modules/customers/components/detail/PersonDetailTabs.js.map +2 -2
  54. package/dist/modules/dashboards/api/roles/widgets/route.js +3 -22
  55. package/dist/modules/dashboards/api/roles/widgets/route.js.map +2 -2
  56. package/dist/modules/dashboards/api/users/widgets/route.js +3 -22
  57. package/dist/modules/dashboards/api/users/widgets/route.js.map +2 -2
  58. package/dist/modules/dashboards/cli.js +3 -4
  59. package/dist/modules/dashboards/cli.js.map +2 -2
  60. package/dist/modules/dashboards/lib/widgetAssignmentScope.js +1 -9
  61. package/dist/modules/dashboards/lib/widgetAssignmentScope.js.map +2 -2
  62. package/dist/modules/dictionaries/commands/entries.js +14 -7
  63. package/dist/modules/dictionaries/commands/entries.js.map +2 -2
  64. package/dist/modules/dictionaries/commands/entry-operations.js +16 -7
  65. package/dist/modules/dictionaries/commands/entry-operations.js.map +2 -2
  66. package/dist/modules/dictionaries/commands/factory.js +3 -9
  67. package/dist/modules/dictionaries/commands/factory.js.map +2 -2
  68. package/dist/modules/dictionaries/fields/dictionary.js +41 -56
  69. package/dist/modules/dictionaries/fields/dictionary.js.map +2 -2
  70. package/dist/modules/directory/utils/organizationScope.js +3 -10
  71. package/dist/modules/directory/utils/organizationScope.js.map +2 -2
  72. package/dist/modules/entities/api/records.js +4 -5
  73. package/dist/modules/entities/api/records.js.map +2 -2
  74. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js +0 -1
  75. package/dist/modules/entities/backend/entities/system/[entityId]/page.meta.js.map +2 -2
  76. package/dist/modules/entities/backend/entities/user/[entityId]/page.js +49 -49
  77. package/dist/modules/entities/backend/entities/user/[entityId]/page.js.map +2 -2
  78. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js +0 -1
  79. package/dist/modules/entities/backend/entities/user/[entityId]/page.meta.js.map +2 -2
  80. package/dist/modules/entities/cli.js +1 -2
  81. package/dist/modules/entities/cli.js.map +2 -2
  82. package/dist/modules/feature_toggles/api/check/boolean/route.js +14 -0
  83. package/dist/modules/feature_toggles/api/check/boolean/route.js.map +2 -2
  84. package/dist/modules/feature_toggles/defaults.json +0 -8
  85. package/dist/modules/integrations/backend/integrations/[id]/page.js +80 -9
  86. package/dist/modules/integrations/backend/integrations/[id]/page.js.map +2 -2
  87. package/dist/modules/payment_gateways/data/entities.js +0 -45
  88. package/dist/modules/payment_gateways/data/entities.js.map +2 -2
  89. package/dist/modules/payment_gateways/lib/gateway-service.js +38 -173
  90. package/dist/modules/payment_gateways/lib/gateway-service.js.map +3 -3
  91. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +14 -16
  92. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +2 -2
  93. package/dist/modules/planner/components/AvailabilityRulesEditor.js +30 -18
  94. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  95. package/dist/modules/query_index/api/status.js +8 -2
  96. package/dist/modules/query_index/api/status.js.map +2 -2
  97. package/dist/modules/resources/backend/resources/resources/[id]/page.js +24 -24
  98. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  99. package/dist/modules/sales/backend/sales/channels/create/page.js +0 -6
  100. package/dist/modules/sales/backend/sales/channels/create/page.js.map +2 -2
  101. package/dist/modules/sales/backend/sales/channels/create/page.meta.js +0 -2
  102. package/dist/modules/sales/backend/sales/channels/create/page.meta.js.map +2 -2
  103. package/dist/modules/sales/backend/sales/channels/offers/page.js +0 -6
  104. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  105. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js +0 -2
  106. package/dist/modules/sales/backend/sales/channels/offers/page.meta.js.map +2 -2
  107. package/dist/modules/sales/backend/sales/channels/page.js +0 -6
  108. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  109. package/dist/modules/sales/backend/sales/channels/page.meta.js +0 -2
  110. package/dist/modules/sales/backend/sales/channels/page.meta.js.map +2 -2
  111. package/dist/modules/sales/backend/sales/documents/[id]/page.js +4 -6
  112. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  113. package/dist/modules/sales/commands/documents.js +0 -44
  114. package/dist/modules/sales/commands/documents.js.map +2 -2
  115. package/dist/modules/sales/commands/returns.js +27 -2
  116. package/dist/modules/sales/commands/returns.js.map +2 -2
  117. package/dist/modules/sales/components/documents/AddressesSection.js +2 -6
  118. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  119. package/dist/modules/sales/components/documents/ItemsSection.js +0 -1
  120. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  121. package/dist/modules/sales/components/documents/LineItemDialog.js +0 -9
  122. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  123. package/dist/modules/sales/components/documents/SalesDocumentForm.js +3 -51
  124. package/dist/modules/sales/components/documents/SalesDocumentForm.js.map +2 -2
  125. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +8 -10
  126. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  127. package/dist/modules/sales/components/documents/ShipmentDialog.js +38 -59
  128. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  129. package/dist/modules/sales/lib/shipments/snapshots.js +1 -27
  130. package/dist/modules/sales/lib/shipments/snapshots.js.map +2 -2
  131. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +25 -22
  132. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  133. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +14 -12
  134. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  135. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js +29 -10
  136. package/dist/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.js.map +2 -2
  137. package/dist/modules/sync_excel/lib/adapters/customers.js +20 -19
  138. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  139. package/dist/modules/sync_excel/lib/parser.js +0 -185
  140. package/dist/modules/sync_excel/lib/parser.js.map +2 -2
  141. package/dist/modules/sync_excel/lib/upload-storage.js +1 -20
  142. package/dist/modules/sync_excel/lib/upload-storage.js.map +2 -2
  143. package/dist/modules/workflows/frontend/checkout-demo/page.js +8 -1
  144. package/dist/modules/workflows/frontend/checkout-demo/page.js.map +2 -2
  145. package/dist/modules/workflows/lib/activity-executor.js +1 -1
  146. package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
  147. package/dist/modules/workflows/lib/find-definition.js +18 -2
  148. package/dist/modules/workflows/lib/find-definition.js.map +2 -2
  149. package/dist/modules/workflows/lib/signal-handler.js +3 -2
  150. package/dist/modules/workflows/lib/signal-handler.js.map +2 -2
  151. package/dist/modules/workflows/lib/step-handler.js +4 -8
  152. package/dist/modules/workflows/lib/step-handler.js.map +2 -2
  153. package/dist/modules/workflows/lib/task-handler.js +3 -5
  154. package/dist/modules/workflows/lib/task-handler.js.map +2 -2
  155. package/dist/modules/workflows/lib/timer-handler.js +2 -1
  156. package/dist/modules/workflows/lib/timer-handler.js.map +2 -2
  157. package/dist/modules/workflows/lib/transition-handler.js +5 -13
  158. package/dist/modules/workflows/lib/transition-handler.js.map +2 -2
  159. package/dist/modules/workflows/lib/workflow-executor.js +11 -9
  160. package/dist/modules/workflows/lib/workflow-executor.js.map +2 -2
  161. package/dist/modules/workflows/migrations/Migration20260715120000.js +68 -0
  162. package/dist/modules/workflows/migrations/Migration20260715120000.js.map +7 -0
  163. package/dist/modules/workflows/migrations/Migration20260716120000.js +24 -0
  164. package/dist/modules/workflows/migrations/Migration20260716120000.js.map +7 -0
  165. package/dist/modules/workflows/workflows.js +2 -0
  166. package/dist/modules/workflows/workflows.js.map +2 -2
  167. package/generated/entities.ids.generated.ts +0 -1
  168. package/generated/entity-fields-registry.ts +0 -12
  169. package/package.json +8 -9
  170. package/src/modules/api_keys/api/keys/route.ts +13 -36
  171. package/src/modules/api_keys/cli.ts +3 -2
  172. package/src/modules/attachments/api/file/[id]/route.ts +7 -11
  173. package/src/modules/attachments/api/image/[id]/[[...slug]]/route.ts +7 -11
  174. package/src/modules/attachments/api/route.ts +21 -26
  175. package/src/modules/attachments/components/AttachmentContentPreview.tsx +44 -9
  176. package/src/modules/audit_logs/backend/audit-logs/page.tsx +22 -12
  177. package/src/modules/auth/api/users/route.ts +2 -11
  178. package/src/modules/auth/cli.ts +2 -3
  179. package/src/modules/configs/i18n/de.json +1 -5
  180. package/src/modules/configs/i18n/en.json +1 -5
  181. package/src/modules/configs/i18n/es.json +1 -5
  182. package/src/modules/configs/i18n/pl.json +1 -5
  183. package/src/modules/configs/lib/upgrade-actions.ts +0 -24
  184. package/src/modules/customer_accounts/AGENTS.md +6 -8
  185. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +0 -1
  186. package/src/modules/customer_accounts/api/admin/roles/[id].ts +3 -17
  187. package/src/modules/customer_accounts/api/portal/users/[id]/roles.ts +1 -6
  188. package/src/modules/customer_accounts/api/portal/users-invite.ts +1 -6
  189. package/src/modules/customer_accounts/services/customerInvitationService.ts +0 -1
  190. package/src/modules/customer_accounts/services/customerRbacService.ts +2 -9
  191. package/src/modules/customer_accounts/widgets/injection-table.ts +2 -0
  192. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +1 -11
  193. package/src/modules/customers/backend/customers/deals/pipeline/components/ViewTabsRow.tsx +2 -6
  194. package/src/modules/customers/components/detail/CompanyDetailTabs.tsx +55 -39
  195. package/src/modules/customers/components/detail/DealDetailTabs.tsx +49 -24
  196. package/src/modules/customers/components/detail/DetailTabsLayout.tsx +24 -13
  197. package/src/modules/customers/components/detail/MobilePersonDetail.tsx +31 -25
  198. package/src/modules/customers/components/detail/PersonDetailTabs.tsx +50 -29
  199. package/src/modules/dashboards/api/roles/widgets/route.ts +3 -22
  200. package/src/modules/dashboards/api/users/widgets/route.ts +3 -22
  201. package/src/modules/dashboards/cli.ts +9 -4
  202. package/src/modules/dashboards/lib/widgetAssignmentScope.ts +0 -18
  203. package/src/modules/dictionaries/commands/entries.ts +16 -7
  204. package/src/modules/dictionaries/commands/entry-operations.ts +18 -8
  205. package/src/modules/dictionaries/commands/factory.ts +2 -8
  206. package/src/modules/dictionaries/fields/dictionary.tsx +32 -55
  207. package/src/modules/dictionaries/i18n/de.json +0 -2
  208. package/src/modules/dictionaries/i18n/en.json +0 -2
  209. package/src/modules/dictionaries/i18n/es.json +0 -2
  210. package/src/modules/dictionaries/i18n/pl.json +0 -2
  211. package/src/modules/directory/utils/organizationScope.ts +2 -36
  212. package/src/modules/entities/api/records.ts +7 -5
  213. package/src/modules/entities/backend/entities/system/[entityId]/page.meta.ts +0 -1
  214. package/src/modules/entities/backend/entities/user/[entityId]/page.meta.ts +0 -1
  215. package/src/modules/entities/backend/entities/user/[entityId]/page.tsx +49 -51
  216. package/src/modules/entities/cli.ts +1 -2
  217. package/src/modules/entities/i18n/de.json +0 -34
  218. package/src/modules/entities/i18n/en.json +0 -34
  219. package/src/modules/entities/i18n/es.json +0 -34
  220. package/src/modules/entities/i18n/pl.json +0 -34
  221. package/src/modules/feature_toggles/api/check/boolean/route.ts +15 -0
  222. package/src/modules/feature_toggles/defaults.json +0 -8
  223. package/src/modules/integrations/backend/integrations/[id]/page.tsx +59 -16
  224. package/src/modules/payment_gateways/data/entities.ts +0 -39
  225. package/src/modules/payment_gateways/lib/gateway-service.ts +40 -211
  226. package/src/modules/payment_gateways/migrations/.snapshot-open-mercato.json +0 -236
  227. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +20 -14
  228. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +34 -16
  229. package/src/modules/query_index/api/status.ts +8 -2
  230. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +37 -27
  231. package/src/modules/sales/backend/sales/channels/create/page.meta.ts +0 -3
  232. package/src/modules/sales/backend/sales/channels/create/page.tsx +0 -7
  233. package/src/modules/sales/backend/sales/channels/offers/page.meta.ts +0 -3
  234. package/src/modules/sales/backend/sales/channels/offers/page.tsx +0 -7
  235. package/src/modules/sales/backend/sales/channels/page.meta.ts +0 -4
  236. package/src/modules/sales/backend/sales/channels/page.tsx +0 -7
  237. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +5 -7
  238. package/src/modules/sales/commands/documents.ts +0 -65
  239. package/src/modules/sales/commands/returns.ts +32 -2
  240. package/src/modules/sales/components/documents/AddressesSection.tsx +2 -6
  241. package/src/modules/sales/components/documents/ItemsSection.tsx +0 -5
  242. package/src/modules/sales/components/documents/LineItemDialog.tsx +0 -10
  243. package/src/modules/sales/components/documents/SalesDocumentForm.tsx +3 -53
  244. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +8 -10
  245. package/src/modules/sales/components/documents/ShipmentDialog.tsx +40 -65
  246. package/src/modules/sales/i18n/de.json +0 -5
  247. package/src/modules/sales/i18n/en.json +0 -5
  248. package/src/modules/sales/i18n/es.json +0 -5
  249. package/src/modules/sales/i18n/pl.json +0 -5
  250. package/src/modules/sales/lib/shipments/snapshots.ts +0 -30
  251. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +34 -25
  252. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +17 -11
  253. package/src/modules/staff/lib/timesheets-projects-ui/SavedViewTabs.tsx +28 -11
  254. package/src/modules/sync_excel/lib/adapters/customers.ts +22 -20
  255. package/src/modules/sync_excel/lib/parser.ts +0 -245
  256. package/src/modules/sync_excel/lib/upload-storage.ts +1 -23
  257. package/src/modules/workflows/frontend/checkout-demo/page.tsx +12 -1
  258. package/src/modules/workflows/lib/activity-executor.ts +22 -2
  259. package/src/modules/workflows/lib/find-definition.ts +53 -1
  260. package/src/modules/workflows/lib/signal-handler.ts +5 -4
  261. package/src/modules/workflows/lib/step-handler.ts +4 -8
  262. package/src/modules/workflows/lib/task-handler.ts +2 -4
  263. package/src/modules/workflows/lib/timer-handler.ts +3 -2
  264. package/src/modules/workflows/lib/transition-handler.ts +5 -13
  265. package/src/modules/workflows/lib/workflow-executor.ts +11 -8
  266. package/src/modules/workflows/migrations/Migration20260715120000.ts +114 -0
  267. package/src/modules/workflows/migrations/Migration20260716120000.ts +57 -0
  268. package/src/modules/workflows/workflows.ts +2 -0
  269. package/dist/generated/entities/gateway_session_initialization/index.js +0 -23
  270. package/dist/generated/entities/gateway_session_initialization/index.js.map +0 -7
  271. package/dist/modules/attachments/lib/reconcileOrganization.js +0 -110
  272. package/dist/modules/attachments/lib/reconcileOrganization.js.map +0 -7
  273. package/dist/modules/attachments/lib/requestScope.js +0 -10
  274. package/dist/modules/attachments/lib/requestScope.js.map +0 -7
  275. package/dist/modules/auth/api/autologin.js +0 -85
  276. package/dist/modules/auth/api/autologin.js.map +0 -7
  277. package/dist/modules/auth/lib/autologin.js +0 -19
  278. package/dist/modules/auth/lib/autologin.js.map +0 -7
  279. package/dist/modules/customers/components/detail/useDealsAccess.js +0 -16
  280. package/dist/modules/customers/components/detail/useDealsAccess.js.map +0 -7
  281. package/dist/modules/payment_gateways/lib/session-idempotency.js +0 -104
  282. package/dist/modules/payment_gateways/lib/session-idempotency.js.map +0 -7
  283. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js +0 -11
  284. package/dist/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.js.map +0 -7
  285. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js +0 -15
  286. package/dist/modules/sales/components/SalesChannelsDisabledNotice.js.map +0 -7
  287. package/dist/modules/sales/components/useSalesChannelsEnabled.js +0 -44
  288. package/dist/modules/sales/components/useSalesChannelsEnabled.js.map +0 -7
  289. package/dist/modules/sales/lib/salesChannelsToggle.js +0 -18
  290. package/dist/modules/sales/lib/salesChannelsToggle.js.map +0 -7
  291. package/dist/modules/sales/lib/salesChannelsToggleId.js +0 -5
  292. package/dist/modules/sales/lib/salesChannelsToggleId.js.map +0 -7
  293. package/generated/entities/gateway_session_initialization/index.ts +0 -10
  294. package/src/modules/attachments/lib/reconcileOrganization.ts +0 -188
  295. package/src/modules/attachments/lib/requestScope.ts +0 -27
  296. package/src/modules/auth/api/autologin.ts +0 -100
  297. package/src/modules/auth/lib/autologin.ts +0 -36
  298. package/src/modules/customers/components/detail/useDealsAccess.ts +0 -19
  299. package/src/modules/payment_gateways/lib/session-idempotency.ts +0 -142
  300. package/src/modules/payment_gateways/migrations/Migration20260709220938_payment_gateways.ts +0 -10
  301. package/src/modules/sales/components/SalesChannelsDisabledNotice.tsx +0 -21
  302. package/src/modules/sales/components/useSalesChannelsEnabled.ts +0 -59
  303. package/src/modules/sales/lib/salesChannelsToggle.ts +0 -26
  304. package/src/modules/sales/lib/salesChannelsToggleId.ts +0 -1
@@ -2,7 +2,6 @@
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { Button } from "@open-mercato/ui/primitives/button";
5
- import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
6
5
  import { cn } from "@open-mercato/shared/lib/utils";
7
6
  import { Plus } from "lucide-react";
8
7
  function DetailTabsLayout({
@@ -26,12 +25,28 @@ function DetailTabsLayout({
26
25
  return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
27
26
  /* @__PURE__ */ jsxs("div", { className: cn("flex flex-wrap items-center justify-between gap-3", headerClassName), children: [
28
27
  /* @__PURE__ */ jsx(
29
- Tabs,
28
+ "nav",
30
29
  {
31
- value: activeTab,
32
- onValueChange: (value) => handleTabChange(value),
33
- variant: "underline",
34
- children: /* @__PURE__ */ jsx(TabsList, { className: cn("h-auto flex-wrap border-b-0", navClassName), "aria-label": navAriaLabel, children: tabs.map((tab) => /* @__PURE__ */ jsx(TabsTrigger, { value: tab.id, children: tab.label }, tab.id)) })
30
+ className: cn("flex flex-wrap items-center gap-3 text-sm", navClassName),
31
+ role: "tablist",
32
+ "aria-label": navAriaLabel,
33
+ children: tabs.map((tab) => /* @__PURE__ */ jsx(
34
+ Button,
35
+ {
36
+ type: "button",
37
+ variant: "ghost",
38
+ size: "sm",
39
+ role: "tab",
40
+ "aria-selected": activeTab === tab.id,
41
+ onClick: () => handleTabChange(tab.id),
42
+ className: cn(
43
+ "h-auto rounded-none border-b-2 px-0 py-1",
44
+ activeTab === tab.id ? "border-accent-indigo text-foreground hover:bg-transparent" : "border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent"
45
+ ),
46
+ children: tab.label
47
+ },
48
+ tab.id
49
+ ))
35
50
  }
36
51
  ),
37
52
  sectionAction ? /* @__PURE__ */ jsxs(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/DetailTabsLayout.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { SectionAction } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Plus } from 'lucide-react'\n\nexport type DetailTabDefinition<TId extends string = string> = {\n id: TId\n label: React.ReactNode\n}\n\ntype DetailTabsLayoutProps<TId extends string = string> = {\n tabs: DetailTabDefinition<TId>[]\n activeTab: TId\n onTabChange: (id: TId) => void\n sectionAction: SectionAction | null\n onSectionAction: () => void\n navAriaLabel: string\n className?: string\n headerClassName?: string\n navClassName?: string\n children: React.ReactNode\n}\n\nexport function DetailTabsLayout<TId extends string = string>({\n tabs,\n activeTab,\n onTabChange,\n sectionAction,\n onSectionAction,\n navAriaLabel,\n className,\n headerClassName,\n navClassName,\n children,\n}: DetailTabsLayoutProps<TId>) {\n const handleTabChange = React.useCallback(\n (id: TId) => {\n onTabChange(id)\n },\n [onTabChange],\n )\n\n return (\n <div className={cn('space-y-4', className)}>\n <div className={cn('flex flex-wrap items-center justify-between gap-3', headerClassName)}>\n <Tabs\n value={activeTab}\n onValueChange={(value) => handleTabChange(value as TId)}\n variant=\"underline\"\n >\n <TabsList className={cn('h-auto flex-wrap border-b-0', navClassName)} aria-label={navAriaLabel}>\n {tabs.map((tab) => (\n <TabsTrigger key={tab.id} value={tab.id}>\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSectionAction}\n disabled={sectionAction.disabled}\n >\n {sectionAction.icon ?? (typeof sectionAction.label === 'string' || typeof sectionAction.label === 'number' ? (\n <Plus className=\"mr-2 h-4 w-4\" />\n ) : null)}\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n <div>{children}</div>\n </div>\n )\n}\n"],
5
- "mappings": ";AAwDc,cAOJ,YAPI;AAtDd,YAAY,WAAW;AAEvB,SAAS,cAAc;AACvB,SAAS,MAAM,UAAU,mBAAmB;AAC5C,SAAS,UAAU;AACnB,SAAS,YAAY;AAoBd,SAAS,iBAA8C;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA+B;AAC7B,QAAM,kBAAkB,MAAM;AAAA,IAC5B,CAAC,OAAY;AACX,kBAAY,EAAE;AAAA,IAChB;AAAA,IACA,CAAC,WAAW;AAAA,EACd;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACvC;AAAA,yBAAC,SAAI,WAAW,GAAG,qDAAqD,eAAe,GACrF;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAO;AAAA,UACP,eAAe,CAAC,UAAU,gBAAgB,KAAY;AAAA,UACtD,SAAQ;AAAA,UAER,8BAAC,YAAS,WAAW,GAAG,+BAA+B,YAAY,GAAG,cAAY,cAC/E,eAAK,IAAI,CAAC,QACT,oBAAC,eAAyB,OAAO,IAAI,IAClC,cAAI,SADW,IAAI,EAEtB,CACD,GACH;AAAA;AAAA,MACF;AAAA,MACC,gBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU,cAAc;AAAA,UAEvB;AAAA,0BAAc,SAAS,OAAO,cAAc,UAAU,YAAY,OAAO,cAAc,UAAU,WAChG,oBAAC,QAAK,WAAU,gBAAe,IAC7B;AAAA,YACH,cAAc;AAAA;AAAA;AAAA,MACjB,IACE;AAAA,OACN;AAAA,IACA,oBAAC,SAAK,UAAS;AAAA,KACjB;AAEJ;",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { SectionAction } from '@open-mercato/ui/backend/detail'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Plus } from 'lucide-react'\n\nexport type DetailTabDefinition<TId extends string = string> = {\n id: TId\n label: React.ReactNode\n}\n\ntype DetailTabsLayoutProps<TId extends string = string> = {\n tabs: DetailTabDefinition<TId>[]\n activeTab: TId\n onTabChange: (id: TId) => void\n sectionAction: SectionAction | null\n onSectionAction: () => void\n navAriaLabel: string\n className?: string\n headerClassName?: string\n navClassName?: string\n children: React.ReactNode\n}\n\nexport function DetailTabsLayout<TId extends string = string>({\n tabs,\n activeTab,\n onTabChange,\n sectionAction,\n onSectionAction,\n navAriaLabel,\n className,\n headerClassName,\n navClassName,\n children,\n}: DetailTabsLayoutProps<TId>) {\n const handleTabChange = React.useCallback(\n (id: TId) => {\n onTabChange(id)\n },\n [onTabChange],\n )\n\n return (\n <div className={cn('space-y-4', className)}>\n <div className={cn('flex flex-wrap items-center justify-between gap-3', headerClassName)}>\n <nav\n className={cn('flex flex-wrap items-center gap-3 text-sm', navClassName)}\n role=\"tablist\"\n aria-label={navAriaLabel}\n >\n {tabs.map((tab) => (\n <Button\n key={tab.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={activeTab === tab.id}\n onClick={() => handleTabChange(tab.id)}\n className={cn(\n 'h-auto rounded-none border-b-2 px-0 py-1',\n activeTab === tab.id\n ? 'border-accent-indigo text-foreground hover:bg-transparent'\n : 'border-transparent text-muted-foreground hover:text-foreground hover:bg-transparent'\n )}\n >\n {tab.label}\n </Button>\n ))}\n </nav>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSectionAction}\n disabled={sectionAction.disabled}\n >\n {sectionAction.icon ?? (typeof sectionAction.label === 'string' || typeof sectionAction.label === 'number' ? (\n <Plus className=\"mr-2 h-4 w-4\" />\n ) : null)}\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n <div>{children}</div>\n </div>\n )\n}\n"],
5
+ "mappings": ";AAsDY,cAoBF,YApBE;AApDZ,YAAY,WAAW;AAEvB,SAAS,cAAc;AACvB,SAAS,UAAU;AACnB,SAAS,YAAY;AAoBd,SAAS,iBAA8C;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA+B;AAC7B,QAAM,kBAAkB,MAAM;AAAA,IAC5B,CAAC,OAAY;AACX,kBAAY,EAAE;AAAA,IAChB;AAAA,IACA,CAAC,WAAW;AAAA,EACd;AAEA,SACE,qBAAC,SAAI,WAAW,GAAG,aAAa,SAAS,GACvC;AAAA,yBAAC,SAAI,WAAW,GAAG,qDAAqD,eAAe,GACrF;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,GAAG,6CAA6C,YAAY;AAAA,UACvE,MAAK;AAAA,UACL,cAAY;AAAA,UAEX,eAAK,IAAI,CAAC,QACT;AAAA,YAAC;AAAA;AAAA,cAEC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,MAAK;AAAA,cACL,iBAAe,cAAc,IAAI;AAAA,cACjC,SAAS,MAAM,gBAAgB,IAAI,EAAE;AAAA,cACrC,WAAW;AAAA,gBACT;AAAA,gBACA,cAAc,IAAI,KACd,8DACA;AAAA,cACN;AAAA,cAEC,cAAI;AAAA;AAAA,YAdA,IAAI;AAAA,UAeX,CACD;AAAA;AAAA,MACH;AAAA,MACC,gBACC;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU,cAAc;AAAA,UAEvB;AAAA,0BAAc,SAAS,OAAO,cAAc,UAAU,YAAY,OAAO,cAAc,UAAU,WAChG,oBAAC,QAAK,WAAU,gBAAe,IAC7B;AAAA,YACH,cAAc;AAAA;AAAA;AAAA,MACjB,IACE;AAAA,OACN;AAAA,IACA,oBAAC,SAAK,UAAS;AAAA,KACjB;AAEJ;",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,8 @@ import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import { useRouter, useSearchParams } from "next/navigation";
5
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
6
- import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
6
+ import { cn } from "@open-mercato/shared/lib/utils";
7
+ import { Button } from "@open-mercato/ui/primitives/button";
7
8
  const ZONES = [
8
9
  { id: "details", labelKey: "customers.people.mobile.zoneSwitcher.details", fallback: "Details" },
9
10
  { id: "activity", labelKey: "customers.people.mobile.zoneSwitcher.activity", fallback: "Activity" }
@@ -54,33 +55,38 @@ function MobilePersonDetail({
54
55
  [handleZoneChange, zone]
55
56
  );
56
57
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
57
- /* @__PURE__ */ jsx("div", { onKeyDown: handleKeyDown, children: /* @__PURE__ */ jsx(
58
- Tabs,
58
+ /* @__PURE__ */ jsx(
59
+ "div",
59
60
  {
60
- value: zone,
61
- onValueChange: (value) => handleZoneChange(value),
62
- variant: "underline",
63
- children: /* @__PURE__ */ jsx(
64
- TabsList,
65
- {
66
- "aria-label": t(
67
- "customers.people.mobile.zoneSwitcher.ariaLabel",
68
- "Zone selector"
69
- ),
70
- className: "w-full",
71
- children: ZONES.map((entry) => /* @__PURE__ */ jsx(
72
- TabsTrigger,
73
- {
74
- value: entry.id,
75
- className: "flex-1 justify-center",
76
- children: t(entry.labelKey, entry.fallback)
77
- },
78
- entry.id
79
- ))
80
- }
81
- )
61
+ role: "tablist",
62
+ "aria-label": t(
63
+ "customers.people.mobile.zoneSwitcher.ariaLabel",
64
+ "Zone selector"
65
+ ),
66
+ className: "grid grid-cols-2 gap-1 rounded-lg border border-border/70 bg-muted/20 p-1",
67
+ onKeyDown: handleKeyDown,
68
+ children: ZONES.map((entry) => {
69
+ const isActive = zone === entry.id;
70
+ return /* @__PURE__ */ jsx(
71
+ Button,
72
+ {
73
+ type: "button",
74
+ role: "tab",
75
+ "aria-selected": isActive,
76
+ "aria-controls": `mobile-person-zone-${entry.id}`,
77
+ variant: isActive ? "default" : "ghost",
78
+ className: cn(
79
+ "h-11 rounded-lg text-sm font-semibold",
80
+ isActive ? "" : "text-muted-foreground"
81
+ ),
82
+ onClick: () => handleZoneChange(entry.id),
83
+ children: t(entry.labelKey, entry.fallback)
84
+ },
85
+ entry.id
86
+ );
87
+ })
82
88
  }
83
- ) }),
89
+ ),
84
90
  /* @__PURE__ */ jsx(
85
91
  "div",
86
92
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/MobilePersonDetail.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\n\nexport type MobilePersonZone = 'details' | 'activity'\n\nexport type MobilePersonDetailProps = {\n zone1: React.ReactNode\n zone2: React.ReactNode\n defaultZone?: MobilePersonZone\n /**\n * Controls whether zone state is synced to the URL `?zone=` query param.\n * Defaults to true on the page route so deep links round-trip.\n */\n syncToUrl?: boolean\n}\n\nconst ZONES: Array<{ id: MobilePersonZone; labelKey: string; fallback: string }> = [\n { id: 'details', labelKey: 'customers.people.mobile.zoneSwitcher.details', fallback: 'Details' },\n { id: 'activity', labelKey: 'customers.people.mobile.zoneSwitcher.activity', fallback: 'Activity' },\n]\n\nexport function MobilePersonDetail({\n zone1,\n zone2,\n defaultZone = 'details',\n syncToUrl = true,\n}: MobilePersonDetailProps) {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n\n const initialZone = React.useMemo<MobilePersonZone>(() => {\n if (!syncToUrl) return defaultZone\n const raw = searchParams?.get('zone')\n if (raw === 'activity' || raw === 'details') return raw\n return defaultZone\n // Intentionally compute only from the initial searchParams snapshot; external URL updates\n // (browser back, manual edit) are acceptable to be out of sync on mobile \u2014 local clicks\n // own the zone state thereafter.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const [zone, setZone] = React.useState<MobilePersonZone>(initialZone)\n const lastWrittenZoneRef = React.useRef<MobilePersonZone | null>(null)\n\n const handleZoneChange = React.useCallback(\n (next: MobilePersonZone) => {\n setZone(next)\n lastWrittenZoneRef.current = next\n if (!syncToUrl) return\n const params = new URLSearchParams(searchParams?.toString() ?? '')\n if (next === defaultZone) {\n params.delete('zone')\n } else {\n params.set('zone', next)\n }\n const query = params.toString()\n router.replace(query.length ? `?${query}` : '?', { scroll: false })\n },\n [defaultZone, router, searchParams, syncToUrl],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return\n event.preventDefault()\n const currentIndex = ZONES.findIndex((entry) => entry.id === zone)\n if (currentIndex < 0) return\n const offset = event.key === 'ArrowRight' ? 1 : -1\n const nextIndex = (currentIndex + offset + ZONES.length) % ZONES.length\n handleZoneChange(ZONES[nextIndex].id)\n },\n [handleZoneChange, zone],\n )\n\n return (\n <div className=\"space-y-4\">\n <div onKeyDown={handleKeyDown}>\n <Tabs\n value={zone}\n onValueChange={(value) => handleZoneChange(value as MobilePersonZone)}\n variant=\"underline\"\n >\n <TabsList\n aria-label={t(\n 'customers.people.mobile.zoneSwitcher.ariaLabel',\n 'Zone selector',\n )}\n className=\"w-full\"\n >\n {ZONES.map((entry) => (\n <TabsTrigger\n key={entry.id}\n value={entry.id}\n className=\"flex-1 justify-center\"\n >\n {t(entry.labelKey, entry.fallback)}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n </div>\n <div\n id={`mobile-person-zone-${zone}`}\n role=\"tabpanel\"\n aria-labelledby={`mobile-person-zone-${zone}-tab`}\n >\n {zone === 'details' ? zone1 : zone2}\n </div>\n </div>\n )\n}\n\nexport default MobilePersonDetail\n"],
5
- "mappings": ";AAgFI,SAeU,KAfV;AA9EJ,YAAY,WAAW;AACvB,SAAS,WAAW,uBAAuB;AAC3C,SAAS,YAAY;AACrB,SAAS,MAAM,UAAU,mBAAmB;AAe5C,MAAM,QAA6E;AAAA,EACjF,EAAE,IAAI,WAAW,UAAU,gDAAgD,UAAU,UAAU;AAAA,EAC/F,EAAE,IAAI,YAAY,UAAU,iDAAiD,UAAU,WAAW;AACpG;AAEO,SAAS,mBAAmB;AAAA,EACjC;AAAA,EACA;AAAA,EACA,cAAc;AAAA,EACd,YAAY;AACd,GAA4B;AAC1B,QAAM,IAAI,KAAK;AACf,QAAM,SAAS,UAAU;AACzB,QAAM,eAAe,gBAAgB;AAErC,QAAM,cAAc,MAAM,QAA0B,MAAM;AACxD,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,MAAM,cAAc,IAAI,MAAM;AACpC,QAAI,QAAQ,cAAc,QAAQ,UAAW,QAAO;AACpD,WAAO;AAAA,EAKT,GAAG,CAAC,CAAC;AAEL,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAA2B,WAAW;AACpE,QAAM,qBAAqB,MAAM,OAAgC,IAAI;AAErE,QAAM,mBAAmB,MAAM;AAAA,IAC7B,CAAC,SAA2B;AAC1B,cAAQ,IAAI;AACZ,yBAAmB,UAAU;AAC7B,UAAI,CAAC,UAAW;AAChB,YAAM,SAAS,IAAI,gBAAgB,cAAc,SAAS,KAAK,EAAE;AACjE,UAAI,SAAS,aAAa;AACxB,eAAO,OAAO,MAAM;AAAA,MACtB,OAAO;AACL,eAAO,IAAI,QAAQ,IAAI;AAAA,MACzB;AACA,YAAM,QAAQ,OAAO,SAAS;AAC9B,aAAO,QAAQ,MAAM,SAAS,IAAI,KAAK,KAAK,KAAK,EAAE,QAAQ,MAAM,CAAC;AAAA,IACpE;AAAA,IACA,CAAC,aAAa,QAAQ,cAAc,SAAS;AAAA,EAC/C;AAEA,QAAM,gBAAgB,MAAM;AAAA,IAC1B,CAAC,UAA+C;AAC9C,UAAI,MAAM,QAAQ,eAAe,MAAM,QAAQ,aAAc;AAC7D,YAAM,eAAe;AACrB,YAAM,eAAe,MAAM,UAAU,CAAC,UAAU,MAAM,OAAO,IAAI;AACjE,UAAI,eAAe,EAAG;AACtB,YAAM,SAAS,MAAM,QAAQ,eAAe,IAAI;AAChD,YAAM,aAAa,eAAe,SAAS,MAAM,UAAU,MAAM;AACjE,uBAAiB,MAAM,SAAS,EAAE,EAAE;AAAA,IACtC;AAAA,IACA,CAAC,kBAAkB,IAAI;AAAA,EACzB;AAEA,SACE,qBAAC,SAAI,WAAU,aACb;AAAA,wBAAC,SAAI,WAAW,eACd;AAAA,MAAC;AAAA;AAAA,QACC,OAAO;AAAA,QACP,eAAe,CAAC,UAAU,iBAAiB,KAAyB;AAAA,QACpE,SAAQ;AAAA,QAER;AAAA,UAAC;AAAA;AAAA,YACC,cAAY;AAAA,cACV;AAAA,cACA;AAAA,YACF;AAAA,YACA,WAAU;AAAA,YAET,gBAAM,IAAI,CAAC,UACV;AAAA,cAAC;AAAA;AAAA,gBAEC,OAAO,MAAM;AAAA,gBACb,WAAU;AAAA,gBAET,YAAE,MAAM,UAAU,MAAM,QAAQ;AAAA;AAAA,cAJ5B,MAAM;AAAA,YAKb,CACD;AAAA;AAAA,QACH;AAAA;AAAA,IACF,GACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,IAAI,sBAAsB,IAAI;AAAA,QAC9B,MAAK;AAAA,QACL,mBAAiB,sBAAsB,IAAI;AAAA,QAE1C,mBAAS,YAAY,QAAQ;AAAA;AAAA,IAChC;AAAA,KACF;AAEJ;AAEA,IAAO,6BAAQ;",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useRouter, useSearchParams } from 'next/navigation'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { Button } from '@open-mercato/ui/primitives/button'\n\nexport type MobilePersonZone = 'details' | 'activity'\n\nexport type MobilePersonDetailProps = {\n zone1: React.ReactNode\n zone2: React.ReactNode\n defaultZone?: MobilePersonZone\n /**\n * Controls whether zone state is synced to the URL `?zone=` query param.\n * Defaults to true on the page route so deep links round-trip.\n */\n syncToUrl?: boolean\n}\n\nconst ZONES: Array<{ id: MobilePersonZone; labelKey: string; fallback: string }> = [\n { id: 'details', labelKey: 'customers.people.mobile.zoneSwitcher.details', fallback: 'Details' },\n { id: 'activity', labelKey: 'customers.people.mobile.zoneSwitcher.activity', fallback: 'Activity' },\n]\n\nexport function MobilePersonDetail({\n zone1,\n zone2,\n defaultZone = 'details',\n syncToUrl = true,\n}: MobilePersonDetailProps) {\n const t = useT()\n const router = useRouter()\n const searchParams = useSearchParams()\n\n const initialZone = React.useMemo<MobilePersonZone>(() => {\n if (!syncToUrl) return defaultZone\n const raw = searchParams?.get('zone')\n if (raw === 'activity' || raw === 'details') return raw\n return defaultZone\n // Intentionally compute only from the initial searchParams snapshot; external URL updates\n // (browser back, manual edit) are acceptable to be out of sync on mobile \u2014 local clicks\n // own the zone state thereafter.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const [zone, setZone] = React.useState<MobilePersonZone>(initialZone)\n const lastWrittenZoneRef = React.useRef<MobilePersonZone | null>(null)\n\n const handleZoneChange = React.useCallback(\n (next: MobilePersonZone) => {\n setZone(next)\n lastWrittenZoneRef.current = next\n if (!syncToUrl) return\n const params = new URLSearchParams(searchParams?.toString() ?? '')\n if (next === defaultZone) {\n params.delete('zone')\n } else {\n params.set('zone', next)\n }\n const query = params.toString()\n router.replace(query.length ? `?${query}` : '?', { scroll: false })\n },\n [defaultZone, router, searchParams, syncToUrl],\n )\n\n const handleKeyDown = React.useCallback(\n (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key !== 'ArrowLeft' && event.key !== 'ArrowRight') return\n event.preventDefault()\n const currentIndex = ZONES.findIndex((entry) => entry.id === zone)\n if (currentIndex < 0) return\n const offset = event.key === 'ArrowRight' ? 1 : -1\n const nextIndex = (currentIndex + offset + ZONES.length) % ZONES.length\n handleZoneChange(ZONES[nextIndex].id)\n },\n [handleZoneChange, zone],\n )\n\n return (\n <div className=\"space-y-4\">\n <div\n role=\"tablist\"\n aria-label={t(\n 'customers.people.mobile.zoneSwitcher.ariaLabel',\n 'Zone selector',\n )}\n className=\"grid grid-cols-2 gap-1 rounded-lg border border-border/70 bg-muted/20 p-1\"\n onKeyDown={handleKeyDown}\n >\n {ZONES.map((entry) => {\n const isActive = zone === entry.id\n return (\n <Button\n key={entry.id}\n type=\"button\"\n role=\"tab\"\n aria-selected={isActive}\n aria-controls={`mobile-person-zone-${entry.id}`}\n variant={isActive ? 'default' : 'ghost'}\n className={cn(\n 'h-11 rounded-lg text-sm font-semibold',\n isActive ? '' : 'text-muted-foreground',\n )}\n onClick={() => handleZoneChange(entry.id)}\n >\n {t(entry.labelKey, entry.fallback)}\n </Button>\n )\n })}\n </div>\n <div\n id={`mobile-person-zone-${zone}`}\n role=\"tabpanel\"\n aria-labelledby={`mobile-person-zone-${zone}-tab`}\n >\n {zone === 'details' ? zone1 : zone2}\n </div>\n </div>\n )\n}\n\nexport default MobilePersonDetail\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,9 +1,9 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
+ import { cn } from "@open-mercato/shared/lib/utils";
4
5
  import { useT } from "@open-mercato/shared/lib/i18n/context";
5
6
  import { Button } from "@open-mercato/ui/primitives/button";
6
- import { Tabs, TabsList, TabsTrigger } from "@open-mercato/ui/primitives/tabs";
7
7
  import {
8
8
  SquareCheckBig,
9
9
  Mail,
@@ -20,9 +20,12 @@ function resolveLegacyTab(tab) {
20
20
  if (!tab) return "activities";
21
21
  return SUPPORTED_TAB_IDS.has(tab) ? tab : "activities";
22
22
  }
23
- function formatTabCount(count) {
24
- if (count <= 0) return void 0;
25
- return count > 999 ? "999+" : count;
23
+ function CountBadge({ count }) {
24
+ if (count <= 0) return null;
25
+ return /* @__PURE__ */ jsx("span", { className: "ml-1 rounded-full bg-muted px-1.5 py-0.5 text-xs font-medium leading-none text-muted-foreground", children: count > 999 ? "999+" : count });
26
+ }
27
+ function NewBadge() {
28
+ return /* @__PURE__ */ jsx("span", { className: "ml-1.5 rounded bg-foreground px-1.5 py-0.5 text-overline font-semibold leading-none text-background", children: "NEW" });
26
29
  }
27
30
  function PersonDetailTabs({
28
31
  activeTab,
@@ -44,7 +47,7 @@ function PersonDetailTabs({
44
47
  id: "activities",
45
48
  label: t("customers.people.detail.tabs.activities", "Activities"),
46
49
  icon: /* @__PURE__ */ jsx(SquareCheckBig, { className: "size-4" }),
47
- count: formatTabCount(activitiesCount)
50
+ badge: /* @__PURE__ */ jsx(CountBadge, { count: activitiesCount })
48
51
  },
49
52
  {
50
53
  id: "emails",
@@ -55,37 +58,37 @@ function PersonDetailTabs({
55
58
  id: "deals",
56
59
  label: t("customers.people.detail.tabs.deals", "Deals"),
57
60
  icon: /* @__PURE__ */ jsx(Briefcase, { className: "size-4" }),
58
- count: formatTabCount(dealsCount)
61
+ badge: /* @__PURE__ */ jsx(CountBadge, { count: dealsCount })
59
62
  },
60
63
  {
61
64
  id: "companies",
62
65
  label: t("customers.people.detail.tabs.companies", "Companies"),
63
66
  icon: /* @__PURE__ */ jsx(Building2, { className: "size-4" }),
64
- count: formatTabCount(companiesCount)
67
+ badge: /* @__PURE__ */ jsx(CountBadge, { count: companiesCount })
65
68
  },
66
69
  {
67
70
  id: "addresses",
68
71
  label: t("customers.people.detail.tabs.addresses", "Addresses"),
69
72
  icon: /* @__PURE__ */ jsx(MapPin, { className: "size-4" }),
70
- count: formatTabCount(addressesCount)
73
+ badge: /* @__PURE__ */ jsx(CountBadge, { count: addressesCount })
71
74
  },
72
75
  {
73
76
  id: "tasks",
74
77
  label: t("customers.people.detail.tabs.tasks", "Tasks"),
75
78
  icon: /* @__PURE__ */ jsx(Check, { className: "size-4" }),
76
- count: formatTabCount(tasksCount)
79
+ badge: /* @__PURE__ */ jsx(CountBadge, { count: tasksCount })
77
80
  },
78
81
  {
79
82
  id: "changelog",
80
83
  label: t("customers.people.detail.tabs.changelog", "Change log"),
81
84
  icon: /* @__PURE__ */ jsx(History, { className: "size-4" }),
82
- count: "NEW"
85
+ badge: /* @__PURE__ */ jsx(NewBadge, {})
83
86
  },
84
87
  {
85
88
  id: "files",
86
89
  label: t("customers.people.detail.tabs.files", "Files"),
87
90
  icon: /* @__PURE__ */ jsx(Paperclip, { className: "size-4" }),
88
- count: formatTabCount(filesCount)
91
+ badge: /* @__PURE__ */ jsx(CountBadge, { count: filesCount })
89
92
  }
90
93
  ],
91
94
  [t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount]
@@ -101,24 +104,31 @@ function PersonDetailTabs({
101
104
  [builtInTabs, injectedTabs]
102
105
  );
103
106
  return /* @__PURE__ */ jsxs("div", { children: [
104
- /* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-2 border-b", children: [
105
- /* @__PURE__ */ jsx(
106
- Tabs,
107
- {
108
- value: activeTab,
109
- onValueChange: (value) => onTabChange(value),
110
- variant: "underline",
111
- className: "min-w-0 flex-1",
112
- children: /* @__PURE__ */ jsx(
113
- TabsList,
114
- {
115
- "aria-label": t("customers.people.detail.tabs.label", "Person detail sections"),
116
- className: "-mb-px w-full overflow-x-auto border-b-0 px-1",
117
- children: allTabs.map((tab) => /* @__PURE__ */ jsx(TabsTrigger, { value: tab.id, leading: tab.icon, count: tab.count, children: tab.label }, tab.id))
118
- }
119
- )
120
- }
121
- ),
107
+ /* @__PURE__ */ jsxs("div", { className: "flex items-end justify-between gap-2 border-b", role: "tablist", "aria-label": t("customers.people.detail.tabs.label", "Person detail sections"), children: [
108
+ /* @__PURE__ */ jsx("nav", { className: "-mb-px flex flex-1 gap-1 overflow-x-auto px-1", children: allTabs.map((tab) => {
109
+ const isActive = activeTab === tab.id;
110
+ return /* @__PURE__ */ jsxs(
111
+ Button,
112
+ {
113
+ type: "button",
114
+ variant: "ghost",
115
+ size: "sm",
116
+ role: "tab",
117
+ "aria-selected": isActive,
118
+ onClick: () => onTabChange(tab.id),
119
+ className: cn(
120
+ "h-auto shrink-0 rounded-none border-b-2 px-3 py-2.5 hover:bg-transparent",
121
+ isActive ? "border-foreground text-foreground font-semibold" : "border-transparent text-muted-foreground hover:text-foreground"
122
+ ),
123
+ children: [
124
+ tab.icon && /* @__PURE__ */ jsx("span", { className: "mr-1.5", children: tab.icon }),
125
+ tab.label,
126
+ tab.badge
127
+ ]
128
+ },
129
+ tab.id
130
+ );
131
+ }) }),
122
132
  sectionAction ? /* @__PURE__ */ jsxs(
123
133
  Button,
124
134
  {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/customers/components/detail/PersonDetailTabs.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Tabs, TabsList, TabsTrigger } from '@open-mercato/ui/primitives/tabs'\nimport {\n SquareCheckBig,\n Mail,\n Briefcase,\n Building2,\n Check,\n History,\n Paperclip,\n Plus,\n MapPin,\n} from 'lucide-react'\nimport type { SectionAction } from '@open-mercato/ui/backend/detail'\n\nexport type PersonTabId =\n | 'activities'\n | 'emails'\n | 'deals'\n | 'companies'\n | 'addresses'\n | 'tasks'\n | 'changelog'\n | 'files'\n | string\n\ntype TabDef = {\n id: PersonTabId\n label: string\n icon?: React.ReactNode\n count?: React.ReactNode\n}\n\ntype PersonDetailTabsProps = {\n activeTab: PersonTabId\n onTabChange: (tab: PersonTabId) => void\n injectedTabs?: Array<{ id: string; label: string }>\n activitiesCount?: number\n dealsCount?: number\n companiesCount?: number\n addressesCount?: number\n tasksCount?: number\n filesCount?: number\n sectionAction?: SectionAction | null\n children: React.ReactNode\n}\n\nconst SUPPORTED_TAB_IDS = new Set<PersonTabId>(['activities', 'emails', 'deals', 'companies', 'addresses', 'tasks', 'changelog', 'files'])\n\nexport function resolveLegacyTab(tab: string | null | undefined): PersonTabId {\n if (!tab) return 'activities'\n return SUPPORTED_TAB_IDS.has(tab as PersonTabId) ? (tab as PersonTabId) : 'activities'\n}\n\nfunction formatTabCount(count: number): string | number | undefined {\n if (count <= 0) return undefined\n return count > 999 ? '999+' : count\n}\n\nexport function PersonDetailTabs({\n activeTab,\n onTabChange,\n injectedTabs = [],\n activitiesCount = 0,\n dealsCount = 0,\n companiesCount = 0,\n addressesCount = 0,\n tasksCount = 0,\n filesCount = 0,\n sectionAction = null,\n children,\n}: PersonDetailTabsProps) {\n const t = useT()\n\n const builtInTabs: TabDef[] = React.useMemo(\n () => [\n {\n id: 'activities',\n label: t('customers.people.detail.tabs.activities', 'Activities'),\n icon: <SquareCheckBig className=\"size-4\" />,\n count: formatTabCount(activitiesCount),\n },\n {\n id: 'emails',\n label: t('customers.people.detail.tabs.emails', 'Emails'),\n icon: <Mail className=\"size-4\" />,\n },\n {\n id: 'deals',\n label: t('customers.people.detail.tabs.deals', 'Deals'),\n icon: <Briefcase className=\"size-4\" />,\n count: formatTabCount(dealsCount),\n },\n {\n id: 'companies',\n label: t('customers.people.detail.tabs.companies', 'Companies'),\n icon: <Building2 className=\"size-4\" />,\n count: formatTabCount(companiesCount),\n },\n {\n id: 'addresses',\n label: t('customers.people.detail.tabs.addresses', 'Addresses'),\n icon: <MapPin className=\"size-4\" />,\n count: formatTabCount(addressesCount),\n },\n {\n id: 'tasks',\n label: t('customers.people.detail.tabs.tasks', 'Tasks'),\n icon: <Check className=\"size-4\" />,\n count: formatTabCount(tasksCount),\n },\n {\n id: 'changelog',\n label: t('customers.people.detail.tabs.changelog', 'Change log'),\n icon: <History className=\"size-4\" />,\n count: 'NEW',\n },\n {\n id: 'files',\n label: t('customers.people.detail.tabs.files', 'Files'),\n icon: <Paperclip className=\"size-4\" />,\n count: formatTabCount(filesCount),\n },\n ],\n [t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount],\n )\n\n const allTabs: TabDef[] = React.useMemo(\n () => [\n ...builtInTabs,\n ...injectedTabs.map((tab) => ({\n id: tab.id as PersonTabId,\n label: tab.label,\n })),\n ],\n [builtInTabs, injectedTabs],\n )\n\n return (\n <div>\n {/* Tab navigation \u2014 full width above both zones */}\n <div className=\"flex items-end justify-between gap-2 border-b\">\n <Tabs\n value={activeTab}\n onValueChange={(value) => onTabChange(value as PersonTabId)}\n variant=\"underline\"\n className=\"min-w-0 flex-1\"\n >\n <TabsList\n aria-label={t('customers.people.detail.tabs.label', 'Person detail sections')}\n className=\"-mb-px w-full overflow-x-auto border-b-0 px-1\"\n >\n {allTabs.map((tab) => (\n <TabsTrigger key={tab.id} value={tab.id} leading={tab.icon} count={tab.count}>\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={sectionAction.onClick}\n disabled={sectionAction.disabled}\n className=\"mb-1.5 mr-1 shrink-0\"\n >\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n\n {/* Two-column content below tabs */}\n <div className=\"pt-6\">\n {children}\n </div>\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { cn } from '@open-mercato/shared/lib/utils'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport {\n SquareCheckBig,\n Mail,\n Briefcase,\n Building2,\n Check,\n History,\n Paperclip,\n Plus,\n MapPin,\n} from 'lucide-react'\nimport type { SectionAction } from '@open-mercato/ui/backend/detail'\n\nexport type PersonTabId =\n | 'activities'\n | 'emails'\n | 'deals'\n | 'companies'\n | 'addresses'\n | 'tasks'\n | 'changelog'\n | 'files'\n | string\n\ntype TabDef = {\n id: PersonTabId\n label: string\n icon?: React.ReactNode\n badge?: React.ReactNode\n}\n\ntype PersonDetailTabsProps = {\n activeTab: PersonTabId\n onTabChange: (tab: PersonTabId) => void\n injectedTabs?: Array<{ id: string; label: string }>\n activitiesCount?: number\n dealsCount?: number\n companiesCount?: number\n addressesCount?: number\n tasksCount?: number\n filesCount?: number\n sectionAction?: SectionAction | null\n children: React.ReactNode\n}\n\nconst SUPPORTED_TAB_IDS = new Set<PersonTabId>(['activities', 'emails', 'deals', 'companies', 'addresses', 'tasks', 'changelog', 'files'])\n\nexport function resolveLegacyTab(tab: string | null | undefined): PersonTabId {\n if (!tab) return 'activities'\n return SUPPORTED_TAB_IDS.has(tab as PersonTabId) ? (tab as PersonTabId) : 'activities'\n}\n\nfunction CountBadge({ count }: { count: number }) {\n if (count <= 0) return null\n return (\n <span className=\"ml-1 rounded-full bg-muted px-1.5 py-0.5 text-xs font-medium leading-none text-muted-foreground\">\n {count > 999 ? '999+' : count}\n </span>\n )\n}\n\nfunction NewBadge() {\n return (\n <span className=\"ml-1.5 rounded bg-foreground px-1.5 py-0.5 text-overline font-semibold leading-none text-background\">\n NEW\n </span>\n )\n}\n\nexport function PersonDetailTabs({\n activeTab,\n onTabChange,\n injectedTabs = [],\n activitiesCount = 0,\n dealsCount = 0,\n companiesCount = 0,\n addressesCount = 0,\n tasksCount = 0,\n filesCount = 0,\n sectionAction = null,\n children,\n}: PersonDetailTabsProps) {\n const t = useT()\n\n const builtInTabs: TabDef[] = React.useMemo(\n () => [\n {\n id: 'activities',\n label: t('customers.people.detail.tabs.activities', 'Activities'),\n icon: <SquareCheckBig className=\"size-4\" />,\n badge: <CountBadge count={activitiesCount} />,\n },\n {\n id: 'emails',\n label: t('customers.people.detail.tabs.emails', 'Emails'),\n icon: <Mail className=\"size-4\" />,\n },\n {\n id: 'deals',\n label: t('customers.people.detail.tabs.deals', 'Deals'),\n icon: <Briefcase className=\"size-4\" />,\n badge: <CountBadge count={dealsCount} />,\n },\n {\n id: 'companies',\n label: t('customers.people.detail.tabs.companies', 'Companies'),\n icon: <Building2 className=\"size-4\" />,\n badge: <CountBadge count={companiesCount} />,\n },\n {\n id: 'addresses',\n label: t('customers.people.detail.tabs.addresses', 'Addresses'),\n icon: <MapPin className=\"size-4\" />,\n badge: <CountBadge count={addressesCount} />,\n },\n {\n id: 'tasks',\n label: t('customers.people.detail.tabs.tasks', 'Tasks'),\n icon: <Check className=\"size-4\" />,\n badge: <CountBadge count={tasksCount} />,\n },\n {\n id: 'changelog',\n label: t('customers.people.detail.tabs.changelog', 'Change log'),\n icon: <History className=\"size-4\" />,\n badge: <NewBadge />,\n },\n {\n id: 'files',\n label: t('customers.people.detail.tabs.files', 'Files'),\n icon: <Paperclip className=\"size-4\" />,\n badge: <CountBadge count={filesCount} />,\n },\n ],\n [t, activitiesCount, dealsCount, companiesCount, addressesCount, tasksCount, filesCount],\n )\n\n const allTabs: TabDef[] = React.useMemo(\n () => [\n ...builtInTabs,\n ...injectedTabs.map((tab) => ({\n id: tab.id as PersonTabId,\n label: tab.label,\n })),\n ],\n [builtInTabs, injectedTabs],\n )\n\n return (\n <div>\n {/* Tab navigation \u2014 full width above both zones */}\n <div className=\"flex items-end justify-between gap-2 border-b\" role=\"tablist\" aria-label={t('customers.people.detail.tabs.label', 'Person detail sections')}>\n <nav className=\"-mb-px flex flex-1 gap-1 overflow-x-auto px-1\">\n {allTabs.map((tab) => {\n const isActive = activeTab === tab.id\n return (\n <Button\n key={tab.id}\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n role=\"tab\"\n aria-selected={isActive}\n onClick={() => onTabChange(tab.id)}\n className={cn(\n 'h-auto shrink-0 rounded-none border-b-2 px-3 py-2.5 hover:bg-transparent',\n isActive\n ? 'border-foreground text-foreground font-semibold'\n : 'border-transparent text-muted-foreground hover:text-foreground',\n )}\n >\n {tab.icon && <span className=\"mr-1.5\">{tab.icon}</span>}\n {tab.label}\n {tab.badge}\n </Button>\n )\n })}\n </nav>\n {sectionAction ? (\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={sectionAction.onClick}\n disabled={sectionAction.disabled}\n className=\"mb-1.5 mr-1 shrink-0\"\n >\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {sectionAction.label}\n </Button>\n ) : null}\n </div>\n\n {/* Two-column content below tabs */}\n <div className=\"pt-6\">\n {children}\n </div>\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -6,10 +6,7 @@ import { DashboardRoleWidgets } from "@open-mercato/core/modules/dashboards/data
6
6
  import { Role } from "@open-mercato/core/modules/auth/data/entities";
7
7
  import { roleWidgetSettingsSchema } from "@open-mercato/core/modules/dashboards/data/validators";
8
8
  import { loadAllWidgets } from "@open-mercato/core/modules/dashboards/lib/widgets";
9
- import {
10
- resolveWidgetAssignmentReadScope,
11
- resolveWidgetAssignmentTargetAccess
12
- } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
9
+ import { resolveWidgetAssignmentReadScope } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
13
10
  import { hasFeature } from "@open-mercato/shared/security/features";
14
11
  import {
15
12
  runCrudMutationGuardAfterSuccess,
@@ -64,15 +61,7 @@ async function GET(req) {
64
61
  queryOrganizationId: url.searchParams.get("organizationId")
65
62
  });
66
63
  const role = await em.findOne(Role, { id: roleId, deletedAt: null });
67
- const access = resolveWidgetAssignmentTargetAccess({
68
- isSuperAdmin: !!acl.isSuperAdmin,
69
- scopeTenantId: tenantId,
70
- target: role
71
- });
72
- if (access === "forbidden") {
73
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
74
- }
75
- if (access === "not-found") {
64
+ if (!role || tenantId && role.tenantId !== tenantId) {
76
65
  return NextResponse.json({ error: "Role not found" }, { status: 404 });
77
66
  }
78
67
  const records = await em.find(DashboardRoleWidgets, { roleId, deletedAt: null });
@@ -114,15 +103,7 @@ async function PUT(req) {
114
103
  const tenantId = auth.tenantId ?? null;
115
104
  const organizationId = auth.orgId ?? null;
116
105
  const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null });
117
- const access = resolveWidgetAssignmentTargetAccess({
118
- isSuperAdmin: !!acl.isSuperAdmin,
119
- scopeTenantId: tenantId,
120
- target: role
121
- });
122
- if (access === "forbidden") {
123
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
124
- }
125
- if (access === "not-found") {
106
+ if (!role || tenantId && role.tenantId !== tenantId) {
126
107
  return NextResponse.json({ error: "Role not found" }, { status: 404 });
127
108
  }
128
109
  const guardResult = await validateCrudMutationGuard(container, {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/dashboards/api/roles/widgets/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { roleWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport {\n resolveWidgetAssignmentReadScope,\n resolveWidgetAssignmentTargetAccess,\n} from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardRoleWidgetsResponseSchema,\n dashboardRoleWidgetsUpdateResponseSchema,\n} from '../../openapi'\n\nconst FEATURE = 'dashboards.admin.assign-widgets'\nconst RESOURCE_KIND = 'dashboards.roleWidgets'\n\nfunction pickBestRecord(records: DashboardRoleWidgets[], tenantId: string | null, organizationId: string | null): DashboardRoleWidgets | null {\n let best: DashboardRoleWidgets | null = null\n let bestScore = -1\n for (const record of records) {\n if (record.deletedAt) continue\n if (record.tenantId && tenantId && record.tenantId !== tenantId) continue\n if (record.tenantId && !tenantId) continue\n if (record.organizationId && organizationId && record.organizationId !== organizationId) continue\n if (record.organizationId && !organizationId) continue\n const score = (record.tenantId ? 1 : 0) + (record.organizationId ? 2 : 0)\n if (score > bestScore) {\n best = record\n bestScore = score\n }\n }\n return best\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleId = url.searchParams.get('roleId')\n if (!roleId) return NextResponse.json({ error: 'roleId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const { tenantId, organizationId } = resolveWidgetAssignmentReadScope({\n auth: { tenantId: auth.tenantId ?? null, orgId: auth.orgId ?? null },\n isSuperAdmin: !!acl.isSuperAdmin,\n queryTenantId: url.searchParams.get('tenantId'),\n queryOrganizationId: url.searchParams.get('organizationId'),\n })\n\n const role = await em.findOne(Role, { id: roleId, deletedAt: null })\n const access = resolveWidgetAssignmentTargetAccess({\n isSuperAdmin: !!acl.isSuperAdmin,\n scopeTenantId: tenantId,\n target: role,\n })\n if (access === 'forbidden') {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (access === 'not-found') {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const records = await em.find(DashboardRoleWidgets, { roleId, deletedAt: null })\n const best = pickBestRecord(records, tenantId, organizationId)\n\n const response = {\n widgetIds: best ? best.widgetIdsJson : [],\n hasCustom: !!best,\n scope: {\n tenantId,\n organizationId,\n },\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = roleWidgetSettingsSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const widgets = await loadAllWidgets()\n const validWidgetIds = new Set(widgets.map((w) => w.metadata.id))\n const widgetIds = parsed.data.widgetIds.filter((id) => validWidgetIds.has(id))\n\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })\n const access = resolveWidgetAssignmentTargetAccess({\n isSuperAdmin: !!acl.isSuperAdmin,\n scopeTenantId: tenantId,\n target: role,\n })\n if (access === 'forbidden') {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n if (access === 'not-found') {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { roleId: parsed.data.roleId, widgetIds },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let record = await em.findOne(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!widgetIds.length) {\n if (record) {\n await em.remove(record).flush()\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true, widgetIds: [] })\n }\n\n if (!record) {\n record = em.create(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n widgetIdsJson: widgetIds,\n })\n em.persist(record)\n } else {\n record.widgetIdsJson = widgetIds\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, widgetIds })\n}\n\nconst roleWidgetsQuerySchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nconst roleWidgetsGetDoc: OpenApiMethodDoc = {\n summary: 'Fetch widget assignments for a role',\n description: 'Returns the widgets explicitly assigned to the given role together with the evaluation scope.',\n tags: [dashboardsTag],\n query: roleWidgetsQuerySchema,\n responses: [\n { status: 200, description: 'Current widget configuration for the role.', schema: dashboardRoleWidgetsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing role identifier', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nconst roleWidgetsPutDoc: OpenApiMethodDoc = {\n summary: 'Update widgets assigned to a role',\n description: 'Persists the widget list for a role within the provided tenant and organization scope.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: roleWidgetSettingsSchema,\n description: 'Role identifier and the widget ids that should remain assigned. Scope is derived from the authenticated session.',\n },\n responses: [\n { status: 200, description: 'Widgets updated successfully.', schema: dashboardRoleWidgetsUpdateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or unknown widgets', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage dashboard widgets assigned to a role',\n methods: {\n GET: roleWidgetsGetDoc,\n PUT: roleWidgetsPutDoc,\n },\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { z } from 'zod'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { DashboardRoleWidgets } from '@open-mercato/core/modules/dashboards/data/entities'\nimport { Role } from '@open-mercato/core/modules/auth/data/entities'\nimport { roleWidgetSettingsSchema } from '@open-mercato/core/modules/dashboards/data/validators'\nimport { loadAllWidgets } from '@open-mercato/core/modules/dashboards/lib/widgets'\nimport { resolveWidgetAssignmentReadScope } from '@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope'\nimport { hasFeature } from '@open-mercato/shared/security/features'\nimport {\n runCrudMutationGuardAfterSuccess,\n validateCrudMutationGuard,\n} from '@open-mercato/shared/lib/crud/mutation-guard'\nimport type { OpenApiMethodDoc, OpenApiRouteDoc } from '@open-mercato/shared/lib/openapi'\nimport {\n dashboardsTag,\n dashboardsErrorSchema,\n dashboardRoleWidgetsResponseSchema,\n dashboardRoleWidgetsUpdateResponseSchema,\n} from '../../openapi'\n\nconst FEATURE = 'dashboards.admin.assign-widgets'\nconst RESOURCE_KIND = 'dashboards.roleWidgets'\n\nfunction pickBestRecord(records: DashboardRoleWidgets[], tenantId: string | null, organizationId: string | null): DashboardRoleWidgets | null {\n let best: DashboardRoleWidgets | null = null\n let bestScore = -1\n for (const record of records) {\n if (record.deletedAt) continue\n if (record.tenantId && tenantId && record.tenantId !== tenantId) continue\n if (record.tenantId && !tenantId) continue\n if (record.organizationId && organizationId && record.organizationId !== organizationId) continue\n if (record.organizationId && !organizationId) continue\n const score = (record.tenantId ? 1 : 0) + (record.organizationId ? 2 : 0)\n if (score > bestScore) {\n best = record\n bestScore = score\n }\n }\n return best\n}\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: [FEATURE] },\n PUT: { requireAuth: true, requireFeatures: [FEATURE] },\n}\n\nexport async function GET(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n const url = new URL(req.url)\n const roleId = url.searchParams.get('roleId')\n if (!roleId) return NextResponse.json({ error: 'roleId is required' }, { status: 400 })\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as any\n const rbac = container.resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const { tenantId, organizationId } = resolveWidgetAssignmentReadScope({\n auth: { tenantId: auth.tenantId ?? null, orgId: auth.orgId ?? null },\n isSuperAdmin: !!acl.isSuperAdmin,\n queryTenantId: url.searchParams.get('tenantId'),\n queryOrganizationId: url.searchParams.get('organizationId'),\n })\n\n const role = await em.findOne(Role, { id: roleId, deletedAt: null })\n if (!role || (tenantId && role.tenantId !== tenantId)) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const records = await em.find(DashboardRoleWidgets, { roleId, deletedAt: null })\n const best = pickBestRecord(records, tenantId, organizationId)\n\n const response = {\n widgetIds: best ? best.widgetIdsJson : [],\n hasCustom: !!best,\n scope: {\n tenantId,\n organizationId,\n },\n }\n\n return NextResponse.json(response)\n}\n\nexport async function PUT(req: Request) {\n const auth = await getAuthFromRequest(req)\n if (!auth) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })\n\n let payload: unknown\n try {\n payload = await req.json()\n } catch {\n return NextResponse.json({ error: 'Invalid JSON body' }, { status: 400 })\n }\n const parsed = roleWidgetSettingsSchema.safeParse(payload)\n if (!parsed.success) {\n return NextResponse.json({ error: 'Invalid payload', issues: parsed.error.issues }, { status: 400 })\n }\n\n const container = await createRequestContainer()\n const { resolve } = container\n const em = resolve('em') as any\n const rbac = resolve('rbacService') as any\n const acl = await rbac.loadAcl(auth.sub, { tenantId: auth.tenantId ?? null, organizationId: auth.orgId ?? null })\n if (!acl.isSuperAdmin && !hasFeature(acl.features, FEATURE)) {\n return NextResponse.json({ error: 'Forbidden' }, { status: 403 })\n }\n\n const widgets = await loadAllWidgets()\n const validWidgetIds = new Set(widgets.map((w) => w.metadata.id))\n const widgetIds = parsed.data.widgetIds.filter((id) => validWidgetIds.has(id))\n\n const tenantId = auth.tenantId ?? null\n const organizationId = auth.orgId ?? null\n\n const role = await em.findOne(Role, { id: parsed.data.roleId, deletedAt: null })\n if (!role || (tenantId && role.tenantId !== tenantId)) {\n return NextResponse.json({ error: 'Role not found' }, { status: 404 })\n }\n\n const guardResult = await validateCrudMutationGuard(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n mutationPayload: { roleId: parsed.data.roleId, widgetIds },\n })\n if (guardResult && !guardResult.ok) {\n return NextResponse.json(guardResult.body, { status: guardResult.status })\n }\n\n let record = await em.findOne(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n deletedAt: null,\n })\n\n if (!widgetIds.length) {\n if (record) {\n await em.remove(record).flush()\n }\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n return NextResponse.json({ ok: true, widgetIds: [] })\n }\n\n if (!record) {\n record = em.create(DashboardRoleWidgets, {\n roleId: parsed.data.roleId,\n tenantId,\n organizationId,\n widgetIdsJson: widgetIds,\n })\n em.persist(record)\n } else {\n record.widgetIdsJson = widgetIds\n }\n await em.flush()\n\n if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {\n await runCrudMutationGuardAfterSuccess(container, {\n tenantId: tenantId ?? '',\n organizationId,\n userId: String(auth.sub),\n resourceKind: RESOURCE_KIND,\n resourceId: parsed.data.roleId,\n operation: 'update',\n requestMethod: req.method,\n requestHeaders: req.headers,\n metadata: guardResult.metadata ?? null,\n })\n }\n\n return NextResponse.json({ ok: true, widgetIds })\n}\n\nconst roleWidgetsQuerySchema = z.object({\n roleId: z.string().uuid(),\n tenantId: z.string().uuid().optional(),\n organizationId: z.string().uuid().optional(),\n})\n\nconst roleWidgetsGetDoc: OpenApiMethodDoc = {\n summary: 'Fetch widget assignments for a role',\n description: 'Returns the widgets explicitly assigned to the given role together with the evaluation scope.',\n tags: [dashboardsTag],\n query: roleWidgetsQuerySchema,\n responses: [\n { status: 200, description: 'Current widget configuration for the role.', schema: dashboardRoleWidgetsResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Missing role identifier', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nconst roleWidgetsPutDoc: OpenApiMethodDoc = {\n summary: 'Update widgets assigned to a role',\n description: 'Persists the widget list for a role within the provided tenant and organization scope.',\n tags: [dashboardsTag],\n requestBody: {\n contentType: 'application/json',\n schema: roleWidgetSettingsSchema,\n description: 'Role identifier and the widget ids that should remain assigned. Scope is derived from the authenticated session.',\n },\n responses: [\n { status: 200, description: 'Widgets updated successfully.', schema: dashboardRoleWidgetsUpdateResponseSchema },\n ],\n errors: [\n { status: 400, description: 'Invalid payload or unknown widgets', schema: dashboardsErrorSchema },\n { status: 401, description: 'Authentication required', schema: dashboardsErrorSchema },\n { status: 403, description: 'Insufficient permissions to manage role widgets', schema: dashboardsErrorSchema },\n ],\n}\n\nexport const openApi: OpenApiRouteDoc = {\n tag: dashboardsTag,\n summary: 'Manage dashboard widgets assigned to a role',\n methods: {\n GET: roleWidgetsGetDoc,\n PUT: roleWidgetsPutDoc,\n },\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -7,10 +7,7 @@ import { User } from "@open-mercato/core/modules/auth/data/entities";
7
7
  import { userWidgetSettingsSchema } from "@open-mercato/core/modules/dashboards/data/validators";
8
8
  import { loadAllWidgets } from "@open-mercato/core/modules/dashboards/lib/widgets";
9
9
  import { resolveAllowedWidgetIds } from "@open-mercato/core/modules/dashboards/lib/access";
10
- import {
11
- resolveWidgetAssignmentReadScope,
12
- resolveWidgetAssignmentTargetAccess
13
- } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
10
+ import { resolveWidgetAssignmentReadScope } from "@open-mercato/core/modules/dashboards/lib/widgetAssignmentScope";
14
11
  import { hasFeature } from "@open-mercato/shared/security/features";
15
12
  import {
16
13
  runCrudMutationGuardAfterSuccess,
@@ -48,15 +45,7 @@ async function GET(req) {
48
45
  queryOrganizationId: url.searchParams.get("organizationId")
49
46
  });
50
47
  const targetUserForRead = await em.findOne(User, { id: userId, deletedAt: null });
51
- const readAccess = resolveWidgetAssignmentTargetAccess({
52
- isSuperAdmin: !!acl.isSuperAdmin,
53
- scopeTenantId: tenantId,
54
- target: targetUserForRead
55
- });
56
- if (readAccess === "forbidden") {
57
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
58
- }
59
- if (readAccess === "not-found") {
48
+ if (!targetUserForRead || tenantId && (targetUserForRead.tenantId ?? null) !== tenantId) {
60
49
  return NextResponse.json({ error: "User not found" }, { status: 404 });
61
50
  }
62
51
  const widgets = await loadAllWidgets();
@@ -114,15 +103,7 @@ async function PUT(req) {
114
103
  const tenantId = auth.tenantId ?? null;
115
104
  const organizationId = auth.orgId ?? null;
116
105
  const targetUser = await em.findOne(User, { id: parsed.data.userId, deletedAt: null });
117
- const writeAccess = resolveWidgetAssignmentTargetAccess({
118
- isSuperAdmin: !!acl.isSuperAdmin,
119
- scopeTenantId: tenantId,
120
- target: targetUser
121
- });
122
- if (writeAccess === "forbidden") {
123
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
124
- }
125
- if (writeAccess === "not-found") {
106
+ if (!targetUser || tenantId && (targetUser.tenantId ?? null) !== tenantId) {
126
107
  return NextResponse.json({ error: "User not found" }, { status: 404 });
127
108
  }
128
109
  const guardResult = await validateCrudMutationGuard(container, {