[#77] added complex ui for repetition, fixed broken tests due to new feature
This commit is contained in:
@@ -2,7 +2,7 @@ import { CalendarApi } from "@fullcalendar/core";
|
||||
import { jest } from "@jest/globals";
|
||||
import { ThunkDispatch } from "@reduxjs/toolkit";
|
||||
import "@testing-library/jest-dom";
|
||||
import { screen } from "@testing-library/react";
|
||||
import { act, screen } from "@testing-library/react";
|
||||
import * as appHooks from "../../src/app/hooks";
|
||||
import CalendarApp from "../../src/components/Calendar/Calendar";
|
||||
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||
@@ -88,16 +88,17 @@ describe("CalendarApp integration", () => {
|
||||
{ timeout: 3000 }
|
||||
);
|
||||
expect(eventEl).toBeInTheDocument();
|
||||
act(() => {
|
||||
if (calendarApi) {
|
||||
const fcEvent = calendarApi.getEventById("event1");
|
||||
expect(fcEvent?.title).toBe("Test Event");
|
||||
const oldEnd = new Date(today.getTime() + 3600000); // +1 hour
|
||||
const newEnd = new Date(oldEnd.getTime() + 1800000); // +30 min
|
||||
|
||||
if (calendarApi) {
|
||||
const fcEvent = calendarApi.getEventById("event1");
|
||||
expect(fcEvent?.title).toBe("Test Event");
|
||||
const oldEnd = new Date(today.getTime() + 3600000); // +1 hour
|
||||
const newEnd = new Date(oldEnd.getTime() + 1800000); // +30 min
|
||||
fcEvent?.setEnd(newEnd);
|
||||
|
||||
fcEvent?.setEnd(newEnd);
|
||||
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
}
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { screen, fireEvent, waitFor, act } from "@testing-library/react";
|
||||
import * as eventThunks from "../../../src/features/Calendars/CalendarSlice";
|
||||
import { renderWithProviders } from "../../utils/Renderwithproviders";
|
||||
import EventDisplayModal, {
|
||||
@@ -7,6 +7,7 @@ import EventDisplayModal, {
|
||||
stringToColor,
|
||||
} from "../../../src/features/Events/EventDisplay";
|
||||
import EventPreviewModal from "../../../src/features/Events/EventDisplayPreview";
|
||||
|
||||
describe("Event Preview Display", () => {
|
||||
const mockOnClose = jest.fn();
|
||||
const day = new Date();
|
||||
@@ -915,7 +916,19 @@ describe("Event Full Display", () => {
|
||||
const updatedEvent = spy.mock.calls[0][0].newEvent;
|
||||
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
||||
});
|
||||
test("toggle Show More reveals extra fields", () => {
|
||||
it("toggle Show More reveals extra fields", async () => {
|
||||
const spy = jest
|
||||
.spyOn(eventThunks, "getEventAsync")
|
||||
.mockImplementation((payload) => {
|
||||
return () =>
|
||||
Promise.resolve({
|
||||
calId: payload.calId,
|
||||
event:
|
||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
|
||||
.events["event1"],
|
||||
}) as any;
|
||||
});
|
||||
|
||||
renderWithProviders(
|
||||
<EventDisplayModal
|
||||
open={true}
|
||||
@@ -925,14 +938,23 @@ describe("Event Full Display", () => {
|
||||
/>,
|
||||
preloadedState
|
||||
);
|
||||
fireEvent.click(screen.getByText("Show More"));
|
||||
expect(screen.getByLabelText("Alarm")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Repetition")).toBeInTheDocument();
|
||||
expect(screen.getByLabelText("Visibility")).toBeInTheDocument();
|
||||
act(() => {
|
||||
fireEvent.click(screen.getByText("Show More"));
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(spy).toHaveBeenCalled();
|
||||
});
|
||||
console.log(spy);
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText(/Alarm/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/Repetition/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/Visibility/i)).toBeInTheDocument();
|
||||
});
|
||||
fireEvent.click(screen.getByText("Show Less"));
|
||||
});
|
||||
|
||||
test("can edit title when user is organizer", () => {
|
||||
it("can edit title when user is organizer", () => {
|
||||
renderWithProviders(
|
||||
<EventDisplayModal
|
||||
open={true}
|
||||
@@ -946,7 +968,7 @@ describe("Event Full Display", () => {
|
||||
fireEvent.change(titleField, { target: { value: "New Title" } });
|
||||
expect(screen.getByDisplayValue("New Title")).toBeInTheDocument();
|
||||
});
|
||||
test("calendar select is disabled when not organizer", () => {
|
||||
it("calendar select is disabled when not organizer", () => {
|
||||
const rsvpState = {
|
||||
...preloadedState,
|
||||
calendars: {
|
||||
@@ -988,7 +1010,7 @@ describe("Event Full Display", () => {
|
||||
);
|
||||
expect(screen.getByLabelText("Calendar")).toHaveClass("Mui-disabled");
|
||||
});
|
||||
test("toggle all-day updates end date correctly", () => {
|
||||
it("toggle all-day updates end date correctly", () => {
|
||||
renderWithProviders(
|
||||
<EventDisplayModal
|
||||
open={true}
|
||||
@@ -1010,18 +1032,18 @@ describe("Event Full Display", () => {
|
||||
});
|
||||
|
||||
describe("Helper functions", () => {
|
||||
test("stringToColor generates consistent color", () => {
|
||||
it("stringToColor generates consistent color", () => {
|
||||
expect(stringToColor("Alice")).toMatch(/^#[0-9a-f]{6}$/);
|
||||
expect(stringToColor("Alice")).toBe(stringToColor("Alice"));
|
||||
});
|
||||
|
||||
test("stringAvatar returns correct props", () => {
|
||||
it("stringAvatar returns correct props", () => {
|
||||
const result = stringAvatar("Alice");
|
||||
expect(result.children).toBe("A");
|
||||
expect(result.sx.bgcolor).toMatch(/^#/);
|
||||
});
|
||||
|
||||
test("InfoRow renders text and link if url is valid", () => {
|
||||
it("InfoRow renders text and link if url is valid", () => {
|
||||
renderWithProviders(
|
||||
<InfoRow
|
||||
icon={<span>ico</span>}
|
||||
|
||||
@@ -247,7 +247,6 @@ describe("EventPopover", () => {
|
||||
uid: "6045c603-11ab-43c5-bc30-0641420bb3a8",
|
||||
description: "Discuss project",
|
||||
location: "Zoom",
|
||||
repetition: "",
|
||||
organizer: { cn: "test", cal_address: "test@test.com" },
|
||||
timezone: "Europe/Paris",
|
||||
transp: "OPAQUE",
|
||||
@@ -302,7 +301,6 @@ describe("EventPopover", () => {
|
||||
).toBe(formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.end)).split("T")[0]);
|
||||
expect(receivedPayload.newEvent.location).toBe(newEvent.location);
|
||||
expect(receivedPayload.newEvent.organizer).toEqual(newEvent.organizer);
|
||||
expect(receivedPayload.newEvent.repetition).toEqual(newEvent.repetition);
|
||||
expect(receivedPayload.newEvent.color).toEqual(
|
||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"].color
|
||||
);
|
||||
|
||||
@@ -167,7 +167,7 @@ describe("calendarEventToJCal", () => {
|
||||
allday: false,
|
||||
location: "Room 101",
|
||||
description: "Discuss project roadmap.",
|
||||
repetition: "WEEKLY",
|
||||
repetition: { freq: "WEEKLY" },
|
||||
organizer: {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
@@ -280,7 +280,7 @@ describe("calendarEventToJCal", () => {
|
||||
allday: true,
|
||||
location: "Room 101",
|
||||
description: "Discuss project roadmap.",
|
||||
repetition: "WEEKLY",
|
||||
repetition: { freq: "WEEKLY" },
|
||||
organizer: {
|
||||
cn: "Alice",
|
||||
cal_address: "alice@example.com",
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
MenuItem,
|
||||
Box,
|
||||
Stack,
|
||||
Paper,
|
||||
Typography,
|
||||
TextField,
|
||||
Checkbox,
|
||||
List,
|
||||
ListItem,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
Radio,
|
||||
RadioGroup,
|
||||
} from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import { RepetitionObject } from "../../features/Events/EventsTypes";
|
||||
|
||||
export default function RepeatEvent({
|
||||
repetition,
|
||||
setRepetition,
|
||||
isOwn = true,
|
||||
}: {
|
||||
repetition: RepetitionObject;
|
||||
setRepetition: Function;
|
||||
isOwn?: boolean;
|
||||
}) {
|
||||
console.log(JSON.stringify(repetition));
|
||||
|
||||
const repetitionValues = ["day", "week", "month", "year"];
|
||||
const [interval, setInterval] = useState(repetition.interval ?? 0);
|
||||
const [selectedDays, setSelectedDays] = useState<string[]>(
|
||||
repetition.selectedDays ?? []
|
||||
);
|
||||
const [endOption, setEndOption] = useState("");
|
||||
const [occurrences, setOccurrences] = useState(repetition.occurrences) ?? 0;
|
||||
const [endDate, setEndDate] = useState(repetition.endDate ?? "");
|
||||
const days = ["MO", "TU", "WE", "TH", "FR", "SA", "SU"];
|
||||
|
||||
const handleDayChange = (day: string) => {
|
||||
setSelectedDays((prev: string[]) =>
|
||||
prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day]
|
||||
);
|
||||
};
|
||||
return (
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="repeat">Repetition</InputLabel>
|
||||
<Select
|
||||
labelId="repeat"
|
||||
value={repetition.freq ?? ""}
|
||||
disabled={!isOwn}
|
||||
label="Repetition"
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
setRepetition({ ...repetition, freq: e.target.value })
|
||||
}
|
||||
>
|
||||
<MenuItem value={""}>No Repetition</MenuItem>
|
||||
<MenuItem value={"daily"}>Repeat daily</MenuItem>
|
||||
<MenuItem value={"weekly"}>Repeat weekly</MenuItem>
|
||||
<MenuItem value={"monthly"}>Repeat monthly</MenuItem>
|
||||
<MenuItem value={"yearly"}>Repeat yearly</MenuItem>
|
||||
</Select>
|
||||
{repetition.freq && (
|
||||
<Stack>
|
||||
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||
<Typography>Interval:</Typography>
|
||||
<TextField
|
||||
type="number"
|
||||
value={interval}
|
||||
onChange={(e) => setInterval(Number(e.target.value))}
|
||||
size="small"
|
||||
sx={{ width: 80 }}
|
||||
/>
|
||||
|
||||
<Typography>
|
||||
{
|
||||
repetitionValues[
|
||||
repetitionValues.findIndex((el) => el === repetition.freq)
|
||||
]
|
||||
}
|
||||
</Typography>
|
||||
</Box>
|
||||
{repetition.freq === "weekly" && (
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
On days:
|
||||
</Typography>
|
||||
<FormGroup row>
|
||||
{days.map((day) => (
|
||||
<FormControlLabel
|
||||
key={day}
|
||||
control={
|
||||
<Checkbox
|
||||
checked={selectedDays.includes(day)}
|
||||
onChange={() => handleDayChange(day)}
|
||||
/>
|
||||
}
|
||||
label={day}
|
||||
/>
|
||||
))}
|
||||
</FormGroup>
|
||||
</Box>
|
||||
)}
|
||||
<Box>
|
||||
<Typography variant="body2" gutterBottom sx={{ mt: 2 }}>
|
||||
End:
|
||||
</Typography>
|
||||
<RadioGroup
|
||||
value={endOption}
|
||||
onChange={(e) => setEndOption(e.target.value)}
|
||||
>
|
||||
<FormControlLabel
|
||||
value="never"
|
||||
control={<Radio />}
|
||||
label="Never"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
value="after"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
After
|
||||
<TextField
|
||||
type="number"
|
||||
size="small"
|
||||
value={occurrences}
|
||||
onChange={(e) => setOccurrences(Number(e.target.value))}
|
||||
sx={{ width: 100 }}
|
||||
inputProps={{ min: 1 }}
|
||||
disabled={endOption !== "after"}
|
||||
/>
|
||||
occurrences
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
value="on"
|
||||
control={<Radio />}
|
||||
label={
|
||||
<Box display="flex" alignItems="center" gap={1}>
|
||||
On
|
||||
<TextField
|
||||
type="date"
|
||||
size="small"
|
||||
value={endDate}
|
||||
onChange={(e) => setEndDate(e.target.value)}
|
||||
disabled={endOption !== "on"}
|
||||
/>
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</RadioGroup>
|
||||
</Box>
|
||||
</Stack>
|
||||
)}
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export function Menubar() {
|
||||
>
|
||||
<div className="app-grid">
|
||||
{applist.map((prop: AppIconProps) => (
|
||||
<AppIcon prop={prop} />
|
||||
<AppIcon key={prop.name} prop={prop} />
|
||||
))}
|
||||
</div>
|
||||
</Popover>
|
||||
@@ -96,7 +96,6 @@ export function MainTitle() {
|
||||
function AppIcon({ prop }: { prop: AppIconProps }) {
|
||||
return (
|
||||
<a
|
||||
key={prop.name}
|
||||
href={prop.link}
|
||||
target="_blank"
|
||||
style={{ textDecoration: "none", color: "inherit" }}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { CalendarEvent } from "../Events/EventsTypes";
|
||||
import { getCalendar, getCalendars } from "./CalendarApi";
|
||||
import { getOpenPaasUser, getUserDetails } from "../User/userAPI";
|
||||
import { parseCalendarEvent } from "../Events/eventUtils";
|
||||
import { deleteEvent, moveEvent, putEvent } from "../Events/EventApi";
|
||||
import { deleteEvent, getEvent, moveEvent, putEvent } from "../Events/EventApi";
|
||||
import { formatDateToYYYYMMDDTHHMMSS } from "../../utils/dateUtils";
|
||||
|
||||
export const getCalendarsListAsync = createAsyncThunk<
|
||||
@@ -92,6 +92,18 @@ export const putEventAsync = createAsyncThunk<
|
||||
events,
|
||||
};
|
||||
});
|
||||
|
||||
export const getEventAsync = createAsyncThunk<
|
||||
{ calId: string; event: CalendarEvent }, // Return type
|
||||
CalendarEvent // Arg type
|
||||
>("calendars/getEvent", async (event) => {
|
||||
const response: CalendarEvent = await getEvent(event);
|
||||
return {
|
||||
calId: event.calId,
|
||||
event: response,
|
||||
};
|
||||
});
|
||||
|
||||
export const moveEventAsync = createAsyncThunk<
|
||||
{ calId: string; events: CalendarEvent[] }, // Return type
|
||||
{ cal: Calendars; newEvent: CalendarEvent; newURL: string } // Arg type
|
||||
@@ -233,6 +245,24 @@ const CalendarSlice = createSlice({
|
||||
});
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
getEventAsync.fulfilled,
|
||||
(
|
||||
state,
|
||||
action: PayloadAction<{ calId: string; event: CalendarEvent }>
|
||||
) => {
|
||||
state.pending = false;
|
||||
if (!state.list[action.payload.calId]) {
|
||||
state.list[action.payload.calId] = {
|
||||
id: action.payload.calId,
|
||||
events: {},
|
||||
} as Calendars;
|
||||
}
|
||||
|
||||
state.list[action.payload.calId].events[action.payload.event.uid] =
|
||||
action.payload.event;
|
||||
}
|
||||
)
|
||||
.addCase(
|
||||
moveEventAsync.fulfilled,
|
||||
(
|
||||
@@ -266,6 +296,9 @@ const CalendarSlice = createSlice({
|
||||
.addCase(getCalendarDetailAsync.pending, (state) => {
|
||||
state.pending = true;
|
||||
})
|
||||
.addCase(getEventAsync.pending, (state) => {
|
||||
state.pending = true;
|
||||
})
|
||||
.addCase(getCalendarsListAsync.pending, (state) => {
|
||||
state.pending = true;
|
||||
})
|
||||
|
||||
@@ -1,6 +1,20 @@
|
||||
import { api } from "../../utils/apiUtils";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
import { calendarEventToJCal } from "./eventUtils";
|
||||
import { calendarEventToJCal, parseCalendarEvent } from "./eventUtils";
|
||||
import ICAL from "ical.js";
|
||||
|
||||
export async function getEvent(event: CalendarEvent) {
|
||||
const response = await api.get(`dav${event.URL}`);
|
||||
const eventData = await response.text();
|
||||
const eventical = ICAL.parse(eventData);
|
||||
const eventjson = parseCalendarEvent(
|
||||
eventical[2][1][1],
|
||||
event.color ?? "",
|
||||
event.calId,
|
||||
event.URL
|
||||
);
|
||||
return { ...eventjson, ...event };
|
||||
}
|
||||
|
||||
export async function putEvent(event: CalendarEvent) {
|
||||
const response = await api(`dav${event.URL}`, {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
deleteEventAsync,
|
||||
getEventAsync,
|
||||
moveEventAsync,
|
||||
putEventAsync,
|
||||
removeEvent,
|
||||
@@ -42,10 +43,10 @@ import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
||||
import { userAttendee } from "../User/userDataTypes";
|
||||
import { TIMEZONES } from "../../utils/timezone-data";
|
||||
import { Calendars } from "../Calendars/CalendarTypes";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
import { CalendarEvent, RepetitionObject } from "./EventsTypes";
|
||||
import { isValidUrl } from "../../utils/apiUtils";
|
||||
import { formatLocalDateTime } from "./EventModal";
|
||||
import RepeatEvent from "./EventRepeat";
|
||||
import RepeatEvent from "../../components/Event/EventRepeat";
|
||||
|
||||
export default function EventDisplayModal({
|
||||
eventId,
|
||||
@@ -68,8 +69,8 @@ export default function EventDisplayModal({
|
||||
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
||||
const [showMore, setShowMore] = useState(false);
|
||||
|
||||
const calendars = useAppSelector((state) =>
|
||||
Object.values(state.calendars.list)
|
||||
const calendars = Object.values(
|
||||
useAppSelector((state) => state.calendars.list)
|
||||
);
|
||||
|
||||
const userPersonnalCalendars: Calendars[] = calendars.filter(
|
||||
@@ -87,13 +88,15 @@ export default function EventDisplayModal({
|
||||
formatLocalDateTime(new Date(event?.end ?? Date.now()))
|
||||
);
|
||||
const [allday, setAllDay] = useState(event?.allday);
|
||||
const [repetition, setRepetition] = useState(event?.repetition ?? "");
|
||||
const [repetition, setRepetition] = useState<RepetitionObject>(
|
||||
event.repetition ?? ({} as RepetitionObject)
|
||||
);
|
||||
const [alarm, setAlarm] = useState("");
|
||||
const [eventClass, setEventClass] = useState(event?.class ?? "PUBLIC");
|
||||
const [timezone, setTimezone] = useState(event?.timezone ?? "UTC");
|
||||
const [newCalId, setNewCalId] = useState(event.calId);
|
||||
const [calendarid, setCalendarid] = useState(
|
||||
event?.calId.split("/")[0] === user.userData.openpaasId
|
||||
calId.split("/")[0] === user.userData.openpaasId
|
||||
? userPersonnalCalendars.findIndex((cal) => cal.id === calId)
|
||||
: calendars.findIndex((cal) => cal.id === calId)
|
||||
);
|
||||
@@ -103,7 +106,6 @@ export default function EventDisplayModal({
|
||||
(a) => a.cal_address !== event?.organizer?.cal_address
|
||||
)
|
||||
);
|
||||
|
||||
const currentUserAttendee = event?.attendee?.find(
|
||||
(person) => person.cal_address === user.userData.email
|
||||
);
|
||||
@@ -120,7 +122,7 @@ export default function EventDisplayModal({
|
||||
if (!event || !calendar) {
|
||||
onClose({}, "backdropClick");
|
||||
}
|
||||
}, [event, calendar, onClose]);
|
||||
}, [open, eventId, dispatch, onClose]);
|
||||
|
||||
if (!event || !calendar) return null;
|
||||
|
||||
@@ -187,8 +189,18 @@ export default function EventDisplayModal({
|
||||
onClose({}, "backdropClick");
|
||||
};
|
||||
|
||||
const [detailsLoaded, setDetailsLoaded] = useState(false);
|
||||
|
||||
const handleToggleShowMore = async () => {
|
||||
if (!detailsLoaded) {
|
||||
await dispatch(getEventAsync(event));
|
||||
setDetailsLoaded(true);
|
||||
}
|
||||
setShowMore(!showMore);
|
||||
};
|
||||
|
||||
const calList =
|
||||
event.calId.split("/")[0] === user.userData.openpaasId
|
||||
calId.split("/")[0] === user.userData.openpaasId
|
||||
? Object.keys(userPersonnalCalendars).map((calendar, index) => (
|
||||
<MenuItem key={index} value={index}>
|
||||
<Typography variant="body2">
|
||||
@@ -230,7 +242,7 @@ export default function EventDisplayModal({
|
||||
|
||||
<CardHeader title={isOwn ? "Edit Event" : "Event Details"} />
|
||||
|
||||
<CardContent sx={{ maxHeight: "85vh", overflow: "auto" }}>
|
||||
<CardContent sx={{ overflow: "auto" }}>
|
||||
{/* Title */}
|
||||
<TextField
|
||||
fullWidth
|
||||
@@ -403,7 +415,7 @@ export default function EventDisplayModal({
|
||||
? attendees
|
||||
: attendees.slice(0, attendeeDisplayLimit)
|
||||
).map((a, idx) => (
|
||||
<Box>
|
||||
<Box key={a.cal_address}>
|
||||
{renderAttendeeBadge(a, idx.toString())}
|
||||
{isOwn && (
|
||||
<IconButton
|
||||
@@ -491,7 +503,7 @@ export default function EventDisplayModal({
|
||||
<InputLabel id="busy">is Busy</InputLabel>
|
||||
<Select
|
||||
labelId="busy"
|
||||
value={eventClass}
|
||||
value={""}
|
||||
disabled={!isOwn}
|
||||
label="is busy"
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
@@ -531,7 +543,7 @@ export default function EventDisplayModal({
|
||||
<DeleteIcon fontSize="small" />
|
||||
</IconButton>
|
||||
)}
|
||||
<Button size="small" onClick={() => setShowMore(!showMore)}>
|
||||
<Button size="small" onClick={handleToggleShowMore}>
|
||||
{showMore ? "Show Less" : "Show More"}
|
||||
</Button>
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { deleteEventAsync, putEventAsync } from "../Calendars/CalendarSlice";
|
||||
import {
|
||||
deleteEventAsync,
|
||||
getEventAsync,
|
||||
putEventAsync,
|
||||
} from "../Calendars/CalendarSlice";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import {
|
||||
Popover,
|
||||
@@ -32,6 +36,8 @@ import EventDisplayModal, {
|
||||
renderAttendeeBadge,
|
||||
stringAvatar,
|
||||
} from "./EventDisplay";
|
||||
import { getEvent } from "./EventApi";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
|
||||
export default function EventPreviewModal({
|
||||
eventId,
|
||||
@@ -47,13 +53,15 @@ export default function EventPreviewModal({
|
||||
onClose: (event: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
const calendar = useAppSelector((state) => state.calendars.list[calId]);
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const calendar = calendars.list[calId];
|
||||
const event = useAppSelector(
|
||||
(state) => state.calendars.list[calId]?.events[eventId]
|
||||
);
|
||||
const user = useAppSelector((state) => state.user);
|
||||
const [showAllAttendees, setShowAllAttendees] = useState(false);
|
||||
const [openFullDisplay, setOpenFullDisplay] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!event || !calendar) {
|
||||
onClose({}, "backdropClick");
|
||||
@@ -126,7 +134,7 @@ export default function EventPreviewModal({
|
||||
<>
|
||||
<IconButton
|
||||
size="small"
|
||||
onClick={() => {
|
||||
onClick={async () => {
|
||||
setOpenFullDisplay(!openFullDisplay);
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -22,9 +22,9 @@ import { TIMEZONES } from "../../utils/timezone-data";
|
||||
import { putEventAsync } from "../Calendars/CalendarSlice";
|
||||
import { Calendars } from "../Calendars/CalendarTypes";
|
||||
import { userAttendee } from "../User/userDataTypes";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
import { CalendarEvent, RepetitionObject } from "./EventsTypes";
|
||||
import { createSelector } from "@reduxjs/toolkit";
|
||||
import RepeatEvent from "./EventRepeat";
|
||||
import RepeatEvent from "../../components/Event/EventRepeat";
|
||||
|
||||
function EventPopover({
|
||||
anchorEl,
|
||||
@@ -70,7 +70,9 @@ function EventPopover({
|
||||
const [end, setEnd] = useState("");
|
||||
const [calendarid, setCalendarid] = useState(0);
|
||||
const [allday, setAllDay] = useState(false);
|
||||
const [repetition, setRepetition] = useState("");
|
||||
const [repetition, setRepetition] = useState<RepetitionObject>(
|
||||
{} as RepetitionObject
|
||||
);
|
||||
const [attendees, setAttendees] = useState<userAttendee[]>([]);
|
||||
const [alarm, setAlarm] = useState("");
|
||||
const [eventClass, setEventClass] = useState("PUBLIC");
|
||||
@@ -343,7 +345,7 @@ function EventPopover({
|
||||
<InputLabel id="busy">is Busy</InputLabel>
|
||||
<Select
|
||||
labelId="busy"
|
||||
value={eventClass}
|
||||
value={""}
|
||||
label="is busy"
|
||||
onChange={(e: SelectChangeEvent) =>
|
||||
console.log(e.target.value)
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
MenuItem,
|
||||
} from "@mui/material";
|
||||
|
||||
export default function RepeatEvent({
|
||||
repetition,
|
||||
setRepetition,
|
||||
isOwn = true,
|
||||
}: {
|
||||
repetition: string;
|
||||
setRepetition: Function;
|
||||
isOwn?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<FormControl fullWidth margin="dense" size="small">
|
||||
<InputLabel id="repeat">Repetition</InputLabel>
|
||||
<Select
|
||||
labelId="repeat"
|
||||
value={repetition}
|
||||
disabled={!isOwn}
|
||||
label="Repetition"
|
||||
onChange={(e: SelectChangeEvent) => setRepetition(e.target.value)}
|
||||
>
|
||||
<MenuItem value={""}>No Repetition</MenuItem>
|
||||
<MenuItem value={"daily"}>Repeat daily</MenuItem>
|
||||
<MenuItem value={"weekly"}>Repeat weekly</MenuItem>
|
||||
<MenuItem value={"monthly"}>Repeat monthly</MenuItem>
|
||||
<MenuItem value={"yearly"}>Repeat yearly</MenuItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
@@ -21,5 +21,13 @@ export interface CalendarEvent {
|
||||
error?: string;
|
||||
status?: string;
|
||||
timezone: string;
|
||||
repetition?: string;
|
||||
repetition?: RepetitionObject;
|
||||
}
|
||||
|
||||
export interface RepetitionObject {
|
||||
freq: string;
|
||||
interval?: number;
|
||||
selectedDays?: string[];
|
||||
occurrences?: number;
|
||||
endDate?: string;
|
||||
}
|
||||
|
||||
@@ -81,6 +81,22 @@ export function parseCalendarEvent(
|
||||
break;
|
||||
case "status":
|
||||
event.status = String(value);
|
||||
break;
|
||||
case "rrule":
|
||||
event.repetition = { freq: value.freq.toLowerCase() };
|
||||
if (value.byday) {
|
||||
event.repetition.selectedDays = value.byday;
|
||||
}
|
||||
if (value.until) {
|
||||
event.repetition.selectedDays = value.endDate;
|
||||
}
|
||||
if (value.count) {
|
||||
event.repetition.selectedDays = value.occurrences;
|
||||
}
|
||||
if (value.interval) {
|
||||
event.repetition.interval = value.interval;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (recurrenceId && event.uid) {
|
||||
@@ -105,7 +121,7 @@ export function calendarEventToJCal(event: CalendarEvent): any[] {
|
||||
const vevent: any[] = [
|
||||
"vevent",
|
||||
[
|
||||
["uid", {}, "text", event.uid],
|
||||
["uid", {}, "text", event.uid.split("/")[0]],
|
||||
["transp", {}, "text", event.transp ?? "OPAQUE"],
|
||||
[
|
||||
"dtstart",
|
||||
@@ -150,8 +166,21 @@ export function calendarEventToJCal(event: CalendarEvent): any[] {
|
||||
if (event.description) {
|
||||
vevent[1].push(["description", {}, "text", event.description]);
|
||||
}
|
||||
if (event.repetition) {
|
||||
vevent[1].push(["rrule", {}, "recur", { freq: event.repetition }]);
|
||||
if (event.repetition?.freq) {
|
||||
const repetitionRule: Record<string, any> = { freq: event.repetition.freq };
|
||||
if (event.repetition.interval) {
|
||||
repetitionRule["interval"] = event.repetition.interval;
|
||||
}
|
||||
if (event.repetition.occurrences) {
|
||||
repetitionRule["count"] = event.repetition.occurrences;
|
||||
}
|
||||
if (event.repetition.endDate) {
|
||||
repetitionRule["until"] = event.repetition.endDate;
|
||||
}
|
||||
if (event.repetition.selectedDays) {
|
||||
repetitionRule["byday"] = event.repetition.selectedDays;
|
||||
}
|
||||
vevent[1].push(["rrule", {}, "recur", repetitionRule]);
|
||||
}
|
||||
|
||||
event.attendee.forEach((att) => {
|
||||
|
||||
Reference in New Issue
Block a user