Merge pull request #95 from linagora/77-implement-complex-repetition-ui
implement complex repetition ui
This commit is contained in:
@@ -0,0 +1,366 @@
|
|||||||
|
import { screen, fireEvent, waitFor, act } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { renderWithProviders } from "../utils/Renderwithproviders";
|
||||||
|
import RepeatEvent from "../../src/components/Event/EventRepeat";
|
||||||
|
import EventPopover from "../../src/features/Events/EventModal";
|
||||||
|
import { RepetitionObject } from "../../src/features/Events/EventsTypes";
|
||||||
|
import { DateSelectArg } from "@fullcalendar/core";
|
||||||
|
import { formatDateToYYYYMMDDTHHMMSS } from "../../src/utils/dateUtils";
|
||||||
|
import * as eventThunks from "../../src/features/Calendars/CalendarSlice";
|
||||||
|
import * as apiUtils from "../../src/utils/apiUtils";
|
||||||
|
|
||||||
|
const baseRepetition: RepetitionObject = {
|
||||||
|
freq: "",
|
||||||
|
interval: 1,
|
||||||
|
occurrences: 0,
|
||||||
|
endDate: "",
|
||||||
|
selectedDays: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockOnClose = jest.fn();
|
||||||
|
const mockSetSelectedRange = jest.fn();
|
||||||
|
const mockCalendarRef = { current: { select: jest.fn() } } as any;
|
||||||
|
|
||||||
|
const preloadedState = {
|
||||||
|
user: {
|
||||||
|
userData: {
|
||||||
|
sub: "test",
|
||||||
|
email: "test@test.com",
|
||||||
|
sid: "aiYbWZSk2g0F+LrQeD7Dg4QcUMR8R/zTZdZBiA7N6Ro",
|
||||||
|
openpaasId: "667037022b752d0026472254",
|
||||||
|
},
|
||||||
|
organiserData: {
|
||||||
|
cn: "test",
|
||||||
|
cal_address: "test@test.com",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
calendars: {
|
||||||
|
list: {
|
||||||
|
"667037022b752d0026472254/cal1": {
|
||||||
|
id: "667037022b752d0026472254/cal1",
|
||||||
|
name: "Calendar 1",
|
||||||
|
color: "#FF0000",
|
||||||
|
},
|
||||||
|
"667037022b752d0026472254/cal2": {
|
||||||
|
id: "667037022b752d0026472254/cal2",
|
||||||
|
name: "Calendar 2",
|
||||||
|
color: "#00FF00",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pending: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultSelectedRange = {
|
||||||
|
startStr: "2025-07-18T09:00",
|
||||||
|
endStr: "2025-07-18T10:00",
|
||||||
|
start: new Date("2025-07-18T09:00"),
|
||||||
|
end: new Date("2025-07-18T10:00"),
|
||||||
|
allDay: false,
|
||||||
|
resource: undefined,
|
||||||
|
} as unknown as DateSelectArg;
|
||||||
|
|
||||||
|
function setupRepeatEvent(props?: Partial<RepetitionObject>, state?: any) {
|
||||||
|
const setRepetition = jest.fn();
|
||||||
|
renderWithProviders(
|
||||||
|
<RepeatEvent
|
||||||
|
repetition={{ ...baseRepetition, ...props }}
|
||||||
|
eventStart={defaultSelectedRange.start}
|
||||||
|
setRepetition={setRepetition}
|
||||||
|
isOwn={true}
|
||||||
|
/>,
|
||||||
|
state
|
||||||
|
);
|
||||||
|
return { setRepetition };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupEventPopover(
|
||||||
|
overrides?: Partial<{ start: string; end: string }>
|
||||||
|
) {
|
||||||
|
jest
|
||||||
|
.spyOn(crypto, "randomUUID")
|
||||||
|
.mockReturnValue("fixed-uuid-with-correct-format");
|
||||||
|
const originalDateResolvedOptions =
|
||||||
|
new Intl.DateTimeFormat().resolvedOptions();
|
||||||
|
jest.spyOn(Intl.DateTimeFormat.prototype, "resolvedOptions").mockReturnValue({
|
||||||
|
...originalDateResolvedOptions,
|
||||||
|
timeZone: "UTC",
|
||||||
|
});
|
||||||
|
|
||||||
|
renderWithProviders(
|
||||||
|
<EventPopover
|
||||||
|
anchorEl={document.body}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
selectedRange={defaultSelectedRange}
|
||||||
|
setSelectedRange={mockSetSelectedRange}
|
||||||
|
calendarRef={mockCalendarRef}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
act(() => {
|
||||||
|
fireEvent.change(screen.getByLabelText("Title"), {
|
||||||
|
target: { value: "Meeting" },
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByLabelText("All day"));
|
||||||
|
fireEvent.change(screen.getByLabelText("Start"), {
|
||||||
|
target: {
|
||||||
|
value: (overrides?.start ?? "2025-07-18T00:00:00.000Z").split("T")[0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fireEvent.change(screen.getByLabelText("End"), {
|
||||||
|
target: {
|
||||||
|
value: (overrides?.end ?? "2025-07-19T00:00:00.000Z").split("T")[0],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
fireEvent.click(screen.getByText("Show More"));
|
||||||
|
});
|
||||||
|
const select = screen.getByLabelText(/repetition/i);
|
||||||
|
userEvent.click(select);
|
||||||
|
|
||||||
|
return jest.spyOn(apiUtils, "api");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function expectRRule(expected: any) {
|
||||||
|
const spyAPi = jest.spyOn(apiUtils, "api");
|
||||||
|
|
||||||
|
act(() => fireEvent.click(screen.getByText("Save")));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spyAPi).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
const receivedPayload: string =
|
||||||
|
spyAPi.mock.calls[0][1]?.body?.toString() ?? "";
|
||||||
|
const [, , [vevent]] = JSON.parse(receivedPayload);
|
||||||
|
const rrule = vevent[1].find(([name]: any) => name === "rrule");
|
||||||
|
|
||||||
|
if (rrule[3].byday) {
|
||||||
|
expect({
|
||||||
|
...rrule[3],
|
||||||
|
byday: rrule[3].byday.sort(),
|
||||||
|
}).toEqual({
|
||||||
|
...expected,
|
||||||
|
byday: expected.byday.sort(),
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
expect(rrule[3]).toEqual(expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("RepeatEvent", () => {
|
||||||
|
it("renders with no repetition by default", () => {
|
||||||
|
setupRepeatEvent();
|
||||||
|
expect(screen.getByLabelText(/repetition/i)).toBeInTheDocument();
|
||||||
|
expect(screen.queryByText(/daily/i)).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows selecting repetition frequency", async () => {
|
||||||
|
const { setRepetition } = setupRepeatEvent();
|
||||||
|
const select = screen.getByLabelText(/repetition/i);
|
||||||
|
act(() => {
|
||||||
|
userEvent.click(select);
|
||||||
|
});
|
||||||
|
await waitFor(async () =>
|
||||||
|
userEvent.click(await screen.findByText(/repeat weekly/i))
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(setRepetition).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ freq: "weekly" })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders interval input when frequency is selected", () => {
|
||||||
|
setupRepeatEvent({ freq: "daily", interval: 2 });
|
||||||
|
expect(screen.getByText(/interval/i)).toBeInTheDocument();
|
||||||
|
expect(screen.getByDisplayValue("2")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates interval value", () => {
|
||||||
|
const { setRepetition } = setupRepeatEvent({ freq: "daily", interval: 1 });
|
||||||
|
const input = screen.getByDisplayValue("1");
|
||||||
|
fireEvent.change(input, { target: { value: "5" } });
|
||||||
|
expect(setRepetition).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ interval: 5 })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("toggles day selection for weekly frequency", () => {
|
||||||
|
const { setRepetition } = setupRepeatEvent({
|
||||||
|
freq: "weekly",
|
||||||
|
selectedDays: [],
|
||||||
|
});
|
||||||
|
act(() => {
|
||||||
|
const mondayCheckbox = screen.getByLabelText("MO");
|
||||||
|
fireEvent.click(mondayCheckbox);
|
||||||
|
});
|
||||||
|
expect(setRepetition).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({ selectedDays: ["MO"] })
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Repeat Event API calls", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
jest.restoreAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct CalendarEvent payload", async () => {
|
||||||
|
setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat weekly/i));
|
||||||
|
|
||||||
|
const spy = jest
|
||||||
|
.spyOn(eventThunks, "putEventAsync")
|
||||||
|
.mockImplementation((payload) => () => Promise.resolve(payload) as any);
|
||||||
|
act(() => fireEvent.click(screen.getByText("Save")));
|
||||||
|
await waitFor(() => expect(spy).toHaveBeenCalled());
|
||||||
|
|
||||||
|
const received = spy.mock.calls[0][0];
|
||||||
|
expect(received.cal).toEqual(
|
||||||
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
|
||||||
|
);
|
||||||
|
expect(received.newEvent.title).toBe("Meeting");
|
||||||
|
expect(
|
||||||
|
formatDateToYYYYMMDDTHHMMSS(received.newEvent.start).split("T")[0]
|
||||||
|
).toBe("20250718");
|
||||||
|
expect(
|
||||||
|
formatDateToYYYYMMDDTHHMMSS(received.newEvent.end || new Date()).split(
|
||||||
|
"T"
|
||||||
|
)[0]
|
||||||
|
).toBe("20250719");
|
||||||
|
expect(received.newEvent.organizer).toEqual(
|
||||||
|
preloadedState.user.organiserData
|
||||||
|
);
|
||||||
|
const day = new Date(received.newEvent.start)
|
||||||
|
.toLocaleString("en-UK", {
|
||||||
|
weekday: "short",
|
||||||
|
})
|
||||||
|
.slice(0, 2)
|
||||||
|
.toUpperCase();
|
||||||
|
|
||||||
|
expect(received.newEvent.repetition).toEqual({
|
||||||
|
freq: "weekly",
|
||||||
|
selectedDays: [day],
|
||||||
|
});
|
||||||
|
expect(received.newEvent.color).toEqual(
|
||||||
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"].color
|
||||||
|
);
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct API payload for repeat daily", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat daily/i));
|
||||||
|
|
||||||
|
await expectRRule({ freq: "daily" });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct API payload for repeat daily with 2 day interval", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat daily/i));
|
||||||
|
const intervalInput = screen.getByDisplayValue("1");
|
||||||
|
fireEvent.change(intervalInput, { target: { value: "2" } });
|
||||||
|
|
||||||
|
await expectRRule({ freq: "daily", interval: 2 });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct API payload for repeat daily for 5 repetitions", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat daily/i));
|
||||||
|
userEvent.click(screen.getByLabelText(/after/i));
|
||||||
|
const input = screen.getAllByRole("spinbutton")[1];
|
||||||
|
fireEvent.change(input, { target: { value: "5" } });
|
||||||
|
|
||||||
|
await expectRRule({ freq: "daily", count: 5 });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct API payload for repeat daily until now+5days", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat daily/i));
|
||||||
|
userEvent.click(screen.getAllByLabelText(/on/i)[3]);
|
||||||
|
|
||||||
|
const untilInput = screen.getByTestId("end-date");
|
||||||
|
const futureDate = new Date();
|
||||||
|
futureDate.setDate(futureDate.getDate() + 5);
|
||||||
|
|
||||||
|
fireEvent.change(untilInput, {
|
||||||
|
target: { value: futureDate.toISOString().split("T")[0] },
|
||||||
|
});
|
||||||
|
await expectRRule({
|
||||||
|
freq: "daily",
|
||||||
|
until: futureDate.toISOString().split("T")[0],
|
||||||
|
});
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct API payload for repeat weekly on Thursday and event day (Friday)", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat weekly/i));
|
||||||
|
userEvent.click(screen.getByLabelText("TH"));
|
||||||
|
|
||||||
|
await expectRRule({ freq: "weekly", byday: ["TH", "FR"] });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("sends correct API payload for repeat weekly on Thursday and event day (Friday) and an interval of 3 weeks", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat weekly/i));
|
||||||
|
|
||||||
|
userEvent.click(screen.getByLabelText("TH"));
|
||||||
|
|
||||||
|
const intervalInput = screen.getByDisplayValue("1");
|
||||||
|
fireEvent.change(intervalInput, { target: { value: "3" } });
|
||||||
|
|
||||||
|
await expectRRule({ freq: "weekly", byday: ["TH", "FR"], interval: 3 });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("sends correct API payload for repeat monthly", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat monthly/i));
|
||||||
|
|
||||||
|
await expectRRule({ freq: "monthly" });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("sends correct API payload for repeat monthly and end after 5 occurrences", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat monthly/i));
|
||||||
|
userEvent.click(screen.getByLabelText(/after/i));
|
||||||
|
const input = screen.getAllByRole("spinbutton")[1];
|
||||||
|
fireEvent.change(input, { target: { value: "5" } });
|
||||||
|
await expectRRule({ freq: "monthly", count: 5 });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
it("sends correct API payload for repeat yearly", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat yearly/i));
|
||||||
|
|
||||||
|
await expectRRule({ freq: "yearly" });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sends correct API payload for repeat yearly, but user first choose to end after 5 occurrences then changed mind and chose to not end", async () => {
|
||||||
|
await setupEventPopover();
|
||||||
|
|
||||||
|
userEvent.click(await screen.findByText(/repeat yearly/i));
|
||||||
|
userEvent.click(screen.getByLabelText(/after/i));
|
||||||
|
const input = screen.getAllByRole("spinbutton")[1];
|
||||||
|
fireEvent.change(input, { target: { value: "5" } });
|
||||||
|
userEvent.click(screen.getByLabelText(/never/i));
|
||||||
|
|
||||||
|
await expectRRule({ freq: "yearly" });
|
||||||
|
expect(mockOnClose).toHaveBeenCalledWith({}, "backdropClick");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -474,6 +474,37 @@ describe("Event Preview Display", () => {
|
|||||||
const updatedEvent = spy.mock.calls[0][0].newEvent;
|
const updatedEvent = spy.mock.calls[0][0].newEvent;
|
||||||
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
||||||
});
|
});
|
||||||
|
it("handles Edit click", 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(
|
||||||
|
<EventPreviewModal
|
||||||
|
anchorPosition={{ top: 0, left: 0 }}
|
||||||
|
open={true}
|
||||||
|
onClose={mockOnClose}
|
||||||
|
calId={"667037022b752d0026472254/cal1"}
|
||||||
|
eventId={"event1"}
|
||||||
|
/>,
|
||||||
|
preloadedState
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("EditIcon"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
expect(screen.getByText("Edit Event")).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("Event Full Display", () => {
|
describe("Event Full Display", () => {
|
||||||
@@ -917,18 +948,6 @@ describe("Event Full Display", () => {
|
|||||||
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
expect(updatedEvent.attendee[0].partstat).toBe("DECLINED");
|
||||||
});
|
});
|
||||||
it("toggle Show More reveals extra fields", async () => {
|
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(
|
renderWithProviders(
|
||||||
<EventDisplayModal
|
<EventDisplayModal
|
||||||
open={true}
|
open={true}
|
||||||
@@ -942,10 +961,6 @@ describe("Event Full Display", () => {
|
|||||||
fireEvent.click(screen.getByText("Show More"));
|
fireEvent.click(screen.getByText("Show More"));
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(spy).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
console.log(spy);
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByLabelText(/Alarm/i)).toBeInTheDocument();
|
expect(screen.getByLabelText(/Alarm/i)).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText(/Repetition/i)).toBeInTheDocument();
|
expect(screen.getByLabelText(/Repetition/i)).toBeInTheDocument();
|
||||||
|
|||||||
@@ -286,7 +286,6 @@ describe("EventPopover", () => {
|
|||||||
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
|
preloadedState.calendars.list["667037022b752d0026472254/cal1"]
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log(receivedPayload.newEvent.start);
|
|
||||||
expect(receivedPayload.newEvent.title).toBe(newEvent.title);
|
expect(receivedPayload.newEvent.title).toBe(newEvent.title);
|
||||||
expect(receivedPayload.newEvent.description).toBe(newEvent.description);
|
expect(receivedPayload.newEvent.description).toBe(newEvent.description);
|
||||||
expect(
|
expect(
|
||||||
|
|||||||
@@ -280,7 +280,7 @@ describe("calendarEventToJCal", () => {
|
|||||||
allday: true,
|
allday: true,
|
||||||
location: "Room 101",
|
location: "Room 101",
|
||||||
description: "Discuss project roadmap.",
|
description: "Discuss project roadmap.",
|
||||||
repetition: { freq: "WEEKLY" },
|
repetition: { freq: "WEEKLY", interval: 2 },
|
||||||
organizer: {
|
organizer: {
|
||||||
cn: "Alice",
|
cn: "Alice",
|
||||||
cal_address: "alice@example.com",
|
cal_address: "alice@example.com",
|
||||||
@@ -315,7 +315,7 @@ describe("calendarEventToJCal", () => {
|
|||||||
["location", {}, "text", "Room 101"],
|
["location", {}, "text", "Room 101"],
|
||||||
["description", {}, "text", "Discuss project roadmap."],
|
["description", {}, "text", "Discuss project roadmap."],
|
||||||
["x-openpaas-videoconference", {}, "unknown", null],
|
["x-openpaas-videoconference", {}, "unknown", null],
|
||||||
["rrule", {}, "recur", { freq: "WEEKLY" }],
|
["rrule", {}, "recur", { freq: "WEEKLY", interval: 2 }],
|
||||||
[
|
[
|
||||||
"organizer",
|
"organizer",
|
||||||
{ cn: "Alice" },
|
{ cn: "Alice" },
|
||||||
|
|||||||
@@ -32,10 +32,12 @@ export default function UserSearch({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const delayDebounceFn = setTimeout(async () => {
|
const delayDebounceFn = setTimeout(async () => {
|
||||||
setLoading(true);
|
if (query) {
|
||||||
const res = await searchUsers(query);
|
setLoading(true);
|
||||||
setOptions(res);
|
const res = await searchUsers(query);
|
||||||
setLoading(false);
|
setOptions(res);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
return () => clearTimeout(delayDebounceFn);
|
return () => clearTimeout(delayDebounceFn);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { CalendarEvent } from "../../features/Events/EventsTypes";
|
|||||||
import CalendarSelection from "./CalendarSelection";
|
import CalendarSelection from "./CalendarSelection";
|
||||||
import {
|
import {
|
||||||
getCalendarDetailAsync,
|
getCalendarDetailAsync,
|
||||||
|
getEventAsync,
|
||||||
putEventAsync,
|
putEventAsync,
|
||||||
updateEventLocal,
|
updateEventLocal,
|
||||||
} from "../../features/Calendars/CalendarSlice";
|
} from "../../features/Calendars/CalendarSlice";
|
||||||
@@ -326,11 +327,21 @@ export default function CalendarApp() {
|
|||||||
setEventDisplayedCalId(info.event.extendedProps.calId);
|
setEventDisplayedCalId(info.event.extendedProps.calId);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
eventAllow={(dropInfo, draggedEvent) => {
|
||||||
|
if (
|
||||||
|
draggedEvent?.extendedProps.uid &&
|
||||||
|
draggedEvent.extendedProps.uid.split("/")[1]
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}}
|
||||||
eventDrop={(arg) => {
|
eventDrop={(arg) => {
|
||||||
const event =
|
const event =
|
||||||
calendars[arg.event._def.extendedProps.calId].events[
|
calendars[arg.event._def.extendedProps.calId].events[
|
||||||
arg.event._def.extendedProps.uid
|
arg.event._def.extendedProps.uid
|
||||||
];
|
];
|
||||||
|
|
||||||
const totalDeltaMs = getDeltaInMilliseconds(arg.delta);
|
const totalDeltaMs = getDeltaInMilliseconds(arg.delta);
|
||||||
|
|
||||||
const originalStart = new Date(event.start);
|
const originalStart = new Date(event.start);
|
||||||
@@ -358,7 +369,9 @@ export default function CalendarApp() {
|
|||||||
calendars[arg.event._def.extendedProps.calId].events[
|
calendars[arg.event._def.extendedProps.calId].events[
|
||||||
arg.event._def.extendedProps.uid
|
arg.event._def.extendedProps.uid
|
||||||
];
|
];
|
||||||
|
if (event.uid.split("/")[1]) {
|
||||||
|
dispatch(getEventAsync(event));
|
||||||
|
}
|
||||||
const originalStart = new Date(event.start);
|
const originalStart = new Date(event.start);
|
||||||
const computedNewStart = new Date(
|
const computedNewStart = new Date(
|
||||||
originalStart.getTime() + getDeltaInMilliseconds(arg.startDelta)
|
originalStart.getTime() + getDeltaInMilliseconds(arg.startDelta)
|
||||||
@@ -372,7 +385,7 @@ export default function CalendarApp() {
|
|||||||
start: computedNewStart,
|
start: computedNewStart,
|
||||||
end: computedNewEnd,
|
end: computedNewEnd,
|
||||||
} as CalendarEvent;
|
} as CalendarEvent;
|
||||||
console.log(event , newEvent);
|
console.log(event, newEvent);
|
||||||
dispatch(
|
dispatch(
|
||||||
putEventAsync({ cal: calendars[newEvent.calId], newEvent })
|
putEventAsync({ cal: calendars[newEvent.calId], newEvent })
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,46 +6,56 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
Box,
|
Box,
|
||||||
Stack,
|
Stack,
|
||||||
Paper,
|
|
||||||
Typography,
|
Typography,
|
||||||
TextField,
|
TextField,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
List,
|
|
||||||
ListItem,
|
|
||||||
FormControlLabel,
|
FormControlLabel,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Radio,
|
Radio,
|
||||||
RadioGroup,
|
RadioGroup,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { RepetitionObject } from "../../features/Events/EventsTypes";
|
import { RepetitionObject } from "../../features/Events/EventsTypes";
|
||||||
|
|
||||||
export default function RepeatEvent({
|
export default function RepeatEvent({
|
||||||
repetition,
|
repetition,
|
||||||
|
eventStart,
|
||||||
setRepetition,
|
setRepetition,
|
||||||
isOwn = true,
|
isOwn = true,
|
||||||
}: {
|
}: {
|
||||||
repetition: RepetitionObject;
|
repetition: RepetitionObject;
|
||||||
|
eventStart: Date;
|
||||||
setRepetition: Function;
|
setRepetition: Function;
|
||||||
isOwn?: boolean;
|
isOwn?: boolean;
|
||||||
}) {
|
}) {
|
||||||
console.log(JSON.stringify(repetition));
|
|
||||||
|
|
||||||
const repetitionValues = ["day", "week", "month", "year"];
|
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 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";
|
||||||
|
if (repetition.endDate) return "on";
|
||||||
|
return "never";
|
||||||
|
};
|
||||||
|
|
||||||
|
const [endOption, setEndOption] = useState(getEndOption());
|
||||||
|
|
||||||
|
// keep endOption in sync if repetition changes from parent
|
||||||
|
useEffect(() => {
|
||||||
|
if (!endOption) {
|
||||||
|
setEndOption(getEndOption());
|
||||||
|
}
|
||||||
|
}, [repetition.occurrences, repetition.endDate]);
|
||||||
|
|
||||||
const handleDayChange = (day: string) => {
|
const handleDayChange = (day: string) => {
|
||||||
setSelectedDays((prev: string[]) =>
|
const updatedDays = repetition.selectedDays?.includes(day)
|
||||||
prev.includes(day) ? prev.filter((d) => d !== day) : [...prev, day]
|
? repetition.selectedDays.filter((d) => d !== day)
|
||||||
);
|
: [...(repetition.selectedDays ?? []), day];
|
||||||
|
|
||||||
|
setRepetition({ ...repetition, selectedDays: updatedDays });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormControl fullWidth margin="dense" size="small">
|
<FormControl fullWidth margin="dense" size="small">
|
||||||
<InputLabel id="repeat">Repetition</InputLabel>
|
<InputLabel id="repeat">Repetition</InputLabel>
|
||||||
@@ -54,9 +64,17 @@ export default function RepeatEvent({
|
|||||||
value={repetition.freq ?? ""}
|
value={repetition.freq ?? ""}
|
||||||
disabled={!isOwn}
|
disabled={!isOwn}
|
||||||
label="Repetition"
|
label="Repetition"
|
||||||
onChange={(e: SelectChangeEvent) =>
|
onChange={(e: SelectChangeEvent) => {
|
||||||
setRepetition({ ...repetition, freq: e.target.value })
|
if (e.target.value === "weekly") {
|
||||||
}
|
setRepetition({
|
||||||
|
...repetition,
|
||||||
|
freq: e.target.value,
|
||||||
|
selectedDays: [days[day.getDay() - 1]],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setRepetition({ ...repetition, freq: e.target.value });
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MenuItem value={""}>No Repetition</MenuItem>
|
<MenuItem value={""}>No Repetition</MenuItem>
|
||||||
<MenuItem value={"daily"}>Repeat daily</MenuItem>
|
<MenuItem value={"daily"}>Repeat daily</MenuItem>
|
||||||
@@ -64,18 +82,24 @@ export default function RepeatEvent({
|
|||||||
<MenuItem value={"monthly"}>Repeat monthly</MenuItem>
|
<MenuItem value={"monthly"}>Repeat monthly</MenuItem>
|
||||||
<MenuItem value={"yearly"}>Repeat yearly</MenuItem>
|
<MenuItem value={"yearly"}>Repeat yearly</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
{repetition.freq && (
|
{repetition.freq && (
|
||||||
<Stack>
|
<Stack>
|
||||||
|
{/* Interval */}
|
||||||
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
||||||
<Typography>Interval:</Typography>
|
<Typography>Interval:</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
value={interval}
|
value={repetition.interval ?? 1}
|
||||||
onChange={(e) => setInterval(Number(e.target.value))}
|
onChange={(e) =>
|
||||||
|
setRepetition({
|
||||||
|
...repetition,
|
||||||
|
interval: Number(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
size="small"
|
size="small"
|
||||||
sx={{ width: 80 }}
|
sx={{ width: 80 }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Typography>
|
<Typography>
|
||||||
{
|
{
|
||||||
repetitionValues[
|
repetitionValues[
|
||||||
@@ -84,6 +108,8 @@ export default function RepeatEvent({
|
|||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
|
{/* Weekly selection */}
|
||||||
{repetition.freq === "weekly" && (
|
{repetition.freq === "weekly" && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="body2" gutterBottom>
|
<Typography variant="body2" gutterBottom>
|
||||||
@@ -95,7 +121,9 @@ export default function RepeatEvent({
|
|||||||
key={day}
|
key={day}
|
||||||
control={
|
control={
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={selectedDays.includes(day)}
|
checked={
|
||||||
|
repetition.selectedDays?.includes(day) ?? false
|
||||||
|
}
|
||||||
onChange={() => handleDayChange(day)}
|
onChange={() => handleDayChange(day)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
@@ -105,13 +133,36 @@ export default function RepeatEvent({
|
|||||||
</FormGroup>
|
</FormGroup>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* End options */}
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="body2" gutterBottom sx={{ mt: 2 }}>
|
<Typography variant="body2" gutterBottom sx={{ mt: 2 }}>
|
||||||
End:
|
End:
|
||||||
</Typography>
|
</Typography>
|
||||||
<RadioGroup
|
<RadioGroup
|
||||||
value={endOption}
|
value={endOption}
|
||||||
onChange={(e) => setEndOption(e.target.value)}
|
onChange={(e) => {
|
||||||
|
const value = e.target.value;
|
||||||
|
setEndOption(value);
|
||||||
|
|
||||||
|
if (value === "never") {
|
||||||
|
setRepetition({ ...repetition, occurrences: 0, endDate: "" });
|
||||||
|
}
|
||||||
|
if (value === "after") {
|
||||||
|
setRepetition({
|
||||||
|
...repetition,
|
||||||
|
occurrences: 0,
|
||||||
|
endDate: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (value === "on") {
|
||||||
|
setRepetition({
|
||||||
|
...repetition,
|
||||||
|
occurrences: 0,
|
||||||
|
endDate: new Date().toISOString().slice(0, 16),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
value="never"
|
value="never"
|
||||||
@@ -128,8 +179,14 @@ export default function RepeatEvent({
|
|||||||
<TextField
|
<TextField
|
||||||
type="number"
|
type="number"
|
||||||
size="small"
|
size="small"
|
||||||
value={occurrences}
|
value={repetition.occurrences ?? 0}
|
||||||
onChange={(e) => setOccurrences(Number(e.target.value))}
|
onChange={(e) =>
|
||||||
|
setRepetition({
|
||||||
|
...repetition,
|
||||||
|
endDate: "",
|
||||||
|
occurrences: Number(e.target.value),
|
||||||
|
})
|
||||||
|
}
|
||||||
sx={{ width: 100 }}
|
sx={{ width: 100 }}
|
||||||
inputProps={{ min: 1 }}
|
inputProps={{ min: 1 }}
|
||||||
disabled={endOption !== "after"}
|
disabled={endOption !== "after"}
|
||||||
@@ -147,9 +204,16 @@ export default function RepeatEvent({
|
|||||||
On
|
On
|
||||||
<TextField
|
<TextField
|
||||||
type="date"
|
type="date"
|
||||||
|
inputProps={{ "data-testid": "end-date" }}
|
||||||
size="small"
|
size="small"
|
||||||
value={endDate}
|
value={repetition.endDate ?? ""}
|
||||||
onChange={(e) => setEndDate(e.target.value)}
|
onChange={(e) =>
|
||||||
|
setRepetition({
|
||||||
|
...repetition,
|
||||||
|
occurrences: 0,
|
||||||
|
endDate: e.target.value,
|
||||||
|
})
|
||||||
|
}
|
||||||
disabled={endOption !== "on"}
|
disabled={endOption !== "on"}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -71,11 +71,18 @@ export const putEventAsync = createAsyncThunk<
|
|||||||
{ cal: Calendars; newEvent: CalendarEvent } // Arg type
|
{ cal: Calendars; newEvent: CalendarEvent } // Arg type
|
||||||
>("calendars/putEvent", async ({ cal, newEvent }) => {
|
>("calendars/putEvent", async ({ cal, newEvent }) => {
|
||||||
const response = await putEvent(newEvent);
|
const response = await putEvent(newEvent);
|
||||||
|
const eventDate = new Date(newEvent.start);
|
||||||
|
|
||||||
|
const weekStart = new Date(eventDate);
|
||||||
|
weekStart.setHours(0, 0, 0, 0);
|
||||||
|
weekStart.setDate(eventDate.getDate() - eventDate.getDay());
|
||||||
|
|
||||||
|
const weekEnd = new Date(weekStart);
|
||||||
|
weekEnd.setDate(weekStart.getDate() + 7);
|
||||||
|
|
||||||
const calEvents = (await getCalendar(cal.id, {
|
const calEvents = (await getCalendar(cal.id, {
|
||||||
start: formatDateToYYYYMMDDTHHMMSS(new Date(newEvent.start)),
|
start: formatDateToYYYYMMDDTHHMMSS(weekStart),
|
||||||
end: formatDateToYYYYMMDDTHHMMSS(
|
end: formatDateToYYYYMMDDTHHMMSS(weekEnd),
|
||||||
new Date(new Date(newEvent.start).getTime() + 86400000)
|
|
||||||
),
|
|
||||||
})) as Record<string, any>;
|
})) as Record<string, any>;
|
||||||
const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap(
|
const events: CalendarEvent[] = calEvents._embedded["dav:item"].flatMap(
|
||||||
(eventdata: any) => {
|
(eventdata: any) => {
|
||||||
@@ -291,7 +298,20 @@ const CalendarSlice = createSlice({
|
|||||||
)
|
)
|
||||||
.addCase(deleteEventAsync.fulfilled, (state, action) => {
|
.addCase(deleteEventAsync.fulfilled, (state, action) => {
|
||||||
state.pending = false;
|
state.pending = false;
|
||||||
delete state.list[action.payload.calId].events[action.payload.eventId];
|
const [baseId, recurrenceId] = action.payload.eventId.split("/");
|
||||||
|
if (recurrenceId) {
|
||||||
|
Object.keys(state.list[action.payload.calId].events).forEach(
|
||||||
|
(element) => {
|
||||||
|
if (element.split("/")[0] === baseId) {
|
||||||
|
delete state.list[action.payload.calId].events[element];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
delete state.list[action.payload.calId].events[
|
||||||
|
action.payload.eventId
|
||||||
|
];
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.addCase(getCalendarDetailAsync.pending, (state) => {
|
.addCase(getCalendarDetailAsync.pending, (state) => {
|
||||||
state.pending = true;
|
state.pending = true;
|
||||||
|
|||||||
@@ -124,7 +124,8 @@ export default function EventDisplayModal({
|
|||||||
if (!event || !calendar) {
|
if (!event || !calendar) {
|
||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
}
|
}
|
||||||
}, [open, eventId, dispatch, onClose]);
|
setRepetition(event.repetition ?? ({} as RepetitionObject));
|
||||||
|
}, [open, eventId, dispatch, onClose, event]);
|
||||||
|
|
||||||
if (!event || !calendar) return null;
|
if (!event || !calendar) return null;
|
||||||
|
|
||||||
@@ -161,6 +162,16 @@ export default function EventDisplayModal({
|
|||||||
color: userPersonnalCalendars[calendarid]?.color,
|
color: userPersonnalCalendars[calendarid]?.color,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const [baseId, recurrenceId] = event.uid.split("/");
|
||||||
|
if (recurrenceId) {
|
||||||
|
Object.keys(userPersonnalCalendars[calendarid].events).forEach(
|
||||||
|
(element) => {
|
||||||
|
if (element.split("/")[0] === baseId) {
|
||||||
|
dispatch(removeEvent({ calendarUid: calId, eventUid: element }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
await dispatch(
|
await dispatch(
|
||||||
putEventAsync({
|
putEventAsync({
|
||||||
cal: userPersonnalCalendars[calendarid],
|
cal: userPersonnalCalendars[calendarid],
|
||||||
@@ -181,13 +192,7 @@ export default function EventDisplayModal({
|
|||||||
onClose({}, "backdropClick");
|
onClose({}, "backdropClick");
|
||||||
};
|
};
|
||||||
|
|
||||||
const [detailsLoaded, setDetailsLoaded] = useState(false);
|
|
||||||
|
|
||||||
const handleToggleShowMore = async () => {
|
const handleToggleShowMore = async () => {
|
||||||
if (!detailsLoaded) {
|
|
||||||
await dispatch(getEventAsync(event));
|
|
||||||
setDetailsLoaded(true);
|
|
||||||
}
|
|
||||||
setShowMore(!showMore);
|
setShowMore(!showMore);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -224,315 +229,332 @@ export default function EventDisplayModal({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open={open} onClose={onClose}>
|
<Modal open={open} onClose={onClose}>
|
||||||
<Card sx={{ minWidth: 300, width: "50vw", p: 2, position: "absolute" }}>
|
<Box
|
||||||
{/* Close button */}
|
sx={{
|
||||||
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
position: "absolute",
|
||||||
<IconButton size="small" onClick={() => onClose({}, "backdropClick")}>
|
top: "5vh",
|
||||||
<CloseIcon fontSize="small" />
|
left: "50%",
|
||||||
</IconButton>
|
transform: "translate(-50%, -50%)",
|
||||||
</Box>
|
width: "50vw",
|
||||||
|
maxHeight: "80vh",
|
||||||
<CardHeader title={isOwn ? "Edit Event" : "Event Details"} />
|
}}
|
||||||
|
>
|
||||||
<CardContent sx={{ overflow: "auto" }}>
|
<Card sx={{ p: 2, position: "absolute" }}>
|
||||||
{/* Title */}
|
{/* Close button */}
|
||||||
<TextField
|
<Box sx={{ position: "absolute", top: 8, right: 8 }}>
|
||||||
fullWidth
|
<IconButton
|
||||||
disabled={!isOwn}
|
size="small"
|
||||||
label="Title"
|
onClick={() => onClose({}, "backdropClick")}
|
||||||
value={title}
|
|
||||||
onChange={(e) => setTitle(e.target.value)}
|
|
||||||
size="small"
|
|
||||||
margin="dense"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* RSVP */}
|
|
||||||
{currentUserAttendee && isOwnCal && (
|
|
||||||
<Card sx={{ my: 1 }}>
|
|
||||||
<ButtonGroup size="small" fullWidth>
|
|
||||||
<Button
|
|
||||||
color={
|
|
||||||
currentUserAttendee.partstat === "ACCEPTED"
|
|
||||||
? "success"
|
|
||||||
: "primary"
|
|
||||||
}
|
|
||||||
onClick={() => handleRSVP("ACCEPTED")}
|
|
||||||
>
|
|
||||||
Accept
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color={
|
|
||||||
currentUserAttendee.partstat === "TENTATIVE"
|
|
||||||
? "warning"
|
|
||||||
: "primary"
|
|
||||||
}
|
|
||||||
onClick={() => handleRSVP("TENTATIVE")}
|
|
||||||
>
|
|
||||||
Maybe
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color={
|
|
||||||
currentUserAttendee.partstat === "DECLINED"
|
|
||||||
? "error"
|
|
||||||
: "primary"
|
|
||||||
}
|
|
||||||
onClick={() => handleRSVP("DECLINED")}
|
|
||||||
>
|
|
||||||
Decline
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
onClick={() => console.log("proposenewtime")}
|
|
||||||
>
|
|
||||||
Propose new time
|
|
||||||
</Button>
|
|
||||||
</ButtonGroup>
|
|
||||||
</Card>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Calendar selector */}
|
|
||||||
<FormControl fullWidth margin="dense" size="small">
|
|
||||||
<InputLabel id="calendar-select-label">Calendar</InputLabel>
|
|
||||||
<Select
|
|
||||||
disabled={!isOwn}
|
|
||||||
labelId="calendar-select-label"
|
|
||||||
value={calendarid.toString()}
|
|
||||||
label="Calendar"
|
|
||||||
onChange={(e: SelectChangeEvent) => {
|
|
||||||
const newId = Number(e.target.value);
|
|
||||||
setCalendarid(newId);
|
|
||||||
setNewCalId(userPersonnalCalendars[newId].id);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{calList}
|
<CloseIcon fontSize="small" />
|
||||||
</Select>
|
</IconButton>
|
||||||
</FormControl>
|
</Box>
|
||||||
|
|
||||||
{/* Dates */}
|
<CardHeader title={isOwn ? "Edit Event" : "Event Details"} />
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label="Start"
|
|
||||||
disabled={!isOwn}
|
|
||||||
type={allday ? "date" : "datetime-local"}
|
|
||||||
value={allday ? start.split("T")[0] : start.slice(0, 16)}
|
|
||||||
onChange={(e) =>
|
|
||||||
setStart(formatLocalDateTime(new Date(e.target.value)))
|
|
||||||
}
|
|
||||||
size="small"
|
|
||||||
margin="dense"
|
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<TextField
|
<CardContent sx={{ maxHeight: "75vh", overflow: "auto" }}>
|
||||||
fullWidth
|
{/* Title */}
|
||||||
disabled={!isOwn}
|
<TextField
|
||||||
label="End"
|
fullWidth
|
||||||
type={allday ? "date" : "datetime-local"}
|
disabled={!isOwn}
|
||||||
value={allday ? end.split("T")[0] : end.slice(0, 16)}
|
label="Title"
|
||||||
onChange={(e) =>
|
value={title}
|
||||||
setEnd(formatLocalDateTime(new Date(e.target.value)))
|
onChange={(e) => setTitle(e.target.value)}
|
||||||
}
|
size="small"
|
||||||
size="small"
|
margin="dense"
|
||||||
margin="dense"
|
/>
|
||||||
InputLabelProps={{ shrink: true }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormControlLabel
|
{/* RSVP */}
|
||||||
control={
|
{currentUserAttendee && isOwnCal && (
|
||||||
<Checkbox
|
<Card sx={{ my: 1 }}>
|
||||||
|
<ButtonGroup size="small" fullWidth>
|
||||||
|
<Button
|
||||||
|
color={
|
||||||
|
currentUserAttendee.partstat === "ACCEPTED"
|
||||||
|
? "success"
|
||||||
|
: "primary"
|
||||||
|
}
|
||||||
|
onClick={() => handleRSVP("ACCEPTED")}
|
||||||
|
>
|
||||||
|
Accept
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={
|
||||||
|
currentUserAttendee.partstat === "TENTATIVE"
|
||||||
|
? "warning"
|
||||||
|
: "primary"
|
||||||
|
}
|
||||||
|
onClick={() => handleRSVP("TENTATIVE")}
|
||||||
|
>
|
||||||
|
Maybe
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color={
|
||||||
|
currentUserAttendee.partstat === "DECLINED"
|
||||||
|
? "error"
|
||||||
|
: "primary"
|
||||||
|
}
|
||||||
|
onClick={() => handleRSVP("DECLINED")}
|
||||||
|
>
|
||||||
|
Decline
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="primary"
|
||||||
|
onClick={() => console.log("proposenewtime")}
|
||||||
|
>
|
||||||
|
Propose new time
|
||||||
|
</Button>
|
||||||
|
</ButtonGroup>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Calendar selector */}
|
||||||
|
<FormControl fullWidth margin="dense" size="small">
|
||||||
|
<InputLabel id="calendar-select-label">Calendar</InputLabel>
|
||||||
|
<Select
|
||||||
disabled={!isOwn}
|
disabled={!isOwn}
|
||||||
checked={allday}
|
labelId="calendar-select-label"
|
||||||
onChange={() => {
|
value={calendarid.toString()}
|
||||||
const endDate = new Date(end);
|
label="Calendar"
|
||||||
const startDate = new Date(start);
|
onChange={(e: SelectChangeEvent) => {
|
||||||
setAllDay(!allday);
|
const newId = Number(e.target.value);
|
||||||
if (endDate.getDate() === startDate.getDate()) {
|
setCalendarid(newId);
|
||||||
endDate.setDate(startDate.getDate() + 1);
|
setNewCalId(userPersonnalCalendars[newId].id);
|
||||||
setEnd(formatLocalDateTime(endDate));
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
label="All day"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Description & Location */}
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
disabled={!isOwn}
|
|
||||||
label="Description"
|
|
||||||
value={description}
|
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
|
||||||
size="small"
|
|
||||||
margin="dense"
|
|
||||||
multiline
|
|
||||||
rows={2}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{isOwn && (
|
|
||||||
<AttendeeSelector
|
|
||||||
attendees={attendees}
|
|
||||||
setAttendees={(value: userAttendee[]) => {
|
|
||||||
const newAttendeeList = attendees.concat(value);
|
|
||||||
setAttendees(newAttendeeList);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextField
|
|
||||||
fullWidth
|
|
||||||
label="Location"
|
|
||||||
disabled={!isOwn}
|
|
||||||
value={location}
|
|
||||||
onChange={(e) => setLocation(e.target.value)}
|
|
||||||
size="small"
|
|
||||||
margin="dense"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Video */}
|
|
||||||
{event.x_openpass_videoconference && (
|
|
||||||
<InfoRow
|
|
||||||
icon={<VideocamIcon sx={{ fontSize: 18 }} />}
|
|
||||||
text="Video conference available"
|
|
||||||
data={event.x_openpass_videoconference}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Attendees */}
|
|
||||||
{event.attendee?.length > 0 && (
|
|
||||||
<Box sx={{ mb: 1 }}>
|
|
||||||
<Typography variant="subtitle2">Attendees:</Typography>
|
|
||||||
{organizer.cal_address &&
|
|
||||||
renderAttendeeBadge(organizer, "org", true)}
|
|
||||||
{(showAllAttendees
|
|
||||||
? attendees
|
|
||||||
: attendees.slice(0, attendeeDisplayLimit)
|
|
||||||
).map((a, idx) => (
|
|
||||||
<Box key={a.cal_address}>
|
|
||||||
{renderAttendeeBadge(a, idx.toString())}
|
|
||||||
{isOwn && (
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
const newAttendeesList = [...attendees];
|
|
||||||
newAttendeesList.splice(idx, 1);
|
|
||||||
setAttendees(newAttendeesList);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CloseIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
))}
|
|
||||||
{attendees.length > attendeeDisplayLimit && (
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
color="primary"
|
|
||||||
sx={{ cursor: "pointer", mt: 0.5 }}
|
|
||||||
onClick={() => setShowAllAttendees(!showAllAttendees)}
|
|
||||||
>
|
|
||||||
{showAllAttendees
|
|
||||||
? "Show less"
|
|
||||||
: `Show more (${
|
|
||||||
attendees.length - attendeeDisplayLimit
|
|
||||||
} more)`}
|
|
||||||
</Typography>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Divider sx={{ my: 1 }} />
|
|
||||||
|
|
||||||
{/* Extended options */}
|
|
||||||
{showMore && (
|
|
||||||
<>
|
|
||||||
<RepeatEvent
|
|
||||||
repetition={repetition}
|
|
||||||
setRepetition={setRepetition}
|
|
||||||
isOwn={isOwn}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormControl fullWidth margin="dense" size="small">
|
|
||||||
<InputLabel id="alarm">Alarm</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="alarm"
|
|
||||||
value={alarm}
|
|
||||||
disabled={!isOwn}
|
|
||||||
onChange={(e: SelectChangeEvent) => setAlarm(e.target.value)}
|
|
||||||
>
|
|
||||||
<MenuItem value={""}>No Alarm</MenuItem>
|
|
||||||
<MenuItem value={"-PT1M"}>1 minute</MenuItem>
|
|
||||||
<MenuItem value={"-PT5M"}>2 minutes</MenuItem>
|
|
||||||
<MenuItem value={"-PT10M"}>10 minutes</MenuItem>
|
|
||||||
<MenuItem value={"-PT15M"}>15 minutes</MenuItem>
|
|
||||||
<MenuItem value={"-PT30M"}>30 minutes</MenuItem>
|
|
||||||
<MenuItem value={"-PT1H"}>1 hours</MenuItem>
|
|
||||||
<MenuItem value={"-PT2H"}>2 hours</MenuItem>
|
|
||||||
<MenuItem value={"-PT5H"}>5 hours</MenuItem>
|
|
||||||
<MenuItem value={"-PT12H"}>12 hours</MenuItem>
|
|
||||||
<MenuItem value={"-PT1D"}>1 day</MenuItem>
|
|
||||||
<MenuItem value={"-PT2D"}>2 days</MenuItem>
|
|
||||||
<MenuItem value={"-PT1W"}>1 week</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
|
|
||||||
<FormControl fullWidth margin="dense" size="small">
|
|
||||||
<InputLabel id="Visibility">Visibility</InputLabel>
|
|
||||||
<Select
|
|
||||||
labelId="Visibility"
|
|
||||||
label="Visibility"
|
|
||||||
value={eventClass}
|
|
||||||
disabled={!isOwn}
|
|
||||||
onChange={(e: SelectChangeEvent) =>
|
|
||||||
setEventClass(e.target.value)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MenuItem value={"PUBLIC"}>Public</MenuItem>
|
|
||||||
<MenuItem value={"CONFIDENTIAL"}>Show time only</MenuItem>
|
|
||||||
<MenuItem value={"PRIVATE"}>Private</MenuItem>
|
|
||||||
</Select>
|
|
||||||
</FormControl>
|
|
||||||
{/* Error */}
|
|
||||||
{event.error && (
|
|
||||||
<InfoRow
|
|
||||||
icon={
|
|
||||||
<ErrorOutlineIcon color="error" sx={{ fontSize: 18 }} />
|
|
||||||
}
|
|
||||||
text={event.error}
|
|
||||||
error
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
|
|
||||||
<CardActions>
|
|
||||||
<ButtonGroup>
|
|
||||||
{isOwn && (
|
|
||||||
<IconButton
|
|
||||||
size="small"
|
|
||||||
onClick={() => {
|
|
||||||
onClose({}, "backdropClick");
|
|
||||||
dispatch(
|
|
||||||
deleteEventAsync({ calId, eventId, eventURL: event.URL })
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DeleteIcon fontSize="small" />
|
{calList}
|
||||||
</IconButton>
|
</Select>
|
||||||
)}
|
</FormControl>
|
||||||
<Button size="small" onClick={handleToggleShowMore}>
|
|
||||||
{showMore ? "Show Less" : "Show More"}
|
{/* Dates */}
|
||||||
</Button>
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
label="Start"
|
||||||
|
disabled={!isOwn}
|
||||||
|
type={allday ? "date" : "datetime-local"}
|
||||||
|
value={allday ? start.split("T")[0] : start.slice(0, 16)}
|
||||||
|
onChange={(e) =>
|
||||||
|
setStart(formatLocalDateTime(new Date(e.target.value)))
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
margin="dense"
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
disabled={!isOwn}
|
||||||
|
label="End"
|
||||||
|
type={allday ? "date" : "datetime-local"}
|
||||||
|
value={allday ? end.split("T")[0] : end.slice(0, 16)}
|
||||||
|
onChange={(e) =>
|
||||||
|
setEnd(formatLocalDateTime(new Date(e.target.value)))
|
||||||
|
}
|
||||||
|
size="small"
|
||||||
|
margin="dense"
|
||||||
|
InputLabelProps={{ shrink: true }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControlLabel
|
||||||
|
control={
|
||||||
|
<Checkbox
|
||||||
|
disabled={!isOwn}
|
||||||
|
checked={allday}
|
||||||
|
onChange={() => {
|
||||||
|
const endDate = new Date(end);
|
||||||
|
const startDate = new Date(start);
|
||||||
|
setAllDay(!allday);
|
||||||
|
if (endDate.getDate() === startDate.getDate()) {
|
||||||
|
endDate.setDate(startDate.getDate() + 1);
|
||||||
|
setEnd(formatLocalDateTime(endDate));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
label="All day"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Description & Location */}
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
disabled={!isOwn}
|
||||||
|
label="Description"
|
||||||
|
value={description}
|
||||||
|
onChange={(e) => setDescription(e.target.value)}
|
||||||
|
size="small"
|
||||||
|
margin="dense"
|
||||||
|
multiline
|
||||||
|
rows={2}
|
||||||
|
/>
|
||||||
|
|
||||||
{isOwn && (
|
{isOwn && (
|
||||||
<Button size="small" onClick={handleSave}>
|
<AttendeeSelector
|
||||||
Save
|
attendees={attendees}
|
||||||
</Button>
|
setAttendees={(value: userAttendee[]) => {
|
||||||
|
const newAttendeeList = attendees.concat(value);
|
||||||
|
setAttendees(newAttendeeList);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</ButtonGroup>
|
|
||||||
</CardActions>
|
<TextField
|
||||||
</Card>
|
fullWidth
|
||||||
|
label="Location"
|
||||||
|
disabled={!isOwn}
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
size="small"
|
||||||
|
margin="dense"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Video */}
|
||||||
|
{event.x_openpass_videoconference && (
|
||||||
|
<InfoRow
|
||||||
|
icon={<VideocamIcon sx={{ fontSize: 18 }} />}
|
||||||
|
text="Video conference available"
|
||||||
|
data={event.x_openpass_videoconference}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Attendees */}
|
||||||
|
{event.attendee?.length > 0 && (
|
||||||
|
<Box sx={{ mb: 1 }}>
|
||||||
|
<Typography variant="subtitle2">Attendees:</Typography>
|
||||||
|
{organizer.cal_address &&
|
||||||
|
renderAttendeeBadge(organizer, "org", true)}
|
||||||
|
{(showAllAttendees
|
||||||
|
? attendees
|
||||||
|
: attendees.slice(0, attendeeDisplayLimit)
|
||||||
|
).map((a, idx) => (
|
||||||
|
<Box key={a.cal_address}>
|
||||||
|
{renderAttendeeBadge(a, idx.toString())}
|
||||||
|
{isOwn && (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
const newAttendeesList = [...attendees];
|
||||||
|
newAttendeesList.splice(idx, 1);
|
||||||
|
setAttendees(newAttendeesList);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
{attendees.length > attendeeDisplayLimit && (
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
color="primary"
|
||||||
|
sx={{ cursor: "pointer", mt: 0.5 }}
|
||||||
|
onClick={() => setShowAllAttendees(!showAllAttendees)}
|
||||||
|
>
|
||||||
|
{showAllAttendees
|
||||||
|
? "Show less"
|
||||||
|
: `Show more (${
|
||||||
|
attendees.length - attendeeDisplayLimit
|
||||||
|
} more)`}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Divider sx={{ my: 1 }} />
|
||||||
|
|
||||||
|
{/* Extended options */}
|
||||||
|
{showMore && (
|
||||||
|
<>
|
||||||
|
<RepeatEvent
|
||||||
|
repetition={repetition}
|
||||||
|
eventStart={event.start}
|
||||||
|
setRepetition={setRepetition}
|
||||||
|
isOwn={isOwn}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="dense" size="small">
|
||||||
|
<InputLabel id="alarm">Alarm</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="alarm"
|
||||||
|
value={alarm}
|
||||||
|
disabled={!isOwn}
|
||||||
|
onChange={(e: SelectChangeEvent) =>
|
||||||
|
setAlarm(e.target.value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuItem value={""}>No Alarm</MenuItem>
|
||||||
|
<MenuItem value={"-PT1M"}>1 minute</MenuItem>
|
||||||
|
<MenuItem value={"-PT5M"}>2 minutes</MenuItem>
|
||||||
|
<MenuItem value={"-PT10M"}>10 minutes</MenuItem>
|
||||||
|
<MenuItem value={"-PT15M"}>15 minutes</MenuItem>
|
||||||
|
<MenuItem value={"-PT30M"}>30 minutes</MenuItem>
|
||||||
|
<MenuItem value={"-PT1H"}>1 hours</MenuItem>
|
||||||
|
<MenuItem value={"-PT2H"}>2 hours</MenuItem>
|
||||||
|
<MenuItem value={"-PT5H"}>5 hours</MenuItem>
|
||||||
|
<MenuItem value={"-PT12H"}>12 hours</MenuItem>
|
||||||
|
<MenuItem value={"-PT1D"}>1 day</MenuItem>
|
||||||
|
<MenuItem value={"-PT2D"}>2 days</MenuItem>
|
||||||
|
<MenuItem value={"-PT1W"}>1 week</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormControl fullWidth margin="dense" size="small">
|
||||||
|
<InputLabel id="Visibility">Visibility</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="Visibility"
|
||||||
|
label="Visibility"
|
||||||
|
value={eventClass}
|
||||||
|
disabled={!isOwn}
|
||||||
|
onChange={(e: SelectChangeEvent) =>
|
||||||
|
setEventClass(e.target.value)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<MenuItem value={"PUBLIC"}>Public</MenuItem>
|
||||||
|
<MenuItem value={"CONFIDENTIAL"}>Show time only</MenuItem>
|
||||||
|
<MenuItem value={"PRIVATE"}>Private</MenuItem>
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
{/* Error */}
|
||||||
|
{event.error && (
|
||||||
|
<InfoRow
|
||||||
|
icon={
|
||||||
|
<ErrorOutlineIcon color="error" sx={{ fontSize: 18 }} />
|
||||||
|
}
|
||||||
|
text={event.error}
|
||||||
|
error
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
|
||||||
|
<CardActions>
|
||||||
|
<ButtonGroup>
|
||||||
|
{isOwn && (
|
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
onClose({}, "backdropClick");
|
||||||
|
dispatch(
|
||||||
|
deleteEventAsync({ calId, eventId, eventURL: event.URL })
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DeleteIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
<Button size="small" onClick={handleToggleShowMore}>
|
||||||
|
{showMore ? "Show Less" : "Show More"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{isOwn && (
|
||||||
|
<Button size="small" onClick={handleSave}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</ButtonGroup>
|
||||||
|
</CardActions>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export default function EventPreviewModal({
|
|||||||
size="small"
|
size="small"
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setOpenFullDisplay(!openFullDisplay);
|
setOpenFullDisplay(!openFullDisplay);
|
||||||
|
await dispatch(getEventAsync(event));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<EditIcon fontSize="small" />
|
<EditIcon fontSize="small" />
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ function EventPopover({
|
|||||||
<>
|
<>
|
||||||
<RepeatEvent
|
<RepeatEvent
|
||||||
repetition={repetition}
|
repetition={repetition}
|
||||||
|
eventStart={selectedRange?.start ?? new Date()}
|
||||||
setRepetition={setRepetition}
|
setRepetition={setRepetition}
|
||||||
/>
|
/>
|
||||||
<FormControl fullWidth margin="dense" size="small">
|
<FormControl fullWidth margin="dense" size="small">
|
||||||
|
|||||||
@@ -88,10 +88,10 @@ export function parseCalendarEvent(
|
|||||||
event.repetition.selectedDays = value.byday;
|
event.repetition.selectedDays = value.byday;
|
||||||
}
|
}
|
||||||
if (value.until) {
|
if (value.until) {
|
||||||
event.repetition.selectedDays = value.endDate;
|
event.repetition.endDate = value.until;
|
||||||
}
|
}
|
||||||
if (value.count) {
|
if (value.count) {
|
||||||
event.repetition.selectedDays = value.occurrences;
|
event.repetition.occurrences = value.count;
|
||||||
}
|
}
|
||||||
if (value.interval) {
|
if (value.interval) {
|
||||||
event.repetition.interval = value.interval;
|
event.repetition.interval = value.interval;
|
||||||
|
|||||||
Reference in New Issue
Block a user