@open-mercato/core 0.9.0 → 0.9.1-develop.7462.1.94c09f158f
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/attachments/api/route.js +29 -3
- package/dist/modules/attachments/api/route.js.map +2 -2
- package/dist/modules/attachments/lib/ocrLimits.js +49 -0
- package/dist/modules/attachments/lib/ocrLimits.js.map +7 -0
- package/dist/modules/attachments/lib/ocrQueue.js +76 -2
- package/dist/modules/attachments/lib/ocrQueue.js.map +2 -2
- package/dist/modules/attachments/lib/ocrService.js +4 -0
- package/dist/modules/attachments/lib/ocrService.js.map +2 -2
- package/dist/modules/attachments/lib/pdfProcessing.js +14 -2
- package/dist/modules/attachments/lib/pdfProcessing.js.map +2 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js +4 -2
- package/dist/modules/attachments/lib/scoped-upload-service.js.map +2 -2
- package/dist/modules/attachments/lib/textExtraction.js +13 -1
- package/dist/modules/attachments/lib/textExtraction.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/catalog/search.js +3 -1
- package/dist/modules/catalog/search.js.map +2 -2
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.js +2 -1
- package/dist/modules/communication_channels/api/get/oauth/[provider]/callback/route.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/backend/profile/communication-channels/page.js +10 -7
- package/dist/modules/communication_channels/backend/profile/communication-channels/page.js.map +2 -2
- 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-result.js +12 -0
- package/dist/modules/communication_channels/lib/oauth-result.js.map +7 -0
- 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/attachments/api/route.ts +36 -3
- package/src/modules/attachments/lib/ocrLimits.ts +62 -0
- package/src/modules/attachments/lib/ocrQueue.ts +95 -2
- package/src/modules/attachments/lib/ocrService.ts +4 -0
- package/src/modules/attachments/lib/pdfProcessing.ts +14 -2
- package/src/modules/attachments/lib/scoped-upload-service.ts +4 -2
- package/src/modules/attachments/lib/textExtraction.ts +14 -1
- 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/catalog/search.ts +5 -1
- package/src/modules/communication_channels/api/get/oauth/[provider]/callback/route.ts +2 -1
- 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/backend/profile/communication-channels/page.tsx +12 -9
- package/src/modules/communication_channels/commands/deliver-outbound-message.ts +6 -1
- package/src/modules/communication_channels/i18n/de.json +4 -1
- package/src/modules/communication_channels/i18n/en.json +4 -1
- package/src/modules/communication_channels/i18n/es.json +4 -1
- package/src/modules/communication_channels/i18n/ko.json +4 -1
- package/src/modules/communication_channels/i18n/pl.json +4 -1
- package/src/modules/communication_channels/lib/inbound-message-origin.ts +2 -6
- package/src/modules/communication_channels/lib/oauth-result.ts +13 -0
- 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
|
@@ -1,65 +1,43 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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>(
|
|
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(
|
|
38
|
+
if (open && !openRef.current) setDraft(preferences)
|
|
61
39
|
openRef.current = open
|
|
62
|
-
}, [open, preferences
|
|
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
|
-
<
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
265
|
+
updateDrag({ dayMs: dayStart.getTime(), startMin: minute, endMin: minute, moved: false })
|
|
261
266
|
}
|
|
262
267
|
|
|
263
268
|
const moveDrag = (event: React.PointerEvent<HTMLDivElement>) => {
|
|
264
|
-
|
|
265
|
-
|
|
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
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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={() =>
|
|
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
|
/>
|