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';
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)
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') {
44 busySlotsActions.setAttendeeVisibility({
51 const visibilityText = (() => {
52 if (status === 'not-available') {
53 return c('Description').t`Availability unknown`;
55 if (status === 'loading') {
56 return c('Description').t`Loading availability`;
58 return isVisible ? c('Action').t`Hide busy times` : c('Action').t`Show busy times`;
61 const dotDisplay = (() => {
62 if (status === 'loading') {
65 if (!hasAvailability) {
68 return isVisible ? 'circle' : 'bordered-circle';
71 const dotClassName = (() => {
72 if (status !== 'loading' && status !== 'not-available') {
73 return 'cursor-pointer';
81 className="flex items-start mb-1 group-hover-opacity-container"
83 onHighlight(email, true);
86 onHighlight(email, false);
90 data-testid="busy-participant-dot"
91 className="shrink-0 flex mt-0.5 pt-2 w-custom"
92 style={{ width: '1rem' }}
94 <BusyParticipantRowDot
97 tooltipText={visibilityText}
98 onClick={handleVisibilityToggle}
99 classname={dotClassName}
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'])}>
107 <span className="text-semibold text-sm" data-testid="busy-participant:contact-name">
110 <span className="color-weak ml-1 text-sm" data-testid="busy-participant:contact-email">
115 <span className="text-semibold text-sm" data-testid="busy-participant:email">
120 {isOptional && <span className="color-weak w-full text-sm">{c('Label').t`Optional`}</span>}
123 <Tooltip title={visibilityText}>
124 <div className={clsx(status === 'not-available' && 'hidden')}>
130 className="flex shrink-0 group-hover:opacity-100 group-hover:opacity-100-no-width"
131 onClick={handleVisibilityToggle}
133 <Icon name={isVisible ? 'eye' : 'eye-slash'} alt={visibilityText} />
140 ? c('Action').t`Make this participant required`
141 : c('Action').t`Make this participant optional`
149 className="flex shrink-0 group-hover:opacity-100 group-hover:opacity-100-no-width"
150 onClick={() => onToggleOptional(attendee)}
152 <Icon name={isOptional ? 'user' : 'user-filled'} alt={c('Action').t`Remove this participant`} />
155 <Tooltip title={c('Action').t`Remove this participant`}>
160 className="flex shrink-0 group-hover:opacity-100 group-hover:opacity-100-no-width"
162 dispatch(busySlotsActions.removeAttendee(attendeeEmail));
166 <Icon name="cross" alt={c('Action').t`Remove this participant`} />
173 export default BusyParticipantRow;