Merge pull request #43 from CPSC319-Winter-term-2/mbalsdon-calendar-upgrade
Add functionality to calendar
This commit is contained in:
commit
03803ea711
@ -74,6 +74,86 @@ const meetings = [
|
|||||||
joinUrl: "",
|
joinUrl: "",
|
||||||
topic: "Back-end Meeting",
|
topic: "Back-end Meeting",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
meetingId: "3",
|
||||||
|
liveParticipantIds: [],
|
||||||
|
registrantIds: ["0", "1"],
|
||||||
|
start: "2022-03-10T07:27:27",
|
||||||
|
duration: 727,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "WHEN YOU",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "4",
|
||||||
|
liveParticipantIds: [],
|
||||||
|
registrantIds: ["0", "2", "3"],
|
||||||
|
start: "2022-03-10T12:30:00",
|
||||||
|
duration: 120,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "Bathroom Break",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "5",
|
||||||
|
liveParticipantIds: ["0"],
|
||||||
|
registrantIds: ["0"],
|
||||||
|
start: "2022-03-24T23:11:11",
|
||||||
|
duration: 11,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "Drink Coffee",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "6",
|
||||||
|
liveParticipantIds: ["0", "1"],
|
||||||
|
registrantIds: ["0", "1", "2", "3", "4", "5"],
|
||||||
|
start: "2022-03-25T09:00:00",
|
||||||
|
duration: 360,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "Get grilled by Jerry",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "7",
|
||||||
|
liveParticipantIds: [],
|
||||||
|
registrantIds: ["0", "5"],
|
||||||
|
start: "2022-03-25T15:00:00",
|
||||||
|
duration: 150,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "Get grilled by Arthur",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "8",
|
||||||
|
liveParticipantIds: ["2"],
|
||||||
|
registrantIds: ["0", "5", "2", "3"],
|
||||||
|
start: "2022-03-25T17:45:00",
|
||||||
|
duration: 60,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "Jerry comes back for round 2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "9",
|
||||||
|
liveParticipantIds: ["2", "5"],
|
||||||
|
registrantIds: ["0", "4"],
|
||||||
|
start: "2022-03-25T18:15:30",
|
||||||
|
duration: 75,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "Tag team!",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
meetingId: "10",
|
||||||
|
liveParticipantIds: [],
|
||||||
|
registrantIds: ["0", "1", "2", "3", "4", "5", "6"],
|
||||||
|
start: "2022-04-04T18:30:00",
|
||||||
|
duration: 90,
|
||||||
|
timezone: "",
|
||||||
|
joinUrl: "",
|
||||||
|
topic: "MVP Deadline",
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const team = {
|
const team = {
|
||||||
|
|||||||
@ -1,12 +1,154 @@
|
|||||||
import { Toolbar } from "@mui/material";
|
import { Calendar, momentLocalizer, Views } from "react-big-calendar";
|
||||||
import UserCalendar from "./UserCalendar";
|
import moment from "moment";
|
||||||
|
|
||||||
|
import "react-big-calendar/lib/css/react-big-calendar.css";
|
||||||
|
import {
|
||||||
|
Divider,
|
||||||
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
SelectChangeEvent,
|
||||||
|
Toolbar,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { selectMeetings } from "../../redux/slices/meetingsAndUserStatusSlice";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
|
||||||
|
import {
|
||||||
|
selectMe,
|
||||||
|
selectTeam,
|
||||||
|
selectUser,
|
||||||
|
selectUsers,
|
||||||
|
} from "../../redux/slices/usersSlice";
|
||||||
|
import { open } from "../../redux/slices/meetingDetailsOpenSlice";
|
||||||
|
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
||||||
|
import { useState } from "react";
|
||||||
|
import UserLite from "../../api-bodies/UserLite";
|
||||||
|
|
||||||
|
// TODO: match the styles and themes
|
||||||
|
|
||||||
|
// A superset of DetailedMeeting; contains fields required for the calendar to work
|
||||||
|
interface CalendarEvent {
|
||||||
|
meetingId: string;
|
||||||
|
liveParticipantIds: string[];
|
||||||
|
registrantIds: string[];
|
||||||
|
startIso: string; // names overlap here; this is equivalent to "start" in DetailedMeeting
|
||||||
|
duration: number;
|
||||||
|
timezone: string;
|
||||||
|
joinUrl: string;
|
||||||
|
topic: string;
|
||||||
|
title: string;
|
||||||
|
start: Date;
|
||||||
|
end: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum CalendarTaskView {
|
||||||
|
ViewAll = "VIEWALL",
|
||||||
|
}
|
||||||
|
|
||||||
const CalendarPage: React.FC = () => {
|
const CalendarPage: React.FC = () => {
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const currentUserUuid: string = useAppSelector(selectMe);
|
||||||
|
const currentUser: UserLite | undefined = useAppSelector((state) =>
|
||||||
|
selectUser(state, currentUserUuid)
|
||||||
|
);
|
||||||
|
const teamUuids: string[] = useAppSelector(selectTeam);
|
||||||
|
const team: UserLite[] = useAppSelector((state) =>
|
||||||
|
selectUsers(state, teamUuids)
|
||||||
|
);
|
||||||
|
const meetings: CalendarEvent[] = useAppSelector((state) =>
|
||||||
|
selectMeetings(state)
|
||||||
|
).map((m) => ({
|
||||||
|
meetingId: m.meetingId,
|
||||||
|
liveParticipantIds: m.liveParticipantIds,
|
||||||
|
registrantIds: m.registrantIds,
|
||||||
|
startIso: m.start,
|
||||||
|
duration: m.duration,
|
||||||
|
timezone: m.timezone,
|
||||||
|
joinUrl: m.joinUrl,
|
||||||
|
topic: m.topic,
|
||||||
|
// fields needed by calendar
|
||||||
|
title: m.topic,
|
||||||
|
start: new Date(Date.parse(m.start)), // Turns the ISO String into a date object
|
||||||
|
end: new Date(Date.parse(m.start) + m.duration * 60000), // result of Date.parse() is milliseconds, and m.duration is given in minutes
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [selectedUserUuid, setSelectedUserUuid] = useState(currentUserUuid);
|
||||||
|
|
||||||
|
// turns calendar event back into DetailedMeeting then opens meeting view
|
||||||
|
const handleSelectEvent = (event: CalendarEvent) => {
|
||||||
|
const meeting: DetailedMeeting = {
|
||||||
|
meetingId: event.meetingId,
|
||||||
|
liveParticipantIds: event.liveParticipantIds,
|
||||||
|
registrantIds: event.registrantIds,
|
||||||
|
start: event.startIso,
|
||||||
|
duration: event.duration,
|
||||||
|
timezone: event.timezone,
|
||||||
|
joinUrl: event.joinUrl,
|
||||||
|
topic: event.topic,
|
||||||
|
};
|
||||||
|
dispatch(open(meeting));
|
||||||
|
};
|
||||||
|
|
||||||
|
// dropdown selection event
|
||||||
|
const handleUserChange = (event: SelectChangeEvent) => {
|
||||||
|
setSelectedUserUuid(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
// filter the meetings based on the uuid
|
||||||
|
const getUserMeetings = (uuid: string): CalendarEvent[] => {
|
||||||
|
if (uuid == CalendarTaskView.ViewAll) {
|
||||||
|
// "View all team member meetings" option
|
||||||
|
return meetings;
|
||||||
|
} else {
|
||||||
|
return meetings.filter((meeting) => meeting.registrantIds.includes(uuid));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<UserCalendar />
|
|
||||||
|
<FormControl variant="filled" fullWidth>
|
||||||
|
<InputLabel id="calendar-user-select-label">
|
||||||
|
<Typography color="black">User</Typography>
|
||||||
|
</InputLabel>
|
||||||
|
<Select
|
||||||
|
labelId="calendar-user-select-label"
|
||||||
|
id="calendar-user-select"
|
||||||
|
value={selectedUserUuid}
|
||||||
|
label="User"
|
||||||
|
onChange={handleUserChange}
|
||||||
|
>
|
||||||
|
<MenuItem value={CalendarTaskView.ViewAll}>
|
||||||
|
View all team member meetings
|
||||||
|
</MenuItem>
|
||||||
|
{currentUser ? (
|
||||||
|
<MenuItem value={currentUserUuid}>
|
||||||
|
{currentUser.name + " (Me)"}
|
||||||
|
</MenuItem>
|
||||||
|
) : null}
|
||||||
|
{team.map((user) => (
|
||||||
|
<MenuItem key={user.uuid} value={user.uuid}>
|
||||||
|
{user.name}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<Divider sx={{ my: 1 }} />
|
||||||
|
|
||||||
|
<Calendar
|
||||||
|
events={getUserMeetings(selectedUserUuid)}
|
||||||
|
views={["month", "week", "day"]}
|
||||||
|
defaultView={Views.WEEK}
|
||||||
|
onSelectEvent={handleSelectEvent}
|
||||||
|
step={60}
|
||||||
|
showMultiDayTimes
|
||||||
|
localizer={momentLocalizer(moment)}
|
||||||
|
style={{ height: "83%" }}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,185 +0,0 @@
|
|||||||
/*
|
|
||||||
Test data from
|
|
||||||
https://github.com/jquense/react-big-calendar/blob/master/examples/events.js
|
|
||||||
*/
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
|
|
||||||
export default [
|
|
||||||
{
|
|
||||||
id: 0,
|
|
||||||
title: "All Day Event very long title",
|
|
||||||
allDay: true,
|
|
||||||
start: new Date(2022, 2, 0),
|
|
||||||
end: new Date(2022, 2, 1),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "Long Event",
|
|
||||||
start: new Date(2022, 2, 7),
|
|
||||||
end: new Date(2022, 2, 10),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "DTS STARTS",
|
|
||||||
start: new Date(2022, 1, 13, 0, 0, 0),
|
|
||||||
end: new Date(2022, 1, 20, 0, 0, 0),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "DTS ENDS",
|
|
||||||
start: new Date(2022, 9, 6, 0, 0, 0),
|
|
||||||
end: new Date(2022, 9, 13, 0, 0, 0),
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Some Event",
|
|
||||||
start: new Date(2022, 2, 9, 0, 0, 0),
|
|
||||||
end: new Date(2022, 2, 10, 0, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 5,
|
|
||||||
title: "Conference",
|
|
||||||
start: new Date(2022, 2, 11),
|
|
||||||
end: new Date(2022, 2, 13),
|
|
||||||
desc: "Big conference for important people",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 6,
|
|
||||||
title: "Meeting",
|
|
||||||
start: new Date(2022, 2, 12, 10, 30, 0, 0),
|
|
||||||
end: new Date(2022, 2, 12, 12, 30, 0, 0),
|
|
||||||
desc: "Pre-meeting meeting, to prepare for the meeting",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 7,
|
|
||||||
title: "Lunch",
|
|
||||||
start: new Date(2022, 2, 12, 12, 0, 0, 0),
|
|
||||||
end: new Date(2022, 2, 12, 13, 0, 0, 0),
|
|
||||||
desc: "Power lunch",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 8,
|
|
||||||
title: "Meeting",
|
|
||||||
start: new Date(2022, 2, 12, 14, 0, 0, 0),
|
|
||||||
end: new Date(2022, 2, 12, 15, 0, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 9,
|
|
||||||
title: "Happy Hour",
|
|
||||||
start: new Date(2022, 2, 12, 17, 0, 0, 0),
|
|
||||||
end: new Date(2022, 2, 12, 17, 30, 0, 0),
|
|
||||||
desc: "Most important meal of the day",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 10,
|
|
||||||
title: "Dinner",
|
|
||||||
start: new Date(2022, 2, 12, 20, 0, 0, 0),
|
|
||||||
end: new Date(2022, 2, 12, 21, 0, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11,
|
|
||||||
title: "Planning Meeting with Paige",
|
|
||||||
start: new Date(2022, 2, 13, 8, 0, 0),
|
|
||||||
end: new Date(2022, 2, 13, 10, 30, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11.1,
|
|
||||||
title: "Inconvenient multi-day Conference Call",
|
|
||||||
start: new Date(2022, 2, 13, 9, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 1, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11.2,
|
|
||||||
title: "Project Kickoff - Lou's Shoes",
|
|
||||||
start: new Date(2022, 2, 13, 11, 30, 0),
|
|
||||||
end: new Date(2022, 2, 13, 14, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 11.3,
|
|
||||||
title: "Quote Follow-up - Tea by Tina",
|
|
||||||
start: new Date(2022, 2, 13, 15, 30, 0),
|
|
||||||
end: new Date(2022, 2, 13, 16, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 12,
|
|
||||||
title: "Late Night Event",
|
|
||||||
start: new Date(2022, 2, 17, 19, 30, 0),
|
|
||||||
end: new Date(2022, 2, 18, 2, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 12.5,
|
|
||||||
title: "Late Same Night Event",
|
|
||||||
start: new Date(2022, 2, 17, 19, 30, 0),
|
|
||||||
end: new Date(2022, 2, 17, 23, 30, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 13,
|
|
||||||
title: "Multi-day Event",
|
|
||||||
start: new Date(2022, 2, 20, 19, 30, 0),
|
|
||||||
end: new Date(2022, 2, 22, 2, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 14,
|
|
||||||
title: "Today",
|
|
||||||
start: new Date(new Date().setHours(new Date().getHours() - 3)),
|
|
||||||
end: new Date(new Date().setHours(new Date().getHours() + 3)),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 15,
|
|
||||||
title: "Point in Time Event",
|
|
||||||
start: now,
|
|
||||||
end: now,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 16,
|
|
||||||
title: "Video Record",
|
|
||||||
start: new Date(2022, 2, 14, 15, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 19, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 17,
|
|
||||||
title: "Dutch Song Producing",
|
|
||||||
start: new Date(2022, 2, 14, 16, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 20, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 18,
|
|
||||||
title: "Itaewon Halloween Meeting",
|
|
||||||
start: new Date(2022, 2, 14, 16, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 17, 30, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 19,
|
|
||||||
title: "Online Coding Test",
|
|
||||||
start: new Date(2022, 2, 14, 17, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 20, 30, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 20,
|
|
||||||
title: "An overlapped Event",
|
|
||||||
start: new Date(2022, 2, 14, 17, 0, 0),
|
|
||||||
end: new Date(2022, 2, 14, 18, 30, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 21,
|
|
||||||
title: "Phone Interview",
|
|
||||||
start: new Date(2022, 2, 14, 17, 0, 0),
|
|
||||||
end: new Date(2022, 2, 14, 18, 30, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 22,
|
|
||||||
title: "Cooking Class",
|
|
||||||
start: new Date(2022, 2, 14, 17, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 19, 0, 0),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 23,
|
|
||||||
title: "Go to the gym",
|
|
||||||
start: new Date(2022, 2, 14, 18, 30, 0),
|
|
||||||
end: new Date(2022, 2, 14, 20, 0, 0),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
import { Calendar, momentLocalizer } from "react-big-calendar";
|
|
||||||
import moment from "moment";
|
|
||||||
import testEvents from "./Events";
|
|
||||||
|
|
||||||
import "react-big-calendar/lib/css/react-big-calendar.css";
|
|
||||||
|
|
||||||
const localizer = momentLocalizer(moment);
|
|
||||||
|
|
||||||
const UserCalendar: React.FC = () => {
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Calendar
|
|
||||||
events={testEvents}
|
|
||||||
views={["month", "week", "day"]}
|
|
||||||
step={60}
|
|
||||||
showMultiDayTimes
|
|
||||||
localizer={localizer}
|
|
||||||
style={{ height: "80%" }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default UserCalendar;
|
|
||||||
@ -4,8 +4,8 @@ import { userLites, team } from "../../api-bodies/MockData";
|
|||||||
import UserLite from "../../api-bodies/UserLite";
|
import UserLite from "../../api-bodies/UserLite";
|
||||||
|
|
||||||
interface TeamState {
|
interface TeamState {
|
||||||
user: string | null;
|
user: string;
|
||||||
manager: string | null;
|
manager: string;
|
||||||
sameManager: string[];
|
sameManager: string[];
|
||||||
directReports: string[];
|
directReports: string[];
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ interface UsersState {
|
|||||||
|
|
||||||
const initialState: UsersState = {
|
const initialState: UsersState = {
|
||||||
users: {},
|
users: {},
|
||||||
team: { user: null, manager: null, sameManager: [], directReports: [] },
|
team: { user: "", manager: "", sameManager: [], directReports: [] },
|
||||||
};
|
};
|
||||||
|
|
||||||
export const usersSlice = createSlice({
|
export const usersSlice = createSlice({
|
||||||
@ -61,9 +61,11 @@ export const selectUsers = (state: RootState, uuids: string[]): UserLite[] => {
|
|||||||
};
|
};
|
||||||
export const selectTeam = (state: RootState) => {
|
export const selectTeam = (state: RootState) => {
|
||||||
const team: string[] = [];
|
const team: string[] = [];
|
||||||
if (state.users.team.manager !== null) team.push(state.users.team.manager);
|
if (state.users.team.manager) team.push(state.users.team.manager);
|
||||||
state.users.team.sameManager.forEach((u) => team.push(u));
|
state.users.team.sameManager.forEach((u) => team.push(u));
|
||||||
state.users.team.directReports.forEach((u) => team.push(u));
|
state.users.team.directReports.forEach((u) => team.push(u));
|
||||||
return team;
|
return team;
|
||||||
};
|
};
|
||||||
|
export const selectMe = (state: RootState) => state.users.team.user;
|
||||||
|
|
||||||
export default usersSlice.reducer;
|
export default usersSlice.reducer;
|
||||||
|
|||||||
@ -17,7 +17,8 @@ const getStatusColor = (ms: MeetingStatus): string => {
|
|||||||
const formatTimeFromDate = (date: Date): string => {
|
const formatTimeFromDate = (date: Date): string => {
|
||||||
let hour = date.getHours();
|
let hour = date.getHours();
|
||||||
let ampm = "";
|
let ampm = "";
|
||||||
let minutes = date.getMinutes() < 10 ? "0" + date.getMinutes() : "" + date.getMinutes();
|
const minutes =
|
||||||
|
date.getMinutes() < 10 ? "0" + date.getMinutes() : "" + date.getMinutes();
|
||||||
if (hour < 12) {
|
if (hour < 12) {
|
||||||
ampm = "am";
|
ampm = "am";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user