@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,4 +1,6 @@
|
|
|
1
|
+
import { calendarEventTypeKeySchema, resolveCalendarEventType } from "../../calendar-event-types.js";
|
|
1
2
|
import { parseRecurrenceRule } from "./recurrence.js";
|
|
3
|
+
import { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, calendarWallTimeToInstant, defaultCalendarTimezone, isCalendarTimezone } from "./timezone.js";
|
|
2
4
|
const EDITOR_KINDS = ["meeting", "call", "email", "note", "event", "task"];
|
|
3
5
|
const KIND_CONFIG = {
|
|
4
6
|
meeting: { dateLabel: "starts", hasEnd: true, hasAllDay: true, hasRepeat: true, location: "location", people: "attendees", hasPriority: false },
|
|
@@ -21,6 +23,9 @@ const KIND_BY_INTERACTION_TYPE = {
|
|
|
21
23
|
deadline: "task"
|
|
22
24
|
};
|
|
23
25
|
function editorKindOfInteractionType(interactionType) {
|
|
26
|
+
const parsedKey = calendarEventTypeKeySchema.safeParse(interactionType);
|
|
27
|
+
const effectiveKind = parsedKey.success ? resolveCalendarEventType(parsedKey.data)?.behavior.baseKind : void 0;
|
|
28
|
+
if (effectiveKind) return effectiveKind;
|
|
24
29
|
return KIND_BY_INTERACTION_TYPE[interactionType] ?? "meeting";
|
|
25
30
|
}
|
|
26
31
|
const EDITOR_KIND_ICONS = {
|
|
@@ -113,6 +118,7 @@ function createDefaultFormState(defaultDate, now = /* @__PURE__ */ new Date(), r
|
|
|
113
118
|
}
|
|
114
119
|
return {
|
|
115
120
|
kind: "meeting",
|
|
121
|
+
timezone: defaultCalendarTimezone(),
|
|
116
122
|
title: "",
|
|
117
123
|
relatedTo: null,
|
|
118
124
|
dealId: null,
|
|
@@ -151,22 +157,27 @@ function buildRecurrenceRule(state) {
|
|
|
151
157
|
}
|
|
152
158
|
if (state.repeatEndType === "count") rule += `;COUNT=${state.repeatCount}`;
|
|
153
159
|
if (state.repeatEndType === "date" && state.repeatUntilDate) {
|
|
154
|
-
|
|
160
|
+
const until = state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : null;
|
|
161
|
+
rule += `;UNTIL=${until ? until.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z") : `${state.repeatUntilDate.replace(/-/g, "")}T235959Z`}`;
|
|
155
162
|
}
|
|
156
163
|
return rule;
|
|
157
164
|
}
|
|
158
165
|
function computeDurationMinutes(state) {
|
|
159
|
-
const start = /* @__PURE__ */ new Date(`${state.date}T${state.startTime}:00`);
|
|
160
|
-
const end = /* @__PURE__ */ new Date(`${state.endDate}T${state.endTime}:00`);
|
|
161
|
-
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null;
|
|
166
|
+
const start = state.timezone ? calendarWallTimeToInstant(state.date, state.startTime, state.timezone) : /* @__PURE__ */ new Date(`${state.date}T${state.startTime}:00`);
|
|
167
|
+
const end = state.timezone ? calendarWallTimeToInstant(state.endDate, state.endTime, state.timezone) : /* @__PURE__ */ new Date(`${state.endDate}T${state.endTime}:00`);
|
|
168
|
+
if (!start || !end || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null;
|
|
162
169
|
const minutes = Math.round((end.getTime() - start.getTime()) / 6e4);
|
|
163
170
|
return minutes > 0 ? minutes : null;
|
|
164
171
|
}
|
|
165
172
|
function buildInteractionPayload(state, options) {
|
|
166
|
-
const config = KIND_CONFIG[state.kind];
|
|
173
|
+
const config = options.config ?? KIND_CONFIG[state.kind];
|
|
167
174
|
const time = state.allDay && config.hasAllDay ? "00:00" : state.startTime;
|
|
168
|
-
const
|
|
175
|
+
const instant = state.timezone ? state.allDay && config.hasAllDay ? calendarDayStartInstant(state.date, state.timezone) : calendarWallTimeToInstant(state.date, time, state.timezone) : /* @__PURE__ */ new Date(`${state.date}T${time}:00`);
|
|
176
|
+
if (!instant || !Number.isFinite(instant.getTime())) throw new Error("[internal] Invalid calendar local time");
|
|
177
|
+
const scheduledAt = instant.toISOString();
|
|
169
178
|
const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null;
|
|
179
|
+
const recurrenceEnd = recurrenceRule && state.repeatEndType === "date" && state.repeatUntilDate ? state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : new Date(state.repeatUntilDate) : null;
|
|
180
|
+
if (recurrenceRule && state.repeatEndType === "date" && state.repeatUntilDate && !recurrenceEnd) throw new Error("[internal] Invalid calendar recurrence day");
|
|
170
181
|
const payload = {
|
|
171
182
|
...options.mode === "edit" && options.id ? { id: options.id } : {},
|
|
172
183
|
entityId: state.relatedTo?.id ?? null,
|
|
@@ -178,11 +189,12 @@ function buildInteractionPayload(state, options) {
|
|
|
178
189
|
date: state.date,
|
|
179
190
|
time,
|
|
180
191
|
scheduledAt,
|
|
192
|
+
...state.timezone ? { timezone: state.timezone } : {},
|
|
181
193
|
durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,
|
|
182
194
|
allDay: config.hasAllDay ? state.allDay : null,
|
|
183
195
|
location: config.location ? state.location.trim() || null : null,
|
|
184
196
|
recurrenceRule,
|
|
185
|
-
recurrenceEnd:
|
|
197
|
+
recurrenceEnd: recurrenceEnd?.toISOString() ?? null,
|
|
186
198
|
participants: config.people && config.people !== "assignee" && state.participants.length > 0 ? state.participants.map((participant) => ({
|
|
187
199
|
userId: participant.userId,
|
|
188
200
|
name: participant.name,
|
|
@@ -209,10 +221,11 @@ function readUnknownString(value) {
|
|
|
209
221
|
function readUnknownNumber(value) {
|
|
210
222
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
211
223
|
}
|
|
212
|
-
function parseRepeatFromRule(rawRule, start) {
|
|
224
|
+
function parseRepeatFromRule(rawRule, start, timezone) {
|
|
225
|
+
const wallStart = timezone ? /* @__PURE__ */ new Date(`${calendarInstantToWallTime(start, timezone).date}T12:00:00`) : start;
|
|
213
226
|
const fallback = {
|
|
214
227
|
repeatFreq: "none",
|
|
215
|
-
repeatDays: defaultRepeatDays(
|
|
228
|
+
repeatDays: defaultRepeatDays(wallStart),
|
|
216
229
|
repeatEndType: "never",
|
|
217
230
|
repeatCount: 8,
|
|
218
231
|
repeatUntilDate: ""
|
|
@@ -221,7 +234,7 @@ function parseRepeatFromRule(rawRule, start) {
|
|
|
221
234
|
if (!ruleText) return fallback;
|
|
222
235
|
const parsed = parseRecurrenceRule(ruleText);
|
|
223
236
|
if (!parsed) return fallback;
|
|
224
|
-
const repeatDays = defaultRepeatDays(
|
|
237
|
+
const repeatDays = defaultRepeatDays(wallStart);
|
|
225
238
|
if (parsed.byDay) {
|
|
226
239
|
repeatDays.fill(false);
|
|
227
240
|
for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true;
|
|
@@ -234,7 +247,7 @@ function parseRepeatFromRule(rawRule, start) {
|
|
|
234
247
|
repeatCount = parsed.count;
|
|
235
248
|
} else if (parsed.until) {
|
|
236
249
|
repeatEndType = "date";
|
|
237
|
-
repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`;
|
|
250
|
+
repeatUntilDate = timezone ? calendarInstantToWallTime(parsed.until, timezone).date : `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`;
|
|
238
251
|
}
|
|
239
252
|
return {
|
|
240
253
|
repeatFreq: parsed.freq === "DAILY" ? "daily" : "weekly",
|
|
@@ -260,6 +273,9 @@ function parseItemToFormState(item) {
|
|
|
260
273
|
const kind = editorKindOfInteractionType(item.interactionType);
|
|
261
274
|
const raw = item.raw;
|
|
262
275
|
const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities);
|
|
276
|
+
const timezone = isCalendarTimezone(raw.timezone) ? raw.timezone : defaultCalendarTimezone();
|
|
277
|
+
const startWall = calendarInstantToWallTime(item.start, timezone);
|
|
278
|
+
const endWall = calendarInstantToWallTime(item.end, timezone);
|
|
263
279
|
return {
|
|
264
280
|
kind,
|
|
265
281
|
title: item.title,
|
|
@@ -267,11 +283,13 @@ function parseItemToFormState(item) {
|
|
|
267
283
|
dealId: item.dealId,
|
|
268
284
|
dealLabel: null,
|
|
269
285
|
allDay: item.allDay,
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
286
|
+
timezone,
|
|
287
|
+
date: startWall.date,
|
|
288
|
+
startTime: startWall.time,
|
|
289
|
+
endDate: endWall.date,
|
|
290
|
+
endTime: endWall.time,
|
|
291
|
+
...parseRepeatFromRule(item.raw.recurrenceRule, item.start, isCalendarTimezone(raw.timezone) ? timezone : void 0),
|
|
292
|
+
...item.raw.recurrenceEnd && isCalendarTimezone(raw.timezone) ? { repeatUntilDate: calendarInstantToWallTime(new Date(item.raw.recurrenceEnd), timezone).date } : {},
|
|
275
293
|
category: item.interactionType,
|
|
276
294
|
location: item.location ?? "",
|
|
277
295
|
participants: parseParticipants(item),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/modules/customers/lib/calendar/editorPayload.ts"],
|
|
4
|
-
"sourcesContent": ["import type { CalendarItem } from '../../components/calendar/types'\nimport { parseRecurrenceRule } from './recurrence'\n\nexport type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'\n\nexport const EDITOR_KINDS: EditorKind[] = ['meeting', 'call', 'email', 'note', 'event', 'task']\n\nexport type EditorDateLabel = 'starts' | 'when' | 'sent' | 'logged' | 'due'\n\nexport type EditorPeopleMode = 'attendees' | 'participants' | 'to' | 'assignee'\n\nexport type EditorKindConfig = {\n dateLabel: EditorDateLabel\n hasEnd: boolean\n hasAllDay: boolean\n hasRepeat: boolean\n location: 'location' | 'phoneLink' | null\n people: EditorPeopleMode | null\n hasPriority: boolean\n}\n\nexport const KIND_CONFIG: Record<EditorKind, EditorKindConfig> = {\n meeting: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n call: { dateLabel: 'when', hasEnd: false, hasAllDay: true, hasRepeat: true, location: 'phoneLink', people: 'participants', hasPriority: false },\n email: { dateLabel: 'sent', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'to', hasPriority: false },\n note: { dateLabel: 'logged', hasEnd: false, hasAllDay: false, hasRepeat: false, location: null, people: null, hasPriority: false },\n event: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n task: { dateLabel: 'due', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'assignee', hasPriority: true },\n}\n\nconst KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {\n meeting: 'meeting',\n call: 'call',\n 'video-call': 'call',\n email: 'email',\n note: 'note',\n event: 'event',\n webinar: 'event',\n task: 'task',\n todo: 'task',\n deadline: 'task',\n}\n\nexport function editorKindOfInteractionType(interactionType: string): EditorKind {\n return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'\n}\n\nexport type EditorTypeOption = { value: string; label: string; icon: string | null }\n\n/** Fallback switcher icons matching the seeded dictionary appearance icons. */\nexport const EDITOR_KIND_ICONS: Record<EditorKind, string> = {\n meeting: 'lucide:users',\n call: 'lucide:phone-call',\n email: 'lucide:mail',\n note: 'lucide:notebook',\n event: 'lucide:calendar',\n task: 'lucide:check-square',\n}\n\n/**\n * Builds the editor's type-switcher options from the tenant `activity-types`\n * dictionary (#3552) so tenant-added types are first-class calendar event types,\n * not just Category values. Field morphology per option resolves through\n * `editorKindOfInteractionType` (custom types get the meeting-shaped default).\n * Falls back to the built-in kinds when the dictionary is empty/unavailable,\n * and always includes the currently selected value (e.g. a since-deleted type\n * on an existing event). Icons come from the dictionary entry appearance with\n * the seeded kind icons as fallback.\n */\nexport function buildEditorTypeOptions(params: {\n typeLabels: Record<string, string>\n typeIcons?: Record<string, string | null>\n selectedValue: string\n kindLabels: Record<EditorKind, string>\n}): EditorTypeOption[] {\n const { typeLabels, typeIcons, selectedValue, kindLabels } = params\n const iconOf = (value: string): string | null =>\n typeIcons?.[value] ?? EDITOR_KIND_ICONS[editorKindOfInteractionType(value)] ?? null\n const dictionaryEntries = Object.entries(typeLabels)\n const options: EditorTypeOption[] = dictionaryEntries.length\n ? dictionaryEntries.map(([value, label]) => ({ value, label, icon: iconOf(value) }))\n : EDITOR_KINDS.map((kind) => ({ value: kind, label: kindLabels[kind], icon: EDITOR_KIND_ICONS[kind] }))\n if (!options.some((option) => option.value === selectedValue)) {\n options.unshift({\n value: selectedValue,\n label: typeLabels[selectedValue] ?? kindLabels[editorKindOfInteractionType(selectedValue)] ?? selectedValue,\n icon: iconOf(selectedValue),\n })\n }\n return options\n}\n\nexport type EditorRepeatFreq = 'none' | 'daily' | 'weekly'\n\nexport type EditorRepeatEndType = 'never' | 'date' | 'count'\n\nexport type EditorPriority = 'low' | 'medium' | 'high'\n\nexport const PRIORITY_NUMBER: Record<EditorPriority, number> = { low: 10, medium: 50, high: 90 }\n\nexport function priorityFromNumber(value: number | null | undefined): EditorPriority {\n if (typeof value !== 'number' || Number.isNaN(value)) return 'medium'\n if (value <= 33) return 'low'\n if (value <= 66) return 'medium'\n return 'high'\n}\n\nexport type EditorRelatedTo = {\n id: string\n kind: 'person' | 'company' | 'unknown'\n label: string\n}\n\nexport type EditorParticipant = {\n userId?: string\n name: string\n email?: string\n isCustomer: boolean\n status?: string\n}\n\nconst CUSTOMER_PARTICIPANT_STATUS = 'customer'\n\nfunction resolveParticipantStatus(participant: EditorParticipant): string {\n if (participant.isCustomer) return CUSTOMER_PARTICIPANT_STATUS\n if (participant.status && participant.status !== CUSTOMER_PARTICIPANT_STATUS) return participant.status\n return 'pending'\n}\n\n/** linkedEntities `type` marker for resource assignments (FK-id + label snapshot). */\nexport const RESOURCE_LINK_TYPE = 'resource'\n\nexport type EditorLinkedEntity = { id: string; type: string; label: string }\n\nexport type EditorResource = { id: string; label: string }\n\nexport function parseLinkedEntities(raw: unknown): {\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n} {\n const resources: EditorResource[] = []\n const preservedLinkedEntities: EditorLinkedEntity[] = []\n if (!Array.isArray(raw)) return { resources, preservedLinkedEntities }\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, type, label } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id.length || typeof type !== 'string' || !type.length) continue\n const safeLabel = typeof label === 'string' ? label : ''\n if (type === RESOURCE_LINK_TYPE) resources.push({ id, label: safeLabel || id })\n else preservedLinkedEntities.push({ id, type, label: safeLabel })\n }\n return { resources, preservedLinkedEntities }\n}\n\nexport type EditorFormState = {\n kind: EditorKind\n title: string\n relatedTo: EditorRelatedTo | null\n dealId: string | null\n dealLabel: string | null\n allDay: boolean\n date: string\n startTime: string\n endDate: string\n endTime: string\n repeatFreq: EditorRepeatFreq\n repeatDays: boolean[]\n repeatEndType: EditorRepeatEndType\n repeatCount: number\n repeatUntilDate: string\n category: string | null\n location: string\n participants: EditorParticipant[]\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n assigneeUserId: string | null\n assigneeName: string | null\n priority: EditorPriority\n description: string\n status: 'planned' | 'done' | 'canceled'\n}\n\nconst MO_FIRST_DAY_TOKENS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] as const\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nexport function formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nexport function formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\nfunction moFirstIndexOfDate(date: Date): number {\n return (date.getDay() + 6) % 7\n}\n\nexport function defaultRepeatDaysForDateInput(dateInput: string): boolean[] {\n const parsed = new Date(`${dateInput}T00:00:00`)\n return defaultRepeatDays(Number.isNaN(parsed.getTime()) ? new Date() : parsed)\n}\n\nfunction defaultRepeatDays(start: Date): boolean[] {\n const days = [false, false, false, false, false, false, false]\n days[moFirstIndexOfDate(start)] = true\n return days\n}\n\n/**\n * Resolves the `ownerUserId` an interaction will actually be SAVED with, so the\n * editor's conflict probe keys on the same owner the grid sees after save (see\n * buildInteractionPayload + the create/update command):\n * - Task (people === 'assignee'): owner = the assignee.\n * - Other kinds: the payload omits owner, so edit preserves the existing owner\n * and create is ownerless (null) \u2014 conflicts then come from participants only.\n */\nexport function resolveSavedOwnerUserId(\n config: EditorKindConfig,\n form: EditorFormState,\n isEdit: boolean,\n existingOwnerUserId: string | null,\n): string | null {\n if (config.people === 'assignee') return form.assigneeUserId ?? null\n return isEdit ? existingOwnerUserId : null\n}\n\nexport function createDefaultFormState(\n defaultDate?: Date | null,\n now: Date = new Date(),\n range?: { start: Date; end: Date } | null,\n): EditorFormState {\n let base: Date\n let end: Date\n if (range) {\n base = new Date(range.start)\n end = new Date(range.end)\n } else {\n base = defaultDate ? new Date(defaultDate) : new Date(now)\n base.setHours(now.getHours(), 0, 0, 0)\n base.setHours(base.getHours() + 1)\n end = new Date(base.getTime() + 90 * 60_000)\n }\n return {\n kind: 'meeting',\n title: '',\n relatedTo: null,\n dealId: null,\n dealLabel: null,\n allDay: false,\n date: formatLocalDateInput(base),\n startTime: formatLocalTimeInput(base),\n endDate: formatLocalDateInput(end),\n endTime: formatLocalTimeInput(end),\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(base),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n category: null,\n location: '',\n participants: [],\n resources: [],\n preservedLinkedEntities: [],\n assigneeUserId: null,\n assigneeName: null,\n priority: 'medium',\n description: '',\n status: 'planned',\n }\n}\n\nexport function buildRecurrenceRule(state: EditorFormState): string | null {\n if (state.repeatFreq === 'none') return null\n let rule: string\n if (state.repeatFreq === 'daily') {\n rule = 'FREQ=DAILY'\n } else {\n const selected = MO_FIRST_DAY_TOKENS.filter((_, index) => state.repeatDays[index])\n const tokens = selected.length > 0\n ? selected\n : [MO_FIRST_DAY_TOKENS[moFirstIndexOfDate(new Date(`${state.date}T00:00:00`))]]\n rule = `FREQ=WEEKLY;BYDAY=${tokens.join(',')}`\n }\n if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`\n if (state.repeatEndType === 'date' && state.repeatUntilDate) {\n rule += `;UNTIL=${state.repeatUntilDate.replace(/-/g, '')}T235959Z`\n }\n return rule\n}\n\nexport function computeDurationMinutes(state: EditorFormState): number | null {\n const start = new Date(`${state.date}T${state.startTime}:00`)\n const end = new Date(`${state.endDate}T${state.endTime}:00`)\n if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null\n const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)\n return minutes > 0 ? minutes : null\n}\n\nexport type BuildPayloadOptions = {\n mode: 'create' | 'edit'\n id?: string\n /**\n * When the resources module is loaded the payload owns `linkedEntities`\n * (preserved non-resource links + current resource assignments). When it is\n * not, the key is omitted entirely so edits never clobber links written by\n * other surfaces.\n */\n resourcesEnabled?: boolean\n /**\n * When the staff module is absent the Assignee field is hidden, so the\n * payload omits `ownerUserId` (edit keeps the stored owner untouched).\n */\n staffEnabled?: boolean\n}\n\nexport function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {\n const config = KIND_CONFIG[state.kind]\n const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime\n const scheduledAt = new Date(`${state.date}T${time}:00`).toISOString()\n const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null\n const payload: Record<string, unknown> = {\n ...(options.mode === 'edit' && options.id ? { id: options.id } : {}),\n entityId: state.relatedTo?.id ?? null,\n dealId: state.dealId ?? null,\n interactionType: state.category ?? state.kind,\n title: state.title.trim(),\n body: state.description.trim() || null,\n status: options.mode === 'create' ? 'planned' : state.status,\n date: state.date,\n time,\n scheduledAt,\n durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,\n allDay: config.hasAllDay ? state.allDay : null,\n location: config.location ? state.location.trim() || null : null,\n recurrenceRule,\n recurrenceEnd:\n recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate\n ? new Date(state.repeatUntilDate).toISOString()\n : null,\n participants:\n config.people && config.people !== 'assignee' && state.participants.length > 0\n ? state.participants.map((participant) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n status: resolveParticipantStatus(participant),\n }))\n : null,\n }\n if (config.people === 'assignee' && (options.staffEnabled ?? true)) {\n payload.ownerUserId = state.assigneeUserId ?? null\n }\n if (config.hasPriority) payload.priority = PRIORITY_NUMBER[state.priority]\n if (options.resourcesEnabled) {\n const linkedEntities: EditorLinkedEntity[] = [\n ...state.preservedLinkedEntities,\n ...state.resources.map((resource) => ({ id: resource.id, type: RESOURCE_LINK_TYPE, label: resource.label })),\n ]\n payload.linkedEntities = linkedEntities.length > 0 ? linkedEntities : null\n }\n return payload\n}\n\nfunction readUnknownString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction readUnknownNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null\n}\n\ntype ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>\n\nfunction parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {\n const fallback: ParsedRepeat = {\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(start),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n }\n const ruleText = readUnknownString(rawRule)\n if (!ruleText) return fallback\n const parsed = parseRecurrenceRule(ruleText)\n if (!parsed) return fallback\n const repeatDays = defaultRepeatDays(start)\n if (parsed.byDay) {\n repeatDays.fill(false)\n for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true\n }\n let repeatEndType: EditorRepeatEndType = 'never'\n let repeatCount = 8\n let repeatUntilDate = ''\n if (parsed.count !== null) {\n repeatEndType = 'count'\n repeatCount = parsed.count\n } else if (parsed.until) {\n repeatEndType = 'date'\n repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`\n }\n return {\n repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',\n repeatDays,\n repeatEndType,\n repeatCount,\n repeatUntilDate,\n }\n}\n\nfunction parseParticipants(item: CalendarItem): EditorParticipant[] {\n return item.participants.map((participant) => {\n const status = readUnknownString(participant.status)\n return {\n userId: participant.userId,\n name: participant.name ?? participant.email ?? participant.userId ?? '',\n email: participant.email,\n isCustomer: Boolean(participant.userId) && status === CUSTOMER_PARTICIPANT_STATUS,\n ...(status ? { status } : {}),\n }\n })\n}\n\nexport function parseItemToFormState(item: CalendarItem): EditorFormState {\n const kind = editorKindOfInteractionType(item.interactionType)\n const raw = item.raw as Record<string, unknown>\n const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities)\n return {\n kind,\n title: item.title,\n relatedTo: item.entityId ? { id: item.entityId, kind: 'unknown', label: '' } : null,\n dealId: item.dealId,\n dealLabel: null,\n allDay: item.allDay,\n date: formatLocalDateInput(item.start),\n startTime: formatLocalTimeInput(item.start),\n endDate: formatLocalDateInput(item.end),\n endTime: formatLocalTimeInput(item.end),\n ...parseRepeatFromRule(item.raw.recurrenceRule, item.start),\n category: item.interactionType,\n location: item.location ?? '',\n participants: parseParticipants(item),\n resources,\n preservedLinkedEntities,\n assigneeUserId: item.ownerUserId,\n assigneeName: null,\n priority: priorityFromNumber(readUnknownNumber(raw.priority)),\n description: readUnknownString(raw.body) ?? '',\n status: item.status,\n }\n}\n"],
|
|
5
|
-
"mappings": "AACA,SAAS,2BAA2B;
|
|
4
|
+
"sourcesContent": ["import type { CalendarItem } from '../../components/calendar/types'\nimport { calendarEventTypeKeySchema, resolveCalendarEventType } from '../../calendar-event-types'\nimport { parseRecurrenceRule } from './recurrence'\nimport { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, calendarWallTimeToInstant, defaultCalendarTimezone, isCalendarTimezone } from './timezone'\n\nexport type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'\n\nexport const EDITOR_KINDS: EditorKind[] = ['meeting', 'call', 'email', 'note', 'event', 'task']\n\nexport type EditorDateLabel = 'starts' | 'when' | 'sent' | 'logged' | 'due'\n\nexport type EditorPeopleMode = 'attendees' | 'participants' | 'to' | 'assignee'\n\nexport type EditorKindConfig = {\n dateLabel: EditorDateLabel\n hasEnd: boolean\n hasAllDay: boolean\n hasRepeat: boolean\n location: 'location' | 'phoneLink' | null\n people: EditorPeopleMode | null\n hasPriority: boolean\n}\n\nexport const KIND_CONFIG: Record<EditorKind, EditorKindConfig> = {\n meeting: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n call: { dateLabel: 'when', hasEnd: false, hasAllDay: true, hasRepeat: true, location: 'phoneLink', people: 'participants', hasPriority: false },\n email: { dateLabel: 'sent', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'to', hasPriority: false },\n note: { dateLabel: 'logged', hasEnd: false, hasAllDay: false, hasRepeat: false, location: null, people: null, hasPriority: false },\n event: { dateLabel: 'starts', hasEnd: true, hasAllDay: true, hasRepeat: true, location: 'location', people: 'attendees', hasPriority: false },\n task: { dateLabel: 'due', hasEnd: false, hasAllDay: true, hasRepeat: true, location: null, people: 'assignee', hasPriority: true },\n}\n\nconst KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {\n meeting: 'meeting',\n call: 'call',\n 'video-call': 'call',\n email: 'email',\n note: 'note',\n event: 'event',\n webinar: 'event',\n task: 'task',\n todo: 'task',\n deadline: 'task',\n}\n\n/** @deprecated Resolve the effective calendar event type and read `behavior.baseKind`. */\nexport function editorKindOfInteractionType(interactionType: string): EditorKind {\n const parsedKey = calendarEventTypeKeySchema.safeParse(interactionType)\n const effectiveKind = parsedKey.success ? resolveCalendarEventType(parsedKey.data)?.behavior.baseKind : undefined\n if (effectiveKind) return effectiveKind\n return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'\n}\n\nexport type EditorTypeOption = { value: string; label: string; icon: string | null }\n\n/** Fallback switcher icons matching the seeded dictionary appearance icons. */\nexport const EDITOR_KIND_ICONS: Record<EditorKind, string> = {\n meeting: 'lucide:users',\n call: 'lucide:phone-call',\n email: 'lucide:mail',\n note: 'lucide:notebook',\n event: 'lucide:calendar',\n task: 'lucide:check-square',\n}\n\n/**\n * Builds the editor's type-switcher options from the tenant `activity-types`\n * dictionary (#3552) so tenant-added types are first-class calendar event types,\n * not just Category values. Field morphology per option resolves through\n * `editorKindOfInteractionType` (custom types get the meeting-shaped default).\n * Falls back to the built-in kinds when the dictionary is empty/unavailable,\n * and always includes the currently selected value (e.g. a since-deleted type\n * on an existing event). Icons come from the dictionary entry appearance with\n * the seeded kind icons as fallback.\n */\nexport function buildEditorTypeOptions(params: {\n typeLabels: Record<string, string>\n typeIcons?: Record<string, string | null>\n selectedValue: string\n kindLabels: Record<EditorKind, string>\n}): EditorTypeOption[] {\n const { typeLabels, typeIcons, selectedValue, kindLabels } = params\n const iconOf = (value: string): string | null =>\n typeIcons?.[value] ?? EDITOR_KIND_ICONS[editorKindOfInteractionType(value)] ?? null\n const dictionaryEntries = Object.entries(typeLabels)\n const options: EditorTypeOption[] = dictionaryEntries.length\n ? dictionaryEntries.map(([value, label]) => ({ value, label, icon: iconOf(value) }))\n : EDITOR_KINDS.map((kind) => ({ value: kind, label: kindLabels[kind], icon: EDITOR_KIND_ICONS[kind] }))\n if (!options.some((option) => option.value === selectedValue)) {\n options.unshift({\n value: selectedValue,\n label: typeLabels[selectedValue] ?? kindLabels[editorKindOfInteractionType(selectedValue)] ?? selectedValue,\n icon: iconOf(selectedValue),\n })\n }\n return options\n}\n\nexport type EditorRepeatFreq = 'none' | 'daily' | 'weekly'\n\nexport type EditorRepeatEndType = 'never' | 'date' | 'count'\n\nexport type EditorPriority = 'low' | 'medium' | 'high'\n\nexport const PRIORITY_NUMBER: Record<EditorPriority, number> = { low: 10, medium: 50, high: 90 }\n\nexport function priorityFromNumber(value: number | null | undefined): EditorPriority {\n if (typeof value !== 'number' || Number.isNaN(value)) return 'medium'\n if (value <= 33) return 'low'\n if (value <= 66) return 'medium'\n return 'high'\n}\n\nexport type EditorRelatedTo = {\n id: string\n kind: 'person' | 'company' | 'unknown'\n label: string\n}\n\nexport type EditorParticipant = {\n userId?: string\n name: string\n email?: string\n isCustomer: boolean\n status?: string\n}\n\nconst CUSTOMER_PARTICIPANT_STATUS = 'customer'\n\nfunction resolveParticipantStatus(participant: EditorParticipant): string {\n if (participant.isCustomer) return CUSTOMER_PARTICIPANT_STATUS\n if (participant.status && participant.status !== CUSTOMER_PARTICIPANT_STATUS) return participant.status\n return 'pending'\n}\n\n/** linkedEntities `type` marker for resource assignments (FK-id + label snapshot). */\nexport const RESOURCE_LINK_TYPE = 'resource'\n\nexport type EditorLinkedEntity = { id: string; type: string; label: string }\n\nexport type EditorResource = { id: string; label: string }\n\nexport function parseLinkedEntities(raw: unknown): {\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n} {\n const resources: EditorResource[] = []\n const preservedLinkedEntities: EditorLinkedEntity[] = []\n if (!Array.isArray(raw)) return { resources, preservedLinkedEntities }\n for (const entry of raw) {\n if (!entry || typeof entry !== 'object') continue\n const { id, type, label } = entry as Record<string, unknown>\n if (typeof id !== 'string' || !id.length || typeof type !== 'string' || !type.length) continue\n const safeLabel = typeof label === 'string' ? label : ''\n if (type === RESOURCE_LINK_TYPE) resources.push({ id, label: safeLabel || id })\n else preservedLinkedEntities.push({ id, type, label: safeLabel })\n }\n return { resources, preservedLinkedEntities }\n}\n\nexport type EditorFormState = {\n kind: EditorKind\n title: string\n relatedTo: EditorRelatedTo | null\n dealId: string | null\n dealLabel: string | null\n allDay: boolean\n date: string\n startTime: string\n timezone?: string\n endDate: string\n endTime: string\n repeatFreq: EditorRepeatFreq\n repeatDays: boolean[]\n repeatEndType: EditorRepeatEndType\n repeatCount: number\n repeatUntilDate: string\n category: string | null\n location: string\n participants: EditorParticipant[]\n resources: EditorResource[]\n preservedLinkedEntities: EditorLinkedEntity[]\n assigneeUserId: string | null\n assigneeName: string | null\n priority: EditorPriority\n description: string\n status: 'planned' | 'done' | 'canceled'\n}\n\nconst MO_FIRST_DAY_TOKENS = ['MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU'] as const\n\nfunction padDatePart(value: number): string {\n return String(value).padStart(2, '0')\n}\n\nexport function formatLocalDateInput(date: Date): string {\n return `${date.getFullYear()}-${padDatePart(date.getMonth() + 1)}-${padDatePart(date.getDate())}`\n}\n\nexport function formatLocalTimeInput(date: Date): string {\n return `${padDatePart(date.getHours())}:${padDatePart(date.getMinutes())}`\n}\n\nfunction moFirstIndexOfDate(date: Date): number {\n return (date.getDay() + 6) % 7\n}\n\nexport function defaultRepeatDaysForDateInput(dateInput: string): boolean[] {\n const parsed = new Date(`${dateInput}T00:00:00`)\n return defaultRepeatDays(Number.isNaN(parsed.getTime()) ? new Date() : parsed)\n}\n\nfunction defaultRepeatDays(start: Date): boolean[] {\n const days = [false, false, false, false, false, false, false]\n days[moFirstIndexOfDate(start)] = true\n return days\n}\n\n/**\n * Resolves the `ownerUserId` an interaction will actually be SAVED with, so the\n * editor's conflict probe keys on the same owner the grid sees after save (see\n * buildInteractionPayload + the create/update command):\n * - Task (people === 'assignee'): owner = the assignee.\n * - Other kinds: the payload omits owner, so edit preserves the existing owner\n * and create is ownerless (null) \u2014 conflicts then come from participants only.\n */\nexport function resolveSavedOwnerUserId(\n config: EditorKindConfig,\n form: EditorFormState,\n isEdit: boolean,\n existingOwnerUserId: string | null,\n): string | null {\n if (config.people === 'assignee') return form.assigneeUserId ?? null\n return isEdit ? existingOwnerUserId : null\n}\n\nexport function createDefaultFormState(\n defaultDate?: Date | null,\n now: Date = new Date(),\n range?: { start: Date; end: Date } | null,\n): EditorFormState {\n let base: Date\n let end: Date\n if (range) {\n base = new Date(range.start)\n end = new Date(range.end)\n } else {\n base = defaultDate ? new Date(defaultDate) : new Date(now)\n base.setHours(now.getHours(), 0, 0, 0)\n base.setHours(base.getHours() + 1)\n end = new Date(base.getTime() + 90 * 60_000)\n }\n return {\n kind: 'meeting',\n timezone: defaultCalendarTimezone(),\n title: '',\n relatedTo: null,\n dealId: null,\n dealLabel: null,\n allDay: false,\n date: formatLocalDateInput(base),\n startTime: formatLocalTimeInput(base),\n endDate: formatLocalDateInput(end),\n endTime: formatLocalTimeInput(end),\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(base),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n category: null,\n location: '',\n participants: [],\n resources: [],\n preservedLinkedEntities: [],\n assigneeUserId: null,\n assigneeName: null,\n priority: 'medium',\n description: '',\n status: 'planned',\n }\n}\n\nexport function buildRecurrenceRule(state: EditorFormState): string | null {\n if (state.repeatFreq === 'none') return null\n let rule: string\n if (state.repeatFreq === 'daily') {\n rule = 'FREQ=DAILY'\n } else {\n const selected = MO_FIRST_DAY_TOKENS.filter((_, index) => state.repeatDays[index])\n const tokens = selected.length > 0\n ? selected\n : [MO_FIRST_DAY_TOKENS[moFirstIndexOfDate(new Date(`${state.date}T00:00:00`))]]\n rule = `FREQ=WEEKLY;BYDAY=${tokens.join(',')}`\n }\n if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`\n if (state.repeatEndType === 'date' && state.repeatUntilDate) {\n const until = state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : null\n rule += `;UNTIL=${until ? until.toISOString().replace(/[-:]/g, '').replace(/\\.\\d{3}Z$/, 'Z') : `${state.repeatUntilDate.replace(/-/g, '')}T235959Z`}`\n }\n return rule\n}\n\nexport function computeDurationMinutes(state: EditorFormState): number | null {\n const start = state.timezone ? calendarWallTimeToInstant(state.date, state.startTime, state.timezone) : new Date(`${state.date}T${state.startTime}:00`)\n const end = state.timezone ? calendarWallTimeToInstant(state.endDate, state.endTime, state.timezone) : new Date(`${state.endDate}T${state.endTime}:00`)\n if (!start || !end || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null\n const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)\n return minutes > 0 ? minutes : null\n}\n\nexport type BuildPayloadOptions = {\n mode: 'create' | 'edit'\n config?: EditorKindConfig\n id?: string\n /**\n * When the resources module is loaded the payload owns `linkedEntities`\n * (preserved non-resource links + current resource assignments). When it is\n * not, the key is omitted entirely so edits never clobber links written by\n * other surfaces.\n */\n resourcesEnabled?: boolean\n /**\n * When the staff module is absent the Assignee field is hidden, so the\n * payload omits `ownerUserId` (edit keeps the stored owner untouched).\n */\n staffEnabled?: boolean\n}\n\nexport function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {\n const config = options.config ?? KIND_CONFIG[state.kind]\n const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime\n const instant = state.timezone ? (state.allDay && config.hasAllDay ? calendarDayStartInstant(state.date, state.timezone) : calendarWallTimeToInstant(state.date, time, state.timezone)) : new Date(`${state.date}T${time}:00`)\n if (!instant || !Number.isFinite(instant.getTime())) throw new Error('[internal] Invalid calendar local time')\n const scheduledAt = instant.toISOString()\n const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null\n const recurrenceEnd = recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate\n ? state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : new Date(state.repeatUntilDate)\n : null\n if (recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate && !recurrenceEnd) throw new Error('[internal] Invalid calendar recurrence day')\n const payload: Record<string, unknown> = {\n ...(options.mode === 'edit' && options.id ? { id: options.id } : {}),\n entityId: state.relatedTo?.id ?? null,\n dealId: state.dealId ?? null,\n interactionType: state.category ?? state.kind,\n title: state.title.trim(),\n body: state.description.trim() || null,\n status: options.mode === 'create' ? 'planned' : state.status,\n date: state.date,\n time,\n scheduledAt,\n ...(state.timezone ? { timezone: state.timezone } : {}),\n durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,\n allDay: config.hasAllDay ? state.allDay : null,\n location: config.location ? state.location.trim() || null : null,\n recurrenceRule,\n recurrenceEnd: recurrenceEnd?.toISOString() ?? null,\n participants:\n config.people && config.people !== 'assignee' && state.participants.length > 0\n ? state.participants.map((participant) => ({\n userId: participant.userId,\n name: participant.name,\n email: participant.email,\n status: resolveParticipantStatus(participant),\n }))\n : null,\n }\n if (config.people === 'assignee' && (options.staffEnabled ?? true)) {\n payload.ownerUserId = state.assigneeUserId ?? null\n }\n if (config.hasPriority) payload.priority = PRIORITY_NUMBER[state.priority]\n if (options.resourcesEnabled) {\n const linkedEntities: EditorLinkedEntity[] = [\n ...state.preservedLinkedEntities,\n ...state.resources.map((resource) => ({ id: resource.id, type: RESOURCE_LINK_TYPE, label: resource.label })),\n ]\n payload.linkedEntities = linkedEntities.length > 0 ? linkedEntities : null\n }\n return payload\n}\n\nfunction readUnknownString(value: unknown): string | null {\n return typeof value === 'string' && value.length > 0 ? value : null\n}\n\nfunction readUnknownNumber(value: unknown): number | null {\n return typeof value === 'number' && Number.isFinite(value) ? value : null\n}\n\ntype ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>\n\nfunction parseRepeatFromRule(rawRule: unknown, start: Date, timezone?: string): ParsedRepeat {\n const wallStart = timezone ? new Date(`${calendarInstantToWallTime(start, timezone).date}T12:00:00`) : start\n const fallback: ParsedRepeat = {\n repeatFreq: 'none',\n repeatDays: defaultRepeatDays(wallStart),\n repeatEndType: 'never',\n repeatCount: 8,\n repeatUntilDate: '',\n }\n const ruleText = readUnknownString(rawRule)\n if (!ruleText) return fallback\n const parsed = parseRecurrenceRule(ruleText)\n if (!parsed) return fallback\n const repeatDays = defaultRepeatDays(wallStart)\n if (parsed.byDay) {\n repeatDays.fill(false)\n for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true\n }\n let repeatEndType: EditorRepeatEndType = 'never'\n let repeatCount = 8\n let repeatUntilDate = ''\n if (parsed.count !== null) {\n repeatEndType = 'count'\n repeatCount = parsed.count\n } else if (parsed.until) {\n repeatEndType = 'date'\n repeatUntilDate = timezone ? calendarInstantToWallTime(parsed.until, timezone).date : `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`\n }\n return {\n repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',\n repeatDays,\n repeatEndType,\n repeatCount,\n repeatUntilDate,\n }\n}\n\nfunction parseParticipants(item: CalendarItem): EditorParticipant[] {\n return item.participants.map((participant) => {\n const status = readUnknownString(participant.status)\n return {\n userId: participant.userId,\n name: participant.name ?? participant.email ?? participant.userId ?? '',\n email: participant.email,\n isCustomer: Boolean(participant.userId) && status === CUSTOMER_PARTICIPANT_STATUS,\n ...(status ? { status } : {}),\n }\n })\n}\n\nexport function parseItemToFormState(item: CalendarItem): EditorFormState {\n const kind = editorKindOfInteractionType(item.interactionType)\n const raw = item.raw as Record<string, unknown>\n const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities)\n const timezone = isCalendarTimezone(raw.timezone) ? raw.timezone : defaultCalendarTimezone()\n const startWall = calendarInstantToWallTime(item.start, timezone)\n const endWall = calendarInstantToWallTime(item.end, timezone)\n return {\n kind,\n title: item.title,\n relatedTo: item.entityId ? { id: item.entityId, kind: 'unknown', label: '' } : null,\n dealId: item.dealId,\n dealLabel: null,\n allDay: item.allDay,\n timezone,\n date: startWall.date,\n startTime: startWall.time,\n endDate: endWall.date,\n endTime: endWall.time,\n ...parseRepeatFromRule(item.raw.recurrenceRule, item.start, isCalendarTimezone(raw.timezone) ? timezone : undefined),\n ...(item.raw.recurrenceEnd && isCalendarTimezone(raw.timezone) ? { repeatUntilDate: calendarInstantToWallTime(new Date(item.raw.recurrenceEnd), timezone).date } : {}),\n category: item.interactionType,\n location: item.location ?? '',\n participants: parseParticipants(item),\n resources,\n preservedLinkedEntities,\n assigneeUserId: item.ownerUserId,\n assigneeName: null,\n priority: priorityFromNumber(readUnknownNumber(raw.priority)),\n description: readUnknownString(raw.body) ?? '',\n status: item.status,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { findWithDecryption } from "@open-mercato/shared/lib/encryption/find";
|
|
2
|
+
import { waitForAsyncRegistration } from "@open-mercato/shared/lib/bootstrap/factory";
|
|
3
|
+
import {
|
|
4
|
+
getInjectionRegistryVersion,
|
|
5
|
+
loadInjectionWidgetsForSpot
|
|
6
|
+
} from "@open-mercato/shared/modules/widgets/injection-loader";
|
|
7
|
+
import {
|
|
8
|
+
calendarEventTypeBehaviorSchema,
|
|
9
|
+
calendarEventTypes,
|
|
10
|
+
getCalendarEventTypes,
|
|
11
|
+
getCalendarEventTypeTombstones,
|
|
12
|
+
resolveCalendarEventType,
|
|
13
|
+
registerWidgetCalendarEventTypeContributions
|
|
14
|
+
} from "../../calendar-event-types.js";
|
|
15
|
+
import { CustomerDictionaryEntry } from "../../data/entities.js";
|
|
16
|
+
import { extensionPoints } from "../../extension-points.js";
|
|
17
|
+
function deepFreeze(value) {
|
|
18
|
+
if (value !== null && typeof value === "object" && !Object.isFrozen(value)) {
|
|
19
|
+
Object.freeze(value);
|
|
20
|
+
for (const child of Object.values(value)) deepFreeze(child);
|
|
21
|
+
}
|
|
22
|
+
return value;
|
|
23
|
+
}
|
|
24
|
+
function serializeStaticType(type) {
|
|
25
|
+
return deepFreeze({
|
|
26
|
+
key: type.key,
|
|
27
|
+
label: type.label,
|
|
28
|
+
...type.labelKey ? { labelKey: type.labelKey } : {},
|
|
29
|
+
icon: type.icon ?? null,
|
|
30
|
+
color: type.color ?? null,
|
|
31
|
+
behavior: type.behavior,
|
|
32
|
+
selectable: type.behavior.selectable,
|
|
33
|
+
...type.panelKey ? { panelKey: type.panelKey } : {},
|
|
34
|
+
source: type.provenance.key?.moduleId ?? "customers",
|
|
35
|
+
provenance: type.provenance,
|
|
36
|
+
historical: type.historical,
|
|
37
|
+
...type.fallbackReason ? { fallbackReason: type.fallbackReason } : {},
|
|
38
|
+
adminConfigurable: type.adminConfigurable !== false,
|
|
39
|
+
isInherited: false,
|
|
40
|
+
isLocalOverride: false,
|
|
41
|
+
updatedAt: null,
|
|
42
|
+
missingCustomFieldsetIds: []
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function resolveBaselineCalendarEventTypes() {
|
|
46
|
+
const types = getCalendarEventTypes();
|
|
47
|
+
return deepFreeze({ items: types.map(serializeStaticType), fallbackKey: "meeting" });
|
|
48
|
+
}
|
|
49
|
+
let loadedWidgetVersion = -1;
|
|
50
|
+
let loadingWidgets = null;
|
|
51
|
+
async function ensureCalendarEventTypeWidgetsLoaded() {
|
|
52
|
+
await waitForAsyncRegistration();
|
|
53
|
+
const version = getInjectionRegistryVersion();
|
|
54
|
+
if (loadedWidgetVersion === version) return;
|
|
55
|
+
if (loadingWidgets) return loadingWidgets;
|
|
56
|
+
const pending = (async () => {
|
|
57
|
+
const widgets = await loadInjectionWidgetsForSpot(extensionPoints.hosts.calendarEventTypes.spotId);
|
|
58
|
+
registerWidgetCalendarEventTypeContributions(widgets.map((widget) => {
|
|
59
|
+
return {
|
|
60
|
+
moduleId: widget.moduleId,
|
|
61
|
+
widgetId: widget.metadata.id,
|
|
62
|
+
priority: typeof widget.placement?.priority === "number" ? widget.placement.priority : 0,
|
|
63
|
+
definitions: "eventTypes" in widget ? widget.eventTypes : null,
|
|
64
|
+
overrides: "eventTypeOverrides" in widget ? widget.eventTypeOverrides : void 0,
|
|
65
|
+
patches: "eventTypePatches" in widget ? widget.eventTypePatches : void 0
|
|
66
|
+
};
|
|
67
|
+
}));
|
|
68
|
+
loadedWidgetVersion = version;
|
|
69
|
+
})();
|
|
70
|
+
loadingWidgets = pending;
|
|
71
|
+
try {
|
|
72
|
+
await pending;
|
|
73
|
+
} finally {
|
|
74
|
+
if (loadingWidgets === pending) loadingWidgets = null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function selectDictionaryRows(rows, organizationId, readableOrganizationIds) {
|
|
78
|
+
const orderedOrganizationIds = Array.from(/* @__PURE__ */ new Set([organizationId, ...readableOrganizationIds]));
|
|
79
|
+
const rank = new Map(orderedOrganizationIds.map((id, index) => [id, index]));
|
|
80
|
+
const sorted = [...rows].sort((left, right) => {
|
|
81
|
+
const rankDelta = (rank.get(left.organizationId) ?? Number.MAX_SAFE_INTEGER) - (rank.get(right.organizationId) ?? Number.MAX_SAFE_INTEGER);
|
|
82
|
+
return rankDelta || left.normalizedValue.localeCompare(right.normalizedValue);
|
|
83
|
+
});
|
|
84
|
+
const selected = /* @__PURE__ */ new Map();
|
|
85
|
+
for (const row of sorted) {
|
|
86
|
+
const key = row.normalizedValue.trim() || row.value.trim().toLowerCase();
|
|
87
|
+
if (key && !selected.has(key)) selected.set(key, row);
|
|
88
|
+
}
|
|
89
|
+
return selected;
|
|
90
|
+
}
|
|
91
|
+
function isUnchangedShippedDictionaryRow(row, key) {
|
|
92
|
+
const shipped = calendarEventTypes.find((entry) => entry.key === key);
|
|
93
|
+
return !!shipped && !row.activityTypeBehavior && row.label === shipped.label && row.icon === shipped.icon && row.color === shipped.color;
|
|
94
|
+
}
|
|
95
|
+
async function resolveScopedCalendarEventTypes(input) {
|
|
96
|
+
await ensureCalendarEventTypeWidgetsLoaded();
|
|
97
|
+
const scopeOrganizationIds = Array.from(/* @__PURE__ */ new Set([input.organizationId, ...input.readableOrganizationIds]));
|
|
98
|
+
const rows = await findWithDecryption(
|
|
99
|
+
input.em,
|
|
100
|
+
CustomerDictionaryEntry,
|
|
101
|
+
{
|
|
102
|
+
tenantId: input.tenantId,
|
|
103
|
+
organizationId: { $in: scopeOrganizationIds },
|
|
104
|
+
kind: "activity_type"
|
|
105
|
+
},
|
|
106
|
+
{ orderBy: { label: "asc" } },
|
|
107
|
+
{ tenantId: input.tenantId, organizationId: input.organizationId }
|
|
108
|
+
);
|
|
109
|
+
const dictionaryRows = selectDictionaryRows(rows, input.organizationId, scopeOrganizationIds);
|
|
110
|
+
for (const [key, row] of dictionaryRows) {
|
|
111
|
+
if (isUnchangedShippedDictionaryRow(row, key)) dictionaryRows.delete(key);
|
|
112
|
+
}
|
|
113
|
+
const staticCatalog = resolveBaselineCalendarEventTypes();
|
|
114
|
+
const availableFieldsets = input.availableCustomFieldsetIdsFromEntitiesBoundary ? new Set(input.availableCustomFieldsetIdsFromEntitiesBoundary) : null;
|
|
115
|
+
const staticByKey = new Map(staticCatalog.items.map((item) => [item.key, item]));
|
|
116
|
+
const tombstonedKeys = new Set(getCalendarEventTypeTombstones().map((entry) => entry.key));
|
|
117
|
+
const keys = /* @__PURE__ */ new Set([...staticByKey.keys(), ...[...dictionaryRows.keys()].filter((key) => !tombstonedKeys.has(key))]);
|
|
118
|
+
const fallbackBehavior = calendarEventTypes[0].behavior;
|
|
119
|
+
const items = [...keys].map((key) => {
|
|
120
|
+
const base = staticByKey.get(key);
|
|
121
|
+
const row = dictionaryRows.get(key);
|
|
122
|
+
if (!row) {
|
|
123
|
+
if (!availableFieldsets) return base;
|
|
124
|
+
return deepFreeze({
|
|
125
|
+
...base,
|
|
126
|
+
missingCustomFieldsetIds: base.behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id))
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (base?.adminConfigurable === false) {
|
|
130
|
+
return deepFreeze({
|
|
131
|
+
...base,
|
|
132
|
+
inactiveDictionaryOverride: true
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
const parsedBehavior = row.activityTypeBehavior ? calendarEventTypeBehaviorSchema.safeParse(row.activityTypeBehavior) : null;
|
|
136
|
+
const behavior = parsedBehavior?.success ? parsedBehavior.data : base?.behavior ?? { ...fallbackBehavior, selectable: true };
|
|
137
|
+
const isLocalOverride = row.organizationId === input.organizationId;
|
|
138
|
+
const defaultLabel = calendarEventTypes.find((entry) => entry.key === key)?.label ?? base?.label;
|
|
139
|
+
const hasCustomLabel = !!row.label && row.label !== defaultLabel;
|
|
140
|
+
return deepFreeze({
|
|
141
|
+
key,
|
|
142
|
+
label: hasCustomLabel ? row.label : base?.label || row.label || row.value,
|
|
143
|
+
...!hasCustomLabel && base?.labelKey ? { labelKey: base.labelKey } : {},
|
|
144
|
+
icon: row.icon ?? base?.icon ?? null,
|
|
145
|
+
color: row.color ?? base?.color ?? null,
|
|
146
|
+
behavior,
|
|
147
|
+
selectable: behavior.selectable,
|
|
148
|
+
...base?.panelKey ? { panelKey: base.panelKey } : {},
|
|
149
|
+
source: base?.source ?? "dictionary",
|
|
150
|
+
provenance: base?.provenance ?? {},
|
|
151
|
+
historical: false,
|
|
152
|
+
adminConfigurable: true,
|
|
153
|
+
isInherited: !isLocalOverride,
|
|
154
|
+
isLocalOverride,
|
|
155
|
+
updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : null,
|
|
156
|
+
missingCustomFieldsetIds: availableFieldsets ? behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id)) : []
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
items.sort((left, right) => left.behavior.order - right.behavior.order || left.key.localeCompare(right.key));
|
|
160
|
+
return deepFreeze({ items, fallbackKey: "meeting" });
|
|
161
|
+
}
|
|
162
|
+
function resolveCatalogEventType(catalog, key, options = {}) {
|
|
163
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
164
|
+
const item = catalog.items.find((entry) => entry.key === normalizedKey);
|
|
165
|
+
if (item || !options.includeHistorical) return item;
|
|
166
|
+
const registryHistorical = resolveCalendarEventType(key, { includeHistorical: true });
|
|
167
|
+
if (registryHistorical) {
|
|
168
|
+
const serialized = serializeStaticType(registryHistorical);
|
|
169
|
+
return deepFreeze({
|
|
170
|
+
...serialized,
|
|
171
|
+
selectable: false,
|
|
172
|
+
behavior: { ...serialized.behavior, selectable: false },
|
|
173
|
+
historical: true
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
const fallback = catalog.items.find((entry) => entry.key === catalog.fallbackKey);
|
|
177
|
+
if (!fallback) return void 0;
|
|
178
|
+
return deepFreeze({
|
|
179
|
+
...fallback,
|
|
180
|
+
key,
|
|
181
|
+
label: key,
|
|
182
|
+
labelKey: void 0,
|
|
183
|
+
behavior: { ...fallback.behavior, selectable: false },
|
|
184
|
+
selectable: false,
|
|
185
|
+
historical: true,
|
|
186
|
+
source: "historical-fallback",
|
|
187
|
+
isInherited: false,
|
|
188
|
+
isLocalOverride: false,
|
|
189
|
+
updatedAt: null
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
export {
|
|
193
|
+
resolveBaselineCalendarEventTypes,
|
|
194
|
+
resolveCatalogEventType,
|
|
195
|
+
resolveScopedCalendarEventTypes
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=eventTypeResolver.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/customers/lib/calendar/eventTypeResolver.ts"],
|
|
4
|
+
"sourcesContent": ["import type { EntityManager } from '@mikro-orm/postgresql'\nimport { findWithDecryption } from '@open-mercato/shared/lib/encryption/find'\nimport { waitForAsyncRegistration } from '@open-mercato/shared/lib/bootstrap/factory'\nimport {\n getInjectionRegistryVersion,\n loadInjectionWidgetsForSpot,\n} from '@open-mercato/shared/modules/widgets/injection-loader'\nimport {\n calendarEventTypeBehaviorSchema,\n calendarEventTypes,\n getCalendarEventTypes,\n getCalendarEventTypeTombstones,\n resolveCalendarEventType,\n registerWidgetCalendarEventTypeContributions,\n type CalendarEventTypeBehavior,\n type CalendarEventTypeProvenance,\n type EffectiveCalendarEventType,\n} from '../../calendar-event-types'\nimport { CustomerDictionaryEntry } from '../../data/entities'\nimport { extensionPoints } from '../../extension-points'\n\nexport type ScopedCalendarEventType = Readonly<{\n key: string\n label: string\n labelKey?: string\n icon?: string | null\n color?: string | null\n behavior: CalendarEventTypeBehavior\n selectable: boolean\n panelKey?: string\n source: string\n provenance: Readonly<Record<string, CalendarEventTypeProvenance>>\n historical: boolean\n fallbackReason?: 'tombstoned' | 'module-unavailable' | 'unknown'\n adminConfigurable: boolean\n isInherited: boolean\n isLocalOverride: boolean\n updatedAt: string | null\n missingCustomFieldsetIds: readonly string[]\n inactiveDictionaryOverride?: boolean\n}>\n\nexport type CalendarEventTypeCatalog = Readonly<{\n items: readonly ScopedCalendarEventType[]\n fallbackKey: 'meeting'\n}>\n\ntype ResolveCalendarEventTypesInput = {\n em: EntityManager\n tenantId: string\n organizationId: string\n readableOrganizationIds: readonly string[]\n availableCustomFieldsetIdsFromEntitiesBoundary?: readonly string[]\n}\n\nfunction deepFreeze<T>(value: T): T {\n if (value !== null && typeof value === 'object' && !Object.isFrozen(value)) {\n Object.freeze(value)\n for (const child of Object.values(value as Record<string, unknown>)) deepFreeze(child)\n }\n return value\n}\n\nfunction serializeStaticType(type: EffectiveCalendarEventType): ScopedCalendarEventType {\n return deepFreeze({\n key: type.key,\n label: type.label,\n ...(type.labelKey ? { labelKey: type.labelKey } : {}),\n icon: type.icon ?? null,\n color: type.color ?? null,\n behavior: type.behavior,\n selectable: type.behavior.selectable,\n ...(type.panelKey ? { panelKey: type.panelKey } : {}),\n source: type.provenance.key?.moduleId ?? 'customers',\n provenance: type.provenance,\n historical: type.historical,\n ...(type.fallbackReason ? { fallbackReason: type.fallbackReason } : {}),\n adminConfigurable: type.adminConfigurable !== false,\n isInherited: false,\n isLocalOverride: false,\n updatedAt: null,\n missingCustomFieldsetIds: [],\n })\n}\n\nexport function resolveBaselineCalendarEventTypes(): CalendarEventTypeCatalog {\n const types: readonly EffectiveCalendarEventType[] = getCalendarEventTypes()\n return deepFreeze({ items: types.map(serializeStaticType), fallbackKey: 'meeting' })\n}\n\nlet loadedWidgetVersion = -1\nlet loadingWidgets: Promise<void> | null = null\n\nasync function ensureCalendarEventTypeWidgetsLoaded(): Promise<void> {\n await waitForAsyncRegistration()\n const version = getInjectionRegistryVersion()\n if (loadedWidgetVersion === version) return\n if (loadingWidgets) return loadingWidgets\n const pending = (async () => {\n const widgets = await loadInjectionWidgetsForSpot(extensionPoints.hosts.calendarEventTypes.spotId)\n registerWidgetCalendarEventTypeContributions(widgets.map((widget) => {\n return {\n moduleId: widget.moduleId,\n widgetId: widget.metadata.id,\n priority: typeof widget.placement?.priority === 'number' ? widget.placement.priority : 0,\n definitions: 'eventTypes' in widget ? widget.eventTypes : null,\n overrides: 'eventTypeOverrides' in widget ? widget.eventTypeOverrides : undefined,\n patches: 'eventTypePatches' in widget ? widget.eventTypePatches : undefined,\n }\n }))\n loadedWidgetVersion = version\n })()\n loadingWidgets = pending\n try {\n await pending\n } finally {\n if (loadingWidgets === pending) loadingWidgets = null\n }\n}\n\nfunction selectDictionaryRows(\n rows: readonly CustomerDictionaryEntry[],\n organizationId: string,\n readableOrganizationIds: readonly string[],\n): Map<string, CustomerDictionaryEntry> {\n const orderedOrganizationIds = Array.from(new Set([organizationId, ...readableOrganizationIds]))\n const rank = new Map(orderedOrganizationIds.map((id, index) => [id, index]))\n const sorted = [...rows].sort((left, right) => {\n const rankDelta = (rank.get(left.organizationId) ?? Number.MAX_SAFE_INTEGER) -\n (rank.get(right.organizationId) ?? Number.MAX_SAFE_INTEGER)\n return rankDelta || left.normalizedValue.localeCompare(right.normalizedValue)\n })\n const selected = new Map<string, CustomerDictionaryEntry>()\n for (const row of sorted) {\n const key = row.normalizedValue.trim() || row.value.trim().toLowerCase()\n if (key && !selected.has(key)) selected.set(key, row)\n }\n return selected\n}\n\nfunction isUnchangedShippedDictionaryRow(row: CustomerDictionaryEntry, key: string): boolean {\n const shipped = calendarEventTypes.find((entry) => entry.key === key)\n return !!shipped && !row.activityTypeBehavior && row.label === shipped.label &&\n row.icon === shipped.icon && row.color === shipped.color\n}\n\nexport async function resolveScopedCalendarEventTypes(\n input: ResolveCalendarEventTypesInput,\n): Promise<CalendarEventTypeCatalog> {\n await ensureCalendarEventTypeWidgetsLoaded()\n const scopeOrganizationIds = Array.from(new Set([input.organizationId, ...input.readableOrganizationIds]))\n const rows = await findWithDecryption(\n input.em,\n CustomerDictionaryEntry,\n {\n tenantId: input.tenantId,\n organizationId: { $in: scopeOrganizationIds },\n kind: 'activity_type',\n },\n { orderBy: { label: 'asc' } },\n { tenantId: input.tenantId, organizationId: input.organizationId },\n )\n const dictionaryRows = selectDictionaryRows(rows, input.organizationId, scopeOrganizationIds)\n for (const [key, row] of dictionaryRows) {\n if (isUnchangedShippedDictionaryRow(row, key)) dictionaryRows.delete(key)\n }\n const staticCatalog = resolveBaselineCalendarEventTypes()\n const availableFieldsets = input.availableCustomFieldsetIdsFromEntitiesBoundary\n ? new Set(input.availableCustomFieldsetIdsFromEntitiesBoundary)\n : null\n const staticByKey = new Map(staticCatalog.items.map((item) => [item.key, item]))\n const tombstonedKeys = new Set(getCalendarEventTypeTombstones().map((entry) => entry.key))\n const keys = new Set([...staticByKey.keys(), ...[...dictionaryRows.keys()].filter((key) => !tombstonedKeys.has(key))])\n const fallbackBehavior = calendarEventTypes[0]!.behavior\n const items = [...keys].map((key): ScopedCalendarEventType => {\n const base = staticByKey.get(key)\n const row = dictionaryRows.get(key)\n if (!row) {\n if (!availableFieldsets) return base!\n return deepFreeze({\n ...base!,\n missingCustomFieldsetIds: base!.behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id)),\n })\n }\n if (base?.adminConfigurable === false) {\n return deepFreeze({\n ...base,\n inactiveDictionaryOverride: true,\n })\n }\n const parsedBehavior = row.activityTypeBehavior\n ? calendarEventTypeBehaviorSchema.safeParse(row.activityTypeBehavior)\n : null\n const behavior = parsedBehavior?.success\n ? parsedBehavior.data\n : base?.behavior ?? { ...fallbackBehavior, selectable: true }\n const isLocalOverride = row.organizationId === input.organizationId\n const defaultLabel = calendarEventTypes.find((entry) => entry.key === key)?.label ?? base?.label\n const hasCustomLabel = !!row.label && row.label !== defaultLabel\n return deepFreeze({\n key,\n label: hasCustomLabel ? row.label : base?.label || row.label || row.value,\n ...(!hasCustomLabel && base?.labelKey ? { labelKey: base.labelKey } : {}),\n icon: row.icon ?? base?.icon ?? null,\n color: row.color ?? base?.color ?? null,\n behavior,\n selectable: behavior.selectable,\n ...(base?.panelKey ? { panelKey: base.panelKey } : {}),\n source: base?.source ?? 'dictionary',\n provenance: base?.provenance ?? {},\n historical: false,\n adminConfigurable: true,\n isInherited: !isLocalOverride,\n isLocalOverride,\n updatedAt: row.updatedAt instanceof Date ? row.updatedAt.toISOString() : null,\n missingCustomFieldsetIds: availableFieldsets\n ? behavior.customFieldsetIds.filter((id) => !availableFieldsets.has(id))\n : [],\n })\n })\n items.sort((left, right) => left.behavior.order - right.behavior.order || left.key.localeCompare(right.key))\n return deepFreeze({ items, fallbackKey: 'meeting' })\n}\n\nexport function resolveCatalogEventType(\n catalog: CalendarEventTypeCatalog,\n key: string,\n options: { includeHistorical?: boolean } = {},\n): ScopedCalendarEventType | undefined {\n const normalizedKey = key.trim().toLowerCase()\n const item = catalog.items.find((entry) => entry.key === normalizedKey)\n if (item || !options.includeHistorical) return item\n const registryHistorical = resolveCalendarEventType(key, { includeHistorical: true })\n if (registryHistorical) {\n const serialized = serializeStaticType(registryHistorical)\n return deepFreeze({\n ...serialized,\n selectable: false,\n behavior: { ...serialized.behavior, selectable: false },\n historical: true,\n })\n }\n const fallback = catalog.items.find((entry) => entry.key === catalog.fallbackKey)\n if (!fallback) return undefined\n return deepFreeze({\n ...fallback,\n key,\n label: key,\n labelKey: undefined,\n behavior: { ...fallback.behavior, selectable: false },\n selectable: false,\n historical: true,\n source: 'historical-fallback',\n isInherited: false,\n isLocalOverride: false,\n updatedAt: null,\n })\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const CORE_FIELD_RULES = [
|
|
2
|
+
["endTime", ["durationMinutes"]],
|
|
3
|
+
["allDay", ["allDay"]],
|
|
4
|
+
["recurrence", ["recurrenceRule", "recurrenceEnd"]],
|
|
5
|
+
["priority", ["priority"]]
|
|
6
|
+
];
|
|
7
|
+
function hasValue(value) {
|
|
8
|
+
if (value === null || value === void 0 || value === "" || value === false) return false;
|
|
9
|
+
if (Array.isArray(value)) return value.length > 0;
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
function hasResourceLinks(value) {
|
|
13
|
+
return Array.isArray(value) && value.some((link) => link !== null && typeof link === "object" && "type" in link && link.type === "resource");
|
|
14
|
+
}
|
|
15
|
+
function findInapplicableCoreFields(behavior, values) {
|
|
16
|
+
const fields = [];
|
|
17
|
+
for (const [rule, names] of CORE_FIELD_RULES) {
|
|
18
|
+
if (behavior.fields[rule]) continue;
|
|
19
|
+
for (const name of names) if (hasValue(values[name])) fields.push(name);
|
|
20
|
+
}
|
|
21
|
+
if (behavior.fields.location === "none" && hasValue(values.location)) fields.push("location");
|
|
22
|
+
if (behavior.fields.people === "none" && hasValue(values.participants)) fields.push("participants");
|
|
23
|
+
if (!behavior.fields.resources && hasResourceLinks(values.linkedEntities)) fields.push("linkedEntities");
|
|
24
|
+
return fields;
|
|
25
|
+
}
|
|
26
|
+
function clearInapplicableCoreFields(behavior, values) {
|
|
27
|
+
const cleared = {};
|
|
28
|
+
for (const field of findInapplicableCoreFields(behavior, values)) {
|
|
29
|
+
if (field === "linkedEntities") {
|
|
30
|
+
cleared.linkedEntities = values.linkedEntities.filter((link) => link.type !== "resource");
|
|
31
|
+
} else {
|
|
32
|
+
cleared[field] = null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return cleared;
|
|
36
|
+
}
|
|
37
|
+
function omitHiddenCoreClears(behavior, patch) {
|
|
38
|
+
const result = { ...patch };
|
|
39
|
+
for (const [rule, names] of CORE_FIELD_RULES) {
|
|
40
|
+
if (behavior.fields[rule]) continue;
|
|
41
|
+
for (const name of names) if (result[name] === null) delete result[name];
|
|
42
|
+
}
|
|
43
|
+
if (behavior.fields.location === "none" && result.location === null) delete result.location;
|
|
44
|
+
if (behavior.fields.people === "none" && result.participants === null) delete result.participants;
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
function preserveHiddenCoreValuesOnSameTypeEdit(behavior, current, patch, options = { ignoreHiddenClears: true }) {
|
|
48
|
+
const result = options.ignoreHiddenClears !== false ? omitHiddenCoreClears(behavior, patch) : { ...patch };
|
|
49
|
+
if (!behavior.fields.resources && result.linkedEntities !== void 0) {
|
|
50
|
+
const existingLinks = Array.isArray(current.linkedEntities) ? current.linkedEntities : [];
|
|
51
|
+
const retainedResources = existingLinks.filter((link) => link !== null && typeof link === "object" && "type" in link && link.type === "resource");
|
|
52
|
+
const nextLinks = Array.isArray(result.linkedEntities) ? result.linkedEntities : [];
|
|
53
|
+
const sentResourceIds = new Set(nextLinks.flatMap((link) => link !== null && typeof link === "object" && "type" in link && link.type === "resource" && "id" in link ? [link.id] : []));
|
|
54
|
+
result.linkedEntities = [
|
|
55
|
+
...nextLinks,
|
|
56
|
+
...retainedResources.filter((link) => !("id" in link) || !sentResourceIds.has(link.id))
|
|
57
|
+
];
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
clearInapplicableCoreFields,
|
|
63
|
+
findInapplicableCoreFields,
|
|
64
|
+
omitHiddenCoreClears,
|
|
65
|
+
preserveHiddenCoreValuesOnSameTypeEdit
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=interactionApplicability.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/modules/customers/lib/calendar/interactionApplicability.ts"],
|
|
4
|
+
"sourcesContent": ["import type { CalendarEventTypeBehavior } from '../../calendar-event-types'\n\nexport type InteractionCoreValues = Readonly<Record<string, unknown>>\n\nconst CORE_FIELD_RULES: readonly [keyof CalendarEventTypeBehavior['fields'], readonly string[]][] = [\n ['endTime', ['durationMinutes']],\n ['allDay', ['allDay']],\n ['recurrence', ['recurrenceRule', 'recurrenceEnd']],\n ['priority', ['priority']],\n]\n\nfunction hasValue(value: unknown): boolean {\n if (value === null || value === undefined || value === '' || value === false) return false\n if (Array.isArray(value)) return value.length > 0\n return true\n}\n\nfunction hasResourceLinks(value: unknown): boolean {\n return Array.isArray(value) && value.some((link) =>\n link !== null && typeof link === 'object' && 'type' in link && link.type === 'resource')\n}\n\nexport function findInapplicableCoreFields(\n behavior: CalendarEventTypeBehavior,\n values: InteractionCoreValues,\n): string[] {\n const fields: string[] = []\n for (const [rule, names] of CORE_FIELD_RULES) {\n if (behavior.fields[rule]) continue\n for (const name of names) if (hasValue(values[name])) fields.push(name)\n }\n if (behavior.fields.location === 'none' && hasValue(values.location)) fields.push('location')\n if (behavior.fields.people === 'none' && hasValue(values.participants)) fields.push('participants')\n if (!behavior.fields.resources && hasResourceLinks(values.linkedEntities)) fields.push('linkedEntities')\n return fields\n}\n\nexport function clearInapplicableCoreFields(\n behavior: CalendarEventTypeBehavior,\n values: InteractionCoreValues,\n): Record<string, unknown> {\n const cleared: Record<string, unknown> = {}\n for (const field of findInapplicableCoreFields(behavior, values)) {\n if (field === 'linkedEntities') {\n cleared.linkedEntities = (values.linkedEntities as readonly Record<string, unknown>[])\n .filter((link) => link.type !== 'resource')\n } else {\n cleared[field] = null\n }\n }\n return cleared\n}\n\n/**\n * Under the selectable-type opt-in an explicit `null` for a field the type hides\n * means \"not shown\", not \"clear it\". Dropping it lets a type change judge the\n * stored value, so the destructive clear is confirmed instead of silent.\n */\nexport function omitHiddenCoreClears(\n behavior: CalendarEventTypeBehavior,\n patch: InteractionCoreValues,\n): Record<string, unknown> {\n const result = { ...patch }\n for (const [rule, names] of CORE_FIELD_RULES) {\n if (behavior.fields[rule]) continue\n for (const name of names) if (result[name] === null) delete result[name]\n }\n if (behavior.fields.location === 'none' && result.location === null) delete result.location\n if (behavior.fields.people === 'none' && result.participants === null) delete result.participants\n return result\n}\n\n/**\n * `ignoreHiddenClears` belongs to the selectable-type opt-in: only a caller that\n * declares `enforceSelectableType` has its explicit `null` clears of fields the\n * type hides dropped. Retaining stored resource links is unconditional \u2014 it\n * protects bookings a partial payload never meant to release.\n */\nexport function preserveHiddenCoreValuesOnSameTypeEdit(\n behavior: CalendarEventTypeBehavior,\n current: InteractionCoreValues,\n patch: InteractionCoreValues,\n options: { ignoreHiddenClears?: boolean } = { ignoreHiddenClears: true },\n): Record<string, unknown> {\n const result = options.ignoreHiddenClears !== false ? omitHiddenCoreClears(behavior, patch) : { ...patch }\n if (!behavior.fields.resources && result.linkedEntities !== undefined) {\n const existingLinks = Array.isArray(current.linkedEntities) ? current.linkedEntities : []\n const retainedResources = existingLinks.filter((link) =>\n link !== null && typeof link === 'object' && 'type' in link && link.type === 'resource')\n const nextLinks = Array.isArray(result.linkedEntities) ? result.linkedEntities : []\n const sentResourceIds = new Set(nextLinks.flatMap((link) =>\n link !== null && typeof link === 'object' && 'type' in link && link.type === 'resource' && 'id' in link ? [link.id] : []))\n result.linkedEntities = [\n ...nextLinks,\n ...retainedResources.filter((link) => !('id' in link) || !sentResourceIds.has(link.id)),\n ]\n }\n return result\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -3,6 +3,7 @@ import { endOfDay } from "date-fns/endOfDay";
|
|
|
3
3
|
import { startOfDay } from "date-fns/startOfDay";
|
|
4
4
|
import { categoryOf } from "./categories.js";
|
|
5
5
|
import { participantActorKey } from "./participantIdentity.js";
|
|
6
|
+
import { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, isCalendarTimezone } from "./timezone.js";
|
|
6
7
|
const DEFAULT_DURATION_MINUTES = 30;
|
|
7
8
|
function narrowStatus(status) {
|
|
8
9
|
if (status === "done") return "done";
|
|
@@ -52,8 +53,10 @@ function mapInteractionToCalendarItem(payload, typeColorByType) {
|
|
|
52
53
|
if (Number.isNaN(parsedStart.getTime())) return null;
|
|
53
54
|
const allDay = payload.allDay === true;
|
|
54
55
|
const durationMinutes = payload.durationMinutes ?? DEFAULT_DURATION_MINUTES;
|
|
55
|
-
const
|
|
56
|
-
const
|
|
56
|
+
const eventDate = allDay && isCalendarTimezone(payload.timezone) ? calendarInstantToWallTime(parsedStart, payload.timezone).date : null;
|
|
57
|
+
const start = eventDate ? calendarDayStartInstant(eventDate, payload.timezone) : allDay ? startOfDay(parsedStart) : parsedStart;
|
|
58
|
+
const end = eventDate ? calendarDayEndInstant(eventDate, payload.timezone) : allDay ? endOfDay(parsedStart) : addMinutes(parsedStart, durationMinutes);
|
|
59
|
+
if (!start || !end) return null;
|
|
57
60
|
const location = payload.location ?? null;
|
|
58
61
|
const platform = detectPlatform(location);
|
|
59
62
|
return {
|