Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / calendar / src / app / components / eventModal / eventForm / modelToProperties.ts
blobce32c2b80ccbb401d757063148aad1739323752d
1 import { addDays, isSameDay } from 'date-fns';
3 import {
4     addZoomInfoToDescription,
5     removeZoomInfoFromDescription,
6 } from '@proton/calendar/components/videoConferencing/zoom/zoomHelpers';
7 import { dedupeNotifications } from '@proton/shared/lib/calendar/alarms';
8 import { modelToValarmComponent } from '@proton/shared/lib/calendar/alarms/modelToValarm';
9 import { ICAL_EVENT_STATUS, MAX_CHARS_API } from '@proton/shared/lib/calendar/constants';
10 import {
11     buildVcalOrganizer,
12     getDateProperty,
13     getDateTimeProperty,
14     propertyToUTCDate,
15 } from '@proton/shared/lib/calendar/vcalConverter';
16 import { withRequiredProperties } from '@proton/shared/lib/calendar/veventHelper';
17 import { omit } from '@proton/shared/lib/helpers/object';
18 import type { DateTimeModel, EventModel } from '@proton/shared/lib/interfaces/calendar';
19 import type { VcalVeventComponent } from '@proton/shared/lib/interfaces/calendar/VcalModel';
21 import modelToFrequencyProperties from './modelToFrequencyProperties';
23 export const modelToDateProperty = ({ date, time, tzid }: DateTimeModel, isAllDay: boolean) => {
24     const dateObject = {
25         year: date.getFullYear(),
26         month: date.getMonth() + 1,
27         day: date.getDate(),
28     };
30     if (isAllDay) {
31         return getDateProperty(dateObject);
32     }
34     const dateTimeObject = {
35         ...dateObject,
36         hours: time.getHours(),
37         minutes: time.getMinutes(),
38         seconds: 0,
39     };
41     return getDateTimeProperty(dateTimeObject, tzid);
44 const modelToDateProperties = ({ start, end, isAllDay }: EventModel) => {
45     const dtstart = modelToDateProperty(start, isAllDay);
47     // All day events date ranges are stored non-inclusively, so add a full day from the selected date to the end date
48     const modifiedEnd = isAllDay ? { ...end, date: addDays(end.date, 1) } : end;
49     const dtend = modelToDateProperty(modifiedEnd, isAllDay);
51     const ignoreDtend = isAllDay
52         ? isSameDay(start.date, end.date)
53         : +propertyToUTCDate(dtstart) === +propertyToUTCDate(dtend);
55     return ignoreDtend ? { dtstart } : { dtstart, dtend };
58 export const modelToGeneralProperties = ({
59     uid,
60     title,
61     location,
62     status,
63     color,
64     rest,
65 }: Partial<EventModel>): Omit<VcalVeventComponent, 'dtstart' | 'dtend'> => {
66     const properties = omit(rest, ['dtstart', 'dtend']);
68     if (title) {
69         properties.summary = { value: title.trim().slice(0, MAX_CHARS_API.TITLE) };
70     }
72     if (uid) {
73         properties.uid = { value: uid };
74     }
76     if (location) {
77         properties.location = { value: location.slice(0, MAX_CHARS_API.LOCATION) };
78     }
80     if (color) {
81         properties.color = { value: color };
82     }
84     properties.status = { value: status || ICAL_EVENT_STATUS.CONFIRMED };
86     return properties;
89 const modelToOrganizerProperties = ({ organizer }: EventModel) => {
90     const organizerEmail = organizer?.email;
91     if (!organizerEmail) {
92         return {};
93     }
94     return {
95         organizer: buildVcalOrganizer(organizerEmail, organizer?.cn || organizerEmail),
96     };
99 const modelToAttendeeProperties = ({ attendees }: EventModel) => {
100     if (!Array.isArray(attendees) || !attendees.length) {
101         return {};
102     }
103     return {
104         attendee: attendees.map(({ email, rsvp, role, token, partstat }) => ({
105             value: email,
106             parameters: {
107                 // cutype: 'INDIVIDUAL',
108                 cn: email,
109                 role,
110                 rsvp,
111                 partstat,
112                 'x-pm-token': token,
113             },
114         })),
115     };
118 const modelToVideoConferenceProperties = ({
119     conferenceId,
120     conferencePassword,
121     conferenceUrl,
122     conferenceHost,
123 }: Partial<EventModel>) => {
124     if (!conferenceId || !conferenceUrl) {
125         return;
126     }
128     return {
129         'x-pm-conference-id': {
130             value: conferenceId,
131             parameters: {
132                 provider: '1',
133             },
134         },
135         'x-pm-conference-url': {
136             value: conferenceUrl,
137             parameters: {
138                 ...(conferencePassword && { password: conferencePassword }),
139                 ...(conferenceHost && { host: conferenceHost }),
140             },
141         },
142     };
145 const modelToDescriptionProperties = ({
146     description,
147     conferenceId,
148     conferencePassword,
149     conferenceUrl,
150     conferenceHost,
151 }: Partial<EventModel>) => {
152     const hasZoom = !!(conferenceUrl && conferenceId);
154     // Return an empty object if there is no description and no Zoom meeting
155     if (!description && !hasZoom) {
156         return {};
157     }
159     // Return the description if there is no Zoom meeting
160     if (description && !hasZoom) {
161         const cleanedDescription = removeZoomInfoFromDescription(description ?? '');
162         return { description: { value: cleanedDescription?.slice(0, MAX_CHARS_API.EVENT_DESCRIPTION) } };
163     }
165     // We remove the Zoom info from the description to avoid saving it twice
166     const cleanedDescription = removeZoomInfoFromDescription(description ?? '');
167     // We slice the description smaller to avoid too long descriptions with the generated Zoom info
168     const slicedDescription = cleanedDescription?.slice(0, MAX_CHARS_API.EVENT_DESCRIPTION);
169     const newDescription = addZoomInfoToDescription({
170         host: conferenceHost,
171         meedingURL: conferenceUrl,
172         password: conferencePassword,
173         meetingId: conferenceId,
174         description: slicedDescription,
175     });
177     return {
178         description: { value: newDescription },
179     };
182 export const modelToValarmComponents = ({ isAllDay, fullDayNotifications, partDayNotifications }: EventModel) =>
183     dedupeNotifications(isAllDay ? fullDayNotifications : partDayNotifications).map((notification) =>
184         modelToValarmComponent(notification)
185     );
187 export const modelToVeventComponent = (model: EventModel) => {
188     const dateProperties = modelToDateProperties(model);
189     const frequencyProperties = modelToFrequencyProperties(model);
190     const organizerProperties = modelToOrganizerProperties(model);
191     const attendeeProperties = modelToAttendeeProperties(model);
192     const generalProperties = modelToGeneralProperties(model);
193     const valarmComponents = modelToValarmComponents(model);
194     const descriptionProperties = modelToDescriptionProperties(model);
195     const videoConferenceProperties = modelToVideoConferenceProperties(model);
197     return withRequiredProperties({
198         ...generalProperties,
199         ...frequencyProperties,
200         ...dateProperties,
201         ...organizerProperties,
202         ...attendeeProperties,
203         ...videoConferenceProperties,
204         ...descriptionProperties,
205         component: 'vevent',
206         components: [...valarmComponents],
207     });