@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
@@ -1,65 +1,43 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
- import { Info, X } from 'lucide-react'
4
+ import { X } from 'lucide-react'
5
5
  import { VisuallyHidden } from '@radix-ui/react-visually-hidden'
6
6
  import { useT } from '@open-mercato/shared/lib/i18n/context'
7
7
  import { Button } from '@open-mercato/ui/primitives/button'
8
8
  import { Dialog, DialogContent, DialogTitle } from '@open-mercato/ui/primitives/dialog'
9
9
  import { IconButton } from '@open-mercato/ui/primitives/icon-button'
10
10
  import { Switch } from '@open-mercato/ui/primitives/switch'
11
- import { TagInput } from '@open-mercato/ui/primitives/tag-input'
12
- import { SimpleTooltip } from '@open-mercato/ui/primitives/tooltip'
13
11
  import { useDialogKeyHandler } from '@open-mercato/ui/hooks/useDialogKeyHandler'
14
- import {
15
- CalendarPreferences,
16
- ConflictScope,
17
- MAX_ACTIVITY_TYPES,
18
- MAX_EVENT_CATEGORIES,
19
- } from '../../lib/calendar/preferences'
12
+ import { CalendarPreferences, ConflictScope } from '../../lib/calendar/preferences'
13
+ import { getCustomerDictionaryManageHref } from '../../lib/dictionaries'
20
14
  import { SegmentGroup } from './editor/SegmentGroup'
21
15
 
22
16
  export type CalendarSettingsModalProps = {
23
17
  open: boolean
24
18
  preferences: CalendarPreferences
25
- seedActivityTypes: string[]
19
+ /** @deprecated Activity types are loaded from the scoped catalog. */
20
+ seedActivityTypes?: string[]
26
21
  onOpenChange(open: boolean): void
27
22
  onSave(next: CalendarPreferences): void
28
23
  }
29
24
 
30
25
  type ToggleKey = 'showCrmActivities' | 'aiSummaries' | 'conflictWarnings' | 'showWeekends'
31
26
 
32
- // An empty Activity Types list is an intentional floor meaning "surface all
33
- // dictionary types" rather than "surface none". When the stored list is empty
34
- // the modal seeds the dictionary types for display. NOTE: since the editor's
35
- // Category quick-pick was removed (owner feedback, #3552) these lists no longer
36
- // affect the event editor — the type switcher always shows the full dictionary.
37
- function buildDraft(preferences: CalendarPreferences, seedActivityTypes: string[]): CalendarPreferences {
38
- return {
39
- ...preferences,
40
- eventCategories: [...preferences.eventCategories],
41
- activityTypes:
42
- preferences.activityTypes.length > 0
43
- ? [...preferences.activityTypes]
44
- : seedActivityTypes.slice(0, MAX_ACTIVITY_TYPES),
45
- }
46
- }
47
-
48
27
  export function CalendarSettingsModal({
49
28
  open,
50
29
  preferences,
51
- seedActivityTypes,
52
30
  onOpenChange,
53
31
  onSave,
54
32
  }: CalendarSettingsModalProps) {
55
33
  const t = useT()
56
- const [draft, setDraft] = React.useState<CalendarPreferences>(() => buildDraft(preferences, seedActivityTypes))
34
+ const [draft, setDraft] = React.useState<CalendarPreferences>(preferences)
57
35
  const openRef = React.useRef(false)
58
36
 
59
37
  React.useEffect(() => {
60
- if (open && !openRef.current) setDraft(buildDraft(preferences, seedActivityTypes))
38
+ if (open && !openRef.current) setDraft(preferences)
61
39
  openRef.current = open
62
- }, [open, preferences, seedActivityTypes])
40
+ }, [open, preferences])
63
41
 
64
42
  const handleSave = React.useCallback(() => {
65
43
  onSave(draft)
@@ -78,11 +56,6 @@ export function CalendarSettingsModal({
78
56
  ]
79
57
 
80
58
  const title = t('customers.calendar.settings.title', 'Customization')
81
- const removeTagLabel = React.useCallback(
82
- (tag: string) => t('customers.calendar.settings.removeTag', 'Remove {tag}', { tag }),
83
- [t],
84
- )
85
-
86
59
  return (
87
60
  <Dialog open={open} onOpenChange={onOpenChange}>
88
61
  <DialogContent
@@ -113,32 +86,16 @@ export function CalendarSettingsModal({
113
86
  </div>
114
87
 
115
88
  <div className="flex flex-col gap-4 p-5">
116
- <SettingsTagInput
117
- label={t('customers.calendar.settings.eventCategories', 'Event Categories')}
118
- maxLabel={t('customers.calendar.settings.max', '(max. {count})', { count: MAX_EVENT_CATEGORIES })}
119
- hint={t(
120
- 'customers.calendar.settings.eventCategoriesHint',
121
- 'Your own grouping labels (e.g. Team Meeting, Sales Call). Offered when creating an event.',
122
- )}
123
- placeholder={t('customers.calendar.settings.addCategory', 'Add a category…')}
124
- value={draft.eventCategories}
125
- maxTags={MAX_EVENT_CATEGORIES}
126
- removeTagLabel={removeTagLabel}
127
- onChange={(eventCategories) => setDraft((current) => ({ ...current, eventCategories }))}
128
- />
129
- <SettingsTagInput
130
- label={t('customers.calendar.settings.activityTypes', 'Activity Types')}
131
- maxLabel={t('customers.calendar.settings.max', '(max. {count})', { count: MAX_ACTIVITY_TYPES })}
132
- hint={t(
133
- 'customers.calendar.settings.activityTypesHint',
134
- 'The activity types your calendar surfaces when creating an event. Seeded from your workspace dictionary.',
135
- )}
136
- placeholder={t('customers.calendar.settings.addType', 'Add a type…')}
137
- value={draft.activityTypes}
138
- maxTags={MAX_ACTIVITY_TYPES}
139
- removeTagLabel={removeTagLabel}
140
- onChange={(activityTypes) => setDraft((current) => ({ ...current, activityTypes }))}
141
- />
89
+ <div className="flex items-center justify-between gap-3">
90
+ <span className="text-sm text-foreground">
91
+ {t('customers.calendar.settings.activityTypes', 'Activity Types')}
92
+ </span>
93
+ <Button type="button" variant="outline" size="sm" asChild>
94
+ <a href={getCustomerDictionaryManageHref('activity-types')}>
95
+ {t('customers.calendar.settings.manageActivityTypes', 'Manage activity types')}
96
+ </a>
97
+ </Button>
98
+ </div>
142
99
  {toggleRows.map((row) => (
143
100
  <React.Fragment key={row.key}>
144
101
  <div className="flex items-center gap-2">
@@ -184,38 +141,3 @@ export function CalendarSettingsModal({
184
141
  </Dialog>
185
142
  )
186
143
  }
187
-
188
- type SettingsTagInputProps = {
189
- label: string
190
- maxLabel: string
191
- hint: string
192
- placeholder: string
193
- value: string[]
194
- maxTags: number
195
- removeTagLabel: (tag: string) => string
196
- onChange(value: string[]): void
197
- }
198
-
199
- function SettingsTagInput({ label, maxLabel, hint, placeholder, value, maxTags, removeTagLabel, onChange }: SettingsTagInputProps) {
200
- return (
201
- <div className="flex flex-col gap-1">
202
- <div className="flex items-center gap-1">
203
- <span className="text-sm font-medium leading-5 text-foreground">{label}</span>
204
- <span className="text-sm leading-5 text-muted-foreground">{maxLabel}</span>
205
- <SimpleTooltip content={hint}>
206
- <span className="inline-flex text-muted-foreground" tabIndex={0} role="img" aria-label={hint}>
207
- <Info aria-hidden className="size-4" />
208
- </span>
209
- </SimpleTooltip>
210
- </div>
211
- <TagInput
212
- value={value}
213
- onChange={onChange}
214
- placeholder={placeholder}
215
- maxTags={maxTags}
216
- aria-label={label}
217
- removeTagLabel={removeTagLabel}
218
- />
219
- </div>
220
- )
221
- }
@@ -175,6 +175,11 @@ export function TimeGrid({
175
175
  const anchorMs = anchor.getTime()
176
176
  const [selectedId, setSelectedId] = React.useState<string | null>(null)
177
177
  const [drag, setDrag] = React.useState<DragState | null>(null)
178
+ const dragRef = React.useRef<DragState | null>(null)
179
+ const updateDrag = (next: DragState | null) => {
180
+ dragRef.current = next
181
+ setDrag(next)
182
+ }
178
183
 
179
184
  const dayStarts = React.useMemo(() => {
180
185
  const rangeStart = getVisibleRange(days === 7 ? 'week' : 'day', new Date(anchorMs), 0).from
@@ -257,32 +262,29 @@ export function TimeGrid({
257
262
  // Pointer capture is best-effort; ignore environments that reject it.
258
263
  }
259
264
  setSelectedId(null)
260
- setDrag({ dayMs: dayStart.getTime(), startMin: minute, endMin: minute, moved: false })
265
+ updateDrag({ dayMs: dayStart.getTime(), startMin: minute, endMin: minute, moved: false })
261
266
  }
262
267
 
263
268
  const moveDrag = (event: React.PointerEvent<HTMLDivElement>) => {
264
- // Read the layer rect synchronously: `event.currentTarget` is only valid during
265
- // event dispatch, but the setDrag updater below runs later during re-render.
269
+ const previous = dragRef.current
270
+ if (!previous) return
266
271
  const rect = event.currentTarget.getBoundingClientRect()
267
272
  const minute = offsetYToMinutes(event.clientY - rect.top, HOUR_HEIGHT_PX)
268
- setDrag((previous) => {
269
- if (!previous) return previous
270
- return {
271
- ...previous,
272
- endMin: minute,
273
- moved: previous.moved || Math.abs(minute - previous.startMin) >= DRAG_SNAP_MINUTES,
274
- }
273
+ if (minute === previous.endMin) return
274
+ updateDrag({
275
+ ...previous,
276
+ endMin: minute,
277
+ moved: previous.moved || Math.abs(minute - previous.startMin) >= DRAG_SNAP_MINUTES,
275
278
  })
276
279
  }
277
280
 
278
281
  const endDrag = (dayStart: Date) => {
279
- setDrag((previous) => {
280
- if (previous && previous.moved && onCreateRange) {
281
- const range = buildDragRange(dayStart, previous.startMin, previous.endMin)
282
- onCreateRange(range.start, range.end)
283
- }
284
- return null
285
- })
282
+ const previous = dragRef.current
283
+ updateDrag(null)
284
+ if (previous && previous.moved && onCreateRange) {
285
+ const range = buildDragRange(dayStart, previous.startMin, previous.endMin)
286
+ onCreateRange(range.start, range.end)
287
+ }
286
288
  }
287
289
 
288
290
  return (
@@ -428,7 +430,7 @@ export function TimeGrid({
428
430
  onPointerDown={(event) => beginDrag(event, dayStart)}
429
431
  onPointerMove={moveDrag}
430
432
  onPointerUp={() => endDrag(dayStart)}
431
- onPointerCancel={() => setDrag(null)}
433
+ onPointerCancel={() => updateDrag(null)}
432
434
  aria-hidden
433
435
  />
434
436
  ) : null}
@@ -0,0 +1,230 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import { z } from 'zod'
5
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
6
+ import { registerComponent } from '@open-mercato/shared/modules/widgets/component-registry'
7
+ import { useRegisteredComponent } from '@open-mercato/ui/backend/injection/useRegisteredComponent'
8
+ import type { CalendarEventTypePanelProps, EffectiveCalendarEventType } from '../../../calendar-event-types'
9
+ import { defaultRepeatDaysForDateInput, type EditorFormState } from '../../../lib/calendar/editorPayload'
10
+ import { extensionPoints } from '../../../extension-points'
11
+ import { Field } from './inputs'
12
+ import { LocationField } from './LocationField'
13
+ import { PeopleField } from './PeopleField'
14
+ import { PriorityField } from './PriorityField'
15
+ import { RepeatField } from './RepeatField'
16
+ import { ResourcesField } from './ResourcesField'
17
+ import { ScheduleSection } from './ScheduleSection'
18
+ import { eventTypeConfig } from './useEventTypeCatalog'
19
+
20
+ const PEOPLE_FIELD_TEXT = {
21
+ attendees: { labelKey: 'customers.calendar.editor.attendees', label: 'Attendees', placeholderKey: 'customers.calendar.editor.addPeoplePlaceholder', placeholder: 'Add staff or customer…' },
22
+ participants: { labelKey: 'customers.calendar.editor.participants', label: 'Participants', placeholderKey: 'customers.calendar.editor.addPeoplePlaceholder', placeholder: 'Add staff or customer…' },
23
+ to: { labelKey: 'customers.calendar.editor.to', label: 'To', placeholderKey: 'customers.calendar.editor.addRecipientPlaceholder', placeholder: 'Add recipient…' },
24
+ } as const
25
+
26
+ const MemoScheduleSection = React.memo(ScheduleSection)
27
+ const MemoRepeatField = React.memo(RepeatField)
28
+ const MemoPeopleField = React.memo(PeopleField)
29
+ const MemoLocationField = React.memo(LocationField)
30
+ const MemoResourcesField = React.memo(ResourcesField)
31
+ const MemoPriorityField = React.memo(PriorityField)
32
+
33
+ export function DefaultEventTypePanel({ definition, values, errors, disabled, capabilities, setValue }: CalendarEventTypePanelProps) {
34
+ const t = useT()
35
+ const form = values as unknown as EditorFormState
36
+ const config = eventTypeConfig(definition)
37
+ // The handlers read the latest draft through a ref so their identities depend
38
+ // only on `setValue`. Every field below is memoized, so a keystroke in an
39
+ // unrelated field (title, description) re-renders none of them.
40
+ const formRef = React.useRef(form)
41
+ formRef.current = form
42
+ const handlers = React.useMemo(() => {
43
+ const update = (patch: Partial<EditorFormState>) => {
44
+ for (const [key, value] of Object.entries(patch)) setValue(key, value)
45
+ }
46
+ return {
47
+ allDay: (allDay: boolean) => update({ allDay }),
48
+ date: (date: string) => {
49
+ const current = formRef.current
50
+ const untouchedDefault = JSON.stringify(current.repeatDays) === JSON.stringify(defaultRepeatDaysForDateInput(current.date))
51
+ update(untouchedDefault ? { date, repeatDays: defaultRepeatDaysForDateInput(date) } : { date })
52
+ },
53
+ startTime: (startTime: string) => update({ startTime }),
54
+ endDate: (endDate: string) => update({ endDate }),
55
+ endTime: (endTime: string) => update({ endTime }),
56
+ repeatFreq: (repeatFreq: EditorFormState['repeatFreq']) => update(repeatFreq === 'weekly'
57
+ ? { repeatFreq, repeatDays: defaultRepeatDaysForDateInput(formRef.current.date) }
58
+ : { repeatFreq }),
59
+ repeatDay: (index: number) => update({
60
+ repeatDays: formRef.current.repeatDays.map((active, dayIndex) => dayIndex === index ? !active : active),
61
+ }),
62
+ repeatEndType: (repeatEndType: EditorFormState['repeatEndType']) => update({ repeatEndType }),
63
+ repeatCount: (repeatCount: number) => update({ repeatCount }),
64
+ repeatUntilDate: (repeatUntilDate: string) => update({ repeatUntilDate }),
65
+ participants: (participants: EditorFormState['participants']) => update({ participants }),
66
+ assignee: (entries: EditorFormState['participants']) => {
67
+ const next = entries[entries.length - 1] ?? null
68
+ update({ assigneeUserId: next?.userId ?? null, assigneeName: next?.name ?? null })
69
+ },
70
+ location: (location: string) => update({ location }),
71
+ resources: (resources: EditorFormState['resources']) => update({ resources }),
72
+ priority: (priority: EditorFormState['priority']) => update({ priority }),
73
+ }
74
+ }, [setValue])
75
+ const phoneContactIds = React.useMemo(() => {
76
+ const ids: string[] = []
77
+ if (form.relatedTo && form.relatedTo.kind !== 'company') ids.push(form.relatedTo.id)
78
+ for (const participant of form.participants) {
79
+ if (participant.isCustomer && participant.userId) ids.push(participant.userId)
80
+ }
81
+ return Array.from(new Set(ids))
82
+ }, [form.relatedTo, form.participants])
83
+ const assigneeValue = React.useMemo(
84
+ () => form.assigneeUserId
85
+ ? [{ userId: form.assigneeUserId, name: form.assigneeName ?? form.assigneeUserId, isCustomer: false }]
86
+ : [],
87
+ [form.assigneeUserId, form.assigneeName],
88
+ )
89
+ const priorityLabels = React.useMemo(() => ({
90
+ low: t('customers.calendar.editor.priority.low', 'Low'),
91
+ medium: t('customers.calendar.editor.priority.medium', 'Medium'),
92
+ high: t('customers.calendar.editor.priority.high', 'High'),
93
+ }), [t])
94
+
95
+ return (
96
+ <fieldset disabled={disabled} className="grid min-w-0 grid-cols-1 items-start gap-4 border-0 p-0 lg:grid-cols-2 lg:gap-x-6">
97
+ <div className="flex w-full flex-col gap-4">
98
+ <MemoScheduleSection
99
+ dateLabel={config.dateLabel}
100
+ hasAllDay={config.hasAllDay}
101
+ hasEnd={config.hasEnd}
102
+ allDay={form.allDay}
103
+ date={form.date}
104
+ startTime={form.startTime}
105
+ endDate={form.endDate}
106
+ endTime={form.endTime}
107
+ startError={errors.startTime ?? errors.date}
108
+ endsError={errors.ends}
109
+ disabled={disabled}
110
+ onAllDayChange={handlers.allDay}
111
+ onDateChange={handlers.date}
112
+ onStartTimeChange={handlers.startTime}
113
+ onEndDateChange={handlers.endDate}
114
+ onEndTimeChange={handlers.endTime}
115
+ />
116
+ {config.hasRepeat ? (
117
+ <MemoRepeatField
118
+ freq={form.repeatFreq}
119
+ days={form.repeatDays}
120
+ endType={form.repeatEndType}
121
+ count={form.repeatCount}
122
+ untilDate={form.repeatUntilDate}
123
+ untilError={errors.repeatUntilDate}
124
+ disabled={disabled}
125
+ onFreqChange={handlers.repeatFreq}
126
+ onToggleDay={handlers.repeatDay}
127
+ onEndTypeChange={handlers.repeatEndType}
128
+ onCountChange={handlers.repeatCount}
129
+ onUntilDateChange={handlers.repeatUntilDate}
130
+ />
131
+ ) : null}
132
+ {config.people && config.people !== 'assignee' ? (
133
+ <Field label={t(PEOPLE_FIELD_TEXT[config.people].labelKey, PEOPLE_FIELD_TEXT[config.people].label)}>
134
+ <MemoPeopleField
135
+ mode="multi"
136
+ includeCustomers
137
+ includeStaff={capabilities.staffEnabled}
138
+ placeholder={t(PEOPLE_FIELD_TEXT[config.people].placeholderKey, PEOPLE_FIELD_TEXT[config.people].placeholder)}
139
+ ariaLabel={t(PEOPLE_FIELD_TEXT[config.people].labelKey, PEOPLE_FIELD_TEXT[config.people].label)}
140
+ value={form.participants}
141
+ onChange={handlers.participants}
142
+ disabled={disabled}
143
+ />
144
+ </Field>
145
+ ) : null}
146
+ {config.people === 'assignee' && capabilities.staffEnabled ? (
147
+ <Field label={t('customers.calendar.editor.assignee', 'Assignee')} error={errors.assignee}>
148
+ <MemoPeopleField
149
+ mode="single"
150
+ includeCustomers={false}
151
+ includeStaff
152
+ placeholder={t('customers.calendar.editor.assigneePlaceholder', 'Assign to a team member…')}
153
+ ariaLabel={t('customers.calendar.editor.assignee', 'Assignee')}
154
+ value={assigneeValue}
155
+ onChange={handlers.assignee}
156
+ disabled={disabled}
157
+ />
158
+ </Field>
159
+ ) : null}
160
+ </div>
161
+ <div className="flex w-full flex-col gap-4">
162
+ {config.location ? (
163
+ <MemoLocationField
164
+ variant={config.location}
165
+ value={form.location}
166
+ onChange={handlers.location}
167
+ phoneContactIds={phoneContactIds}
168
+ disabled={disabled}
169
+ />
170
+ ) : null}
171
+ {capabilities.resourcesEnabled && definition.behavior.fields.resources ? (
172
+ <Field label={t('customers.calendar.editor.resources', 'Resources')}>
173
+ <MemoResourcesField
174
+ placeholder={t('customers.calendar.editor.resourcesPlaceholder', 'Add a resource…')}
175
+ ariaLabel={t('customers.calendar.editor.resources', 'Resources')}
176
+ value={form.resources}
177
+ onChange={handlers.resources}
178
+ disabled={disabled}
179
+ />
180
+ </Field>
181
+ ) : null}
182
+ {config.hasPriority ? (
183
+ <Field label={t('customers.calendar.editor.priority.label', 'Priority')}>
184
+ <MemoPriorityField
185
+ value={form.priority}
186
+ ariaLabel={t('customers.calendar.editor.priority.label', 'Priority')}
187
+ labels={priorityLabels}
188
+ onChange={handlers.priority}
189
+ disabled={disabled}
190
+ />
191
+ </Field>
192
+ ) : null}
193
+ </div>
194
+ </fieldset>
195
+ )
196
+ }
197
+
198
+ const callbackSchema = z.custom<CalendarEventTypePanelProps['setValue']>(
199
+ (value) => typeof value === 'function',
200
+ { message: '[internal] expected a field setter' },
201
+ )
202
+
203
+ const eventTypePanelPropsSchema: z.ZodType<CalendarEventTypePanelProps> = z.object({
204
+ definition: z.custom<EffectiveCalendarEventType>((value) => value !== null && typeof value === 'object'),
205
+ panelKey: z.string().optional(),
206
+ mode: z.enum(['create', 'edit']),
207
+ values: z.record(z.string(), z.unknown()),
208
+ errors: z.record(z.string(), z.string().optional()),
209
+ disabled: z.boolean(),
210
+ capabilities: z.object({ resourcesEnabled: z.boolean(), staffEnabled: z.boolean() }),
211
+ setValue: callbackSchema,
212
+ })
213
+
214
+ registerComponent<CalendarEventTypePanelProps>({
215
+ id: extensionPoints.hosts.calendarEventTypePanel.componentId,
216
+ component: DefaultEventTypePanel,
217
+ metadata: {
218
+ module: 'customers',
219
+ description: 'Calendar event editor type-specific panel.',
220
+ propsSchema: eventTypePanelPropsSchema,
221
+ },
222
+ })
223
+
224
+ export function EventTypePanel(props: CalendarEventTypePanelProps) {
225
+ const Resolved = useRegisteredComponent<CalendarEventTypePanelProps>(
226
+ extensionPoints.hosts.calendarEventTypePanel.componentId,
227
+ DefaultEventTypePanel,
228
+ )
229
+ return <Resolved {...props} />
230
+ }
@@ -14,9 +14,11 @@ import { fetchPeoplePhones, type ContactPhone } from './lookups'
14
14
  function PhoneSuggestions({
15
15
  contactIds,
16
16
  onPick,
17
+ disabled,
17
18
  }: {
18
19
  contactIds: string[]
19
20
  onPick(phone: string): void
21
+ disabled?: boolean
20
22
  }) {
21
23
  const t = useT()
22
24
  const [contacts, setContacts] = React.useState<ContactPhone[]>([])
@@ -47,6 +49,7 @@ function PhoneSuggestions({
47
49
  key={contact.id}
48
50
  type="button"
49
51
  onClick={() => onPick(contact.phone)}
52
+ disabled={disabled}
50
53
  title={t('customers.calendar.editor.phoneSuggestion.use', 'Use {name}’s number', { name: contact.name })}
51
54
  className="inline-flex max-w-full items-center gap-1.5 rounded-md border border-border bg-background px-2 py-1 text-xs text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
52
55
  >
@@ -65,12 +68,14 @@ export function LocationField({
65
68
  value,
66
69
  onChange,
67
70
  phoneContactIds,
71
+ disabled,
68
72
  }: {
69
73
  variant: 'location' | 'phoneLink'
70
74
  value: string
71
75
  onChange(next: string): void
72
76
  /** Person ids whose phone can be suggested (Call variant only). */
73
77
  phoneContactIds?: string[]
78
+ disabled?: boolean
74
79
  }) {
75
80
  const t = useT()
76
81
  const label = variant === 'phoneLink'
@@ -89,10 +94,11 @@ export function LocationField({
89
94
  onChange={(event) => onChange(event.target.value)}
90
95
  placeholder={placeholder}
91
96
  aria-label={label}
97
+ disabled={disabled}
92
98
  leftIcon={variant === 'phoneLink' ? <Phone /> : <MapPin />}
93
99
  />
94
100
  {showSuggestions ? (
95
- <PhoneSuggestions contactIds={phoneContactIds ?? []} onPick={(phone) => onChange(phone)} />
101
+ <PhoneSuggestions contactIds={phoneContactIds ?? []} onPick={(phone) => onChange(phone)} disabled={disabled} />
96
102
  ) : null}
97
103
  </Field>
98
104
  )
@@ -30,6 +30,7 @@ export function PeopleField({
30
30
  onChange,
31
31
  includeCustomers,
32
32
  includeStaff = true,
33
+ disabled,
33
34
  }: {
34
35
  mode: 'multi' | 'single'
35
36
  placeholder: string
@@ -39,6 +40,7 @@ export function PeopleField({
39
40
  includeCustomers: boolean
40
41
  /** Pass false when the staff module is not loaded — customer-only options. */
41
42
  includeStaff?: boolean
43
+ disabled?: boolean
42
44
  }) {
43
45
  const t = useT()
44
46
  const activeOrgId = useCurrentOrganization()?.id ?? null
@@ -134,6 +136,7 @@ export function PeopleField({
134
136
  badge={participant.isCustomer ? customerBadge : participant.userId ? undefined : guestBadge}
135
137
  onRemove={() => onChange(value.filter((_, entryIndex) => entryIndex !== index))}
136
138
  removeLabel={t('customers.calendar.editor.removePerson', 'Remove {name}', { name: participant.name })}
139
+ disabled={disabled}
137
140
  />
138
141
  ))}
139
142
  <Input
@@ -145,6 +148,7 @@ export function PeopleField({
145
148
  aria-label={ariaLabel}
146
149
  role="combobox"
147
150
  aria-expanded={open}
151
+ disabled={disabled}
148
152
  className="min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none"
149
153
  inputClassName="text-sm text-foreground placeholder:text-muted-foreground"
150
154
  />
@@ -23,11 +23,13 @@ export function PriorityField({
23
23
  labels,
24
24
  ariaLabel,
25
25
  onChange,
26
+ disabled,
26
27
  }: {
27
28
  value: EditorPriority
28
29
  labels: Record<EditorPriority, string>
29
30
  ariaLabel: string
30
31
  onChange(next: EditorPriority): void
32
+ disabled?: boolean
31
33
  }) {
32
34
  const [open, setOpen] = React.useState(false)
33
35
  const close = React.useCallback(() => setOpen(false), [])
@@ -49,6 +51,7 @@ export function PriorityField({
49
51
  aria-haspopup="listbox"
50
52
  aria-expanded={open}
51
53
  aria-label={ariaLabel}
54
+ disabled={disabled}
52
55
  onClick={() => setOpen((previous) => !previous)}
53
56
  className={cn('h-9 w-full justify-between bg-background px-3 font-normal shadow-none', CONTROL_BORDER)}
54
57
  >
@@ -26,6 +26,8 @@ export function RepeatField({
26
26
  endType,
27
27
  count,
28
28
  untilDate,
29
+ untilError,
30
+ disabled,
29
31
  onFreqChange,
30
32
  onToggleDay,
31
33
  onEndTypeChange,
@@ -37,6 +39,8 @@ export function RepeatField({
37
39
  endType: EditorRepeatEndType
38
40
  count: number
39
41
  untilDate: string
42
+ untilError?: string | null
43
+ disabled?: boolean
40
44
  onFreqChange(next: EditorRepeatFreq): void
41
45
  onToggleDay(index: number): void
42
46
  onEndTypeChange(next: EditorRepeatEndType): void
@@ -51,6 +55,7 @@ export function RepeatField({
51
55
  <Select
52
56
  value={freq}
53
57
  onValueChange={(value) => onFreqChange(value as EditorRepeatFreq)}
58
+ disabled={disabled}
54
59
  >
55
60
  <SelectTrigger aria-label={freqLabel}>
56
61
  <SelectValue />
@@ -72,6 +77,7 @@ export function RepeatField({
72
77
  variant={isActive ? 'default' : 'outline'}
73
78
  aria-pressed={isActive}
74
79
  aria-label={t(day.ariaKey, day.ariaFallback)}
80
+ disabled={disabled}
75
81
  onClick={() => onToggleDay(index)}
76
82
  className={cn(
77
83
  'size-9 px-0 text-xs font-medium',
@@ -91,6 +97,7 @@ export function RepeatField({
91
97
  ariaLabel={t('customers.calendar.editor.repeat.ends', 'Ends')}
92
98
  value={endType}
93
99
  onChange={onEndTypeChange}
100
+ disabled={disabled}
94
101
  options={[
95
102
  { value: 'never', label: t('customers.calendar.editor.repeat.never', 'Never') },
96
103
  { value: 'date', label: t('customers.calendar.editor.repeat.onDate', 'On date') },
@@ -103,8 +110,10 @@ export function RepeatField({
103
110
  value={untilDate || ''}
104
111
  onChange={onUntilDateChange}
105
112
  ariaLabel={t('customers.calendar.editor.repeat.onDate', 'On date')}
113
+ disabled={disabled}
106
114
  />
107
115
  ) : null}
116
+ {endType === 'date' && untilError ? <p className="w-full text-xs text-status-error-text">{untilError}</p> : null}
108
117
  {endType === 'count' ? (
109
118
  <span className="flex items-center gap-2">
110
119
  <Input
@@ -117,6 +126,7 @@ export function RepeatField({
117
126
  if (Number.isInteger(next) && next >= 1) onCountChange(next)
118
127
  }}
119
128
  aria-label={t('customers.calendar.editor.repeat.after', 'After')}
129
+ disabled={disabled}
120
130
  className="h-9 w-20"
121
131
  />
122
132
  <span className={LABEL_CLASS}>{t('customers.calendar.editor.repeat.times', 'times')}</span>
@@ -25,11 +25,13 @@ export function ResourcesField({
25
25
  ariaLabel,
26
26
  value,
27
27
  onChange,
28
+ disabled,
28
29
  }: {
29
30
  placeholder: string
30
31
  ariaLabel: string
31
32
  value: EditorResource[]
32
33
  onChange(next: EditorResource[]): void
34
+ disabled?: boolean
33
35
  }) {
34
36
  const t = useT()
35
37
  const [query, setQuery] = React.useState('')
@@ -128,6 +130,7 @@ export function ResourcesField({
128
130
  onChange(value.filter((entry) => entry.id !== resource.id))
129
131
  }}
130
132
  aria-label={t('customers.calendar.editor.removeResource', 'Remove {name}', { name: resource.label })}
133
+ disabled={disabled}
131
134
  className="size-5 shrink-0"
132
135
  >
133
136
  <Plus aria-hidden className="size-3.5 rotate-45 opacity-50" />
@@ -143,6 +146,7 @@ export function ResourcesField({
143
146
  aria-label={ariaLabel}
144
147
  role="combobox"
145
148
  aria-expanded={open}
149
+ disabled={disabled}
146
150
  className="min-w-36 flex-1 border-0 bg-transparent px-0 shadow-none hover:bg-transparent focus-within:border-transparent focus-within:shadow-none"
147
151
  inputClassName="text-sm text-foreground placeholder:text-muted-foreground"
148
152
  />