sidebar changes
This commit is contained in:
parent
16587686d4
commit
19906db3da
@ -22,7 +22,8 @@ const App: React.FC = () => {
|
|||||||
<Route path="/login" element={<Login />} />
|
<Route path="/login" element={<Login />} />
|
||||||
<Route element={<ProtectedRoute />}>
|
<Route element={<ProtectedRoute />}>
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
<Route path="/contacts" element={<Contacts />} />
|
<Route path="/contacts/" element={<Contacts />} />
|
||||||
|
<Route path="/contacts/:uuid" element={<Contacts />} />
|
||||||
<Route path="/calendar" element={<CalendarPage />} />
|
<Route path="/calendar" element={<CalendarPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|||||||
@ -2,11 +2,6 @@ import {useLocation, Outlet, Navigate} from "react-router-dom";
|
|||||||
import useAuth from "./hooks/useAuth";
|
import useAuth from "./hooks/useAuth";
|
||||||
import Navbar from "./components/navbar/Navbar";
|
import Navbar from "./components/navbar/Navbar";
|
||||||
import Sidebar from "./components/sidebar/Sidebar";
|
import Sidebar from "./components/sidebar/Sidebar";
|
||||||
import React, { useState } from "react";
|
|
||||||
import {
|
|
||||||
MeetingStatus,
|
|
||||||
SidebarUserObj,
|
|
||||||
} from "./components/sidebar/SidebarUser";
|
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
const ProtectedRoute = () => {
|
const ProtectedRoute = () => {
|
||||||
@ -14,29 +9,6 @@ const ProtectedRoute = () => {
|
|||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
/* Temporary data */
|
/* Temporary data */
|
||||||
const [sidebarUsers] = useState<SidebarUserObj[]>([
|
|
||||||
{ id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.ONLINE },
|
|
||||||
{ id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING },
|
|
||||||
{ id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.OFFLINE },
|
|
||||||
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.AWAY },
|
|
||||||
{ id: 4, name: "Bob B.", meetingStatus: MeetingStatus.IN_MEETING },
|
|
||||||
{ id: 5, name: "Bob C.", meetingStatus: MeetingStatus.OFFLINE },
|
|
||||||
{ id: 6, name: "Bob D.", meetingStatus: MeetingStatus.ONLINE },
|
|
||||||
{ id: 7, name: "Bob E.", meetingStatus: MeetingStatus.AWAY },
|
|
||||||
{ id: 8, name: "Bob F.", meetingStatus: MeetingStatus.OFFLINE },
|
|
||||||
{ id: 9, name: "Bob G.", meetingStatus: MeetingStatus.ONLINE },
|
|
||||||
{ id: 10, name: "Bob H.", meetingStatus: MeetingStatus.AWAY },
|
|
||||||
{ id: 11, name: "Bob I.", meetingStatus: MeetingStatus.OFFLINE },
|
|
||||||
{ id: 12, name: "Bob J.", meetingStatus: MeetingStatus.AWAY },
|
|
||||||
{ id: 13, name: "Bob K.", meetingStatus: MeetingStatus.IN_MEETING },
|
|
||||||
{ id: 14, name: "Bob L.", meetingStatus: MeetingStatus.IN_MEETING },
|
|
||||||
{ id: 15, name: "Bob M.", meetingStatus: MeetingStatus.OFFLINE },
|
|
||||||
{ id: 16, name: "Bob N.", meetingStatus: MeetingStatus.OFFLINE },
|
|
||||||
{ id: 17, name: "Bob O.", meetingStatus: MeetingStatus.AWAY },
|
|
||||||
{ id: 18, name: "Bob P.", meetingStatus: MeetingStatus.AWAY },
|
|
||||||
{ id: 19, name: "Bob Q.", meetingStatus: MeetingStatus.ONLINE },
|
|
||||||
{ id: 20, name: "Bob R.", meetingStatus: MeetingStatus.ONLINE },
|
|
||||||
]);
|
|
||||||
return auth?.isLoggedIn ? (
|
return auth?.isLoggedIn ? (
|
||||||
<>
|
<>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
@ -45,7 +17,7 @@ const ProtectedRoute = () => {
|
|||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
<Box>
|
<Box>
|
||||||
<Sidebar users={sidebarUsers} />
|
<Sidebar />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -4,18 +4,20 @@ import Body from "./contacts-components/Body";
|
|||||||
|
|
||||||
import Sidebar from "./contacts-components/Sidebar";
|
import Sidebar from "./contacts-components/Sidebar";
|
||||||
import UserLite from "../../api-bodies/UserLite";
|
import UserLite from "../../api-bodies/UserLite";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { selectUser } from "../../redux/slices/usersSlice";
|
||||||
|
import { useAppSelector } from "../../redux/hooks";
|
||||||
|
|
||||||
const Contacts: React.FC = () => {
|
const Contacts: React.FC = () => {
|
||||||
const [contactSelected, setContactSelected] = React.useState<UserLite | null>(
|
const { uuid } = useParams();
|
||||||
null
|
const uriContact: UserLite | undefined = useAppSelector((state) =>
|
||||||
|
selectUser(state, uuid)
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", height: "100%" }}>
|
<Box sx={{ display: "flex", height: "100%" }}>
|
||||||
<Sidebar setContactSelected={setContactSelected} />
|
<Sidebar />
|
||||||
{contactSelected !== null ? (
|
{uriContact ? <Body contactSelected={uriContact} /> : null}
|
||||||
<Body contactSelected={contactSelected} />
|
|
||||||
) : null}
|
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +0,0 @@
|
|||||||
enum Status {
|
|
||||||
Online = "Online",
|
|
||||||
Offline = "Offline",
|
|
||||||
Away = "Away",
|
|
||||||
InMeeting = "In Meeting",
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Status;
|
|
||||||
@ -1,20 +1,4 @@
|
|||||||
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
import DetailedMeeting from "../../api-bodies/DetailedMeeting";
|
||||||
import Status from "./Status";
|
|
||||||
|
|
||||||
const returnStatusColor = (
|
|
||||||
status: Status
|
|
||||||
): "success" | "disabled" | "warning" | "error" => {
|
|
||||||
switch (status) {
|
|
||||||
case Status.Online:
|
|
||||||
return "success";
|
|
||||||
case Status.Offline:
|
|
||||||
return "disabled";
|
|
||||||
case Status.Away:
|
|
||||||
return "warning";
|
|
||||||
case Status.InMeeting:
|
|
||||||
return "error";
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
||||||
const startDate = new Date(meeting.start);
|
const startDate = new Date(meeting.start);
|
||||||
@ -34,4 +18,4 @@ const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
|||||||
return `${startTime} - ${endTime}`;
|
return `${startTime} - ${endTime}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export { returnStatusColor, getUpcomingMeetingTime };
|
export { getUpcomingMeetingTime };
|
||||||
|
|||||||
@ -13,15 +13,10 @@ import ContactItem from "./sidebar-components/ContactItem";
|
|||||||
import { useAppSelector } from "../../../redux/hooks";
|
import { useAppSelector } from "../../../redux/hooks";
|
||||||
import { selectUsers, selectTeam } from "../../../redux/slices/usersSlice";
|
import { selectUsers, selectTeam } from "../../../redux/slices/usersSlice";
|
||||||
import { selectFavorites } from "../../../redux/slices/favoritesSlice";
|
import { selectFavorites } from "../../../redux/slices/favoritesSlice";
|
||||||
import UserLite from "../../../api-bodies/UserLite";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
setContactSelected: (contactInfo: UserLite) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sidebarWidth = 240;
|
const sidebarWidth = 240;
|
||||||
|
|
||||||
const Sidebar: React.FC<Props> = (props) => {
|
const Sidebar: React.FC = () => {
|
||||||
const [favoritesOpen, setFavoritesOpen] = React.useState<boolean>(true);
|
const [favoritesOpen, setFavoritesOpen] = React.useState<boolean>(true);
|
||||||
const [teamOpen, setTeamOpen] = React.useState<boolean>(false);
|
const [teamOpen, setTeamOpen] = React.useState<boolean>(false);
|
||||||
|
|
||||||
@ -29,7 +24,8 @@ const Sidebar: React.FC<Props> = (props) => {
|
|||||||
const favorites = useAppSelector((state) =>
|
const favorites = useAppSelector((state) =>
|
||||||
selectUsers(state, favoritesUuids)
|
selectUsers(state, favoritesUuids)
|
||||||
);
|
);
|
||||||
const team = useAppSelector(selectTeam);
|
const teamUuids = useAppSelector(selectTeam);
|
||||||
|
const team = useAppSelector((state) => selectUsers(state, teamUuids));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
@ -58,11 +54,7 @@ const Sidebar: React.FC<Props> = (props) => {
|
|||||||
<Collapse in={favoritesOpen} timeout="auto" unmountOnExit>
|
<Collapse in={favoritesOpen} timeout="auto" unmountOnExit>
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
{favorites.map((favorite, i) => (
|
{favorites.map((favorite, i) => (
|
||||||
<ContactItem
|
<ContactItem contactInfo={favorite} key={i} />
|
||||||
contactInfo={favorite}
|
|
||||||
key={i}
|
|
||||||
setContactSelected={props.setContactSelected}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
@ -74,11 +66,7 @@ const Sidebar: React.FC<Props> = (props) => {
|
|||||||
<Collapse in={teamOpen} timeout="auto" unmountOnExit>
|
<Collapse in={teamOpen} timeout="auto" unmountOnExit>
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
{team.map((member, i) => (
|
{team.map((member, i) => (
|
||||||
<ContactItem
|
<ContactItem contactInfo={member} key={i} />
|
||||||
contactInfo={member}
|
|
||||||
key={i}
|
|
||||||
setContactSelected={props.setContactSelected}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
</Collapse>
|
</Collapse>
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import PhoneIcon from "@mui/icons-material/Phone";
|
|||||||
import VideocamIcon from "@mui/icons-material/Videocam";
|
import VideocamIcon from "@mui/icons-material/Videocam";
|
||||||
import GroupsIcon from "@mui/icons-material/Groups";
|
import GroupsIcon from "@mui/icons-material/Groups";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import Status from "../../Status";
|
|
||||||
import UserLite from "../../../../api-bodies/UserLite";
|
import UserLite from "../../../../api-bodies/UserLite";
|
||||||
import UserStatus from "../../../../api-bodies/UserStatus";
|
import UserStatus from "../../../../api-bodies/UserStatus";
|
||||||
import { useAppSelector, useAppDispatch } from "../../../../redux/hooks";
|
import { useAppSelector, useAppDispatch } from "../../../../redux/hooks";
|
||||||
@ -18,6 +17,7 @@ import {
|
|||||||
removeFavorite,
|
removeFavorite,
|
||||||
} from "../../../../redux/slices/favoritesSlice";
|
} from "../../../../redux/slices/favoritesSlice";
|
||||||
import RemoveIcon from "@mui/icons-material/Remove";
|
import RemoveIcon from "@mui/icons-material/Remove";
|
||||||
|
import { MeetingStatus } from "../../../../utils";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contactInfo: UserLite;
|
contactInfo: UserLite;
|
||||||
@ -25,15 +25,17 @@ interface Props {
|
|||||||
|
|
||||||
const UpperBody: React.FC<Props> = (props) => {
|
const UpperBody: React.FC<Props> = (props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const userStatus: UserStatus | null = useAppSelector((state) =>
|
const userStatus: UserStatus = useAppSelector((state) =>
|
||||||
selectUserStatus(state, props.contactInfo.uuid)
|
selectUserStatus(state, props.contactInfo.uuid)
|
||||||
);
|
);
|
||||||
const favoritesUuids = useAppSelector(selectFavorites);
|
const favoritesUuids = useAppSelector(selectFavorites);
|
||||||
const detailedMeeting = useAppSelector((state) =>
|
const detailedMeeting = useAppSelector((state) =>
|
||||||
selectMeeting(state, userStatus.meetingID)
|
selectMeeting(state, userStatus.meetingID)
|
||||||
);
|
);
|
||||||
const status: Status =
|
const meetingStatus: MeetingStatus =
|
||||||
userStatus && userStatus.inMeeting ? Status.Online : Status.Offline;
|
userStatus && userStatus.inMeeting
|
||||||
|
? MeetingStatus.IN_MEETING
|
||||||
|
: MeetingStatus.NOT_IN_MEETING;
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -102,7 +104,7 @@ const UpperBody: React.FC<Props> = (props) => {
|
|||||||
</IconButton>
|
</IconButton>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||||
<Typography sx={{ textAlign: "right" }}>{status}</Typography>
|
<Typography sx={{ textAlign: "right" }}>{meetingStatus}</Typography>
|
||||||
{detailedMeeting ? (
|
{detailedMeeting ? (
|
||||||
<Typography>{detailedMeeting.topic}</Typography>
|
<Typography>{detailedMeeting.topic}</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@ -2,28 +2,30 @@ import { ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
|
import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
|
||||||
import CircleIcon from "@mui/icons-material/Circle";
|
import CircleIcon from "@mui/icons-material/Circle";
|
||||||
import { returnStatusColor } from "../../Utils";
|
|
||||||
import UserLite from "../../../../api-bodies/UserLite";
|
import UserLite from "../../../../api-bodies/UserLite";
|
||||||
import Status from "../../Status";
|
import { MeetingStatus, getStatusColor } from "../../../../utils";
|
||||||
import { useAppSelector } from "../../../../redux/hooks";
|
import { useAppSelector } from "../../../../redux/hooks";
|
||||||
import { selectUserStatus } from "../../../../redux/slices/meetingsAndUserStatusSlice";
|
import { selectUserStatus } from "../../../../redux/slices/meetingsAndUserStatusSlice";
|
||||||
import UserStatus from "../../../../api-bodies/UserStatus";
|
import UserStatus from "../../../../api-bodies/UserStatus";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
contactInfo: UserLite;
|
contactInfo: UserLite;
|
||||||
setContactSelected: (contactInfo: UserLite) => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ContactItem: React.FC<Props> = (props) => {
|
const ContactItem: React.FC<Props> = (props) => {
|
||||||
|
const navigate = useNavigate();
|
||||||
const userStatus: UserStatus | null = useAppSelector((state) =>
|
const userStatus: UserStatus | null = useAppSelector((state) =>
|
||||||
selectUserStatus(state, props.contactInfo.uuid)
|
selectUserStatus(state, props.contactInfo.uuid)
|
||||||
);
|
);
|
||||||
const status: Status =
|
const status: MeetingStatus =
|
||||||
userStatus && userStatus.inMeeting ? Status.Online : Status.Offline;
|
userStatus && userStatus.inMeeting
|
||||||
|
? MeetingStatus.IN_MEETING
|
||||||
|
: MeetingStatus.NOT_IN_MEETING;
|
||||||
return (
|
return (
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.setContactSelected(props.contactInfo);
|
navigate(`/contacts/${props.contactInfo.uuid}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
@ -34,7 +36,7 @@ const ContactItem: React.FC<Props> = (props) => {
|
|||||||
secondary={status}
|
secondary={status}
|
||||||
sx={{ flexGrow: 1 }}
|
sx={{ flexGrow: 1 }}
|
||||||
/>
|
/>
|
||||||
<CircleIcon color={returnStatusColor(status)} />
|
<CircleIcon sx={{ color: getStatusColor(status) }} />
|
||||||
</ListItemButton>
|
</ListItemButton>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,15 @@
|
|||||||
import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, FormGroup, Typography } from "@mui/material";
|
import {
|
||||||
import { SidebarUserObj } from "../sidebar/SidebarUser";
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Dialog,
|
||||||
|
DialogActions,
|
||||||
|
DialogContent,
|
||||||
|
DialogTitle,
|
||||||
|
FormControlLabel,
|
||||||
|
FormGroup,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import { SidebarUserObj } from "./Home";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@ -8,33 +18,42 @@ interface Props {
|
|||||||
users: SidebarUserObj[];
|
users: SidebarUserObj[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const CallFavouritesDialog: React.FC<Props> = ({ open, selectedValue, onClose, users }: Props) => {
|
const CallFavouritesDialog: React.FC<Props> = ({
|
||||||
|
open,
|
||||||
|
selectedValue,
|
||||||
|
onClose,
|
||||||
|
users,
|
||||||
|
}: Props) => {
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
onClose(selectedValue);
|
onClose(selectedValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog
|
<Dialog onClose={handleClose} open={open} fullWidth maxWidth="sm">
|
||||||
onClose={handleClose}
|
|
||||||
open={open}
|
|
||||||
fullWidth
|
|
||||||
maxWidth="sm"
|
|
||||||
>
|
|
||||||
<DialogTitle>Select who to call:</DialogTitle>
|
<DialogTitle>Select who to call:</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<FormControlLabel control={<Checkbox color="success" />} label="Select everyone" />
|
<FormControlLabel
|
||||||
|
control={<Checkbox color="success" />}
|
||||||
|
label="Select everyone"
|
||||||
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogContent sx={{ height: "40vh" }} dividers>
|
<DialogContent sx={{ height: "40vh" }} dividers>
|
||||||
<FormGroup>
|
<FormGroup>
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<FormControlLabel key={user.id} control={<Checkbox color="success" />} label={user.name} />
|
<FormControlLabel
|
||||||
|
key={user.id}
|
||||||
|
control={<Checkbox color="success" />}
|
||||||
|
label={user.name}
|
||||||
|
/>
|
||||||
))}
|
))}
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button color="success" onClick={handleClose}>
|
<Button color="success" onClick={handleClose}>
|
||||||
<Typography variant="button" color="black">Call</Typography>
|
<Typography variant="button" color="black">
|
||||||
|
Call
|
||||||
|
</Typography>
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@ -2,22 +2,26 @@ import MeetingsPanel from "./MeetingsPanel";
|
|||||||
import ShortCuts from "./ShortCuts";
|
import ShortCuts from "./ShortCuts";
|
||||||
import Container from "@mui/material/Container";
|
import Container from "@mui/material/Container";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import { MeetingStatus, SidebarUserObj } from "../sidebar/SidebarUser";
|
import { MeetingStatus } from "../../utils";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
export interface SidebarUserObj {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
meetingStatus: MeetingStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Home: React.FC = () => {
|
||||||
/* Temporary data - this is the same as what's in ProtectedRoute.tsx so it should not
|
/* 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
|
be duplicated like this in the future (Route components were being weird when I tried
|
||||||
to pass it down from App.tsx) */
|
to pass it down from App.tsx) */
|
||||||
const [mockUsers] = useState<SidebarUserObj[]>([
|
const [mockUsers] = useState<SidebarUserObj[]>([
|
||||||
{ id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.ONLINE },
|
{ id: 0, name: "Jincheng L.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||||
{ id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING },
|
{ id: 1, name: "Matt B.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||||
{ id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.OFFLINE },
|
{ id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||||
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.AWAY }
|
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container className="main-home">
|
<Container className="main-home">
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import CircleIcon from "@mui/icons-material/Circle";
|
|||||||
import Button from "@mui/material/Button";
|
import Button from "@mui/material/Button";
|
||||||
import Grid from "@mui/material/Grid";
|
import Grid from "@mui/material/Grid";
|
||||||
import Typography from "@mui/material/Typography";
|
import Typography from "@mui/material/Typography";
|
||||||
import { SidebarUserObj } from "../sidebar/SidebarUser";
|
import { SidebarUserObj } from "./Home";
|
||||||
import CallFavouritesDialog from "./CallFavouritesDialog";
|
import CallFavouritesDialog from "./CallFavouritesDialog";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@ -33,13 +33,17 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
|||||||
<Button className="tile">
|
<Button className="tile">
|
||||||
<PeopleIcon className="icon" />
|
<PeopleIcon className="icon" />
|
||||||
</Button>
|
</Button>
|
||||||
<Typography className="mylabel" sx={{ ml: 1 }}>New Meeting</Typography>
|
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||||
|
New Meeting
|
||||||
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item sm={6}>
|
<Grid item sm={6}>
|
||||||
<Button className="tile">
|
<Button className="tile">
|
||||||
<AddIcon className="icon" />
|
<AddIcon className="icon" />
|
||||||
</Button>
|
</Button>
|
||||||
<Typography className="mylabel" sx={{ ml: 1 }}>Join</Typography>
|
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||||
|
Join
|
||||||
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid className="row-2" container spacing={1}>
|
<Grid className="row-2" container spacing={1}>
|
||||||
@ -51,14 +55,19 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
|||||||
selectedValue={selectedValue}
|
selectedValue={selectedValue}
|
||||||
open={open}
|
open={open}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
users={users}/>
|
users={users}
|
||||||
<Typography className="mylabel" sx={{ ml: 1 }}>Call Favourites</Typography>
|
/>
|
||||||
|
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||||
|
Call Favourites
|
||||||
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item sm={6}>
|
<Grid item sm={6}>
|
||||||
<Button className="tile">
|
<Button className="tile">
|
||||||
<CircleIcon className="icon" />
|
<CircleIcon className="icon" />
|
||||||
</Button>
|
</Button>
|
||||||
<Typography className="mylabel" sx={{ ml: 1 }}>Recordings</Typography>
|
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||||
|
Recordings
|
||||||
|
</Typography>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,14 +1,25 @@
|
|||||||
import { FormControl, InputLabel, MenuItem, Select, SelectChangeEvent, Typography } from "@mui/material";
|
import {
|
||||||
import { useState } from "react";
|
FormControl,
|
||||||
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
|
Select,
|
||||||
|
SelectChangeEvent,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
// TODO: change MenuItem values to something meaningful
|
// TODO: change MenuItem values to something meaningful
|
||||||
|
|
||||||
const GroupSelect: React.FC = () => {
|
interface Props {
|
||||||
|
group: string;
|
||||||
|
setGroup: (group: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const [group, setGroup] = useState("");
|
const groups = ["Favorites", "Team"];
|
||||||
|
|
||||||
const handleChange = (event: SelectChangeEvent) => {
|
const GroupSelect: React.FC<Props> = ({ group, setGroup }) => {
|
||||||
setGroup(event.target.value);
|
const handleChange = (e: SelectChangeEvent) => {
|
||||||
|
setGroup(e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -23,15 +34,11 @@ const GroupSelect: React.FC = () => {
|
|||||||
label="Group"
|
label="Group"
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
>
|
>
|
||||||
<MenuItem value={0}>
|
{groups.map((group, i) => (
|
||||||
<Typography color="black">Favourites</Typography>
|
<MenuItem value={group} key={i}>
|
||||||
</MenuItem>
|
<Typography color="black">{group}</Typography>
|
||||||
<MenuItem value={1}>
|
|
||||||
<Typography color="black">Contacts</Typography>
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem value={2}>
|
|
||||||
<Typography color="black">Office</Typography>
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,31 @@
|
|||||||
import { Settings } from "@mui/icons-material";
|
import { Settings } from "@mui/icons-material";
|
||||||
import { IconButton, Menu, MenuItem } from "@mui/material";
|
import { IconButton, Menu, MenuItem } from "@mui/material";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import UserLite from "../../api-bodies/UserLite";
|
||||||
|
import UserStatus from "../../api-bodies/UserStatus";
|
||||||
|
import { useAppDispatch, useAppSelector } from "../../redux/hooks";
|
||||||
|
import {
|
||||||
|
selectFavorites,
|
||||||
|
addFavorite,
|
||||||
|
removeFavorite,
|
||||||
|
} from "../../redux/slices/favoritesSlice";
|
||||||
|
import { open as openMeetingDetails } from "../../redux/slices/meetingDetailsOpenSlice";
|
||||||
|
import { selectMeeting } from "../../redux/slices/meetingsAndUserStatusSlice";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
user: UserLite;
|
||||||
|
status: UserStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
|
||||||
const SettingsButton: React.FC = () => {
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
|
||||||
|
const favoritesUuids = useAppSelector(selectFavorites);
|
||||||
|
const meeting = useAppSelector((state) =>
|
||||||
|
selectMeeting(state, status.meetingID)
|
||||||
|
);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
const open = Boolean(anchorEl);
|
const open = Boolean(anchorEl);
|
||||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
setAnchorEl(event.currentTarget);
|
setAnchorEl(event.currentTarget);
|
||||||
@ -22,7 +41,8 @@ const SettingsButton: React.FC = () => {
|
|||||||
aria-controls={open ? "context-menu" : undefined}
|
aria-controls={open ? "context-menu" : undefined}
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded={open ? "true" : undefined}
|
aria-expanded={open ? "true" : undefined}
|
||||||
onClick={handleClick}>
|
onClick={handleClick}
|
||||||
|
>
|
||||||
<Settings />
|
<Settings />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Menu
|
<Menu
|
||||||
@ -32,9 +52,43 @@ const SettingsButton: React.FC = () => {
|
|||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
MenuListProps={{ "aria-labelledby": "basic-button" }}
|
MenuListProps={{ "aria-labelledby": "basic-button" }}
|
||||||
>
|
>
|
||||||
<MenuItem onClick={handleClose}>Remove from Favourites</MenuItem>
|
{favoritesUuids.includes(user.uuid) ? (
|
||||||
<MenuItem onClick={handleClose}>View current meeting</MenuItem>
|
<MenuItem
|
||||||
<MenuItem onClick={handleClose}>View upcoming meetings</MenuItem>
|
onClick={() => {
|
||||||
|
handleClose();
|
||||||
|
dispatch(removeFavorite(user.uuid));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove from Favorites
|
||||||
|
</MenuItem>
|
||||||
|
) : (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleClose();
|
||||||
|
dispatch(addFavorite(user.uuid));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Add to Favorites
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
|
{status.inMeeting ? (
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleClose();
|
||||||
|
dispatch(openMeetingDetails(meeting));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View current meeting
|
||||||
|
</MenuItem>
|
||||||
|
) : null}
|
||||||
|
<MenuItem
|
||||||
|
onClick={() => {
|
||||||
|
handleClose();
|
||||||
|
navigate(`../contacts/${user.uuid}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
View upcoming meetings
|
||||||
|
</MenuItem>
|
||||||
<MenuItem onClick={handleClose}>Create meeting</MenuItem>
|
<MenuItem onClick={handleClose}>Create meeting</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,18 +6,35 @@ import {
|
|||||||
ListSubheader,
|
ListSubheader,
|
||||||
Toolbar,
|
Toolbar,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useAppSelector } from "../../redux/hooks";
|
||||||
|
import { selectFavorites } from "../../redux/slices/favoritesSlice";
|
||||||
|
import { selectUserStatuses } from "../../redux/slices/meetingsAndUserStatusSlice";
|
||||||
|
import { selectTeam, selectUsers } from "../../redux/slices/usersSlice";
|
||||||
import GroupSelect from "./GroupSelect";
|
import GroupSelect from "./GroupSelect";
|
||||||
import SidebarUser, { SidebarUserObj } from "./SidebarUser";
|
import SidebarUser from "./SidebarUser";
|
||||||
|
|
||||||
// TODO: toolbar on top of sidebar since it goes under the navbar
|
// TODO: toolbar on top of sidebar since it goes under the navbar
|
||||||
|
|
||||||
const drawerWidth = 200;
|
const drawerWidth = 200;
|
||||||
|
|
||||||
interface Props {
|
const Sidebar: React.FC = () => {
|
||||||
users: SidebarUserObj[];
|
const [group, setGroup] = useState<string>("Favorites");
|
||||||
}
|
|
||||||
|
const favoritesUuids = useAppSelector(selectFavorites);
|
||||||
|
const teamUuids = useAppSelector(selectTeam);
|
||||||
|
|
||||||
|
const groupMembersUuids: string[] =
|
||||||
|
group === "Favorites" ? favoritesUuids : teamUuids;
|
||||||
|
const groupMembers = useAppSelector((state) =>
|
||||||
|
selectUsers(state, groupMembersUuids)
|
||||||
|
);
|
||||||
|
const groupMemberStatuses = useAppSelector((state) =>
|
||||||
|
selectUserStatuses(state, groupMembersUuids)
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(group);
|
||||||
|
|
||||||
const Sidebar: React.FC<Props> = ({ users }: Props) => {
|
|
||||||
return (
|
return (
|
||||||
<Drawer
|
<Drawer
|
||||||
sx={{
|
sx={{
|
||||||
@ -32,11 +49,11 @@ const Sidebar: React.FC<Props> = ({ users }: Props) => {
|
|||||||
<Toolbar />
|
<Toolbar />
|
||||||
<List disablePadding sx={{ height: "80%", overflow: "auto" }}>
|
<List disablePadding sx={{ height: "80%", overflow: "auto" }}>
|
||||||
<ListSubheader disableGutters>
|
<ListSubheader disableGutters>
|
||||||
<GroupSelect />
|
<GroupSelect group={group} setGroup={setGroup} />
|
||||||
</ListSubheader>
|
</ListSubheader>
|
||||||
{users.map((user) => (
|
{groupMembers.map((user, i) => (
|
||||||
<ListItem disablePadding key={user.id}>
|
<ListItem disablePadding key={user.uuid}>
|
||||||
<SidebarUser user={user} />
|
<SidebarUser user={user} status={groupMemberStatuses[i]} />
|
||||||
</ListItem>
|
</ListItem>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
|||||||
@ -1,68 +1,34 @@
|
|||||||
import { Box, Typography } from "@mui/material";
|
import { Box, Typography } from "@mui/material";
|
||||||
|
import UserLite from "../../api-bodies/UserLite";
|
||||||
|
import UserStatus from "../../api-bodies/UserStatus";
|
||||||
import SettingsButton from "./SettingsButton";
|
import SettingsButton from "./SettingsButton";
|
||||||
|
import { MeetingStatus, getStatusColor } from "../../utils";
|
||||||
export interface SidebarUserObj {
|
|
||||||
id: number,
|
|
||||||
name: string,
|
|
||||||
meetingStatus: MeetingStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const enum MeetingStatus {
|
|
||||||
OFFLINE = 0,
|
|
||||||
ONLINE = 1,
|
|
||||||
AWAY = 2,
|
|
||||||
IN_MEETING = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMeetingStatusText = (ms: MeetingStatus): string => {
|
|
||||||
switch (ms) {
|
|
||||||
case MeetingStatus.OFFLINE: {
|
|
||||||
return "Offline";
|
|
||||||
}
|
|
||||||
case MeetingStatus.ONLINE: {
|
|
||||||
return "Online";
|
|
||||||
}
|
|
||||||
case MeetingStatus.AWAY: {
|
|
||||||
return "Away";
|
|
||||||
}
|
|
||||||
case MeetingStatus.IN_MEETING: {
|
|
||||||
return "In meeting";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getStatusColor = (ms: MeetingStatus): string => {
|
|
||||||
switch (ms) {
|
|
||||||
case MeetingStatus.OFFLINE: {
|
|
||||||
return "#b8b8b8";
|
|
||||||
}
|
|
||||||
case MeetingStatus.ONLINE: {
|
|
||||||
return "#70ff70";
|
|
||||||
}
|
|
||||||
case MeetingStatus.AWAY: {
|
|
||||||
return "#ff7070";
|
|
||||||
}
|
|
||||||
case MeetingStatus.IN_MEETING: {
|
|
||||||
return "#e8da46";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
user: SidebarUserObj;
|
user: UserLite;
|
||||||
|
status: UserStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const SidebarUser: React.FC<Props> = ({ user }: Props) => {
|
const SidebarUser: React.FC<Props> = ({ user, status }: Props) => {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: "flex", flexDirection: "row", p: 1 }}>
|
<Box sx={{ display: "flex", flexDirection: "row", p: 1 }}>
|
||||||
<Box
|
<Box
|
||||||
bgcolor={getStatusColor(user.meetingStatus)}
|
bgcolor={getStatusColor(
|
||||||
sx={{ p: 1 }} />
|
status.inMeeting
|
||||||
|
? MeetingStatus.IN_MEETING
|
||||||
|
: MeetingStatus.NOT_IN_MEETING
|
||||||
|
)}
|
||||||
|
sx={{ p: 1 }}
|
||||||
|
/>
|
||||||
<Box sx={{ display: "flex", flexDirection: "column", ml: 1 }}>
|
<Box sx={{ display: "flex", flexDirection: "column", ml: 1 }}>
|
||||||
<Typography variant="h6">{user.name}</Typography>
|
<Typography variant="h6">{user.name}</Typography>
|
||||||
<Typography variant="caption">{getMeetingStatusText(user.meetingStatus)}</Typography>
|
<Typography variant="caption">
|
||||||
|
{status.inMeeting
|
||||||
|
? MeetingStatus.IN_MEETING
|
||||||
|
: MeetingStatus.NOT_IN_MEETING}
|
||||||
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<SettingsButton />
|
<SettingsButton user={user} status={status} />
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -19,6 +19,7 @@ export const meetingDetailsOpenSlice = createSlice({
|
|||||||
open: (state, action) => {
|
open: (state, action) => {
|
||||||
state.open = true;
|
state.open = true;
|
||||||
state.meeting = action.payload;
|
state.meeting = action.payload;
|
||||||
|
console.log(action.payload);
|
||||||
},
|
},
|
||||||
close: (state) => {
|
close: (state) => {
|
||||||
state.open = false;
|
state.open = false;
|
||||||
|
|||||||
@ -75,4 +75,23 @@ export const selectUserStatus = (
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
export const selectUserStatuses = (
|
||||||
|
state: RootState,
|
||||||
|
uuids: string[]
|
||||||
|
): UserStatus[] => {
|
||||||
|
const userStatuses: UserStatus[] = [];
|
||||||
|
uuids.forEach((uuid) => {
|
||||||
|
const userStatus = state.meetingsAndUserStatuses.userStatuses[uuid];
|
||||||
|
if (userStatus) {
|
||||||
|
userStatuses.push(userStatus);
|
||||||
|
} else {
|
||||||
|
userStatuses.push({
|
||||||
|
uuid: uuid,
|
||||||
|
inMeeting: false,
|
||||||
|
meetingID: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return userStatuses;
|
||||||
|
};
|
||||||
export default meetingsAndUserStatusSlice.reducer;
|
export default meetingsAndUserStatusSlice.reducer;
|
||||||
|
|||||||
@ -48,19 +48,22 @@ export const fetchUsers = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectUser = (
|
||||||
|
state: RootState,
|
||||||
|
uuid: string | undefined
|
||||||
|
): UserLite | undefined => {
|
||||||
|
return uuid ? state.users.users[uuid] : undefined;
|
||||||
|
};
|
||||||
export const selectUsers = (state: RootState, uuids: string[]): UserLite[] => {
|
export const selectUsers = (state: RootState, uuids: string[]): UserLite[] => {
|
||||||
const users: UserLite[] = [];
|
const users: UserLite[] = [];
|
||||||
uuids.forEach((uuid) => users.push(state.users.users[uuid]));
|
uuids.forEach((uuid) => users.push(state.users.users[uuid]));
|
||||||
return users;
|
return users;
|
||||||
};
|
};
|
||||||
export const selectTeam = (state: RootState) => {
|
export const selectTeam = (state: RootState) => {
|
||||||
const team: UserLite[] = [];
|
const team: string[] = [];
|
||||||
if (state.users.team.manager !== null)
|
if (state.users.team.manager !== null) team.push(state.users.team.manager);
|
||||||
team.push(state.users.users[state.users.team.manager]);
|
state.users.team.sameManager.forEach((u) => team.push(u));
|
||||||
state.users.team.sameManager.forEach((u) => team.push(state.users.users[u]));
|
state.users.team.directReports.forEach((u) => team.push(u));
|
||||||
state.users.team.directReports.forEach((u) =>
|
|
||||||
team.push(state.users.users[u])
|
|
||||||
);
|
|
||||||
return team;
|
return team;
|
||||||
};
|
};
|
||||||
export default usersSlice.reducer;
|
export default usersSlice.reducer;
|
||||||
|
|||||||
17
src/utils.tsx
Normal file
17
src/utils.tsx
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const enum MeetingStatus {
|
||||||
|
NOT_IN_MEETING = "Not in meeting",
|
||||||
|
IN_MEETING = "In meeting",
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStatusColor = (ms: MeetingStatus): string => {
|
||||||
|
switch (ms) {
|
||||||
|
case MeetingStatus.NOT_IN_MEETING: {
|
||||||
|
return "#70ff70";
|
||||||
|
}
|
||||||
|
case MeetingStatus.IN_MEETING: {
|
||||||
|
return "#ff7070";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { MeetingStatus, getStatusColor };
|
||||||
Reference in New Issue
Block a user