@open-mercato/core 0.8.1-develop.7454.1.010095490f → 0.8.1-develop.7455.1.8f7e53badb
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/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/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/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/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/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/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/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 +7 -7
- package/src/modules/customers/api/activity-types/route.ts +154 -0
- 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/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/types.ts +1 -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 +83 -0
- package/src/modules/customers/i18n/en.json +83 -0
- package/src/modules/customers/i18n/es.json +83 -0
- package/src/modules/customers/i18n/ko.json +83 -0
- package/src/modules/customers/i18n/pl.json +83 -0
- 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/planner/lib/availabilityMerge.ts +106 -41
- package/src/modules/planner/services/plannerAvailabilityService.ts +4 -0
- package/src/modules/staff/backend/staff/time-tracking/projects/[id]/page.tsx +25 -13
|
@@ -193,6 +193,11 @@
|
|
|
193
193
|
"customers.audit.todos.link": "할 일 연결",
|
|
194
194
|
"customers.audit.todos.unlink": "할 일 연결 해제",
|
|
195
195
|
"customers.calendar.actions.newEvent": "새 이벤트",
|
|
196
|
+
"customers.calendar.activityTypes.errors.changeConfirmationRequired": "이벤트 유형을 변경하면 기존 값이 삭제됩니다.",
|
|
197
|
+
"customers.calendar.activityTypes.errors.fieldNotApplicable": "이 필드는 선택한 활동 유형에 적용되지 않습니다.",
|
|
198
|
+
"customers.calendar.activityTypes.errors.forbidden": "활동 유형을 볼 권한이 없습니다.",
|
|
199
|
+
"customers.calendar.activityTypes.errors.loadFailed": "활동 유형을 불러오지 못했습니다.",
|
|
200
|
+
"customers.calendar.activityTypes.errors.unavailable": "이 활동 유형은 사용할 수 없습니다.",
|
|
196
201
|
"customers.calendar.agenda.eventsCount.few": "이벤트 {count}건",
|
|
197
202
|
"customers.calendar.agenda.eventsCount.many": "이벤트 {count}건",
|
|
198
203
|
"customers.calendar.agenda.eventsCount.one": "이벤트 {count}건",
|
|
@@ -232,6 +237,8 @@
|
|
|
232
237
|
"customers.calendar.editor.assigneePlaceholder": "팀원에게 배정…",
|
|
233
238
|
"customers.calendar.editor.attendees": "참석자",
|
|
234
239
|
"customers.calendar.editor.cancel": "취소",
|
|
240
|
+
"customers.calendar.editor.catalogLoadFailed": "활동 유형을 불러오지 못했습니다. 다시 시도한 후 이벤트를 저장하세요.",
|
|
241
|
+
"customers.calendar.editor.catalogLoading": "활동 유형을 불러오는 중…",
|
|
235
242
|
"customers.calendar.editor.category": "카테고리",
|
|
236
243
|
"customers.calendar.editor.close": "닫기",
|
|
237
244
|
"customers.calendar.editor.conflictTitle": "일정 충돌",
|
|
@@ -249,8 +256,14 @@
|
|
|
249
256
|
"customers.calendar.editor.dealsSection": "거래",
|
|
250
257
|
"customers.calendar.editor.description": "설명",
|
|
251
258
|
"customers.calendar.editor.descriptionPlaceholder": "세부 정보 추가…",
|
|
259
|
+
"customers.calendar.editor.discardConfirm": "유형 변경 및 값 삭제",
|
|
260
|
+
"customers.calendar.editor.discardDescription": "이벤트 유형을 변경하면 새 유형에 적용되지 않는 값이 삭제됩니다.",
|
|
261
|
+
"customers.calendar.editor.discardFields": "삭제할 값:",
|
|
262
|
+
"customers.calendar.editor.discardTitle": "이벤트 유형을 변경할까요?",
|
|
263
|
+
"customers.calendar.editor.discardUnknownField": "사용자 정의 필드",
|
|
252
264
|
"customers.calendar.editor.error": "이벤트를 저장하지 못했습니다",
|
|
253
265
|
"customers.calendar.editor.guestBadge": "게스트",
|
|
266
|
+
"customers.calendar.editor.historicalType": "이 이벤트 유형은 새 이벤트에 사용할 수 없습니다. 현재 이벤트를 계속 편집하거나 다른 유형을 선택하세요.",
|
|
254
267
|
"customers.calendar.editor.location": "장소",
|
|
255
268
|
"customers.calendar.editor.locationPlaceholder": "장소 또는 링크 추가…",
|
|
256
269
|
"customers.calendar.editor.multiDayHint": "여러 날 이벤트 · {count}일",
|
|
@@ -295,6 +308,7 @@
|
|
|
295
308
|
"customers.calendar.editor.saved": "이벤트가 저장되었습니다",
|
|
296
309
|
"customers.calendar.editor.saving": "저장 중…",
|
|
297
310
|
"customers.calendar.editor.searching": "검색 중…",
|
|
311
|
+
"customers.calendar.editor.timezone": "시간대",
|
|
298
312
|
"customers.calendar.editor.title.create": "새 이벤트",
|
|
299
313
|
"customers.calendar.editor.title.edit": "이벤트 수정",
|
|
300
314
|
"customers.calendar.editor.titleLabel.email": "제목",
|
|
@@ -303,6 +317,7 @@
|
|
|
303
317
|
"customers.calendar.editor.titlePlaceholder": "제목 추가…",
|
|
304
318
|
"customers.calendar.editor.to": "받는 사람",
|
|
305
319
|
"customers.calendar.editor.typeSwitcher": "이벤트 유형",
|
|
320
|
+
"customers.calendar.editor.typeUnavailable": "저장하기 전에 사용 가능한 이벤트 유형을 선택하세요.",
|
|
306
321
|
"customers.calendar.editor.types.call": "통화",
|
|
307
322
|
"customers.calendar.editor.types.email": "이메일",
|
|
308
323
|
"customers.calendar.editor.types.event": "이벤트",
|
|
@@ -312,6 +327,8 @@
|
|
|
312
327
|
"customers.calendar.editor.validation.assigneeRequired": "이 작업을 팀 구성원에게 할당",
|
|
313
328
|
"customers.calendar.editor.validation.endsBeforeStarts": "종료 시각은 시작 시각 이후여야 합니다",
|
|
314
329
|
"customers.calendar.editor.validation.relatedToRequired": "이 이벤트에 연결할 연락처 또는 회사를 선택하세요",
|
|
330
|
+
"customers.calendar.editor.validation.timezoneGap": "선택한 시간대에 이 현지 시간이 존재하지 않습니다",
|
|
331
|
+
"customers.calendar.editor.validation.timezoneInvalid": "유효한 시간대를 선택하세요",
|
|
315
332
|
"customers.calendar.editor.validation.titleRequired": "제목은 필수입니다",
|
|
316
333
|
"customers.calendar.empty.action": "새 이벤트",
|
|
317
334
|
"customers.calendar.empty.agenda": "이 기간에 예정된 일정이 없습니다",
|
|
@@ -368,6 +385,7 @@
|
|
|
368
385
|
"customers.calendar.settings.conflictWarnings": "충돌 경고",
|
|
369
386
|
"customers.calendar.settings.eventCategories": "이벤트 카테고리",
|
|
370
387
|
"customers.calendar.settings.eventCategoriesHint": "직접 지정하는 그룹 라벨입니다(예: 팀 미팅, 영업 통화). 이벤트 생성 시 선택할 수 있습니다.",
|
|
388
|
+
"customers.calendar.settings.manageActivityTypes": "활동 유형 관리",
|
|
371
389
|
"customers.calendar.settings.max": "(최대 {count}개)",
|
|
372
390
|
"customers.calendar.settings.removeTag": "{tag} 제거",
|
|
373
391
|
"customers.calendar.settings.save": "변경사항 저장",
|
|
@@ -798,6 +816,71 @@
|
|
|
798
816
|
"customers.companies.presets.inactive60": "60일 초과 비활성",
|
|
799
817
|
"customers.companies.presets.myAccounts": "내 담당 계정",
|
|
800
818
|
"customers.companies.presets.recentlyCreated": "최근 생성됨",
|
|
819
|
+
"customers.config.activityTypes.appearance": "모양",
|
|
820
|
+
"customers.config.activityTypes.behavior": "양식 동작",
|
|
821
|
+
"customers.config.activityTypes.columns.active": "선택 가능",
|
|
822
|
+
"customers.config.activityTypes.columns.behavior": "동작",
|
|
823
|
+
"customers.config.activityTypes.columns.fields": "필드",
|
|
824
|
+
"customers.config.activityTypes.columns.fieldsets": "필드셋",
|
|
825
|
+
"customers.config.activityTypes.columns.key": "고정 키",
|
|
826
|
+
"customers.config.activityTypes.columns.order": "순서",
|
|
827
|
+
"customers.config.activityTypes.columns.source": "출처",
|
|
828
|
+
"customers.config.activityTypes.columns.type": "활동 유형",
|
|
829
|
+
"customers.config.activityTypes.configure": "설정",
|
|
830
|
+
"customers.config.activityTypes.delete": "삭제",
|
|
831
|
+
"customers.config.activityTypes.empty": "사용 가능한 활동 유형이 없습니다.",
|
|
832
|
+
"customers.config.activityTypes.fields.allDay": "하루 종일",
|
|
833
|
+
"customers.config.activityTypes.fields.baseKind": "기본 동작",
|
|
834
|
+
"customers.config.activityTypes.fields.color": "색상 (#RRGGBB)",
|
|
835
|
+
"customers.config.activityTypes.fields.customFieldsetIds": "사용자 지정 필드셋",
|
|
836
|
+
"customers.config.activityTypes.fields.endTime": "종료 시간",
|
|
837
|
+
"customers.config.activityTypes.fields.icon": "아이콘",
|
|
838
|
+
"customers.config.activityTypes.fields.label": "레이블",
|
|
839
|
+
"customers.config.activityTypes.fields.location": "위치 필드",
|
|
840
|
+
"customers.config.activityTypes.fields.order": "표시 순서",
|
|
841
|
+
"customers.config.activityTypes.fields.people": "사람 필드",
|
|
842
|
+
"customers.config.activityTypes.fields.priority": "우선순위",
|
|
843
|
+
"customers.config.activityTypes.fields.recurrence": "반복",
|
|
844
|
+
"customers.config.activityTypes.fields.resources": "리소스",
|
|
845
|
+
"customers.config.activityTypes.fields.selectable": "새 이벤트에 사용 가능",
|
|
846
|
+
"customers.config.activityTypes.fields.value": "고정 키",
|
|
847
|
+
"customers.config.activityTypes.fieldsetsUnavailable": "필드셋을 사용할 수 없습니다. 저장하기 전에 다시 불러오세요.",
|
|
848
|
+
"customers.config.activityTypes.inactiveHint": "현재 모듈이 이 재정의의 적용을 막고 있습니다.",
|
|
849
|
+
"customers.config.activityTypes.inherited": "상속됨",
|
|
850
|
+
"customers.config.activityTypes.inheritedHint": "저장하면 이 조직에 대한 로컬 재정의가 생성됩니다.",
|
|
851
|
+
"customers.config.activityTypes.invalidBehavior": "양식 동작 값을 확인하세요.",
|
|
852
|
+
"customers.config.activityTypes.invalidKey": "소문자, 숫자, 하이픈 또는 밑줄로 된 키를 사용하세요.",
|
|
853
|
+
"customers.config.activityTypes.loadFailed": "활동 유형을 불러오지 못했습니다.",
|
|
854
|
+
"customers.config.activityTypes.loadingFieldsets": "필드셋 불러오는 중…",
|
|
855
|
+
"customers.config.activityTypes.local": "로컬 재정의",
|
|
856
|
+
"customers.config.activityTypes.missing": "누락됨",
|
|
857
|
+
"customers.config.activityTypes.missingHint": "설정된 일부 필드셋이 더 이상 존재하지 않습니다.",
|
|
858
|
+
"customers.config.activityTypes.new": "새 활동 유형",
|
|
859
|
+
"customers.config.activityTypes.no": "아니요",
|
|
860
|
+
"customers.config.activityTypes.noFieldsets": "아직 사용자 지정 필드셋이 정의되지 않았습니다.",
|
|
861
|
+
"customers.config.activityTypes.options.assignee": "담당자",
|
|
862
|
+
"customers.config.activityTypes.options.attendees": "참석자",
|
|
863
|
+
"customers.config.activityTypes.options.call": "통화",
|
|
864
|
+
"customers.config.activityTypes.options.email": "이메일",
|
|
865
|
+
"customers.config.activityTypes.options.event": "이벤트",
|
|
866
|
+
"customers.config.activityTypes.options.location": "위치",
|
|
867
|
+
"customers.config.activityTypes.options.meeting": "회의",
|
|
868
|
+
"customers.config.activityTypes.options.none": "없음",
|
|
869
|
+
"customers.config.activityTypes.options.note": "메모",
|
|
870
|
+
"customers.config.activityTypes.options.participants": "참여자",
|
|
871
|
+
"customers.config.activityTypes.options.phoneLink": "전화 링크",
|
|
872
|
+
"customers.config.activityTypes.options.recipients": "수신자",
|
|
873
|
+
"customers.config.activityTypes.options.task": "작업",
|
|
874
|
+
"customers.config.activityTypes.refresh": "새로고침",
|
|
875
|
+
"customers.config.activityTypes.removed": "활동 유형 설정을 제거했습니다.",
|
|
876
|
+
"customers.config.activityTypes.reset": "재정의 초기화",
|
|
877
|
+
"customers.config.activityTypes.resetConfirm": "로컬 활동 유형 설정을 제거할까요?",
|
|
878
|
+
"customers.config.activityTypes.retry": "다시 시도",
|
|
879
|
+
"customers.config.activityTypes.save": "저장",
|
|
880
|
+
"customers.config.activityTypes.saveFailed": "활동 유형을 저장하지 못했습니다.",
|
|
881
|
+
"customers.config.activityTypes.saved": "활동 유형을 저장했습니다.",
|
|
882
|
+
"customers.config.activityTypes.view": "보기",
|
|
883
|
+
"customers.config.activityTypes.yes": "예",
|
|
801
884
|
"customers.config.addressFormat.description": "고객 모듈 전반에서 주소 입력 폼과 표시 방식을 어떻게 구성할지 선택하세요.",
|
|
802
885
|
"customers.config.addressFormat.error": "주소 설정을 불러오지 못했습니다",
|
|
803
886
|
"customers.config.addressFormat.errorSave": "주소 설정 업데이트에 실패했습니다",
|
|
@@ -193,6 +193,11 @@
|
|
|
193
193
|
"customers.audit.todos.link": "Powiąż zadanie",
|
|
194
194
|
"customers.audit.todos.unlink": "Odepnij zadanie",
|
|
195
195
|
"customers.calendar.actions.newEvent": "Nowe wydarzenie",
|
|
196
|
+
"customers.calendar.activityTypes.errors.changeConfirmationRequired": "Zmiana typu wydarzenia usunie istniejące wartości.",
|
|
197
|
+
"customers.calendar.activityTypes.errors.fieldNotApplicable": "To pole nie dotyczy wybranego typu aktywności.",
|
|
198
|
+
"customers.calendar.activityTypes.errors.forbidden": "Nie masz uprawnień do wyświetlania typów aktywności.",
|
|
199
|
+
"customers.calendar.activityTypes.errors.loadFailed": "Nie udało się wczytać typów aktywności.",
|
|
200
|
+
"customers.calendar.activityTypes.errors.unavailable": "Ten typ aktywności jest niedostępny.",
|
|
196
201
|
"customers.calendar.agenda.eventsCount.few": "{count} wydarzenia",
|
|
197
202
|
"customers.calendar.agenda.eventsCount.many": "{count} wydarzeń",
|
|
198
203
|
"customers.calendar.agenda.eventsCount.one": "{count} wydarzenie",
|
|
@@ -232,6 +237,8 @@
|
|
|
232
237
|
"customers.calendar.editor.assigneePlaceholder": "Przypisz do członka zespołu…",
|
|
233
238
|
"customers.calendar.editor.attendees": "Uczestnicy",
|
|
234
239
|
"customers.calendar.editor.cancel": "Anuluj",
|
|
240
|
+
"customers.calendar.editor.catalogLoadFailed": "Nie udało się wczytać typów aktywności. Spróbuj ponownie, aby zapisać wydarzenie.",
|
|
241
|
+
"customers.calendar.editor.catalogLoading": "Wczytywanie typów aktywności…",
|
|
235
242
|
"customers.calendar.editor.category": "Kategoria",
|
|
236
243
|
"customers.calendar.editor.close": "Zamknij",
|
|
237
244
|
"customers.calendar.editor.conflictTitle": "Konflikt w kalendarzu",
|
|
@@ -249,8 +256,14 @@
|
|
|
249
256
|
"customers.calendar.editor.dealsSection": "Szanse",
|
|
250
257
|
"customers.calendar.editor.description": "Opis",
|
|
251
258
|
"customers.calendar.editor.descriptionPlaceholder": "Dodaj szczegóły…",
|
|
259
|
+
"customers.calendar.editor.discardConfirm": "Zmień typ i usuń wartości",
|
|
260
|
+
"customers.calendar.editor.discardDescription": "Zmiana typu wydarzenia usunie wartości, które nie dotyczą nowego typu.",
|
|
261
|
+
"customers.calendar.editor.discardFields": "Wartości do usunięcia:",
|
|
262
|
+
"customers.calendar.editor.discardTitle": "Zmienić typ wydarzenia?",
|
|
263
|
+
"customers.calendar.editor.discardUnknownField": "Pole niestandardowe",
|
|
252
264
|
"customers.calendar.editor.error": "Nie udało się zapisać wydarzenia",
|
|
253
265
|
"customers.calendar.editor.guestBadge": "Gość",
|
|
266
|
+
"customers.calendar.editor.historicalType": "Ten typ wydarzenia nie jest dostępny dla nowych wydarzeń. Możesz dalej edytować to wydarzenie albo wybrać inny typ.",
|
|
254
267
|
"customers.calendar.editor.location": "Lokalizacja",
|
|
255
268
|
"customers.calendar.editor.locationPlaceholder": "Dodaj lokalizację lub link…",
|
|
256
269
|
"customers.calendar.editor.multiDayHint": "Wydarzenie wielodniowe · {count} dni",
|
|
@@ -295,6 +308,7 @@
|
|
|
295
308
|
"customers.calendar.editor.saved": "Wydarzenie zapisane",
|
|
296
309
|
"customers.calendar.editor.saving": "Zapisywanie…",
|
|
297
310
|
"customers.calendar.editor.searching": "Wyszukiwanie…",
|
|
311
|
+
"customers.calendar.editor.timezone": "Strefa czasowa",
|
|
298
312
|
"customers.calendar.editor.title.create": "Nowe wydarzenie",
|
|
299
313
|
"customers.calendar.editor.title.edit": "Edytuj wydarzenie",
|
|
300
314
|
"customers.calendar.editor.titleLabel.email": "Temat",
|
|
@@ -303,6 +317,7 @@
|
|
|
303
317
|
"customers.calendar.editor.titlePlaceholder": "Dodaj tytuł…",
|
|
304
318
|
"customers.calendar.editor.to": "Do",
|
|
305
319
|
"customers.calendar.editor.typeSwitcher": "Typ wydarzenia",
|
|
320
|
+
"customers.calendar.editor.typeUnavailable": "Przed zapisaniem wybierz dostępny typ wydarzenia.",
|
|
306
321
|
"customers.calendar.editor.types.call": "Telefon",
|
|
307
322
|
"customers.calendar.editor.types.email": "E-mail",
|
|
308
323
|
"customers.calendar.editor.types.event": "Wydarzenie",
|
|
@@ -312,6 +327,8 @@
|
|
|
312
327
|
"customers.calendar.editor.validation.assigneeRequired": "Przypisz to zadanie do członka zespołu",
|
|
313
328
|
"customers.calendar.editor.validation.endsBeforeStarts": "Zakończenie musi być po rozpoczęciu",
|
|
314
329
|
"customers.calendar.editor.validation.relatedToRequired": "Wybierz osobę lub firmę, aby powiązać to wydarzenie",
|
|
330
|
+
"customers.calendar.editor.validation.timezoneGap": "Ta godzina nie istnieje w wybranej strefie czasowej",
|
|
331
|
+
"customers.calendar.editor.validation.timezoneInvalid": "Wybierz poprawną strefę czasową",
|
|
315
332
|
"customers.calendar.editor.validation.titleRequired": "Tytuł jest wymagany",
|
|
316
333
|
"customers.calendar.empty.action": "Nowe wydarzenie",
|
|
317
334
|
"customers.calendar.empty.agenda": "Brak zaplanowanych wydarzeń w tym okresie",
|
|
@@ -368,6 +385,7 @@
|
|
|
368
385
|
"customers.calendar.settings.conflictWarnings": "Ostrzeżenia o konfliktach",
|
|
369
386
|
"customers.calendar.settings.eventCategories": "Kategorie wydarzeń",
|
|
370
387
|
"customers.calendar.settings.eventCategoriesHint": "Twoje własne etykiety grupujące (np. Spotkanie zespołu, Telefon sprzedażowy). Pojawiają się podczas tworzenia wydarzenia.",
|
|
388
|
+
"customers.calendar.settings.manageActivityTypes": "Zarządzaj typami aktywności",
|
|
371
389
|
"customers.calendar.settings.max": "(maks. {count})",
|
|
372
390
|
"customers.calendar.settings.removeTag": "Usuń {tag}",
|
|
373
391
|
"customers.calendar.settings.save": "Zapisz zmiany",
|
|
@@ -798,6 +816,71 @@
|
|
|
798
816
|
"customers.companies.presets.inactive60": "Nieaktywne > 60 dni",
|
|
799
817
|
"customers.companies.presets.myAccounts": "Moje konta",
|
|
800
818
|
"customers.companies.presets.recentlyCreated": "Ostatnio utworzone",
|
|
819
|
+
"customers.config.activityTypes.appearance": "Wygląd",
|
|
820
|
+
"customers.config.activityTypes.behavior": "Zachowanie formularza",
|
|
821
|
+
"customers.config.activityTypes.columns.active": "Możliwość wyboru",
|
|
822
|
+
"customers.config.activityTypes.columns.behavior": "Zachowanie",
|
|
823
|
+
"customers.config.activityTypes.columns.fields": "Pola",
|
|
824
|
+
"customers.config.activityTypes.columns.fieldsets": "Zestawy pól",
|
|
825
|
+
"customers.config.activityTypes.columns.key": "Stały klucz",
|
|
826
|
+
"customers.config.activityTypes.columns.order": "Kolejność",
|
|
827
|
+
"customers.config.activityTypes.columns.source": "Źródło",
|
|
828
|
+
"customers.config.activityTypes.columns.type": "Typ aktywności",
|
|
829
|
+
"customers.config.activityTypes.configure": "Konfiguruj",
|
|
830
|
+
"customers.config.activityTypes.delete": "Usuń",
|
|
831
|
+
"customers.config.activityTypes.empty": "Brak dostępnych typów aktywności.",
|
|
832
|
+
"customers.config.activityTypes.fields.allDay": "Cały dzień",
|
|
833
|
+
"customers.config.activityTypes.fields.baseKind": "Podstawowe zachowanie",
|
|
834
|
+
"customers.config.activityTypes.fields.color": "Kolor (#RRGGBB)",
|
|
835
|
+
"customers.config.activityTypes.fields.customFieldsetIds": "Zestawy pól niestandardowych",
|
|
836
|
+
"customers.config.activityTypes.fields.endTime": "Czas zakończenia",
|
|
837
|
+
"customers.config.activityTypes.fields.icon": "Ikona",
|
|
838
|
+
"customers.config.activityTypes.fields.label": "Etykieta",
|
|
839
|
+
"customers.config.activityTypes.fields.location": "Pole lokalizacji",
|
|
840
|
+
"customers.config.activityTypes.fields.order": "Kolejność wyświetlania",
|
|
841
|
+
"customers.config.activityTypes.fields.people": "Pole osób",
|
|
842
|
+
"customers.config.activityTypes.fields.priority": "Priorytet",
|
|
843
|
+
"customers.config.activityTypes.fields.recurrence": "Powtarzanie",
|
|
844
|
+
"customers.config.activityTypes.fields.resources": "Zasoby",
|
|
845
|
+
"customers.config.activityTypes.fields.selectable": "Dostępny dla nowych wydarzeń",
|
|
846
|
+
"customers.config.activityTypes.fields.value": "Stały klucz",
|
|
847
|
+
"customers.config.activityTypes.fieldsetsUnavailable": "Zestawy pól są niedostępne. Wczytaj je ponownie przed zapisem.",
|
|
848
|
+
"customers.config.activityTypes.inactiveHint": "Moduł obecnie uniemożliwia zastosowanie tego nadpisania.",
|
|
849
|
+
"customers.config.activityTypes.inherited": "Dziedziczony",
|
|
850
|
+
"customers.config.activityTypes.inheritedHint": "Zapis utworzy lokalne nadpisanie dla tej organizacji.",
|
|
851
|
+
"customers.config.activityTypes.invalidBehavior": "Sprawdź wartości zachowania formularza.",
|
|
852
|
+
"customers.config.activityTypes.invalidKey": "Użyj klucza z małych liter, cyfr, łączników lub podkreśleń.",
|
|
853
|
+
"customers.config.activityTypes.loadFailed": "Nie udało się wczytać typów aktywności.",
|
|
854
|
+
"customers.config.activityTypes.loadingFieldsets": "Wczytywanie zestawów pól…",
|
|
855
|
+
"customers.config.activityTypes.local": "Lokalne nadpisanie",
|
|
856
|
+
"customers.config.activityTypes.missing": "brak",
|
|
857
|
+
"customers.config.activityTypes.missingHint": "Niektóre skonfigurowane zestawy pól już nie istnieją.",
|
|
858
|
+
"customers.config.activityTypes.new": "Nowy typ aktywności",
|
|
859
|
+
"customers.config.activityTypes.no": "Nie",
|
|
860
|
+
"customers.config.activityTypes.noFieldsets": "Nie zdefiniowano jeszcze zestawów pól niestandardowych.",
|
|
861
|
+
"customers.config.activityTypes.options.assignee": "Osoba przypisana",
|
|
862
|
+
"customers.config.activityTypes.options.attendees": "Uczestnicy",
|
|
863
|
+
"customers.config.activityTypes.options.call": "Połączenie",
|
|
864
|
+
"customers.config.activityTypes.options.email": "E-mail",
|
|
865
|
+
"customers.config.activityTypes.options.event": "Wydarzenie",
|
|
866
|
+
"customers.config.activityTypes.options.location": "Lokalizacja",
|
|
867
|
+
"customers.config.activityTypes.options.meeting": "Spotkanie",
|
|
868
|
+
"customers.config.activityTypes.options.none": "Brak",
|
|
869
|
+
"customers.config.activityTypes.options.note": "Notatka",
|
|
870
|
+
"customers.config.activityTypes.options.participants": "Osoby uczestniczące",
|
|
871
|
+
"customers.config.activityTypes.options.phoneLink": "Łącze telefoniczne",
|
|
872
|
+
"customers.config.activityTypes.options.recipients": "Odbiorcy",
|
|
873
|
+
"customers.config.activityTypes.options.task": "Zadanie",
|
|
874
|
+
"customers.config.activityTypes.refresh": "Odśwież",
|
|
875
|
+
"customers.config.activityTypes.removed": "Usunięto konfigurację typu aktywności.",
|
|
876
|
+
"customers.config.activityTypes.reset": "Zresetuj nadpisanie",
|
|
877
|
+
"customers.config.activityTypes.resetConfirm": "Usunąć lokalną konfigurację typu aktywności?",
|
|
878
|
+
"customers.config.activityTypes.retry": "Ponów",
|
|
879
|
+
"customers.config.activityTypes.save": "Zapisz",
|
|
880
|
+
"customers.config.activityTypes.saveFailed": "Nie udało się zapisać typu aktywności.",
|
|
881
|
+
"customers.config.activityTypes.saved": "Zapisano typ aktywności.",
|
|
882
|
+
"customers.config.activityTypes.view": "Wyświetl",
|
|
883
|
+
"customers.config.activityTypes.yes": "Tak",
|
|
801
884
|
"customers.config.addressFormat.description": "Wybierz, jak powinny być zbudowane formularze i widoki w module klientów.",
|
|
802
885
|
"customers.config.addressFormat.error": "Nie udało się wczytać ustawień adresu",
|
|
803
886
|
"customers.config.addressFormat.errorSave": "Nie udało się zaktualizować ustawień adresu",
|
|
@@ -1,29 +1,70 @@
|
|
|
1
1
|
import type { CalendarItem, CalendarItemStatus, CalendarParticipant } from '../../components/calendar/types'
|
|
2
|
+
import { parseLinkedEntities } from './editorPayload'
|
|
2
3
|
import { participantActorKey } from './participantIdentity'
|
|
3
4
|
import type { ConflictScope } from './preferences'
|
|
4
5
|
|
|
5
6
|
export const EDITOR_DRAFT_CONFLICT_ID = '__draft__'
|
|
6
7
|
|
|
7
8
|
export type FindConflictsOptions = {
|
|
8
|
-
// 'all' (default) flags any
|
|
9
|
-
// where `currentUserId` is an actor of BOTH
|
|
10
|
-
//
|
|
9
|
+
// 'all' (default) flags any overlap that shares a person or a resource. 'mine'
|
|
10
|
+
// narrows the PEOPLE overlaps to those where `currentUserId` is an actor of BOTH
|
|
11
|
+
// events (the user is double-booked); a shared resource is flagged in both
|
|
12
|
+
// scopes. 'mine' with no `currentUserId` degrades to 'all' — it cannot resolve
|
|
13
|
+
// "mine".
|
|
11
14
|
scope?: ConflictScope
|
|
12
15
|
currentUserId?: string | null
|
|
13
16
|
}
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
export type ConflictSubject = { kind: 'person' | 'resource'; key: string; label: string }
|
|
19
|
+
|
|
20
|
+
function participantLabel(participant: CalendarParticipant): string {
|
|
21
|
+
return participant.name?.trim() || participant.email?.trim() || ''
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function bookedResources(item: CalendarItem): Array<{ id: string; label: string }> {
|
|
25
|
+
return parseLinkedEntities((item.raw as { linkedEntities?: unknown }).linkedEntities).resources
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The people and resources two events have in common — what makes an overlap a
|
|
30
|
+
* double-booking. A person is shared through the owner or a participant; a
|
|
31
|
+
* resource through a `resource` link. Labels are empty when the event carries no
|
|
32
|
+
* name for the subject.
|
|
33
|
+
*/
|
|
34
|
+
export function sharedConflictSubjects(first: CalendarItem, second: CalendarItem): ConflictSubject[] {
|
|
35
|
+
const shared = new Map<string, ConflictSubject>()
|
|
36
|
+
const labelOf = (item: CalendarItem, actorKey: string): string => {
|
|
37
|
+
const match = item.participants.find((participant) => participantActorKey(participant) === actorKey)
|
|
38
|
+
return match ? participantLabel(match) : ''
|
|
39
|
+
}
|
|
40
|
+
if (first.ownerUserId && second.ownerUserId && first.ownerUserId === second.ownerUserId) {
|
|
41
|
+
const key = `user:${first.ownerUserId}`
|
|
42
|
+
shared.set(key, { kind: 'person', key, label: labelOf(first, key) || labelOf(second, key) })
|
|
43
|
+
}
|
|
44
|
+
if (first.participants.length > 0 && second.participants.length > 0) {
|
|
45
|
+
const secondActorKeys = new Set(
|
|
46
|
+
second.participants
|
|
47
|
+
.map(participantActorKey)
|
|
48
|
+
.filter((actorKey): actorKey is string => actorKey !== null),
|
|
49
|
+
)
|
|
50
|
+
for (const participant of first.participants) {
|
|
51
|
+
const key = participantActorKey(participant)
|
|
52
|
+
if (key === null || !secondActorKeys.has(key) || shared.has(key)) continue
|
|
53
|
+
shared.set(key, { kind: 'person', key, label: participantLabel(participant) || labelOf(second, key) })
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const firstResources = bookedResources(first)
|
|
57
|
+
if (firstResources.length > 0) {
|
|
58
|
+
const secondResources = new Map(bookedResources(second).map((resource) => [resource.id, resource]))
|
|
59
|
+
for (const resource of firstResources) {
|
|
60
|
+
const other = secondResources.get(resource.id)
|
|
61
|
+
if (!other) continue
|
|
62
|
+
const key = `resource:${resource.id}`
|
|
63
|
+
const label = resource.label !== resource.id ? resource.label : other.label !== other.id ? other.label : ''
|
|
64
|
+
shared.set(key, { kind: 'resource', key, label })
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [...shared.values()]
|
|
27
68
|
}
|
|
28
69
|
|
|
29
70
|
function isActor(item: CalendarItem, userId: string): boolean {
|
|
@@ -52,12 +93,20 @@ export function findConflicts(items: CalendarItem[], options: FindConflictsOptio
|
|
|
52
93
|
const other = candidates[lookahead]
|
|
53
94
|
if (other.start.getTime() >= currentEnd) break
|
|
54
95
|
if (current.start.getTime() >= other.end.getTime()) continue
|
|
55
|
-
// Org-wide rule first: the two events must share at least one actor
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// 'mine'
|
|
60
|
-
|
|
96
|
+
// Org-wide rule first: the two events must share at least one actor or
|
|
97
|
+
// booked resource.
|
|
98
|
+
const shared = sharedConflictSubjects(current, other)
|
|
99
|
+
if (shared.length === 0) continue
|
|
100
|
+
// 'mine' narrows the people overlaps to the current user's OWN
|
|
101
|
+
// double-bookings — they must be an actor of both events. Because they are
|
|
102
|
+
// then the shared actor, 'mine' is always a strict subset of 'all' (it never
|
|
103
|
+
// invents new conflicts). A double-booked resource is not anyone's in
|
|
104
|
+
// particular — a room cannot host two events — so it is flagged in both scopes.
|
|
105
|
+
if (
|
|
106
|
+
restrictToCurrentUser
|
|
107
|
+
&& !shared.some((subject) => subject.kind === 'resource')
|
|
108
|
+
&& !(isActor(current, currentUserId) && isActor(other, currentUserId))
|
|
109
|
+
) continue
|
|
61
110
|
appendConflict(conflicts, current.id, other.id)
|
|
62
111
|
appendConflict(conflicts, other.id, current.id)
|
|
63
112
|
}
|
|
@@ -74,20 +123,29 @@ export type EditorConflictDraft = {
|
|
|
74
123
|
// exactly as the grid excludes canceled items, so the editor never warns about
|
|
75
124
|
// a record the grid would not badge/ring. Defaults to 'planned'.
|
|
76
125
|
status?: CalendarItemStatus
|
|
126
|
+
// Resources the draft books. Omitted when the type has no resources field.
|
|
127
|
+
resources?: Array<{ id: string; label: string }>
|
|
128
|
+
// Display name of `ownerUserId`, when the editor knows it (a task's assignee).
|
|
129
|
+
// An owner who is not also a participant has no name on either event.
|
|
130
|
+
ownerLabel?: string | null
|
|
77
131
|
}
|
|
78
132
|
|
|
133
|
+
export type EditorConflict = { item: CalendarItem; shared: ConflictSubject[] }
|
|
134
|
+
|
|
79
135
|
/**
|
|
80
136
|
* Resolves the items a calendar editor draft conflicts with, using the SAME
|
|
81
|
-
* `findConflicts` rules as the grid (overlap + shared owner/participant)
|
|
82
|
-
* editor's conflict warning is always consistent with the grid
|
|
83
|
-
*
|
|
137
|
+
* `findConflicts` rules as the grid (overlap + shared owner/participant/resource)
|
|
138
|
+
* so the editor's conflict warning is always consistent with the grid
|
|
139
|
+
* badges/rings. Each conflict names the people and resources that are
|
|
140
|
+
* double-booked. The edited record itself (`excludeId`) is never treated as a
|
|
141
|
+
* conflict.
|
|
84
142
|
*/
|
|
85
|
-
export function
|
|
143
|
+
export function findEditorConflicts(
|
|
86
144
|
draft: EditorConflictDraft,
|
|
87
145
|
others: CalendarItem[],
|
|
88
146
|
excludeId: string | null,
|
|
89
147
|
options: FindConflictsOptions = {},
|
|
90
|
-
):
|
|
148
|
+
): EditorConflict[] {
|
|
91
149
|
const draftStatus: CalendarItemStatus = draft.status ?? 'planned'
|
|
92
150
|
const draftItem: CalendarItem = {
|
|
93
151
|
id: EDITOR_DRAFT_CONFLICT_ID,
|
|
@@ -108,7 +166,12 @@ export function findEditorConflictItems(
|
|
|
108
166
|
color: null,
|
|
109
167
|
isRecurringOccurrence: false,
|
|
110
168
|
updatedAt: null,
|
|
111
|
-
raw: {
|
|
169
|
+
raw: {
|
|
170
|
+
id: EDITOR_DRAFT_CONFLICT_ID,
|
|
171
|
+
interactionType: 'meeting',
|
|
172
|
+
status: draftStatus,
|
|
173
|
+
linkedEntities: (draft.resources ?? []).map((resource) => ({ id: resource.id, type: 'resource', label: resource.label })),
|
|
174
|
+
},
|
|
112
175
|
}
|
|
113
176
|
// Exclude the edited record by its underlying interaction id (`raw.id`), which
|
|
114
177
|
// also drops every expanded occurrence of an edited recurring series (those
|
|
@@ -122,4 +185,20 @@ export function findEditorConflictItems(
|
|
|
122
185
|
return conflictIds
|
|
123
186
|
.map((id) => byId.get(id))
|
|
124
187
|
.filter((item): item is CalendarItem => Boolean(item))
|
|
188
|
+
.map((item) => ({
|
|
189
|
+
item,
|
|
190
|
+
shared: sharedConflictSubjects(draftItem, item).map((subject) =>
|
|
191
|
+
!subject.label && draft.ownerLabel && subject.key === `user:${draft.ownerUserId}`
|
|
192
|
+
? { ...subject, label: draft.ownerLabel }
|
|
193
|
+
: subject),
|
|
194
|
+
}))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function findEditorConflictItems(
|
|
198
|
+
draft: EditorConflictDraft,
|
|
199
|
+
others: CalendarItem[],
|
|
200
|
+
excludeId: string | null,
|
|
201
|
+
options: FindConflictsOptions = {},
|
|
202
|
+
): CalendarItem[] {
|
|
203
|
+
return findEditorConflicts(draft, others, excludeId, options).map((conflict) => conflict.item)
|
|
125
204
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { CalendarItem } from '../../components/calendar/types'
|
|
2
|
+
import { calendarEventTypeKeySchema, resolveCalendarEventType } from '../../calendar-event-types'
|
|
2
3
|
import { parseRecurrenceRule } from './recurrence'
|
|
4
|
+
import { calendarDayEndInstant, calendarDayStartInstant, calendarInstantToWallTime, calendarWallTimeToInstant, defaultCalendarTimezone, isCalendarTimezone } from './timezone'
|
|
3
5
|
|
|
4
6
|
export type EditorKind = 'meeting' | 'call' | 'email' | 'note' | 'event' | 'task'
|
|
5
7
|
|
|
@@ -41,7 +43,11 @@ const KIND_BY_INTERACTION_TYPE: Record<string, EditorKind> = {
|
|
|
41
43
|
deadline: 'task',
|
|
42
44
|
}
|
|
43
45
|
|
|
46
|
+
/** @deprecated Resolve the effective calendar event type and read `behavior.baseKind`. */
|
|
44
47
|
export function editorKindOfInteractionType(interactionType: string): EditorKind {
|
|
48
|
+
const parsedKey = calendarEventTypeKeySchema.safeParse(interactionType)
|
|
49
|
+
const effectiveKind = parsedKey.success ? resolveCalendarEventType(parsedKey.data)?.behavior.baseKind : undefined
|
|
50
|
+
if (effectiveKind) return effectiveKind
|
|
45
51
|
return KIND_BY_INTERACTION_TYPE[interactionType] ?? 'meeting'
|
|
46
52
|
}
|
|
47
53
|
|
|
@@ -161,6 +167,7 @@ export type EditorFormState = {
|
|
|
161
167
|
allDay: boolean
|
|
162
168
|
date: string
|
|
163
169
|
startTime: string
|
|
170
|
+
timezone?: string
|
|
164
171
|
endDate: string
|
|
165
172
|
endTime: string
|
|
166
173
|
repeatFreq: EditorRepeatFreq
|
|
@@ -245,6 +252,7 @@ export function createDefaultFormState(
|
|
|
245
252
|
}
|
|
246
253
|
return {
|
|
247
254
|
kind: 'meeting',
|
|
255
|
+
timezone: defaultCalendarTimezone(),
|
|
248
256
|
title: '',
|
|
249
257
|
relatedTo: null,
|
|
250
258
|
dealId: null,
|
|
@@ -286,21 +294,23 @@ export function buildRecurrenceRule(state: EditorFormState): string | null {
|
|
|
286
294
|
}
|
|
287
295
|
if (state.repeatEndType === 'count') rule += `;COUNT=${state.repeatCount}`
|
|
288
296
|
if (state.repeatEndType === 'date' && state.repeatUntilDate) {
|
|
289
|
-
|
|
297
|
+
const until = state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : null
|
|
298
|
+
rule += `;UNTIL=${until ? until.toISOString().replace(/[-:]/g, '').replace(/\.\d{3}Z$/, 'Z') : `${state.repeatUntilDate.replace(/-/g, '')}T235959Z`}`
|
|
290
299
|
}
|
|
291
300
|
return rule
|
|
292
301
|
}
|
|
293
302
|
|
|
294
303
|
export function computeDurationMinutes(state: EditorFormState): number | null {
|
|
295
|
-
const start = new Date(`${state.date}T${state.startTime}:00`)
|
|
296
|
-
const end = new Date(`${state.endDate}T${state.endTime}:00`)
|
|
297
|
-
if (Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null
|
|
304
|
+
const start = state.timezone ? calendarWallTimeToInstant(state.date, state.startTime, state.timezone) : new Date(`${state.date}T${state.startTime}:00`)
|
|
305
|
+
const end = state.timezone ? calendarWallTimeToInstant(state.endDate, state.endTime, state.timezone) : new Date(`${state.endDate}T${state.endTime}:00`)
|
|
306
|
+
if (!start || !end || Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())) return null
|
|
298
307
|
const minutes = Math.round((end.getTime() - start.getTime()) / 60_000)
|
|
299
308
|
return minutes > 0 ? minutes : null
|
|
300
309
|
}
|
|
301
310
|
|
|
302
311
|
export type BuildPayloadOptions = {
|
|
303
312
|
mode: 'create' | 'edit'
|
|
313
|
+
config?: EditorKindConfig
|
|
304
314
|
id?: string
|
|
305
315
|
/**
|
|
306
316
|
* When the resources module is loaded the payload owns `linkedEntities`
|
|
@@ -317,10 +327,16 @@ export type BuildPayloadOptions = {
|
|
|
317
327
|
}
|
|
318
328
|
|
|
319
329
|
export function buildInteractionPayload(state: EditorFormState, options: BuildPayloadOptions): Record<string, unknown> {
|
|
320
|
-
const config = KIND_CONFIG[state.kind]
|
|
330
|
+
const config = options.config ?? KIND_CONFIG[state.kind]
|
|
321
331
|
const time = state.allDay && config.hasAllDay ? '00:00' : state.startTime
|
|
322
|
-
const
|
|
332
|
+
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`)
|
|
333
|
+
if (!instant || !Number.isFinite(instant.getTime())) throw new Error('[internal] Invalid calendar local time')
|
|
334
|
+
const scheduledAt = instant.toISOString()
|
|
323
335
|
const recurrenceRule = config.hasRepeat ? buildRecurrenceRule(state) : null
|
|
336
|
+
const recurrenceEnd = recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate
|
|
337
|
+
? state.timezone ? calendarDayEndInstant(state.repeatUntilDate, state.timezone) : new Date(state.repeatUntilDate)
|
|
338
|
+
: null
|
|
339
|
+
if (recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate && !recurrenceEnd) throw new Error('[internal] Invalid calendar recurrence day')
|
|
324
340
|
const payload: Record<string, unknown> = {
|
|
325
341
|
...(options.mode === 'edit' && options.id ? { id: options.id } : {}),
|
|
326
342
|
entityId: state.relatedTo?.id ?? null,
|
|
@@ -332,14 +348,12 @@ export function buildInteractionPayload(state: EditorFormState, options: BuildPa
|
|
|
332
348
|
date: state.date,
|
|
333
349
|
time,
|
|
334
350
|
scheduledAt,
|
|
351
|
+
...(state.timezone ? { timezone: state.timezone } : {}),
|
|
335
352
|
durationMinutes: config.hasEnd && !(state.allDay && config.hasAllDay) ? computeDurationMinutes(state) : null,
|
|
336
353
|
allDay: config.hasAllDay ? state.allDay : null,
|
|
337
354
|
location: config.location ? state.location.trim() || null : null,
|
|
338
355
|
recurrenceRule,
|
|
339
|
-
recurrenceEnd:
|
|
340
|
-
recurrenceRule && state.repeatEndType === 'date' && state.repeatUntilDate
|
|
341
|
-
? new Date(state.repeatUntilDate).toISOString()
|
|
342
|
-
: null,
|
|
356
|
+
recurrenceEnd: recurrenceEnd?.toISOString() ?? null,
|
|
343
357
|
participants:
|
|
344
358
|
config.people && config.people !== 'assignee' && state.participants.length > 0
|
|
345
359
|
? state.participants.map((participant) => ({
|
|
@@ -374,10 +388,11 @@ function readUnknownNumber(value: unknown): number | null {
|
|
|
374
388
|
|
|
375
389
|
type ParsedRepeat = Pick<EditorFormState, 'repeatFreq' | 'repeatDays' | 'repeatEndType' | 'repeatCount' | 'repeatUntilDate'>
|
|
376
390
|
|
|
377
|
-
function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
|
|
391
|
+
function parseRepeatFromRule(rawRule: unknown, start: Date, timezone?: string): ParsedRepeat {
|
|
392
|
+
const wallStart = timezone ? new Date(`${calendarInstantToWallTime(start, timezone).date}T12:00:00`) : start
|
|
378
393
|
const fallback: ParsedRepeat = {
|
|
379
394
|
repeatFreq: 'none',
|
|
380
|
-
repeatDays: defaultRepeatDays(
|
|
395
|
+
repeatDays: defaultRepeatDays(wallStart),
|
|
381
396
|
repeatEndType: 'never',
|
|
382
397
|
repeatCount: 8,
|
|
383
398
|
repeatUntilDate: '',
|
|
@@ -386,7 +401,7 @@ function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
|
|
|
386
401
|
if (!ruleText) return fallback
|
|
387
402
|
const parsed = parseRecurrenceRule(ruleText)
|
|
388
403
|
if (!parsed) return fallback
|
|
389
|
-
const repeatDays = defaultRepeatDays(
|
|
404
|
+
const repeatDays = defaultRepeatDays(wallStart)
|
|
390
405
|
if (parsed.byDay) {
|
|
391
406
|
repeatDays.fill(false)
|
|
392
407
|
for (const jsWeekday of parsed.byDay) repeatDays[(jsWeekday + 6) % 7] = true
|
|
@@ -399,7 +414,7 @@ function parseRepeatFromRule(rawRule: unknown, start: Date): ParsedRepeat {
|
|
|
399
414
|
repeatCount = parsed.count
|
|
400
415
|
} else if (parsed.until) {
|
|
401
416
|
repeatEndType = 'date'
|
|
402
|
-
repeatUntilDate = `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`
|
|
417
|
+
repeatUntilDate = timezone ? calendarInstantToWallTime(parsed.until, timezone).date : `${parsed.until.getUTCFullYear()}-${padDatePart(parsed.until.getUTCMonth() + 1)}-${padDatePart(parsed.until.getUTCDate())}`
|
|
403
418
|
}
|
|
404
419
|
return {
|
|
405
420
|
repeatFreq: parsed.freq === 'DAILY' ? 'daily' : 'weekly',
|
|
@@ -427,6 +442,9 @@ export function parseItemToFormState(item: CalendarItem): EditorFormState {
|
|
|
427
442
|
const kind = editorKindOfInteractionType(item.interactionType)
|
|
428
443
|
const raw = item.raw as Record<string, unknown>
|
|
429
444
|
const { resources, preservedLinkedEntities } = parseLinkedEntities(raw.linkedEntities)
|
|
445
|
+
const timezone = isCalendarTimezone(raw.timezone) ? raw.timezone : defaultCalendarTimezone()
|
|
446
|
+
const startWall = calendarInstantToWallTime(item.start, timezone)
|
|
447
|
+
const endWall = calendarInstantToWallTime(item.end, timezone)
|
|
430
448
|
return {
|
|
431
449
|
kind,
|
|
432
450
|
title: item.title,
|
|
@@ -434,11 +452,13 @@ export function parseItemToFormState(item: CalendarItem): EditorFormState {
|
|
|
434
452
|
dealId: item.dealId,
|
|
435
453
|
dealLabel: null,
|
|
436
454
|
allDay: item.allDay,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
455
|
+
timezone,
|
|
456
|
+
date: startWall.date,
|
|
457
|
+
startTime: startWall.time,
|
|
458
|
+
endDate: endWall.date,
|
|
459
|
+
endTime: endWall.time,
|
|
460
|
+
...parseRepeatFromRule(item.raw.recurrenceRule, item.start, isCalendarTimezone(raw.timezone) ? timezone : undefined),
|
|
461
|
+
...(item.raw.recurrenceEnd && isCalendarTimezone(raw.timezone) ? { repeatUntilDate: calendarInstantToWallTime(new Date(item.raw.recurrenceEnd), timezone).date } : {}),
|
|
442
462
|
category: item.interactionType,
|
|
443
463
|
location: item.location ?? '',
|
|
444
464
|
participants: parseParticipants(item),
|