@open-mercato/core 0.9.0 → 0.9.1-develop.7459.1.99e3accd30

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 (243) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
  3. package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
  4. package/dist/generated/entities/customer_interaction/index.js +2 -0
  5. package/dist/generated/entities/customer_interaction/index.js.map +2 -2
  6. package/dist/generated/entity-fields-registry.js +2 -0
  7. package/dist/generated/entity-fields-registry.js.map +2 -2
  8. package/dist/modules/catalog/commands/categories.js +1 -1
  9. package/dist/modules/catalog/commands/categories.js.map +2 -2
  10. package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
  11. package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
  12. package/dist/modules/catalog/commands/products.js +52 -17
  13. package/dist/modules/catalog/commands/products.js.map +2 -2
  14. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
  15. package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
  16. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
  17. package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
  18. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
  19. package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
  20. package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
  21. package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
  22. package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
  23. package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
  24. package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
  25. package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
  26. package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
  27. package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
  28. package/dist/modules/customers/api/activity-types/route.js +151 -0
  29. package/dist/modules/customers/api/activity-types/route.js.map +7 -0
  30. package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
  31. package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
  32. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
  33. package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
  34. package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
  35. package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
  36. package/dist/modules/customers/api/interactions/route.js +3 -0
  37. package/dist/modules/customers/api/interactions/route.js.map +2 -2
  38. package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
  39. package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
  40. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
  41. package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
  42. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
  43. package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
  44. package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
  45. package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
  46. package/dist/modules/customers/calendar-event-types.js +548 -0
  47. package/dist/modules/customers/calendar-event-types.js.map +7 -0
  48. package/dist/modules/customers/commands/dictionaries.js +70 -10
  49. package/dist/modules/customers/commands/dictionaries.js.map +2 -2
  50. package/dist/modules/customers/commands/interactions.js +187 -22
  51. package/dist/modules/customers/commands/interactions.js.map +3 -3
  52. package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
  53. package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
  54. package/dist/modules/customers/components/DictionarySettings.js +3 -1
  55. package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
  56. package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
  57. package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
  58. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
  59. package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
  60. package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
  61. package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
  62. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
  63. package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
  64. package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
  65. package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
  66. package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
  67. package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
  68. package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
  69. package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
  70. package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
  71. package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
  72. package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
  73. package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
  74. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
  75. package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
  76. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
  77. package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
  78. package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
  79. package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
  80. package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
  81. package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
  82. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
  83. package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
  84. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
  85. package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
  86. package/dist/modules/customers/components/calendar/types.js +1 -0
  87. package/dist/modules/customers/components/calendar/types.js.map +2 -2
  88. package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
  89. package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
  90. package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
  91. package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
  92. package/dist/modules/customers/components/detail/DealForm.js +7 -3
  93. package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
  94. package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
  95. package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
  96. package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
  97. package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
  98. package/dist/modules/customers/components/detail/PersonCard.js +5 -2
  99. package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
  100. package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
  101. package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
  102. package/dist/modules/customers/data/entities.js +6 -0
  103. package/dist/modules/customers/data/entities.js.map +2 -2
  104. package/dist/modules/customers/data/validators.js +39 -10
  105. package/dist/modules/customers/data/validators.js.map +2 -2
  106. package/dist/modules/customers/di.js +20 -1
  107. package/dist/modules/customers/di.js.map +2 -2
  108. package/dist/modules/customers/extension-points.js +13 -0
  109. package/dist/modules/customers/extension-points.js.map +2 -2
  110. package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
  111. package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
  112. package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
  113. package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
  114. package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
  115. package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
  116. package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
  117. package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
  118. package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
  119. package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
  120. package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
  121. package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
  122. package/dist/modules/customers/lib/calendar/timezone.js +111 -0
  123. package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
  124. package/dist/modules/customers/lib/interactionReadModel.js +1 -0
  125. package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
  126. package/dist/modules/customers/lib/todoCompatibility.js +1 -0
  127. package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
  128. package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
  129. package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
  130. package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
  131. package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
  132. package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
  133. package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
  134. package/dist/modules/messages/subscribers/message-notification.js +13 -1
  135. package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
  136. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
  137. package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
  138. package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
  139. package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
  140. package/dist/modules/planner/lib/availabilityMerge.js +101 -38
  141. package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
  142. package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
  143. package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
  144. package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
  145. package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
  146. package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
  147. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
  148. package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
  149. package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
  150. package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
  151. package/generated/entities/customer_dictionary_entry/index.ts +1 -0
  152. package/generated/entities/customer_interaction/index.ts +1 -0
  153. package/generated/entity-fields-registry.ts +2 -0
  154. package/package.json +9 -8
  155. package/src/modules/auth/i18n/de.json +3 -0
  156. package/src/modules/auth/i18n/en.json +3 -0
  157. package/src/modules/auth/i18n/es.json +3 -0
  158. package/src/modules/auth/i18n/ko.json +3 -0
  159. package/src/modules/auth/i18n/pl.json +3 -0
  160. package/src/modules/catalog/commands/categories.ts +1 -1
  161. package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
  162. package/src/modules/catalog/commands/products.ts +58 -17
  163. package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
  164. package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
  165. package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
  166. package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
  167. package/src/modules/communication_channels/i18n/de.json +4 -0
  168. package/src/modules/communication_channels/i18n/en.json +4 -0
  169. package/src/modules/communication_channels/i18n/es.json +4 -0
  170. package/src/modules/communication_channels/i18n/ko.json +4 -0
  171. package/src/modules/communication_channels/i18n/pl.json +4 -0
  172. package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
  173. package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
  174. package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
  175. package/src/modules/customers/api/activity-types/route.ts +154 -0
  176. package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
  177. package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
  178. package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
  179. package/src/modules/customers/api/interactions/route.ts +4 -0
  180. package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
  181. package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
  182. package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
  183. package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
  184. package/src/modules/customers/calendar-event-types.ts +674 -0
  185. package/src/modules/customers/commands/dictionaries.ts +84 -8
  186. package/src/modules/customers/commands/interactions.ts +244 -21
  187. package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
  188. package/src/modules/customers/components/DictionarySettings.tsx +5 -3
  189. package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
  190. package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
  191. package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
  192. package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
  193. package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
  194. package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
  195. package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
  196. package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
  197. package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
  198. package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
  199. package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
  200. package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
  201. package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
  202. package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
  203. package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
  204. package/src/modules/customers/components/calendar/types.ts +1 -0
  205. package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
  206. package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
  207. package/src/modules/customers/components/detail/DealForm.tsx +7 -3
  208. package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
  209. package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
  210. package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
  211. package/src/modules/customers/components/detail/types.ts +1 -0
  212. package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
  213. package/src/modules/customers/data/entities.ts +8 -1
  214. package/src/modules/customers/data/validators.ts +42 -7
  215. package/src/modules/customers/di.ts +21 -0
  216. package/src/modules/customers/extension-points.ts +13 -0
  217. package/src/modules/customers/i18n/de.json +104 -8
  218. package/src/modules/customers/i18n/en.json +104 -8
  219. package/src/modules/customers/i18n/es.json +104 -8
  220. package/src/modules/customers/i18n/ko.json +104 -8
  221. package/src/modules/customers/i18n/pl.json +104 -8
  222. package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
  223. package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
  224. package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
  225. package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
  226. package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
  227. package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
  228. package/src/modules/customers/lib/calendar/timezone.ts +117 -0
  229. package/src/modules/customers/lib/interactionReadModel.ts +1 -0
  230. package/src/modules/customers/lib/todoCompatibility.ts +2 -0
  231. package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
  232. package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
  233. package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
  234. package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
  235. package/src/modules/messages/subscribers/message-notification.ts +13 -3
  236. package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
  237. package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
  238. package/src/modules/planner/lib/availabilityMerge.ts +106 -41
  239. package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
  240. package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
  241. package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
  242. package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
  243. package/src/modules/sync_excel/lib/adapters/customers.ts +33 -27
@@ -193,6 +193,11 @@
193
193
  "customers.audit.todos.link": "Powiąż zadanie",
194
194
  "customers.audit.todos.unlink": "Odepnij zadanie",
195
195
  "customers.calendar.actions.newEvent": "Nowe wydarzenie",
196
+ "customers.calendar.activityTypes.errors.changeConfirmationRequired": "Zmiana typu wydarzenia usunie istniejące wartości.",
197
+ "customers.calendar.activityTypes.errors.fieldNotApplicable": "To pole nie dotyczy wybranego typu aktywności.",
198
+ "customers.calendar.activityTypes.errors.forbidden": "Nie masz uprawnień do wyświetlania typów aktywności.",
199
+ "customers.calendar.activityTypes.errors.loadFailed": "Nie udało się wczytać typów aktywności.",
200
+ "customers.calendar.activityTypes.errors.unavailable": "Ten typ aktywności jest niedostępny.",
196
201
  "customers.calendar.agenda.eventsCount.few": "{count} wydarzenia",
197
202
  "customers.calendar.agenda.eventsCount.many": "{count} wydarzeń",
198
203
  "customers.calendar.agenda.eventsCount.one": "{count} wydarzenie",
@@ -232,6 +237,8 @@
232
237
  "customers.calendar.editor.assigneePlaceholder": "Przypisz do członka zespołu…",
233
238
  "customers.calendar.editor.attendees": "Uczestnicy",
234
239
  "customers.calendar.editor.cancel": "Anuluj",
240
+ "customers.calendar.editor.catalogLoadFailed": "Nie udało się wczytać typów aktywności. Spróbuj ponownie, aby zapisać wydarzenie.",
241
+ "customers.calendar.editor.catalogLoading": "Wczytywanie typów aktywności…",
235
242
  "customers.calendar.editor.category": "Kategoria",
236
243
  "customers.calendar.editor.close": "Zamknij",
237
244
  "customers.calendar.editor.conflictTitle": "Konflikt w kalendarzu",
@@ -249,8 +256,14 @@
249
256
  "customers.calendar.editor.dealsSection": "Szanse",
250
257
  "customers.calendar.editor.description": "Opis",
251
258
  "customers.calendar.editor.descriptionPlaceholder": "Dodaj szczegóły…",
259
+ "customers.calendar.editor.discardConfirm": "Zmień typ i usuń wartości",
260
+ "customers.calendar.editor.discardDescription": "Zmiana typu wydarzenia usunie wartości, które nie dotyczą nowego typu.",
261
+ "customers.calendar.editor.discardFields": "Wartości do usunięcia:",
262
+ "customers.calendar.editor.discardTitle": "Zmienić typ wydarzenia?",
263
+ "customers.calendar.editor.discardUnknownField": "Pole niestandardowe",
252
264
  "customers.calendar.editor.error": "Nie udało się zapisać wydarzenia",
253
265
  "customers.calendar.editor.guestBadge": "Gość",
266
+ "customers.calendar.editor.historicalType": "Ten typ wydarzenia nie jest dostępny dla nowych wydarzeń. Możesz dalej edytować to wydarzenie albo wybrać inny typ.",
254
267
  "customers.calendar.editor.location": "Lokalizacja",
255
268
  "customers.calendar.editor.locationPlaceholder": "Dodaj lokalizację lub link…",
256
269
  "customers.calendar.editor.multiDayHint": "Wydarzenie wielodniowe · {count} dni",
@@ -295,6 +308,7 @@
295
308
  "customers.calendar.editor.saved": "Wydarzenie zapisane",
296
309
  "customers.calendar.editor.saving": "Zapisywanie…",
297
310
  "customers.calendar.editor.searching": "Wyszukiwanie…",
311
+ "customers.calendar.editor.timezone": "Strefa czasowa",
298
312
  "customers.calendar.editor.title.create": "Nowe wydarzenie",
299
313
  "customers.calendar.editor.title.edit": "Edytuj wydarzenie",
300
314
  "customers.calendar.editor.titleLabel.email": "Temat",
@@ -303,6 +317,7 @@
303
317
  "customers.calendar.editor.titlePlaceholder": "Dodaj tytuł…",
304
318
  "customers.calendar.editor.to": "Do",
305
319
  "customers.calendar.editor.typeSwitcher": "Typ wydarzenia",
320
+ "customers.calendar.editor.typeUnavailable": "Przed zapisaniem wybierz dostępny typ wydarzenia.",
306
321
  "customers.calendar.editor.types.call": "Telefon",
307
322
  "customers.calendar.editor.types.email": "E-mail",
308
323
  "customers.calendar.editor.types.event": "Wydarzenie",
@@ -312,6 +327,8 @@
312
327
  "customers.calendar.editor.validation.assigneeRequired": "Przypisz to zadanie do członka zespołu",
313
328
  "customers.calendar.editor.validation.endsBeforeStarts": "Zakończenie musi być po rozpoczęciu",
314
329
  "customers.calendar.editor.validation.relatedToRequired": "Wybierz osobę lub firmę, aby powiązać to wydarzenie",
330
+ "customers.calendar.editor.validation.timezoneGap": "Ta godzina nie istnieje w wybranej strefie czasowej",
331
+ "customers.calendar.editor.validation.timezoneInvalid": "Wybierz poprawną strefę czasową",
315
332
  "customers.calendar.editor.validation.titleRequired": "Tytuł jest wymagany",
316
333
  "customers.calendar.empty.action": "Nowe wydarzenie",
317
334
  "customers.calendar.empty.agenda": "Brak zaplanowanych wydarzeń w tym okresie",
@@ -368,6 +385,7 @@
368
385
  "customers.calendar.settings.conflictWarnings": "Ostrzeżenia o konfliktach",
369
386
  "customers.calendar.settings.eventCategories": "Kategorie wydarzeń",
370
387
  "customers.calendar.settings.eventCategoriesHint": "Twoje własne etykiety grupujące (np. Spotkanie zespołu, Telefon sprzedażowy). Pojawiają się podczas tworzenia wydarzenia.",
388
+ "customers.calendar.settings.manageActivityTypes": "Zarządzaj typami aktywności",
371
389
  "customers.calendar.settings.max": "(maks. {count})",
372
390
  "customers.calendar.settings.removeTag": "Usuń {tag}",
373
391
  "customers.calendar.settings.save": "Zapisz zmiany",
@@ -642,7 +660,6 @@
642
660
  "customers.companies.detail.notes.placeholder": "Napisz notatkę o tej firmie…",
643
661
  "customers.companies.detail.people.add": "Dodaj osobę",
644
662
  "customers.companies.detail.people.empty": "Brak powiązanych osób z tą firmą.",
645
- "customers.companies.detail.people.filter": "Filtry",
646
663
  "customers.companies.detail.people.helper": "Osoby powiązane z tą firmą pojawiają się tutaj i mogą być tworzone, przypinane, otwierane lub odpinane bez opuszczania strony.",
647
664
  "customers.companies.detail.people.linkAction": "Podepnij istniejącą osobę",
648
665
  "customers.companies.detail.people.linkCancel": "Anuluj",
@@ -671,20 +688,13 @@
671
688
  "customers.companies.detail.people.linkedOn": "Powiązano {{date}}",
672
689
  "customers.companies.detail.people.loadError": "Nie udało się załadować osób.",
673
690
  "customers.companies.detail.people.loading": "Ładowanie osób…",
674
- "customers.companies.detail.people.next": "Następna",
675
- "customers.companies.detail.people.noSearchResults": "Żadna osoba nie pasuje do wyszukiwania.",
676
691
  "customers.companies.detail.people.open": "Otwórz osobę",
677
- "customers.companies.detail.people.pageSummary": "Strona {{page}} z {{total}}",
678
- "customers.companies.detail.people.previous": "Poprzednia",
679
692
  "customers.companies.detail.people.remove": "Odepnij",
680
693
  "customers.companies.detail.people.removeError": "Nie udało się odpiąć osoby od firmy.",
681
694
  "customers.companies.detail.people.removeSuccess": "Osoba odpięta od firmy.",
682
695
  "customers.companies.detail.people.searchPlaceholder": "Szukaj po nazwisku, roli, e-mailu...",
683
696
  "customers.companies.detail.people.sectionSubtitle": "Pracownicy i decydenci po stronie klienta",
684
697
  "customers.companies.detail.people.sectionTitle": "Osoby",
685
- "customers.companies.detail.people.sortNameAsc": "Sortuj: Nazwa A-Z",
686
- "customers.companies.detail.people.sortNameDesc": "Sortuj: Nazwa Z-A",
687
- "customers.companies.detail.people.sortRecent": "Sortuj: Ostatnio powiązane",
688
698
  "customers.companies.detail.relativeTime.daysAgo": "{{days}} dni temu",
689
699
  "customers.companies.detail.relativeTime.monthsAgo": "{{months}} mies. temu",
690
700
  "customers.companies.detail.relativeTime.oneDayAgo": "1 dzień temu",
@@ -806,6 +816,71 @@
806
816
  "customers.companies.presets.inactive60": "Nieaktywne > 60 dni",
807
817
  "customers.companies.presets.myAccounts": "Moje konta",
808
818
  "customers.companies.presets.recentlyCreated": "Ostatnio utworzone",
819
+ "customers.config.activityTypes.appearance": "Wygląd",
820
+ "customers.config.activityTypes.behavior": "Zachowanie formularza",
821
+ "customers.config.activityTypes.columns.active": "Możliwość wyboru",
822
+ "customers.config.activityTypes.columns.behavior": "Zachowanie",
823
+ "customers.config.activityTypes.columns.fields": "Pola",
824
+ "customers.config.activityTypes.columns.fieldsets": "Zestawy pól",
825
+ "customers.config.activityTypes.columns.key": "Stały klucz",
826
+ "customers.config.activityTypes.columns.order": "Kolejność",
827
+ "customers.config.activityTypes.columns.source": "Źródło",
828
+ "customers.config.activityTypes.columns.type": "Typ aktywności",
829
+ "customers.config.activityTypes.configure": "Konfiguruj",
830
+ "customers.config.activityTypes.delete": "Usuń",
831
+ "customers.config.activityTypes.empty": "Brak dostępnych typów aktywności.",
832
+ "customers.config.activityTypes.fields.allDay": "Cały dzień",
833
+ "customers.config.activityTypes.fields.baseKind": "Podstawowe zachowanie",
834
+ "customers.config.activityTypes.fields.color": "Kolor (#RRGGBB)",
835
+ "customers.config.activityTypes.fields.customFieldsetIds": "Zestawy pól niestandardowych",
836
+ "customers.config.activityTypes.fields.endTime": "Czas zakończenia",
837
+ "customers.config.activityTypes.fields.icon": "Ikona",
838
+ "customers.config.activityTypes.fields.label": "Etykieta",
839
+ "customers.config.activityTypes.fields.location": "Pole lokalizacji",
840
+ "customers.config.activityTypes.fields.order": "Kolejność wyświetlania",
841
+ "customers.config.activityTypes.fields.people": "Pole osób",
842
+ "customers.config.activityTypes.fields.priority": "Priorytet",
843
+ "customers.config.activityTypes.fields.recurrence": "Powtarzanie",
844
+ "customers.config.activityTypes.fields.resources": "Zasoby",
845
+ "customers.config.activityTypes.fields.selectable": "Dostępny dla nowych wydarzeń",
846
+ "customers.config.activityTypes.fields.value": "Stały klucz",
847
+ "customers.config.activityTypes.fieldsetsUnavailable": "Zestawy pól są niedostępne. Wczytaj je ponownie przed zapisem.",
848
+ "customers.config.activityTypes.inactiveHint": "Moduł obecnie uniemożliwia zastosowanie tego nadpisania.",
849
+ "customers.config.activityTypes.inherited": "Dziedziczony",
850
+ "customers.config.activityTypes.inheritedHint": "Zapis utworzy lokalne nadpisanie dla tej organizacji.",
851
+ "customers.config.activityTypes.invalidBehavior": "Sprawdź wartości zachowania formularza.",
852
+ "customers.config.activityTypes.invalidKey": "Użyj klucza z małych liter, cyfr, łączników lub podkreśleń.",
853
+ "customers.config.activityTypes.loadFailed": "Nie udało się wczytać typów aktywności.",
854
+ "customers.config.activityTypes.loadingFieldsets": "Wczytywanie zestawów pól…",
855
+ "customers.config.activityTypes.local": "Lokalne nadpisanie",
856
+ "customers.config.activityTypes.missing": "brak",
857
+ "customers.config.activityTypes.missingHint": "Niektóre skonfigurowane zestawy pól już nie istnieją.",
858
+ "customers.config.activityTypes.new": "Nowy typ aktywności",
859
+ "customers.config.activityTypes.no": "Nie",
860
+ "customers.config.activityTypes.noFieldsets": "Nie zdefiniowano jeszcze zestawów pól niestandardowych.",
861
+ "customers.config.activityTypes.options.assignee": "Osoba przypisana",
862
+ "customers.config.activityTypes.options.attendees": "Uczestnicy",
863
+ "customers.config.activityTypes.options.call": "Połączenie",
864
+ "customers.config.activityTypes.options.email": "E-mail",
865
+ "customers.config.activityTypes.options.event": "Wydarzenie",
866
+ "customers.config.activityTypes.options.location": "Lokalizacja",
867
+ "customers.config.activityTypes.options.meeting": "Spotkanie",
868
+ "customers.config.activityTypes.options.none": "Brak",
869
+ "customers.config.activityTypes.options.note": "Notatka",
870
+ "customers.config.activityTypes.options.participants": "Osoby uczestniczące",
871
+ "customers.config.activityTypes.options.phoneLink": "Łącze telefoniczne",
872
+ "customers.config.activityTypes.options.recipients": "Odbiorcy",
873
+ "customers.config.activityTypes.options.task": "Zadanie",
874
+ "customers.config.activityTypes.refresh": "Odśwież",
875
+ "customers.config.activityTypes.removed": "Usunięto konfigurację typu aktywności.",
876
+ "customers.config.activityTypes.reset": "Zresetuj nadpisanie",
877
+ "customers.config.activityTypes.resetConfirm": "Usunąć lokalną konfigurację typu aktywności?",
878
+ "customers.config.activityTypes.retry": "Ponów",
879
+ "customers.config.activityTypes.save": "Zapisz",
880
+ "customers.config.activityTypes.saveFailed": "Nie udało się zapisać typu aktywności.",
881
+ "customers.config.activityTypes.saved": "Zapisano typ aktywności.",
882
+ "customers.config.activityTypes.view": "Wyświetl",
883
+ "customers.config.activityTypes.yes": "Tak",
809
884
  "customers.config.addressFormat.description": "Wybierz, jak powinny być zbudowane formularze i widoki w module klientów.",
810
885
  "customers.config.addressFormat.error": "Nie udało się wczytać ustawień adresu",
811
886
  "customers.config.addressFormat.errorSave": "Nie udało się zaktualizować ustawień adresu",
@@ -1073,7 +1148,18 @@
1073
1148
  "customers.deals.detail.notesEmptyAction": "Dodaj notatkę",
1074
1149
  "customers.deals.detail.notesEmptyTitle": "Dziel się najważniejszymi informacjami",
1075
1150
  "customers.deals.detail.notesLoading": "Ładowanie notatek…",
1151
+ "customers.deals.detail.people.addNewSubtitle": "Nowy kontakt zostanie automatycznie podpięty do tej szansy sprzedaży",
1152
+ "customers.deals.detail.people.dialogSubtitle": "Podepnij istniejący kontakt do tej szansy sprzedaży",
1153
+ "customers.deals.detail.people.linkAction": "Podepnij istniejącą osobę",
1154
+ "customers.deals.detail.people.loadError": "Nie udało się załadować osób.",
1155
+ "customers.deals.detail.people.searchPlaceholder": "Szukaj po nazwisku, roli, e-mailu...",
1156
+ "customers.deals.detail.people.sectionSubtitle": "Kontakty zaangażowane w tę szansę sprzedaży",
1157
+ "customers.deals.detail.people.sectionTitle": "Osoby",
1158
+ "customers.deals.detail.peopleAdd": "Dodaj osobę",
1076
1159
  "customers.deals.detail.peopleEditorTitle": "Zarządzaj powiązanymi osobami",
1160
+ "customers.deals.detail.peopleEmpty": "Brak osób powiązanych z tą szansą sprzedaży.",
1161
+ "customers.deals.detail.peopleEmptyAction": "Dodaj osobę",
1162
+ "customers.deals.detail.peopleEmptyTitle": "Podepnij zaangażowane osoby",
1077
1163
  "customers.deals.detail.peopleNoDetails": "Brak dodatkowych informacji",
1078
1164
  "customers.deals.detail.peopleSearch": "Szukaj powiązanych osób…",
1079
1165
  "customers.deals.detail.peopleSection": "Osoby",
@@ -1702,6 +1788,15 @@
1702
1788
  "customers.interactions.status.waiting": "Oczekuje / zablokowana",
1703
1789
  "customers.interactions.update.error": "Nie udało się zaktualizować interakcji.",
1704
1790
  "customers.interactions.update.success": "Zaktualizowano interakcję.",
1791
+ "customers.linkedPeople.filter": "Filtry",
1792
+ "customers.linkedPeople.loading": "Ładowanie osób…",
1793
+ "customers.linkedPeople.next": "Następna",
1794
+ "customers.linkedPeople.noSearchResults": "Żadna osoba nie pasuje do wyszukiwania.",
1795
+ "customers.linkedPeople.pageSummary": "Strona {{page}} z {{total}}",
1796
+ "customers.linkedPeople.previous": "Poprzednia",
1797
+ "customers.linkedPeople.sortNameAsc": "Sortuj: Nazwa A-Z",
1798
+ "customers.linkedPeople.sortNameDesc": "Sortuj: Nazwa Z-A",
1799
+ "customers.linkedPeople.sortRecent": "Sortuj: Ostatnio powiązane",
1705
1800
  "customers.linking.actions.cancel": "Anuluj",
1706
1801
  "customers.linking.actions.save": "Zapisz powiązania",
1707
1802
  "customers.linking.actions.saving": "Zapisywanie…",
@@ -1812,6 +1907,7 @@
1812
1907
  "customers.people.createDialog.required": "Pola oznaczone * są wymagane",
1813
1908
  "customers.people.createDialog.submit": "Utwórz osobę",
1814
1909
  "customers.people.createDialog.success": "Utworzono osobę i powiązano z firmą",
1910
+ "customers.people.createDialog.successPlain": "Osoba została utworzona",
1815
1911
  "customers.people.createDialog.title": "Dodaj nową osobę",
1816
1912
  "customers.people.decisionMakers.count": "{{count}} kluczowych osób",
1817
1913
  "customers.people.decisionMakers.label": "Decydenci",
@@ -1,29 +1,70 @@
1
1
  import type { CalendarItem, CalendarItemStatus, CalendarParticipant } from '../../components/calendar/types'
2
+ import { parseLinkedEntities } from './editorPayload'
2
3
  import { participantActorKey } from './participantIdentity'
3
4
  import type { ConflictScope } from './preferences'
4
5
 
5
6
  export const EDITOR_DRAFT_CONFLICT_ID = '__draft__'
6
7
 
7
8
  export type FindConflictsOptions = {
8
- // 'all' (default) flags any actor-sharing overlap; 'mine' only flags overlaps
9
- // where `currentUserId` is an actor of BOTH events (the user is double-booked).
10
- // 'mine' with no `currentUserId` degrades to 'all' — it cannot resolve "mine".
9
+ // 'all' (default) flags any overlap that shares a person or a resource. 'mine'
10
+ // narrows the PEOPLE overlaps to those where `currentUserId` is an actor of BOTH
11
+ // events (the user is double-booked); a shared resource is flagged in both
12
+ // scopes. 'mine' with no `currentUserId` degrades to 'all' — it cannot resolve
13
+ // "mine".
11
14
  scope?: ConflictScope
12
15
  currentUserId?: string | null
13
16
  }
14
17
 
15
- function sharesActor(first: CalendarItem, second: CalendarItem): boolean {
16
- if (first.ownerUserId && second.ownerUserId && first.ownerUserId === second.ownerUserId) return true
17
- if (first.participants.length === 0 || second.participants.length === 0) return false
18
- const firstActorKeys = new Set(
19
- first.participants
20
- .map(participantActorKey)
21
- .filter((actorKey): actorKey is string => actorKey !== null),
22
- )
23
- return second.participants.some((participant) => {
24
- const actorKey = participantActorKey(participant)
25
- return actorKey !== null && firstActorKeys.has(actorKey)
26
- })
18
+ export type ConflictSubject = { kind: 'person' | 'resource'; key: string; label: string }
19
+
20
+ function participantLabel(participant: CalendarParticipant): string {
21
+ return participant.name?.trim() || participant.email?.trim() || ''
22
+ }
23
+
24
+ function bookedResources(item: CalendarItem): Array<{ id: string; label: string }> {
25
+ return parseLinkedEntities((item.raw as { linkedEntities?: unknown }).linkedEntities).resources
26
+ }
27
+
28
+ /**
29
+ * The people and resources two events have in common — what makes an overlap a
30
+ * double-booking. A person is shared through the owner or a participant; a
31
+ * resource through a `resource` link. Labels are empty when the event carries no
32
+ * name for the subject.
33
+ */
34
+ export function sharedConflictSubjects(first: CalendarItem, second: CalendarItem): ConflictSubject[] {
35
+ const shared = new Map<string, ConflictSubject>()
36
+ const labelOf = (item: CalendarItem, actorKey: string): string => {
37
+ const match = item.participants.find((participant) => participantActorKey(participant) === actorKey)
38
+ return match ? participantLabel(match) : ''
39
+ }
40
+ if (first.ownerUserId && second.ownerUserId && first.ownerUserId === second.ownerUserId) {
41
+ const key = `user:${first.ownerUserId}`
42
+ shared.set(key, { kind: 'person', key, label: labelOf(first, key) || labelOf(second, key) })
43
+ }
44
+ if (first.participants.length > 0 && second.participants.length > 0) {
45
+ const secondActorKeys = new Set(
46
+ second.participants
47
+ .map(participantActorKey)
48
+ .filter((actorKey): actorKey is string => actorKey !== null),
49
+ )
50
+ for (const participant of first.participants) {
51
+ const key = participantActorKey(participant)
52
+ if (key === null || !secondActorKeys.has(key) || shared.has(key)) continue
53
+ shared.set(key, { kind: 'person', key, label: participantLabel(participant) || labelOf(second, key) })
54
+ }
55
+ }
56
+ const firstResources = bookedResources(first)
57
+ if (firstResources.length > 0) {
58
+ const secondResources = new Map(bookedResources(second).map((resource) => [resource.id, resource]))
59
+ for (const resource of firstResources) {
60
+ const other = secondResources.get(resource.id)
61
+ if (!other) continue
62
+ const key = `resource:${resource.id}`
63
+ const label = resource.label !== resource.id ? resource.label : other.label !== other.id ? other.label : ''
64
+ shared.set(key, { kind: 'resource', key, label })
65
+ }
66
+ }
67
+ return [...shared.values()]
27
68
  }
28
69
 
29
70
  function isActor(item: CalendarItem, userId: string): boolean {
@@ -52,12 +93,20 @@ export function findConflicts(items: CalendarItem[], options: FindConflictsOptio
52
93
  const other = candidates[lookahead]
53
94
  if (other.start.getTime() >= currentEnd) break
54
95
  if (current.start.getTime() >= other.end.getTime()) continue
55
- // Org-wide rule first: the two events must share at least one actor.
56
- if (!sharesActor(current, other)) continue
57
- // 'mine' narrows that set to the current user's OWN double-bookings — they
58
- // must be an actor of both events. Because they are then the shared actor,
59
- // 'mine' is always a strict subset of 'all' (it never invents new conflicts).
60
- if (restrictToCurrentUser && !(isActor(current, currentUserId) && isActor(other, currentUserId))) continue
96
+ // Org-wide rule first: the two events must share at least one actor or
97
+ // booked resource.
98
+ const shared = sharedConflictSubjects(current, other)
99
+ if (shared.length === 0) continue
100
+ // 'mine' narrows the people overlaps to the current user's OWN
101
+ // double-bookings — they must be an actor of both events. Because they are
102
+ // then the shared actor, 'mine' is always a strict subset of 'all' (it never
103
+ // invents new conflicts). A double-booked resource is not anyone's in
104
+ // particular — a room cannot host two events — so it is flagged in both scopes.
105
+ if (
106
+ restrictToCurrentUser
107
+ && !shared.some((subject) => subject.kind === 'resource')
108
+ && !(isActor(current, currentUserId) && isActor(other, currentUserId))
109
+ ) continue
61
110
  appendConflict(conflicts, current.id, other.id)
62
111
  appendConflict(conflicts, other.id, current.id)
63
112
  }
@@ -74,20 +123,29 @@ export type EditorConflictDraft = {
74
123
  // exactly as the grid excludes canceled items, so the editor never warns about
75
124
  // a record the grid would not badge/ring. Defaults to 'planned'.
76
125
  status?: CalendarItemStatus
126
+ // Resources the draft books. Omitted when the type has no resources field.
127
+ resources?: Array<{ id: string; label: string }>
128
+ // Display name of `ownerUserId`, when the editor knows it (a task's assignee).
129
+ // An owner who is not also a participant has no name on either event.
130
+ ownerLabel?: string | null
77
131
  }
78
132
 
133
+ export type EditorConflict = { item: CalendarItem; shared: ConflictSubject[] }
134
+
79
135
  /**
80
136
  * Resolves the items a calendar editor draft conflicts with, using the SAME
81
- * `findConflicts` rules as the grid (overlap + shared owner/participant) so the
82
- * editor's conflict warning is always consistent with the grid badges/rings.
83
- * The edited record itself (`excludeId`) is never treated as a conflict.
137
+ * `findConflicts` rules as the grid (overlap + shared owner/participant/resource)
138
+ * so the editor's conflict warning is always consistent with the grid
139
+ * badges/rings. Each conflict names the people and resources that are
140
+ * double-booked. The edited record itself (`excludeId`) is never treated as a
141
+ * conflict.
84
142
  */
85
- export function findEditorConflictItems(
143
+ export function findEditorConflicts(
86
144
  draft: EditorConflictDraft,
87
145
  others: CalendarItem[],
88
146
  excludeId: string | null,
89
147
  options: FindConflictsOptions = {},
90
- ): CalendarItem[] {
148
+ ): EditorConflict[] {
91
149
  const draftStatus: CalendarItemStatus = draft.status ?? 'planned'
92
150
  const draftItem: CalendarItem = {
93
151
  id: EDITOR_DRAFT_CONFLICT_ID,
@@ -108,7 +166,12 @@ export function findEditorConflictItems(
108
166
  color: null,
109
167
  isRecurringOccurrence: false,
110
168
  updatedAt: null,
111
- raw: { id: EDITOR_DRAFT_CONFLICT_ID, interactionType: 'meeting', status: draftStatus },
169
+ raw: {
170
+ id: EDITOR_DRAFT_CONFLICT_ID,
171
+ interactionType: 'meeting',
172
+ status: draftStatus,
173
+ linkedEntities: (draft.resources ?? []).map((resource) => ({ id: resource.id, type: 'resource', label: resource.label })),
174
+ },
112
175
  }
113
176
  // Exclude the edited record by its underlying interaction id (`raw.id`), which
114
177
  // also drops every expanded occurrence of an edited recurring series (those
@@ -122,4 +185,20 @@ export function findEditorConflictItems(
122
185
  return conflictIds
123
186
  .map((id) => byId.get(id))
124
187
  .filter((item): item is CalendarItem => Boolean(item))
188
+ .map((item) => ({
189
+ item,
190
+ shared: sharedConflictSubjects(draftItem, item).map((subject) =>
191
+ !subject.label && draft.ownerLabel && subject.key === `user:${draft.ownerUserId}`
192
+ ? { ...subject, label: draft.ownerLabel }
193
+ : subject),
194
+ }))
195
+ }
196
+
197
+ export function findEditorConflictItems(
198
+ draft: EditorConflictDraft,
199
+ others: CalendarItem[],
200
+ excludeId: string | null,
201
+ options: FindConflictsOptions = {},
202
+ ): CalendarItem[] {
203
+ return findEditorConflicts(draft, others, excludeId, options).map((conflict) => conflict.item)
125
204
  }
@@ -1,5 +1,7 @@
1
1
  import type { CalendarItem } from '../../components/calendar/types'
2
+ import { calendarEventTypeKeySchema, resolveCalendarEventType } from '../../calendar-event-types'
2
3
  import { parseRecurrenceRule } from './recurrence'
4
+ import { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, calendarWallTimeToInstant, defaultCalendarTimezone, isCalendarTimezone } from './timezone'
3
5
 
4
6
  export type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'
5
7
 
@@ -41,7 +43,11 @@ const KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {
41
43
  deadline: 'task',
42
44
  }
43
45
 
46
+ /** @deprecated Resolve the effective calendar event type and read `behavior.baseKind`. */
44
47
  export function editorKindOfInteractionType(interactionType: string): EditorKind {
48
+ const parsedKey = calendarEventTypeKeySchema.safeParse(interactionType)
49
+ const effectiveKind = parsedKey.success ? resolveCalendarEventType(parsedKey.data)?.behavior.baseKind : undefined
50
+ if (effectiveKind) return effectiveKind
45
51
  return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'
46
52
  }
47
53
 
@@ -161,6 +167,7 @@ export type EditorFormState = {
161
167
  allDay: boolean
162
168
  date: string
163
169
  startTime: string
170
+ timezone?: string
164
171
  endDate: string
165
172
  endTime: string
166
173
  repeatFreq: EditorRepeatFreq
@@ -245,6 +252,7 @@ export function createDefaultFormState(
245
252
  }
246
253
  return {
247
254
  kind: 'meeting',
255
+ timezone: defaultCalendarTimezone(),
248
256
  title: '',
249
257
  relatedTo: null,
250
258
  dealId: null,
@@ -286,21 +294,23 @@ export function buildRecurrenceRule(state: EditorFormState): string | null {
286
294
  }
287
295
  if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`
288
296
  if (state.repeatEndType === 'date' && state.repeatUntilDate) {
289
- rule += `;UNTIL=${state.repeatUntilDate.replace(/-/g, '')}T235959Z`
297
+ const until = state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : null
298
+ rule += `;UNTIL=${until ? until.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z') : `${state.repeatUntilDate.replace(/-/g, '')}T235959Z`}`
290
299
  }
291
300
  return rule
292
301
  }
293
302
 
294
303
  export function computeDurationMinutes(state: EditorFormState): number | null {
295
- const start = new Date(`${state.date}T${state.startTime}:00`)
296
- const end = new Date(`${state.endDate}T${state.endTime}:00`)
297
- if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null
304
+ const start = state.timezone ? calendarWallTimeToInstant(state.date, state.startTime, state.timezone) : new Date(`${state.date}T${state.startTime}:00`)
305
+ const end = state.timezone ? calendarWallTimeToInstant(state.endDate, state.endTime, state.timezone) : new Date(`${state.endDate}T${state.endTime}:00`)
306
+ if (!start || !end || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null
298
307
  const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)
299
308
  return minutes > 0 ? minutes : null
300
309
  }
301
310
 
302
311
  export type BuildPayloadOptions = {
303
312
  mode: 'create' | 'edit'
313
+ config?: EditorKindConfig
304
314
  id?: string
305
315
  /**
306
316
  * When the resources module is loaded the payload owns `linkedEntities`
@@ -317,10 +327,16 @@ export type BuildPayloadOptions = {
317
327
  }
318
328
 
319
329
  export function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {
320
- const config = KIND_CONFIG[state.kind]
330
+ const config = options.config ?? KIND_CONFIG[state.kind]
321
331
  const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime
322
- const scheduledAt = new Date(`${state.date}T${time}:00`).toISOString()
332
+ const instant = state.timezone ? (state.allDay && config.hasAllDay ? calendarDayStartInstant(state.date, state.timezone) : calendarWallTimeToInstant(state.date, time, state.timezone)) : new Date(`${state.date}T${time}:00`)
333
+ if (!instant || !Number.isFinite(instant.getTime())) throw new Error('[internal] Invalid calendar local time')
334
+ const scheduledAt = instant.toISOString()
323
335
  const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null
336
+ const recurrenceEnd = recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate
337
+ ? state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : new Date(state.repeatUntilDate)
338
+ : null
339
+ if (recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate && !recurrenceEnd) throw new Error('[internal] Invalid calendar recurrence day')
324
340
  const payload: Record<string, unknown> = {
325
341
  ...(options.mode === 'edit' && options.id ? { id: options.id } : {}),
326
342
  entityId: state.relatedTo?.id ?? null,
@@ -332,14 +348,12 @@ export function buildInteractionPayload(state: EditorFormState, options: BuildPa
332
348
  date: state.date,
333
349
  time,
334
350
  scheduledAt,
351
+ ...(state.timezone ? { timezone: state.timezone } : {}),
335
352
  durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,
336
353
  allDay: config.hasAllDay ? state.allDay : null,
337
354
  location: config.location ? state.location.trim() || null : null,
338
355
  recurrenceRule,
339
- recurrenceEnd:
340
- recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate
341
- ? new Date(state.repeatUntilDate).toISOString()
342
- : null,
356
+ recurrenceEnd: recurrenceEnd?.toISOString() ?? null,
343
357
  participants:
344
358
  config.people && config.people !== 'assignee' && state.participants.length > 0
345
359
  ? state.participants.map((participant) => ({
@@ -374,10 +388,11 @@ function readUnknownNumber(value: unknown): number | null {
374
388
 
375
389
  type ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>
376
390
 
377
- function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
391
+ function parseRepeatFromRule(rawRule: unknown, start: Date, timezone?: string): ParsedRepeat {
392
+ const wallStart = timezone ? new Date(`${calendarInstantToWallTime(start, timezone).date}T12:00:00`) : start
378
393
  const fallback: ParsedRepeat = {
379
394
  repeatFreq: 'none',
380
- repeatDays: defaultRepeatDays(start),
395
+ repeatDays: defaultRepeatDays(wallStart),
381
396
  repeatEndType: 'never',
382
397
  repeatCount: 8,
383
398
  repeatUntilDate: '',
@@ -386,7 +401,7 @@ function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
386
401
  if (!ruleText) return fallback
387
402
  const parsed = parseRecurrenceRule(ruleText)
388
403
  if (!parsed) return fallback
389
- const repeatDays = defaultRepeatDays(start)
404
+ const repeatDays = defaultRepeatDays(wallStart)
390
405
  if (parsed.byDay) {
391
406
  repeatDays.fill(false)
392
407
  for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true
@@ -399,7 +414,7 @@ function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
399
414
  repeatCount = parsed.count
400
415
  } else if (parsed.until) {
401
416
  repeatEndType = 'date'
402
- repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`
417
+ repeatUntilDate = timezone ? calendarInstantToWallTime(parsed.until, timezone).date : `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`
403
418
  }
404
419
  return {
405
420
  repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',
@@ -427,6 +442,9 @@ export function parseItemToFormState(item: CalendarItem): EditorFormState {
427
442
  const kind = editorKindOfInteractionType(item.interactionType)
428
443
  const raw = item.raw as Record<string, unknown>
429
444
  const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities)
445
+ const timezone = isCalendarTimezone(raw.timezone) ? raw.timezone : defaultCalendarTimezone()
446
+ const startWall = calendarInstantToWallTime(item.start, timezone)
447
+ const endWall = calendarInstantToWallTime(item.end, timezone)
430
448
  return {
431
449
  kind,
432
450
  title: item.title,
@@ -434,11 +452,13 @@ export function parseItemToFormState(item: CalendarItem): EditorFormState {
434
452
  dealId: item.dealId,
435
453
  dealLabel: null,
436
454
  allDay: item.allDay,
437
- date: formatLocalDateInput(item.start),
438
- startTime: formatLocalTimeInput(item.start),
439
- endDate: formatLocalDateInput(item.end),
440
- endTime: formatLocalTimeInput(item.end),
441
- ...parseRepeatFromRule(item.raw.recurrenceRule, item.start),
455
+ timezone,
456
+ date: startWall.date,
457
+ startTime: startWall.time,
458
+ endDate: endWall.date,
459
+ endTime: endWall.time,
460
+ ...parseRepeatFromRule(item.raw.recurrenceRule, item.start, isCalendarTimezone(raw.timezone) ? timezone : undefined),
461
+ ...(item.raw.recurrenceEnd && isCalendarTimezone(raw.timezone) ? { repeatUntilDate: calendarInstantToWallTime(new Date(item.raw.recurrenceEnd), timezone).date } : {}),
442
462
  category: item.interactionType,
443
463
  location: item.location ?? '',
444
464
  participants: parseParticipants(item),