refactor recurring event modification (#173)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import {
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from "@mui/material";
|
||||
import { CalendarEvent } from "../../features/Events/EventsTypes";
|
||||
import { useState } from "react";
|
||||
|
||||
export function EditModeDialog({
|
||||
type,
|
||||
setOpen,
|
||||
event,
|
||||
eventAction,
|
||||
}: {
|
||||
type: string | null;
|
||||
setOpen: (e: string | null) => void;
|
||||
event: CalendarEvent;
|
||||
eventAction: (type: "solo" | "all" | undefined) => void;
|
||||
}) {
|
||||
const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>(
|
||||
"solo"
|
||||
);
|
||||
const handleEvent = async () => {
|
||||
eventAction(typeOfAction);
|
||||
handleClose();
|
||||
};
|
||||
const handleClose = () => {
|
||||
setOpen(null);
|
||||
setTypeOfAction("solo");
|
||||
};
|
||||
return (
|
||||
<Dialog open={Boolean(type)} onClose={handleClose}>
|
||||
<DialogTitle>
|
||||
{type === "edit" && "Update the reccurent event"}
|
||||
{type === "attendance" && "Update the participation status"}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<RadioGroup
|
||||
value={typeOfAction}
|
||||
onChange={(e) =>
|
||||
setTypeOfAction(e.target.value as "solo" | "all" | undefined)
|
||||
}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="solo"
|
||||
control={<Radio />}
|
||||
label="This event"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="all"
|
||||
control={<Radio />}
|
||||
label="All the events"
|
||||
/>
|
||||
</RadioGroup>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<ButtonGroup>
|
||||
<Button onClick={handleClose}>Cancel</Button>
|
||||
<Button onClick={handleEvent}>Ok</Button>
|
||||
</ButtonGroup>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -27,12 +27,13 @@ import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import {
|
||||
deleteEventAsync,
|
||||
getEventAsync,
|
||||
putEventAsync,
|
||||
} from "../../features/Calendars/CalendarSlice";
|
||||
import { dlEvent } from "../../features/Events/EventApi";
|
||||
import EventDisplayModal from "../../features/Events/EventDisplay";
|
||||
import { ResponsiveDialog } from "../Dialog";
|
||||
import { EditModeDialog } from "./EditModeDialog";
|
||||
import EventDuplication from "./EventDuplicate";
|
||||
import { handleDelete, handleRSVP } from "./eventHandlers/eventHandlers";
|
||||
import { InfoRow } from "./InfoRow";
|
||||
import { renderAttendeeBadge } from "./utils/eventUtils";
|
||||
export default function EventPreviewModal({
|
||||
@@ -50,13 +51,26 @@ export default function EventPreviewModal({
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const calendarList = Object.values(
|
||||
useAppSelector((state) => state.calendars.list)
|
||||
);
|
||||
const calendar = tempEvent
|
||||
? calendars.templist[calId]
|
||||
: calendars.list[calId];
|
||||
const event = calendar.events[eventId];
|
||||
const user = useAppSelector((state) => state.user);
|
||||
|
||||
const isRecurring = event?.uid?.includes("/");
|
||||
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
||||
const [openFullDisplay, setOpenFullDisplay] = useState(false);
|
||||
const [openEditModePopup, setOpenEditModePopup] = useState<string | null>(
|
||||
null
|
||||
);
|
||||
const [typeOfAction, setTypeOfAction] = useState<"solo" | "all" | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [afterChoiceFunc, setAfterChoiceFunc] = useState<Function>();
|
||||
|
||||
const [toggleActionMenu, setToggleActionMenu] = useState<Element | null>(
|
||||
null
|
||||
);
|
||||
@@ -89,18 +103,6 @@ export default function EventPreviewModal({
|
||||
(a) => a.cal_address === event.organizer?.cal_address
|
||||
);
|
||||
|
||||
function handleRSVP(rsvp: string) {
|
||||
const newEvent = {
|
||||
...event,
|
||||
attendee: event.attendee?.map((a) =>
|
||||
a.cal_address === user.userData.email ? { ...a, partstat: rsvp } : a
|
||||
),
|
||||
};
|
||||
|
||||
dispatch(putEventAsync({ cal: calendar, newEvent }));
|
||||
onClose({}, "backdropClick");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ResponsiveDialog
|
||||
@@ -137,8 +139,16 @@ export default function EventPreviewModal({
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={async () => {
|
||||
setOpenFullDisplay(!openFullDisplay);
|
||||
await dispatch(getEventAsync(event));
|
||||
if (isRecurring) {
|
||||
setAfterChoiceFunc(() => async () => {
|
||||
await dispatch(getEventAsync(event));
|
||||
setOpenFullDisplay(!openFullDisplay);
|
||||
});
|
||||
setOpenEditModePopup("edit");
|
||||
} else {
|
||||
await dispatch(getEventAsync(event));
|
||||
setOpenFullDisplay(!openFullDisplay);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
@@ -173,14 +183,32 @@ export default function EventPreviewModal({
|
||||
{user.userData.email === event.organizer?.cal_address && (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
onClose({}, "backdropClick");
|
||||
dispatch(
|
||||
deleteEventAsync({
|
||||
calId,
|
||||
eventId,
|
||||
eventURL: event.URL,
|
||||
})
|
||||
);
|
||||
if (isRecurring) {
|
||||
setAfterChoiceFunc(
|
||||
() =>
|
||||
(typeOfAction?: "solo" | "all" | undefined) =>
|
||||
handleDelete(
|
||||
isRecurring,
|
||||
typeOfAction,
|
||||
onClose,
|
||||
dispatch,
|
||||
calendar,
|
||||
event,
|
||||
calId,
|
||||
eventId
|
||||
)
|
||||
);
|
||||
setOpenEditModePopup("edit");
|
||||
} else {
|
||||
onClose({}, "backdropClick");
|
||||
dispatch(
|
||||
deleteEventAsync({
|
||||
calId,
|
||||
eventId,
|
||||
eventURL: event.URL,
|
||||
})
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Delete event
|
||||
@@ -216,10 +244,7 @@ export default function EventPreviewModal({
|
||||
}
|
||||
actions={
|
||||
<>
|
||||
{currentUserAttendee &&
|
||||
event.uid?.split("/")[1] &&
|
||||
"event is a reccuring event, the interraction between acceptance and recurrence is broken and to avoid loosing reccuring events by accepting or refusing them this feature isn't available for now"}
|
||||
{currentUserAttendee && !event.uid?.split("/")[1] && (
|
||||
{currentUserAttendee && (
|
||||
<Box style={{ display: "flex", flexDirection: "row" }}>
|
||||
<Typography variant="body2">Attending?</Typography>
|
||||
<ButtonGroup size="small" fullWidth>
|
||||
@@ -229,7 +254,34 @@ export default function EventPreviewModal({
|
||||
? "success"
|
||||
: "primary"
|
||||
}
|
||||
onClick={() => handleRSVP("ACCEPTED")}
|
||||
onClick={() => {
|
||||
if (isRecurring) {
|
||||
setAfterChoiceFunc(
|
||||
() => (type: string) =>
|
||||
handleRSVP(
|
||||
dispatch,
|
||||
calendar,
|
||||
user,
|
||||
event,
|
||||
"ACCEPTED",
|
||||
|
||||
onClose,
|
||||
type,
|
||||
calendarList
|
||||
)
|
||||
);
|
||||
setOpenEditModePopup("attendance");
|
||||
} else {
|
||||
handleRSVP(
|
||||
dispatch,
|
||||
calendar,
|
||||
user,
|
||||
event,
|
||||
"ACCEPTED",
|
||||
onClose
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Accept
|
||||
</Button>
|
||||
@@ -239,7 +291,33 @@ export default function EventPreviewModal({
|
||||
? "warning"
|
||||
: "primary"
|
||||
}
|
||||
onClick={() => handleRSVP("TENTATIVE")}
|
||||
onClick={() => {
|
||||
if (isRecurring) {
|
||||
setAfterChoiceFunc(
|
||||
() => (type: string) =>
|
||||
handleRSVP(
|
||||
dispatch,
|
||||
calendar,
|
||||
user,
|
||||
event,
|
||||
"TENTATIVE",
|
||||
onClose,
|
||||
type,
|
||||
calendarList
|
||||
)
|
||||
);
|
||||
setOpenEditModePopup("attendance");
|
||||
} else {
|
||||
handleRSVP(
|
||||
dispatch,
|
||||
calendar,
|
||||
user,
|
||||
event,
|
||||
"TENTATIVE",
|
||||
onClose
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Maybe
|
||||
</Button>
|
||||
@@ -249,7 +327,33 @@ export default function EventPreviewModal({
|
||||
? "error"
|
||||
: "primary"
|
||||
}
|
||||
onClick={() => handleRSVP("DECLINED")}
|
||||
onClick={() => {
|
||||
if (isRecurring) {
|
||||
setAfterChoiceFunc(
|
||||
() => (type: string) =>
|
||||
handleRSVP(
|
||||
dispatch,
|
||||
calendar,
|
||||
user,
|
||||
event,
|
||||
"DECLINED",
|
||||
onClose,
|
||||
type,
|
||||
calendarList
|
||||
)
|
||||
);
|
||||
setOpenEditModePopup("attendance");
|
||||
} else {
|
||||
handleRSVP(
|
||||
dispatch,
|
||||
calendar,
|
||||
user,
|
||||
event,
|
||||
"DECLINED",
|
||||
onClose
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
Decline
|
||||
</Button>
|
||||
@@ -387,11 +491,21 @@ export default function EventPreviewModal({
|
||||
</>
|
||||
)}
|
||||
</ResponsiveDialog>
|
||||
<EditModeDialog
|
||||
type={openEditModePopup}
|
||||
setOpen={setOpenEditModePopup}
|
||||
event={event}
|
||||
eventAction={(type: "solo" | "all" | undefined) => {
|
||||
setTypeOfAction(type);
|
||||
afterChoiceFunc && afterChoiceFunc(type);
|
||||
}}
|
||||
/>
|
||||
<EventDisplayModal
|
||||
open={openFullDisplay}
|
||||
onClose={() => setOpenFullDisplay(false)}
|
||||
eventId={eventId}
|
||||
calId={calId}
|
||||
typeOfAction={typeOfAction}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -29,7 +29,6 @@ export default function RepeatEvent({
|
||||
}) {
|
||||
const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
const day = new Date(eventStart);
|
||||
|
||||
// derive endOption based on repetition
|
||||
const getEndOption = () => {
|
||||
if (repetition.occurrences && repetition.occurrences >= 0) return "after";
|
||||
@@ -65,6 +64,7 @@ export default function RepeatEvent({
|
||||
<TextField
|
||||
type="number"
|
||||
value={repetition.interval ?? 1}
|
||||
disabled={!isOwn}
|
||||
onChange={(e) =>
|
||||
setRepetition({
|
||||
...repetition,
|
||||
@@ -78,6 +78,7 @@ export default function RepeatEvent({
|
||||
<FormControl size="small" style={{ minWidth: 120 }}>
|
||||
<Select
|
||||
value={repetition.freq ?? "daily"}
|
||||
disabled={!isOwn}
|
||||
onChange={(e: SelectChangeEvent) => {
|
||||
if (e.target.value === "weekly") {
|
||||
setRepetition({
|
||||
@@ -86,7 +87,11 @@ export default function RepeatEvent({
|
||||
selectedDays: [days[day.getDay() - 1]],
|
||||
});
|
||||
} else {
|
||||
setRepetition({ ...repetition, freq: e.target.value });
|
||||
setRepetition({
|
||||
...repetition,
|
||||
freq: e.target.value,
|
||||
selectedDays: undefined,
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -108,6 +113,7 @@ export default function RepeatEvent({
|
||||
{days.map((day) => (
|
||||
<FormControlLabel
|
||||
key={day}
|
||||
disabled={!isOwn}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={repetition.selectedDays?.includes(day) ?? false}
|
||||
@@ -151,9 +157,15 @@ export default function RepeatEvent({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<FormControlLabel value="never" control={<Radio />} label="Never" />
|
||||
<FormControlLabel
|
||||
disabled={!isOwn}
|
||||
value="never"
|
||||
control={<Radio />}
|
||||
label="Never"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
disabled={!isOwn}
|
||||
value="after"
|
||||
control={<Radio />}
|
||||
label={
|
||||
@@ -172,7 +184,7 @@ export default function RepeatEvent({
|
||||
}
|
||||
style={{ width: 100 }}
|
||||
inputProps={{ min: 1 }}
|
||||
disabled={endOption !== "after"}
|
||||
disabled={!isOwn || endOption !== "after"}
|
||||
/>
|
||||
occurrences
|
||||
</Box>
|
||||
@@ -180,6 +192,7 @@ export default function RepeatEvent({
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
disabled={!isOwn}
|
||||
value="on"
|
||||
control={<Radio />}
|
||||
label={
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import { ThunkDispatch } from "@reduxjs/toolkit";
|
||||
import { useAppSelector } from "../../../app/hooks";
|
||||
import {
|
||||
updateEventInstanceAsync,
|
||||
updateSeriesAsync,
|
||||
putEventAsync,
|
||||
deleteEventInstanceAsync,
|
||||
deleteEventAsync,
|
||||
} from "../../../features/Calendars/CalendarSlice";
|
||||
import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
||||
import { getEvent } from "../../../features/Events/EventApi";
|
||||
import { CalendarEvent } from "../../../features/Events/EventsTypes";
|
||||
import { userData } from "../../../features/User/userDataTypes";
|
||||
import { getCalendarRange } from "../../../utils/dateUtils";
|
||||
import { refreshCalendars } from "../utils/eventUtils";
|
||||
|
||||
export async function handleRSVP(
|
||||
dispatch: ThunkDispatch<any, any, any>,
|
||||
calendar: Calendars,
|
||||
user: { userData: userData },
|
||||
event: CalendarEvent,
|
||||
rsvp: string,
|
||||
onClose?: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void,
|
||||
typeOfAction?: string,
|
||||
calendars?: Calendars[]
|
||||
) {
|
||||
const newEvent = {
|
||||
...event,
|
||||
attendee: event.attendee?.map((a) =>
|
||||
a.cal_address === user.userData.email ? { ...a, partstat: rsvp } : a
|
||||
),
|
||||
};
|
||||
if (typeOfAction === "solo") {
|
||||
dispatch(updateEventInstanceAsync({ cal: calendar, event: newEvent }));
|
||||
} else if (typeOfAction === "all") {
|
||||
const master = await getEvent(newEvent, true);
|
||||
const calendarRange = getCalendarRange(new Date(event.start));
|
||||
|
||||
dispatch(
|
||||
updateSeriesAsync({
|
||||
cal: calendar,
|
||||
event: {
|
||||
...master,
|
||||
attendee: event.attendee?.map((a) =>
|
||||
a.cal_address === user.userData.email ? { ...a, partstat: rsvp } : a
|
||||
),
|
||||
},
|
||||
})
|
||||
);
|
||||
if (calendars) {
|
||||
await refreshCalendars(dispatch, calendars, calendarRange);
|
||||
}
|
||||
} else {
|
||||
dispatch(putEventAsync({ cal: calendar, newEvent }));
|
||||
}
|
||||
}
|
||||
|
||||
export function handleDelete(
|
||||
isRecurring: boolean,
|
||||
typeOfAction: "solo" | "all" | undefined,
|
||||
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void,
|
||||
dispatch: Function,
|
||||
calendar: Calendars,
|
||||
event: CalendarEvent,
|
||||
calId: string,
|
||||
eventId: string
|
||||
) {
|
||||
if (isRecurring) {
|
||||
onClose({}, "backdropClick");
|
||||
if (typeOfAction === "solo") {
|
||||
dispatch(deleteEventInstanceAsync({ cal: calendar, event }));
|
||||
} else {
|
||||
dispatch(
|
||||
deleteEventAsync({
|
||||
calId,
|
||||
eventId,
|
||||
eventURL: event.URL,
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
onClose({}, "backdropClick");
|
||||
dispatch(
|
||||
deleteEventAsync({
|
||||
calId,
|
||||
eventId,
|
||||
eventURL: event.URL,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,14 @@ import Avatar from "@mui/material/Avatar";
|
||||
import Badge from "@mui/material/Badge";
|
||||
import Box from "@mui/material/Box";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { ThunkDispatch } from "@reduxjs/toolkit";
|
||||
import {
|
||||
getCalendarDetailAsync,
|
||||
getCalendarsListAsync,
|
||||
} from "../../../features/Calendars/CalendarSlice";
|
||||
import { Calendars } from "../../../features/Calendars/CalendarTypes";
|
||||
import { userAttendee } from "../../../features/User/userDataTypes";
|
||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../../utils/dateUtils";
|
||||
|
||||
export function renderAttendeeBadge(
|
||||
a: userAttendee,
|
||||
@@ -103,3 +110,24 @@ export function stringAvatar(name: string) {
|
||||
children: name[0],
|
||||
};
|
||||
}
|
||||
|
||||
export async function refreshCalendars(
|
||||
dispatch: ThunkDispatch<any, any, any>,
|
||||
calendars: Calendars[],
|
||||
calendarRange: { start: Date; end: Date }
|
||||
) {
|
||||
await dispatch(getCalendarsListAsync());
|
||||
|
||||
calendars.map(
|
||||
async (cal) =>
|
||||
await dispatch(
|
||||
getCalendarDetailAsync({
|
||||
calId: cal.id,
|
||||
match: {
|
||||
start: formatDateToYYYYMMDDTHHMMSS(calendarRange.start),
|
||||
end: formatDateToYYYYMMDDTHHMMSS(calendarRange.end),
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user