Merge branch 'feat/inda-383-daily-stat' into 'main'
[ProtonMail-WebClient.git] / applications / calendar / src / app / store / busySlots / busySlotsSlice.ts
blob85adc32bcf9d0adf479dac771c2ec363eea46b26
1 import type { PayloadAction } from '@reduxjs/toolkit';
2 import { createSlice } from '@reduxjs/toolkit';
4 import type { VIEWS } from '@proton/shared/lib/calendar/constants';
5 import { canonicalizeEmailByGuess } from '@proton/shared/lib/helpers/email';
6 import type { GetBusySlotsResponse } from '@proton/shared/lib/interfaces/calendar';
7 import diff from '@proton/utils/diff';
9 import type { CalendarViewBusyEvent } from '../../containers/calendar/interface';
11 export interface AttendeeBusySlot extends CalendarViewBusyEvent {}
13 /** Attendee email */
14 export type BusySlotsEmail = string;
15 /** Attendee visibility */
16 export type BusySlotsVisibility = 'visible' | 'hidden';
17 /**
18  * Attendee fetchStatus
19  * - loading: request is in progress
20  * - success: request has been successful
21  * - error: request failed.
22  */
23 export type BusySlotFetchStatus = 'loading' | 'success' | 'error';
25 export type BusySlot = Exclude<GetBusySlotsResponse['BusySchedule']['BusyTimeSlots'], null>[number] & {
26     isAllDay: boolean;
29 export interface BusySlotsAttendeeFetchStatusSuccessActionPayload {
30     email: BusySlotsEmail;
31     busySlots: BusySlot[];
32     isDataAccessible: boolean;
33     visibility: BusySlotsVisibility;
36 export interface BusySlotsState {
37     /** Display or not on calendar grid */
38     displayOnGrid: boolean;
39     /** Metadata of the event */
40     metadata?: {
41         /** The currently selected TZ in the interface */
42         tzid: string;
43         /** The current view when opening event form */
44         view: VIEWS;
45         /** Start date of the calendar view in UTC unix timestamp */
46         viewStartDate: number;
47         /** End date of the calendar view in UTC unix timestamp */
48         viewEndDate: number;
49     };
50     /** List of event attendees */
51     attendees: BusySlotsEmail[];
52     /**
53      * Selected color for the attendee.
54      * Each attendee will be assigned a different color from the actual
55      * calendar colors the user has and the actual attendees colors assigned
56      */
57     attendeeColor: Record<BusySlotsEmail, string>;
58     /** Is attendee email hidden  */
59     attendeeVisibility: Record<BusySlotsEmail, BusySlotsVisibility>;
60     /** Are attendee busy slots accessible ?  */
61     attendeeDataAccessible: Record<BusySlotsEmail, boolean>;
62     /**
63      * Allows to know status of getBusySlots request for each user
64      * - loading: request is in progress
65      * - success: request has been successful
66      * - error: request failed.
67      */
68     attendeeFetchStatus: Record<BusySlotsEmail, BusySlotFetchStatus>;
69     /** Busy time slots for each attendees */
70     attendeeBusySlots: Record<BusySlotsEmail, BusySlot[]>;
71     /** Highlighted attendee in the ui */
72     attendeeHighlight: BusySlotsEmail | undefined;
75 export const busySlotsSliceName = 'calendarBusySlots';
77 const initialState: BusySlotsState = {
78     metadata: undefined,
79     displayOnGrid: true,
80     attendees: [],
81     attendeeColor: {},
82     attendeeVisibility: {},
83     attendeeDataAccessible: {},
84     attendeeBusySlots: {},
85     attendeeFetchStatus: {},
86     attendeeHighlight: undefined,
89 const busySlotsSlice = createSlice({
90     name: busySlotsSliceName,
91     initialState,
92     reducers: {
93         reset: (state) => {
94             return { ...initialState, metadata: state.metadata };
95         },
96         setAttendees: (state, { payload }: PayloadAction<BusySlotsEmail[]>) => {
97             if (diff(payload, state.attendees).length > 0) {
98                 state.attendees = payload.map((email) => canonicalizeEmailByGuess(email));
99             }
100         },
101         setMetadata: (state, { payload }: PayloadAction<Required<BusySlotsState>['metadata']>) => {
102             state.metadata = payload;
103         },
104         removeAttendee: (state, action: PayloadAction<BusySlotsEmail>) => {
105             const emailCanonicalized = canonicalizeEmailByGuess(action.payload);
106             state.attendees = state.attendees.filter((email) => email !== emailCanonicalized);
107         },
108         setAttendeeVisibility: (state, action: PayloadAction<{ email: BusySlotsEmail; visible: boolean }>) => {
109             const emailCanonicalized = canonicalizeEmailByGuess(action.payload.email);
111             if (state.attendeeDataAccessible[emailCanonicalized] === false) {
112                 state.attendeeVisibility[emailCanonicalized] = 'hidden';
113             }
115             state.attendeeVisibility[emailCanonicalized] = action.payload.visible ? 'visible' : 'hidden';
116         },
117         setAttendeeFetchStatusLoadingAndColor: (
118             state,
119             action: PayloadAction<{ email: BusySlotsEmail; color: string }[]>
120         ) => {
121             for (const { email, color } of action.payload) {
122                 const emailCanonicalized = canonicalizeEmailByGuess(email);
123                 state.attendeeFetchStatus[emailCanonicalized] = 'loading';
124                 state.attendeeVisibility[email] = 'hidden';
125                 state.attendeeColor[emailCanonicalized] = color;
126             }
127         },
128         setFetchStatusesSuccess: (
129             state,
130             { payload }: PayloadAction<BusySlotsAttendeeFetchStatusSuccessActionPayload[]>
131         ) => {
132             for (const { email, busySlots, isDataAccessible, visibility } of payload) {
133                 const emailCanonicalized = canonicalizeEmailByGuess(email);
134                 state.attendeeBusySlots[emailCanonicalized] = busySlots;
135                 state.attendeeDataAccessible[emailCanonicalized] = isDataAccessible;
136                 state.attendeeFetchStatus[emailCanonicalized] = 'success';
137                 state.attendeeVisibility[emailCanonicalized] = visibility;
138             }
139         },
140         setFetchStatusesFail: (state, action: PayloadAction<BusySlotsEmail[]>) => {
141             for (const email of action.payload) {
142                 const emailCanonicalized = canonicalizeEmailByGuess(email);
143                 state.attendeeFetchStatus[emailCanonicalized] = 'error';
144                 state.attendeeVisibility[emailCanonicalized] = 'hidden';
145             }
146         },
147         setHighlightedAttendee: (state, action: PayloadAction<BusySlotsEmail | undefined>) => {
148             state.attendeeHighlight = action.payload ? canonicalizeEmailByGuess(action.payload) : undefined;
149         },
150         setDisplay: (state, action: PayloadAction<boolean>) => {
151             state.displayOnGrid = action.payload;
152         },
153     },
156 export const busySlotsActions = busySlotsSlice.actions;
157 export const busySlotsReducer = { [busySlotsSliceName]: busySlotsSlice.reducer };