Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / calendar / src / app / components / eventModal / rows / DateTimeRow.tsx
blobab28137603370ffe7fe0306a0e4a79cd2c3a416e
1 import { useMemo, useState } from 'react';
3 import { c } from 'ttag';
5 import {
6     DateInput,
7     MemoizedIconRow as IconRow,
8     TimeInput,
9     TimeZoneSelector,
10     UnderlineButton,
11     useActiveBreakpoint,
12 } from '@proton/components';
13 import { DATE_INPUT_ID, MAXIMUM_DATE, MINIMUM_DATE } from '@proton/shared/lib/calendar/constants';
14 import type { WeekStartsOn } from '@proton/shared/lib/date-fns-utc/interface';
15 import { convertUTCDateTimeToZone, fromUTCDate, toUTCDate } from '@proton/shared/lib/date/timezone';
16 import type { EventModel } from '@proton/shared/lib/interfaces/calendar';
17 import clsx from '@proton/utils/clsx';
19 import getFrequencyModelChange from '../eventForm/getFrequencyModelChange';
20 import { getAllDayCheck } from '../eventForm/stateActions';
21 import { getDateTime, getDateTimeState, getTimeInUtc } from '../eventForm/time';
22 import useDateTimeFormHandlers from '../hooks/useDateTimeFormHandlers';
23 import AllDayCheckbox from '../inputs/AllDayCheckbox';
25 interface Props {
26     model: EventModel;
27     setModel: (value: EventModel) => void;
28     displayWeekNumbers: boolean;
29     weekStartsOn: WeekStartsOn;
30     endError?: string;
31     tzid: string;
34 export const DateTimeRow = ({ model, setModel, displayWeekNumbers, weekStartsOn, endError, tzid }: Props) => {
35     const { start, end, frequencyModel, isAllDay } = model;
36     const {
37         handleChangeStartDate,
38         handleChangeStartTime,
39         handleChangeEndDate,
40         handleChangeEndTime,
41         minEndDate,
42         minEndTime,
43         isDuration,
44     } = useDateTimeFormHandlers({ model, setModel });
45     const canToggleTzSelector = start.tzid === end.tzid && start.tzid === tzid;
46     const [showTzSelector, setShowTzSelector] = useState<boolean>(!canToggleTzSelector);
47     const handleChangeStart = (tzid: string) => {
48         const startUtcDate = getTimeInUtc(start, false);
49         const newStartUtcDate = toUTCDate(convertUTCDateTimeToZone(fromUTCDate(startUtcDate), tzid));
50         const newStart = getDateTimeState(newStartUtcDate, tzid);
51         const newFrequencyModel = getFrequencyModelChange(start, newStart, frequencyModel);
53         setModel({
54             ...model,
55             start: newStart,
56             frequencyModel: newFrequencyModel,
57         });
58     };
59     const handleChangeEnd = (tzid: string) => {
60         const endUtcDate = getTimeInUtc(end, false);
61         const newEndUtcDate = toUTCDate(convertUTCDateTimeToZone(fromUTCDate(endUtcDate), tzid));
63         setModel({
64             ...model,
65             end: getDateTimeState(newEndUtcDate, tzid),
66         });
67     };
69     const startDateTime = useMemo(() => getDateTime(start), [start]);
70     const endDateTime = useMemo(() => getDateTime(end), [end]);
72     const { viewportWidth } = useActiveBreakpoint();
74     return (
75         <IconRow id={DATE_INPUT_ID} icon="clock" title={c('Label').t`Date and time`}>
76             <div className={clsx([isAllDay && 'w-full md:w-1/2'])}>
77                 <div className="flex flex-nowrap flex-column md:flex-row mb-2">
78                     <div className="flex flex-nowrap md:flex-1 grow">
79                         <div className="flex *:min-size-auto flex-1 grow-custom" style={{ '--grow-custom': '1.25' }}>
80                             <DateInput
81                                 id={DATE_INPUT_ID}
82                                 className={clsx(['flex-1', viewportWidth['<=small'] && 'h-full'])}
83                                 required
84                                 value={start.date}
85                                 onChange={handleChangeStartDate}
86                                 displayWeekNumbers={displayWeekNumbers}
87                                 weekStartsOn={weekStartsOn}
88                                 min={MINIMUM_DATE}
89                                 max={MAXIMUM_DATE}
90                                 title={c('Title').t`Select event start date`}
91                             />
92                         </div>
94                         {!isAllDay && (
95                             <div className="ml-2 flex-1">
96                                 <TimeInput
97                                     id="event-startTime"
98                                     value={start.time}
99                                     onChange={handleChangeStartTime}
100                                     title={c('Title').t`Select event start time`}
101                                 />
102                             </div>
103                         )}
104                     </div>
106                     {!isAllDay && showTzSelector && (
107                         <TimeZoneSelector
108                             className="field ml-0 md:ml-2 mt-2 md:mt-0 mb-2 md:mb-2 md:flex-1"
109                             id="event-start-timezone-select"
110                             data-testid="create-event-modal/start:time-zone-dropdown"
111                             timezone={start.tzid}
112                             onChange={handleChangeStart}
113                             date={startDateTime}
114                             title={c('Title').t`Select the time zone for the event start time`}
115                             telemetrySource="event_start"
116                         />
117                     )}
118                 </div>
120                 <div className="flex flex-nowrap flex-column md:flex-row mb-2">
121                     <div className="flex flex-nowrap md:flex-1 grow">
122                         <div className="flex *:min-size-auto flex-1 grow-custom" style={{ '--grow-custom': '1.25' }}>
123                             <DateInput
124                                 id="event-endDate"
125                                 className={clsx(['flex-1', viewportWidth['<=small'] && 'h-full'])}
126                                 required
127                                 value={end.date}
128                                 onChange={handleChangeEndDate}
129                                 aria-invalid={!!endError}
130                                 displayWeekNumbers={displayWeekNumbers}
131                                 weekStartsOn={weekStartsOn}
132                                 min={minEndDate}
133                                 max={MAXIMUM_DATE}
134                                 title={c('Title').t`Select event end date`}
135                             />
136                         </div>
138                         {!isAllDay && (
139                             <div className="ml-2 flex-1">
140                                 <TimeInput
141                                     id="event-endTime"
142                                     value={end.time}
143                                     onChange={handleChangeEndTime}
144                                     aria-invalid={!!endError}
145                                     displayDuration={isDuration}
146                                     min={minEndTime}
147                                     title={c('Title').t`Select event end time`}
148                                 />
149                             </div>
150                         )}
151                     </div>
153                     {!isAllDay && showTzSelector && (
154                         <TimeZoneSelector
155                             className="field ml-0 md:ml-2 mt-2 md:mt-0 mb-2 md:mb-2 md:flex-1"
156                             id="event-end-timezone-select"
157                             data-testid="create-event-modal/end:time-zone-dropdown"
158                             timezone={end.tzid}
159                             onChange={handleChangeEnd}
160                             date={endDateTime}
161                             title={c('Title').t`Select the time zone for the event end time`}
162                             telemetrySource="event_end"
163                         />
164                     )}
165                 </div>
166             </div>
168             <div className="flex justify-space-between gap-2">
169                 <AllDayCheckbox
170                     title={
171                         model.isAllDay
172                             ? c('Title').t`Event is happening on defined time slot`
173                             : c('Title').t`Event is happening all day`
174                     }
175                     checked={isAllDay}
176                     onChange={(isAllDay) => setModel({ ...model, ...getAllDayCheck({ oldModel: model, isAllDay }) })}
177                 />
179                 {!isAllDay &&
180                     canToggleTzSelector &&
181                     (showTzSelector ? (
182                         <UnderlineButton
183                             className="p-0"
184                             data-testid="hide-tz"
185                             onClick={() => setShowTzSelector(false)}
186                             title={c('Title').t`Hide time zones for event start and end times`}
187                         >
188                             {c('Action').t`Hide time zones`}
189                         </UnderlineButton>
190                     ) : (
191                         <UnderlineButton
192                             className="p-0"
193                             data-testid="show-tz"
194                             onClick={() => setShowTzSelector(true)}
195                             title={c('Title').t`Show time zones for event start and end times`}
196                         >
197                             {c('Action').t`Show time zones`}
198                         </UnderlineButton>
199                     ))}
200             </div>
201         </IconRow>
202     );