@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
@@ -21,6 +21,8 @@ function DateTimeRow({
21
21
  showTime,
22
22
  onDateChange,
23
23
  onTimeChange,
24
+ error,
25
+ disabled,
24
26
  }: {
25
27
  label: string
26
28
  date: string
@@ -28,14 +30,17 @@ function DateTimeRow({
28
30
  showTime: boolean
29
31
  onDateChange(next: string): void
30
32
  onTimeChange(next: string): void
33
+ error?: string | null
34
+ disabled?: boolean
31
35
  }) {
32
36
  return (
33
- <div className="flex w-full items-end gap-2.5">
37
+ <div className="flex w-full flex-wrap items-end gap-x-2.5 gap-y-1">
34
38
  <div className="flex min-w-0 flex-1 flex-col gap-1.5">
35
39
  <span className={LABEL_CLASS}>{label}</span>
36
- <DateControl value={date} onChange={onDateChange} ariaLabel={label} />
40
+ <DateControl value={date} onChange={onDateChange} ariaLabel={label} disabled={disabled} />
37
41
  </div>
38
- {showTime ? <TimeControl value={time} onChange={onTimeChange} ariaLabel={label} /> : null}
42
+ {showTime ? <TimeControl value={time} onChange={onTimeChange} ariaLabel={label} disabled={disabled} /> : null}
43
+ {error ? <p className="w-full text-xs text-status-error-text">{error}</p> : null}
39
44
  </div>
40
45
  )
41
46
  }
@@ -49,7 +54,9 @@ export function ScheduleSection({
49
54
  startTime,
50
55
  endDate,
51
56
  endTime,
57
+ startError,
52
58
  endsError,
59
+ disabled,
53
60
  onAllDayChange,
54
61
  onDateChange,
55
62
  onStartTimeChange,
@@ -64,7 +71,9 @@ export function ScheduleSection({
64
71
  startTime: string
65
72
  endDate: string
66
73
  endTime: string
74
+ startError?: string | null
67
75
  endsError?: string | null
76
+ disabled?: boolean
68
77
  onAllDayChange(next: boolean): void
69
78
  onDateChange(next: string): void
70
79
  onStartTimeChange(next: string): void
@@ -79,7 +88,7 @@ export function ScheduleSection({
79
88
  {hasAllDay ? (
80
89
  <div className="flex w-full items-center justify-between">
81
90
  <span className={LABEL_CLASS}>{t('customers.calendar.editor.allDay', 'All day')}</span>
82
- <AllDayToggle checked={allDay} onCheckedChange={onAllDayChange} label={t('customers.calendar.editor.allDay', 'All day')} />
91
+ <AllDayToggle checked={allDay} onCheckedChange={onAllDayChange} label={t('customers.calendar.editor.allDay', 'All day')} disabled={disabled} />
83
92
  </div>
84
93
  ) : null}
85
94
  <DateTimeRow
@@ -89,6 +98,8 @@ export function ScheduleSection({
89
98
  showTime={showTime}
90
99
  onDateChange={onDateChange}
91
100
  onTimeChange={onStartTimeChange}
101
+ error={startError}
102
+ disabled={disabled}
92
103
  />
93
104
  {hasEnd ? (
94
105
  <DateTimeRow
@@ -98,6 +109,7 @@ export function ScheduleSection({
98
109
  showTime={showTime}
99
110
  onDateChange={onEndDateChange}
100
111
  onTimeChange={onEndTimeChange}
112
+ disabled={disabled}
101
113
  />
102
114
  ) : null}
103
115
  {endsError ? <p className="text-xs text-status-error-text">{endsError}</p> : null}
@@ -12,12 +12,14 @@ export function SegmentGroup<T extends string>({
12
12
  onChange,
13
13
  size = 'sm',
14
14
  ariaLabel,
15
+ disabled = false,
15
16
  }: {
16
17
  options: Array<SegmentOption<T>>
17
18
  value: T
18
19
  onChange(next: T): void
19
20
  size?: 'sm' | 'md'
20
21
  ariaLabel: string
22
+ disabled?: boolean
21
23
  }) {
22
24
  return (
23
25
  <div
@@ -34,6 +36,7 @@ export function SegmentGroup<T extends string>({
34
36
  variant="ghost"
35
37
  aria-pressed={isActive}
36
38
  onClick={() => onChange(option.value)}
39
+ disabled={disabled}
37
40
  title={option.label}
38
41
  data-state={isActive ? 'active' : 'inactive'}
39
42
  className={cn(
@@ -1,11 +1,12 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import { calendarDayEndInstant, calendarDayStartInstant, calendarWallTimeToInstant } from '../../../lib/calendar/timezone'
4
5
  import { endOfDay } from 'date-fns/endOfDay'
5
6
  import { startOfDay } from 'date-fns/startOfDay'
6
7
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
8
  import { computeDurationMinutes, type EditorFormState, type EditorKindConfig } from '../../../lib/calendar/editorPayload'
8
- import { findEditorConflictItems } from '../../../lib/calendar/conflicts'
9
+ import { findEditorConflicts } from '../../../lib/calendar/conflicts'
9
10
  import type { ConflictScope } from '../../../lib/calendar/preferences'
10
11
  import { mapInteractionToCalendarItem } from '../../../lib/calendar/mapItem'
11
12
  import { participantActorKey } from '../../../lib/calendar/participantIdentity'
@@ -55,12 +56,14 @@ export function useEditorLabelResolution(
55
56
  }, [activeOrgId, open, form.relatedTo, form.dealId, form.dealLabel, form.assigneeUserId, form.assigneeName, update])
56
57
  }
57
58
 
59
+ const NO_DRAFT_RESOURCES: ReadonlyArray<{ id: string; label: string }> = []
60
+
58
61
  function formatClock(date: Date): string {
59
62
  return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
60
63
  }
61
64
 
62
65
  // Debounced save-time conflict probe. Detection uses the SAME `findConflicts`
63
- // logic the grid uses (overlap + shared owner/participant) against a freshly
66
+ // logic the grid uses (overlap + shared owner/participant/resource) against a freshly
64
67
  // fetched ±1-day window, so the editor warning is always consistent with the
65
68
  // conflict badges/rings the user sees on the calendar. The warning is
66
69
  // informational and never blocks saving.
@@ -72,9 +75,11 @@ export function useConflictProbe(
72
75
  draftOwnerUserId: string | null,
73
76
  scope: ConflictScope,
74
77
  currentUserId: string | null,
78
+ draftResources: ReadonlyArray<{ id: string; label: string }> = NO_DRAFT_RESOURCES,
75
79
  ): string | null {
76
80
  const t = useT()
77
81
  const [conflict, setConflict] = React.useState<string | null>(null)
82
+ const resourcesKey = draftResources.map((resource) => resource.id).join(',')
78
83
  // Keys on the canonical actor key, not the raw userId — an external guest has
79
84
  // no userId, so a userId-only key never changes when guests are added or
80
85
  // removed and the probe would keep reporting the previous attendee set.
@@ -93,23 +98,26 @@ export function useConflictProbe(
93
98
  let start: Date
94
99
  let end: Date
95
100
  if (isAllDay) {
96
- const dayDate = new Date(`${form.date}T00:00:00`)
97
- if (Number.isNaN(dayDate.getTime())) {
101
+ const dayDate = form.timezone ? calendarDayStartInstant(form.date, form.timezone) : new Date(`${form.date}T00:00:00`)
102
+ if (!dayDate || Number.isNaN(dayDate.getTime())) {
98
103
  setConflict(null)
99
104
  return
100
105
  }
101
- start = startOfDay(dayDate)
102
- end = endOfDay(dayDate)
106
+ start = form.timezone ? dayDate : startOfDay(dayDate)
107
+ const dayEnd = form.timezone ? calendarDayEndInstant(form.date, form.timezone) : endOfDay(dayDate)
108
+ if (!dayEnd) { setConflict(null); return }
109
+ end = dayEnd
103
110
  } else {
104
111
  if (!form.startTime) {
105
112
  setConflict(null)
106
113
  return
107
114
  }
108
- start = new Date(`${form.date}T${form.startTime}:00`)
109
- if (Number.isNaN(start.getTime())) {
115
+ const draftStart = form.timezone ? calendarWallTimeToInstant(form.date, form.startTime, form.timezone) : new Date(`${form.date}T${form.startTime}:00`)
116
+ if (!draftStart || Number.isNaN(draftStart.getTime())) {
110
117
  setConflict(null)
111
118
  return
112
119
  }
120
+ start = draftStart
113
121
  const durationMinutes = config.hasEnd ? computeDurationMinutes(form) ?? 30 : 30
114
122
  end = new Date(start.getTime() + durationMinutes * 60_000)
115
123
  }
@@ -134,25 +142,33 @@ export function useConflictProbe(
134
142
  if (!item) continue
135
143
  others.push(...expandOccurrences(item, fetchWindow))
136
144
  }
137
- const conflictItems = findEditorConflictItems(
145
+ const conflicts = findEditorConflicts(
138
146
  {
139
147
  start,
140
148
  end,
141
149
  ownerUserId: draftOwnerUserId,
142
150
  participants: form.participants.map((participant) => ({ userId: participant.userId, name: participant.name })),
143
151
  status: form.status,
152
+ resources: [...draftResources],
153
+ ownerLabel: draftOwnerUserId && draftOwnerUserId === form.assigneeUserId ? form.assigneeName : null,
144
154
  },
145
155
  others,
146
156
  excludeId,
147
157
  { scope, currentUserId },
148
158
  )
149
159
  if (!active) return
150
- if (conflictItems.length === 0) {
160
+ if (conflicts.length === 0) {
151
161
  setConflict(null)
152
162
  return
153
163
  }
154
- const summary = conflictItems
155
- .map((item) => `${formatClock(item.start)}–${formatClock(item.end)}: ${item.title || t('customers.calendar.grid.untitled', 'Untitled')}`)
164
+ // Name who and what is double-booked: an overlap alone does not tell the
165
+ // user which attendee or room to change.
166
+ const summary = conflicts
167
+ .map(({ item, shared }) => {
168
+ const names = shared.map((subject) => subject.label).filter(Boolean)
169
+ const entry = `${formatClock(item.start)}–${formatClock(item.end)}: ${item.title || t('customers.calendar.grid.untitled', 'Untitled')}`
170
+ return names.length ? `${entry} (${names.join(', ')})` : entry
171
+ })
156
172
  .join(', ')
157
173
  setConflict(summary ? t('customers.calendar.editor.conflictWarning', 'Overlaps with: {items}', { items: summary }) : null)
158
174
  } catch {
@@ -165,6 +181,6 @@ export function useConflictProbe(
165
181
  controller.abort()
166
182
  }
167
183
  // Re-probe when schedule-relevant inputs change (time window, all-day, attendees, owner, status, scope).
168
- }, [open, form.date, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]) // eslint-disable-line react-hooks/exhaustive-deps
184
+ }, [open, form.date, form.timezone, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, resourcesKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]) // eslint-disable-line react-hooks/exhaustive-deps
169
185
  return conflict
170
186
  }
@@ -69,12 +69,13 @@ export function UppercaseBadge({ style, className, children }: { style?: React.C
69
69
  )
70
70
  }
71
71
 
72
- export function AllDayToggle({ checked, onCheckedChange, label }: { checked: boolean; onCheckedChange(next: boolean): void; label: string }) {
72
+ export function AllDayToggle({ checked, onCheckedChange, label, disabled }: { checked: boolean; onCheckedChange(next: boolean): void; label: string; disabled?: boolean }) {
73
73
  return (
74
74
  <Switch
75
75
  checked={checked}
76
76
  aria-label={label}
77
77
  onCheckedChange={onCheckedChange}
78
+ disabled={disabled}
78
79
  className="h-6 w-10"
79
80
  />
80
81
  )
@@ -112,11 +113,13 @@ export function DateControl({
112
113
  onChange,
113
114
  ariaLabel,
114
115
  className,
116
+ disabled,
115
117
  }: {
116
118
  value: string
117
119
  onChange(next: string): void
118
120
  ariaLabel: string
119
121
  className?: string
122
+ disabled?: boolean
120
123
  }) {
121
124
  const [open, setOpen] = React.useState(false)
122
125
  useCloseOnEditorScroll(setOpen)
@@ -128,6 +131,7 @@ export function DateControl({
128
131
  open={open}
129
132
  onOpenChange={setOpen}
130
133
  aria-label={ariaLabel}
134
+ disabled={disabled}
131
135
  className={cn('w-full', className)}
132
136
  />
133
137
  )
@@ -140,13 +144,18 @@ const TIME_OPTIONS: string[] = Array.from({ length: 48 }, (_, index) => {
140
144
  return `${String(hour).padStart(2, '0')}:${minute}`
141
145
  })
142
146
 
143
- export function TimeControl({ value, onChange, ariaLabel }: { value: string; onChange(next: string): void; ariaLabel: string }) {
147
+ export function TimeControl({ value, onChange, ariaLabel, disabled }: { value: string; onChange(next: string): void; ariaLabel: string; disabled?: boolean }) {
144
148
  const [open, setOpen] = React.useState(false)
145
149
  useCloseOnEditorScroll(setOpen)
146
- // Keep an off-grid value (e.g. an imported 22:15) selectable.
147
- const options = value && !TIME_OPTIONS.includes(value) ? [value, ...TIME_OPTIONS] : TIME_OPTIONS
150
+ // Radix renders every item even while the list is closed, so a closed picker
151
+ // mounts only the selected time and the full grid is built on open. An
152
+ // off-grid value (e.g. an imported 22:15) stays selectable.
153
+ const options = React.useMemo(() => {
154
+ if (!open) return value ? [value] : []
155
+ return value && !TIME_OPTIONS.includes(value) ? [value, ...TIME_OPTIONS] : TIME_OPTIONS
156
+ }, [open, value])
148
157
  return (
149
- <Select value={value} onValueChange={onChange} open={open} onOpenChange={setOpen}>
158
+ <Select value={value} onValueChange={onChange} open={open} onOpenChange={setOpen} disabled={disabled}>
150
159
  <SelectTrigger aria-label={ariaLabel} className="h-9 w-32 shrink-0">
151
160
  <SelectValue />
152
161
  </SelectTrigger>
@@ -165,12 +174,14 @@ export function PersonChip({
165
174
  compact,
166
175
  onRemove,
167
176
  removeLabel,
177
+ disabled,
168
178
  }: {
169
179
  name: string
170
180
  badge?: React.ReactNode
171
181
  compact?: boolean
172
182
  onRemove?: () => void
173
183
  removeLabel?: string
184
+ disabled?: boolean
174
185
  }) {
175
186
  return (
176
187
  <span
@@ -188,6 +199,7 @@ export function PersonChip({
188
199
  size="xs"
189
200
  onClick={(event) => { event.stopPropagation(); onRemove() }}
190
201
  aria-label={removeLabel}
202
+ disabled={disabled}
191
203
  className="size-5 shrink-0"
192
204
  >
193
205
  <Plus aria-hidden className="size-3.5 rotate-45 opacity-50" />
@@ -0,0 +1,49 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { buildFormFieldsFromCustomFields, fetchCustomFieldFormStructure } from '@open-mercato/ui/backend/utils/customFieldForms'
5
+ import { filterCustomFieldDefs, type CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'
6
+ import { validateValuesAgainstDefs } from '@open-mercato/shared/modules/entities/validation'
7
+
8
+ export function selectCalendarCustomFields(definitions: CustomFieldDefDto[], fieldsetIds: readonly string[]) {
9
+ if (!fieldsetIds.length) return filterCustomFieldDefs(definitions, 'form')
10
+ const allowed = new Set(fieldsetIds)
11
+ return filterCustomFieldDefs(definitions, 'form').filter((definition) => {
12
+ const fieldsets = definition.fieldsets?.length ? definition.fieldsets : definition.fieldset ? [definition.fieldset] : []
13
+ return fieldsets.length ? fieldsets.some((fieldset) => allowed.has(fieldset)) : allowed.has('__general__')
14
+ })
15
+ }
16
+
17
+ export function projectCalendarCustomValues(values: Record<string, unknown>, definitions: CustomFieldDefDto[]) {
18
+ const activeIds = new Set(definitions.map((definition) => `cf_${definition.key}`))
19
+ return Object.fromEntries(Object.entries(values).filter(([key]) => !key.startsWith('cf_') || activeIds.has(key)))
20
+ }
21
+
22
+ export function validateCalendarCustomValues(values: Record<string, unknown>, definitions: CustomFieldDefDto[]) {
23
+ const customValues = Object.fromEntries(definitions.map((definition) => [definition.key, values[`cf_${definition.key}`]]))
24
+ return validateValuesAgainstDefs(customValues, definitions.map((definition) => ({
25
+ key: definition.key,
26
+ kind: definition.kind,
27
+ configJson: { validation: definition.validation ?? [] },
28
+ })))
29
+ }
30
+
31
+ export function useCalendarCustomFields(open: boolean, entityId: string, fieldsetIds: readonly string[]) {
32
+ const [state, setState] = React.useState<{ definitions: CustomFieldDefDto[]; status: 'loading' | 'ready' | 'error' }>({
33
+ definitions: [], status: 'loading',
34
+ })
35
+ React.useEffect(() => {
36
+ if (!open) return
37
+ let active = true
38
+ setState({ definitions: [], status: 'loading' })
39
+ void fetchCustomFieldFormStructure([entityId]).then(({ definitions }) => {
40
+ if (active) setState({ definitions, status: 'ready' })
41
+ }).catch(() => {
42
+ if (active) setState({ definitions: [], status: 'error' })
43
+ })
44
+ return () => { active = false }
45
+ }, [open, entityId])
46
+ const definitions = React.useMemo(() => selectCalendarCustomFields(state.definitions, fieldsetIds), [state.definitions, fieldsetIds])
47
+ const fields = React.useMemo(() => buildFormFieldsFromCustomFields(definitions), [definitions])
48
+ return { definitions, allDefinitions: state.definitions, fields, status: state.status }
49
+ }
@@ -0,0 +1,100 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
5
+ import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
6
+ import { calendarEventTypes, type EffectiveCalendarEventType } from '../../../calendar-event-types'
7
+ import type { CalendarEventTypeCatalog, ScopedCalendarEventType } from '../../../lib/calendar/eventTypeResolver'
8
+ import { KIND_CONFIG, type EditorKindConfig, type EditorTypeOption } from '../../../lib/calendar/editorPayload'
9
+
10
+ type CatalogState =
11
+ | { status: 'loading' | 'error'; items: readonly ScopedCalendarEventType[] }
12
+ | { status: 'ready'; items: readonly ScopedCalendarEventType[] }
13
+
14
+ export function useEventTypeCatalog(open: boolean) {
15
+ const [state, setState] = React.useState<CatalogState>({ status: 'loading', items: [] })
16
+ const [attempt, setAttempt] = React.useState(0)
17
+
18
+ React.useEffect(() => {
19
+ if (!open) {
20
+ setState({ status: 'loading', items: [] })
21
+ return
22
+ }
23
+ let active = true
24
+ setState({ status: 'loading', items: [] })
25
+ void readApiResultOrThrow<CalendarEventTypeCatalog>('/api/customers/activity-types')
26
+ .then((catalog) => {
27
+ if (!Array.isArray(catalog.items)) throw new Error('[internal] Invalid activity type catalog')
28
+ if (active) setState({ status: 'ready', items: catalog.items })
29
+ })
30
+ .catch((error) => {
31
+ if (!active) return
32
+ getTelemetryRuntime()?.reportError(error, {
33
+ module: 'customers',
34
+ code: 'customers.activity_type_catalog_load_failed',
35
+ })
36
+ setState({ status: 'error', items: [] })
37
+ })
38
+ return () => { active = false }
39
+ }, [open, attempt])
40
+
41
+ const retry = React.useCallback(() => setAttempt((value) => value + 1), [])
42
+
43
+ return { ...state, retry }
44
+ }
45
+
46
+ export function selectedEventType(
47
+ items: readonly ScopedCalendarEventType[],
48
+ key: string,
49
+ ): EffectiveCalendarEventType {
50
+ const normalizedKey = key.trim().toLowerCase()
51
+ const selected = items.find((item) => item.key === normalizedKey)
52
+ if (selected) return selected
53
+ const baseline = calendarEventTypes.find((item) => item.key === normalizedKey) ?? calendarEventTypes[0]!
54
+ return {
55
+ ...baseline,
56
+ key,
57
+ label: key,
58
+ provenance: {},
59
+ historical: true,
60
+ fallbackReason: 'unknown',
61
+ }
62
+ }
63
+
64
+ export function isSelectableEventType(items: readonly ScopedCalendarEventType[], key: string): boolean {
65
+ const normalizedKey = key.trim().toLowerCase()
66
+ return items.some((item) => item.key === normalizedKey && item.selectable && !item.historical)
67
+ }
68
+
69
+ export function eventTypeOptions(
70
+ items: readonly ScopedCalendarEventType[],
71
+ selectedKey: string,
72
+ translate: (key: string, fallback: string) => string,
73
+ ): EditorTypeOption[] {
74
+ const displayLabel = (item: ScopedCalendarEventType) => item.labelKey
75
+ ? translate(item.labelKey, item.label)
76
+ : item.label
77
+ const normalizedSelectedKey = selectedKey.trim().toLowerCase()
78
+ const options = items
79
+ .filter((item) => item.selectable && !item.historical)
80
+ .sort((left, right) => left.behavior.order - right.behavior.order)
81
+ .map((item) => ({ value: item.key === normalizedSelectedKey ? selectedKey : item.key, label: displayLabel(item), icon: item.icon ?? null }))
82
+ if (!options.some((option) => option.value === selectedKey)) {
83
+ const current = items.find((item) => item.key === normalizedSelectedKey)
84
+ options.unshift({ value: selectedKey, label: current ? displayLabel(current) : selectedKey, icon: current?.icon ?? null })
85
+ }
86
+ return options
87
+ }
88
+
89
+ export function eventTypeConfig(definition: EffectiveCalendarEventType): EditorKindConfig {
90
+ const { baseKind, fields } = definition.behavior
91
+ return {
92
+ dateLabel: KIND_CONFIG[baseKind].dateLabel,
93
+ hasEnd: fields.endTime,
94
+ hasAllDay: fields.allDay,
95
+ hasRepeat: fields.recurrence,
96
+ location: fields.location === 'none' ? null : fields.location,
97
+ people: fields.people === 'none' ? null : fields.people === 'recipients' ? 'to' : fields.people,
98
+ hasPriority: fields.priority,
99
+ }
100
+ }
@@ -33,6 +33,7 @@ export const calendarInteractionPayloadSchema = z
33
33
  scheduledAt: z.string().nullable().optional(),
34
34
  occurredAt: z.string().nullable().optional(),
35
35
  durationMinutes: z.number().nullable().optional(),
36
+ timezone: z.string().nullable().optional(),
36
37
  allDay: z.boolean().nullable().optional(),
37
38
  location: z.string().nullable().optional(),
38
39
  participants: z.array(calendarParticipantSchema).nullable().optional(),