@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
|
@@ -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
|
/>
|