Merge branch 'main' into mbalsdon-browser-notifications
This commit is contained in:
commit
ffc230a631
@ -10,20 +10,12 @@ import { fetchUsers, selectMe } from "./redux/slices/usersSlice";
|
||||
import { useAppDispatch, useAppSelector, useInterval } from "./redux/hooks";
|
||||
import DetailedMeeting from "./api-bodies/DetailedMeeting";
|
||||
import React, { useState } from "react";
|
||||
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const ProtectedRoute = () => {
|
||||
const auth = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
/* Temporary data */
|
||||
if (auth?.isLoggedIn != undefined && auth?.isLoggedIn == true) {
|
||||
store.dispatch(fetchMeetings(auth?.uuid));
|
||||
store.dispatch(fetchUsers(auth?.uuid));
|
||||
store.dispatch(fetchFavorites(auth?.uuid));
|
||||
}
|
||||
|
||||
useAppDispatch();
|
||||
const currentUserUuid: string = useAppSelector(selectMe);
|
||||
const meetings: DetailedMeeting[] = useAppSelector(selectMeetings);
|
||||
@ -64,8 +56,6 @@ const ProtectedRoute = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
// -----
|
||||
|
||||
return auth?.isLoggedIn ? (
|
||||
<>
|
||||
{/* ----- */}
|
||||
|
||||
@ -2,7 +2,7 @@ interface DetailedMeeting {
|
||||
meetingId: string; // primary key
|
||||
liveParticipantIds: string[];
|
||||
registrantIds: string[];
|
||||
start: string;
|
||||
startTime: string;
|
||||
duration: number;
|
||||
timezone: string;
|
||||
joinUrl: string;
|
||||
|
||||
@ -48,7 +48,7 @@ const meetings = [
|
||||
meetingId: "",
|
||||
liveParticipantIds: [],
|
||||
registrantIds: ["", "1", "2", "3", "4", "5", "6"],
|
||||
start: "2022-03-13T17:00:00",
|
||||
startTime: "2022-03-13T17:00:00",
|
||||
duration: 15,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -58,7 +58,7 @@ const meetings = [
|
||||
meetingId: "1",
|
||||
liveParticipantIds: [],
|
||||
registrantIds: ["", "2", "4"],
|
||||
start: "2022-03-16T17:30:00",
|
||||
startTime: "2022-03-16T17:30:00",
|
||||
duration: 30,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -68,7 +68,7 @@ const meetings = [
|
||||
meetingId: "2",
|
||||
liveParticipantIds: ["3", "5"],
|
||||
registrantIds: ["3", "5", "6"],
|
||||
start: "2022-03-13T17:30:00",
|
||||
startTime: "2022-03-13T17:30:00",
|
||||
duration: 30,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -78,7 +78,7 @@ const meetings = [
|
||||
meetingId: "3",
|
||||
liveParticipantIds: [],
|
||||
registrantIds: ["", "1"],
|
||||
start: "2022-03-10T07:27:27",
|
||||
startTime: "2022-03-10T07:27:27",
|
||||
duration: 727,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -88,7 +88,7 @@ const meetings = [
|
||||
meetingId: "4",
|
||||
liveParticipantIds: [],
|
||||
registrantIds: ["", "2", "3"],
|
||||
start: "2022-03-10T12:30:00",
|
||||
startTime: "2022-03-10T12:30:00",
|
||||
duration: 120,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -98,7 +98,7 @@ const meetings = [
|
||||
meetingId: "5",
|
||||
liveParticipantIds: [""],
|
||||
registrantIds: [""],
|
||||
start: "2022-03-24T23:11:11",
|
||||
startTime: "2022-03-24T23:11:11",
|
||||
duration: 11,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -108,7 +108,7 @@ const meetings = [
|
||||
meetingId: "6",
|
||||
liveParticipantIds: ["", "1"],
|
||||
registrantIds: ["", "1", "2", "3", "4", "5"],
|
||||
start: "2022-03-25T09:00:00",
|
||||
startTime: "2022-03-25T09:00:00",
|
||||
duration: 360,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -118,7 +118,7 @@ const meetings = [
|
||||
meetingId: "7",
|
||||
liveParticipantIds: [],
|
||||
registrantIds: ["", "5"],
|
||||
start: "2022-03-25T15:00:00",
|
||||
startTime: "2022-03-25T15:00:00",
|
||||
duration: 150,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -128,7 +128,7 @@ const meetings = [
|
||||
meetingId: "8",
|
||||
liveParticipantIds: ["2"],
|
||||
registrantIds: ["", "5", "2", "3"],
|
||||
start: "2022-03-25T17:45:00",
|
||||
startTime: "2022-03-25T17:45:00",
|
||||
duration: 60,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -138,7 +138,7 @@ const meetings = [
|
||||
meetingId: "9",
|
||||
liveParticipantIds: ["2", "5"],
|
||||
registrantIds: ["", "4"],
|
||||
start: "2022-03-25T18:15:30",
|
||||
startTime: "2022-03-25T18:15:30",
|
||||
duration: 75,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
@ -148,7 +148,7 @@ const meetings = [
|
||||
meetingId: "10",
|
||||
liveParticipantIds: [],
|
||||
registrantIds: ["", "1", "2", "3", "4", "5", "6"],
|
||||
start: "2022-04-04T18:30:00",
|
||||
startTime: "2022-04-04T18:30:00",
|
||||
duration: 90,
|
||||
timezone: "",
|
||||
joinUrl: "",
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
interface NewMeeting {
|
||||
uuid: string;
|
||||
startTime: string;
|
||||
duration: number;
|
||||
timezone: string;
|
||||
topic: string;
|
||||
registrantsIds: string[];
|
||||
registrantIds: string[];
|
||||
}
|
||||
|
||||
export default NewMeeting;
|
||||
|
||||
@ -2,7 +2,7 @@ import UserLite from "./UserLite";
|
||||
|
||||
interface UserFull {
|
||||
userInfo: UserLite;
|
||||
manager: UserLite;
|
||||
manager?: UserLite;
|
||||
managerDirectReports: UserLite[];
|
||||
userDirectReports: UserLite[];
|
||||
}
|
||||
|
||||
@ -64,15 +64,15 @@ const CalendarPage: React.FC = () => {
|
||||
meetingId: m.meetingId,
|
||||
liveParticipantIds: m.liveParticipantIds,
|
||||
registrantIds: m.registrantIds,
|
||||
startIso: m.start,
|
||||
startIso: m.startTime,
|
||||
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
|
||||
start: new Date(Date.parse(m.startTime)), // Turns the ISO String into a date object
|
||||
end: new Date(Date.parse(m.startTime) + m.duration * 60000), // result of Date.parse() is milliseconds, and m.duration is given in minutes
|
||||
}));
|
||||
|
||||
const [selectedUserUuid, setSelectedUserUuid] = useState(currentUserUuid);
|
||||
@ -83,7 +83,7 @@ const CalendarPage: React.FC = () => {
|
||||
meetingId: event.meetingId,
|
||||
liveParticipantIds: event.liveParticipantIds,
|
||||
registrantIds: event.registrantIds,
|
||||
start: event.startIso,
|
||||
startTime: event.startIso,
|
||||
duration: event.duration,
|
||||
timezone: event.timezone,
|
||||
joinUrl: event.joinUrl,
|
||||
@ -108,14 +108,16 @@ const CalendarPage: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "98.5%",
|
||||
height: "99.5%",
|
||||
ml: 1,
|
||||
mt: 1}}>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "98.5%",
|
||||
height: "99.5%",
|
||||
ml: 1,
|
||||
mt: 1,
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
|
||||
<FormControl fullWidth>
|
||||
@ -156,13 +158,12 @@ const CalendarPage: React.FC = () => {
|
||||
showMultiDayTimes
|
||||
localizer={momentLocalizer(moment)}
|
||||
style={{ height: "83%" }}
|
||||
eventPropGetter = {() => {
|
||||
eventPropGetter={() => {
|
||||
const backgroundColor = "IndianRed";
|
||||
const borderColor = "White";
|
||||
return { style: { backgroundColor, borderColor } };
|
||||
}}
|
||||
/>
|
||||
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -7,6 +7,7 @@ import UserLite from "../../api-bodies/UserLite";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { selectUser } from "../../redux/slices/usersSlice";
|
||||
import { useAppSelector } from "../../redux/hooks";
|
||||
import EmptyBody from "./contacts-components/EmptyBody";
|
||||
|
||||
const Contacts: React.FC = () => {
|
||||
const { uuid } = useParams();
|
||||
@ -15,9 +16,14 @@ const Contacts: React.FC = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", height: "100%" }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
height: "100%",
|
||||
}}
|
||||
>
|
||||
<Sidebar />
|
||||
{uriContact ? <Body contactSelected={uriContact} /> : null}
|
||||
{uriContact ? <Body contactSelected={uriContact} /> : <EmptyBody />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
||||
|
||||
const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
||||
const startDate = new Date(meeting.start);
|
||||
const startDate = new Date(meeting.startTime);
|
||||
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
|
||||
const startTime = startDate
|
||||
.toTimeString()
|
||||
|
||||
@ -16,7 +16,7 @@ const Body: React.FC<Props> = (props) => {
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
mt: 2,
|
||||
mt: 5,
|
||||
}}
|
||||
>
|
||||
<Toolbar />
|
||||
|
||||
29
src/components/contacts/contacts-components/EmptyBody.tsx
Normal file
29
src/components/contacts/contacts-components/EmptyBody.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import PermContactCalendarIcon from "@mui/icons-material/PermContactCalendar";
|
||||
|
||||
const EmptyBody: React.FC = () => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<Box>
|
||||
<PermContactCalendarIcon color="secondary" sx={{ fontSize: 100 }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>
|
||||
View contact info by clicking a contact in the left panel
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default EmptyBody;
|
||||
@ -24,40 +24,55 @@ const LowerBody: React.FC<Props> = (props) => {
|
||||
flexDirection: "column",
|
||||
border: 1,
|
||||
borderRadius: 2,
|
||||
borderColor: "#af000d",
|
||||
width: "70%",
|
||||
height: "60%",
|
||||
alignSelf: "center",
|
||||
}}
|
||||
>
|
||||
<Typography variant="h4" textAlign="center">
|
||||
<Typography
|
||||
sx={{ color: "white", my: 1 }}
|
||||
variant="h4"
|
||||
textAlign="center"
|
||||
>
|
||||
Upcoming meetings
|
||||
</Typography>
|
||||
<List sx={{ maxHeight: "100%", overflow: "auto" }}>
|
||||
{meetings.map((meeting, i) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
height: "50px",
|
||||
px: "10px",
|
||||
borderTop: 1,
|
||||
borderBottom: i === meetings.length - 1 ? 1 : 0,
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
onClick={() => dispatch(open(meeting))}
|
||||
{meetings.length === 0 ? (
|
||||
<Typography
|
||||
variant="h5"
|
||||
textAlign="center"
|
||||
sx={{ position: "relative", top: "30%" }}
|
||||
>
|
||||
No upcoming meetings today
|
||||
</Typography>
|
||||
) : (
|
||||
<List sx={{ maxHeight: "100%", overflow: "auto" }}>
|
||||
{meetings.map((meeting, i) => (
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
height: "50px",
|
||||
px: "10px",
|
||||
borderTop: 1,
|
||||
borderBottom: i === meetings.length - 1 ? 1 : 0,
|
||||
}}
|
||||
key={i}
|
||||
>
|
||||
{meeting.topic}
|
||||
</Button>
|
||||
<Typography sx={{ pt: 1.5 }}>
|
||||
{getUpcomingMeetingTime(meeting)}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
onClick={() => dispatch(open(meeting))}
|
||||
>
|
||||
{meeting.topic}
|
||||
</Button>
|
||||
<Typography sx={{ pt: 1.5 }}>
|
||||
{getUpcomingMeetingTime(meeting)}
|
||||
</Typography>
|
||||
</Box>
|
||||
))}
|
||||
</List>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
import { Box, Button, IconButton, Typography } from "@mui/material";
|
||||
import React from "react";
|
||||
import PhoneIcon from "@mui/icons-material/Phone";
|
||||
import VideocamIcon from "@mui/icons-material/Videocam";
|
||||
import GroupsIcon from "@mui/icons-material/Groups";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import UserLite from "../../../../api-bodies/UserLite";
|
||||
import UserStatus from "../../../../api-bodies/UserStatus";
|
||||
@ -18,26 +16,50 @@ import {
|
||||
} from "../../../../redux/slices/favoritesSlice";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import { MeetingStatus } from "../../../../utils";
|
||||
import { selectMe } from "../../../../redux/slices/usersSlice";
|
||||
import { selectManager, selectMe } from "../../../../redux/slices/usersSlice";
|
||||
import axios from "../../../../api/axios";
|
||||
import NewMeeting from "../../../../api-bodies/NewMeeting";
|
||||
|
||||
interface Props {
|
||||
contactInfo: UserLite;
|
||||
}
|
||||
|
||||
const UpperBody: React.FC<Props> = (props) => {
|
||||
const UpperBody: React.FC<Props> = ({ contactInfo }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const userStatus: UserStatus = useAppSelector((state) =>
|
||||
selectUserStatus(state, props.contactInfo.uuid)
|
||||
selectUserStatus(state, contactInfo.uuid)
|
||||
);
|
||||
const me = useAppSelector(selectMe);
|
||||
const favoritesUuids = useAppSelector(selectFavorites);
|
||||
const detailedMeeting = useAppSelector((state) =>
|
||||
selectMeeting(state, userStatus.meetingId)
|
||||
selectMeeting(state, userStatus.inMeeting ? userStatus.meetingId : null)
|
||||
);
|
||||
const managerId = useAppSelector(selectManager);
|
||||
const meetingStatus: MeetingStatus =
|
||||
userStatus && userStatus.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING;
|
||||
managerId === contactInfo.uuid
|
||||
? MeetingStatus.NOT_AVAILABLE
|
||||
: userStatus && userStatus.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING;
|
||||
|
||||
const startCall = async () => {
|
||||
const newMeeting: NewMeeting = {
|
||||
startTime: "2022-03-30T23:40:00Z",
|
||||
duration: 30,
|
||||
topic: `Meeting with ${contactInfo.name}`,
|
||||
registrantIds: [contactInfo.uuid],
|
||||
};
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`/users/${me}/meetings`,
|
||||
JSON.stringify(newMeeting)
|
||||
);
|
||||
window.open(response.data.joinUrl, "_blank")?.focus();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -45,6 +67,7 @@ const UpperBody: React.FC<Props> = (props) => {
|
||||
flexDirection: "column",
|
||||
border: 1,
|
||||
borderRadius: 2,
|
||||
borderColor: "#af000d",
|
||||
width: "70%",
|
||||
alignSelf: "center",
|
||||
mb: 2,
|
||||
@ -59,16 +82,14 @@ const UpperBody: React.FC<Props> = (props) => {
|
||||
mx: 4,
|
||||
}}
|
||||
>
|
||||
<Typography variant="h3">{props.contactInfo.name}</Typography>
|
||||
{!favoritesUuids.includes(props.contactInfo.uuid) ? (
|
||||
<Typography variant="h3">{contactInfo.name}</Typography>
|
||||
{!favoritesUuids.includes(contactInfo.uuid) ? (
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="success"
|
||||
startIcon={<AddIcon />}
|
||||
onClick={() =>
|
||||
dispatch(
|
||||
addFavorite({ userId: me, toBeAdded: props.contactInfo.uuid })
|
||||
)
|
||||
dispatch(addFavorite({ userId: me, toBeAdded: contactInfo.uuid }))
|
||||
}
|
||||
>
|
||||
favorites
|
||||
@ -82,7 +103,7 @@ const UpperBody: React.FC<Props> = (props) => {
|
||||
dispatch(
|
||||
removeFavorite({
|
||||
userId: me,
|
||||
toBeRemoved: props.contactInfo.uuid,
|
||||
toBeRemoved: contactInfo.uuid,
|
||||
})
|
||||
)
|
||||
}
|
||||
@ -103,23 +124,20 @@ const UpperBody: React.FC<Props> = (props) => {
|
||||
>
|
||||
<Box>
|
||||
<IconButton
|
||||
sx={{ border: 1, mr: 1, backgroundColor: "secondary" }}
|
||||
sx={{ border: 1, backgroundColor: "white", mr: 1 }}
|
||||
size="large"
|
||||
color="secondary"
|
||||
onClick={startCall}
|
||||
>
|
||||
<PhoneIcon fontSize="large" />
|
||||
</IconButton>
|
||||
<IconButton sx={{ border: 1, mr: 1 }} size="large" color="secondary">
|
||||
<VideocamIcon fontSize="large" />
|
||||
</IconButton>
|
||||
<IconButton sx={{ border: 1, mr: 1 }} size="large" color="secondary">
|
||||
<GroupsIcon fontSize="large" />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography sx={{ textAlign: "right" }}>{meetingStatus}</Typography>
|
||||
<Typography variant="button" sx={{ textAlign: "right" }}>
|
||||
{meetingStatus}
|
||||
</Typography>
|
||||
{detailedMeeting ? (
|
||||
<Typography>{detailedMeeting.topic}</Typography>
|
||||
<Typography variant="h6">{detailedMeeting.topic}</Typography>
|
||||
) : null}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
@ -8,31 +8,35 @@ import { useAppSelector } from "../../../../redux/hooks";
|
||||
import { selectUserStatus } from "../../../../redux/slices/meetingsAndUserStatusSlice";
|
||||
import UserStatus from "../../../../api-bodies/UserStatus";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { selectManager } from "../../../../redux/slices/usersSlice";
|
||||
|
||||
interface Props {
|
||||
contactInfo: UserLite;
|
||||
}
|
||||
|
||||
const ContactItem: React.FC<Props> = (props) => {
|
||||
const ContactItem: React.FC<Props> = ({ contactInfo }) => {
|
||||
const navigate = useNavigate();
|
||||
const userStatus: UserStatus | null = useAppSelector((state) =>
|
||||
selectUserStatus(state, props.contactInfo.uuid)
|
||||
selectUserStatus(state, contactInfo.uuid)
|
||||
);
|
||||
const managerId = useAppSelector(selectManager);
|
||||
const status: MeetingStatus =
|
||||
userStatus && userStatus.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING;
|
||||
managerId === contactInfo.uuid
|
||||
? MeetingStatus.NOT_AVAILABLE
|
||||
: userStatus && userStatus.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING;
|
||||
return (
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
navigate(`/contacts/${props.contactInfo.uuid}`);
|
||||
navigate(`/contacts/${contactInfo.uuid}`);
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<PersonOutlineIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={props.contactInfo.name}
|
||||
primary={contactInfo.name}
|
||||
secondary={status}
|
||||
sx={{ flexGrow: 1 }}
|
||||
/>
|
||||
|
||||
@ -15,25 +15,16 @@ import { useAppSelector } from "../../redux/hooks";
|
||||
import { selectFavorites } from "../../redux/slices/favoritesSlice";
|
||||
import { selectTeam, selectUsers } from "../../redux/slices/usersSlice";
|
||||
import GroupSelect from "../sidebar/GroupSelect";
|
||||
import { SidebarUserObj } from "./Home";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
selectedValue: SidebarUserObj;
|
||||
onClose: (value: SidebarUserObj) => void;
|
||||
users: SidebarUserObj[];
|
||||
handleClose: () => void;
|
||||
}
|
||||
|
||||
const CallFavouritesDialog: React.FC<Props> = ({
|
||||
open,
|
||||
selectedValue,
|
||||
onClose,
|
||||
users,
|
||||
handleClose,
|
||||
}: Props) => {
|
||||
const handleClose = () => {
|
||||
onClose(selectedValue);
|
||||
};
|
||||
console.log(users);
|
||||
|
||||
const [group, setGroup] = useState<string>("Favorites");
|
||||
const [inputText, setInputText] = useState<string>("");
|
||||
@ -60,6 +51,10 @@ const CallFavouritesDialog: React.FC<Props> = ({
|
||||
selectUsers(state, groupMembersUuids)
|
||||
);
|
||||
|
||||
const handleCall = () => {
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog onClose={handleClose} open={open} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Select who to call:</DialogTitle>
|
||||
@ -107,7 +102,7 @@ const CallFavouritesDialog: React.FC<Props> = ({
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button color="success" onClick={handleClose}>
|
||||
<Button color="success" onClick={handleCall}>
|
||||
<Typography variant="button" color="black">
|
||||
Call
|
||||
</Typography>
|
||||
|
||||
@ -3,7 +3,6 @@ import ShortCuts from "./ShortCuts";
|
||||
import Container from "@mui/material/Container";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { MeetingStatus } from "../../utils";
|
||||
import { useState } from "react";
|
||||
|
||||
export interface SidebarUserObj {
|
||||
id: number;
|
||||
@ -15,12 +14,12 @@ const Home: React.FC = () => {
|
||||
/* Temporary data - this is the same as what's in ProtectedRoute.tsx so it should not
|
||||
be duplicated like this in the future (Route components were being weird when I tried
|
||||
to pass it down from App.tsx) */
|
||||
const [mockUsers] = useState<SidebarUserObj[]>([
|
||||
{ id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||
{ id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||
{ id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||
]);
|
||||
// const [mockUsers] = useState<SidebarUserObj[]>([
|
||||
// { id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||
// { id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||
// { id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||
// { id: 3, name: "Bob A.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||
// ]);
|
||||
|
||||
return (
|
||||
<Container className="main-home">
|
||||
@ -29,7 +28,7 @@ const Home: React.FC = () => {
|
||||
<MeetingsPanel />
|
||||
</Grid>
|
||||
<Grid item sm={4}>
|
||||
<ShortCuts users={mockUsers} />
|
||||
<ShortCuts />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
||||
@ -1,17 +1,30 @@
|
||||
import ListItemButton from "@mui/material/ListItemButton";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
||||
import { useAppDispatch } from "../../redux/hooks";
|
||||
import { open } from "../../redux/slices/meetingDetailsOpenSlice";
|
||||
import { Box, Typography } from "@mui/material";
|
||||
|
||||
interface Props {
|
||||
meeting: DetailedMeeting;
|
||||
meetingClass: string;
|
||||
meetingName: string;
|
||||
meetingTime: string;
|
||||
meetingMembers: string
|
||||
}
|
||||
|
||||
function Meeting(props: Props) {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
<ListItemButton component="a" href="#" className="row meeting">
|
||||
<ListItemText primary={props.meetingName} secondary={props.meetingMembers} />
|
||||
<ListItemText primary={props.meetingTime} />
|
||||
<ListItemButton
|
||||
onClick={() => dispatch(open(props.meeting))}
|
||||
component="a"
|
||||
className={"row meeting " + props.meetingClass}>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography color="white" variant="h5">{props.meetingName}</Typography>
|
||||
<Typography color="white" variant="h6">{props.meetingTime.toUpperCase()}</Typography>
|
||||
<Typography color="mistyrose" variant="body1">{props.meetingMembers}</Typography>
|
||||
</Box>
|
||||
</ListItemButton>
|
||||
);
|
||||
}
|
||||
|
||||
@ -13,15 +13,13 @@ const MeetingsPanel: React.FC = () => {
|
||||
const uuids: string[] = [];
|
||||
meetings.forEach((meeting) => {
|
||||
meeting.liveParticipantIds.forEach((uuid) => {
|
||||
if(!uuids.includes(uuid)) {
|
||||
if (!uuids.includes(uuid)) {
|
||||
uuids.push(uuid);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const participants = useAppSelector((state) =>
|
||||
selectUsers(state,uuids)
|
||||
);
|
||||
const participants = useAppSelector((state) => selectUsers(state, uuids));
|
||||
|
||||
// const participants: (UserLite | undefined)[] = [];
|
||||
// uuids.forEach((uuid) => {
|
||||
@ -30,41 +28,82 @@ const MeetingsPanel: React.FC = () => {
|
||||
// );
|
||||
// participants.push(userLite);
|
||||
// });
|
||||
|
||||
const [currentDate, setCurrentDate] = useState<Date>(new Date());
|
||||
|
||||
useEffect(() => {
|
||||
setInterval(() => setCurrentDate(new Date()), 1000);
|
||||
}, []);
|
||||
|
||||
let i = 1;
|
||||
|
||||
return (
|
||||
<div className="meetings-panel">
|
||||
<div className="row panel-label">
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
Meetings in Progress
|
||||
<Typography
|
||||
className="mylabel"
|
||||
variant="h5"
|
||||
sx={{ color: "white", ml: 1 }}
|
||||
>
|
||||
Meetings in progress
|
||||
</Typography>
|
||||
</div>
|
||||
|
||||
<List style={{maxHeight: '100%', overflow: 'auto'}} >
|
||||
<List style={{ maxHeight: "100%", overflow: "auto" }}>
|
||||
{meetings.map((meeting) => {
|
||||
const meetingMembers: UserLite[] = [];
|
||||
participants.forEach((userLite) => {
|
||||
if (userLite != undefined && meeting.liveParticipantIds.includes(userLite.uuid)) {
|
||||
if (
|
||||
userLite != undefined &&
|
||||
meeting.liveParticipantIds.includes(userLite.uuid)
|
||||
) {
|
||||
meetingMembers.push(userLite);
|
||||
}
|
||||
});
|
||||
const startDate = new Date(meeting.start);
|
||||
const startDate = new Date(meeting.startTime);
|
||||
const startDatemil = startDate.getTime();
|
||||
const endDatemil = startDatemil + meeting.duration*60000;
|
||||
const endDatemil = startDatemil + meeting.duration * 60000;
|
||||
const endDate = new Date(endDatemil);
|
||||
|
||||
const currentDatemil = currentDate.getTime();
|
||||
|
||||
const meetingMembersString = () => {
|
||||
if (meetingMembers.length > 3) {
|
||||
return (
|
||||
"Participants: " +
|
||||
meetingMembers[0].name +
|
||||
", " +
|
||||
meetingMembers[1].name +
|
||||
", " +
|
||||
meetingMembers[2].name +
|
||||
", and " +
|
||||
(meetingMembers.length - 3).toString() +
|
||||
" more..."
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
"Participants: " +
|
||||
meetingMembers.map((userLite) => " " + userLite.name).toString()
|
||||
);
|
||||
}
|
||||
};
|
||||
console.log(meetingMembersString);
|
||||
if (currentDatemil >= startDatemil && currentDatemil <= endDatemil) {
|
||||
const lastMeetingClass = meetings.length == i ? " lastMeeting" : "";
|
||||
i += 1;
|
||||
return (
|
||||
<Meeting
|
||||
meeting={meeting}
|
||||
meetingClass={"meeting-" + (i - 1) + lastMeetingClass}
|
||||
meetingName={meeting.topic}
|
||||
meetingTime={formatTimeFromDate(startDate) + " - " + formatTimeFromDate(endDate)}
|
||||
meetingMembers={meetingMembers.map((userLite) => (" " + userLite.name + " ")).toString()}
|
||||
meetingTime={
|
||||
formatTimeFromDate(startDate) +
|
||||
" - " +
|
||||
formatTimeFromDate(endDate)
|
||||
}
|
||||
meetingMembers={meetingMembers
|
||||
.map((userLite) => " " + userLite.name + " ")
|
||||
.toString()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,29 +1,20 @@
|
||||
import PeopleIcon from "@mui/icons-material/People";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import PhoneEnabledIcon from "@mui/icons-material/PhoneEnabled";
|
||||
import CircleIcon from "@mui/icons-material/Circle";
|
||||
import Button from "@mui/material/Button";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import Typography from "@mui/material/Typography";
|
||||
import { SidebarUserObj } from "./Home";
|
||||
import CallFavouritesDialog from "./CallFavouritesDialog";
|
||||
import { useState } from "react";
|
||||
|
||||
interface Props {
|
||||
users: SidebarUserObj[];
|
||||
}
|
||||
|
||||
const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
||||
const ShortCuts: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [selectedValue, setSelectedValue] = useState(users[0]);
|
||||
|
||||
const handleClickOpen = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
|
||||
const handleClose = (value: SidebarUserObj) => {
|
||||
const handleClose = () => {
|
||||
setOpen(false);
|
||||
setSelectedValue(value);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -33,10 +24,24 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
||||
<Button className="tile">
|
||||
<PeopleIcon className="icon" />
|
||||
</Button>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
New Meeting
|
||||
<Typography variant="h6" className="mylabel" sx={{ ml: 1 }}>
|
||||
Schedule Meeting
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile" onClick={handleClickOpen}>
|
||||
<PhoneEnabledIcon className="icon" />
|
||||
</Button>
|
||||
<CallFavouritesDialog
|
||||
open={open}
|
||||
handleClose={handleClose}
|
||||
/>
|
||||
<Typography variant="h6" className="mylabel" sx={{ ml: 1 }}>
|
||||
Call Group
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
{/* <Grid className="row-2" container spacing={1}>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile">
|
||||
<AddIcon className="icon" />
|
||||
@ -45,22 +50,6 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
||||
Join
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid className="row-2" container spacing={1}>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile" onClick={handleClickOpen}>
|
||||
<PhoneEnabledIcon className="icon" />
|
||||
</Button>
|
||||
<CallFavouritesDialog
|
||||
selectedValue={selectedValue}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
users={users}
|
||||
/>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
Call Group
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile">
|
||||
<CircleIcon className="icon" />
|
||||
@ -69,7 +58,7 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
||||
Recordings
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -9,6 +9,13 @@ import zoomLogo from "../../assets/zoom.png";
|
||||
import LoginIcon from "@mui/icons-material/Login";
|
||||
import useAuth from "../../hooks/useAuth";
|
||||
import axios from "../../api/axios";
|
||||
import { store } from "../../redux/store";
|
||||
import { fetchFavorites } from "../../redux/slices/favoritesSlice";
|
||||
import {
|
||||
fetchMeetings,
|
||||
socketActions,
|
||||
} from "../../redux/slices/meetingsAndUserStatusSlice";
|
||||
import { fetchUsers } from "../../redux/slices/usersSlice";
|
||||
|
||||
interface LocationState {
|
||||
from: { pathname: string };
|
||||
@ -36,13 +43,17 @@ const Login: React.FC = () => {
|
||||
// setErrMsg('');
|
||||
// }, [user, pwd])
|
||||
|
||||
const handleLogin = async(e: React.SyntheticEvent) => {
|
||||
const handleLogin = async (e: React.SyntheticEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
try {
|
||||
if (email === "" && password === "") {
|
||||
setAuth["uuid"] = "";
|
||||
setAuth["isLoggedIn"] = true;
|
||||
store.dispatch(fetchMeetings(""));
|
||||
store.dispatch(fetchUsers(""));
|
||||
store.dispatch(fetchFavorites(""));
|
||||
store.dispatch(socketActions.startConnecting());
|
||||
navigate(from, { replace: true });
|
||||
}
|
||||
|
||||
@ -62,6 +73,10 @@ const Login: React.FC = () => {
|
||||
if (logedInUserId != undefined) {
|
||||
setAuth["uuid"] = logedInUserId;
|
||||
setAuth["isLoggedIn"] = true;
|
||||
store.dispatch(fetchMeetings(logedInUserId));
|
||||
store.dispatch(fetchUsers(logedInUserId));
|
||||
store.dispatch(fetchFavorites(logedInUserId));
|
||||
store.dispatch(socketActions.startConnecting());
|
||||
navigate(from, { replace: true });
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@ -1,16 +1,7 @@
|
||||
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
||||
|
||||
const getMeetingStatus = (meeting: DetailedMeeting) => {
|
||||
const startDate = new Date(meeting.start);
|
||||
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
|
||||
const currentDate = new Date();
|
||||
if (currentDate > startDate && currentDate < endDate) return "Live";
|
||||
else if (currentDate < startDate) return "Scheduled";
|
||||
else return "Finished";
|
||||
};
|
||||
|
||||
const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
||||
const startDate = new Date(meeting.start);
|
||||
const startDate = new Date(meeting.startTime);
|
||||
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
|
||||
const startTime = startDate
|
||||
.toTimeString()
|
||||
@ -27,4 +18,22 @@ const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
||||
return `${startTime} - ${endTime}`;
|
||||
};
|
||||
|
||||
export { getMeetingStatus, getUpcomingMeetingTime };
|
||||
const getMeetingDuration = (meeting: DetailedMeeting) => {
|
||||
const startDate = new Date(meeting.startTime);
|
||||
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
|
||||
const startDateString = startDate.toDateString();
|
||||
const startTimeString = startDate
|
||||
.toTimeString()
|
||||
.split(" ")[0]
|
||||
.split(":")
|
||||
.splice(0, 2)
|
||||
.join(":");
|
||||
const endTimeString = endDate
|
||||
.toTimeString()
|
||||
.split(" ")[0]
|
||||
.split(":")
|
||||
.slice(0, 2)
|
||||
.join(":");
|
||||
return `${startDateString} ${startTimeString} - ${endTimeString}`;
|
||||
};
|
||||
export { getUpcomingMeetingTime, getMeetingDuration };
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import {
|
||||
Box,
|
||||
Divider,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
@ -11,18 +12,80 @@ import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
|
||||
import DetailedMeeting from "../../../api-bodies/DetailedMeeting";
|
||||
import { useAppSelector } from "../../../redux/hooks";
|
||||
import { selectUsers } from "../../../redux/slices/usersSlice";
|
||||
import { getMeetingStatus } from "../Utils";
|
||||
import UserLite from "../../../api-bodies/UserLite";
|
||||
|
||||
enum MeetingStatus {
|
||||
Live = "Live",
|
||||
Scheduled = "Scheduled",
|
||||
Finished = "Finished",
|
||||
}
|
||||
|
||||
interface Props {
|
||||
meeting: DetailedMeeting;
|
||||
}
|
||||
|
||||
const Body: React.FC<Props> = (props) => {
|
||||
const getMeetingStatus = (meeting: DetailedMeeting) => {
|
||||
const startDate = new Date(meeting.startTime);
|
||||
const endDate = new Date(startDate.getTime() + meeting.duration * 60000);
|
||||
const currentDate = new Date();
|
||||
if (meeting.liveParticipantIds.length === 0) {
|
||||
if (startDate.getTime() - currentDate.getTime() > 0)
|
||||
return MeetingStatus.Scheduled;
|
||||
else if (endDate.getTime() - currentDate.getTime() > 0)
|
||||
return MeetingStatus.Live;
|
||||
else return MeetingStatus.Finished;
|
||||
} else {
|
||||
return MeetingStatus.Live;
|
||||
}
|
||||
};
|
||||
|
||||
const Body: React.FC<Props> = ({ meeting }) => {
|
||||
const registrants: UserLite[] = useAppSelector((state) =>
|
||||
selectUsers(state, props.meeting.registrantIds)
|
||||
selectUsers(state, meeting.registrantIds)
|
||||
);
|
||||
const liveParticipants: UserLite[] = useAppSelector((state) =>
|
||||
selectUsers(state, meeting.liveParticipantIds)
|
||||
);
|
||||
|
||||
const parseIsoString = (s: string) => {
|
||||
const month = [
|
||||
"January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December",
|
||||
][parseInt(s.slice(5, 7)) - 1];
|
||||
const day = s.slice(8, 10);
|
||||
const isoTime: string = s.slice(11, 16);
|
||||
const hour: number = parseInt(isoTime.slice(0, 2));
|
||||
const hourMod: string = hour % 12 == 0 ? "12" : (hour % 12).toString();
|
||||
return (
|
||||
month +
|
||||
" " +
|
||||
day +
|
||||
", " +
|
||||
hourMod +
|
||||
":" +
|
||||
isoTime.slice(3, 5) +
|
||||
(hour > 11 ? "PM" : "AM")
|
||||
);
|
||||
};
|
||||
|
||||
const start = meeting.startTime;
|
||||
const startDateString = parseIsoString(start);
|
||||
const listLiveParticipants = () => {
|
||||
const s: string[] = liveParticipants.map((user) => user.name + ", ");
|
||||
return s.join("").slice(0, -2);
|
||||
};
|
||||
|
||||
const meetingStatus = getMeetingStatus(meeting);
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -39,16 +102,28 @@ const Body: React.FC<Props> = (props) => {
|
||||
flexDirection: "column",
|
||||
width: "70%",
|
||||
height: "100%",
|
||||
mt: 10,
|
||||
}}
|
||||
>
|
||||
<Typography>Feb 10, 2022 10:45 am - 11:00 am</Typography>
|
||||
<Typography>Status: {getMeetingStatus(props.meeting)}</Typography>
|
||||
<Typography>
|
||||
Topic: Lorem Ipsum is simply dummy text of the printing and
|
||||
typesetting industry. Lorem Ipsum has been the industrys standard
|
||||
dummy text ever since the 1500s. Topic: Lorem Ipsum is simply dummy
|
||||
text of the printing and typesetting industry. Lorem Ipsum has been
|
||||
the industrys standard dummy text ever since the 1500s.
|
||||
<Typography sx={{ pl: 5 }} variant="overline">
|
||||
Meeting ID: {meeting.meetingId}
|
||||
</Typography>
|
||||
<Typography sx={{ pl: 5, color: "#af000d" }} variant="h2">
|
||||
{meeting.topic}
|
||||
</Typography>
|
||||
<Divider sx={{ mb: 3 }} />
|
||||
<Typography sx={{ pl: 5 }} variant="h4">
|
||||
Start: {startDateString}
|
||||
</Typography>
|
||||
<Typography sx={{ pl: 5 }} variant="h4">
|
||||
Duration: {meeting.duration.toString() + " minutes"}
|
||||
</Typography>
|
||||
<Typography sx={{ my: 1 }} />
|
||||
<Typography sx={{ pl: 5 }} variant="button">
|
||||
Currently inside: {listLiveParticipants()}
|
||||
</Typography>
|
||||
<Typography sx={{ pl: 5 }} variant="button">
|
||||
Status: {meetingStatus}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
|
||||
@ -6,24 +6,27 @@ import { close } from "../../../redux/slices/meetingDetailsOpenSlice";
|
||||
import DetailedMeeting from "../../../api-bodies/DetailedMeeting";
|
||||
|
||||
interface Props {
|
||||
meeting: DetailedMeeting | null;
|
||||
meeting: DetailedMeeting;
|
||||
}
|
||||
|
||||
const Header: React.FC<Props> = (props) => {
|
||||
const Header: React.FC<Props> = ({ meeting }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
return (
|
||||
<AppBar sx={{ position: "relative" }}>
|
||||
<Toolbar>
|
||||
<Typography variant="h6" sx={{ flexGrow: 1 }}>
|
||||
{props.meeting !== null ? props.meeting.topic : null}
|
||||
{meeting !== null ? meeting.topic : null}
|
||||
</Typography>
|
||||
<Button variant="contained" color="secondary">
|
||||
<Button
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
onClick={() => {
|
||||
window.open(meeting.joinUrl, "_blank")?.focus();
|
||||
}}
|
||||
>
|
||||
Join
|
||||
</Button>
|
||||
<Button variant="contained" color="secondary" sx={{ ml: 1 }}>
|
||||
Recording
|
||||
</Button>
|
||||
<IconButton
|
||||
edge="start"
|
||||
color="inherit"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { Settings } from "@mui/icons-material";
|
||||
import { IconButton, Menu, MenuItem } from "@mui/material";
|
||||
import { Alert, IconButton, Menu, MenuItem, Snackbar } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import UserLite from "../../api-bodies/UserLite";
|
||||
import UserStatus from "../../api-bodies/UserStatus";
|
||||
@ -13,6 +13,8 @@ import { open as openMeetingDetails } from "../../redux/slices/meetingDetailsOpe
|
||||
import { selectMeeting } from "../../redux/slices/meetingsAndUserStatusSlice";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { selectMe } from "../../redux/slices/usersSlice";
|
||||
import NewMeeting from "../../api-bodies/NewMeeting";
|
||||
import axios from "../../api/axios";
|
||||
|
||||
interface Props {
|
||||
user: UserLite;
|
||||
@ -22,6 +24,7 @@ interface Props {
|
||||
const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||
const [snackbarOpen, setSnackbarOpen] = useState(false);
|
||||
const me = useAppSelector(selectMe);
|
||||
const favoritesUuids = useAppSelector(selectFavorites);
|
||||
const meeting = useAppSelector((state) =>
|
||||
@ -35,6 +38,30 @@ const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
|
||||
const handleClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleSnackbarClose = (event?: React.SyntheticEvent | Event, reason?: string) => {
|
||||
if (reason === "clickaway") {
|
||||
return;
|
||||
}
|
||||
setSnackbarOpen(false);
|
||||
};
|
||||
|
||||
const startCall = async () => {
|
||||
const newMeeting: NewMeeting = {
|
||||
startTime: "2022-03-30T23:40:00Z",
|
||||
duration: 30,
|
||||
topic: `Meeting with ${user.name}`,
|
||||
registrantIds: [user.uuid],
|
||||
};
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`/users/${me}/meetings`,
|
||||
JSON.stringify(newMeeting)
|
||||
);
|
||||
window.open(response.data.joinUrl, "_blank")?.focus();
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
@ -66,12 +93,14 @@ const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
setSnackbarOpen(true);
|
||||
handleClose();
|
||||
dispatch(addFavorite({ userId: me, toBeAdded: user.uuid }));
|
||||
}}
|
||||
>
|
||||
Add to Favorites
|
||||
</MenuItem>
|
||||
|
||||
)}
|
||||
{status.inMeeting ? (
|
||||
<MenuItem
|
||||
@ -91,8 +120,22 @@ const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
|
||||
>
|
||||
View upcoming meetings
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleClose}>Create meeting</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
handleClose();
|
||||
startCall();
|
||||
}}
|
||||
>
|
||||
Create meeting
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: "bottom", horizontal: "right" }}
|
||||
open={snackbarOpen} autoHideDuration={3000} onClose={handleSnackbarClose}>
|
||||
<Alert onClose={handleSnackbarClose} severity="success" sx={{ width: "100%" }}>
|
||||
User successfully added to favourites list!
|
||||
</Alert>
|
||||
</Snackbar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import { Middleware } from "redux";
|
||||
import { io, Socket } from "socket.io-client";
|
||||
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
||||
import { socketActions } from "../slices/meetingsAndUserStatusSlice";
|
||||
|
||||
const socketMiddleware: Middleware = (store) => {
|
||||
let socket: Socket;
|
||||
let socket: WebSocket;
|
||||
|
||||
return (next) => (action) => {
|
||||
const isConnectionEstablished =
|
||||
@ -14,28 +12,23 @@ const socketMiddleware: Middleware = (store) => {
|
||||
socketActions.startConnecting.match(action) &&
|
||||
!isConnectionEstablished
|
||||
) {
|
||||
console.log("startConnecting called");
|
||||
socket = io("wss://uo5wdcbn6l.execute-api.us-west-2.amazonaws.com/Prod/");
|
||||
|
||||
socket.on("Connect", () => {
|
||||
console.log("connected!!!");
|
||||
store.dispatch(socketActions.connectionEstablished());
|
||||
});
|
||||
|
||||
socket.on("MeetingCreated", (meeting: DetailedMeeting) => {
|
||||
store.dispatch(socketActions.meetingCreated(meeting));
|
||||
});
|
||||
|
||||
socket.on(
|
||||
"UserStatusChange",
|
||||
(statusChange: {
|
||||
userId: string;
|
||||
inMeeting: boolean;
|
||||
meetingId: string;
|
||||
}) => {
|
||||
store.dispatch(socketActions.userStatusChanged(statusChange));
|
||||
}
|
||||
console.log("start connecting");
|
||||
socket = new WebSocket(
|
||||
"wss://uo5wdcbn6l.execute-api.us-west-2.amazonaws.com/Prod/"
|
||||
);
|
||||
socket.onopen = () => {
|
||||
console.log("connected");
|
||||
store.dispatch(socketActions.connectionEstablished());
|
||||
};
|
||||
socket.addEventListener("message", (event: MessageEvent) => {
|
||||
const json = JSON.parse(event.data);
|
||||
console.log(json);
|
||||
if ("inMeeting" in json) {
|
||||
store.dispatch(socketActions.userStatusChanged(json));
|
||||
} else {
|
||||
store.dispatch(socketActions.meetingCreated(json));
|
||||
}
|
||||
});
|
||||
}
|
||||
next(action);
|
||||
};
|
||||
|
||||
@ -31,10 +31,10 @@ export const meetingsAndUserStatusSlice = createSlice({
|
||||
state.isConnected = true;
|
||||
},
|
||||
meetingCreated: (state, action) => {
|
||||
state.meetings.push(action.payload.meeting);
|
||||
state.meetings.push(action.payload);
|
||||
},
|
||||
userStatusChanged: (state, action) => {
|
||||
state.userStatuses[action.payload.uuid] = action.payload;
|
||||
state.userStatuses[action.payload.userId] = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
@ -90,9 +90,25 @@ export const selectMeeting = (state: RootState, meetingID: string | null) => {
|
||||
: null;
|
||||
};
|
||||
export const selectUserUpcomingMeetings = (state: RootState, uuid: string) => {
|
||||
return state.meetingsAndUserStatuses.meetings.filter((meeting) =>
|
||||
meeting.registrantIds.includes(uuid)
|
||||
);
|
||||
const isToday = (date: Date) => {
|
||||
const today = new Date();
|
||||
return (
|
||||
date.getDate() == today.getDate() &&
|
||||
date.getMonth() == today.getMonth() &&
|
||||
date.getFullYear() == today.getFullYear()
|
||||
);
|
||||
};
|
||||
return state.meetingsAndUserStatuses.meetings
|
||||
.filter(
|
||||
(meeting) =>
|
||||
meeting.registrantIds.includes(uuid) &&
|
||||
meeting.startTime &&
|
||||
isToday(new Date(meeting.startTime))
|
||||
)
|
||||
.sort(
|
||||
(a, b) =>
|
||||
new Date(a.startTime).getTime() - new Date(b.startTime).getTime()
|
||||
);
|
||||
};
|
||||
export const selectUserStatus = (
|
||||
state: RootState,
|
||||
|
||||
@ -47,14 +47,14 @@ export const fetchUsers = createAsyncThunk(
|
||||
};
|
||||
// fetch userfull
|
||||
const userResp = await axios.get(`/users/${uuid}`);
|
||||
const user: UserFull = userResp.data.user;
|
||||
|
||||
const user: UserFull = userResp.data["user"];
|
||||
console.log("1");
|
||||
users[user.userInfo.uuid] = user.userInfo;
|
||||
users[user.manager.uuid] = user.manager;
|
||||
console.log("2");
|
||||
if (user.manager) users[user.manager.uuid] = user.manager;
|
||||
|
||||
team.user = user.userInfo.uuid;
|
||||
team.manager = user.manager.uuid;
|
||||
|
||||
if (user.manager) team.manager = user.manager.uuid;
|
||||
user.userDirectReports.forEach((userLite) => {
|
||||
users[userLite.uuid] = userLite;
|
||||
team.directReports.push(userLite.uuid);
|
||||
@ -76,6 +76,7 @@ export const fetchUsers = createAsyncThunk(
|
||||
);
|
||||
|
||||
export const selectMe = (state: RootState) => state.users.team.user;
|
||||
export const selectManager = (state: RootState) => state.users.team.manager;
|
||||
export const selectUser = (
|
||||
state: RootState,
|
||||
uuid: string | undefined
|
||||
|
||||
@ -11,13 +11,13 @@ a {
|
||||
}
|
||||
|
||||
.main-home .meetings-panel {
|
||||
margin: auto;
|
||||
margin-top: 15vh;
|
||||
padding-bottom: 10vh;
|
||||
border-style: solid;
|
||||
border-color: #D3D3D3;
|
||||
border-color: mistyrose;
|
||||
border-width: 2px;
|
||||
border-radius: 40px;
|
||||
width: 65%;
|
||||
width: 85%;
|
||||
height: 65vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
@ -28,24 +28,24 @@ a {
|
||||
|
||||
.main-home .meetings-panel .panel-label {
|
||||
text-align: center;
|
||||
background-color: grey;
|
||||
background-color: #AF000D;
|
||||
padding-top: 2%;
|
||||
padding-bottom: 2%;
|
||||
}
|
||||
|
||||
.main-home .meetings-panel .meetingEntries::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-home .meetings-panel .meeting {
|
||||
background-color: #D3D3D3;
|
||||
text-align: center;
|
||||
background-color: lightcoral;
|
||||
text-align: left;
|
||||
margin-top: 15px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.main-home .meetings-panel .lastMeeting {
|
||||
margin-bottom: 10%;
|
||||
}
|
||||
|
||||
.main-home .short-cuts .row-1 {
|
||||
margin-top: 30vh;
|
||||
margin-top: 40vh;
|
||||
}
|
||||
|
||||
.main-home .short-cuts .row-2 {
|
||||
@ -54,23 +54,24 @@ a {
|
||||
|
||||
.main-home .short-cuts .mylabel {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
width: 100px;
|
||||
margin-left: 25%;
|
||||
width: 125px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main-home .short-cuts .tile {
|
||||
margin-right: 100%;
|
||||
background-color: #D3D3D3;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin-left: 25%;
|
||||
margin-right: 50%;
|
||||
background-color: mistyrose;
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.main-home .short-cuts .tile .icon {
|
||||
color: black;
|
||||
width: 70%;
|
||||
height: 70%;
|
||||
width: 80%;
|
||||
height: 80%;
|
||||
}
|
||||
|
||||
.login .grid-container {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 3,
|
||||
"mappings": "AG8CA,AAAA,WAAW,CAAC;EACR,KAAK,EAAE,IAAI;CAAG;;AAElB,AAAA,CAAC,CAAC;EACE,eAAe,EAAE,IAAI;CAAG;;AKlD5B,AAAA,UAAU,CAAC;EAGP,SAAS,EAAE,cAAc;CAqDO;;AAxDpC,AAII,UAJM,CAIN,eAAe,CAAC;EACZ,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,IAAI;EAChB,YAAY,EAAE,KAAK;EACnB,YAAY,EPRS,OAAO;EOS5B,YAAY,EAAE,GAAG;EACjB,aAAa,EAAE,IAAI;EACnB,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI;EACZ,QAAQ,EAAE,MAAM;CAuBO;;AApC/B,AAcQ,UAdE,CAIN,eAAe,CAUX,IAAI,CAAC;EACD,KAAK,EAAE,IAAI;CAAG;;AAf1B,AAgBQ,UAhBE,CAIN,eAAe,CAYX,YAAY,CAAC;EACT,UAAU,EAAE,MAAM;EAClB,gBAAgB,EAAE,IAAI;EACtB,WAAW,EAAE,EAAE;EACf,cAAc,EAAE,EAAE;CAAG;;AApBjC,AA8BQ,UA9BE,CAIN,eAAe,CA0BX,eAAe,AAAA,mBAAmB,CAAC;EAC/B,OAAO,EAAE,IAAI;CAAG;;AA/B5B,AAgCQ,UAhCE,CAIN,eAAe,CA4BX,QAAQ,CAAC;EACL,gBAAgB,EPjCC,OAAO;EOkCxB,UAAU,EAAE,MAAM;EAClB,UAAU,EAAE,IAAI;EAChB,WAAW,EAAE,CAAC;CAAG;;AApC7B,AAsCQ,UAtCE,CAqCN,WAAW,CACP,MAAM,CAAC;EACH,UAAU,EAAE,IAAI;CAAG;;AAvC/B,AAwCQ,UAxCE,CAqCN,WAAW,CAGP,MAAM,CAAC;EACH,UAAU,EAAE,GAAG;CAAG;;AAzC9B,AA0CQ,UA1CE,CAqCN,WAAW,CAKP,QAAQ,CAAC;EACL,OAAO,EAAE,YAAY;EACrB,MAAM,EAAE,CAAC;EACT,KAAK,EAAE,KAAK;EACZ,UAAU,EAAE,MAAM;CAAG;;AA9CjC,AA+CQ,UA/CE,CAqCN,WAAW,CAUP,KAAK,CAAC;EACF,YAAY,EAAE,IAAI;EAClB,gBAAgB,EPjDC,OAAO;EOkDxB,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;EACb,aAAa,EAAE,IAAI;CAIC;;AAxDhC,AAqDY,UArDF,CAqCN,WAAW,CAUP,KAAK,CAMD,KAAK,CAAC;EACF,KAAK,EAAE,KAAK;EACZ,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,GAAG;CAAG;;ACxD9B,AACI,MADE,CACF,eAAe,CAAC;EACZ,WAAW,EAAE,MAAM;EACnB,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,GAAG;EACf,SAAS,EAAE,cAAc;CAqBC;;AA1BlC,AAMQ,MANF,CACF,eAAe,GAKT,CAAC,CAAC;EACA,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,MAAM;CAAG;;AAR7B,AASQ,MATF,CACF,eAAe,CAQX,WAAW,CAAC;EACR,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,MAAM;EAClB,MAAM,EAAE,IAAI;CAAG;;AAZ3B,AAaQ,MAbF,CACF,eAAe,CAYX,UAAU,CAAE;EACR,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,GAAG;EACV,gBAAgB,EAAE,OAAO;CAAG;;AAhBxC,AAiBQ,MAjBF,CACF,eAAe,CAgBX,aAAa,CAAC;EACV,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,IAAI;EACZ,WAAW,EAAE,IAAI;CAAG;;AArBhC,AAsBQ,MAtBF,CACF,eAAe,CAqBX,UAAU,CAAC;EACP,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;CAEQ;;AA1BhC,AAyBY,MAzBN,CACF,eAAe,CAqBX,UAAU,CAGN,CAAC,CAAC;EACE,KAAK,EAAE,IAAI;CAAG",
|
||||
"mappings": "AG8CA,AAAA,WAAW,CAAC;EACR,KAAK,EAAE,IAAI;CAAG;;AAElB,AAAA,CAAC,CAAC;EACE,eAAe,EAAE,IAAI;CAAG;;AKlD5B,AAAA,UAAU,CAAC;EAGP,SAAS,EAAE,cAAc;CAuDO;;AA1DpC,AAII,UAJM,CAIN,eAAe,CAAC;EACZ,UAAU,EAAE,IAAI;EAChB,cAAc,EAAE,IAAI;EACpB,YAAY,EAAE,KAAK;EACnB,YAAY,EAAE,SAAS;EACvB,YAAY,EAAE,GAAG;EACjB,aAAa,EAAE,IAAI;EACnB,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,IAAI;EACZ,QAAQ,EAAE,MAAM;CAuBW;;AApCnC,AAcQ,UAdE,CAIN,eAAe,CAUX,IAAI,CAAC;EACD,KAAK,EAAE,IAAI;CAAG;;AAf1B,AAgBQ,UAhBE,CAIN,eAAe,CAYX,YAAY,CAAC;EACT,UAAU,EAAE,MAAM;EAClB,gBAAgB,EAAE,OAAO;EACzB,WAAW,EAAE,EAAE;EACf,cAAc,EAAE,EAAE;CAAG;;AApBjC,AA8BQ,UA9BE,CAIN,eAAe,CA0BX,QAAQ,CAAC;EACL,gBAAgB,EAAE,UAAU;EAC5B,UAAU,EAAE,IAAI;EAChB,UAAU,EAAE,IAAI;EAChB,WAAW,EAAE,CAAC;CAAG;;AAlC7B,AAmCQ,UAnCE,CAIN,eAAe,CA+BX,YAAY,CAAC;EACT,aAAa,EAAE,GAAG;CAAG;;AApCjC,AAsCQ,UAtCE,CAqCN,WAAW,CACP,MAAM,CAAC;EAEH,UAAU,EAAE,IAAI;CAAG;;AAxC/B,AAyCQ,UAzCE,CAqCN,WAAW,CAIP,MAAM,CAAC;EACH,UAAU,EAAE,GAAG;CAAG;;AA1C9B,AA2CQ,UA3CE,CAqCN,WAAW,CAMP,QAAQ,CAAC;EACL,OAAO,EAAE,YAAY;EACrB,WAAW,EAAE,GAAG;EAChB,KAAK,EAAE,KAAK;EACZ,UAAU,EAAE,MAAM;CAAG;;AA/CjC,AAgDQ,UAhDE,CAqCN,WAAW,CAWP,KAAK,CAAC;EACF,WAAW,EAAE,GAAG;EAChB,YAAY,EAAE,GAAG;EACjB,gBAAgB,EAAE,SAAS;EAC3B,KAAK,EAAE,KAAK;EACZ,MAAM,EAAE,KAAK;EACb,aAAa,EAAE,IAAI;CAIC;;AA1DhC,AAuDY,UAvDF,CAqCN,WAAW,CAWP,KAAK,CAOD,KAAK,CAAC;EACF,KAAK,EAAE,KAAK;EACZ,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,GAAG;CAAG;;AC1D9B,AACI,MADE,CACF,eAAe,CAAC;EACZ,WAAW,EAAE,MAAM;EACnB,MAAM,EAAE,IAAI;EACZ,UAAU,EAAE,GAAG;EACf,SAAS,EAAE,cAAc;CAqBC;;AA1BlC,AAMQ,MANF,CACF,eAAe,GAKT,CAAC,CAAC;EACA,KAAK,EAAE,GAAG;EACV,MAAM,EAAE,MAAM;CAAG;;AAR7B,AASQ,MATF,CACF,eAAe,CAQX,WAAW,CAAC;EACR,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,MAAM;EAClB,MAAM,EAAE,IAAI;CAAG;;AAZ3B,AAaQ,MAbF,CACF,eAAe,CAYX,UAAU,CAAE;EACR,MAAM,EAAE,IAAI;EACZ,KAAK,EAAE,GAAG;EACV,gBAAgB,EAAE,OAAO;CAAG;;AAhBxC,AAiBQ,MAjBF,CACF,eAAe,CAgBX,aAAa,CAAC;EACV,OAAO,EAAE,YAAY;EACrB,UAAU,EAAE,IAAI;EAChB,MAAM,EAAE,IAAI;EACZ,WAAW,EAAE,IAAI;CAAG;;AArBhC,AAsBQ,MAtBF,CACF,eAAe,CAqBX,UAAU,CAAC;EACP,KAAK,EAAE,IAAI;EACX,MAAM,EAAE,IAAI;CAEQ;;AA1BhC,AAyBY,MAzBN,CACF,eAAe,CAqBX,UAAU,CAGN,CAAC,CAAC;EACE,KAAK,EAAE,IAAI;CAAG",
|
||||
"sources": [
|
||||
"App.sass",
|
||||
"_variables.sass",
|
||||
|
||||
@ -3,20 +3,20 @@
|
||||
// width: 100% !important
|
||||
max-width: 85% !important
|
||||
.meetings-panel
|
||||
margin: auto
|
||||
margin-top: 15vh
|
||||
padding-bottom: 10vh
|
||||
border-style: solid
|
||||
border-color: $main-home-background-color
|
||||
border-color: mistyrose
|
||||
border-width: 2px
|
||||
border-radius: 40px
|
||||
width: 65%
|
||||
width: 85%
|
||||
height: 65vh
|
||||
overflow: hidden
|
||||
.row
|
||||
width: 100%
|
||||
.panel-label
|
||||
text-align: center
|
||||
background-color: grey
|
||||
background-color: #AF000D
|
||||
padding-top: 2%
|
||||
padding-bottom: 2%
|
||||
// .current-time
|
||||
@ -29,27 +29,31 @@
|
||||
// background-color: grey
|
||||
// padding-bottom: 1%
|
||||
.meeting
|
||||
background-color: $main-home-background-color
|
||||
text-align: center
|
||||
background-color: lightcoral
|
||||
text-align: left
|
||||
margin-top: 15px
|
||||
line-height: 2
|
||||
.lastMeeting
|
||||
margin-bottom: 10%
|
||||
.short-cuts
|
||||
.row-1
|
||||
margin-top: 30vh
|
||||
// margin-top: 30vh
|
||||
margin-top: 40vh
|
||||
.row-2
|
||||
margin-top: 5vh
|
||||
.mylabel
|
||||
display: inline-block
|
||||
margin: 0
|
||||
width: 100px
|
||||
margin-left: 25%
|
||||
width: 125px
|
||||
text-align: center
|
||||
.tile
|
||||
margin-right: 100%
|
||||
background-color: $main-home-background-color
|
||||
width: 100px
|
||||
height: 100px
|
||||
margin-left: 25%
|
||||
margin-right: 50%
|
||||
background-color: mistyrose
|
||||
width: 125px
|
||||
height: 125px
|
||||
border-radius: 16px
|
||||
.icon
|
||||
color: black
|
||||
width: 70%
|
||||
height: 70%
|
||||
width: 80%
|
||||
height: 80%
|
||||
@ -1,6 +1,7 @@
|
||||
const enum MeetingStatus {
|
||||
NOT_IN_MEETING = "Not in meeting",
|
||||
IN_MEETING = "In meeting",
|
||||
NOT_AVAILABLE = "Not available",
|
||||
}
|
||||
|
||||
const getStatusColor = (ms: MeetingStatus): string => {
|
||||
@ -11,6 +12,9 @@ const getStatusColor = (ms: MeetingStatus): string => {
|
||||
case MeetingStatus.IN_MEETING: {
|
||||
return "#ff7070";
|
||||
}
|
||||
case MeetingStatus.NOT_AVAILABLE: {
|
||||
return "#808080";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user