@open-mercato/core 0.6.6-develop.6205.1.109e4b6a84 → 0.6.6-develop.6227.1.2695efff30

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 (198) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/modules/currencies/backend/currencies/[id]/page.js +33 -8
  3. package/dist/modules/currencies/backend/currencies/[id]/page.js.map +2 -2
  4. package/dist/modules/currencies/backend/currencies/page.js +63 -27
  5. package/dist/modules/currencies/backend/currencies/page.js.map +2 -2
  6. package/dist/modules/currencies/backend/exchange-rates/page.js +35 -14
  7. package/dist/modules/currencies/backend/exchange-rates/page.js.map +2 -2
  8. package/dist/modules/currencies/components/CurrencyFetchingConfig.js +104 -41
  9. package/dist/modules/currencies/components/CurrencyFetchingConfig.js.map +2 -2
  10. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js +57 -8
  11. package/dist/modules/customer_accounts/api/admin/roles/[id]/acl.js.map +2 -2
  12. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js +5 -2
  13. package/dist/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.js.map +2 -2
  14. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js +30 -14
  15. package/dist/modules/customer_accounts/backend/customer_accounts/roles/page.js.map +2 -2
  16. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js +29 -19
  17. package/dist/modules/customer_accounts/widgets/injection/account-status/widget.client.js.map +2 -2
  18. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js +166 -64
  19. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.js.map +2 -2
  20. package/dist/modules/customers/components/AddressFormatSettings.js +31 -16
  21. package/dist/modules/customers/components/AddressFormatSettings.js.map +2 -2
  22. package/dist/modules/customers/components/DictionarySettings.js +57 -31
  23. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  24. package/dist/modules/customers/components/PipelineSettings.js +156 -76
  25. package/dist/modules/customers/components/PipelineSettings.js.map +2 -2
  26. package/dist/modules/customers/components/calendar/AgendaList.js +10 -4
  27. package/dist/modules/customers/components/calendar/AgendaList.js.map +2 -2
  28. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +18 -2
  29. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  30. package/dist/modules/customers/components/calendar/MonthGrid.js +5 -2
  31. package/dist/modules/customers/components/calendar/MonthGrid.js.map +2 -2
  32. package/dist/modules/customers/components/calendar/editor/PeopleField.js +6 -0
  33. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  34. package/dist/modules/customers/components/calendar/editor/RelatedToField.js +2 -0
  35. package/dist/modules/customers/components/calendar/editor/RelatedToField.js.map +2 -2
  36. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +4 -1
  37. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  38. package/dist/modules/customers/lib/calendar/labels.js +33 -0
  39. package/dist/modules/customers/lib/calendar/labels.js.map +7 -0
  40. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js +32 -11
  41. package/dist/modules/dashboards/components/WidgetVisibilityEditor.js.map +2 -2
  42. package/dist/modules/data_sync/api/mappings/[id]/route.js +59 -0
  43. package/dist/modules/data_sync/api/mappings/[id]/route.js.map +2 -2
  44. package/dist/modules/data_sync/api/mappings/route.js +44 -0
  45. package/dist/modules/data_sync/api/mappings/route.js.map +2 -2
  46. package/dist/modules/data_sync/api/run.js +31 -0
  47. package/dist/modules/data_sync/api/run.js.map +2 -2
  48. package/dist/modules/data_sync/api/runs/[id]/cancel.js +31 -0
  49. package/dist/modules/data_sync/api/runs/[id]/cancel.js.map +2 -2
  50. package/dist/modules/data_sync/api/runs/[id]/retry.js +31 -0
  51. package/dist/modules/data_sync/api/runs/[id]/retry.js.map +2 -2
  52. package/dist/modules/data_sync/api/schedules/[id]/route.js +59 -3
  53. package/dist/modules/data_sync/api/schedules/[id]/route.js.map +2 -2
  54. package/dist/modules/data_sync/api/schedules/route.js +33 -4
  55. package/dist/modules/data_sync/api/schedules/route.js.map +2 -2
  56. package/dist/modules/directory/api/organization-branding/route.js +54 -2
  57. package/dist/modules/directory/api/organization-branding/route.js.map +2 -2
  58. package/dist/modules/directory/backend/directory/branding/page.js +15 -9
  59. package/dist/modules/directory/backend/directory/branding/page.js.map +2 -2
  60. package/dist/modules/directory/backend/directory/organizations/page.js +28 -9
  61. package/dist/modules/directory/backend/directory/organizations/page.js.map +2 -2
  62. package/dist/modules/directory/backend/directory/tenants/page.js +29 -13
  63. package/dist/modules/directory/backend/directory/tenants/page.js.map +2 -2
  64. package/dist/modules/entities/api/definitions.batch.js +15 -0
  65. package/dist/modules/entities/api/definitions.batch.js.map +2 -2
  66. package/dist/modules/entities/api/definitions.js +26 -0
  67. package/dist/modules/entities/api/definitions.js.map +2 -2
  68. package/dist/modules/entities/api/definitions.mutation-guard.js +57 -0
  69. package/dist/modules/entities/api/definitions.mutation-guard.js.map +7 -0
  70. package/dist/modules/entities/api/definitions.restore.js +15 -0
  71. package/dist/modules/entities/api/definitions.restore.js.map +2 -2
  72. package/dist/modules/entities/api/entities.js +31 -3
  73. package/dist/modules/entities/api/entities.js.map +2 -2
  74. package/dist/modules/entities/api/records.js +18 -0
  75. package/dist/modules/entities/api/records.js.map +2 -2
  76. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js +28 -10
  77. package/dist/modules/entities/backend/entities/user/[entityId]/records/page.js.map +2 -2
  78. package/dist/modules/feature_toggles/api/overrides/route.js +38 -1
  79. package/dist/modules/feature_toggles/api/overrides/route.js.map +2 -2
  80. package/dist/modules/inbox_ops/api/emails/[id]/route.js +30 -0
  81. package/dist/modules/inbox_ops/api/emails/[id]/route.js.map +2 -2
  82. package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js +30 -0
  83. package/dist/modules/inbox_ops/api/proposals/[id]/accept-all/route.js.map +2 -2
  84. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js +31 -0
  85. package/dist/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.js.map +2 -2
  86. package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js +31 -0
  87. package/dist/modules/inbox_ops/api/proposals/[id]/categorize/route.js.map +2 -2
  88. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js +68 -39
  89. package/dist/modules/messages/components/message-detail/hooks/useMessageDetailsActions.js.map +2 -2
  90. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js +14 -4
  91. package/dist/modules/notifications/frontend/NotificationSettingsPageClient.js.map +2 -2
  92. package/dist/modules/payment_gateways/api/cancel/route.js +37 -0
  93. package/dist/modules/payment_gateways/api/cancel/route.js.map +2 -2
  94. package/dist/modules/payment_gateways/api/capture/route.js +37 -0
  95. package/dist/modules/payment_gateways/api/capture/route.js.map +2 -2
  96. package/dist/modules/payment_gateways/api/guards.js +31 -0
  97. package/dist/modules/payment_gateways/api/guards.js.map +7 -0
  98. package/dist/modules/payment_gateways/api/refund/route.js +37 -0
  99. package/dist/modules/payment_gateways/api/refund/route.js.map +2 -2
  100. package/dist/modules/payment_gateways/api/sessions/route.js +37 -0
  101. package/dist/modules/payment_gateways/api/sessions/route.js.map +2 -2
  102. package/dist/modules/planner/api/availability-date-specific.js +11 -1
  103. package/dist/modules/planner/api/availability-date-specific.js.map +2 -2
  104. package/dist/modules/planner/backend/planner/availability-rulesets/page.js +20 -3
  105. package/dist/modules/planner/backend/planner/availability-rulesets/page.js.map +2 -2
  106. package/dist/modules/planner/commands/availability-date-specific.js +16 -0
  107. package/dist/modules/planner/commands/availability-date-specific.js.map +2 -2
  108. package/dist/modules/planner/components/AvailabilityRulesEditor.js +109 -40
  109. package/dist/modules/planner/components/AvailabilityRulesEditor.js.map +2 -2
  110. package/dist/modules/query_index/api/purge.js +35 -3
  111. package/dist/modules/query_index/api/purge.js.map +2 -2
  112. package/dist/modules/query_index/api/reindex.js +41 -3
  113. package/dist/modules/query_index/api/reindex.js.map +2 -2
  114. package/dist/modules/query_index/components/QueryIndexesTable.js +57 -24
  115. package/dist/modules/query_index/components/QueryIndexesTable.js.map +2 -2
  116. package/dist/modules/shipping_carriers/api/shipments/route.js +31 -0
  117. package/dist/modules/shipping_carriers/api/shipments/route.js.map +2 -2
  118. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js +55 -0
  119. package/dist/modules/shipping_carriers/api/tracking/refresh/route.js.map +7 -0
  120. package/dist/modules/shipping_carriers/data/validators.js +6 -1
  121. package/dist/modules/shipping_carriers/data/validators.js.map +2 -2
  122. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js +28 -8
  123. package/dist/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.js.map +2 -2
  124. package/dist/modules/shipping_carriers/lib/shipping-service.js +37 -7
  125. package/dist/modules/shipping_carriers/lib/shipping-service.js.map +2 -2
  126. package/dist/modules/shipping_carriers/workers/status-poller.js +1 -1
  127. package/dist/modules/shipping_carriers/workers/status-poller.js.map +2 -2
  128. package/dist/modules/translations/components/TranslationManager.js +49 -20
  129. package/dist/modules/translations/components/TranslationManager.js.map +2 -2
  130. package/package.json +7 -7
  131. package/src/modules/currencies/backend/currencies/[id]/page.tsx +40 -10
  132. package/src/modules/currencies/backend/currencies/page.tsx +68 -29
  133. package/src/modules/currencies/backend/exchange-rates/page.tsx +40 -15
  134. package/src/modules/currencies/components/CurrencyFetchingConfig.tsx +110 -41
  135. package/src/modules/customer_accounts/api/admin/roles/[id]/acl.ts +69 -7
  136. package/src/modules/customer_accounts/backend/customer_accounts/roles/[id]/page.tsx +16 -8
  137. package/src/modules/customer_accounts/backend/customer_accounts/roles/page.tsx +32 -14
  138. package/src/modules/customer_accounts/widgets/injection/account-status/widget.client.tsx +32 -20
  139. package/src/modules/customers/backend/config/customers/pipeline-stages/page.tsx +171 -64
  140. package/src/modules/customers/components/AddressFormatSettings.tsx +39 -19
  141. package/src/modules/customers/components/DictionarySettings.tsx +63 -29
  142. package/src/modules/customers/components/PipelineSettings.tsx +165 -80
  143. package/src/modules/customers/components/calendar/AgendaList.tsx +13 -8
  144. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +16 -2
  145. package/src/modules/customers/components/calendar/MonthGrid.tsx +5 -2
  146. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +6 -0
  147. package/src/modules/customers/components/calendar/editor/RelatedToField.tsx +2 -0
  148. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +7 -0
  149. package/src/modules/customers/i18n/de.json +11 -7
  150. package/src/modules/customers/i18n/en.json +6 -2
  151. package/src/modules/customers/i18n/es.json +11 -7
  152. package/src/modules/customers/i18n/pl.json +11 -7
  153. package/src/modules/customers/lib/calendar/labels.ts +42 -0
  154. package/src/modules/dashboards/components/WidgetVisibilityEditor.tsx +39 -11
  155. package/src/modules/data_sync/api/mappings/[id]/route.ts +63 -0
  156. package/src/modules/data_sync/api/mappings/route.ts +48 -0
  157. package/src/modules/data_sync/api/run.ts +33 -0
  158. package/src/modules/data_sync/api/runs/[id]/cancel.ts +34 -0
  159. package/src/modules/data_sync/api/runs/[id]/retry.ts +33 -0
  160. package/src/modules/data_sync/api/schedules/[id]/route.ts +64 -2
  161. package/src/modules/data_sync/api/schedules/route.ts +36 -4
  162. package/src/modules/directory/api/organization-branding/route.ts +61 -0
  163. package/src/modules/directory/backend/directory/branding/page.tsx +16 -10
  164. package/src/modules/directory/backend/directory/organizations/page.tsx +35 -8
  165. package/src/modules/directory/backend/directory/tenants/page.tsx +37 -13
  166. package/src/modules/entities/api/definitions.batch.ts +17 -0
  167. package/src/modules/entities/api/definitions.mutation-guard.ts +80 -0
  168. package/src/modules/entities/api/definitions.restore.ts +17 -0
  169. package/src/modules/entities/api/definitions.ts +30 -0
  170. package/src/modules/entities/api/entities.ts +35 -3
  171. package/src/modules/entities/api/records.ts +20 -0
  172. package/src/modules/entities/backend/entities/user/[entityId]/records/page.tsx +33 -10
  173. package/src/modules/feature_toggles/api/overrides/route.ts +44 -1
  174. package/src/modules/inbox_ops/api/emails/[id]/route.ts +32 -0
  175. package/src/modules/inbox_ops/api/proposals/[id]/accept-all/route.ts +32 -0
  176. package/src/modules/inbox_ops/api/proposals/[id]/actions/[actionId]/route.ts +33 -0
  177. package/src/modules/inbox_ops/api/proposals/[id]/categorize/route.ts +33 -0
  178. package/src/modules/messages/components/message-detail/hooks/useMessageDetailsActions.ts +80 -42
  179. package/src/modules/notifications/frontend/NotificationSettingsPageClient.tsx +21 -4
  180. package/src/modules/payment_gateways/api/cancel/route.ts +39 -0
  181. package/src/modules/payment_gateways/api/capture/route.ts +39 -0
  182. package/src/modules/payment_gateways/api/guards.ts +59 -0
  183. package/src/modules/payment_gateways/api/refund/route.ts +39 -0
  184. package/src/modules/payment_gateways/api/sessions/route.ts +40 -0
  185. package/src/modules/planner/api/availability-date-specific.ts +10 -0
  186. package/src/modules/planner/backend/planner/availability-rulesets/page.tsx +26 -5
  187. package/src/modules/planner/commands/availability-date-specific.ts +24 -0
  188. package/src/modules/planner/components/AvailabilityRulesEditor.tsx +122 -41
  189. package/src/modules/query_index/api/purge.ts +37 -3
  190. package/src/modules/query_index/api/reindex.ts +43 -3
  191. package/src/modules/query_index/components/QueryIndexesTable.tsx +66 -24
  192. package/src/modules/shipping_carriers/api/shipments/route.ts +31 -0
  193. package/src/modules/shipping_carriers/api/tracking/refresh/route.ts +53 -0
  194. package/src/modules/shipping_carriers/data/validators.ts +5 -0
  195. package/src/modules/shipping_carriers/lib/shipment-wizard/hooks/useShipmentWizard.ts +29 -8
  196. package/src/modules/shipping_carriers/lib/shipping-service.ts +43 -7
  197. package/src/modules/shipping_carriers/workers/status-poller.ts +1 -1
  198. package/src/modules/translations/components/TranslationManager.tsx +65 -21
@@ -151,11 +151,11 @@
151
151
  "customers.audit.entityRoles.create": "Create entity role",
152
152
  "customers.audit.entityRoles.delete": "Delete entity role",
153
153
  "customers.audit.entityRoles.update": "Update entity role",
154
- "customers.audit.interactions.cancel": "Cancelar interaccion",
155
- "customers.audit.interactions.complete": "Completar interaccion",
156
- "customers.audit.interactions.create": "Crear interaccion",
157
- "customers.audit.interactions.delete": "Eliminar interaccion",
158
- "customers.audit.interactions.update": "Actualizar interaccion",
154
+ "customers.audit.interactions.cancel": "Cancelar interacción",
155
+ "customers.audit.interactions.complete": "Completar interacción",
156
+ "customers.audit.interactions.create": "Crear interacción",
157
+ "customers.audit.interactions.delete": "Eliminar interacción",
158
+ "customers.audit.interactions.update": "Actualizar interacción",
159
159
  "customers.audit.labels.assign": "Asignar etiqueta",
160
160
  "customers.audit.labels.create": "Crear etiqueta",
161
161
  "customers.audit.labels.unassign": "Quitar etiqueta",
@@ -174,8 +174,10 @@
174
174
  "customers.audit.todos.link": "Vincular tarea",
175
175
  "customers.audit.todos.unlink": "Desvincular tarea",
176
176
  "customers.calendar.actions.newEvent": "Nuevo evento",
177
- "customers.calendar.agenda.eventCount": "1 evento",
178
- "customers.calendar.agenda.eventsCount": "{count} eventos",
177
+ "customers.calendar.agenda.eventsCount.few": "{count} eventos",
178
+ "customers.calendar.agenda.eventsCount.many": "{count} eventos",
179
+ "customers.calendar.agenda.eventsCount.one": "{count} evento",
180
+ "customers.calendar.agenda.eventsCount.other": "{count} eventos",
179
181
  "customers.calendar.agenda.today": "Hoy",
180
182
  "customers.calendar.agenda.tomorrow": "Mañana",
181
183
  "customers.calendar.cards.allDay": "Todo el día",
@@ -230,6 +232,7 @@
230
232
  "customers.calendar.editor.error": "No se pudo guardar el evento",
231
233
  "customers.calendar.editor.location": "Ubicación",
232
234
  "customers.calendar.editor.locationPlaceholder": "Añadir una ubicación o enlace…",
235
+ "customers.calendar.editor.multiDayHint": "Evento de varios días · {count} días",
233
236
  "customers.calendar.editor.noResults": "Sin resultados",
234
237
  "customers.calendar.editor.participants": "Participantes",
235
238
  "customers.calendar.editor.phoneLink": "Teléfono / enlace",
@@ -334,6 +337,7 @@
334
337
  "customers.calendar.settings.eventCategories": "Categorías de eventos",
335
338
  "customers.calendar.settings.eventCategoriesHint": "Tus propias etiquetas de agrupación (p. ej., Reunión de equipo, Llamada de ventas). Se ofrecen al crear un evento.",
336
339
  "customers.calendar.settings.max": "(máx. {count})",
340
+ "customers.calendar.settings.removeTag": "Eliminar {tag}",
337
341
  "customers.calendar.settings.save": "Guardar cambios",
338
342
  "customers.calendar.settings.saved": "Configuración del calendario guardada",
339
343
  "customers.calendar.settings.showCrmActivities": "Mostrar actividades de CRM en el calendario",
@@ -151,11 +151,11 @@
151
151
  "customers.audit.entityRoles.create": "Create entity role",
152
152
  "customers.audit.entityRoles.delete": "Delete entity role",
153
153
  "customers.audit.entityRoles.update": "Update entity role",
154
- "customers.audit.interactions.cancel": "Anuluj interakcje",
155
- "customers.audit.interactions.complete": "Zakoncz interakcje",
156
- "customers.audit.interactions.create": "Utworz interakcje",
157
- "customers.audit.interactions.delete": "Usun interakcje",
158
- "customers.audit.interactions.update": "Zaktualizuj interakcje",
154
+ "customers.audit.interactions.cancel": "Anuluj interakcję",
155
+ "customers.audit.interactions.complete": "Zakończ interakcję",
156
+ "customers.audit.interactions.create": "Utwórz interakcję",
157
+ "customers.audit.interactions.delete": "Usuń interakcję",
158
+ "customers.audit.interactions.update": "Zaktualizuj interakcję",
159
159
  "customers.audit.labels.assign": "Przypisz etykietę",
160
160
  "customers.audit.labels.create": "Utwórz etykietę",
161
161
  "customers.audit.labels.unassign": "Usuń przypisanie etykiety",
@@ -174,8 +174,10 @@
174
174
  "customers.audit.todos.link": "Powiąż zadanie",
175
175
  "customers.audit.todos.unlink": "Odepnij zadanie",
176
176
  "customers.calendar.actions.newEvent": "Nowe wydarzenie",
177
- "customers.calendar.agenda.eventCount": "1 wydarzenie",
178
- "customers.calendar.agenda.eventsCount": "{count} wydarzeń",
177
+ "customers.calendar.agenda.eventsCount.few": "{count} wydarzenia",
178
+ "customers.calendar.agenda.eventsCount.many": "{count} wydarzeń",
179
+ "customers.calendar.agenda.eventsCount.one": "{count} wydarzenie",
180
+ "customers.calendar.agenda.eventsCount.other": "{count} wydarzeń",
179
181
  "customers.calendar.agenda.today": "Dzisiaj",
180
182
  "customers.calendar.agenda.tomorrow": "Jutro",
181
183
  "customers.calendar.cards.allDay": "Cały dzień",
@@ -230,6 +232,7 @@
230
232
  "customers.calendar.editor.error": "Nie udało się zapisać wydarzenia",
231
233
  "customers.calendar.editor.location": "Lokalizacja",
232
234
  "customers.calendar.editor.locationPlaceholder": "Dodaj lokalizację lub link…",
235
+ "customers.calendar.editor.multiDayHint": "Wydarzenie wielodniowe · {count} dni",
233
236
  "customers.calendar.editor.noResults": "Brak wyników",
234
237
  "customers.calendar.editor.participants": "Uczestnicy",
235
238
  "customers.calendar.editor.phoneLink": "Telefon / link",
@@ -334,6 +337,7 @@
334
337
  "customers.calendar.settings.eventCategories": "Kategorie wydarzeń",
335
338
  "customers.calendar.settings.eventCategoriesHint": "Twoje własne etykiety grupujące (np. Spotkanie zespołu, Telefon sprzedażowy). Pojawiają się podczas tworzenia wydarzenia.",
336
339
  "customers.calendar.settings.max": "(maks. {count})",
340
+ "customers.calendar.settings.removeTag": "Usuń {tag}",
337
341
  "customers.calendar.settings.save": "Zapisz zmiany",
338
342
  "customers.calendar.settings.saved": "Zapisano ustawienia kalendarza",
339
343
  "customers.calendar.settings.showCrmActivities": "Pokaż aktywności CRM w kalendarzu",
@@ -0,0 +1,42 @@
1
+ const MS_PER_DAY = 24 * 60 * 60 * 1000
2
+
3
+ export function pluralCategory(locale: string, count: number): Intl.LDMLPluralRule {
4
+ try {
5
+ return new Intl.PluralRules(locale).select(count)
6
+ } catch {
7
+ return count === 1 ? 'one' : 'other'
8
+ }
9
+ }
10
+
11
+ export function eventDisplayTitle(title: string | null | undefined, fallback: string): string {
12
+ return title && title.trim().length > 0 ? title : fallback
13
+ }
14
+
15
+ export function composeAccessibleName(
16
+ parts: Array<string | null | undefined>,
17
+ separator = ', ',
18
+ ): string {
19
+ return parts
20
+ .map((part) => (typeof part === 'string' ? part.trim() : ''))
21
+ .filter((part) => part.length > 0)
22
+ .join(separator)
23
+ }
24
+
25
+ function parseDateInput(value: string): number {
26
+ const parsed = new Date(`${value}T00:00:00`)
27
+ return parsed.getTime()
28
+ }
29
+
30
+ /**
31
+ * Number of calendar days a scheduled event spans when its end date falls on a
32
+ * later day than its start date. Returns 0 for same-day, end-before-start, or
33
+ * unparseable inputs, so callers can treat 0 as "not multi-day".
34
+ */
35
+ export function multiDayEventSpan(startDate: string, endDate: string): number {
36
+ const start = parseDateInput(startDate)
37
+ const end = parseDateInput(endDate)
38
+ if (Number.isNaN(start) || Number.isNaN(end)) return 0
39
+ const dayDiff = Math.round((end - start) / MS_PER_DAY)
40
+ if (dayDiff < 1) return 0
41
+ return dayDiff + 1
42
+ }
@@ -6,9 +6,12 @@ import { Spinner } from '@open-mercato/ui/primitives/spinner'
6
6
  import { RadioGroup } from '@open-mercato/ui/primitives/radio'
7
7
  import { RadioField } from '@open-mercato/ui/primitives/radio-field'
8
8
  import { apiCallOrThrow, readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
9
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
9
10
  import { flash } from '@open-mercato/ui/backend/FlashMessages'
10
11
  import { useT } from '@open-mercato/shared/lib/i18n/context'
11
12
 
13
+ const WIDGET_VISIBILITY_CONTEXT_ID = 'dashboards-widget-visibility:save'
14
+
12
15
  type WidgetCatalogItem = {
13
16
  id: string
14
17
  title: string
@@ -94,6 +97,13 @@ export const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHan
94
97
  [t],
95
98
  )
96
99
  const { kind, targetId, tenantId, organizationId, preserveOnTenantChange = false } = props
100
+ const { runMutation, retryLastMutation } = useGuardedMutation<{
101
+ formId: string
102
+ resourceKind: string
103
+ retryLastMutation: () => Promise<boolean>
104
+ }>({
105
+ contextId: WIDGET_VISIBILITY_CONTEXT_ID,
106
+ })
97
107
  const [catalog, setCatalog] = React.useState<WidgetCatalogItem[]>([])
98
108
  const [loading, setLoading] = React.useState(true)
99
109
  const [saving, setSaving] = React.useState(false)
@@ -243,11 +253,20 @@ export const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHan
243
253
  widgetIds: selected,
244
254
  }
245
255
  // optimistic-lock-exempt: per-role widget visibility preference
246
- await apiCallOrThrow('/api/dashboards/roles/widgets', {
247
- method: 'PUT',
248
- headers: { 'content-type': 'application/json' },
249
- body: JSON.stringify(payload),
250
- }, { errorMessage: saveError })
256
+ await runMutation({
257
+ operation: () =>
258
+ apiCallOrThrow('/api/dashboards/roles/widgets', {
259
+ method: 'PUT',
260
+ headers: { 'content-type': 'application/json' },
261
+ body: JSON.stringify(payload),
262
+ }, { errorMessage: saveError }),
263
+ context: {
264
+ formId: WIDGET_VISIBILITY_CONTEXT_ID,
265
+ resourceKind: 'dashboards.role_widget_visibility',
266
+ retryLastMutation,
267
+ },
268
+ mutationPayload: payload,
269
+ })
251
270
  setOriginal(selected)
252
271
  setOriginalMode('override')
253
272
  setEffective(selected)
@@ -260,11 +279,20 @@ export const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHan
260
279
  widgetIds: selected,
261
280
  }
262
281
  // optimistic-lock-exempt: per-user widget visibility preference
263
- await apiCallOrThrow('/api/dashboards/users/widgets', {
264
- method: 'PUT',
265
- headers: { 'content-type': 'application/json' },
266
- body: JSON.stringify(payload),
267
- }, { errorMessage: saveError })
282
+ await runMutation({
283
+ operation: () =>
284
+ apiCallOrThrow('/api/dashboards/users/widgets', {
285
+ method: 'PUT',
286
+ headers: { 'content-type': 'application/json' },
287
+ body: JSON.stringify(payload),
288
+ }, { errorMessage: saveError }),
289
+ context: {
290
+ formId: WIDGET_VISIBILITY_CONTEXT_ID,
291
+ resourceKind: 'dashboards.user_widget_visibility',
292
+ retryLastMutation,
293
+ },
294
+ mutationPayload: payload,
295
+ })
268
296
  setOriginal(selected)
269
297
  if (mode === 'inherit') {
270
298
  const refreshed = await readApiResultOrThrow<UserResponse>(
@@ -286,7 +314,7 @@ export const WidgetVisibilityEditor = React.forwardRef<WidgetVisibilityEditorHan
286
314
  } finally {
287
315
  setSaving(false)
288
316
  }
289
- }, [catalog.length, dirty, error, kind, loading, mode, organizationId, selected, t, targetId, tenantId])
317
+ }, [catalog.length, dirty, error, kind, loading, mode, organizationId, retryLastMutation, runMutation, selected, t, targetId, tenantId])
290
318
 
291
319
  React.useImperativeHandle(ref, () => ({ save }), [save])
292
320
 
@@ -5,6 +5,10 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import type { EntityManager } from '@mikro-orm/postgresql'
6
6
  import { findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
7
  import { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'
8
+ import {
9
+ runCrudMutationGuardAfterSuccess,
10
+ validateCrudMutationGuard,
11
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
8
12
 
9
13
  const idParamsSchema = z.object({ id: z.string().uuid() })
10
14
 
@@ -108,9 +112,38 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
108
112
  return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })
109
113
  }
110
114
 
115
+ const guardResult = await validateCrudMutationGuard(container, {
116
+ tenantId: scope.tenantId,
117
+ organizationId: scope.organizationId,
118
+ userId: auth.sub,
119
+ resourceKind: 'data_sync.mapping',
120
+ resourceId: mapping.id,
121
+ operation: 'update',
122
+ requestMethod: req.method,
123
+ requestHeaders: req.headers,
124
+ mutationPayload: parsedBody.data,
125
+ })
126
+ if (guardResult && !guardResult.ok) {
127
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
128
+ }
129
+
111
130
  mapping.mapping = parsedBody.data.mapping
112
131
  await em.flush()
113
132
 
133
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
134
+ await runCrudMutationGuardAfterSuccess(container, {
135
+ tenantId: scope.tenantId,
136
+ organizationId: scope.organizationId,
137
+ userId: auth.sub,
138
+ resourceKind: 'data_sync.mapping',
139
+ resourceId: mapping.id,
140
+ operation: 'update',
141
+ requestMethod: req.method,
142
+ requestHeaders: req.headers,
143
+ metadata: guardResult.metadata ?? null,
144
+ })
145
+ }
146
+
114
147
  return NextResponse.json({
115
148
  id: mapping.id,
116
149
  integrationId: mapping.integrationId,
@@ -151,8 +184,38 @@ export async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string
151
184
  return NextResponse.json({ error: 'Mapping not found' }, { status: 404 })
152
185
  }
153
186
 
187
+ const guardResult = await validateCrudMutationGuard(container, {
188
+ tenantId: scope.tenantId,
189
+ organizationId: scope.organizationId,
190
+ userId: auth.sub,
191
+ resourceKind: 'data_sync.mapping',
192
+ resourceId: mapping.id,
193
+ operation: 'delete',
194
+ requestMethod: req.method,
195
+ requestHeaders: req.headers,
196
+ mutationPayload: null,
197
+ })
198
+ if (guardResult && !guardResult.ok) {
199
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
200
+ }
201
+
202
+ const mappingId = mapping.id
154
203
  em.remove(mapping)
155
204
  await em.flush()
156
205
 
206
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
207
+ await runCrudMutationGuardAfterSuccess(container, {
208
+ tenantId: scope.tenantId,
209
+ organizationId: scope.organizationId,
210
+ userId: auth.sub,
211
+ resourceKind: 'data_sync.mapping',
212
+ resourceId: mappingId,
213
+ operation: 'delete',
214
+ requestMethod: req.method,
215
+ requestHeaders: req.headers,
216
+ metadata: guardResult.metadata ?? null,
217
+ })
218
+ }
219
+
157
220
  return NextResponse.json({ deleted: true })
158
221
  }
@@ -5,6 +5,10 @@ import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
5
5
  import type { EntityManager, FilterQuery } from '@mikro-orm/postgresql'
6
6
  import { findAndCountWithDecryption, findOneWithDecryption } from '@open-mercato/shared/lib/encryption/find'
7
7
  import { SyncMapping } from '@open-mercato/core/modules/data_sync/data/entities'
8
+ import {
9
+ runCrudMutationGuardAfterSuccess,
10
+ validateCrudMutationGuard,
11
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
8
12
 
9
13
  const listMappingsQuerySchema = z.object({
10
14
  integrationId: z.string().min(1).optional(),
@@ -115,9 +119,39 @@ export async function POST(req: Request) {
115
119
  scope,
116
120
  )
117
121
 
122
+ const guardResult = await validateCrudMutationGuard(container, {
123
+ tenantId: scope.tenantId,
124
+ organizationId: scope.organizationId,
125
+ userId: auth.sub,
126
+ resourceKind: 'data_sync.mapping',
127
+ resourceId: existing?.id ?? scope.organizationId,
128
+ operation: existing ? 'update' : 'create',
129
+ requestMethod: req.method,
130
+ requestHeaders: req.headers,
131
+ mutationPayload: parsed.data,
132
+ })
133
+ if (guardResult && !guardResult.ok) {
134
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
135
+ }
136
+
118
137
  if (existing) {
119
138
  existing.mapping = parsed.data.mapping
120
139
  await em.flush()
140
+
141
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
142
+ await runCrudMutationGuardAfterSuccess(container, {
143
+ tenantId: scope.tenantId,
144
+ organizationId: scope.organizationId,
145
+ userId: auth.sub,
146
+ resourceKind: 'data_sync.mapping',
147
+ resourceId: existing.id,
148
+ operation: 'update',
149
+ requestMethod: req.method,
150
+ requestHeaders: req.headers,
151
+ metadata: guardResult.metadata ?? null,
152
+ })
153
+ }
154
+
121
155
  return NextResponse.json({
122
156
  id: existing.id,
123
157
  integrationId: existing.integrationId,
@@ -135,6 +169,20 @@ export async function POST(req: Request) {
135
169
  })
136
170
  await em.persist(created).flush()
137
171
 
172
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
173
+ await runCrudMutationGuardAfterSuccess(container, {
174
+ tenantId: scope.tenantId,
175
+ organizationId: scope.organizationId,
176
+ userId: auth.sub,
177
+ resourceKind: 'data_sync.mapping',
178
+ resourceId: created.id,
179
+ operation: 'create',
180
+ requestMethod: req.method,
181
+ requestHeaders: req.headers,
182
+ metadata: guardResult.metadata ?? null,
183
+ })
184
+ }
185
+
138
186
  return NextResponse.json({
139
187
  id: created.id,
140
188
  integrationId: created.integrationId,
@@ -9,6 +9,10 @@ import type { SyncRunService } from '../lib/sync-run-service'
9
9
  import { runSyncSchema } from '../data/validators'
10
10
  import { startDataSyncRun } from '../lib/start-run'
11
11
  import { getDataSyncAdapter } from '../lib/adapter-registry'
12
+ import {
13
+ runCrudMutationGuardAfterSuccess,
14
+ validateCrudMutationGuard,
15
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
12
16
 
13
17
  export const metadata = {
14
18
  POST: { requireAuth: true, requireFeatures: ['data_sync.run'] },
@@ -80,6 +84,21 @@ export async function POST(req: Request) {
80
84
  return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })
81
85
  }
82
86
 
87
+ const guardResult = await validateCrudMutationGuard(container, {
88
+ tenantId: auth.tenantId,
89
+ organizationId: scope.organizationId,
90
+ userId: auth.sub,
91
+ resourceKind: 'data_sync.run',
92
+ resourceId: parsed.data.integrationId,
93
+ operation: 'custom',
94
+ requestMethod: req.method,
95
+ requestHeaders: req.headers,
96
+ mutationPayload: parsed.data,
97
+ })
98
+ if (guardResult && !guardResult.ok) {
99
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
100
+ }
101
+
83
102
  const cursor = parsed.data.fullSync
84
103
  ? null
85
104
  : await syncRunService.resolveCursor(parsed.data.integrationId, parsed.data.entityType, parsed.data.direction, scope)
@@ -101,6 +120,20 @@ export async function POST(req: Request) {
101
120
  },
102
121
  })
103
122
 
123
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
124
+ await runCrudMutationGuardAfterSuccess(container, {
125
+ tenantId: auth.tenantId,
126
+ organizationId: scope.organizationId,
127
+ userId: auth.sub,
128
+ resourceKind: 'data_sync.run',
129
+ resourceId: run.id,
130
+ operation: 'custom',
131
+ requestMethod: req.method,
132
+ requestHeaders: req.headers,
133
+ metadata: guardResult.metadata ?? null,
134
+ })
135
+ }
136
+
104
137
  return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })
105
138
  } catch (error) {
106
139
  const message = error instanceof Error ? error.message : String(error)
@@ -6,6 +6,10 @@ import type { IntegrationLogService } from '../../../../integrations/lib/log-ser
6
6
  import type { IntegrationStateService } from '../../../../integrations/lib/state-service'
7
7
  import type { ProgressService } from '../../../../progress/lib/progressService'
8
8
  import type { SyncRunService } from '../../../lib/sync-run-service'
9
+ import {
10
+ runCrudMutationGuardAfterSuccess,
11
+ validateCrudMutationGuard,
12
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
9
13
 
10
14
  const paramsSchema = z.object({ id: z.string().uuid() })
11
15
 
@@ -48,6 +52,21 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
48
52
  return NextResponse.json({ error: 'Only pending or running runs can be cancelled' }, { status: 409 })
49
53
  }
50
54
 
55
+ const guardResult = await validateCrudMutationGuard(container, {
56
+ tenantId: auth.tenantId,
57
+ organizationId: scope.organizationId,
58
+ userId: auth.sub,
59
+ resourceKind: 'data_sync.run',
60
+ resourceId: run.id,
61
+ operation: 'custom',
62
+ requestMethod: req.method,
63
+ requestHeaders: req.headers,
64
+ mutationPayload: { action: 'cancel' },
65
+ })
66
+ if (guardResult && !guardResult.ok) {
67
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
68
+ }
69
+
51
70
  const progressCtx = {
52
71
  tenantId: auth.tenantId,
53
72
  organizationId: auth.orgId,
@@ -84,5 +103,20 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
84
103
  summary: 'The sync run was cancelled before completion.',
85
104
  },
86
105
  }, scope)
106
+
107
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
108
+ await runCrudMutationGuardAfterSuccess(container, {
109
+ tenantId: auth.tenantId,
110
+ organizationId: scope.organizationId,
111
+ userId: auth.sub,
112
+ resourceKind: 'data_sync.run',
113
+ resourceId: run.id,
114
+ operation: 'custom',
115
+ requestMethod: req.method,
116
+ requestHeaders: req.headers,
117
+ metadata: guardResult.metadata ?? null,
118
+ })
119
+ }
120
+
87
121
  return NextResponse.json({ ok: true })
88
122
  }
@@ -7,6 +7,10 @@ import type { ProgressService } from '../../../../progress/lib/progressService'
7
7
  import type { SyncRunService } from '../../../lib/sync-run-service'
8
8
  import { retrySyncSchema } from '../../../data/validators'
9
9
  import { startDataSyncRun } from '../../../lib/start-run'
10
+ import {
11
+ runCrudMutationGuardAfterSuccess,
12
+ validateCrudMutationGuard,
13
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
10
14
 
11
15
  const paramsSchema = z.object({ id: z.string().uuid() })
12
16
 
@@ -63,6 +67,21 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
63
67
  return NextResponse.json({ error: 'A sync run is already in progress for this integration and entity direction' }, { status: 409 })
64
68
  }
65
69
 
70
+ const guardResult = await validateCrudMutationGuard(container, {
71
+ tenantId: auth.tenantId,
72
+ organizationId: scope.organizationId,
73
+ userId: auth.sub,
74
+ resourceKind: 'data_sync.run',
75
+ resourceId: previous.id,
76
+ operation: 'custom',
77
+ requestMethod: req.method,
78
+ requestHeaders: req.headers,
79
+ mutationPayload: { action: 'retry', ...parsedBody.data },
80
+ })
81
+ if (guardResult && !guardResult.ok) {
82
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
83
+ }
84
+
66
85
  const cursor = parsedBody.data.fromBeginning
67
86
  ? null
68
87
  : previous.cursor ?? await syncRunService.resolveCursor(previous.integrationId, previous.entityType, previous.direction, scope)
@@ -87,5 +106,19 @@ export async function POST(req: Request, ctx: { params?: Promise<{ id?: string }
87
106
  },
88
107
  })
89
108
 
109
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
110
+ await runCrudMutationGuardAfterSuccess(container, {
111
+ tenantId: auth.tenantId,
112
+ organizationId: scope.organizationId,
113
+ userId: auth.sub,
114
+ resourceKind: 'data_sync.run',
115
+ resourceId: run.id,
116
+ operation: 'custom',
117
+ requestMethod: req.method,
118
+ requestHeaders: req.headers,
119
+ metadata: guardResult.metadata ?? null,
120
+ })
121
+ }
122
+
90
123
  return NextResponse.json({ id: run.id, progressJobId: progressJob?.id ?? null }, { status: 201 })
91
124
  }
@@ -6,6 +6,10 @@ import { readJsonSafe } from '@open-mercato/shared/lib/http/readJsonSafe'
6
6
  import { updateSyncScheduleSchema } from '../../../data/validators'
7
7
  import type { SyncScheduleService } from '../../../lib/sync-schedule-service'
8
8
  import { serializeSchedule } from '../serialize'
9
+ import {
10
+ runCrudMutationGuardAfterSuccess,
11
+ validateCrudMutationGuard,
12
+ } from '@open-mercato/shared/lib/crud/mutation-guard'
9
13
 
10
14
  const paramsSchema = z.object({
11
15
  id: z.string().uuid(),
@@ -81,6 +85,21 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
81
85
  return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })
82
86
  }
83
87
 
88
+ const guardResult = await validateCrudMutationGuard(container, {
89
+ tenantId: auth.tenantId,
90
+ organizationId: scope.organizationId,
91
+ userId: auth.sub,
92
+ resourceKind: 'data_sync.schedule',
93
+ resourceId: current.id,
94
+ operation: 'update',
95
+ requestMethod: req.method,
96
+ requestHeaders: req.headers,
97
+ mutationPayload: parsed.data,
98
+ })
99
+ if (guardResult && !guardResult.ok) {
100
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
101
+ }
102
+
84
103
  try {
85
104
  const schedule = await scheduleService.saveSchedule({
86
105
  id: current.id,
@@ -93,6 +112,21 @@ export async function PUT(req: Request, ctx: { params?: Promise<{ id?: string }>
93
112
  fullSync: parsed.data.fullSync ?? current.fullSync,
94
113
  isEnabled: parsed.data.isEnabled ?? current.isEnabled,
95
114
  }, scope)
115
+
116
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
117
+ await runCrudMutationGuardAfterSuccess(container, {
118
+ tenantId: auth.tenantId,
119
+ organizationId: scope.organizationId,
120
+ userId: auth.sub,
121
+ resourceKind: 'data_sync.schedule',
122
+ resourceId: schedule.id,
123
+ operation: 'update',
124
+ requestMethod: req.method,
125
+ requestHeaders: req.headers,
126
+ metadata: guardResult.metadata ?? null,
127
+ })
128
+ }
129
+
96
130
  return NextResponse.json(serializeSchedule(schedule))
97
131
  } catch (error) {
98
132
  const message = error instanceof Error ? error.message : 'Failed to update sync schedule'
@@ -117,14 +151,42 @@ export async function DELETE(req: Request, ctx: { params?: Promise<{ id?: string
117
151
 
118
152
  const container = await createRequestContainer()
119
153
  const scheduleService = container.resolve('dataSyncScheduleService') as SyncScheduleService
120
- const deleted = await scheduleService.deleteSchedule(parsedParams.data.id, {
121
- organizationId: auth.orgId as string,
154
+ const scope = { organizationId: auth.orgId as string, tenantId: auth.tenantId }
155
+
156
+ const guardResult = await validateCrudMutationGuard(container, {
122
157
  tenantId: auth.tenantId,
158
+ organizationId: scope.organizationId,
159
+ userId: auth.sub,
160
+ resourceKind: 'data_sync.schedule',
161
+ resourceId: parsedParams.data.id,
162
+ operation: 'delete',
163
+ requestMethod: req.method,
164
+ requestHeaders: req.headers,
165
+ mutationPayload: null,
123
166
  })
167
+ if (guardResult && !guardResult.ok) {
168
+ return NextResponse.json(guardResult.body, { status: guardResult.status })
169
+ }
170
+
171
+ const deleted = await scheduleService.deleteSchedule(parsedParams.data.id, scope)
124
172
 
125
173
  if (!deleted) {
126
174
  return NextResponse.json({ error: 'Schedule not found' }, { status: 404 })
127
175
  }
128
176
 
177
+ if (guardResult?.ok && guardResult.shouldRunAfterSuccess) {
178
+ await runCrudMutationGuardAfterSuccess(container, {
179
+ tenantId: auth.tenantId,
180
+ organizationId: scope.organizationId,
181
+ userId: auth.sub,
182
+ resourceKind: 'data_sync.schedule',
183
+ resourceId: parsedParams.data.id,
184
+ operation: 'delete',
185
+ requestMethod: req.method,
186
+ requestHeaders: req.headers,
187
+ metadata: guardResult.metadata ?? null,
188
+ })
189
+ }
190
+
129
191
  return NextResponse.json({ deleted: true })
130
192
  }