@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
|
@@ -21,6 +21,8 @@ function DateTimeRow({
|
|
|
21
21
|
showTime,
|
|
22
22
|
onDateChange,
|
|
23
23
|
onTimeChange,
|
|
24
|
+
error,
|
|
25
|
+
disabled,
|
|
24
26
|
}: {
|
|
25
27
|
label: string
|
|
26
28
|
date: string
|
|
@@ -28,14 +30,17 @@ function DateTimeRow({
|
|
|
28
30
|
showTime: boolean
|
|
29
31
|
onDateChange(next: string): void
|
|
30
32
|
onTimeChange(next: string): void
|
|
33
|
+
error?: string | null
|
|
34
|
+
disabled?: boolean
|
|
31
35
|
}) {
|
|
32
36
|
return (
|
|
33
|
-
<div className="flex w-full items-end gap-2.5">
|
|
37
|
+
<div className="flex w-full flex-wrap items-end gap-x-2.5 gap-y-1">
|
|
34
38
|
<div className="flex min-w-0 flex-1 flex-col gap-1.5">
|
|
35
39
|
<span className={LABEL_CLASS}>{label}</span>
|
|
36
|
-
<DateControl value={date} onChange={onDateChange} ariaLabel={label} />
|
|
40
|
+
<DateControl value={date} onChange={onDateChange} ariaLabel={label} disabled={disabled} />
|
|
37
41
|
</div>
|
|
38
|
-
{showTime ? <TimeControl value={time} onChange={onTimeChange} ariaLabel={label} /> : null}
|
|
42
|
+
{showTime ? <TimeControl value={time} onChange={onTimeChange} ariaLabel={label} disabled={disabled} /> : null}
|
|
43
|
+
{error ? <p className="w-full text-xs text-status-error-text">{error}</p> : null}
|
|
39
44
|
</div>
|
|
40
45
|
)
|
|
41
46
|
}
|
|
@@ -49,7 +54,9 @@ export function ScheduleSection({
|
|
|
49
54
|
startTime,
|
|
50
55
|
endDate,
|
|
51
56
|
endTime,
|
|
57
|
+
startError,
|
|
52
58
|
endsError,
|
|
59
|
+
disabled,
|
|
53
60
|
onAllDayChange,
|
|
54
61
|
onDateChange,
|
|
55
62
|
onStartTimeChange,
|
|
@@ -64,7 +71,9 @@ export function ScheduleSection({
|
|
|
64
71
|
startTime: string
|
|
65
72
|
endDate: string
|
|
66
73
|
endTime: string
|
|
74
|
+
startError?: string | null
|
|
67
75
|
endsError?: string | null
|
|
76
|
+
disabled?: boolean
|
|
68
77
|
onAllDayChange(next: boolean): void
|
|
69
78
|
onDateChange(next: string): void
|
|
70
79
|
onStartTimeChange(next: string): void
|
|
@@ -79,7 +88,7 @@ export function ScheduleSection({
|
|
|
79
88
|
{hasAllDay ? (
|
|
80
89
|
<div className="flex w-full items-center justify-between">
|
|
81
90
|
<span className={LABEL_CLASS}>{t('customers.calendar.editor.allDay', 'All day')}</span>
|
|
82
|
-
<AllDayToggle checked={allDay} onCheckedChange={onAllDayChange} label={t('customers.calendar.editor.allDay', 'All day')} />
|
|
91
|
+
<AllDayToggle checked={allDay} onCheckedChange={onAllDayChange} label={t('customers.calendar.editor.allDay', 'All day')} disabled={disabled} />
|
|
83
92
|
</div>
|
|
84
93
|
) : null}
|
|
85
94
|
<DateTimeRow
|
|
@@ -89,6 +98,8 @@ export function ScheduleSection({
|
|
|
89
98
|
showTime={showTime}
|
|
90
99
|
onDateChange={onDateChange}
|
|
91
100
|
onTimeChange={onStartTimeChange}
|
|
101
|
+
error={startError}
|
|
102
|
+
disabled={disabled}
|
|
92
103
|
/>
|
|
93
104
|
{hasEnd ? (
|
|
94
105
|
<DateTimeRow
|
|
@@ -98,6 +109,7 @@ export function ScheduleSection({
|
|
|
98
109
|
showTime={showTime}
|
|
99
110
|
onDateChange={onEndDateChange}
|
|
100
111
|
onTimeChange={onEndTimeChange}
|
|
112
|
+
disabled={disabled}
|
|
101
113
|
/>
|
|
102
114
|
) : null}
|
|
103
115
|
{endsError ? <p className="text-xs text-status-error-text">{endsError}</p> : null}
|
|
@@ -12,12 +12,14 @@ export function SegmentGroup<T extends string>({
|
|
|
12
12
|
onChange,
|
|
13
13
|
size = 'sm',
|
|
14
14
|
ariaLabel,
|
|
15
|
+
disabled = false,
|
|
15
16
|
}: {
|
|
16
17
|
options: Array<SegmentOption<T>>
|
|
17
18
|
value: T
|
|
18
19
|
onChange(next: T): void
|
|
19
20
|
size?: 'sm' | 'md'
|
|
20
21
|
ariaLabel: string
|
|
22
|
+
disabled?: boolean
|
|
21
23
|
}) {
|
|
22
24
|
return (
|
|
23
25
|
<div
|
|
@@ -34,6 +36,7 @@ export function SegmentGroup<T extends string>({
|
|
|
34
36
|
variant="ghost"
|
|
35
37
|
aria-pressed={isActive}
|
|
36
38
|
onClick={() => onChange(option.value)}
|
|
39
|
+
disabled={disabled}
|
|
37
40
|
title={option.label}
|
|
38
41
|
data-state={isActive ? 'active' : 'inactive'}
|
|
39
42
|
className={cn(
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"use client"
|
|
2
2
|
|
|
3
3
|
import * as React from 'react'
|
|
4
|
+
import { calendarDayEndInstant, calendarDayStartInstant, calendarWallTimeToInstant } from '../../../lib/calendar/timezone'
|
|
4
5
|
import { endOfDay } from 'date-fns/endOfDay'
|
|
5
6
|
import { startOfDay } from 'date-fns/startOfDay'
|
|
6
7
|
import { useT } from '@open-mercato/shared/lib/i18n/context'
|
|
7
8
|
import { computeDurationMinutes, type EditorFormState, type EditorKindConfig } from '../../../lib/calendar/editorPayload'
|
|
8
|
-
import {
|
|
9
|
+
import { findEditorConflicts } from '../../../lib/calendar/conflicts'
|
|
9
10
|
import type { ConflictScope } from '../../../lib/calendar/preferences'
|
|
10
11
|
import { mapInteractionToCalendarItem } from '../../../lib/calendar/mapItem'
|
|
11
12
|
import { participantActorKey } from '../../../lib/calendar/participantIdentity'
|
|
@@ -55,12 +56,14 @@ export function useEditorLabelResolution(
|
|
|
55
56
|
}, [activeOrgId, open, form.relatedTo, form.dealId, form.dealLabel, form.assigneeUserId, form.assigneeName, update])
|
|
56
57
|
}
|
|
57
58
|
|
|
59
|
+
const NO_DRAFT_RESOURCES: ReadonlyArray<{ id: string; label: string }> = []
|
|
60
|
+
|
|
58
61
|
function formatClock(date: Date): string {
|
|
59
62
|
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
// Debounced save-time conflict probe. Detection uses the SAME `findConflicts`
|
|
63
|
-
// logic the grid uses (overlap + shared owner/participant) against a freshly
|
|
66
|
+
// logic the grid uses (overlap + shared owner/participant/resource) against a freshly
|
|
64
67
|
// fetched ±1-day window, so the editor warning is always consistent with the
|
|
65
68
|
// conflict badges/rings the user sees on the calendar. The warning is
|
|
66
69
|
// informational and never blocks saving.
|
|
@@ -72,9 +75,11 @@ export function useConflictProbe(
|
|
|
72
75
|
draftOwnerUserId: string | null,
|
|
73
76
|
scope: ConflictScope,
|
|
74
77
|
currentUserId: string | null,
|
|
78
|
+
draftResources: ReadonlyArray<{ id: string; label: string }> = NO_DRAFT_RESOURCES,
|
|
75
79
|
): string | null {
|
|
76
80
|
const t = useT()
|
|
77
81
|
const [conflict, setConflict] = React.useState<string | null>(null)
|
|
82
|
+
const resourcesKey = draftResources.map((resource) => resource.id).join(',')
|
|
78
83
|
// Keys on the canonical actor key, not the raw userId — an external guest has
|
|
79
84
|
// no userId, so a userId-only key never changes when guests are added or
|
|
80
85
|
// removed and the probe would keep reporting the previous attendee set.
|
|
@@ -93,23 +98,26 @@ export function useConflictProbe(
|
|
|
93
98
|
let start: Date
|
|
94
99
|
let end: Date
|
|
95
100
|
if (isAllDay) {
|
|
96
|
-
const dayDate = new Date(`${form.date}T00:00:00`)
|
|
97
|
-
if (Number.isNaN(dayDate.getTime())) {
|
|
101
|
+
const dayDate = form.timezone ? calendarDayStartInstant(form.date, form.timezone) : new Date(`${form.date}T00:00:00`)
|
|
102
|
+
if (!dayDate || Number.isNaN(dayDate.getTime())) {
|
|
98
103
|
setConflict(null)
|
|
99
104
|
return
|
|
100
105
|
}
|
|
101
|
-
start = startOfDay(dayDate)
|
|
102
|
-
|
|
106
|
+
start = form.timezone ? dayDate : startOfDay(dayDate)
|
|
107
|
+
const dayEnd = form.timezone ? calendarDayEndInstant(form.date, form.timezone) : endOfDay(dayDate)
|
|
108
|
+
if (!dayEnd) { setConflict(null); return }
|
|
109
|
+
end = dayEnd
|
|
103
110
|
} else {
|
|
104
111
|
if (!form.startTime) {
|
|
105
112
|
setConflict(null)
|
|
106
113
|
return
|
|
107
114
|
}
|
|
108
|
-
|
|
109
|
-
if (Number.isNaN(
|
|
115
|
+
const draftStart = form.timezone ? calendarWallTimeToInstant(form.date, form.startTime, form.timezone) : new Date(`${form.date}T${form.startTime}:00`)
|
|
116
|
+
if (!draftStart || Number.isNaN(draftStart.getTime())) {
|
|
110
117
|
setConflict(null)
|
|
111
118
|
return
|
|
112
119
|
}
|
|
120
|
+
start = draftStart
|
|
113
121
|
const durationMinutes = config.hasEnd ? computeDurationMinutes(form) ?? 30 : 30
|
|
114
122
|
end = new Date(start.getTime() + durationMinutes * 60_000)
|
|
115
123
|
}
|
|
@@ -134,25 +142,33 @@ export function useConflictProbe(
|
|
|
134
142
|
if (!item) continue
|
|
135
143
|
others.push(...expandOccurrences(item, fetchWindow))
|
|
136
144
|
}
|
|
137
|
-
const
|
|
145
|
+
const conflicts = findEditorConflicts(
|
|
138
146
|
{
|
|
139
147
|
start,
|
|
140
148
|
end,
|
|
141
149
|
ownerUserId: draftOwnerUserId,
|
|
142
150
|
participants: form.participants.map((participant) => ({ userId: participant.userId, name: participant.name })),
|
|
143
151
|
status: form.status,
|
|
152
|
+
resources: [...draftResources],
|
|
153
|
+
ownerLabel: draftOwnerUserId && draftOwnerUserId === form.assigneeUserId ? form.assigneeName : null,
|
|
144
154
|
},
|
|
145
155
|
others,
|
|
146
156
|
excludeId,
|
|
147
157
|
{ scope, currentUserId },
|
|
148
158
|
)
|
|
149
159
|
if (!active) return
|
|
150
|
-
if (
|
|
160
|
+
if (conflicts.length === 0) {
|
|
151
161
|
setConflict(null)
|
|
152
162
|
return
|
|
153
163
|
}
|
|
154
|
-
|
|
155
|
-
|
|
164
|
+
// Name who and what is double-booked: an overlap alone does not tell the
|
|
165
|
+
// user which attendee or room to change.
|
|
166
|
+
const summary = conflicts
|
|
167
|
+
.map(({ item, shared }) => {
|
|
168
|
+
const names = shared.map((subject) => subject.label).filter(Boolean)
|
|
169
|
+
const entry = `${formatClock(item.start)}–${formatClock(item.end)}: ${item.title || t('customers.calendar.grid.untitled', 'Untitled')}`
|
|
170
|
+
return names.length ? `${entry} (${names.join(', ')})` : entry
|
|
171
|
+
})
|
|
156
172
|
.join(', ')
|
|
157
173
|
setConflict(summary ? t('customers.calendar.editor.conflictWarning', 'Overlaps with: {items}', { items: summary }) : null)
|
|
158
174
|
} catch {
|
|
@@ -165,6 +181,6 @@ export function useConflictProbe(
|
|
|
165
181
|
controller.abort()
|
|
166
182
|
}
|
|
167
183
|
// Re-probe when schedule-relevant inputs change (time window, all-day, attendees, owner, status, scope).
|
|
168
|
-
}, [open, form.date, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
184
|
+
}, [open, form.date, form.timezone, form.startTime, form.endDate, form.endTime, form.allDay, form.status, participantsKey, resourcesKey, config.hasAllDay, config.hasEnd, excludeId, draftOwnerUserId, scope, currentUserId, t]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
169
185
|
return conflict
|
|
170
186
|
}
|
|
@@ -69,12 +69,13 @@ export function UppercaseBadge({ style, className, children }: { style?: React.C
|
|
|
69
69
|
)
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
export function AllDayToggle({ checked, onCheckedChange, label }: { checked: boolean; onCheckedChange(next: boolean): void; label: string }) {
|
|
72
|
+
export function AllDayToggle({ checked, onCheckedChange, label, disabled }: { checked: boolean; onCheckedChange(next: boolean): void; label: string; disabled?: boolean }) {
|
|
73
73
|
return (
|
|
74
74
|
<Switch
|
|
75
75
|
checked={checked}
|
|
76
76
|
aria-label={label}
|
|
77
77
|
onCheckedChange={onCheckedChange}
|
|
78
|
+
disabled={disabled}
|
|
78
79
|
className="h-6 w-10"
|
|
79
80
|
/>
|
|
80
81
|
)
|
|
@@ -112,11 +113,13 @@ export function DateControl({
|
|
|
112
113
|
onChange,
|
|
113
114
|
ariaLabel,
|
|
114
115
|
className,
|
|
116
|
+
disabled,
|
|
115
117
|
}: {
|
|
116
118
|
value: string
|
|
117
119
|
onChange(next: string): void
|
|
118
120
|
ariaLabel: string
|
|
119
121
|
className?: string
|
|
122
|
+
disabled?: boolean
|
|
120
123
|
}) {
|
|
121
124
|
const [open, setOpen] = React.useState(false)
|
|
122
125
|
useCloseOnEditorScroll(setOpen)
|
|
@@ -128,6 +131,7 @@ export function DateControl({
|
|
|
128
131
|
open={open}
|
|
129
132
|
onOpenChange={setOpen}
|
|
130
133
|
aria-label={ariaLabel}
|
|
134
|
+
disabled={disabled}
|
|
131
135
|
className={cn('w-full', className)}
|
|
132
136
|
/>
|
|
133
137
|
)
|
|
@@ -140,13 +144,18 @@ const TIME_OPTIONS: string[] = Array.from({ length: 48 }, (_, index) => {
|
|
|
140
144
|
return `${String(hour).padStart(2, '0')}:${minute}`
|
|
141
145
|
})
|
|
142
146
|
|
|
143
|
-
export function TimeControl({ value, onChange, ariaLabel }: { value: string; onChange(next: string): void; ariaLabel: string }) {
|
|
147
|
+
export function TimeControl({ value, onChange, ariaLabel, disabled }: { value: string; onChange(next: string): void; ariaLabel: string; disabled?: boolean }) {
|
|
144
148
|
const [open, setOpen] = React.useState(false)
|
|
145
149
|
useCloseOnEditorScroll(setOpen)
|
|
146
|
-
//
|
|
147
|
-
|
|
150
|
+
// Radix renders every item even while the list is closed, so a closed picker
|
|
151
|
+
// mounts only the selected time and the full grid is built on open. An
|
|
152
|
+
// off-grid value (e.g. an imported 22:15) stays selectable.
|
|
153
|
+
const options = React.useMemo(() => {
|
|
154
|
+
if (!open) return value ? [value] : []
|
|
155
|
+
return value && !TIME_OPTIONS.includes(value) ? [value, ...TIME_OPTIONS] : TIME_OPTIONS
|
|
156
|
+
}, [open, value])
|
|
148
157
|
return (
|
|
149
|
-
<Select value={value} onValueChange={onChange} open={open} onOpenChange={setOpen}>
|
|
158
|
+
<Select value={value} onValueChange={onChange} open={open} onOpenChange={setOpen} disabled={disabled}>
|
|
150
159
|
<SelectTrigger aria-label={ariaLabel} className="h-9 w-32 shrink-0">
|
|
151
160
|
<SelectValue />
|
|
152
161
|
</SelectTrigger>
|
|
@@ -165,12 +174,14 @@ export function PersonChip({
|
|
|
165
174
|
compact,
|
|
166
175
|
onRemove,
|
|
167
176
|
removeLabel,
|
|
177
|
+
disabled,
|
|
168
178
|
}: {
|
|
169
179
|
name: string
|
|
170
180
|
badge?: React.ReactNode
|
|
171
181
|
compact?: boolean
|
|
172
182
|
onRemove?: () => void
|
|
173
183
|
removeLabel?: string
|
|
184
|
+
disabled?: boolean
|
|
174
185
|
}) {
|
|
175
186
|
return (
|
|
176
187
|
<span
|
|
@@ -188,6 +199,7 @@ export function PersonChip({
|
|
|
188
199
|
size="xs"
|
|
189
200
|
onClick={(event) => { event.stopPropagation(); onRemove() }}
|
|
190
201
|
aria-label={removeLabel}
|
|
202
|
+
disabled={disabled}
|
|
191
203
|
className="size-5 shrink-0"
|
|
192
204
|
>
|
|
193
205
|
<Plus aria-hidden className="size-3.5 rotate-45 opacity-50" />
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { buildFormFieldsFromCustomFields, fetchCustomFieldFormStructure } from '@open-mercato/ui/backend/utils/customFieldForms'
|
|
5
|
+
import { filterCustomFieldDefs, type CustomFieldDefDto } from '@open-mercato/ui/backend/utils/customFieldDefs'
|
|
6
|
+
import { validateValuesAgainstDefs } from '@open-mercato/shared/modules/entities/validation'
|
|
7
|
+
|
|
8
|
+
export function selectCalendarCustomFields(definitions: CustomFieldDefDto[], fieldsetIds: readonly string[]) {
|
|
9
|
+
if (!fieldsetIds.length) return filterCustomFieldDefs(definitions, 'form')
|
|
10
|
+
const allowed = new Set(fieldsetIds)
|
|
11
|
+
return filterCustomFieldDefs(definitions, 'form').filter((definition) => {
|
|
12
|
+
const fieldsets = definition.fieldsets?.length ? definition.fieldsets : definition.fieldset ? [definition.fieldset] : []
|
|
13
|
+
return fieldsets.length ? fieldsets.some((fieldset) => allowed.has(fieldset)) : allowed.has('__general__')
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function projectCalendarCustomValues(values: Record<string, unknown>, definitions: CustomFieldDefDto[]) {
|
|
18
|
+
const activeIds = new Set(definitions.map((definition) => `cf_${definition.key}`))
|
|
19
|
+
return Object.fromEntries(Object.entries(values).filter(([key]) => !key.startsWith('cf_') || activeIds.has(key)))
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function validateCalendarCustomValues(values: Record<string, unknown>, definitions: CustomFieldDefDto[]) {
|
|
23
|
+
const customValues = Object.fromEntries(definitions.map((definition) => [definition.key, values[`cf_${definition.key}`]]))
|
|
24
|
+
return validateValuesAgainstDefs(customValues, definitions.map((definition) => ({
|
|
25
|
+
key: definition.key,
|
|
26
|
+
kind: definition.kind,
|
|
27
|
+
configJson: { validation: definition.validation ?? [] },
|
|
28
|
+
})))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function useCalendarCustomFields(open: boolean, entityId: string, fieldsetIds: readonly string[]) {
|
|
32
|
+
const [state, setState] = React.useState<{ definitions: CustomFieldDefDto[]; status: 'loading' | 'ready' | 'error' }>({
|
|
33
|
+
definitions: [], status: 'loading',
|
|
34
|
+
})
|
|
35
|
+
React.useEffect(() => {
|
|
36
|
+
if (!open) return
|
|
37
|
+
let active = true
|
|
38
|
+
setState({ definitions: [], status: 'loading' })
|
|
39
|
+
void fetchCustomFieldFormStructure([entityId]).then(({ definitions }) => {
|
|
40
|
+
if (active) setState({ definitions, status: 'ready' })
|
|
41
|
+
}).catch(() => {
|
|
42
|
+
if (active) setState({ definitions: [], status: 'error' })
|
|
43
|
+
})
|
|
44
|
+
return () => { active = false }
|
|
45
|
+
}, [open, entityId])
|
|
46
|
+
const definitions = React.useMemo(() => selectCalendarCustomFields(state.definitions, fieldsetIds), [state.definitions, fieldsetIds])
|
|
47
|
+
const fields = React.useMemo(() => buildFormFieldsFromCustomFields(definitions), [definitions])
|
|
48
|
+
return { definitions, allDefinitions: state.definitions, fields, status: state.status }
|
|
49
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from 'react'
|
|
4
|
+
import { getTelemetryRuntime } from '@open-mercato/shared/lib/telemetry/runtime'
|
|
5
|
+
import { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'
|
|
6
|
+
import { calendarEventTypes, type EffectiveCalendarEventType } from '../../../calendar-event-types'
|
|
7
|
+
import type { CalendarEventTypeCatalog, ScopedCalendarEventType } from '../../../lib/calendar/eventTypeResolver'
|
|
8
|
+
import { KIND_CONFIG, type EditorKindConfig, type EditorTypeOption } from '../../../lib/calendar/editorPayload'
|
|
9
|
+
|
|
10
|
+
type CatalogState =
|
|
11
|
+
| { status: 'loading' | 'error'; items: readonly ScopedCalendarEventType[] }
|
|
12
|
+
| { status: 'ready'; items: readonly ScopedCalendarEventType[] }
|
|
13
|
+
|
|
14
|
+
export function useEventTypeCatalog(open: boolean) {
|
|
15
|
+
const [state, setState] = React.useState<CatalogState>({ status: 'loading', items: [] })
|
|
16
|
+
const [attempt, setAttempt] = React.useState(0)
|
|
17
|
+
|
|
18
|
+
React.useEffect(() => {
|
|
19
|
+
if (!open) {
|
|
20
|
+
setState({ status: 'loading', items: [] })
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
let active = true
|
|
24
|
+
setState({ status: 'loading', items: [] })
|
|
25
|
+
void readApiResultOrThrow<CalendarEventTypeCatalog>('/api/customers/activity-types')
|
|
26
|
+
.then((catalog) => {
|
|
27
|
+
if (!Array.isArray(catalog.items)) throw new Error('[internal] Invalid activity type catalog')
|
|
28
|
+
if (active) setState({ status: 'ready', items: catalog.items })
|
|
29
|
+
})
|
|
30
|
+
.catch((error) => {
|
|
31
|
+
if (!active) return
|
|
32
|
+
getTelemetryRuntime()?.reportError(error, {
|
|
33
|
+
module: 'customers',
|
|
34
|
+
code: 'customers.activity_type_catalog_load_failed',
|
|
35
|
+
})
|
|
36
|
+
setState({ status: 'error', items: [] })
|
|
37
|
+
})
|
|
38
|
+
return () => { active = false }
|
|
39
|
+
}, [open, attempt])
|
|
40
|
+
|
|
41
|
+
const retry = React.useCallback(() => setAttempt((value) => value + 1), [])
|
|
42
|
+
|
|
43
|
+
return { ...state, retry }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function selectedEventType(
|
|
47
|
+
items: readonly ScopedCalendarEventType[],
|
|
48
|
+
key: string,
|
|
49
|
+
): EffectiveCalendarEventType {
|
|
50
|
+
const normalizedKey = key.trim().toLowerCase()
|
|
51
|
+
const selected = items.find((item) => item.key === normalizedKey)
|
|
52
|
+
if (selected) return selected
|
|
53
|
+
const baseline = calendarEventTypes.find((item) => item.key === normalizedKey) ?? calendarEventTypes[0]!
|
|
54
|
+
return {
|
|
55
|
+
...baseline,
|
|
56
|
+
key,
|
|
57
|
+
label: key,
|
|
58
|
+
provenance: {},
|
|
59
|
+
historical: true,
|
|
60
|
+
fallbackReason: 'unknown',
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isSelectableEventType(items: readonly ScopedCalendarEventType[], key: string): boolean {
|
|
65
|
+
const normalizedKey = key.trim().toLowerCase()
|
|
66
|
+
return items.some((item) => item.key === normalizedKey && item.selectable && !item.historical)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function eventTypeOptions(
|
|
70
|
+
items: readonly ScopedCalendarEventType[],
|
|
71
|
+
selectedKey: string,
|
|
72
|
+
translate: (key: string, fallback: string) => string,
|
|
73
|
+
): EditorTypeOption[] {
|
|
74
|
+
const displayLabel = (item: ScopedCalendarEventType) => item.labelKey
|
|
75
|
+
? translate(item.labelKey, item.label)
|
|
76
|
+
: item.label
|
|
77
|
+
const normalizedSelectedKey = selectedKey.trim().toLowerCase()
|
|
78
|
+
const options = items
|
|
79
|
+
.filter((item) => item.selectable && !item.historical)
|
|
80
|
+
.sort((left, right) => left.behavior.order - right.behavior.order)
|
|
81
|
+
.map((item) => ({ value: item.key === normalizedSelectedKey ? selectedKey : item.key, label: displayLabel(item), icon: item.icon ?? null }))
|
|
82
|
+
if (!options.some((option) => option.value === selectedKey)) {
|
|
83
|
+
const current = items.find((item) => item.key === normalizedSelectedKey)
|
|
84
|
+
options.unshift({ value: selectedKey, label: current ? displayLabel(current) : selectedKey, icon: current?.icon ?? null })
|
|
85
|
+
}
|
|
86
|
+
return options
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function eventTypeConfig(definition: EffectiveCalendarEventType): EditorKindConfig {
|
|
90
|
+
const { baseKind, fields } = definition.behavior
|
|
91
|
+
return {
|
|
92
|
+
dateLabel: KIND_CONFIG[baseKind].dateLabel,
|
|
93
|
+
hasEnd: fields.endTime,
|
|
94
|
+
hasAllDay: fields.allDay,
|
|
95
|
+
hasRepeat: fields.recurrence,
|
|
96
|
+
location: fields.location === 'none' ? null : fields.location,
|
|
97
|
+
people: fields.people === 'none' ? null : fields.people === 'recipients' ? 'to' : fields.people,
|
|
98
|
+
hasPriority: fields.priority,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -33,6 +33,7 @@ export const calendarInteractionPayloadSchema = z
|
|
|
33
33
|
scheduledAt: z.string().nullable().optional(),
|
|
34
34
|
occurredAt: z.string().nullable().optional(),
|
|
35
35
|
durationMinutes: z.number().nullable().optional(),
|
|
36
|
+
timezone: z.string().nullable().optional(),
|
|
36
37
|
allDay: z.boolean().nullable().optional(),
|
|
37
38
|
location: z.string().nullable().optional(),
|
|
38
39
|
participants: z.array(calendarParticipantSchema).nullable().optional(),
|