diff --git a/src/components/Calendar/Calendar.tsx b/src/components/Calendar/Calendar.tsx index 46a8e5f..2f7a078 100644 --- a/src/components/Calendar/Calendar.tsx +++ b/src/components/Calendar/Calendar.tsx @@ -77,6 +77,10 @@ export default function CalendarApp({ } }, [dispatch, tokens, userId]); const view = useAppSelector((state) => state.settings.view); + const userData = useAppSelector((state) => state.user.userData); + const hideDeclinedEvents = useAppSelector( + (state) => state.settings.hideDeclinedEvents + ); const calendars = useAppSelector((state) => state.calendars.list); const tempcalendars = useAppSelector((state) => state.calendars.templist); const [selectedCalendars, setSelectedCalendars] = useState([]); @@ -221,7 +225,9 @@ export default function CalendarApp({ let filteredEvents: CalendarEvent[] = extractEvents( selectedCalendars, - calendars || {} + calendars || {}, + userData?.email, + hideDeclinedEvents ); const tempCalendarIds = useMemo( @@ -231,7 +237,9 @@ export default function CalendarApp({ let filteredTempEvents: CalendarEvent[] = extractEvents( tempCalendarIds, - tempcalendars || {} + tempcalendars || {}, + userData?.email, + hideDeclinedEvents ); const sortedSelectedCalendars = useMemo( diff --git a/src/components/Calendar/utils/calendarUtils.ts b/src/components/Calendar/utils/calendarUtils.ts index 6113053..b66ecde 100644 --- a/src/components/Calendar/utils/calendarUtils.ts +++ b/src/components/Calendar/utils/calendarUtils.ts @@ -138,21 +138,30 @@ export const eventToFullCalendarFormat = ( export const extractEvents = ( selectedCalendars: string[], - calendars: Record + calendars: Record, + userAddress?: string, + hideDeclinedEvents?: boolean | null ) => { - let filteredEvents: CalendarEvent[] = []; + const allEvents: CalendarEvent[] = []; + selectedCalendars.forEach((id) => { - if (calendars[id] && calendars[id].events) { - filteredEvents = filteredEvents - .concat( - Object.keys(calendars[id].events).map( - (eventid) => calendars[id].events[eventid] - ) - ) - .filter((event) => !(event.status === "CANCELLED")); + const calendar = calendars[id]; + if (calendar?.events) { + allEvents.push(...Object.values(calendar.events)); } }); - return filteredEvents; + + return allEvents + .filter((event) => event.status !== "CANCELLED") + .filter( + (event) => + !( + hideDeclinedEvents && + event.attendee?.some( + (a) => a.cal_address === userAddress && a.partstat === "DECLINED" + ) + ) + ); }; export const updateCalsDetails = ( diff --git a/src/features/Settings/SettingsPage.tsx b/src/features/Settings/SettingsPage.tsx index a3fcf50..61324ac 100644 --- a/src/features/Settings/SettingsPage.tsx +++ b/src/features/Settings/SettingsPage.tsx @@ -25,6 +25,7 @@ import { setLanguage as setSettingsLanguage, setTimeZone as setSettingsTimeZone, setIsBrowserDefaultTimeZone, + setHideDeclinedEvents, } from "./SettingsSlice"; import { updateUserConfigurationsAsync, @@ -68,6 +69,11 @@ export default function SettingsPage() { const alarmEmailsEnabled = useAppSelector( (state) => state.user?.alarmEmailsEnabled ?? true ); + + const hideDeclinedEvents = useAppSelector( + (state) => state.settings?.hideDeclinedEvents + ); + const [activeNavItem, setActiveNavItem] = useState("settings"); const [activeSettingsSubTab, setActiveSettingsSubTab] = @@ -75,6 +81,8 @@ export default function SettingsPage() { const [languageErrorOpen, setLanguageErrorOpen] = useState(false); const [timeZoneErrorOpen, setTimeZoneErrorOpen] = useState(false); const [alarmEmailsErrorOpen, setAlarmEmailsErrorOpen] = useState(false); + const [hideDeclinedEventsErrorOpen, setHideDeclinedEventsErrorOpen] = + useState(false); const handleBackClick = () => { dispatch(setView("calendar")); @@ -168,6 +176,27 @@ export default function SettingsPage() { setTimeZoneErrorOpen(false); }; + const handleHideDeclinedEvents = (doHideDeclinedEvents: boolean) => { + // Optimistic update - update UI immediately + dispatch(setHideDeclinedEvents(doHideDeclinedEvents)); + + // Call API in background, don't wait for it + dispatch( + updateUserConfigurationsAsync({ + hideDeclinedEvents: doHideDeclinedEvents, + }) + ) + .unwrap() + .catch((error) => { + console.error("Failed to update hide declined event:", error); + dispatch(setHideDeclinedEvents(!doHideDeclinedEvents)); + setHideDeclinedEventsErrorOpen(true); + }); + }; + const handleHideDeclinedEventsErrorClose = () => { + setHideDeclinedEventsErrorOpen(false); + }; + const handleAlarmEmailsToggle = ( event: React.ChangeEvent ) => { @@ -315,6 +344,38 @@ export default function SettingsPage() { + + + {t("settings.calAndEvent")} + + + + + handleHideDeclinedEvents(!hideDeclinedEvents) + } + aria-label={t("settings.showDeclinedEvent")} + /> + } + label={t("settings.showDeclinedEvent")} + labelPlacement="start" + sx={{ + minWidth: 400, + justifyContent: "space-between", + marginLeft: 0, + mb: 2, + }} + /> + + + )} {activeSettingsSubTab === "notifications" && ( @@ -381,6 +442,13 @@ export default function SettingsPage() { "Failed to update email notifications setting" } /> + + ); } diff --git a/src/features/Settings/SettingsSlice.ts b/src/features/Settings/SettingsSlice.ts index 3a036af..ceeef47 100644 --- a/src/features/Settings/SettingsSlice.ts +++ b/src/features/Settings/SettingsSlice.ts @@ -6,6 +6,7 @@ export interface SettingsState { language: string; timeZone: string | null; // Allow null to represent browser default isBrowserDefaultTimeZone: boolean; + hideDeclinedEvents: boolean | null; view: "calendar" | "settings" | "search"; } @@ -21,6 +22,7 @@ const initialState: SettingsState = { language: defaultLang, timeZone: defaultTimeZone, isBrowserDefaultTimeZone: defaultTimeZone === null, + hideDeclinedEvents: null, view: "calendar", }; @@ -39,6 +41,9 @@ export const settingsSlice = createSlice({ setIsBrowserDefaultTimeZone: (state, action: PayloadAction) => { state.isBrowserDefaultTimeZone = action.payload; }, + setHideDeclinedEvents: (state, action: PayloadAction) => { + state.hideDeclinedEvents = action.payload; + }, setView: ( state, action: PayloadAction<"calendar" | "settings" | "search"> @@ -65,6 +70,16 @@ export const settingsSlice = createSlice({ state.isBrowserDefaultTimeZone = true; localStorage.setItem("timeZone", browserDefaultTimeZone); } + const esnCalendarModule = action.payload.configurations?.modules?.find( + (module: any) => module.name === "linagora.esn.calendar" + ); + const hideDeclinedEventsConfig = esnCalendarModule?.configurations?.find( + (config: any) => config.name === "hideDeclinedEvents" + ); + state.hideDeclinedEvents = + typeof hideDeclinedEventsConfig?.value === "boolean" + ? hideDeclinedEventsConfig.value + : null; }); }, }); @@ -74,5 +89,6 @@ export const { setTimeZone, setView, setIsBrowserDefaultTimeZone, + setHideDeclinedEvents, } = settingsSlice.actions; export default settingsSlice.reducer; diff --git a/src/features/User/userAPI.ts b/src/features/User/userAPI.ts index 63fb28c..30de28f 100644 --- a/src/features/User/userAPI.ts +++ b/src/features/User/userAPI.ts @@ -40,6 +40,7 @@ export interface UserConfigurationUpdates { timezone?: string | null; previousConfig?: Record; alarmEmails?: boolean; + hideDeclinedEvents?: boolean; } export async function updateUserConfigurations( @@ -47,6 +48,7 @@ export async function updateUserConfigurations( ): Promise { const coreConfigs: Array<{ name: string; value: any }> = []; const calendarConfigs: Array<{ name: string; value: any }> = []; + const esnCalendarConfigs: Array<{ name: string; value: any }> = []; if (updates.language !== undefined) { coreConfigs.push({ name: "language", value: updates.language }); @@ -69,6 +71,12 @@ export async function updateUserConfigurations( value: updates.alarmEmails, }); } + if (updates.hideDeclinedEvents !== undefined) { + esnCalendarConfigs.push({ + name: "hideDeclinedEvents", + value: updates.hideDeclinedEvents, + }); + } const modules: Array<{ name: string; @@ -88,6 +96,12 @@ export async function updateUserConfigurations( configurations: calendarConfigs, }); } + if (esnCalendarConfigs.length > 0) { + modules.push({ + name: "linagora.esn.calendar", + configurations: esnCalendarConfigs, + }); + } if (modules.length === 0) { return Promise.resolve({ status: 204 }); diff --git a/src/locales/en.json b/src/locales/en.json index 9adb7b5..74f5fe5 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -221,6 +221,9 @@ "notifications.deliveryMethod": "Delivery method", "alarmEmailsUpdateError": "Failed to update email notifications setting", "sync.empty": "Sync settings coming soon", + "calAndEvent": "Calendar and events", + "showDeclinedEvent": "Show declined events", + "hideDeclinedEventsUpdateError": "Failed to update hide declined event.", "back": "Back to calendar" }, "eventPreview": { diff --git a/src/locales/fr.json b/src/locales/fr.json index 17229e0..3a4e4e6 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -221,6 +221,9 @@ "notifications.deliveryMethod": "Mode de réception", "alarmEmailsUpdateError": "Échec de la mise à jour du mode de notification par e-mail", "sync.empty": "Paramètres de synchronisation à venir", + "calAndEvent": "Calendrier et événements", + "showDeclinedEvent": "Afficher les événements déclinés", + "hideDeclinedEventsUpdateError": "Échec de la mise à jour du masquage des événements déclinés.", "back": "Retour au calendrier" }, "eventPreview": { diff --git a/src/locales/ru.json b/src/locales/ru.json index bcc2b7c..c2f7bae 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -221,6 +221,9 @@ "notifications.deliveryMethod": "Способ доставки", "alarmEmailsUpdateError": "Не удалось обновить настройки уведомлений по email", "sync.empty": "Настройки синхронизации скоро появятся", + "calAndEvent": "Календарь и мероприятия", + "showDeclinedEvent": "Показать отклонённые мероприятия", + "hideDeclinedEventsUpdateError": "Не удалось обновить скрытие отклонённых событий.", "back": "Вернуться к календарю" }, "eventPreview": { diff --git a/src/locales/vi.json b/src/locales/vi.json index 30af6a8..7302e52 100644 --- a/src/locales/vi.json +++ b/src/locales/vi.json @@ -221,6 +221,9 @@ "notifications.deliveryMethod": "Phương thức gửi", "alarmEmailsUpdateError": "Cập nhật cài đặt thông báo email thất bại", "sync.empty": "Cài đặt đồng bộ sắp có", + "calAndEvent": "Lịch và sự kiện", + "showDeclinedEvent": "Hiển thị các sự kiện đã từ chối", + "hideDeclinedEventsUpdateError": "Không cập nhật được các sự kiện ẩn bị từ chối.", "back": "Quay lại lịch" }, "eventPreview": {