[#592] temporary fix for slow attendance update (#516)

This commit is contained in:
Camille Moussu
2026-02-06 11:21:11 +01:00
committed by GitHub
parent b44eba1a22
commit 8ca50b8d1b
4 changed files with 121 additions and 26 deletions
@@ -1,9 +1,10 @@
import { userData } from "@/features/User/userDataTypes";
import { Box, Typography } from "@linagora/twake-mui";
import { Dispatch, SetStateAction } from "react";
import { Dispatch, SetStateAction, useState } from "react";
import { useI18n } from "twake-i18n";
import { ContextualizedEvent } from "../EventsTypes";
import { RSVPButton } from "./RSVPButton";
import { PartStat } from "@/features/User/models/attendee";
interface AttendanceValidationProps {
contextualizedEvent: ContextualizedEvent;
@@ -20,6 +21,8 @@ export function AttendanceValidation({
}: AttendanceValidationProps) {
const { currentUserAttendee, isOwn } = contextualizedEvent;
const { t } = useI18n();
const [isLoading, setIsLoading] = useState(false);
const [loadingValue, setLoadingValue] = useState<PartStat | null>(null);
// Check if we should show RSVP buttons
const hasNoAttendeesOrOrganizer =
@@ -30,11 +33,19 @@ export function AttendanceValidation({
return null;
}
const handleLoadingChange = (loading: boolean, value?: PartStat) => {
setIsLoading(loading);
setLoadingValue(loading && value ? value : null);
};
const commonButtonProps = {
contextualizedEvent,
user,
setAfterChoiceFunc,
setOpenEditModePopup,
isLoading,
onLoadingChange: handleLoadingChange,
loadingValue,
};
return (
@@ -1,8 +1,8 @@
import { useAppDispatch } from "@/app/hooks";
import { PartStat } from "@/features/User/models/attendee";
import { userData } from "@/features/User/userDataTypes";
import { Button } from "@linagora/twake-mui";
import { Dispatch, SetStateAction } from "react";
import { Button, CircularProgress, Box, Theme } from "@linagora/twake-mui";
import { Dispatch, SetStateAction, useEffect, useRef, useState } from "react";
import { useI18n } from "twake-i18n";
import { ContextualizedEvent } from "../EventsTypes";
import { handleRSVPClick } from "./handleRSVPClick";
@@ -21,6 +21,9 @@ interface RSVPButtonProps {
user: userData | undefined;
setAfterChoiceFunc: Dispatch<SetStateAction<Function | undefined>>;
setOpenEditModePopup: Dispatch<SetStateAction<string | null>>;
isLoading: boolean;
onLoadingChange: (loading: boolean, value?: PartStat) => void;
loadingValue: PartStat | null;
}
export function RSVPButton({
@@ -29,35 +32,98 @@ export function RSVPButton({
user,
setAfterChoiceFunc,
setOpenEditModePopup,
isLoading,
onLoadingChange,
loadingValue,
}: RSVPButtonProps) {
const { t } = useI18n();
const dispatch = useAppDispatch();
const { currentUserAttendee } = contextualizedEvent;
const showLoading = isLoading && loadingValue === rsvpValue;
const previousPartstatRef = useRef<PartStat | undefined>(
currentUserAttendee?.partstat
);
// Detect when attendee status changes (via WebSocket) and clear loading
useEffect(() => {
const currentPartstat = currentUserAttendee?.partstat;
if (
isLoading &&
previousPartstatRef.current !== undefined &&
currentPartstat !== previousPartstatRef.current
) {
onLoadingChange(false);
}
previousPartstatRef.current = currentPartstat;
}, [currentUserAttendee?.partstat, isLoading, rsvpValue, onLoadingChange]);
const handleClick = async () => {
// Store current partstat before making changes
previousPartstatRef.current = currentUserAttendee?.partstat;
if (previousPartstatRef.current === rsvpValue) {
return;
}
// For recurring events, don't set loading yet - wait for modal choice
if (!contextualizedEvent.isRecurring) {
onLoadingChange(true, rsvpValue);
}
try {
await handleRSVPClick(
rsvpValue,
contextualizedEvent,
user,
setAfterChoiceFunc,
setOpenEditModePopup,
dispatch,
onLoadingChange
);
} catch (error) {
console.error(
`[RSVPButton ${rsvpValue}] Error in handleRSVPClick:`,
error
);
// Clear loading on error
onLoadingChange(false);
}
};
const isCurrentlyActive = currentUserAttendee?.partstat === rsvpValue;
// Show as active (colored) if:
// 1. This button is currently loading, OR
// 2. This is the active status AND nothing is loading
const shouldShowActive = showLoading || (isCurrentlyActive && !isLoading);
const buttonColor = shouldShowActive ? rsvpColor[rsvpValue] : "primary";
return (
<Button
variant={
currentUserAttendee?.partstat === rsvpValue ? "contained" : "outlined"
}
color={
currentUserAttendee?.partstat === rsvpValue
? rsvpColor[rsvpValue]
: "primary"
}
variant={shouldShowActive ? "contained" : "outlined"}
color={buttonColor}
size="medium"
sx={{ borderRadius: "50px" }}
onClick={() =>
handleRSVPClick(
rsvpValue,
contextualizedEvent,
user,
setAfterChoiceFunc,
setOpenEditModePopup,
dispatch
)
}
sx={{
borderRadius: "50px",
// Override MUI's default disabled styles to keep the color
"&.Mui-disabled": shouldShowActive
? {
backgroundColor: (theme: Theme) =>
theme.palette[buttonColor].main,
color: (theme: Theme) => theme.palette[buttonColor].contrastText,
borderColor: (theme: Theme) => theme.palette[buttonColor].main,
}
: {},
}}
onClick={handleClick}
disabled={isLoading}
>
{t(`eventPreview.${rsvpValue}`)}
<Box display="flex" alignItems="center" gap={1}>
{showLoading && <CircularProgress size={20} color="inherit" />}
{t(`eventPreview.${rsvpValue}`)}
</Box>
</Button>
);
}
@@ -11,15 +11,31 @@ export async function handleRSVPClick(
user: userData | undefined,
setAfterChoiceFunc: Dispatch<SetStateAction<Function | undefined>>,
setOpenEditModePopup: Dispatch<SetStateAction<string | null>>,
dispatch: AppDispatch
dispatch: AppDispatch,
onLoadingChange?: (loading: boolean, value?: PartStat) => void
) {
const { isRecurring, calendar, event } = contextualizedEvent;
if (isRecurring) {
setAfterChoiceFunc(() => async (type: string) => {
setAfterChoiceFunc(() => async (type: string | null) => {
// If user cancelled the modal, don't proceed
if (type === null) {
return;
}
// Now start loading since user made a choice
if (onLoadingChange) {
onLoadingChange(true, rsvp);
}
try {
await handleRSVP(dispatch, calendar, user, event, rsvp, type);
} catch (error) {
console.error("Error handling RSVP:", error);
// Clear loading on error
if (onLoadingChange) {
onLoadingChange(false);
}
}
});
setOpenEditModePopup("attendance");
@@ -28,6 +44,9 @@ export async function handleRSVPClick(
await handleRSVP(dispatch, calendar, user, event, rsvp);
} catch (error) {
console.error("Error handling RSVP:", error);
if (onLoadingChange) {
onLoadingChange(false);
}
}
}
}
@@ -435,7 +435,6 @@ export default function EventPreviewModal({
actions={
<AttendanceValidation
contextualizedEvent={contextualizedEvent}
calendarList={calendarList}
user={user}
setAfterChoiceFunc={setAfterChoiceFunc}
setOpenEditModePopup={setOpenEditModePopup}