@open-mercato/core 0.6.5-develop.4534.1.b459babe6d → 0.6.5-develop.4544.1.71c003c861

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 (633) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/AGENTS.md +5 -0
  3. package/dist/generated/entities/role/index.js +3 -1
  4. package/dist/generated/entities/role/index.js.map +2 -2
  5. package/dist/generated/entities/user/index.js +3 -1
  6. package/dist/generated/entities/user/index.js.map +2 -2
  7. package/dist/generated/entity-fields-registry.js +2 -0
  8. package/dist/generated/entity-fields-registry.js.map +2 -2
  9. package/dist/helpers/integration/optimisticLockUi.js +104 -0
  10. package/dist/helpers/integration/optimisticLockUi.js.map +7 -0
  11. package/dist/helpers/integration/salesFixtures.js +17 -0
  12. package/dist/helpers/integration/salesFixtures.js.map +2 -2
  13. package/dist/modules/api_keys/backend/api-keys/page.js +9 -5
  14. package/dist/modules/api_keys/backend/api-keys/page.js.map +2 -2
  15. package/dist/modules/attachments/components/AttachmentPartitionSettings.js +17 -9
  16. package/dist/modules/attachments/components/AttachmentPartitionSettings.js.map +2 -2
  17. package/dist/modules/auth/api/roles/acl/route.js +32 -13
  18. package/dist/modules/auth/api/roles/acl/route.js.map +2 -2
  19. package/dist/modules/auth/api/roles/route.js +3 -1
  20. package/dist/modules/auth/api/roles/route.js.map +2 -2
  21. package/dist/modules/auth/api/sidebar/preferences/route.js +71 -3
  22. package/dist/modules/auth/api/sidebar/preferences/route.js.map +2 -2
  23. package/dist/modules/auth/api/users/acl/route.js +42 -19
  24. package/dist/modules/auth/api/users/acl/route.js.map +2 -2
  25. package/dist/modules/auth/api/users/route.js +3 -1
  26. package/dist/modules/auth/api/users/route.js.map +2 -2
  27. package/dist/modules/auth/backend/roles/[id]/edit/page.js +24 -4
  28. package/dist/modules/auth/backend/roles/[id]/edit/page.js.map +2 -2
  29. package/dist/modules/auth/backend/roles/page.js +8 -4
  30. package/dist/modules/auth/backend/roles/page.js.map +2 -2
  31. package/dist/modules/auth/backend/users/[id]/edit/page.js +27 -5
  32. package/dist/modules/auth/backend/users/[id]/edit/page.js.map +2 -2
  33. package/dist/modules/auth/backend/users/page.js +6 -2
  34. package/dist/modules/auth/backend/users/page.js.map +2 -2
  35. package/dist/modules/auth/components/AclEditor.js +3 -1
  36. package/dist/modules/auth/components/AclEditor.js.map +2 -2
  37. package/dist/modules/auth/data/entities.js +6 -0
  38. package/dist/modules/auth/data/entities.js.map +2 -2
  39. package/dist/modules/auth/services/sidebarPreferencesService.js +32 -4
  40. package/dist/modules/auth/services/sidebarPreferencesService.js.map +2 -2
  41. package/dist/modules/business_rules/api/rules/route.js +28 -0
  42. package/dist/modules/business_rules/api/rules/route.js.map +2 -2
  43. package/dist/modules/business_rules/api/sets/route.js +28 -0
  44. package/dist/modules/business_rules/api/sets/route.js.map +2 -2
  45. package/dist/modules/business_rules/backend/rules/[id]/page.js +11 -4
  46. package/dist/modules/business_rules/backend/rules/[id]/page.js.map +3 -3
  47. package/dist/modules/business_rules/backend/rules/page.js +20 -11
  48. package/dist/modules/business_rules/backend/rules/page.js.map +2 -2
  49. package/dist/modules/business_rules/backend/sets/[id]/page.js +11 -4
  50. package/dist/modules/business_rules/backend/sets/[id]/page.js.map +2 -2
  51. package/dist/modules/business_rules/backend/sets/page.js +20 -11
  52. package/dist/modules/business_rules/backend/sets/page.js.map +2 -2
  53. package/dist/modules/catalog/api/categories/route.js +2 -0
  54. package/dist/modules/catalog/api/categories/route.js.map +2 -2
  55. package/dist/modules/catalog/api/products/route.js +2 -1
  56. package/dist/modules/catalog/api/products/route.js.map +2 -2
  57. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js +2 -0
  58. package/dist/modules/catalog/backend/catalog/categories/[id]/edit/page.js.map +2 -2
  59. package/dist/modules/catalog/backend/catalog/products/[id]/page.js +94 -40
  60. package/dist/modules/catalog/backend/catalog/products/[id]/page.js.map +2 -2
  61. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js +37 -8
  62. package/dist/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.js.map +2 -2
  63. package/dist/modules/catalog/backend/catalog/products/optionSchemaClient.js.map +2 -2
  64. package/dist/modules/catalog/commands/variants.js +32 -31
  65. package/dist/modules/catalog/commands/variants.js.map +2 -2
  66. package/dist/modules/catalog/components/PriceKindSettings.js +12 -5
  67. package/dist/modules/catalog/components/PriceKindSettings.js.map +2 -2
  68. package/dist/modules/catalog/components/categories/CategoriesDataTable.js.map +2 -2
  69. package/dist/modules/catalog/components/products/ProductMediaManager.js.map +2 -2
  70. package/dist/modules/catalog/components/products/ProductsDataTable.js +5 -3
  71. package/dist/modules/catalog/components/products/ProductsDataTable.js.map +2 -2
  72. package/dist/modules/catalog/components/products/productForm.js.map +2 -2
  73. package/dist/modules/catalog/components/products/variantForm.js +2 -1
  74. package/dist/modules/catalog/components/products/variantForm.js.map +2 -2
  75. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js +5 -0
  76. package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
  77. package/dist/modules/currencies/backend/currencies/[id]/page.js +6 -3
  78. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  79. package/dist/modules/currencies/backend/currencies/page.js +18 -11
  80. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  81. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js +1 -0
  82. package/dist/modules/currencies/backend/exchange-rates/[id]/page.js.map +2 -2
  83. package/dist/modules/currencies/backend/exchange-rates/page.js +10 -6
  84. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  85. package/dist/modules/currencies/commands/currencies.js +7 -5
  86. package/dist/modules/currencies/commands/currencies.js.map +2 -2
  87. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +26 -19
  88. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  89. package/dist/modules/customer_accounts/api/admin/roles/[id].js +28 -5
  90. package/dist/modules/customer_accounts/api/admin/roles/[id].js.map +2 -2
  91. package/dist/modules/customer_accounts/api/admin/roles.js +4 -2
  92. package/dist/modules/customer_accounts/api/admin/roles.js.map +2 -2
  93. package/dist/modules/customer_accounts/api/admin/users/[id].js +28 -5
  94. package/dist/modules/customer_accounts/api/admin/users/[id].js.map +2 -2
  95. package/dist/modules/customer_accounts/api/admin/users.js +2 -0
  96. package/dist/modules/customer_accounts/api/admin/users.js.map +2 -2
  97. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +16 -8
  98. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  99. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +8 -4
  100. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  101. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js +8 -4
  102. package/dist/modules/customer_accounts/backend/customer_accounts/settings/domain/page.js.map +2 -2
  103. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js +29 -18
  104. package/dist/modules/customer_accounts/backend/customer_accounts/users/[id]/page.js.map +2 -2
  105. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js +18 -11
  106. package/dist/modules/customer_accounts/backend/customer_accounts/users/page.js.map +2 -2
  107. package/dist/modules/customers/api/companies/route.js +13 -2
  108. package/dist/modules/customers/api/companies/route.js.map +2 -2
  109. package/dist/modules/customers/api/deals/route.js +2 -0
  110. package/dist/modules/customers/api/deals/route.js.map +2 -2
  111. package/dist/modules/customers/api/people/route.js +11 -2
  112. package/dist/modules/customers/api/people/route.js.map +2 -2
  113. package/dist/modules/customers/api/todos/route.js +1 -0
  114. package/dist/modules/customers/api/todos/route.js.map +2 -2
  115. package/dist/modules/customers/backend/config/customers/deals/page.js.map +2 -2
  116. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +34 -21
  117. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  118. package/dist/modules/customers/backend/customers/companies/[id]/page.js +45 -27
  119. package/dist/modules/customers/backend/customers/companies/[id]/page.js.map +2 -2
  120. package/dist/modules/customers/backend/customers/companies/page.js.map +2 -2
  121. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js +22 -5
  122. package/dist/modules/customers/backend/customers/companies-v2/[id]/page.js.map +2 -2
  123. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js +30 -8
  124. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.js.map +2 -2
  125. package/dist/modules/customers/backend/customers/deals/[id]/page.js +1 -0
  126. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  127. package/dist/modules/customers/backend/customers/deals/page.js +16 -6
  128. package/dist/modules/customers/backend/customers/deals/page.js.map +2 -2
  129. package/dist/modules/customers/backend/customers/deals/pipeline/page.js +62 -39
  130. package/dist/modules/customers/backend/customers/deals/pipeline/page.js.map +2 -2
  131. package/dist/modules/customers/backend/customers/people/[id]/page.js +41 -26
  132. package/dist/modules/customers/backend/customers/people/[id]/page.js.map +2 -2
  133. package/dist/modules/customers/backend/customers/people/page.js.map +2 -2
  134. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js +50 -23
  135. package/dist/modules/customers/backend/customers/people-v2/[id]/page.js.map +2 -2
  136. package/dist/modules/customers/commands/addresses.js +16 -14
  137. package/dist/modules/customers/commands/addresses.js.map +2 -2
  138. package/dist/modules/customers/commands/companies.js +1 -1
  139. package/dist/modules/customers/commands/companies.js.map +2 -2
  140. package/dist/modules/customers/commands/interactions.js +41 -4
  141. package/dist/modules/customers/commands/interactions.js.map +2 -2
  142. package/dist/modules/customers/commands/people.js +1 -1
  143. package/dist/modules/customers/commands/people.js.map +2 -2
  144. package/dist/modules/customers/commands/personCompanyLinks.js +8 -5
  145. package/dist/modules/customers/commands/personCompanyLinks.js.map +2 -2
  146. package/dist/modules/customers/commands/pipeline-stages.js +13 -11
  147. package/dist/modules/customers/commands/pipeline-stages.js.map +3 -3
  148. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  149. package/dist/modules/customers/components/DictionarySettings.js +20 -13
  150. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  151. package/dist/modules/customers/components/DictionarySortSettings.js +4 -0
  152. package/dist/modules/customers/components/DictionarySortSettings.js.map +2 -2
  153. package/dist/modules/customers/components/PipelineSettings.js +38 -23
  154. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  155. package/dist/modules/customers/components/detail/ActivityTimeline.js +1 -1
  156. package/dist/modules/customers/components/detail/ActivityTimeline.js.map +2 -2
  157. package/dist/modules/customers/components/detail/AddressesSection.js +4 -0
  158. package/dist/modules/customers/components/detail/AddressesSection.js.map +2 -2
  159. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +28 -22
  160. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  161. package/dist/modules/customers/components/detail/DealsSection.js +36 -24
  162. package/dist/modules/customers/components/detail/DealsSection.js.map +2 -2
  163. package/dist/modules/customers/components/detail/EmailCardActions.js +5 -0
  164. package/dist/modules/customers/components/detail/EmailCardActions.js.map +2 -2
  165. package/dist/modules/customers/components/detail/EntityTagsDialog.js +7 -0
  166. package/dist/modules/customers/components/detail/EntityTagsDialog.js.map +2 -2
  167. package/dist/modules/customers/components/detail/ManageTagsDialog.js +34 -22
  168. package/dist/modules/customers/components/detail/ManageTagsDialog.js.map +2 -2
  169. package/dist/modules/customers/components/detail/PersonCompaniesSection.js +41 -29
  170. package/dist/modules/customers/components/detail/PersonCompaniesSection.js.map +2 -2
  171. package/dist/modules/customers/components/detail/RoleAssignmentRow.js +14 -8
  172. package/dist/modules/customers/components/detail/RoleAssignmentRow.js.map +2 -2
  173. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js +14 -6
  174. package/dist/modules/customers/components/detail/ScheduleActivityDialog.js.map +2 -2
  175. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js +29 -13
  176. package/dist/modules/customers/components/detail/hooks/useInteractionMutations.js.map +2 -2
  177. package/dist/modules/customers/components/detail/hooks/useInteractions.js +77 -35
  178. package/dist/modules/customers/components/detail/hooks/useInteractions.js.map +2 -2
  179. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js +25 -17
  180. package/dist/modules/customers/components/detail/hooks/usePersonTasks.js.map +2 -2
  181. package/dist/modules/customers/components/detail/schedule/useScheduleFormState.js.map +2 -2
  182. package/dist/modules/customers/components/formConfig.js.map +2 -2
  183. package/dist/modules/customers/data/guards.js +66 -0
  184. package/dist/modules/customers/data/guards.js.map +7 -0
  185. package/dist/modules/customers/di.js +37 -0
  186. package/dist/modules/customers/di.js.map +2 -2
  187. package/dist/modules/customers/lib/todoCompatibility.js +11 -0
  188. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  189. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  190. package/dist/modules/data_sync/api/options.js +4 -4
  191. package/dist/modules/data_sync/api/options.js.map +2 -2
  192. package/dist/modules/data_sync/api/schedules/route.js +9 -1
  193. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  194. package/dist/modules/data_sync/backend/data-sync/page.js +17 -8
  195. package/dist/modules/data_sync/backend/data-sync/page.js.map +2 -2
  196. package/dist/modules/data_sync/components/IntegrationScheduleTab.js +43 -22
  197. package/dist/modules/data_sync/components/IntegrationScheduleTab.js.map +2 -2
  198. package/dist/modules/data_sync/lib/sync-schedule-service.js +9 -0
  199. package/dist/modules/data_sync/lib/sync-schedule-service.js.map +2 -2
  200. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js +8 -1
  201. package/dist/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.js.map +2 -2
  202. package/dist/modules/dictionaries/api/[dictionaryId]/route.js +17 -1
  203. package/dist/modules/dictionaries/api/[dictionaryId]/route.js.map +2 -2
  204. package/dist/modules/dictionaries/components/DictionariesManager.js +31 -10
  205. package/dist/modules/dictionaries/components/DictionariesManager.js.map +2 -2
  206. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js +28 -15
  207. package/dist/modules/dictionaries/components/DictionaryEntriesEditor.js.map +2 -2
  208. package/dist/modules/directory/api/organizations/route.js +3 -0
  209. package/dist/modules/directory/api/organizations/route.js.map +2 -2
  210. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js +2 -0
  211. package/dist/modules/directory/backend/directory/organizations/[id]/edit/page.js.map +2 -2
  212. package/dist/modules/directory/backend/directory/organizations/page.js +9 -5
  213. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  214. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js +7 -3
  215. package/dist/modules/directory/backend/directory/tenants/[id]/edit/page.js.map +2 -2
  216. package/dist/modules/directory/backend/directory/tenants/page.js +8 -4
  217. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  218. package/dist/modules/directory/commands/organizations.js +7 -2
  219. package/dist/modules/directory/commands/organizations.js.map +2 -2
  220. package/dist/modules/entities/api/records.js +66 -0
  221. package/dist/modules/entities/api/records.js.map +2 -2
  222. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js +1 -0
  223. package/dist/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.js.map +2 -2
  224. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +8 -4
  225. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  226. package/dist/modules/entities/lib/helpers.js +17 -0
  227. package/dist/modules/entities/lib/helpers.js.map +2 -2
  228. package/dist/modules/feature_toggles/api/global/[id]/override/route.js +2 -1
  229. package/dist/modules/feature_toggles/api/global/[id]/override/route.js.map +2 -2
  230. package/dist/modules/feature_toggles/api/overrides/route.js +15 -0
  231. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  232. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js +15 -14
  233. package/dist/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.js.map +2 -2
  234. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js +20 -12
  235. package/dist/modules/feature_toggles/components/FeatureToggleOverrideCard.js.map +2 -2
  236. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js +6 -2
  237. package/dist/modules/feature_toggles/components/FeatureTogglesTable.js.map +2 -2
  238. package/dist/modules/feature_toggles/components/formConfig.js +2 -1
  239. package/dist/modules/feature_toggles/components/formConfig.js.map +2 -2
  240. package/dist/modules/feature_toggles/components/overrideFormConfig.js +5 -1
  241. package/dist/modules/feature_toggles/components/overrideFormConfig.js.map +2 -2
  242. package/dist/modules/feature_toggles/data/validators.js +7 -4
  243. package/dist/modules/feature_toggles/data/validators.js.map +2 -2
  244. package/dist/modules/inbox_ops/api/settings/route.js +17 -2
  245. package/dist/modules/inbox_ops/api/settings/route.js.map +2 -2
  246. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js +13 -8
  247. package/dist/modules/inbox_ops/backend/inbox-ops/settings/page.js.map +2 -2
  248. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js +9 -4
  249. package/dist/modules/inbox_ops/components/proposals/EditActionDialog.js.map +2 -2
  250. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js +18 -11
  251. package/dist/modules/integrations/backend/integrations/bundle/[id]/page.js.map +2 -2
  252. package/dist/modules/integrations/backend/integrations/page.js +12 -8
  253. package/dist/modules/integrations/backend/integrations/page.js.map +2 -2
  254. package/dist/modules/messages/commands/messages.js +13 -10
  255. package/dist/modules/messages/commands/messages.js.map +2 -2
  256. package/dist/modules/perspectives/api/[tableId]/route.js +39 -30
  257. package/dist/modules/perspectives/api/[tableId]/route.js.map +2 -2
  258. package/dist/modules/perspectives/services/perspectiveService.js +7 -0
  259. package/dist/modules/perspectives/services/perspectiveService.js.map +2 -2
  260. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js +6 -14
  261. package/dist/modules/planner/backend/planner/availability-rulesets/[id]/page.js.map +3 -3
  262. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +4 -2
  263. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  264. package/dist/modules/planner/components/AvailabilityRuleSetForm.js +2 -0
  265. package/dist/modules/planner/components/AvailabilityRuleSetForm.js.map +2 -2
  266. package/dist/modules/planner/components/AvailabilityRulesEditor.js +36 -11
  267. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  268. package/dist/modules/planner/components/AvailabilitySchedule.js +9 -5
  269. package/dist/modules/planner/components/AvailabilitySchedule.js.map +2 -2
  270. package/dist/modules/query_index/lib/engine.js +19 -0
  271. package/dist/modules/query_index/lib/engine.js.map +2 -2
  272. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js +1 -0
  273. package/dist/modules/resources/backend/resources/resource-types/[id]/edit/page.js.map +2 -2
  274. package/dist/modules/resources/backend/resources/resource-types/page.js +4 -2
  275. package/dist/modules/resources/backend/resources/resource-types/page.js.map +2 -2
  276. package/dist/modules/resources/backend/resources/resources/[id]/page.js +14 -3
  277. package/dist/modules/resources/backend/resources/resources/[id]/page.js.map +2 -2
  278. package/dist/modules/resources/backend/resources/resources/page.js +8 -4
  279. package/dist/modules/resources/backend/resources/resources/page.js.map +2 -2
  280. package/dist/modules/resources/components/ResourceCrudForm.js +2 -0
  281. package/dist/modules/resources/components/ResourceCrudForm.js.map +2 -2
  282. package/dist/modules/resources/components/ResourceTypeCrudForm.js +1 -0
  283. package/dist/modules/resources/components/ResourceTypeCrudForm.js.map +2 -2
  284. package/dist/modules/sales/api/documents/factory.js +7 -2
  285. package/dist/modules/sales/api/documents/factory.js.map +2 -2
  286. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js +3 -1
  287. package/dist/modules/sales/backend/sales/channels/[channelId]/edit/page.js.map +2 -2
  288. package/dist/modules/sales/backend/sales/channels/offers/page.js +13 -4
  289. package/dist/modules/sales/backend/sales/channels/offers/page.js.map +2 -2
  290. package/dist/modules/sales/backend/sales/channels/page.js +16 -4
  291. package/dist/modules/sales/backend/sales/channels/page.js.map +2 -2
  292. package/dist/modules/sales/backend/sales/documents/[id]/page.js +68 -22
  293. package/dist/modules/sales/backend/sales/documents/[id]/page.js.map +2 -2
  294. package/dist/modules/sales/backend/sales/documents/create/page.js.map +2 -2
  295. package/dist/modules/sales/commands/documentAddresses.js +181 -2
  296. package/dist/modules/sales/commands/documentAddresses.js.map +2 -2
  297. package/dist/modules/sales/commands/documents.js +29 -1
  298. package/dist/modules/sales/commands/documents.js.map +2 -2
  299. package/dist/modules/sales/commands/returns.js +12 -2
  300. package/dist/modules/sales/commands/returns.js.map +2 -2
  301. package/dist/modules/sales/commands/shared.js +15 -0
  302. package/dist/modules/sales/commands/shared.js.map +2 -2
  303. package/dist/modules/sales/commands/shipments.js +4 -1
  304. package/dist/modules/sales/commands/shipments.js.map +2 -2
  305. package/dist/modules/sales/components/AdjustmentKindSettings.js +19 -11
  306. package/dist/modules/sales/components/AdjustmentKindSettings.js.map +2 -2
  307. package/dist/modules/sales/components/DocumentNumberSettings.js.map +2 -2
  308. package/dist/modules/sales/components/OrderEditingSettings.js.map +2 -2
  309. package/dist/modules/sales/components/PaymentMethodsSettings.js +12 -4
  310. package/dist/modules/sales/components/PaymentMethodsSettings.js.map +2 -2
  311. package/dist/modules/sales/components/ShippingMethodsSettings.js +12 -4
  312. package/dist/modules/sales/components/ShippingMethodsSettings.js.map +2 -2
  313. package/dist/modules/sales/components/StatusSettings.js +18 -11
  314. package/dist/modules/sales/components/StatusSettings.js.map +2 -2
  315. package/dist/modules/sales/components/TaxRatesSettings.js +12 -4
  316. package/dist/modules/sales/components/TaxRatesSettings.js.map +2 -2
  317. package/dist/modules/sales/components/channels/ChannelOfferForm.js +47 -16
  318. package/dist/modules/sales/components/channels/ChannelOfferForm.js.map +2 -2
  319. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js +8 -4
  320. package/dist/modules/sales/components/channels/SalesChannelOffersPanel.js.map +2 -2
  321. package/dist/modules/sales/components/documents/AddressesSection.js +44 -25
  322. package/dist/modules/sales/components/documents/AddressesSection.js.map +2 -2
  323. package/dist/modules/sales/components/documents/AdjustmentsSection.js +43 -23
  324. package/dist/modules/sales/components/documents/AdjustmentsSection.js.map +2 -2
  325. package/dist/modules/sales/components/documents/ItemsSection.js +22 -13
  326. package/dist/modules/sales/components/documents/ItemsSection.js.map +2 -2
  327. package/dist/modules/sales/components/documents/LineItemDialog.js +23 -10
  328. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  329. package/dist/modules/sales/components/documents/PaymentDialog.js +29 -14
  330. package/dist/modules/sales/components/documents/PaymentDialog.js.map +2 -2
  331. package/dist/modules/sales/components/documents/PaymentsSection.js +20 -10
  332. package/dist/modules/sales/components/documents/PaymentsSection.js.map +2 -2
  333. package/dist/modules/sales/components/documents/ReturnDialog.js +26 -17
  334. package/dist/modules/sales/components/documents/ReturnDialog.js.map +2 -2
  335. package/dist/modules/sales/components/documents/ReturnsSection.js +3 -1
  336. package/dist/modules/sales/components/documents/ReturnsSection.js.map +2 -2
  337. package/dist/modules/sales/components/documents/SalesDocumentsTable.js +10 -5
  338. package/dist/modules/sales/components/documents/SalesDocumentsTable.js.map +2 -2
  339. package/dist/modules/sales/components/documents/ShipmentDialog.js +21 -7
  340. package/dist/modules/sales/components/documents/ShipmentDialog.js.map +2 -2
  341. package/dist/modules/sales/components/documents/ShipmentsSection.js +19 -10
  342. package/dist/modules/sales/components/documents/ShipmentsSection.js.map +2 -2
  343. package/dist/modules/sales/components/documents/optimisticLock.js +27 -0
  344. package/dist/modules/sales/components/documents/optimisticLock.js.map +7 -0
  345. package/dist/modules/sales/di.js +18 -0
  346. package/dist/modules/sales/di.js.map +2 -2
  347. package/dist/modules/staff/api/job-histories.js +11 -2
  348. package/dist/modules/staff/api/job-histories.js.map +2 -2
  349. package/dist/modules/staff/api/timesheets/time-entries/route.js +11 -4
  350. package/dist/modules/staff/api/timesheets/time-entries/route.js.map +2 -2
  351. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js +13 -8
  352. package/dist/modules/staff/backend/staff/leave-requests/[id]/page.js.map +2 -2
  353. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js +2 -1
  354. package/dist/modules/staff/backend/staff/my-leave-requests/[id]/page.js.map +2 -2
  355. package/dist/modules/staff/backend/staff/team-members/[id]/page.js +7 -4
  356. package/dist/modules/staff/backend/staff/team-members/[id]/page.js.map +2 -2
  357. package/dist/modules/staff/backend/staff/team-members/page.js +4 -2
  358. package/dist/modules/staff/backend/staff/team-members/page.js.map +2 -2
  359. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js +1 -0
  360. package/dist/modules/staff/backend/staff/team-roles/[id]/edit/page.js.map +2 -2
  361. package/dist/modules/staff/backend/staff/team-roles/page.js +4 -2
  362. package/dist/modules/staff/backend/staff/team-roles/page.js.map +2 -2
  363. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js +5 -2
  364. package/dist/modules/staff/backend/staff/teams/[id]/edit/page.js.map +2 -2
  365. package/dist/modules/staff/backend/staff/teams/page.js +12 -3
  366. package/dist/modules/staff/backend/staff/teams/page.js.map +2 -2
  367. package/dist/modules/staff/backend/staff/timesheets/page.js +4 -1
  368. package/dist/modules/staff/backend/staff/timesheets/page.js.map +2 -2
  369. package/dist/modules/staff/backend/staff/timesheets/projects/[id]/page.js.map +2 -2
  370. package/dist/modules/staff/backend/staff/timesheets/projects/page.js +12 -3
  371. package/dist/modules/staff/backend/staff/timesheets/projects/page.js.map +2 -2
  372. package/dist/modules/staff/commands/job-histories.js +40 -3
  373. package/dist/modules/staff/commands/job-histories.js.map +2 -2
  374. package/dist/modules/staff/components/LeaveRequestForm.js +1 -0
  375. package/dist/modules/staff/components/LeaveRequestForm.js.map +2 -2
  376. package/dist/modules/staff/components/TeamForm.js +1 -0
  377. package/dist/modules/staff/components/TeamForm.js.map +2 -2
  378. package/dist/modules/staff/components/TeamMemberForm.js +1 -0
  379. package/dist/modules/staff/components/TeamMemberForm.js.map +2 -2
  380. package/dist/modules/staff/components/TeamRoleForm.js +1 -0
  381. package/dist/modules/staff/components/TeamRoleForm.js.map +2 -2
  382. package/dist/modules/staff/components/detail/JobHistorySection.js +20 -7
  383. package/dist/modules/staff/components/detail/JobHistorySection.js.map +2 -2
  384. package/dist/modules/staff/data/validators.js +7 -1
  385. package/dist/modules/staff/data/validators.js.map +2 -2
  386. package/dist/modules/staff/lib/leaveRequestHelpers.js +2 -1
  387. package/dist/modules/staff/lib/leaveRequestHelpers.js.map +2 -2
  388. package/dist/modules/translations/components/TranslationManager.js +12 -8
  389. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  390. package/dist/modules/workflows/api/definitions/[id]/route.js +106 -0
  391. package/dist/modules/workflows/api/definitions/[id]/route.js.map +2 -2
  392. package/dist/modules/workflows/backend/definitions/[id]/page.js +11 -3
  393. package/dist/modules/workflows/backend/definitions/[id]/page.js.map +2 -2
  394. package/dist/modules/workflows/backend/definitions/page.js +19 -8
  395. package/dist/modules/workflows/backend/definitions/page.js.map +2 -2
  396. package/dist/modules/workflows/backend/definitions/visual-editor/page.js +29 -16
  397. package/dist/modules/workflows/backend/definitions/visual-editor/page.js.map +2 -2
  398. package/dist/modules/workflows/components/formConfig.js +4 -1
  399. package/dist/modules/workflows/components/formConfig.js.map +2 -2
  400. package/dist/modules/workflows/di.js +12 -0
  401. package/dist/modules/workflows/di.js.map +2 -2
  402. package/generated/entities/role/index.ts +1 -0
  403. package/generated/entities/user/index.ts +1 -0
  404. package/generated/entity-fields-registry.ts +2 -0
  405. package/jest.setup.ts +17 -0
  406. package/package.json +8 -7
  407. package/src/helpers/integration/optimisticLockUi.ts +172 -0
  408. package/src/helpers/integration/salesFixtures.ts +29 -0
  409. package/src/modules/api_keys/backend/api-keys/page.tsx +10 -5
  410. package/src/modules/attachments/components/AttachmentPartitionSettings.tsx +19 -9
  411. package/src/modules/auth/api/roles/acl/route.ts +37 -11
  412. package/src/modules/auth/api/roles/route.ts +2 -0
  413. package/src/modules/auth/api/sidebar/preferences/route.ts +73 -0
  414. package/src/modules/auth/api/users/acl/route.ts +46 -18
  415. package/src/modules/auth/api/users/route.ts +2 -0
  416. package/src/modules/auth/backend/roles/[id]/edit/page.tsx +29 -4
  417. package/src/modules/auth/backend/roles/page.tsx +9 -4
  418. package/src/modules/auth/backend/users/[id]/edit/page.tsx +37 -4
  419. package/src/modules/auth/backend/users/page.tsx +7 -2
  420. package/src/modules/auth/components/AclEditor.tsx +10 -1
  421. package/src/modules/auth/data/entities.ts +7 -1
  422. package/src/modules/auth/services/sidebarPreferencesService.ts +38 -4
  423. package/src/modules/business_rules/api/rules/route.ts +30 -0
  424. package/src/modules/business_rules/api/sets/route.ts +30 -0
  425. package/src/modules/business_rules/backend/rules/[id]/page.tsx +16 -4
  426. package/src/modules/business_rules/backend/rules/page.tsx +20 -11
  427. package/src/modules/business_rules/backend/sets/[id]/page.tsx +16 -4
  428. package/src/modules/business_rules/backend/sets/page.tsx +20 -11
  429. package/src/modules/catalog/api/categories/route.ts +3 -0
  430. package/src/modules/catalog/api/products/route.ts +4 -0
  431. package/src/modules/catalog/backend/catalog/categories/[id]/edit/page.tsx +5 -0
  432. package/src/modules/catalog/backend/catalog/products/[id]/page.tsx +112 -35
  433. package/src/modules/catalog/backend/catalog/products/[productId]/variants/[variantId]/page.tsx +56 -7
  434. package/src/modules/catalog/backend/catalog/products/optionSchemaClient.ts +2 -0
  435. package/src/modules/catalog/commands/variants.ts +32 -32
  436. package/src/modules/catalog/components/PriceKindSettings.tsx +20 -7
  437. package/src/modules/catalog/components/categories/CategoriesDataTable.tsx +1 -0
  438. package/src/modules/catalog/components/products/ProductMediaManager.tsx +2 -0
  439. package/src/modules/catalog/components/products/ProductsDataTable.tsx +8 -4
  440. package/src/modules/catalog/components/products/productForm.ts +3 -0
  441. package/src/modules/catalog/components/products/variantForm.ts +9 -0
  442. package/src/modules/communication_channels/backend/profile/communication-channels/page.tsx +5 -0
  443. package/src/modules/currencies/backend/currencies/[id]/page.tsx +13 -6
  444. package/src/modules/currencies/backend/currencies/page.tsx +18 -11
  445. package/src/modules/currencies/backend/exchange-rates/[id]/page.tsx +3 -0
  446. package/src/modules/currencies/backend/exchange-rates/page.tsx +10 -6
  447. package/src/modules/currencies/commands/currencies.ts +10 -5
  448. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +31 -21
  449. package/src/modules/customer_accounts/api/admin/roles/[id].ts +35 -5
  450. package/src/modules/customer_accounts/api/admin/roles.ts +2 -0
  451. package/src/modules/customer_accounts/api/admin/users/[id].ts +38 -5
  452. package/src/modules/customer_accounts/api/admin/users.ts +2 -0
  453. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +34 -20
  454. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +9 -4
  455. package/src/modules/customer_accounts/backend/customer_accounts/settings/domain/page.tsx +11 -4
  456. package/src/modules/customer_accounts/backend/customer_accounts/users/[id]/page.tsx +28 -17
  457. package/src/modules/customer_accounts/backend/customer_accounts/users/page.tsx +19 -11
  458. package/src/modules/customers/AGENTS.md +2 -2
  459. package/src/modules/customers/api/companies/route.ts +14 -1
  460. package/src/modules/customers/api/deals/route.ts +3 -0
  461. package/src/modules/customers/api/people/route.ts +12 -1
  462. package/src/modules/customers/api/todos/route.ts +1 -0
  463. package/src/modules/customers/backend/config/customers/deals/page.tsx +1 -0
  464. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +36 -21
  465. package/src/modules/customers/backend/customers/companies/[id]/page.tsx +52 -27
  466. package/src/modules/customers/backend/customers/companies/page.tsx +2 -0
  467. package/src/modules/customers/backend/customers/companies-v2/[id]/page.tsx +27 -5
  468. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealFormHandlers.ts +39 -7
  469. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +1 -0
  470. package/src/modules/customers/backend/customers/deals/page.tsx +18 -6
  471. package/src/modules/customers/backend/customers/deals/pipeline/page.tsx +64 -39
  472. package/src/modules/customers/backend/customers/people/[id]/page.tsx +46 -26
  473. package/src/modules/customers/backend/customers/people/page.tsx +2 -0
  474. package/src/modules/customers/backend/customers/people-v2/[id]/page.tsx +84 -24
  475. package/src/modules/customers/commands/addresses.ts +16 -14
  476. package/src/modules/customers/commands/companies.ts +3 -1
  477. package/src/modules/customers/commands/interactions.ts +50 -4
  478. package/src/modules/customers/commands/people.ts +2 -1
  479. package/src/modules/customers/commands/personCompanyLinks.ts +8 -5
  480. package/src/modules/customers/commands/pipeline-stages.ts +16 -16
  481. package/src/modules/customers/components/AddressFormatSettings.tsx +1 -0
  482. package/src/modules/customers/components/DictionarySettings.tsx +18 -13
  483. package/src/modules/customers/components/DictionarySortSettings.tsx +4 -0
  484. package/src/modules/customers/components/PipelineSettings.tsx +42 -21
  485. package/src/modules/customers/components/detail/ActivityTimeline.tsx +3 -3
  486. package/src/modules/customers/components/detail/AddressesSection.tsx +4 -0
  487. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +2 -0
  488. package/src/modules/customers/components/detail/DealsSection.tsx +4 -0
  489. package/src/modules/customers/components/detail/EmailCardActions.tsx +5 -0
  490. package/src/modules/customers/components/detail/EntityTagsDialog.tsx +7 -0
  491. package/src/modules/customers/components/detail/ManageTagsDialog.tsx +4 -0
  492. package/src/modules/customers/components/detail/PersonCompaniesSection.tsx +4 -0
  493. package/src/modules/customers/components/detail/RoleAssignmentRow.tsx +2 -0
  494. package/src/modules/customers/components/detail/ScheduleActivityDialog.tsx +23 -7
  495. package/src/modules/customers/components/detail/hooks/useInteractionMutations.ts +25 -15
  496. package/src/modules/customers/components/detail/hooks/useInteractions.ts +76 -35
  497. package/src/modules/customers/components/detail/hooks/usePersonTasks.ts +30 -17
  498. package/src/modules/customers/components/detail/schedule/useScheduleFormState.ts +2 -0
  499. package/src/modules/customers/components/detail/types.ts +1 -0
  500. package/src/modules/customers/components/formConfig.tsx +2 -0
  501. package/src/modules/customers/data/guards.ts +67 -0
  502. package/src/modules/customers/di.ts +66 -0
  503. package/src/modules/customers/i18n/de.json +2 -0
  504. package/src/modules/customers/i18n/en.json +2 -0
  505. package/src/modules/customers/i18n/es.json +2 -0
  506. package/src/modules/customers/i18n/pl.json +2 -0
  507. package/src/modules/customers/lib/todoCompatibility.ts +14 -0
  508. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +2 -0
  509. package/src/modules/data_sync/api/options.ts +7 -4
  510. package/src/modules/data_sync/api/schedules/route.ts +9 -1
  511. package/src/modules/data_sync/backend/data-sync/page.tsx +18 -5
  512. package/src/modules/data_sync/components/IntegrationScheduleTab.tsx +46 -19
  513. package/src/modules/data_sync/lib/sync-schedule-service.ts +11 -0
  514. package/src/modules/dictionaries/api/[dictionaryId]/entries/[entryId]/route.ts +8 -1
  515. package/src/modules/dictionaries/api/[dictionaryId]/route.ts +23 -0
  516. package/src/modules/dictionaries/components/DictionariesManager.tsx +32 -9
  517. package/src/modules/dictionaries/components/DictionaryEntriesEditor.tsx +30 -14
  518. package/src/modules/dictionaries/i18n/de.json +1 -0
  519. package/src/modules/dictionaries/i18n/en.json +1 -0
  520. package/src/modules/dictionaries/i18n/es.json +1 -0
  521. package/src/modules/dictionaries/i18n/pl.json +1 -0
  522. package/src/modules/directory/api/organizations/route.ts +3 -0
  523. package/src/modules/directory/backend/directory/organizations/[id]/edit/page.tsx +8 -0
  524. package/src/modules/directory/backend/directory/organizations/page.tsx +10 -5
  525. package/src/modules/directory/backend/directory/tenants/[id]/edit/page.tsx +16 -5
  526. package/src/modules/directory/backend/directory/tenants/page.tsx +8 -4
  527. package/src/modules/directory/commands/organizations.ts +7 -4
  528. package/src/modules/entities/api/records.ts +99 -0
  529. package/src/modules/entities/backend/entities/user/[entityId]/records/[recordId]/page.tsx +7 -0
  530. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +8 -4
  531. package/src/modules/entities/lib/helpers.ts +17 -0
  532. package/src/modules/feature_toggles/api/global/[id]/override/route.ts +1 -0
  533. package/src/modules/feature_toggles/api/overrides/route.ts +19 -0
  534. package/src/modules/feature_toggles/backend/feature-toggles/global/[id]/edit/page.tsx +19 -13
  535. package/src/modules/feature_toggles/components/FeatureToggleOverrideCard.tsx +22 -12
  536. package/src/modules/feature_toggles/components/FeatureTogglesTable.tsx +7 -2
  537. package/src/modules/feature_toggles/components/formConfig.tsx +2 -1
  538. package/src/modules/feature_toggles/components/overrideFormConfig.tsx +10 -1
  539. package/src/modules/feature_toggles/data/validators.ts +11 -3
  540. package/src/modules/inbox_ops/api/settings/route.ts +18 -0
  541. package/src/modules/inbox_ops/backend/inbox-ops/settings/page.tsx +15 -10
  542. package/src/modules/inbox_ops/components/proposals/EditActionDialog.tsx +9 -4
  543. package/src/modules/integrations/backend/integrations/bundle/[id]/page.tsx +20 -11
  544. package/src/modules/integrations/backend/integrations/page.tsx +13 -8
  545. package/src/modules/messages/commands/messages.ts +27 -15
  546. package/src/modules/perspectives/api/[tableId]/route.ts +11 -2
  547. package/src/modules/perspectives/services/perspectiveService.ts +13 -1
  548. package/src/modules/planner/backend/planner/availability-rulesets/[id]/page.tsx +16 -14
  549. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +6 -3
  550. package/src/modules/planner/components/AvailabilityRuleSetForm.tsx +3 -0
  551. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +58 -15
  552. package/src/modules/planner/components/AvailabilitySchedule.tsx +22 -7
  553. package/src/modules/query_index/lib/engine.ts +34 -0
  554. package/src/modules/resources/backend/resources/resource-types/[id]/edit/page.tsx +7 -1
  555. package/src/modules/resources/backend/resources/resource-types/page.tsx +6 -3
  556. package/src/modules/resources/backend/resources/resources/[id]/page.tsx +23 -3
  557. package/src/modules/resources/backend/resources/resources/page.tsx +15 -4
  558. package/src/modules/resources/components/ResourceCrudForm.tsx +3 -0
  559. package/src/modules/resources/components/ResourceTypeCrudForm.tsx +2 -0
  560. package/src/modules/sales/api/documents/factory.ts +13 -1
  561. package/src/modules/sales/backend/sales/channels/[channelId]/edit/page.tsx +6 -0
  562. package/src/modules/sales/backend/sales/channels/offers/page.tsx +10 -4
  563. package/src/modules/sales/backend/sales/channels/page.tsx +19 -4
  564. package/src/modules/sales/backend/sales/documents/[id]/page.tsx +73 -20
  565. package/src/modules/sales/backend/sales/documents/create/page.tsx +2 -0
  566. package/src/modules/sales/commands/documentAddresses.ts +226 -4
  567. package/src/modules/sales/commands/documents.ts +28 -0
  568. package/src/modules/sales/commands/returns.ts +12 -3
  569. package/src/modules/sales/commands/shared.ts +36 -0
  570. package/src/modules/sales/commands/shipments.ts +17 -1
  571. package/src/modules/sales/components/AdjustmentKindSettings.tsx +20 -11
  572. package/src/modules/sales/components/DocumentNumberSettings.tsx +1 -0
  573. package/src/modules/sales/components/OrderEditingSettings.tsx +1 -0
  574. package/src/modules/sales/components/PaymentMethodsSettings.tsx +12 -4
  575. package/src/modules/sales/components/ShippingMethodsSettings.tsx +12 -4
  576. package/src/modules/sales/components/StatusSettings.tsx +20 -11
  577. package/src/modules/sales/components/TaxRatesSettings.tsx +12 -5
  578. package/src/modules/sales/components/channels/ChannelOfferForm.tsx +67 -14
  579. package/src/modules/sales/components/channels/SalesChannelOffersPanel.tsx +7 -4
  580. package/src/modules/sales/components/documents/AddressesSection.tsx +35 -25
  581. package/src/modules/sales/components/documents/AdjustmentsSection.tsx +50 -25
  582. package/src/modules/sales/components/documents/ItemsSection.tsx +24 -13
  583. package/src/modules/sales/components/documents/LineItemDialog.tsx +26 -9
  584. package/src/modules/sales/components/documents/PaymentDialog.tsx +33 -14
  585. package/src/modules/sales/components/documents/PaymentsSection.tsx +22 -10
  586. package/src/modules/sales/components/documents/ReturnDialog.tsx +28 -17
  587. package/src/modules/sales/components/documents/ReturnsSection.tsx +4 -1
  588. package/src/modules/sales/components/documents/SalesDocumentsTable.tsx +11 -4
  589. package/src/modules/sales/components/documents/ShipmentDialog.tsx +23 -8
  590. package/src/modules/sales/components/documents/ShipmentsSection.tsx +20 -10
  591. package/src/modules/sales/components/documents/optimisticLock.ts +34 -0
  592. package/src/modules/sales/components/documents/shipmentTypes.ts +1 -0
  593. package/src/modules/sales/di.ts +35 -0
  594. package/src/modules/sales/i18n/de.json +3 -0
  595. package/src/modules/sales/i18n/en.json +3 -0
  596. package/src/modules/sales/i18n/es.json +3 -0
  597. package/src/modules/sales/i18n/pl.json +3 -0
  598. package/src/modules/staff/api/job-histories.ts +12 -2
  599. package/src/modules/staff/api/timesheets/time-entries/route.ts +16 -4
  600. package/src/modules/staff/backend/staff/leave-requests/[id]/page.tsx +12 -7
  601. package/src/modules/staff/backend/staff/my-leave-requests/[id]/page.tsx +2 -0
  602. package/src/modules/staff/backend/staff/team-members/[id]/page.tsx +16 -5
  603. package/src/modules/staff/backend/staff/team-members/page.tsx +6 -2
  604. package/src/modules/staff/backend/staff/team-roles/[id]/edit/page.tsx +8 -0
  605. package/src/modules/staff/backend/staff/team-roles/page.tsx +6 -2
  606. package/src/modules/staff/backend/staff/teams/[id]/edit/page.tsx +13 -3
  607. package/src/modules/staff/backend/staff/teams/page.tsx +9 -3
  608. package/src/modules/staff/backend/staff/timesheets/page.tsx +10 -1
  609. package/src/modules/staff/backend/staff/timesheets/projects/[id]/page.tsx +4 -0
  610. package/src/modules/staff/backend/staff/timesheets/projects/page.tsx +9 -3
  611. package/src/modules/staff/commands/job-histories.ts +42 -3
  612. package/src/modules/staff/components/LeaveRequestForm.tsx +2 -0
  613. package/src/modules/staff/components/TeamForm.tsx +2 -0
  614. package/src/modules/staff/components/TeamMemberForm.tsx +2 -0
  615. package/src/modules/staff/components/TeamRoleForm.tsx +2 -0
  616. package/src/modules/staff/components/detail/JobHistorySection.tsx +28 -6
  617. package/src/modules/staff/data/validators.ts +6 -0
  618. package/src/modules/staff/i18n/de.json +1 -0
  619. package/src/modules/staff/i18n/en.json +1 -0
  620. package/src/modules/staff/i18n/es.json +1 -0
  621. package/src/modules/staff/i18n/pl.json +1 -0
  622. package/src/modules/staff/lib/leaveRequestHelpers.ts +4 -0
  623. package/src/modules/translations/components/TranslationManager.tsx +13 -8
  624. package/src/modules/workflows/api/definitions/[id]/route.ts +112 -0
  625. package/src/modules/workflows/backend/definitions/[id]/page.tsx +20 -4
  626. package/src/modules/workflows/backend/definitions/page.tsx +20 -9
  627. package/src/modules/workflows/backend/definitions/visual-editor/page.tsx +29 -16
  628. package/src/modules/workflows/components/formConfig.tsx +5 -0
  629. package/src/modules/workflows/di.ts +20 -0
  630. package/src/modules/workflows/i18n/de.json +1 -0
  631. package/src/modules/workflows/i18n/en.json +1 -0
  632. package/src/modules/workflows/i18n/es.json +1 -0
  633. package/src/modules/workflows/i18n/pl.json +1 -0
@@ -0,0 +1,104 @@
1
+ import { expect } from "@playwright/test";
2
+ import {
3
+ OPTIMISTIC_LOCK_HEADER_NAME,
4
+ OPTIMISTIC_LOCK_CONFLICT_CODE
5
+ } from "@open-mercato/shared/lib/crud/optimistic-lock-headers";
6
+ const BASE_URL = process.env.BASE_URL?.trim() || "";
7
+ function resolveApiUrl(path) {
8
+ return BASE_URL ? `${BASE_URL}${path}` : path;
9
+ }
10
+ const CONFLICT_BANNER_TESTID = "record-conflict-banner";
11
+ const CONFLICT_DIALOG_TESTID = "record-lock-conflict-dialog";
12
+ function authHeaders(token, lockValue) {
13
+ const headers = {
14
+ Authorization: `Bearer ${token}`,
15
+ "Content-Type": "application/json"
16
+ };
17
+ if (lockValue !== void 0) headers[OPTIMISTIC_LOCK_HEADER_NAME] = lockValue;
18
+ return headers;
19
+ }
20
+ async function readUpdatedAt(request, token, basePath, id, idParam = "id") {
21
+ const response = await request.fetch(
22
+ resolveApiUrl(`${basePath}?${idParam}=${encodeURIComponent(id)}`),
23
+ { method: "GET", headers: authHeaders(token) }
24
+ );
25
+ expect(response.status(), `GET ${basePath}?${idParam}=... should be 200`).toBe(200);
26
+ const body = await response.json();
27
+ const item = Array.isArray(body.items) ? body.items[0] : body;
28
+ expect(item, `response should include the record for id=${id}`).toBeTruthy();
29
+ const raw = item?.updated_at ?? item?.updatedAt;
30
+ expect(typeof raw, `record should expose updated_at, got ${String(raw)}`).toBe("string");
31
+ const ms = Date.parse(raw);
32
+ expect(Number.isFinite(ms), `updated_at should parse, got ${String(raw)}`).toBe(true);
33
+ return new Date(ms).toISOString();
34
+ }
35
+ async function bumpRecordViaApi(request, token, basePath, putBody, opts = {}) {
36
+ const response = await request.fetch(resolveApiUrl(basePath), {
37
+ method: opts.method ?? "PUT",
38
+ headers: authHeaders(token),
39
+ data: putBody
40
+ });
41
+ expect(
42
+ response.status(),
43
+ `out-of-band ${opts.method ?? "PUT"} ${basePath} should succeed (additive path), got ${response.status()}`
44
+ ).toBeLessThan(300);
45
+ const id = putBody[opts.idParam ?? "id"];
46
+ if (typeof id === "string") {
47
+ try {
48
+ return await readUpdatedAt(request, token, basePath, id, opts.idParam);
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ return null;
54
+ }
55
+ async function putWithLock(request, token, basePath, body, lockValue) {
56
+ return request.fetch(resolveApiUrl(basePath), {
57
+ method: "PUT",
58
+ headers: authHeaders(token, lockValue),
59
+ data: body
60
+ });
61
+ }
62
+ async function expectConflictBody(response) {
63
+ expect(response.status(), "stale write should be 409").toBe(409);
64
+ const body = await response.json();
65
+ expect(body.code, "body.code should be the optimistic-lock conflict code").toBe(OPTIMISTIC_LOCK_CONFLICT_CODE);
66
+ return body;
67
+ }
68
+ async function expectConflictBanner(page) {
69
+ const conflictSurface = page.getByTestId(CONFLICT_BANNER_TESTID).or(page.getByTestId(CONFLICT_DIALOG_TESTID));
70
+ await expect(
71
+ conflictSurface.first(),
72
+ "a conflict surface (OSS bar or record_locks dialog) should appear after a stale save"
73
+ ).toBeVisible({ timeout: 1e4 });
74
+ }
75
+ async function expectNoConflictBanner(page) {
76
+ await expect(
77
+ page.getByTestId(CONFLICT_BANNER_TESTID),
78
+ "a clean save must not surface a false-positive conflict bar"
79
+ ).toHaveCount(0);
80
+ await expect(
81
+ page.getByTestId(CONFLICT_DIALOG_TESTID),
82
+ "a clean save must not surface a false-positive conflict dialog"
83
+ ).toHaveCount(0);
84
+ }
85
+ async function clickConflictRefresh(page) {
86
+ await page.getByTestId(CONFLICT_BANNER_TESTID).getByRole("button", { name: /refresh/i }).click();
87
+ }
88
+ async function dismissConflictBanner(page) {
89
+ await page.getByTestId(CONFLICT_BANNER_TESTID).getByRole("button", { name: /dismiss/i }).click();
90
+ }
91
+ export {
92
+ CONFLICT_BANNER_TESTID,
93
+ CONFLICT_DIALOG_TESTID,
94
+ bumpRecordViaApi,
95
+ clickConflictRefresh,
96
+ dismissConflictBanner,
97
+ expectConflictBanner,
98
+ expectConflictBody,
99
+ expectNoConflictBanner,
100
+ putWithLock,
101
+ readUpdatedAt,
102
+ resolveApiUrl
103
+ };
104
+ //# sourceMappingURL=optimisticLockUi.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/helpers/integration/optimisticLockUi.ts"],
4
+ "sourcesContent": ["import { expect, type APIRequestContext, type Page } from '@playwright/test'\nimport {\n OPTIMISTIC_LOCK_HEADER_NAME,\n OPTIMISTIC_LOCK_CONFLICT_CODE,\n} from '@open-mercato/shared/lib/crud/optimistic-lock-headers'\n\n/**\n * Shared helpers for the browser-driven optimistic-lock specs\n * (`TC-LOCK-OSS-014..046`). They make the conflict deterministic without two\n * real tabs or sleeps: the spec loads an edit page in the browser (the form\n * captures the record's `updated_at`), then advances `updated_at` out-of-band\n * via a header-less API PUT (additive path, always succeeds), and finally edits\n * + saves in the browser so the now-stale header triggers the 409 \u2192 conflict bar.\n *\n * See `packages/core/src/modules/sales/__integration__/__concurrent_edit_pattern.md`\n * and the conflict bar component\n * `packages/ui/src/backend/conflicts/RecordConflictBanner.tsx`\n * (`data-testid=\"record-conflict-banner\"`).\n */\n\nconst BASE_URL = process.env.BASE_URL?.trim() || ''\n\nexport function resolveApiUrl(path: string): string {\n return BASE_URL ? `${BASE_URL}${path}` : path\n}\n\nexport const CONFLICT_BANNER_TESTID = 'record-conflict-banner'\n\n/**\n * The enterprise `record_locks` module (enabled in CI via\n * `OM_ENABLE_ENTERPRISE_MODULES=true`) supersedes the OSS banner with a richer\n * \"Conflict detected\" resolution dialog. Both are valid surfaces for \"the stale\n * write was refused\": on a CrudForm edit page either one can win depending on\n * whether the record_locks incoming-changes SSE (fired by the out-of-band bump)\n * is processed before the browser's stale save reaches the server. Asserting one\n * fixed surface is therefore racy; we wait for whichever conflict surface appears.\n * (List-delete / non-form flows have no record_locks lock and only ever surface\n * the OSS banner, so matching either surface is safe there too.)\n */\nexport const CONFLICT_DIALOG_TESTID = 'record-lock-conflict-dialog'\n\nfunction authHeaders(token: string, lockValue?: string): Record<string, string> {\n const headers: Record<string, string> = {\n Authorization: `Bearer ${token}`,\n 'Content-Type': 'application/json',\n }\n if (lockValue !== undefined) headers[OPTIMISTIC_LOCK_HEADER_NAME] = lockValue\n return headers\n}\n\n/**\n * Read a record's current `updated_at` from a list-shaped CRUD GET\n * (`GET <basePath>?id=<id>` \u2192 `items[0].updated_at`), normalized to ISO.\n * Works for the `makeCrudRoute` list responses (snake or camel case).\n */\nexport async function readUpdatedAt(\n request: APIRequestContext,\n token: string,\n basePath: string,\n id: string,\n idParam = 'id',\n): Promise<string> {\n const response = await request.fetch(\n resolveApiUrl(`${basePath}?${idParam}=${encodeURIComponent(id)}`),\n { method: 'GET', headers: authHeaders(token) },\n )\n expect(response.status(), `GET ${basePath}?${idParam}=... should be 200`).toBe(200)\n const body = (await response.json()) as\n | { items?: Array<Record<string, unknown>> }\n | Record<string, unknown>\n const item = Array.isArray((body as { items?: unknown[] }).items)\n ? (body as { items: Array<Record<string, unknown>> }).items[0]\n : (body as Record<string, unknown>)\n expect(item, `response should include the record for id=${id}`).toBeTruthy()\n const raw = (item?.updated_at ?? item?.updatedAt) as string | undefined\n expect(typeof raw, `record should expose updated_at, got ${String(raw)}`).toBe('string')\n const ms = Date.parse(raw as string)\n expect(Number.isFinite(ms), `updated_at should parse, got ${String(raw)}`).toBe(true)\n return new Date(ms).toISOString()\n}\n\n/**\n * Advance a record's `updated_at` out-of-band so the browser's loaded form now\n * holds a stale token. Uses a **header-less** PUT (the strictly-additive path\n * always succeeds and bumps `updated_at`). Returns the new ISO `updated_at`.\n */\nexport async function bumpRecordViaApi(\n request: APIRequestContext,\n token: string,\n basePath: string,\n putBody: Record<string, unknown>,\n opts: { idParam?: string; method?: 'PUT' | 'PATCH' } = {},\n): Promise<string | null> {\n const response = await request.fetch(resolveApiUrl(basePath), {\n method: opts.method ?? 'PUT',\n headers: authHeaders(token),\n data: putBody,\n })\n expect(\n response.status(),\n `out-of-band ${opts.method ?? 'PUT'} ${basePath} should succeed (additive path), got ${response.status()}`,\n ).toBeLessThan(300)\n const id = putBody[opts.idParam ?? 'id']\n if (typeof id === 'string') {\n try {\n return await readUpdatedAt(request, token, basePath, id, opts.idParam)\n } catch {\n return null\n }\n }\n return null\n}\n\n/** Direct API helpers to assert the 409 contract body (used by the negative/UX specs). */\nexport async function putWithLock(\n request: APIRequestContext,\n token: string,\n basePath: string,\n body: Record<string, unknown>,\n lockValue: string,\n) {\n return request.fetch(resolveApiUrl(basePath), {\n method: 'PUT',\n headers: authHeaders(token, lockValue),\n data: body,\n })\n}\n\nexport async function expectConflictBody(response: { status(): number; json(): Promise<unknown> }) {\n expect(response.status(), 'stale write should be 409').toBe(409)\n const body = (await response.json()) as { code?: string; currentUpdatedAt?: string; expectedUpdatedAt?: string }\n expect(body.code, 'body.code should be the optimistic-lock conflict code').toBe(OPTIMISTIC_LOCK_CONFLICT_CODE)\n return body\n}\n\n/**\n * Assert a stale save was refused and surfaced as a conflict. Matches EITHER the\n * OSS `record-conflict-banner` OR the enterprise record_locks \"Conflict detected\"\n * dialog \u2014 see `CONFLICT_DIALOG_TESTID` for why both are valid and why pinning one\n * is racy when enterprise modules are enabled.\n */\nexport async function expectConflictBanner(page: Page): Promise<void> {\n const conflictSurface = page\n .getByTestId(CONFLICT_BANNER_TESTID)\n .or(page.getByTestId(CONFLICT_DIALOG_TESTID))\n await expect(\n conflictSurface.first(),\n 'a conflict surface (OSS bar or record_locks dialog) should appear after a stale save',\n ).toBeVisible({ timeout: 10_000 })\n}\n\n/** Assert no conflict surface is present (a clean single-tab save must not 409). */\nexport async function expectNoConflictBanner(page: Page): Promise<void> {\n await expect(\n page.getByTestId(CONFLICT_BANNER_TESTID),\n 'a clean save must not surface a false-positive conflict bar',\n ).toHaveCount(0)\n await expect(\n page.getByTestId(CONFLICT_DIALOG_TESTID),\n 'a clean save must not surface a false-positive conflict dialog',\n ).toHaveCount(0)\n}\n\n/** Click the conflict bar's Refresh button. */\nexport async function clickConflictRefresh(page: Page): Promise<void> {\n await page.getByTestId(CONFLICT_BANNER_TESTID).getByRole('button', { name: /refresh/i }).click()\n}\n\n/** Dismiss the conflict bar via its close (X) button. */\nexport async function dismissConflictBanner(page: Page): Promise<void> {\n await page.getByTestId(CONFLICT_BANNER_TESTID).getByRole('button', { name: /dismiss/i }).click()\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -46,6 +46,22 @@ async function createOrderLineFixture(request, token, orderId, data) {
46
46
  ["id", "lineId"]
47
47
  );
48
48
  }
49
+ async function canManageSalesOrders(request, token) {
50
+ const response = await apiRequest(request, "POST", "/api/sales/orders", {
51
+ token,
52
+ data: { currencyCode: "USD" }
53
+ });
54
+ if (response.status() === 403) return false;
55
+ if (!response.ok()) return false;
56
+ const id = readId(await response.json(), ["id", "orderId"]);
57
+ if (id) {
58
+ try {
59
+ await apiRequest(request, "DELETE", "/api/sales/orders", { token, data: { id } });
60
+ } catch {
61
+ }
62
+ }
63
+ return true;
64
+ }
49
65
  async function deleteSalesEntityIfExists(request, token, path, id) {
50
66
  if (!token || !id) return;
51
67
  try {
@@ -55,6 +71,7 @@ async function deleteSalesEntityIfExists(request, token, path, id) {
55
71
  }
56
72
  }
57
73
  export {
74
+ canManageSalesOrders,
58
75
  createOrderLineFixture,
59
76
  createSalesOrderFixture,
60
77
  createSalesQuoteFixture,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/helpers/integration/salesFixtures.ts"],
4
- "sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\n\ntype JsonMap = Record<string, unknown>;\n\nfunction readId(payload: unknown, keys: string[]): string | null {\n if (!payload || typeof payload !== 'object') return null;\n const map = payload as JsonMap;\n for (const key of keys) {\n const value = map[key];\n if (typeof value === 'string' && value.length > 0) return value;\n }\n for (const value of Object.values(map)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const nested = readId(value, keys);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nasync function createEntity(\n request: APIRequestContext,\n token: string,\n path: string,\n data: Record<string, unknown>,\n idKeys: string[],\n): Promise<string> {\n const response = await apiRequest(request, 'POST', path, { token, data });\n const body = (await response.json()) as unknown;\n expect(response.ok(), `Failed POST ${path}: ${response.status()}`).toBeTruthy();\n const id = readId(body, idKeys);\n expect(id, `No id in POST ${path} response`).toBeTruthy();\n return id as string;\n}\n\nexport async function createSalesQuoteFixture(\n request: APIRequestContext,\n token: string,\n currencyCode = 'USD',\n): Promise<string> {\n return createEntity(request, token, '/api/sales/quotes', { currencyCode }, ['id', 'quoteId']);\n}\n\nexport async function createSalesOrderFixture(\n request: APIRequestContext,\n token: string,\n currencyCode = 'USD',\n): Promise<string> {\n return createEntity(request, token, '/api/sales/orders', { currencyCode }, ['id', 'orderId']);\n}\n\nexport async function createOrderLineFixture(\n request: APIRequestContext,\n token: string,\n orderId: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n return createEntity(\n request,\n token,\n '/api/sales/order-lines',\n {\n orderId,\n currencyCode: 'USD',\n quantity: 1,\n name: `QA line ${Date.now()}`,\n unitPriceNet: 10,\n unitPriceGross: 12,\n ...(data ?? {}),\n },\n ['id', 'lineId'],\n );\n}\n\nexport async function deleteSalesEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n path: string,\n id: string | null,\n): Promise<void> {\n if (!token || !id) return;\n try {\n await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });\n } catch {\n return;\n }\n}\n\n"],
5
- "mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAI3B,SAAS,OAAO,SAAkB,MAA+B;AAC/D,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,MAAM;AACZ,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,IAAI,GAAG;AACrB,QAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAAA,EAC5D;AACA,aAAW,SAAS,OAAO,OAAO,GAAG,GAAG;AACtC,QAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,YAAM,SAAS,OAAO,OAAO,IAAI;AACjC,UAAI,OAAQ,QAAO;AAAA,IACrB;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,aACb,SACA,OACA,MACA,MACA,QACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,MAAM,EAAE,OAAO,KAAK,CAAC;AACxE,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,SAAS,GAAG,GAAG,eAAe,IAAI,KAAK,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC9E,QAAM,KAAK,OAAO,MAAM,MAAM;AAC9B,SAAO,IAAI,iBAAiB,IAAI,WAAW,EAAE,WAAW;AACxD,SAAO;AACT;AAEA,eAAsB,wBACpB,SACA,OACA,eAAe,OACE;AACjB,SAAO,aAAa,SAAS,OAAO,qBAAqB,EAAE,aAAa,GAAG,CAAC,MAAM,SAAS,CAAC;AAC9F;AAEA,eAAsB,wBACpB,SACA,OACA,eAAe,OACE;AACjB,SAAO,aAAa,SAAS,OAAO,qBAAqB,EAAE,aAAa,GAAG,CAAC,MAAM,SAAS,CAAC;AAC9F;AAEA,eAAsB,uBACpB,SACA,OACA,SACA,MACiB;AACjB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAAA,MACA,cAAc;AAAA,MACd,UAAU;AAAA,MACV,MAAM,WAAW,KAAK,IAAI,CAAC;AAAA,MAC3B,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,GAAI,QAAQ,CAAC;AAAA,IACf;AAAA,IACA,CAAC,MAAM,QAAQ;AAAA,EACjB;AACF;AAEA,eAAsB,0BACpB,SACA,OACA,MACA,IACe;AACf,MAAI,CAAC,SAAS,CAAC,GAAI;AACnB,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,GAAG,IAAI,OAAO,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EACvF,QAAQ;AACN;AAAA,EACF;AACF;",
4
+ "sourcesContent": ["import { expect, type APIRequestContext } from '@playwright/test';\nimport { apiRequest } from './api';\n\ntype JsonMap = Record<string, unknown>;\n\nfunction readId(payload: unknown, keys: string[]): string | null {\n if (!payload || typeof payload !== 'object') return null;\n const map = payload as JsonMap;\n for (const key of keys) {\n const value = map[key];\n if (typeof value === 'string' && value.length > 0) return value;\n }\n for (const value of Object.values(map)) {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const nested = readId(value, keys);\n if (nested) return nested;\n }\n }\n return null;\n}\n\nasync function createEntity(\n request: APIRequestContext,\n token: string,\n path: string,\n data: Record<string, unknown>,\n idKeys: string[],\n): Promise<string> {\n const response = await apiRequest(request, 'POST', path, { token, data });\n const body = (await response.json()) as unknown;\n expect(response.ok(), `Failed POST ${path}: ${response.status()}`).toBeTruthy();\n const id = readId(body, idKeys);\n expect(id, `No id in POST ${path} response`).toBeTruthy();\n return id as string;\n}\n\nexport async function createSalesQuoteFixture(\n request: APIRequestContext,\n token: string,\n currencyCode = 'USD',\n): Promise<string> {\n return createEntity(request, token, '/api/sales/quotes', { currencyCode }, ['id', 'quoteId']);\n}\n\nexport async function createSalesOrderFixture(\n request: APIRequestContext,\n token: string,\n currencyCode = 'USD',\n): Promise<string> {\n return createEntity(request, token, '/api/sales/orders', { currencyCode }, ['id', 'orderId']);\n}\n\nexport async function createOrderLineFixture(\n request: APIRequestContext,\n token: string,\n orderId: string,\n data?: Record<string, unknown>,\n): Promise<string> {\n return createEntity(\n request,\n token,\n '/api/sales/order-lines',\n {\n orderId,\n currencyCode: 'USD',\n quantity: 1,\n name: `QA line ${Date.now()}`,\n unitPriceNet: 10,\n unitPriceGross: 12,\n ...(data ?? {}),\n },\n ['id', 'lineId'],\n );\n}\n\n/**\n * Probe whether the authenticated principal can create a sales order on the\n * current tenant (i.e. holds `sales.orders.manage`). Sales-write integration\n * specs use this to self-skip on dev databases whose role ACLs were never\n * synced (`yarn mercato auth sync-role-acls`) rather than fail spuriously \u2014\n * CI bootstraps a fully-synced tenant so the probe passes there. The probed\n * order is deleted immediately so the check leaves no residue.\n */\nexport async function canManageSalesOrders(\n request: APIRequestContext,\n token: string,\n): Promise<boolean> {\n const response = await apiRequest(request, 'POST', '/api/sales/orders', {\n token,\n data: { currencyCode: 'USD' },\n });\n if (response.status() === 403) return false;\n if (!response.ok()) return false;\n const id = readId((await response.json()) as unknown, ['id', 'orderId']);\n if (id) {\n try {\n await apiRequest(request, 'DELETE', '/api/sales/orders', { token, data: { id } });\n } catch {\n // best-effort cleanup\n }\n }\n return true;\n}\n\nexport async function deleteSalesEntityIfExists(\n request: APIRequestContext,\n token: string | null,\n path: string,\n id: string | null,\n): Promise<void> {\n if (!token || !id) return;\n try {\n await apiRequest(request, 'DELETE', `${path}?id=${encodeURIComponent(id)}`, { token });\n } catch {\n return;\n }\n}\n\n"],
5
+ "mappings": "AAAA,SAAS,cAAsC;AAC/C,SAAS,kBAAkB;AAI3B,SAAS,OAAO,SAAkB,MAA+B;AAC/D,MAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAM,MAAM;AACZ,aAAW,OAAO,MAAM;AACtB,UAAM,QAAQ,IAAI,GAAG;AACrB,QAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAAA,EAC5D;AACA,aAAW,SAAS,OAAO,OAAO,GAAG,GAAG;AACtC,QAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;AAC/D,YAAM,SAAS,OAAO,OAAO,IAAI;AACjC,UAAI,OAAQ,QAAO;AAAA,IACrB;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAe,aACb,SACA,OACA,MACA,MACA,QACiB;AACjB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,MAAM,EAAE,OAAO,KAAK,CAAC;AACxE,QAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,SAAO,SAAS,GAAG,GAAG,eAAe,IAAI,KAAK,SAAS,OAAO,CAAC,EAAE,EAAE,WAAW;AAC9E,QAAM,KAAK,OAAO,MAAM,MAAM;AAC9B,SAAO,IAAI,iBAAiB,IAAI,WAAW,EAAE,WAAW;AACxD,SAAO;AACT;AAEA,eAAsB,wBACpB,SACA,OACA,eAAe,OACE;AACjB,SAAO,aAAa,SAAS,OAAO,qBAAqB,EAAE,aAAa,GAAG,CAAC,MAAM,SAAS,CAAC;AAC9F;AAEA,eAAsB,wBACpB,SACA,OACA,eAAe,OACE;AACjB,SAAO,aAAa,SAAS,OAAO,qBAAqB,EAAE,aAAa,GAAG,CAAC,MAAM,SAAS,CAAC;AAC9F;AAEA,eAAsB,uBACpB,SACA,OACA,SACA,MACiB;AACjB,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,MACE;AAAA,MACA,cAAc;AAAA,MACd,UAAU;AAAA,MACV,MAAM,WAAW,KAAK,IAAI,CAAC;AAAA,MAC3B,cAAc;AAAA,MACd,gBAAgB;AAAA,MAChB,GAAI,QAAQ,CAAC;AAAA,IACf;AAAA,IACA,CAAC,MAAM,QAAQ;AAAA,EACjB;AACF;AAUA,eAAsB,qBACpB,SACA,OACkB;AAClB,QAAM,WAAW,MAAM,WAAW,SAAS,QAAQ,qBAAqB;AAAA,IACtE;AAAA,IACA,MAAM,EAAE,cAAc,MAAM;AAAA,EAC9B,CAAC;AACD,MAAI,SAAS,OAAO,MAAM,IAAK,QAAO;AACtC,MAAI,CAAC,SAAS,GAAG,EAAG,QAAO;AAC3B,QAAM,KAAK,OAAQ,MAAM,SAAS,KAAK,GAAe,CAAC,MAAM,SAAS,CAAC;AACvE,MAAI,IAAI;AACN,QAAI;AACF,YAAM,WAAW,SAAS,UAAU,qBAAqB,EAAE,OAAO,MAAM,EAAE,GAAG,EAAE,CAAC;AAAA,IAClF,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAEA,eAAsB,0BACpB,SACA,OACA,MACA,IACe;AACf,MAAI,CAAC,SAAS,CAAC,GAAI;AACnB,MAAI;AACF,UAAM,WAAW,SAAS,UAAU,GAAG,IAAI,OAAO,mBAAmB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC;AAAA,EACvF,QAAQ;AACN;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -6,7 +6,8 @@ import { Page, PageBody } from "@open-mercato/ui/backend/Page";
6
6
  import { DataTable } from "@open-mercato/ui/backend/DataTable";
7
7
  import { Button } from "@open-mercato/ui/primitives/button";
8
8
  import { RowActions } from "@open-mercato/ui/backend/RowActions";
9
- import { apiCall } from "@open-mercato/ui/backend/utils/apiCall";
9
+ import { apiCall, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
10
+ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
10
11
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
11
12
  import { useOrganizationScopeVersion } from "@open-mercato/shared/lib/frontend/useOrganizationScope";
12
13
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -80,10 +81,13 @@ function ApiKeysListPage() {
80
81
  });
81
82
  if (!confirmed) return;
82
83
  try {
83
- const call = await apiCall(
84
- `/api/api_keys/keys?id=${encodeURIComponent(row.id)}`,
85
- { method: "DELETE" },
86
- { fallback: null }
84
+ const call = await withScopedApiRequestHeaders(
85
+ buildOptimisticLockHeader(row.updatedAt),
86
+ () => apiCall(
87
+ `/api/api_keys/keys?id=${encodeURIComponent(row.id)}`,
88
+ { method: "DELETE" },
89
+ { fallback: null }
90
+ )
87
91
  );
88
92
  if (!call.ok) {
89
93
  const errorPayload = call.result;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/api_keys/backend/api-keys/page.tsx"],
4
- "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\n\ntype RoleSummary = { id: string; name: string | null }\n\ntype Row = {\n id: string\n name: string\n description: string | null\n keyPrefix: string\n organizationId: string | null\n organizationName: string | null\n createdAt: string\n lastUsedAt: string | null\n expiresAt: string | null\n roles: RoleSummary[]\n}\n\ntype ResponsePayload = {\n items: Row[]\n total: number\n page: number\n totalPages: number\n}\n\nfunction formatDate(value: string | null, t: (key: string, params?: Record<string, string | number>) => string) {\n if (!value) return t('api_keys.list.noDate')\n try {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return t('api_keys.list.noDate')\n return date.toLocaleString()\n } catch {\n return t('api_keys.list.noDate')\n }\n}\n\nexport default function ApiKeysListPage() {\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search) params.set('search', search)\n const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResponsePayload>(\n `/api/api_keys/keys?${params.toString()}`,\n undefined,\n { fallback },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('api_keys.list.error.loadFailed')\n flash(message, 'error')\n return\n }\n const payload = call.result ?? fallback\n if (!cancelled) {\n setRows(Array.isArray(payload.items) ? payload.items : [])\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n const message = error instanceof Error ? error.message : t('api_keys.list.error.loadFailed')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [page, search, reloadToken, scopeVersion, t])\n\n const handleDelete = React.useCallback(async (row: Row) => {\n const confirmed = await confirm({\n title: t('api_keys.list.confirmDelete', { name: row.name }),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall<{ error?: string }>(\n `/api/api_keys/keys?id=${encodeURIComponent(row.id)}`,\n { method: 'DELETE' },\n { fallback: null },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('api_keys.list.error.deleteFailed')\n flash(message, 'error')\n return\n }\n flash(t('api_keys.list.success.deleted'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n const message = error instanceof Error ? error.message : t('api_keys.list.error.deleteFailed')\n flash(message, 'error')\n }\n }, [confirm, t])\n\n const columns = React.useMemo<ColumnDef<Row>[]>(() => [\n { accessorKey: 'name', header: t('api_keys.list.columns.name') },\n {\n accessorKey: 'keyPrefix',\n header: t('api_keys.list.columns.key'),\n cell: ({ row }) => <code className=\"text-xs\">{row.original.keyPrefix}\u2026</code>,\n },\n {\n accessorKey: 'organizationName',\n header: t('api_keys.list.columns.organization'),\n cell: ({ row }) => row.original.organizationName || t('api_keys.list.noDate'),\n },\n {\n accessorKey: 'roles',\n header: t('api_keys.list.columns.roles'),\n cell: ({ row }) => (\n <div className=\"flex flex-wrap gap-1\">\n {row.original.roles.length === 0 && <span className=\"text-muted-foreground text-xs\">{t('api_keys.list.noRoles')}</span>}\n {row.original.roles.map((role) => (\n <span\n key={role.id}\n className=\"inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium\"\n >\n {role.name || role.id}\n </span>\n ))}\n </div>\n ),\n },\n {\n accessorKey: 'lastUsedAt',\n header: t('api_keys.list.columns.lastUsed'),\n cell: ({ row }) => formatDate(row.original.lastUsedAt, t),\n },\n {\n accessorKey: 'expiresAt',\n header: t('api_keys.list.columns.expires'),\n cell: ({ row }) => formatDate(row.original.expiresAt, t),\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('api_keys.list.title')}\n actions={(\n <Button asChild>\n <Link href=\"/backend/api-keys/create\">{t('api_keys.list.actions.create')}</Link>\n </Button>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n perspective={{ tableId: 'api_keys.list' }}\n rowActions={(row) => (\n <RowActions items={[\n { id: 'delete', label: t('common.delete'), destructive: true, onSelect: () => { void handleDelete(row) } },\n ]} />\n )}\n pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\nimport * as React from 'react'\nimport Link from 'next/link'\nimport { Page, PageBody } from '@open-mercato/ui/backend/Page'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { apiCall, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\n\ntype RoleSummary = { id: string; name: string | null }\n\ntype Row = {\n id: string\n name: string\n description: string | null\n keyPrefix: string\n organizationId: string | null\n organizationName: string | null\n createdAt: string\n lastUsedAt: string | null\n expiresAt: string | null\n roles: RoleSummary[]\n updatedAt?: string | null\n}\n\ntype ResponsePayload = {\n items: Row[]\n total: number\n page: number\n totalPages: number\n}\n\nfunction formatDate(value: string | null, t: (key: string, params?: Record<string, string | number>) => string) {\n if (!value) return t('api_keys.list.noDate')\n try {\n const date = new Date(value)\n if (Number.isNaN(date.getTime())) return t('api_keys.list.noDate')\n return date.toLocaleString()\n } catch {\n return t('api_keys.list.noDate')\n }\n}\n\nexport default function ApiKeysListPage() {\n const [rows, setRows] = React.useState<Row[]>([])\n const [page, setPage] = React.useState(1)\n const [total, setTotal] = React.useState(0)\n const [totalPages, setTotalPages] = React.useState(1)\n const [search, setSearch] = React.useState('')\n const [isLoading, setIsLoading] = React.useState(true)\n const [reloadToken, setReloadToken] = React.useState(0)\n const scopeVersion = useOrganizationScopeVersion()\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n\n React.useEffect(() => {\n let cancelled = false\n async function load() {\n setIsLoading(true)\n try {\n const params = new URLSearchParams()\n params.set('page', String(page))\n params.set('pageSize', '20')\n if (search) params.set('search', search)\n const fallback: ResponsePayload = { items: [], total: 0, page, totalPages: 1 }\n const call = await apiCall<ResponsePayload>(\n `/api/api_keys/keys?${params.toString()}`,\n undefined,\n { fallback },\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('api_keys.list.error.loadFailed')\n flash(message, 'error')\n return\n }\n const payload = call.result ?? fallback\n if (!cancelled) {\n setRows(Array.isArray(payload.items) ? payload.items : [])\n setTotal(payload.total || 0)\n setTotalPages(payload.totalPages || 1)\n }\n } catch (error) {\n if (!cancelled) {\n const message = error instanceof Error ? error.message : t('api_keys.list.error.loadFailed')\n flash(message, 'error')\n }\n } finally {\n if (!cancelled) setIsLoading(false)\n }\n }\n load()\n return () => { cancelled = true }\n }, [page, search, reloadToken, scopeVersion, t])\n\n const handleDelete = React.useCallback(async (row: Row) => {\n const confirmed = await confirm({\n title: t('api_keys.list.confirmDelete', { name: row.name }),\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(row.updatedAt),\n () => apiCall<{ error?: string }>(\n `/api/api_keys/keys?id=${encodeURIComponent(row.id)}`,\n { method: 'DELETE' },\n { fallback: null },\n ),\n )\n if (!call.ok) {\n const errorPayload = call.result as { error?: string } | undefined\n const message = typeof errorPayload?.error === 'string' ? errorPayload.error : t('api_keys.list.error.deleteFailed')\n flash(message, 'error')\n return\n }\n flash(t('api_keys.list.success.deleted'), 'success')\n setReloadToken((token) => token + 1)\n } catch (error) {\n const message = error instanceof Error ? error.message : t('api_keys.list.error.deleteFailed')\n flash(message, 'error')\n }\n }, [confirm, t])\n\n const columns = React.useMemo<ColumnDef<Row>[]>(() => [\n { accessorKey: 'name', header: t('api_keys.list.columns.name') },\n {\n accessorKey: 'keyPrefix',\n header: t('api_keys.list.columns.key'),\n cell: ({ row }) => <code className=\"text-xs\">{row.original.keyPrefix}\u2026</code>,\n },\n {\n accessorKey: 'organizationName',\n header: t('api_keys.list.columns.organization'),\n cell: ({ row }) => row.original.organizationName || t('api_keys.list.noDate'),\n },\n {\n accessorKey: 'roles',\n header: t('api_keys.list.columns.roles'),\n cell: ({ row }) => (\n <div className=\"flex flex-wrap gap-1\">\n {row.original.roles.length === 0 && <span className=\"text-muted-foreground text-xs\">{t('api_keys.list.noRoles')}</span>}\n {row.original.roles.map((role) => (\n <span\n key={role.id}\n className=\"inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium\"\n >\n {role.name || role.id}\n </span>\n ))}\n </div>\n ),\n },\n {\n accessorKey: 'lastUsedAt',\n header: t('api_keys.list.columns.lastUsed'),\n cell: ({ row }) => formatDate(row.original.lastUsedAt, t),\n },\n {\n accessorKey: 'expiresAt',\n header: t('api_keys.list.columns.expires'),\n cell: ({ row }) => formatDate(row.original.expiresAt, t),\n },\n ], [t])\n\n return (\n <Page>\n <PageBody>\n <DataTable\n title={t('api_keys.list.title')}\n actions={(\n <Button asChild>\n <Link href=\"/backend/api-keys/create\">{t('api_keys.list.actions.create')}</Link>\n </Button>\n )}\n columns={columns}\n data={rows}\n searchValue={search}\n onSearchChange={(value) => { setSearch(value); setPage(1) }}\n perspective={{ tableId: 'api_keys.list' }}\n rowActions={(row) => (\n <RowActions items={[\n { id: 'delete', label: t('common.delete'), destructive: true, onSelect: () => { void handleDelete(row) } },\n ]} />\n )}\n pagination={{ page, pageSize: 20, total, totalPages, onPageChange: setPage }}\n isLoading={isLoading}\n />\n </PageBody>\n {ConfirmDialogElement}\n </Page>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -21,7 +21,8 @@ import {
21
21
  DialogHeader,
22
22
  DialogTitle
23
23
  } from "@open-mercato/ui/primitives/dialog";
24
- import { apiCall, readApiResultOrThrow } from "@open-mercato/ui/backend/utils/apiCall";
24
+ import { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from "@open-mercato/ui/backend/utils/apiCall";
25
+ import { buildOptimisticLockHeader } from "@open-mercato/ui/backend/utils/optimisticLock";
25
26
  import { flash } from "@open-mercato/ui/backend/FlashMessages";
26
27
  import { raiseCrudError } from "@open-mercato/ui/backend/utils/serverErrors";
27
28
  import { useT } from "@open-mercato/shared/lib/i18n/context";
@@ -181,11 +182,15 @@ function AttachmentPartitionSettings({ s3Enabled }) {
181
182
  };
182
183
  const method = dialog.mode === "create" ? "POST" : "PUT";
183
184
  const body = dialog.mode === "edit" ? JSON.stringify({ id: dialog.entry.id, ...payload }) : JSON.stringify(payload);
184
- const call = await apiCall("/api/attachments/partitions", {
185
- method,
186
- headers: { "content-type": "application/json" },
187
- body
188
- });
185
+ const lockHeader = dialog.mode === "edit" ? buildOptimisticLockHeader(dialog.entry.updatedAt) : {};
186
+ const call = await withScopedApiRequestHeaders(
187
+ lockHeader,
188
+ () => apiCall("/api/attachments/partitions", {
189
+ method,
190
+ headers: { "content-type": "application/json" },
191
+ body
192
+ })
193
+ );
189
194
  if (!call.ok) {
190
195
  await raiseCrudError(
191
196
  call.response,
@@ -218,9 +223,12 @@ function AttachmentPartitionSettings({ s3Enabled }) {
218
223
  });
219
224
  if (!confirmed) return;
220
225
  try {
221
- const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {
222
- method: "DELETE"
223
- });
226
+ const call = await withScopedApiRequestHeaders(
227
+ buildOptimisticLockHeader(entry.updatedAt),
228
+ () => apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {
229
+ method: "DELETE"
230
+ })
231
+ );
224
232
  if (!call.ok) {
225
233
  await raiseCrudError(
226
234
  call.response,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/modules/attachments/components/AttachmentPartitionSettings.tsx"],
4
- "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n storageDriver: string\n configJson: Record<string, unknown> | null\n envKey: string\n createdAt: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\ntype S3CredentialsSource = 'integration' | 'env'\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n storageDriver: 'local',\n s3CredentialsSource: 'integration' as S3CredentialsSource,\n s3CredentialsEnvPrefix: '',\n s3Bucket: '',\n s3Region: '',\n s3Endpoint: '',\n s3ForcePathStyle: false,\n}\n\nconst ALL_STORAGE_DRIVER_OPTIONS = [\n { value: 'local', label: 'Local filesystem' },\n { value: 's3', label: 'Amazon S3 / S3-compatible', requiresModule: 's3' },\n]\n\nconst OCR_MODEL_DEFAULT_VALUE = '__default__'\n\nconst OCR_MODEL_OPTIONS = [\n { value: OCR_MODEL_DEFAULT_VALUE, label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport type AttachmentPartitionSettingsProps = {\n s3Enabled: boolean\n}\n\nexport function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSettingsProps) {\n const storageDriverOptions = ALL_STORAGE_DRIVER_OPTIONS.filter(\n (opt) => !opt.requiresModule || (opt.requiresModule === 's3' && s3Enabled),\n )\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const s3Unavailable = !s3Enabled\n const s3SelectedWhileUnavailable = s3Unavailable && form.storageDriver === 's3'\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n const cfg = state.entry.configJson ?? {}\n const credsPrefix = typeof cfg.credentialsEnvPrefix === 'string' ? cfg.credentialsEnvPrefix : ''\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n storageDriver: state.entry.storageDriver ?? 'local',\n s3CredentialsSource: credsPrefix ? 'env' : 'integration',\n s3CredentialsEnvPrefix: credsPrefix,\n s3Bucket: typeof cfg.bucket === 'string' ? cfg.bucket : '',\n s3Region: typeof cfg.region === 'string' ? cfg.region : '',\n s3Endpoint: typeof cfg.endpoint === 'string' ? cfg.endpoint : '',\n s3ForcePathStyle: cfg.forcePathStyle === true,\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n if (form.storageDriver === 's3' && form.s3CredentialsSource === 'env') {\n if (!form.s3CredentialsEnvPrefix.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3EnvPrefixRequired',\n 'Credentials env prefix is required when using a separate credentials source.',\n ),\n )\n return\n }\n if (!form.s3Bucket.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3BucketRequired',\n 'Bucket is required when using a separate credentials source.',\n ),\n )\n return\n }\n }\n setSubmitting(true)\n setError(null)\n try {\n const s3ConfigJson =\n form.storageDriver === 's3'\n ? {\n bucket: form.s3Bucket.trim() || undefined,\n region: form.s3Region.trim() || undefined,\n endpoint: form.s3Endpoint.trim() || undefined,\n forcePathStyle: form.s3ForcePathStyle || undefined,\n ...(form.s3CredentialsSource === 'env'\n ? { credentialsEnvPrefix: form.s3CredentialsEnvPrefix.trim() || undefined }\n : {}),\n }\n : null\n\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n storageDriver: form.storageDriver,\n configJson: s3ConfigJson,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const call = await apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n })\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.storageDriver', 'Storage'),\n accessorKey: 'storageDriver',\n cell: ({ row }) => (\n <span className=\"text-sm capitalize\">\n {row.original.storageDriver === 's3' ? 'S3' : row.original.storageDriver ?? 'local'}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel ? form.ocrModel : OCR_MODEL_DEFAULT_VALUE}\n onValueChange={(value) =>\n setForm((prev) => ({\n ...prev,\n ocrModel: value === OCR_MODEL_DEFAULT_VALUE ? '' : value,\n }))\n }\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(\n `attachments.partitions.form.ocrModelOptions.${\n option.value === OCR_MODEL_DEFAULT_VALUE ? 'default' : option.value\n }`,\n option.label,\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-storage-driver\">\n {t('attachments.partitions.form.storageDriverLabel', 'Storage Driver')}\n </Label>\n <select\n id=\"partition-storage-driver\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.storageDriver}\n onChange={(event) => setForm((prev) => ({ ...prev, storageDriver: event.target.value }))}\n >\n {storageDriverOptions.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.value === 's3' && s3Unavailable}\n >\n {option.label}\n </option>\n ))}\n </select>\n {s3Unavailable ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.storageDriverS3DisabledHelp',\n 'S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart to enable S3 partitions.',\n )}\n </p>\n ) : null}\n </div>\n {form.storageDriver === 's3' && (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {t('attachments.partitions.form.s3ConfigTitle', 'S3 Configuration')}\n </p>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-source\">\n {t('attachments.partitions.form.s3CredsSourceLabel', 'Credentials source')}\n </Label>\n <select\n id=\"partition-s3-creds-source\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.s3CredentialsSource}\n onChange={(event) =>\n setForm((prev) => ({\n ...prev,\n s3CredentialsSource: event.target.value as S3CredentialsSource,\n ...(event.target.value === 'integration' ? { s3CredentialsEnvPrefix: '' } : {}),\n }))\n }\n >\n <option value=\"integration\">\n {t(\n 'attachments.partitions.form.s3CredsSourceIntegration',\n 'Use Integration Marketplace credentials',\n )}\n </option>\n <option value=\"env\">\n {t(\n 'attachments.partitions.form.s3CredsSourceEnv',\n 'Use separate env-based credentials',\n )}\n </option>\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3CredsSourceIntegrationHelp',\n 'This partition reuses the credentials, bucket, region and endpoint configured in the S3 integration. Override individual fields below only when needed.',\n )\n : t(\n 'attachments.partitions.form.s3CredsSourceEnvHelp',\n 'This partition uses its own credentials read from environment variables. The integration credentials are ignored. Bucket and credentials prefix are required.',\n )}\n </p>\n </div>\n {form.s3CredentialsSource === 'env' && (\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-prefix\">\n {t('attachments.partitions.form.s3CredsPrefixLabel', 'Credentials Env Prefix')}\n </Label>\n <Input\n id=\"partition-s3-creds-prefix\"\n value={form.s3CredentialsEnvPrefix}\n onChange={(event) => setForm((prev) => ({ ...prev, s3CredentialsEnvPrefix: event.target.value }))}\n placeholder=\"OM_INTEGRATION_STORAGE_S3\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.s3CredsPrefixHelp',\n 'Reads {PREFIX}_ACCESS_KEY_ID and {PREFIX}_SECRET_ACCESS_KEY from environment variables.',\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-bucket\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3BucketOverrideLabel', 'Bucket override (optional)')\n : t('attachments.partitions.form.s3BucketLabel', 'Bucket')}\n </Label>\n <Input\n id=\"partition-s3-bucket\"\n value={form.s3Bucket}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Bucket: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3BucketOverridePlaceholder',\n 'Leave empty to use integration bucket',\n )\n : 'my-company-attachments'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-region\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3RegionOverrideLabel', 'Region override (optional)')\n : t('attachments.partitions.form.s3RegionLabel', 'Region')}\n </Label>\n <Input\n id=\"partition-s3-region\"\n value={form.s3Region}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Region: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3RegionOverridePlaceholder',\n 'Leave empty to use integration region',\n )\n : 'us-east-1'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-endpoint\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3EndpointOverrideLabel', 'Custom endpoint override (optional)')\n : t('attachments.partitions.form.s3EndpointLabel', 'Custom Endpoint')}\n </Label>\n <Input\n id=\"partition-s3-endpoint\"\n value={form.s3Endpoint}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Endpoint: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3EndpointOverridePlaceholder',\n 'Leave empty to use integration endpoint',\n )\n : 'https://fra1.digitaloceanspaces.com'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('attachments.partitions.form.s3EndpointHelp', 'Leave empty for AWS S3. Required for MinIO, DigitalOcean Spaces, etc.')}\n </p>\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.s3ForcePathStyle}\n onChange={(event) => setForm((prev) => ({ ...prev, s3ForcePathStyle: event.target.checked }))}\n />\n {t('attachments.partitions.form.s3ForcePathStyleLabel', 'Force Path Style (required for MinIO)')}\n </label>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {s3SelectedWhileUnavailable ? (\n <p className=\"text-sm text-status-error-text\">\n {t(\n 'attachments.partitions.errors.s3Disabled',\n 'This partition uses S3, but S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart the app.',\n )}\n </p>\n ) : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting || s3SelectedWhileUnavailable}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport type { ColumnDef } from '@tanstack/react-table'\nimport { DataTable } from '@open-mercato/ui/backend/DataTable'\nimport { RowActions } from '@open-mercato/ui/backend/RowActions'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Input } from '@open-mercato/ui/primitives/input'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport {\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from '@open-mercato/ui/primitives/select'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@open-mercato/ui/primitives/dialog'\nimport { apiCall, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'\nimport { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { raiseCrudError } from '@open-mercato/ui/backend/utils/serverErrors'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'\nimport { resolvePartitionEnvKey } from '@open-mercato/core/modules/attachments/lib/partitionEnv'\n\ntype Partition = {\n id: string\n code: string\n title: string\n description: string | null\n isPublic: boolean\n requiresOcr: boolean\n ocrModel: string | null\n storageDriver: string\n configJson: Record<string, unknown> | null\n envKey: string\n createdAt: string | null\n updatedAt?: string | null\n}\n\ntype DialogState =\n | { mode: 'create' }\n | { mode: 'edit'; entry: Partition }\n\ntype S3CredentialsSource = 'integration' | 'env'\n\nconst DEFAULT_FORM = {\n code: '',\n title: '',\n description: '',\n isPublic: false,\n requiresOcr: true,\n ocrModel: '',\n storageDriver: 'local',\n s3CredentialsSource: 'integration' as S3CredentialsSource,\n s3CredentialsEnvPrefix: '',\n s3Bucket: '',\n s3Region: '',\n s3Endpoint: '',\n s3ForcePathStyle: false,\n}\n\nconst ALL_STORAGE_DRIVER_OPTIONS = [\n { value: 'local', label: 'Local filesystem' },\n { value: 's3', label: 'Amazon S3 / S3-compatible', requiresModule: 's3' },\n]\n\nconst OCR_MODEL_DEFAULT_VALUE = '__default__'\n\nconst OCR_MODEL_OPTIONS = [\n { value: OCR_MODEL_DEFAULT_VALUE, label: 'Default (from environment)' },\n { value: 'gpt-4o', label: 'GPT-4o (Recommended)' },\n { value: 'gpt-4o-mini', label: 'GPT-4o Mini (Faster, Lower Cost)' },\n]\n\nexport type AttachmentPartitionSettingsProps = {\n s3Enabled: boolean\n}\n\nexport function AttachmentPartitionSettings({ s3Enabled }: AttachmentPartitionSettingsProps) {\n const storageDriverOptions = ALL_STORAGE_DRIVER_OPTIONS.filter(\n (opt) => !opt.requiresModule || (opt.requiresModule === 's3' && s3Enabled),\n )\n const t = useT()\n const { confirm, ConfirmDialogElement } = useConfirmDialog()\n const [items, setItems] = React.useState<Partition[]>([])\n const [loading, setLoading] = React.useState(false)\n const [search, setSearch] = React.useState('')\n const [dialog, setDialog] = React.useState<DialogState | null>(null)\n const [form, setForm] = React.useState(DEFAULT_FORM)\n const [submitting, setSubmitting] = React.useState(false)\n const [error, setError] = React.useState<string | null>(null)\n\n const s3Unavailable = !s3Enabled\n const s3SelectedWhileUnavailable = s3Unavailable && form.storageDriver === 's3'\n\n const loadErrorMessage = t('attachments.partitions.errors.load', 'Failed to load partitions.')\n\n const loadItems = React.useCallback(async () => {\n setLoading(true)\n try {\n const payload = await readApiResultOrThrow<{ items?: Partition[] }>(\n '/api/attachments/partitions',\n undefined,\n { errorMessage: loadErrorMessage },\n )\n const normalized = Array.isArray(payload.items) ? payload.items : []\n const withDefaults = normalized.map((entry) => ({\n ...entry,\n requiresOcr: typeof entry.requiresOcr === 'boolean' ? entry.requiresOcr : true,\n }))\n setItems(withDefaults)\n } catch (err) {\n console.error('[attachments.partitions] list failed', err)\n flash(loadErrorMessage, 'error')\n } finally {\n setLoading(false)\n }\n }, [loadErrorMessage])\n\n React.useEffect(() => {\n loadItems().catch(() => {})\n }, [loadItems])\n\n const filteredItems = React.useMemo(() => {\n if (!search.trim()) return items\n const term = search.trim().toLowerCase()\n return items.filter(\n (entry) =>\n entry.code.toLowerCase().includes(term) ||\n entry.title.toLowerCase().includes(term) ||\n (entry.description ?? '').toLowerCase().includes(term),\n )\n }, [items, search])\n\n const openDialog = React.useCallback((state: DialogState) => {\n if (state.mode === 'edit') {\n const cfg = state.entry.configJson ?? {}\n const credsPrefix = typeof cfg.credentialsEnvPrefix === 'string' ? cfg.credentialsEnvPrefix : ''\n setForm({\n code: state.entry.code,\n title: state.entry.title,\n description: state.entry.description ?? '',\n isPublic: state.entry.isPublic,\n requiresOcr: state.entry.requiresOcr,\n ocrModel: state.entry.ocrModel ?? '',\n storageDriver: state.entry.storageDriver ?? 'local',\n s3CredentialsSource: credsPrefix ? 'env' : 'integration',\n s3CredentialsEnvPrefix: credsPrefix,\n s3Bucket: typeof cfg.bucket === 'string' ? cfg.bucket : '',\n s3Region: typeof cfg.region === 'string' ? cfg.region : '',\n s3Endpoint: typeof cfg.endpoint === 'string' ? cfg.endpoint : '',\n s3ForcePathStyle: cfg.forcePathStyle === true,\n })\n } else {\n setForm(DEFAULT_FORM)\n }\n setError(null)\n setDialog(state)\n }, [])\n\n const closeDialog = React.useCallback(() => {\n setDialog(null)\n setError(null)\n setSubmitting(false)\n setForm(DEFAULT_FORM)\n }, [])\n\n const handleSubmit = React.useCallback(async () => {\n if (!dialog) return\n const trimmedCode = form.code.trim()\n const trimmedTitle = form.title.trim()\n if (!trimmedCode || !trimmedTitle) {\n setError(t('attachments.partitions.errors.required', 'Code and title are required.'))\n return\n }\n if (form.storageDriver === 's3' && form.s3CredentialsSource === 'env') {\n if (!form.s3CredentialsEnvPrefix.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3EnvPrefixRequired',\n 'Credentials env prefix is required when using a separate credentials source.',\n ),\n )\n return\n }\n if (!form.s3Bucket.trim()) {\n setError(\n t(\n 'attachments.partitions.errors.s3BucketRequired',\n 'Bucket is required when using a separate credentials source.',\n ),\n )\n return\n }\n }\n setSubmitting(true)\n setError(null)\n try {\n const s3ConfigJson =\n form.storageDriver === 's3'\n ? {\n bucket: form.s3Bucket.trim() || undefined,\n region: form.s3Region.trim() || undefined,\n endpoint: form.s3Endpoint.trim() || undefined,\n forcePathStyle: form.s3ForcePathStyle || undefined,\n ...(form.s3CredentialsSource === 'env'\n ? { credentialsEnvPrefix: form.s3CredentialsEnvPrefix.trim() || undefined }\n : {}),\n }\n : null\n\n const payload = {\n code: trimmedCode,\n title: trimmedTitle,\n description: form.description.trim() || undefined,\n isPublic: form.isPublic,\n requiresOcr: form.requiresOcr,\n ocrModel: form.ocrModel.trim() || null,\n storageDriver: form.storageDriver,\n configJson: s3ConfigJson,\n }\n const method = dialog.mode === 'create' ? 'POST' : 'PUT'\n const body =\n dialog.mode === 'edit'\n ? JSON.stringify({ id: dialog.entry.id, ...payload })\n : JSON.stringify(payload)\n const lockHeader =\n dialog.mode === 'edit' ? buildOptimisticLockHeader(dialog.entry.updatedAt) : {}\n const call = await withScopedApiRequestHeaders(lockHeader, () =>\n apiCall('/api/attachments/partitions', {\n method,\n headers: { 'content-type': 'application/json' },\n body,\n }),\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.save', 'Failed to save partition.'),\n )\n }\n flash(\n dialog.mode === 'create'\n ? t('attachments.partitions.messages.created', 'Partition created.')\n : t('attachments.partitions.messages.updated', 'Partition updated.'),\n 'success',\n )\n closeDialog()\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] save failed', err)\n const message =\n err instanceof Error ? err.message : t('attachments.partitions.errors.save', 'Failed to save partition.')\n setError(message)\n } finally {\n setSubmitting(false)\n }\n }, [dialog, form, t, closeDialog, loadItems])\n\n const handleDelete = React.useCallback(\n async (entry: Partition) => {\n const confirmMessage = t('attachments.partitions.confirm.delete', 'Delete partition \"{{code}}\"?').replace(\n '{{code}}',\n entry.code,\n )\n const confirmed = await confirm({\n title: confirmMessage,\n variant: 'destructive',\n })\n if (!confirmed) return\n try {\n const call = await withScopedApiRequestHeaders(\n buildOptimisticLockHeader(entry.updatedAt),\n () =>\n apiCall(`/api/attachments/partitions?id=${encodeURIComponent(entry.id)}`, {\n method: 'DELETE',\n }),\n )\n if (!call.ok) {\n await raiseCrudError(\n call.response,\n t('attachments.partitions.errors.delete', 'Failed to delete partition.'),\n )\n }\n flash(t('attachments.partitions.messages.deleted', 'Partition removed.'), 'success')\n await loadItems()\n } catch (err) {\n console.error('[attachments.partitions] delete failed', err)\n flash(t('attachments.partitions.errors.delete', 'Failed to delete partition.'), 'error')\n }\n },\n [confirm, loadItems, t],\n )\n\n const columns = React.useMemo<ColumnDef<Partition>[]>(\n () => [\n {\n header: t('attachments.partitions.table.code', 'Code'),\n accessorKey: 'code',\n cell: ({ row }) => <code className=\"font-mono text-xs\">{row.original.code}</code>,\n },\n {\n header: t('attachments.partitions.table.title', 'Title'),\n accessorKey: 'title',\n cell: ({ row }) => (\n <div className=\"flex flex-col\">\n <span className=\"font-medium\">{row.original.title}</span>\n {row.original.description ? (\n <span className=\"text-xs text-muted-foreground line-clamp-2\">{row.original.description}</span>\n ) : null}\n </div>\n ),\n },\n {\n header: t('attachments.partitions.table.visibility', 'Visibility'),\n accessorKey: 'isPublic',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.isPublic\n ? t('attachments.partitions.table.public', 'Public')\n : t('attachments.partitions.table.private', 'Private')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.ocr', 'OCR'),\n accessorKey: 'requiresOcr',\n cell: ({ row }) => (\n <span className=\"text-sm\">\n {row.original.requiresOcr\n ? t('common.enabled', 'Enabled')\n : t('common.disabled', 'Disabled')}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.storageDriver', 'Storage'),\n accessorKey: 'storageDriver',\n cell: ({ row }) => (\n <span className=\"text-sm capitalize\">\n {row.original.storageDriver === 's3' ? 'S3' : row.original.storageDriver ?? 'local'}\n </span>\n ),\n },\n {\n header: t('attachments.partitions.table.envKey', 'Env variable'),\n accessorKey: 'envKey',\n cell: ({ row }) => <code className=\"text-xs\">{row.original.envKey}</code>,\n },\n ],\n [t],\n )\n\n const tableLabels = React.useMemo(\n () => ({\n search: t('attachments.partitions.table.search', 'Search partitions\u2026'),\n empty: t('attachments.partitions.table.empty', 'No partitions configured.'),\n }),\n [t],\n )\n\n const formKeyHandler = React.useCallback(\n (event: React.KeyboardEvent<HTMLFormElement>) => {\n if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {\n event.preventDefault()\n void handleSubmit()\n }\n },\n [handleSubmit],\n )\n\n return (\n <div className=\"space-y-6 rounded-lg border bg-card p-6\">\n <div className=\"flex flex-col gap-2 md:flex-row md:items-center md:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold\">\n {t('attachments.partitions.title', 'Attachment partitions')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {t('attachments.partitions.description', 'Define storage partitions and visibility for uploads.')}\n </p>\n </div>\n <Button size=\"sm\" onClick={() => openDialog({ mode: 'create' })}>\n {t('attachments.partitions.actions.add', 'Add partition')}\n </Button>\n </div>\n <DataTable<Partition>\n columns={columns}\n data={filteredItems}\n searchValue={search}\n onSearchChange={(value: string) => setSearch(value)}\n searchPlaceholder={tableLabels.search}\n emptyState={<p className=\"py-8 text-center text-sm text-muted-foreground\">{tableLabels.empty}</p>}\n isLoading={loading}\n refreshButton={{\n label: t('attachments.partitions.actions.refresh', 'Refresh'),\n onRefresh: () => { void loadItems() },\n isRefreshing: loading,\n }}\n rowActions={(entry) => (\n <RowActions\n items={[\n {\n id: 'edit',\n label: t('attachments.partitions.actions.edit', 'Edit'),\n onSelect: () => openDialog({ mode: 'edit', entry }),\n },\n {\n id: 'delete',\n label: t('attachments.partitions.actions.delete', 'Delete'),\n destructive: true,\n onSelect: () => { void handleDelete(entry) },\n },\n ]}\n />\n )}\n />\n <Dialog open={dialog !== null} onOpenChange={(open) => { if (!open) closeDialog() }}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editTitle', 'Edit partition')\n : t('attachments.partitions.dialog.createTitle', 'Create partition')}\n </DialogTitle>\n <DialogDescription>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.dialog.editDescription', 'Update partition metadata and visibility.')\n : t('attachments.partitions.dialog.createDescription', 'Define a storage partition for attachments.')}\n </DialogDescription>\n </DialogHeader>\n <form\n className=\"space-y-4\"\n onKeyDown={formKeyHandler}\n onSubmit={(event) => {\n event.preventDefault()\n void handleSubmit()\n }}\n >\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-code\">{t('attachments.partitions.form.codeLabel', 'Code')}</Label>\n <Input\n id=\"partition-code\"\n value={form.code}\n onChange={(event) => setForm((prev) => ({ ...prev, code: event.target.value }))}\n placeholder={t('attachments.partitions.form.codePlaceholder', 'e.g. marketingAssets')}\n disabled={dialog?.mode === 'edit'}\n className=\"font-mono uppercase\"\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-title\">{t('attachments.partitions.form.titleLabel', 'Title')}</Label>\n <Input\n id=\"partition-title\"\n value={form.title}\n onChange={(event) => setForm((prev) => ({ ...prev, title: event.target.value }))}\n placeholder={t('attachments.partitions.form.titlePlaceholder', 'e.g. Marketing assets')}\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-description\">{t('attachments.partitions.form.descriptionLabel', 'Description')}</Label>\n <textarea\n id=\"partition-description\"\n className=\"min-h-[80px] w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.description}\n onChange={(event) => setForm((prev) => ({ ...prev, description: event.target.value }))}\n placeholder={t('attachments.partitions.form.descriptionPlaceholder', 'Explain how this partition is used.')}\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.isPublic}\n onChange={(event) => setForm((prev) => ({ ...prev, isPublic: event.target.checked }))}\n />\n {t('attachments.partitions.form.publicLabel', 'Publicly accessible')}\n </label>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.requiresOcr}\n onChange={(event) => setForm((prev) => ({ ...prev, requiresOcr: event.target.checked }))}\n />\n {t('attachments.partitions.form.ocrLabel', 'Require OCR/text extraction')}\n </label>\n {form.requiresOcr && (\n <div className=\"space-y-2 pl-6\">\n <Label htmlFor=\"partition-ocr-model\">\n {t('attachments.partitions.form.ocrModelLabel', 'OCR Model')}\n </Label>\n <Select\n value={form.ocrModel ? form.ocrModel : OCR_MODEL_DEFAULT_VALUE}\n onValueChange={(value) =>\n setForm((prev) => ({\n ...prev,\n ocrModel: value === OCR_MODEL_DEFAULT_VALUE ? '' : value,\n }))\n }\n >\n <SelectTrigger id=\"partition-ocr-model\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {OCR_MODEL_OPTIONS.map((option) => (\n <SelectItem key={option.value} value={option.value}>\n {t(\n `attachments.partitions.form.ocrModelOptions.${\n option.value === OCR_MODEL_DEFAULT_VALUE ? 'default' : option.value\n }`,\n option.label,\n )}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.ocrModelHelp',\n 'Choose the LLM model for OCR processing. Falls back to OCR_MODEL environment variable or gpt-4o.'\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-storage-driver\">\n {t('attachments.partitions.form.storageDriverLabel', 'Storage Driver')}\n </Label>\n <select\n id=\"partition-storage-driver\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.storageDriver}\n onChange={(event) => setForm((prev) => ({ ...prev, storageDriver: event.target.value }))}\n >\n {storageDriverOptions.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.value === 's3' && s3Unavailable}\n >\n {option.label}\n </option>\n ))}\n </select>\n {s3Unavailable ? (\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.storageDriverS3DisabledHelp',\n 'S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart to enable S3 partitions.',\n )}\n </p>\n ) : null}\n </div>\n {form.storageDriver === 's3' && (\n <div className=\"space-y-3 rounded-md border bg-muted/30 p-3\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n {t('attachments.partitions.form.s3ConfigTitle', 'S3 Configuration')}\n </p>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-source\">\n {t('attachments.partitions.form.s3CredsSourceLabel', 'Credentials source')}\n </Label>\n <select\n id=\"partition-s3-creds-source\"\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={form.s3CredentialsSource}\n onChange={(event) =>\n setForm((prev) => ({\n ...prev,\n s3CredentialsSource: event.target.value as S3CredentialsSource,\n ...(event.target.value === 'integration' ? { s3CredentialsEnvPrefix: '' } : {}),\n }))\n }\n >\n <option value=\"integration\">\n {t(\n 'attachments.partitions.form.s3CredsSourceIntegration',\n 'Use Integration Marketplace credentials',\n )}\n </option>\n <option value=\"env\">\n {t(\n 'attachments.partitions.form.s3CredsSourceEnv',\n 'Use separate env-based credentials',\n )}\n </option>\n </select>\n <p className=\"text-xs text-muted-foreground\">\n {form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3CredsSourceIntegrationHelp',\n 'This partition reuses the credentials, bucket, region and endpoint configured in the S3 integration. Override individual fields below only when needed.',\n )\n : t(\n 'attachments.partitions.form.s3CredsSourceEnvHelp',\n 'This partition uses its own credentials read from environment variables. The integration credentials are ignored. Bucket and credentials prefix are required.',\n )}\n </p>\n </div>\n {form.s3CredentialsSource === 'env' && (\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-creds-prefix\">\n {t('attachments.partitions.form.s3CredsPrefixLabel', 'Credentials Env Prefix')}\n </Label>\n <Input\n id=\"partition-s3-creds-prefix\"\n value={form.s3CredentialsEnvPrefix}\n onChange={(event) => setForm((prev) => ({ ...prev, s3CredentialsEnvPrefix: event.target.value }))}\n placeholder=\"OM_INTEGRATION_STORAGE_S3\"\n className=\"font-mono\"\n />\n <p className=\"text-xs text-muted-foreground\">\n {t(\n 'attachments.partitions.form.s3CredsPrefixHelp',\n 'Reads {PREFIX}_ACCESS_KEY_ID and {PREFIX}_SECRET_ACCESS_KEY from environment variables.',\n )}\n </p>\n </div>\n )}\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-bucket\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3BucketOverrideLabel', 'Bucket override (optional)')\n : t('attachments.partitions.form.s3BucketLabel', 'Bucket')}\n </Label>\n <Input\n id=\"partition-s3-bucket\"\n value={form.s3Bucket}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Bucket: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3BucketOverridePlaceholder',\n 'Leave empty to use integration bucket',\n )\n : 'my-company-attachments'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-region\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3RegionOverrideLabel', 'Region override (optional)')\n : t('attachments.partitions.form.s3RegionLabel', 'Region')}\n </Label>\n <Input\n id=\"partition-s3-region\"\n value={form.s3Region}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Region: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3RegionOverridePlaceholder',\n 'Leave empty to use integration region',\n )\n : 'us-east-1'\n }\n />\n </div>\n <div className=\"space-y-2\">\n <Label htmlFor=\"partition-s3-endpoint\">\n {form.s3CredentialsSource === 'integration'\n ? t('attachments.partitions.form.s3EndpointOverrideLabel', 'Custom endpoint override (optional)')\n : t('attachments.partitions.form.s3EndpointLabel', 'Custom Endpoint')}\n </Label>\n <Input\n id=\"partition-s3-endpoint\"\n value={form.s3Endpoint}\n onChange={(event) => setForm((prev) => ({ ...prev, s3Endpoint: event.target.value }))}\n placeholder={\n form.s3CredentialsSource === 'integration'\n ? t(\n 'attachments.partitions.form.s3EndpointOverridePlaceholder',\n 'Leave empty to use integration endpoint',\n )\n : 'https://fra1.digitaloceanspaces.com'\n }\n />\n <p className=\"text-xs text-muted-foreground\">\n {t('attachments.partitions.form.s3EndpointHelp', 'Leave empty for AWS S3. Required for MinIO, DigitalOcean Spaces, etc.')}\n </p>\n </div>\n <label className=\"flex items-center gap-2 text-sm font-medium\">\n <input\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border\"\n checked={form.s3ForcePathStyle}\n onChange={(event) => setForm((prev) => ({ ...prev, s3ForcePathStyle: event.target.checked }))}\n />\n {t('attachments.partitions.form.s3ForcePathStyleLabel', 'Force Path Style (required for MinIO)')}\n </label>\n </div>\n )}\n {dialog ? (\n <div className=\"rounded-md border bg-muted/50 px-3 py-2 text-xs text-muted-foreground\">\n <div>\n {t('attachments.partitions.form.envKeyHelp', 'Set this env var to override storage path:')}\n </div>\n <code>\n {dialog.mode === 'edit'\n ? dialog.entry.envKey\n : form.code.trim()\n ? resolvePartitionEnvKey(form.code.trim())\n : 'ATTACHMENTS_PARTITION_CODE_ROOT'}\n </code>\n </div>\n ) : null}\n {s3SelectedWhileUnavailable ? (\n <p className=\"text-sm text-status-error-text\">\n {t(\n 'attachments.partitions.errors.s3Disabled',\n 'This partition uses S3, but S3 is disabled. Set OM_ENABLE_STORAGE_S3=true and restart the app.',\n )}\n </p>\n ) : null}\n {error ? <p className=\"text-sm text-status-error-text\">{error}</p> : null}\n </form>\n <DialogFooter>\n <Button variant=\"ghost\" onClick={closeDialog}>\n {t('attachments.partitions.actions.cancel', 'Cancel')}\n </Button>\n <Button onClick={() => void handleSubmit()} disabled={submitting || s3SelectedWhileUnavailable}>\n {dialog?.mode === 'edit'\n ? t('attachments.partitions.actions.save', 'Save changes')\n : t('attachments.partitions.actions.create', 'Create partition')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n {ConfirmDialogElement}\n </div>\n )\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }