[1] added state to add event to calendars
This commit is contained in:
Generated
-3143
File diff suppressed because it is too large
Load Diff
@@ -14,9 +14,6 @@
|
||||
"@mui/icons-material": "^7.1.2",
|
||||
"@mui/material": "^7.1.2",
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@schedule-x/events-service": "^2.34.0",
|
||||
"@schedule-x/react": "^2.34.0",
|
||||
"@schedule-x/theme-default": "^2.34.0",
|
||||
"@types/node": "^16.18.126",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
declare namespace Intl {
|
||||
type Key = 'calendar' | 'collation' | 'currency' | 'numberingSystem' | 'timeZone' | 'unit';
|
||||
|
||||
function supportedValuesOf(input: Key): string[];
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { combineReducers, configureStore } from "@reduxjs/toolkit";
|
||||
import userReducer from "../features/User/userSlice";
|
||||
import eventsReducer from "../features/Events/EventsSlice";
|
||||
import eventsCalendar from "../features/Calendars/CalendarSlice";
|
||||
import { createReduxHistoryContext } from "redux-first-history";
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
@@ -11,6 +12,7 @@ const rootReducer = combineReducers({
|
||||
router: routerReducer,
|
||||
user: userReducer,
|
||||
events: eventsReducer,
|
||||
calendars: eventsCalendar,
|
||||
});
|
||||
|
||||
export const setupStore = (preloadedState?: Partial<RootState>) => {
|
||||
|
||||
@@ -8,14 +8,16 @@ import ReactCalendar from "react-calendar";
|
||||
import "./Calendar.css";
|
||||
import { useRef, useState } from "react";
|
||||
import { useAppSelector } from "../../app/hooks";
|
||||
import EventPopover from "../../features/Events/EventModal";
|
||||
import CalendarPopover from "../../features/Calendars/CalendarModal";
|
||||
|
||||
export default function CalendarApp() {
|
||||
const calendarRef = useRef<CalendarApi | null>(null);
|
||||
const [selectedDate, setSelectedDate] = useState(new Date());
|
||||
const [selectedCalendars, setSelectedCalendars] = useState([
|
||||
"Work",
|
||||
"Personnal",
|
||||
]);
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const [selectedCalendars, setSelectedCalendars] = useState(
|
||||
Object.keys(calendars).map((id) => calendars[id].name)
|
||||
);
|
||||
const events = useAppSelector((state) => state.events);
|
||||
|
||||
const handleCalendarToggle = (name: string) => {
|
||||
@@ -23,9 +25,6 @@ export default function CalendarApp() {
|
||||
prev.includes(name) ? prev.filter((n) => n !== name) : [...prev, name]
|
||||
);
|
||||
};
|
||||
const filteredEvent = events.filter((e) =>
|
||||
selectedCalendars.includes(e.calendar)
|
||||
);
|
||||
|
||||
const [date, setDate] = useState(new Date());
|
||||
|
||||
@@ -44,6 +43,22 @@ export default function CalendarApp() {
|
||||
"December",
|
||||
];
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const [anchorElCal, setAnchorElCal] = useState<HTMLElement | null>(null);
|
||||
const [selectedRange, setSelectedRange] = useState<DateSelectArg | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const handleDateSelect = (selectInfo: DateSelectArg) => {
|
||||
setSelectedRange(selectInfo);
|
||||
setAnchorEl(document.body); // fallback: we could use selectInfo.jsEvent.target if from a click
|
||||
};
|
||||
|
||||
const handleClosePopover = () => {
|
||||
setAnchorEl(null);
|
||||
setSelectedRange(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<main>
|
||||
<div className="sidebar">
|
||||
@@ -83,18 +98,20 @@ export default function CalendarApp() {
|
||||
nextLabel={null}
|
||||
showNavigation={false}
|
||||
/>
|
||||
{["Work", "Personnal", "Holidays"].map((name) => (
|
||||
<div key={name}>
|
||||
{Object.keys(calendars).map((id) => (
|
||||
<div key={id}>
|
||||
<label>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedCalendars.includes(name)}
|
||||
onChange={() => handleCalendarToggle(name)}
|
||||
style={{backgroundColor:calendars[id].color}}
|
||||
checked={selectedCalendars.includes(calendars[id].name)}
|
||||
onChange={() => handleCalendarToggle(calendars[id].name)}
|
||||
/>
|
||||
{name}
|
||||
{calendars[id].name}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
<button onClick={() => setAnchorElCal(document.body)}>+</button>
|
||||
</div>
|
||||
<div className="calendar">
|
||||
<FullCalendar
|
||||
@@ -108,13 +125,14 @@ export default function CalendarApp() {
|
||||
weekends={false}
|
||||
editable={true}
|
||||
selectable={true}
|
||||
select={handleDateSelect}
|
||||
nowIndicator
|
||||
selectMirror={true}
|
||||
views={{
|
||||
timeGridWeek: { titleFormat: { month: "long", year: "numeric" } },
|
||||
}}
|
||||
dayMaxEvents={true}
|
||||
events={filteredEvent}
|
||||
events={events}
|
||||
weekNumbers
|
||||
weekNumberFormat={{ week: "long" }}
|
||||
slotDuration={"00:30:00"}
|
||||
@@ -159,6 +177,17 @@ export default function CalendarApp() {
|
||||
right: "dayGridMonth,timeGridWeek,timeGridDay",
|
||||
}}
|
||||
/>
|
||||
<EventPopover
|
||||
anchorEl={anchorEl}
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={handleClosePopover}
|
||||
selectedRange={selectedRange}
|
||||
/>
|
||||
<CalendarPopover
|
||||
anchorEl={anchorElCal}
|
||||
open={Boolean(anchorElCal)}
|
||||
onClose={() => setAnchorElCal(null)}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export default async function getCalendars() {
|
||||
return {}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { useState } from "react";
|
||||
import { createCalendar } from "./CalendarSlice";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import {
|
||||
Popover,
|
||||
TextField,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
Select,
|
||||
ButtonGroup,
|
||||
} from "@mui/material";
|
||||
|
||||
function CalendarPopover({
|
||||
anchorEl,
|
||||
open,
|
||||
onClose,
|
||||
}: {
|
||||
anchorEl: HTMLElement | null;
|
||||
open: boolean;
|
||||
onClose: (Calendar: {}, reason: "backdropClick" | "escapeKeyDown") => void;
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [color, setColor] = useState("");
|
||||
const [timeZone, setTimeZone] = useState("");
|
||||
const timezones = Intl.supportedValuesOf?.("timeZone") || [];
|
||||
const handleSave = () => {
|
||||
|
||||
dispatch(createCalendar({ name, description, color }));
|
||||
onClose({}, "backdropClick");
|
||||
|
||||
// Reset
|
||||
setName("");
|
||||
setDescription("");
|
||||
};
|
||||
const palette = [
|
||||
"#D50000",
|
||||
"#E67C73",
|
||||
"#F4511E",
|
||||
"#F6BF26",
|
||||
"#33B679",
|
||||
"#0B8043",
|
||||
"#039BE5",
|
||||
"#3F51B5",
|
||||
"#7986CB",
|
||||
"#8E24AA",
|
||||
"#616161",
|
||||
];
|
||||
return (
|
||||
<Popover
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
onClose={onClose}
|
||||
anchorOrigin={{
|
||||
vertical: "center",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "center",
|
||||
horizontal: "center",
|
||||
}}
|
||||
>
|
||||
<Box p={2}>
|
||||
<Typography variant="h6" gutterBottom style={{backgroundColor:color}}>
|
||||
Create a Calendar
|
||||
</Typography>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
/>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Description"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
multiline
|
||||
rows={2}
|
||||
/>
|
||||
<ButtonGroup>
|
||||
{palette.map((color) => (
|
||||
<Button
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => setColor(color)}
|
||||
/>
|
||||
))}
|
||||
</ButtonGroup>
|
||||
<Select
|
||||
fullWidth
|
||||
label="timeZone"
|
||||
value={timeZone}
|
||||
onChange={(e) => setTimeZone(e.target.value)}
|
||||
>
|
||||
{timezones.map((zone: string) => (
|
||||
<div>{zone}</div>
|
||||
))}
|
||||
</Select>
|
||||
<Box mt={2} display="flex" justifyContent="flex-end" gap={1}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => onClose({}, "backdropClick")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="contained" onClick={handleSave}>
|
||||
Save
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
|
||||
export default CalendarPopover;
|
||||
@@ -0,0 +1,39 @@
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { Calendars } from "./CalendarTypes";
|
||||
import { CalendarEvent } from "../Events/EventsTypes";
|
||||
|
||||
const CalendarSlice = createSlice({
|
||||
name: "calendars",
|
||||
initialState: {} as Record<string, Calendars>,
|
||||
reducers: {
|
||||
createCalendar: (state, action: PayloadAction<Record<string, string>>) => {
|
||||
const id = Date.now().toString(36);
|
||||
state[id] = {} as Calendars;
|
||||
state[id].name = action.payload.name;
|
||||
state[id].color = action.payload.color;
|
||||
state[id].description = action.payload.description;
|
||||
state[id].events = [];
|
||||
},
|
||||
addEvent: (
|
||||
state,
|
||||
action: PayloadAction<{ calendarUid: string; event: CalendarEvent }>
|
||||
) => {
|
||||
state[action.payload.calendarUid].events.push(action.payload.event);
|
||||
},
|
||||
removeEvent: (
|
||||
state,
|
||||
action: PayloadAction<{ calendarUid: string; eventUid: string }>
|
||||
) => {
|
||||
state[action.payload.calendarUid as string].events = state[
|
||||
action.payload.calendarUid
|
||||
].events.filter((event) => {
|
||||
if (event.uid !== action.payload.calendarUid) {
|
||||
return event;
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { addEvent, removeEvent, createCalendar } = CalendarSlice.actions;
|
||||
export default CalendarSlice.reducer;
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CalendarEvent } from "../Events/EventsTypes";
|
||||
|
||||
export interface Calendars {
|
||||
id: string;
|
||||
name: string;
|
||||
prodid: string;
|
||||
color: string;
|
||||
description: string;
|
||||
calscale?: string;
|
||||
version?: string;
|
||||
events: CalendarEvent[];
|
||||
}
|
||||
@@ -1,10 +1,17 @@
|
||||
// components/EventModal.tsx
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { addEvent } from "./EventsSlice";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
import { DateSelectArg } from "@fullcalendar/core";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { Popover, TextField, Button, Box, Typography } from "@mui/material";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import {
|
||||
Popover,
|
||||
TextField,
|
||||
Button,
|
||||
Box,
|
||||
Typography,
|
||||
Select,
|
||||
MenuItem,
|
||||
} from "@mui/material";
|
||||
|
||||
function EventPopover({
|
||||
anchorEl,
|
||||
@@ -19,12 +26,14 @@ function EventPopover({
|
||||
}) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [title, setTitle] = useState("");
|
||||
const organizer = useAppSelector((state) => state.user.organiserData);
|
||||
const calendars = useAppSelector((state) => state.calendars);
|
||||
const [summary, setSummary] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [location, setLocation] = useState("");
|
||||
const [calendar, setCalendar] = useState("");
|
||||
const [start, setStart] = useState("");
|
||||
const [end, setEnd] = useState("");
|
||||
const [calendarid, setCalendarid] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedRange) {
|
||||
@@ -35,21 +44,31 @@ function EventPopover({
|
||||
|
||||
const handleSave = () => {
|
||||
const newEvent: CalendarEvent = {
|
||||
title,
|
||||
start,
|
||||
end,
|
||||
calendar,
|
||||
extendedProps: {
|
||||
description,
|
||||
location,
|
||||
},
|
||||
summary,
|
||||
start: new Date(start),
|
||||
end: new Date(end),
|
||||
uid: Date.now().toString(36),
|
||||
description,
|
||||
location,
|
||||
organizer,
|
||||
attendee: [
|
||||
{
|
||||
cn: organizer.cn,
|
||||
cal_address: organizer.cal_address,
|
||||
partstat: "ACCEPTED",
|
||||
rsvp: "FALSE",
|
||||
role: "CHAIR",
|
||||
cutype: "INDIVIDUAL",
|
||||
},
|
||||
],
|
||||
transp: "OPAQUE",
|
||||
};
|
||||
dispatch(addEvent(newEvent));
|
||||
console.log(newEvent)
|
||||
console.log(newEvent);
|
||||
onClose({}, "backdropClick");
|
||||
|
||||
// Reset
|
||||
setTitle("");
|
||||
setSummary("");
|
||||
setDescription("");
|
||||
setLocation("");
|
||||
};
|
||||
@@ -72,11 +91,16 @@ function EventPopover({
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Create Event
|
||||
</Typography>
|
||||
<Select onClick={(e) => console.log(e.target)}>
|
||||
{Object.keys(calendars).map((calendar) => (
|
||||
<MenuItem value={calendar}>{calendars[calendar].name}</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
<TextField
|
||||
fullWidth
|
||||
label="Title"
|
||||
value={title}
|
||||
onChange={(e) => setTitle(e.target.value)}
|
||||
label="summary"
|
||||
value={summary}
|
||||
onChange={(e) => setSummary(e.target.value)}
|
||||
size="small"
|
||||
margin="dense"
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
// store/eventsSlice.ts
|
||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
import { CalendarEvent } from "./EventsTypes";
|
||||
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
// types/Event.ts
|
||||
import { userAttendee, userOrganiser } from "../User/userDataTypes";
|
||||
|
||||
export interface CalendarEvent {
|
||||
title: string;
|
||||
start: string; // ISO date
|
||||
end?: string;
|
||||
calendar:string;
|
||||
extendedProps?: {
|
||||
description?: string;
|
||||
location?: string;
|
||||
};
|
||||
uid: string;
|
||||
transp: string;
|
||||
start: Date; // ISO date
|
||||
end?: Date;
|
||||
class?: string;
|
||||
x_openpass_videoconference?: unknown;
|
||||
summary?: string;
|
||||
description?: string;
|
||||
location?: string;
|
||||
organizer: userOrganiser;
|
||||
attendee: userAttendee[];
|
||||
stamp?: Date;
|
||||
sequence?: Number;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Callback } from "./oidcAuth";
|
||||
import { useAppDispatch, useAppSelector } from "../../app/hooks";
|
||||
import { useAppDispatch } from "../../app/hooks";
|
||||
import { push } from "redux-first-history";
|
||||
import { setUserData } from "./userSlice";
|
||||
import { Loading } from "../../components/Loading/Loading";
|
||||
@@ -12,9 +11,6 @@ export function CallbackResume() {
|
||||
const saved = sessionStorage.getItem("redirectState")
|
||||
? JSON.parse(sessionStorage.getItem("redirectState")!)
|
||||
: null;
|
||||
const [tokens, setTokens] = useState<any>();
|
||||
const [userInfo, setUserInfo] = useState<any>();
|
||||
const location = useAppSelector((state) => state.router.location);
|
||||
useEffect(() => {
|
||||
if (hasRun.current) {
|
||||
return;
|
||||
@@ -24,8 +20,6 @@ export function CallbackResume() {
|
||||
try {
|
||||
const data = await Callback(saved?.code_verifier, saved?.state);
|
||||
console.log("data:", data);
|
||||
setTokens(data?.tokenSet);
|
||||
setUserInfo(data?.userinfo);
|
||||
dispatch(setUserData(data?.userinfo));
|
||||
sessionStorage.removeItem("redirectState");
|
||||
// Redirect to main page after successful callback
|
||||
|
||||
@@ -22,7 +22,6 @@ export async function getClientConfig() {
|
||||
}
|
||||
|
||||
export async function Auth() {
|
||||
console.log("bap");
|
||||
let code_verifier = client.randomPKCECodeVerifier();
|
||||
let code_challenge = await client.calculatePKCECodeChallenge(code_verifier);
|
||||
const openIdClientConfig = await getClientConfig();
|
||||
|
||||
@@ -3,3 +3,17 @@ export interface userData {
|
||||
sid: string;
|
||||
sub: string;
|
||||
}
|
||||
|
||||
export interface userOrganiser {
|
||||
cn: string;
|
||||
cal_address: string;
|
||||
}
|
||||
|
||||
export interface userAttendee {
|
||||
cn: string;
|
||||
cal_address: string;
|
||||
partstat: string;
|
||||
rsvp: string;
|
||||
role: string;
|
||||
cutype: string;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { createSlice } from "@reduxjs/toolkit";
|
||||
import { userData } from "./userDataTypes";
|
||||
import { userData, userOrganiser } from "./userDataTypes";
|
||||
|
||||
export const userSlice = createSlice({
|
||||
name: "user",
|
||||
initialState: {
|
||||
userData: null as unknown as userData,
|
||||
organiserData: null as unknown as userOrganiser,
|
||||
},
|
||||
reducers: {
|
||||
setUserData: (state, action) => {
|
||||
state.userData = action.payload;
|
||||
if (!state.organiserData) {
|
||||
state.organiserData = {} as userOrganiser;
|
||||
}
|
||||
state.organiserData.cn = action.payload.sub;
|
||||
state.organiserData.cal_address = `mailto:${action.payload.email}`;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user