@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
@@ -0,0 +1,239 @@
1
+ "use client"
2
+
3
+ import * as React from 'react'
4
+ import type { LegacyColumnDef as ColumnDef } from '@tanstack/react-table/legacy'
5
+ import { E } from '#generated/entities.ids.generated'
6
+ import { useT } from '@open-mercato/shared/lib/i18n/context'
7
+ import { useOrganizationScopeVersion } from '@open-mercato/shared/lib/frontend/useOrganizationScope'
8
+ import { createLogger } from '@open-mercato/shared/lib/logger'
9
+ import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
10
+ import { DataTable } from '@open-mercato/ui/backend/DataTable'
11
+ import { RowActions, type RowActionItem } from '@open-mercato/ui/backend/RowActions'
12
+ import { CrudForm, type CrudField, type CrudFormGroup } from '@open-mercato/ui/backend/CrudForm'
13
+ import { ErrorMessage, LoadingMessage } from '@open-mercato/ui/backend/detail'
14
+ import { flash } from '@open-mercato/ui/backend/FlashMessages'
15
+ import { useConfirmDialog } from '@open-mercato/ui/backend/confirm-dialog'
16
+ import { surfaceRecordConflict } from '@open-mercato/ui/backend/conflicts'
17
+ import { useGuardedMutation } from '@open-mercato/ui/backend/injection/useGuardedMutation'
18
+ import { apiCallOrThrow, readApiResultOrThrow, withScopedApiRequestHeaders } from '@open-mercato/ui/backend/utils/apiCall'
19
+ import { buildOptimisticLockHeader } from '@open-mercato/ui/backend/utils/optimisticLock'
20
+ import { createCrudFormError } from '@open-mercato/ui/backend/utils/serverErrors'
21
+ import { Button } from '@open-mercato/ui/primitives/button'
22
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@open-mercato/ui/primitives/dialog'
23
+ import { calendarEventTypeBehaviorSchema, calendarEventTypeKeySchema, calendarEventTypes } from '../calendar-event-types'
24
+ import type { ScopedCalendarEventType } from '../lib/calendar/eventTypeResolver'
25
+ import { getCustomerDictionarySettingsSectionId } from '../lib/dictionaries'
26
+ import { renderDictionaryColor, renderDictionaryIcon } from '@open-mercato/core/modules/dictionaries/components/dictionaryAppearance'
27
+
28
+ const logger = createLogger('customers')
29
+ const ENTITY_ID = E.customers.customer_interaction
30
+ const API = '/api/customers/dictionaries/activity-types'
31
+
32
+ type DictionaryRow = { id: string; value: string; updatedAt: string | null; isInherited: boolean }
33
+ type Fieldset = { code: string; label: string }
34
+ type FormValues = Record<string, unknown>
35
+
36
+ const defaults = calendarEventTypes[0]!.behavior
37
+
38
+ function initialValues(item: ScopedCalendarEventType | null): FormValues {
39
+ const behavior = item?.behavior ?? defaults
40
+ return {
41
+ value: item?.key ?? '', label: item?.label ?? '', icon: item?.icon ?? '', color: item?.color ?? '',
42
+ order: behavior.order, baseKind: behavior.baseKind, selectable: behavior.selectable,
43
+ ...behavior.fields, customFieldsetIds: [...behavior.customFieldsetIds], updatedAt: item?.updatedAt ?? undefined,
44
+ }
45
+ }
46
+
47
+ export function ActivityTypeEditor({ title, description }: { title: string; description: string }) {
48
+ const t = useT()
49
+ const scopeVersion = useOrganizationScopeVersion()
50
+ const { confirm, ConfirmDialogElement } = useConfirmDialog()
51
+ const [items, setItems] = React.useState<ScopedCalendarEventType[]>([])
52
+ const [rows, setRows] = React.useState<DictionaryRow[]>([])
53
+ const [selected, setSelected] = React.useState<ScopedCalendarEventType | null | undefined>(undefined)
54
+ const [fieldsets, setFieldsets] = React.useState<Fieldset[]>([])
55
+ const [fieldsetsStatus, setFieldsetsStatus] = React.useState<'loading' | 'ready' | 'error'>('loading')
56
+ const [fieldsetAttempt, setFieldsetAttempt] = React.useState(0)
57
+ const [status, setStatus] = React.useState<'loading' | 'ready' | 'error'>('loading')
58
+ const { runMutation, retryLastMutation } = useGuardedMutation({
59
+ contextId: 'customers-activity-types',
60
+ blockedMessage: t('ui.forms.flash.saveBlocked', 'Save blocked by validation'),
61
+ })
62
+ const load = React.useCallback(async () => {
63
+ setStatus('loading')
64
+ try {
65
+ const [catalog, dictionary] = await Promise.all([
66
+ readApiResultOrThrow<{ items: ScopedCalendarEventType[] }>('/api/customers/activity-types'),
67
+ readApiResultOrThrow<{ items: DictionaryRow[] }>(API),
68
+ ])
69
+ if (!Array.isArray(catalog.items) || !Array.isArray(dictionary.items)) throw new Error('[internal] Invalid activity type response')
70
+ setItems(catalog.items)
71
+ setRows(dictionary.items)
72
+ setStatus('ready')
73
+ } catch (err) {
74
+ logger.error('customers.activity_types.settings.load failed', { err })
75
+ getTelemetryRuntime()?.reportError(err, {
76
+ module: 'customers',
77
+ code: 'customers.activity_type_settings_load_failed',
78
+ })
79
+ setStatus('error')
80
+ }
81
+ }, [])
82
+ React.useEffect(() => { void load() }, [load, scopeVersion])
83
+
84
+ React.useEffect(() => {
85
+ if (selected === undefined) return
86
+ let active = true
87
+ setFieldsetsStatus('loading')
88
+ void readApiResultOrThrow<{ fieldsetsByEntity?: Record<string, Fieldset[]> }>(
89
+ `/api/entities/definitions?entityId=${encodeURIComponent(ENTITY_ID)}`,
90
+ ).then((result) => {
91
+ if (!active) return
92
+ setFieldsets(result.fieldsetsByEntity?.[ENTITY_ID] ?? [])
93
+ setFieldsetsStatus('ready')
94
+ }).catch((err) => {
95
+ logger.error('customers.activity_types.fieldsets.load failed', { err })
96
+ getTelemetryRuntime()?.reportError(err, {
97
+ module: 'customers',
98
+ code: 'customers.activity_type_fieldsets_load_failed',
99
+ })
100
+ if (active) setFieldsetsStatus('error')
101
+ })
102
+ return () => { active = false }
103
+ }, [selected, fieldsetAttempt])
104
+
105
+ const localRow = (key: string) => rows.find((row) => row.value.toLowerCase() === key && !row.isInherited)
106
+ const remove = async (item: ScopedCalendarEventType) => {
107
+ const row = localRow(item.key)
108
+ if (!row) return
109
+ const approved = await confirm({
110
+ title: t('customers.config.activityTypes.resetConfirm', 'Remove the local activity type configuration?'),
111
+ variant: 'destructive',
112
+ })
113
+ if (!approved) return
114
+ try {
115
+ await runMutation({
116
+ operation: () => withScopedApiRequestHeaders(buildOptimisticLockHeader(row.updatedAt), () =>
117
+ apiCallOrThrow(`${API}/${encodeURIComponent(row.id)}`, { method: 'DELETE' })),
118
+ context: { formId: 'customers-activity-types', resourceKind: 'customers.dictionary', retryLastMutation },
119
+ mutationPayload: { action: 'delete', id: row.id, kind: 'activity-types' },
120
+ })
121
+ flash(t('customers.config.activityTypes.removed', 'Activity type configuration removed.'), 'success')
122
+ await load()
123
+ } catch (err) {
124
+ logger.error('customers.activity_types.settings.remove failed', { err })
125
+ getTelemetryRuntime()?.reportError(err, {
126
+ module: 'customers',
127
+ code: 'customers.activity_type_settings_remove_failed',
128
+ })
129
+ if (!surfaceRecordConflict(err, t, { onRefresh: () => { void load() } })) {
130
+ flash(t('customers.config.activityTypes.saveFailed', 'Failed to save activity type.'), 'error')
131
+ }
132
+ }
133
+ }
134
+
135
+ const fields = React.useMemo<CrudField[]>(() => {
136
+ const label = (key: string, fallback: string) => t(`customers.config.activityTypes.fields.${key}`, fallback)
137
+ const options = (values: readonly string[]) => values.map((value) => ({ value, label: t(`customers.config.activityTypes.options.${value}`, value) }))
138
+ const knownFieldsets = fieldsets.map(({ code, label: fieldsetLabel }) => ({ value: code, label: fieldsetLabel }))
139
+ const missing = selected?.behavior.customFieldsetIds.filter((code) => !fieldsets.some((fieldset) => fieldset.code === code)) ?? []
140
+ return [
141
+ { id: 'value', type: 'text', label: label('value', 'Stable key'), required: true, readOnly: !!selected },
142
+ { id: 'label', type: 'text', label: label('label', 'Label'), required: true },
143
+ { id: 'icon', type: 'text', label: label('icon', 'Icon') },
144
+ { id: 'color', type: 'text', label: label('color', 'Color (#RRGGBB)') },
145
+ { id: 'order', type: 'number', label: label('order', 'Display order'), required: true },
146
+ { id: 'baseKind', type: 'select', label: label('baseKind', 'Base behavior'), options: options(['meeting', 'call', 'email', 'note', 'event', 'task']) },
147
+ { id: 'selectable', type: 'checkbox', label: label('selectable', 'Available for new events') },
148
+ ...(['endTime', 'allDay', 'recurrence', 'priority', 'resources'] as const).map((id): CrudField => ({ id, type: 'checkbox', label: label(id, id) })),
149
+ { id: 'location', type: 'select', label: label('location', 'Location field'), options: options(['none', 'location', 'phoneLink']) },
150
+ { id: 'people', type: 'select', label: label('people', 'People field'), options: options(['none', 'attendees', 'participants', 'recipients', 'assignee']) },
151
+ { id: 'customFieldsetIds', type: 'select', multiple: true, label: label('customFieldsetIds', 'Custom-field fieldsets'),
152
+ options: [...knownFieldsets, ...missing.map((code) => ({ value: code, label: `${code} (${t('customers.config.activityTypes.missing', 'missing')})` }))] },
153
+ ]
154
+ }, [fieldsets, selected, t])
155
+ const groups: CrudFormGroup[] = [
156
+ { id: 'appearance', title: t('customers.config.activityTypes.appearance', 'Appearance'), fields: ['value', 'label', 'icon', 'color', 'order'] },
157
+ { id: 'behavior', title: t('customers.config.activityTypes.behavior', 'Form behavior'), fields: ['baseKind', 'selectable', 'endTime', 'allDay', 'recurrence', 'location', 'people', 'priority', 'resources'] },
158
+ ...(fieldsets.length || selected?.behavior.customFieldsetIds.length ? [{ id: 'fieldsets', fields: ['customFieldsetIds'] }] : []),
159
+ ]
160
+ const save = async (values: FormValues) => {
161
+ const key = String(values.value ?? '').trim()
162
+ if (!calendarEventTypeKeySchema.safeParse(key).success) throw createCrudFormError(t('customers.config.activityTypes.invalidKey', 'Use a lowercase key with letters, numbers, hyphens, or underscores.'), { value: t('customers.config.activityTypes.invalidKey', 'Use a lowercase key with letters, numbers, hyphens, or underscores.') })
163
+ if (fieldsetsStatus !== 'ready') throw createCrudFormError(t('customers.config.activityTypes.fieldsetsUnavailable', 'Fieldsets are unavailable. Retry loading before saving.'))
164
+ const behavior = calendarEventTypeBehaviorSchema.safeParse({
165
+ schemaVersion: 1, baseKind: values.baseKind, selectable: values.selectable === true,
166
+ order: Number(values.order), customFieldsetIds: values.customFieldsetIds ?? [],
167
+ fields: { endTime: values.endTime === true, allDay: values.allDay === true, recurrence: values.recurrence === true,
168
+ location: values.location, people: values.people, priority: values.priority === true, resources: values.resources === true },
169
+ })
170
+ if (!behavior.success) throw createCrudFormError(t('customers.config.activityTypes.invalidBehavior', 'Check the form behavior values.'))
171
+ const payload = { value: key, label: String(values.label ?? '').trim(), icon: String(values.icon ?? '').trim() || null,
172
+ color: String(values.color ?? '').trim() || null, behavior: behavior.data }
173
+ const row = selected ? localRow(selected.key) : undefined
174
+ await apiCallOrThrow(row ? `${API}/${encodeURIComponent(row.id)}` : API, {
175
+ method: row ? 'PATCH' : 'POST', headers: { 'content-type': 'application/json' },
176
+ body: JSON.stringify(row ? { label: payload.label, icon: payload.icon, color: payload.color, behavior: payload.behavior } : payload),
177
+ })
178
+ setSelected(undefined)
179
+ flash(t('customers.config.activityTypes.saved', 'Activity type saved.'), 'success')
180
+ await load()
181
+ }
182
+
183
+ const columns = React.useMemo<ColumnDef<ScopedCalendarEventType>[]>(() => [
184
+ { accessorKey: 'label', header: t('customers.config.activityTypes.columns.type', 'Activity type'), cell: ({ row }) => (
185
+ <div className="flex items-center gap-2">{row.original.color ? renderDictionaryColor(row.original.color, 'size-4 rounded-full border border-border') : null}
186
+ {row.original.icon ? renderDictionaryIcon(row.original.icon, 'size-4') : null}<span>{row.original.labelKey
187
+ ? t(row.original.labelKey, row.original.label)
188
+ : row.original.label}</span></div>) },
189
+ { accessorKey: 'key', header: t('customers.config.activityTypes.columns.key', 'Stable key') },
190
+ { id: 'source', header: t('customers.config.activityTypes.columns.source', 'Source'), cell: ({ row }) => row.original.isLocalOverride
191
+ ? t('customers.config.activityTypes.local', 'Local override') : row.original.isInherited
192
+ ? t('customers.config.activityTypes.inherited', 'Inherited') : row.original.source },
193
+ { id: 'behavior', header: t('customers.config.activityTypes.columns.behavior', 'Behavior'), cell: ({ row }) =>
194
+ t(`customers.config.activityTypes.options.${row.original.behavior.baseKind}`, row.original.behavior.baseKind) },
195
+ { id: 'active', header: t('customers.config.activityTypes.columns.active', 'Selectable'), cell: ({ row }) => row.original.selectable
196
+ ? t('customers.config.activityTypes.yes', 'Yes') : t('customers.config.activityTypes.no', 'No') },
197
+ { id: 'fields', header: t('customers.config.activityTypes.columns.fields', 'Fields'), cell: ({ row }) =>
198
+ Object.values(row.original.behavior.fields).filter((value) => value !== false && value !== 'none').length },
199
+ { id: 'fieldsets', header: t('customers.config.activityTypes.columns.fieldsets', 'Fieldsets'), cell: ({ row }) => row.original.behavior.customFieldsetIds.length },
200
+ { id: 'order', header: t('customers.config.activityTypes.columns.order', 'Order'), cell: ({ row }) => row.original.behavior.order },
201
+ ], [t])
202
+
203
+ return <section id={getCustomerDictionarySettingsSectionId('activity-types')} className="scroll-mt-24 rounded border bg-card text-card-foreground shadow-sm">
204
+ <div className="space-y-1 border-b px-6 py-4"><h2 className="text-lg font-medium">{title}</h2><p className="text-sm text-muted-foreground">{description}</p></div>
205
+ <div className="px-2 py-4 sm:px-4">
206
+ {status === 'error' ? <div className="space-y-3"><ErrorMessage label={t('customers.config.activityTypes.loadFailed', 'Failed to load activity types.')} /><Button type="button" onClick={() => { void load() }}>{t('customers.config.activityTypes.retry', 'Retry')}</Button></div> :
207
+ <DataTable<ScopedCalendarEventType> embedded columns={columns} data={items} isLoading={status === 'loading'}
208
+ title={title} titleHeadingLevel={2} emptyState={t('customers.config.activityTypes.empty', 'No activity types available.')}
209
+ actions={<Button type="button" size="sm" onClick={() => setSelected(null)}>{t('customers.config.activityTypes.new', 'New activity type')}</Button>}
210
+ refreshButton={{ label: t('customers.config.activityTypes.refresh', 'Refresh'), onRefresh: load, isRefreshing: status === 'loading' }}
211
+ rowActions={(item) => {
212
+ const actions: RowActionItem[] = [{ id: 'edit', label: item.adminConfigurable
213
+ ? t('customers.config.activityTypes.configure', 'Configure') : t('customers.config.activityTypes.view', 'View'), onSelect: () => setSelected(item) }]
214
+ if (localRow(item.key)) actions.push({ id: 'delete', label: item.source === 'dictionary'
215
+ ? t('customers.config.activityTypes.delete', 'Delete') : t('customers.config.activityTypes.reset', 'Reset override'),
216
+ destructive: true, onSelect: () => { void remove(item) } })
217
+ return <RowActions items={actions} />
218
+ }} />}
219
+ </div>
220
+ <Dialog open={selected !== undefined} onOpenChange={(open) => { if (!open) setSelected(undefined) }}>
221
+ <DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
222
+ <DialogHeader><DialogTitle>{selected
223
+ ? t('customers.config.activityTypes.configure', 'Configure') : t('customers.config.activityTypes.new', 'New activity type')}</DialogTitle></DialogHeader>
224
+ {selected?.isInherited ? <p className="text-sm text-muted-foreground">{t('customers.config.activityTypes.inheritedHint', 'Saving creates a local override for this organization.')}</p> : null}
225
+ {selected?.inactiveDictionaryOverride ? <p className="text-sm text-muted-foreground">{t('customers.config.activityTypes.inactiveHint', 'A module currently prevents this override from taking effect.')}</p> : null}
226
+ {selected?.missingCustomFieldsetIds.length ? <p className="text-sm text-muted-foreground">{t('customers.config.activityTypes.missingHint', 'Some configured fieldsets no longer exist.')}: {selected.missingCustomFieldsetIds.join(', ')}</p> : null}
227
+ {fieldsetsStatus === 'loading' ? <LoadingMessage label={t('customers.config.activityTypes.loadingFieldsets', 'Loading fieldsets…')} /> : null}
228
+ {fieldsetsStatus === 'error' ? <ErrorMessage label={t('customers.config.activityTypes.fieldsetsUnavailable', 'Fieldsets are unavailable. Retry loading before saving.')} /> : null}
229
+ {fieldsetsStatus === 'error' ? <Button type="button" onClick={() => setFieldsetAttempt((attempt) => attempt + 1)}>{t('customers.config.activityTypes.retry', 'Retry')}</Button> : null}
230
+ {fieldsetsStatus === 'ready' && fieldsets.length === 0 ? <p className="text-sm text-muted-foreground">{t('customers.config.activityTypes.noFieldsets', 'No custom-field fieldsets are defined yet.')}</p> : null}
231
+ {selected !== undefined && fieldsetsStatus === 'ready' ? <CrudForm<FormValues> key={selected?.key ?? 'new'} embedded
232
+ readOnly={selected?.adminConfigurable === false} fields={fields} groups={groups}
233
+ initialValues={initialValues(selected)} onSubmit={save} submitLabel={t('customers.config.activityTypes.save', 'Save')}
234
+ extraActions={<Button type="button" variant="outline" onClick={() => setSelected(undefined)}>{t('customers.config.dictionaries.dialog.cancel', 'Cancel')}</Button>} /> : null}
235
+ </DialogContent>
236
+ </Dialog>
237
+ {ConfirmDialogElement}
238
+ </section>
239
+ }
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from 'react'
4
+ import dynamic from 'next/dynamic'
4
5
  import {
5
6
  Dialog,
6
7
  DialogContent,
@@ -30,6 +31,7 @@ import {
30
31
  import { createLogger } from '@open-mercato/shared/lib/logger'
31
32
 
32
33
  const logger = createLogger('customers')
34
+ const ActivityTypeEditor = dynamic(() => import('./ActivityTypeEditor').then((module) => module.ActivityTypeEditor), { ssr: false })
33
35
 
34
36
  type SectionDefinition = {
35
37
  kind: CustomerDictionaryKind
@@ -173,9 +175,9 @@ export default function DictionarySettings() {
173
175
  </header>
174
176
 
175
177
  <div className="space-y-6">
176
- {sections.map((section) => (
177
- <CustomerDictionarySection key={section.kind} {...section} />
178
- ))}
178
+ {sections.map((section) => section.kind === 'activity-types'
179
+ ? <ActivityTypeEditor key={section.kind} title={section.title} description={section.description} />
180
+ : <CustomerDictionarySection key={section.kind} {...section} />)}
179
181
  </div>
180
182
  </div>
181
183
  )