Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / calendar / src / app / components / eventModal / rows / BusyParticipantRow.tsx
blob70611a9d857c6bb4f14afb3ad575fc38f0d599af
1 import { c } from 'ttag';
3 import { Button } from '@proton/atoms';
4 import { Icon, Tooltip } from '@proton/components';
5 import { ICAL_ATTENDEE_ROLE } from '@proton/shared/lib/calendar/constants';
6 import { getContactDisplayNameEmail } from '@proton/shared/lib/contacts/contactEmail';
7 import { canonicalizeEmail } from '@proton/shared/lib/helpers/email';
8 import type { AttendeeModel } from '@proton/shared/lib/interfaces/calendar';
9 import type { ContactEmail } from '@proton/shared/lib/interfaces/contacts';
10 import type { SimpleMap } from '@proton/shared/lib/interfaces/utils';
11 import clsx from '@proton/utils/clsx';
13 import { selectAttendeeBusyData } from '../../../store/busySlots/busySlotsSelectors';
14 import { busySlotsActions } from '../../../store/busySlots/busySlotsSlice';
15 import { useCalendarDispatch, useCalendarSelector } from '../../../store/hooks';
16 import BusyParticipantRowDot from './BusyPartipantRowDot';
18 interface Props {
19     attendee: AttendeeModel;
20     contactEmailsMap: SimpleMap<ContactEmail>;
21     onDelete: (attendee: AttendeeModel) => void;
22     onHighlight: (attendeeEmail: string, highlighted: boolean) => void;
23     onToggleOptional: (attendee: AttendeeModel) => void;
26 const BusyParticipantRow = ({ attendee, contactEmailsMap, onDelete, onHighlight, onToggleOptional }: Props) => {
27     const dispatch = useCalendarDispatch();
28     const { email: attendeeEmail, role } = attendee;
29     const canonicalizedEmail = canonicalizeEmail(attendeeEmail);
30     const { status, hasAvailability, isVisible, color } = useCalendarSelector((state) =>
31         selectAttendeeBusyData(state, canonicalizedEmail)
32     );
33     const isOptional = role === ICAL_ATTENDEE_ROLE.OPTIONAL;
34     const { Name: contactName, Email: contactEmail } = contactEmailsMap[canonicalizedEmail] || {};
35     const email = contactEmail || attendeeEmail;
37     const { nameEmail, displayOnlyEmail } = getContactDisplayNameEmail({ name: contactName, email });
39     const handleVisibilityToggle = () => {
40         if (status === 'loading' || status === 'not-available') {
41             return;
42         }
43         dispatch(
44             busySlotsActions.setAttendeeVisibility({
45                 email: attendeeEmail,
46                 visible: !isVisible,
47             })
48         );
49     };
51     const visibilityText = (() => {
52         if (status === 'not-available') {
53             return c('Description').t`Availability unknown`;
54         }
55         if (status === 'loading') {
56             return c('Description').t`Loading availability`;
57         }
58         return isVisible ? c('Action').t`Hide busy times` : c('Action').t`Show busy times`;
59     })();
61     const dotDisplay = (() => {
62         if (status === 'loading') {
63             return 'loader';
64         }
65         if (!hasAvailability) {
66             return 'half-circle';
67         }
68         return isVisible ? 'circle' : 'bordered-circle';
69     })();
71     const dotClassName = (() => {
72         if (status !== 'loading' && status !== 'not-available') {
73             return 'cursor-pointer';
74         }
75         return '';
76     })();
78     return (
79         <div
80             key={email}
81             className="flex items-start mb-1 group-hover-opacity-container"
82             onMouseEnter={() => {
83                 onHighlight(email, true);
84             }}
85             onMouseLeave={() => {
86                 onHighlight(email, false);
87             }}
88         >
89             <div
90                 data-testid="busy-participant-dot"
91                 className="shrink-0 flex mt-0.5 pt-2 w-custom"
92                 style={{ width: '1rem' }}
93             >
94                 <BusyParticipantRowDot
95                     display={dotDisplay}
96                     color={color}
97                     tooltipText={visibilityText}
98                     onClick={handleVisibilityToggle}
99                     classname={dotClassName}
100                 />
101             </div>
103             <div className={clsx('flex flex-1 p-1')} title={nameEmail} data-testid="busy-participant">
104                 <div className={clsx(['text-ellipsis', displayOnlyEmail && 'max-w-full'])}>
105                     {contactName ? (
106                         <>
107                             <span className="text-semibold text-sm" data-testid="busy-participant:contact-name">
108                                 {contactName}
109                             </span>
110                             <span className="color-weak ml-1 text-sm" data-testid="busy-participant:contact-email">
111                                 {email}
112                             </span>
113                         </>
114                     ) : (
115                         <span className="text-semibold text-sm" data-testid="busy-participant:email">
116                             {email}
117                         </span>
118                     )}
119                 </div>
120                 {isOptional && <span className="color-weak w-full text-sm">{c('Label').t`Optional`}</span>}
121             </div>
123             <Tooltip title={visibilityText}>
124                 <div className={clsx(status === 'not-available' && 'hidden')}>
125                     <Button
126                         icon
127                         shape="ghost"
128                         type="button"
129                         size="small"
130                         className="flex shrink-0 group-hover:opacity-100 group-hover:opacity-100-no-width"
131                         onClick={handleVisibilityToggle}
132                     >
133                         <Icon name={isVisible ? 'eye' : 'eye-slash'} alt={visibilityText} />
134                     </Button>
135                 </div>
136             </Tooltip>
137             <Tooltip
138                 title={
139                     isOptional
140                         ? c('Action').t`Make this participant required`
141                         : c('Action').t`Make this participant optional`
142                 }
143             >
144                 <Button
145                     icon
146                     shape="ghost"
147                     size="small"
148                     type="button"
149                     className="flex shrink-0 group-hover:opacity-100 group-hover:opacity-100-no-width"
150                     onClick={() => onToggleOptional(attendee)}
151                 >
152                     <Icon name={isOptional ? 'user' : 'user-filled'} alt={c('Action').t`Remove this participant`} />
153                 </Button>
154             </Tooltip>
155             <Tooltip title={c('Action').t`Remove this participant`}>
156                 <Button
157                     icon
158                     shape="ghost"
159                     size="small"
160                     className="flex shrink-0 group-hover:opacity-100 group-hover:opacity-100-no-width"
161                     onClick={() => {
162                         dispatch(busySlotsActions.removeAttendee(attendeeEmail));
163                         onDelete(attendee);
164                     }}
165                 >
166                     <Icon name="cross" alt={c('Action').t`Remove this participant`} />
167                 </Button>
168             </Tooltip>
169         </div>
170     );
173 export default BusyParticipantRow;