@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.
- package/.turbo/turbo-build.log +1 -1
- package/dist/generated/entities/customer_dictionary_entry/index.js +2 -0
- package/dist/generated/entities/customer_dictionary_entry/index.js.map +2 -2
- package/dist/generated/entities/customer_interaction/index.js +2 -0
- package/dist/generated/entities/customer_interaction/index.js.map +2 -2
- package/dist/generated/entity-fields-registry.js +2 -0
- package/dist/generated/entity-fields-registry.js.map +2 -2
- package/dist/modules/catalog/commands/categories.js +1 -1
- package/dist/modules/catalog/commands/categories.js.map +2 -2
- package/dist/modules/catalog/commands/productDeleteChildren.js +389 -0
- package/dist/modules/catalog/commands/productDeleteChildren.js.map +7 -0
- package/dist/modules/catalog/commands/products.js +52 -17
- package/dist/modules/catalog/commands/products.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js +4 -6
- package/dist/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.js.map +2 -2
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js +47 -11
- package/dist/modules/communication_channels/api/post/oauth/[provider]/initiate/route.js.map +3 -3
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js +5 -1
- package/dist/modules/communication_channels/commands/deliver-outbound-message.js.map +2 -2
- package/dist/modules/communication_channels/lib/inbound-message-origin.js +1 -1
- package/dist/modules/communication_channels/lib/inbound-message-origin.js.map +2 -2
- package/dist/modules/communication_channels/lib/oauth-state.js +4 -1
- package/dist/modules/communication_channels/lib/oauth-state.js.map +2 -2
- package/dist/modules/communication_channels/lib/use-connect-channel.js +10 -0
- package/dist/modules/communication_channels/lib/use-connect-channel.js.map +2 -2
- package/dist/modules/customers/api/activity-types/route.js +151 -0
- package/dist/modules/customers/api/activity-types/route.js.map +7 -0
- package/dist/modules/customers/api/deals/[id]/people/route.js +58 -5
- package/dist/modules/customers/api/deals/[id]/people/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js +13 -5
- package/dist/modules/customers/api/dictionaries/[kind]/[id]/route.js.map +2 -2
- package/dist/modules/customers/api/dictionaries/[kind]/route.js +20 -5
- package/dist/modules/customers/api/dictionaries/[kind]/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/route.js +3 -0
- package/dist/modules/customers/api/interactions/route.js.map +2 -2
- package/dist/modules/customers/api/interactions/tasks/route.js +1 -0
- package/dist/modules/customers/api/interactions/tasks/route.js.map +2 -2
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js +18 -0
- package/dist/modules/customers/backend/config/customers/pipeline-stages/page.meta.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js +0 -23
- package/dist/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.js.map +2 -2
- package/dist/modules/customers/backend/customers/deals/[id]/page.js +15 -17
- package/dist/modules/customers/backend/customers/deals/[id]/page.js.map +2 -2
- package/dist/modules/customers/calendar-event-types.js +548 -0
- package/dist/modules/customers/calendar-event-types.js.map +7 -0
- package/dist/modules/customers/commands/dictionaries.js +70 -10
- package/dist/modules/customers/commands/dictionaries.js.map +2 -2
- package/dist/modules/customers/commands/interactions.js +187 -22
- package/dist/modules/customers/commands/interactions.js.map +3 -3
- package/dist/modules/customers/components/ActivityTypeEditor.js +288 -0
- package/dist/modules/customers/components/ActivityTypeEditor.js.map +7 -0
- package/dist/modules/customers/components/DictionarySettings.js +3 -1
- package/dist/modules/customers/components/DictionarySettings.js.map +2 -2
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js +391 -297
- package/dist/modules/customers/components/calendar/CalendarEventEditor.js.map +3 -3
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js +9 -74
- package/dist/modules/customers/components/calendar/CalendarSettingsModal.js.map +2 -2
- package/dist/modules/customers/components/calendar/TimeGrid.js +20 -16
- package/dist/modules/customers/components/calendar/TimeGrid.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js +218 -0
- package/dist/modules/customers/components/calendar/editor/EventTypePanel.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/LocationField.js +7 -3
- package/dist/modules/customers/components/calendar/editor/LocationField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js +5 -2
- package/dist/modules/customers/components/calendar/editor/PeopleField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/PriorityField.js +3 -1
- package/dist/modules/customers/components/calendar/editor/PriorityField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/RepeatField.js +9 -1
- package/dist/modules/customers/components/calendar/editor/RepeatField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js +4 -1
- package/dist/modules/customers/components/calendar/editor/ResourcesField.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js +15 -7
- package/dist/modules/customers/components/calendar/editor/ScheduleSection.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js +3 -1
- package/dist/modules/customers/components/calendar/editor/SegmentGroup.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/hooks.js +28 -13
- package/dist/modules/customers/components/calendar/editor/hooks.js.map +3 -3
- package/dist/modules/customers/components/calendar/editor/inputs.js +14 -6
- package/dist/modules/customers/components/calendar/editor/inputs.js.map +2 -2
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js +54 -0
- package/dist/modules/customers/components/calendar/editor/useCalendarCustomFields.js.map +7 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js +82 -0
- package/dist/modules/customers/components/calendar/editor/useEventTypeCatalog.js.map +7 -0
- package/dist/modules/customers/components/calendar/types.js +1 -0
- package/dist/modules/customers/components/calendar/types.js.map +2 -2
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js +78 -343
- package/dist/modules/customers/components/detail/CompanyPeopleSection.js.map +2 -2
- package/dist/modules/customers/components/detail/CreatePersonDialog.js +12 -11
- package/dist/modules/customers/components/detail/CreatePersonDialog.js.map +2 -2
- package/dist/modules/customers/components/detail/DealForm.js +7 -3
- package/dist/modules/customers/components/detail/DealForm.js.map +2 -2
- package/dist/modules/customers/components/detail/DealPeopleSection.js +206 -0
- package/dist/modules/customers/components/detail/DealPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js +342 -0
- package/dist/modules/customers/components/detail/LinkedPeopleSection.js.map +7 -0
- package/dist/modules/customers/components/detail/PersonCard.js +5 -2
- package/dist/modules/customers/components/detail/PersonCard.js.map +2 -2
- package/dist/modules/customers/components/linking/adapters/personAdapter.js +3 -0
- package/dist/modules/customers/components/linking/adapters/personAdapter.js.map +2 -2
- package/dist/modules/customers/data/entities.js +6 -0
- package/dist/modules/customers/data/entities.js.map +2 -2
- package/dist/modules/customers/data/validators.js +39 -10
- package/dist/modules/customers/data/validators.js.map +2 -2
- package/dist/modules/customers/di.js +20 -1
- package/dist/modules/customers/di.js.map +2 -2
- package/dist/modules/customers/extension-points.js +13 -0
- package/dist/modules/customers/extension-points.js.map +2 -2
- package/dist/modules/customers/lib/calendar/conflicts.js +59 -16
- package/dist/modules/customers/lib/calendar/conflicts.js.map +2 -2
- package/dist/modules/customers/lib/calendar/editorPayload.js +34 -16
- package/dist/modules/customers/lib/calendar/editorPayload.js.map +2 -2
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js +197 -0
- package/dist/modules/customers/lib/calendar/eventTypeResolver.js.map +7 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js +67 -0
- package/dist/modules/customers/lib/calendar/interactionApplicability.js.map +7 -0
- package/dist/modules/customers/lib/calendar/mapItem.js +5 -2
- package/dist/modules/customers/lib/calendar/mapItem.js.map +2 -2
- package/dist/modules/customers/lib/calendar/recurrence.js +62 -1
- package/dist/modules/customers/lib/calendar/recurrence.js.map +2 -2
- package/dist/modules/customers/lib/calendar/timezone.js +111 -0
- package/dist/modules/customers/lib/calendar/timezone.js.map +7 -0
- package/dist/modules/customers/lib/interactionReadModel.js +1 -0
- package/dist/modules/customers/lib/interactionReadModel.js.map +2 -2
- package/dist/modules/customers/lib/todoCompatibility.js +1 -0
- package/dist/modules/customers/lib/todoCompatibility.js.map +2 -2
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260928175058_customers.js.map +7 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js +17 -0
- package/dist/modules/customers/migrations/Migration20260930110235_customers.js.map +7 -0
- package/dist/modules/dictionaries/components/DictionaryTable.js +60 -51
- package/dist/modules/dictionaries/components/DictionaryTable.js.map +2 -2
- package/dist/modules/messages/subscribers/message-notification.js +13 -1
- package/dist/modules/messages/subscribers/message-notification.js.map +2 -2
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js +201 -53
- package/dist/modules/payment_gateways/api/webhook/[provider]/route.js.map +2 -2
- package/dist/modules/payment_gateways/lib/gateway-service.js +37 -11
- package/dist/modules/payment_gateways/lib/gateway-service.js.map +2 -2
- package/dist/modules/planner/lib/availabilityMerge.js +101 -38
- package/dist/modules/planner/lib/availabilityMerge.js.map +3 -3
- package/dist/modules/planner/services/plannerAvailabilityService.js.map +2 -2
- package/dist/modules/sales/components/documents/LineItemDialog.js +159 -81
- package/dist/modules/sales/components/documents/LineItemDialog.js.map +2 -2
- package/dist/modules/sales/components/documents/lineItemUtils.js +7 -0
- package/dist/modules/sales/components/documents/lineItemUtils.js.map +2 -2
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js +12 -7
- package/dist/modules/staff/backend/staff/time-tracking/projects/[id]/page.js.map +2 -2
- package/dist/modules/sync_excel/lib/adapters/customers.js +28 -25
- package/dist/modules/sync_excel/lib/adapters/customers.js.map +2 -2
- package/generated/entities/customer_dictionary_entry/index.ts +1 -0
- package/generated/entities/customer_interaction/index.ts +1 -0
- package/generated/entity-fields-registry.ts +2 -0
- package/package.json +9 -8
- package/src/modules/auth/i18n/de.json +3 -0
- package/src/modules/auth/i18n/en.json +3 -0
- package/src/modules/auth/i18n/es.json +3 -0
- package/src/modules/auth/i18n/ko.json +3 -0
- package/src/modules/auth/i18n/pl.json +3 -0
- package/src/modules/catalog/commands/categories.ts +1 -1
- package/src/modules/catalog/commands/productDeleteChildren.ts +557 -0
- package/src/modules/catalog/commands/products.ts +58 -17
- package/src/modules/communication_channels/api/post/channels/connect/credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/channels/connect/tenant-credentials/route.ts +4 -6
- package/src/modules/communication_channels/api/post/oauth/[provider]/initiate/route.ts +46 -10
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -0
- package/src/modules/communication_channels/i18n/en.json +4 -0
- package/src/modules/communication_channels/i18n/es.json +4 -0
- package/src/modules/communication_channels/i18n/ko.json +4 -0
- package/src/modules/communication_channels/i18n/pl.json +4 -0
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-state.ts +4 -1
- package/src/modules/communication_channels/lib/use-connect-channel.ts +10 -0
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- package/src/modules/customers/api/deals/[id]/people/route.ts +82 -3
- package/src/modules/customers/api/dictionaries/[kind]/[id]/route.ts +9 -1
- package/src/modules/customers/api/dictionaries/[kind]/route.ts +18 -2
- package/src/modules/customers/api/interactions/route.ts +4 -0
- package/src/modules/customers/api/interactions/tasks/route.ts +1 -0
- package/src/modules/customers/backend/config/customers/pipeline-stages/page.meta.ts +20 -0
- package/src/modules/customers/backend/customers/deals/[id]/hooks/useDealAssociations.ts +0 -29
- package/src/modules/customers/backend/customers/deals/[id]/page.tsx +15 -17
- package/src/modules/customers/calendar-event-types.ts +674 -0
- package/src/modules/customers/commands/dictionaries.ts +84 -8
- package/src/modules/customers/commands/interactions.ts +244 -21
- package/src/modules/customers/components/ActivityTypeEditor.tsx +239 -0
- package/src/modules/customers/components/DictionarySettings.tsx +5 -3
- package/src/modules/customers/components/calendar/CalendarEventEditor.tsx +347 -218
- package/src/modules/customers/components/calendar/CalendarSettingsModal.tsx +18 -96
- package/src/modules/customers/components/calendar/TimeGrid.tsx +20 -18
- package/src/modules/customers/components/calendar/editor/EventTypePanel.tsx +230 -0
- package/src/modules/customers/components/calendar/editor/LocationField.tsx +7 -1
- package/src/modules/customers/components/calendar/editor/PeopleField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/PriorityField.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/RepeatField.tsx +10 -0
- package/src/modules/customers/components/calendar/editor/ResourcesField.tsx +4 -0
- package/src/modules/customers/components/calendar/editor/ScheduleSection.tsx +16 -4
- package/src/modules/customers/components/calendar/editor/SegmentGroup.tsx +3 -0
- package/src/modules/customers/components/calendar/editor/hooks.ts +29 -13
- package/src/modules/customers/components/calendar/editor/inputs.tsx +17 -5
- package/src/modules/customers/components/calendar/editor/useCalendarCustomFields.ts +49 -0
- package/src/modules/customers/components/calendar/editor/useEventTypeCatalog.ts +100 -0
- package/src/modules/customers/components/calendar/types.ts +1 -0
- package/src/modules/customers/components/detail/CompanyPeopleSection.tsx +94 -388
- package/src/modules/customers/components/detail/CreatePersonDialog.tsx +39 -19
- package/src/modules/customers/components/detail/DealForm.tsx +7 -3
- package/src/modules/customers/components/detail/DealPeopleSection.tsx +256 -0
- package/src/modules/customers/components/detail/LinkedPeopleSection.tsx +474 -0
- package/src/modules/customers/components/detail/PersonCard.tsx +12 -4
- package/src/modules/customers/components/detail/types.ts +1 -0
- package/src/modules/customers/components/linking/adapters/personAdapter.tsx +4 -0
- package/src/modules/customers/data/entities.ts +8 -1
- package/src/modules/customers/data/validators.ts +42 -7
- package/src/modules/customers/di.ts +21 -0
- package/src/modules/customers/extension-points.ts +13 -0
- package/src/modules/customers/i18n/de.json +104 -8
- package/src/modules/customers/i18n/en.json +104 -8
- package/src/modules/customers/i18n/es.json +104 -8
- package/src/modules/customers/i18n/ko.json +104 -8
- package/src/modules/customers/i18n/pl.json +104 -8
- package/src/modules/customers/lib/calendar/conflicts.ts +106 -27
- package/src/modules/customers/lib/calendar/editorPayload.ts +39 -19
- package/src/modules/customers/lib/calendar/eventTypeResolver.ts +258 -0
- package/src/modules/customers/lib/calendar/interactionApplicability.ts +99 -0
- package/src/modules/customers/lib/calendar/mapItem.ts +6 -2
- package/src/modules/customers/lib/calendar/recurrence.ts +65 -1
- package/src/modules/customers/lib/calendar/timezone.ts +117 -0
- package/src/modules/customers/lib/interactionReadModel.ts +1 -0
- package/src/modules/customers/lib/todoCompatibility.ts +2 -0
- package/src/modules/customers/migrations/.snapshot-open-mercato.json +34 -0
- package/src/modules/customers/migrations/Migration20260928175058_customers.ts +15 -0
- package/src/modules/customers/migrations/Migration20260930110235_customers.ts +15 -0
- package/src/modules/dictionaries/components/DictionaryTable.tsx +63 -56
- package/src/modules/messages/subscribers/message-notification.ts +13 -3
- package/src/modules/payment_gateways/api/webhook/[provider]/route.ts +243 -62
- package/src/modules/payment_gateways/lib/gateway-service.ts +39 -12
- package/src/modules/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/sales/components/documents/LineItemDialog.tsx +194 -111
- package/src/modules/sales/components/documents/lineItemUtils.ts +18 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
- 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
|
-
<
|
|
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
|
)
|