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 element={<ProtectedRoute />}>
|
||||
<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>
|
||||
</Routes>
|
||||
|
||||
@ -1,42 +1,14 @@
|
||||
import {useLocation, Outlet, Navigate} from "react-router-dom";
|
||||
import { useLocation, Outlet, Navigate } from "react-router-dom";
|
||||
import useAuth from "./hooks/useAuth";
|
||||
import Navbar from "./components/navbar/Navbar";
|
||||
import Sidebar from "./components/sidebar/Sidebar";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
MeetingStatus,
|
||||
SidebarUserObj,
|
||||
} from "./components/sidebar/SidebarUser";
|
||||
import { Box } from "@mui/material";
|
||||
|
||||
const ProtectedRoute = () => {
|
||||
const auth= useAuth();
|
||||
const auth = useAuth();
|
||||
const location = useLocation();
|
||||
|
||||
/* 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 ? (
|
||||
<>
|
||||
<Navbar />
|
||||
@ -45,7 +17,7 @@ const ProtectedRoute = () => {
|
||||
<Outlet />
|
||||
</Box>
|
||||
<Box>
|
||||
<Sidebar users={sidebarUsers} />
|
||||
<Sidebar />
|
||||
</Box>
|
||||
</Box>
|
||||
</>
|
||||
|
||||
@ -4,18 +4,20 @@ import Body from "./contacts-components/Body";
|
||||
|
||||
import Sidebar from "./contacts-components/Sidebar";
|
||||
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 [contactSelected, setContactSelected] = React.useState<UserLite | null>(
|
||||
null
|
||||
const { uuid } = useParams();
|
||||
const uriContact: UserLite | undefined = useAppSelector((state) =>
|
||||
selectUser(state, uuid)
|
||||
);
|
||||
|
||||
return (
|
||||
<Box sx={{ display: "flex", height: "100%" }}>
|
||||
<Sidebar setContactSelected={setContactSelected} />
|
||||
{contactSelected !== null ? (
|
||||
<Body contactSelected={contactSelected} />
|
||||
) : null}
|
||||
<Sidebar />
|
||||
{uriContact ? <Body contactSelected={uriContact} /> : null}
|
||||
</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 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 startDate = new Date(meeting.start);
|
||||
@ -34,4 +18,4 @@ const getUpcomingMeetingTime = (meeting: DetailedMeeting) => {
|
||||
return `${startTime} - ${endTime}`;
|
||||
};
|
||||
|
||||
export { returnStatusColor, getUpcomingMeetingTime };
|
||||
export { getUpcomingMeetingTime };
|
||||
|
||||
@ -13,15 +13,10 @@ import ContactItem from "./sidebar-components/ContactItem";
|
||||
import { useAppSelector } from "../../../redux/hooks";
|
||||
import { selectUsers, selectTeam } from "../../../redux/slices/usersSlice";
|
||||
import { selectFavorites } from "../../../redux/slices/favoritesSlice";
|
||||
import UserLite from "../../../api-bodies/UserLite";
|
||||
|
||||
interface Props {
|
||||
setContactSelected: (contactInfo: UserLite) => void;
|
||||
}
|
||||
|
||||
const sidebarWidth = 240;
|
||||
|
||||
const Sidebar: React.FC<Props> = (props) => {
|
||||
const Sidebar: React.FC = () => {
|
||||
const [favoritesOpen, setFavoritesOpen] = React.useState<boolean>(true);
|
||||
const [teamOpen, setTeamOpen] = React.useState<boolean>(false);
|
||||
|
||||
@ -29,7 +24,8 @@ const Sidebar: React.FC<Props> = (props) => {
|
||||
const favorites = useAppSelector((state) =>
|
||||
selectUsers(state, favoritesUuids)
|
||||
);
|
||||
const team = useAppSelector(selectTeam);
|
||||
const teamUuids = useAppSelector(selectTeam);
|
||||
const team = useAppSelector((state) => selectUsers(state, teamUuids));
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
@ -58,11 +54,7 @@ const Sidebar: React.FC<Props> = (props) => {
|
||||
<Collapse in={favoritesOpen} timeout="auto" unmountOnExit>
|
||||
<List disablePadding>
|
||||
{favorites.map((favorite, i) => (
|
||||
<ContactItem
|
||||
contactInfo={favorite}
|
||||
key={i}
|
||||
setContactSelected={props.setContactSelected}
|
||||
/>
|
||||
<ContactItem contactInfo={favorite} key={i} />
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
@ -74,11 +66,7 @@ const Sidebar: React.FC<Props> = (props) => {
|
||||
<Collapse in={teamOpen} timeout="auto" unmountOnExit>
|
||||
<List disablePadding>
|
||||
{team.map((member, i) => (
|
||||
<ContactItem
|
||||
contactInfo={member}
|
||||
key={i}
|
||||
setContactSelected={props.setContactSelected}
|
||||
/>
|
||||
<ContactItem contactInfo={member} key={i} />
|
||||
))}
|
||||
</List>
|
||||
</Collapse>
|
||||
|
||||
@ -4,7 +4,6 @@ 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 Status from "../../Status";
|
||||
import UserLite from "../../../../api-bodies/UserLite";
|
||||
import UserStatus from "../../../../api-bodies/UserStatus";
|
||||
import { useAppSelector, useAppDispatch } from "../../../../redux/hooks";
|
||||
@ -18,6 +17,7 @@ import {
|
||||
removeFavorite,
|
||||
} from "../../../../redux/slices/favoritesSlice";
|
||||
import RemoveIcon from "@mui/icons-material/Remove";
|
||||
import { MeetingStatus } from "../../../../utils";
|
||||
|
||||
interface Props {
|
||||
contactInfo: UserLite;
|
||||
@ -25,15 +25,17 @@ interface Props {
|
||||
|
||||
const UpperBody: React.FC<Props> = (props) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const userStatus: UserStatus | null = useAppSelector((state) =>
|
||||
const userStatus: UserStatus = useAppSelector((state) =>
|
||||
selectUserStatus(state, props.contactInfo.uuid)
|
||||
);
|
||||
const favoritesUuids = useAppSelector(selectFavorites);
|
||||
const detailedMeeting = useAppSelector((state) =>
|
||||
selectMeeting(state, userStatus.meetingID)
|
||||
);
|
||||
const status: Status =
|
||||
userStatus && userStatus.inMeeting ? Status.Online : Status.Offline;
|
||||
const meetingStatus: MeetingStatus =
|
||||
userStatus && userStatus.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING;
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
@ -102,7 +104,7 @@ const UpperBody: React.FC<Props> = (props) => {
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Box sx={{ display: "flex", flexDirection: "column" }}>
|
||||
<Typography sx={{ textAlign: "right" }}>{status}</Typography>
|
||||
<Typography sx={{ textAlign: "right" }}>{meetingStatus}</Typography>
|
||||
{detailedMeeting ? (
|
||||
<Typography>{detailedMeeting.topic}</Typography>
|
||||
) : null}
|
||||
|
||||
@ -2,28 +2,30 @@ import { ListItemButton, ListItemIcon, ListItemText } from "@mui/material";
|
||||
import React from "react";
|
||||
import PersonOutlineIcon from "@mui/icons-material/PersonOutline";
|
||||
import CircleIcon from "@mui/icons-material/Circle";
|
||||
import { returnStatusColor } from "../../Utils";
|
||||
import UserLite from "../../../../api-bodies/UserLite";
|
||||
import Status from "../../Status";
|
||||
import { MeetingStatus, getStatusColor } from "../../../../utils";
|
||||
import { useAppSelector } from "../../../../redux/hooks";
|
||||
import { selectUserStatus } from "../../../../redux/slices/meetingsAndUserStatusSlice";
|
||||
import UserStatus from "../../../../api-bodies/UserStatus";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
interface Props {
|
||||
contactInfo: UserLite;
|
||||
setContactSelected: (contactInfo: UserLite) => void;
|
||||
}
|
||||
|
||||
const ContactItem: React.FC<Props> = (props) => {
|
||||
const navigate = useNavigate();
|
||||
const userStatus: UserStatus | null = useAppSelector((state) =>
|
||||
selectUserStatus(state, props.contactInfo.uuid)
|
||||
);
|
||||
const status: Status =
|
||||
userStatus && userStatus.inMeeting ? Status.Online : Status.Offline;
|
||||
const status: MeetingStatus =
|
||||
userStatus && userStatus.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING;
|
||||
return (
|
||||
<ListItemButton
|
||||
onClick={() => {
|
||||
props.setContactSelected(props.contactInfo);
|
||||
navigate(`/contacts/${props.contactInfo.uuid}`);
|
||||
}}
|
||||
>
|
||||
<ListItemIcon>
|
||||
@ -34,7 +36,7 @@ const ContactItem: React.FC<Props> = (props) => {
|
||||
secondary={status}
|
||||
sx={{ flexGrow: 1 }}
|
||||
/>
|
||||
<CircleIcon color={returnStatusColor(status)} />
|
||||
<CircleIcon sx={{ color: getStatusColor(status) }} />
|
||||
</ListItemButton>
|
||||
);
|
||||
};
|
||||
|
||||
@ -1,40 +1,59 @@
|
||||
import { Button, Checkbox, Dialog, DialogActions, DialogContent, DialogTitle, FormControlLabel, FormGroup, Typography } from "@mui/material";
|
||||
import { SidebarUserObj } from "../sidebar/SidebarUser";
|
||||
import {
|
||||
Button,
|
||||
Checkbox,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
FormControlLabel,
|
||||
FormGroup,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import { SidebarUserObj } from "./Home";
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
selectedValue: SidebarUserObj;
|
||||
onClose: (value: SidebarUserObj) => void;
|
||||
users: SidebarUserObj[];
|
||||
open: boolean;
|
||||
selectedValue: SidebarUserObj;
|
||||
onClose: (value: SidebarUserObj) => void;
|
||||
users: SidebarUserObj[];
|
||||
}
|
||||
|
||||
const CallFavouritesDialog: React.FC<Props> = ({ open, selectedValue, onClose, users }: Props) => {
|
||||
const CallFavouritesDialog: React.FC<Props> = ({
|
||||
open,
|
||||
selectedValue,
|
||||
onClose,
|
||||
users,
|
||||
}: Props) => {
|
||||
const handleClose = () => {
|
||||
onClose(selectedValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={handleClose}
|
||||
open={open}
|
||||
fullWidth
|
||||
maxWidth="sm"
|
||||
>
|
||||
<Dialog onClose={handleClose} open={open} fullWidth maxWidth="sm">
|
||||
<DialogTitle>Select who to call:</DialogTitle>
|
||||
<DialogContent>
|
||||
<FormControlLabel control={<Checkbox color="success" />} label="Select everyone" />
|
||||
<FormControlLabel
|
||||
control={<Checkbox color="success" />}
|
||||
label="Select everyone"
|
||||
/>
|
||||
</DialogContent>
|
||||
<DialogContent sx={{ height: "40vh" }} dividers>
|
||||
<FormGroup>
|
||||
{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>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions>
|
||||
<Button color="success" onClick={handleClose}>
|
||||
<Typography variant="button" color="black">Call</Typography>
|
||||
<Typography variant="button" color="black">
|
||||
Call
|
||||
</Typography>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
@ -2,22 +2,26 @@ import MeetingsPanel from "./MeetingsPanel";
|
||||
import ShortCuts from "./ShortCuts";
|
||||
import Container from "@mui/material/Container";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import { MeetingStatus, SidebarUserObj } from "../sidebar/SidebarUser";
|
||||
import { MeetingStatus } from "../../utils";
|
||||
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
|
||||
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.ONLINE },
|
||||
{ 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.OFFLINE },
|
||||
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.AWAY }
|
||||
{ id: 2, name: "Taehee C.", meetingStatus: MeetingStatus.IN_MEETING },
|
||||
{ id: 3, name: "Bob A.", meetingStatus: MeetingStatus.NOT_IN_MEETING },
|
||||
]);
|
||||
|
||||
|
||||
return (
|
||||
<Container className="main-home">
|
||||
<Grid container>
|
||||
@ -25,7 +29,7 @@ const Home: React.FC = () => {
|
||||
<MeetingsPanel />
|
||||
</Grid>
|
||||
<Grid item sm={4}>
|
||||
<ShortCuts users={mockUsers}/>
|
||||
<ShortCuts users={mockUsers} />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
|
||||
@ -5,7 +5,7 @@ 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 "../sidebar/SidebarUser";
|
||||
import { SidebarUserObj } from "./Home";
|
||||
import CallFavouritesDialog from "./CallFavouritesDialog";
|
||||
import { useState } from "react";
|
||||
|
||||
@ -27,19 +27,23 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="short-cuts" >
|
||||
<div className="short-cuts">
|
||||
<Grid className="row-1" container spacing={1}>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile">
|
||||
<PeopleIcon className="icon" />
|
||||
</Button>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>New Meeting</Typography>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
New Meeting
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile">
|
||||
<AddIcon className="icon" />
|
||||
</Button>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>Join</Typography>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
Join
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid className="row-2" container spacing={1}>
|
||||
@ -51,14 +55,19 @@ const ShortCuts: React.FC<Props> = ({ users }: Props) => {
|
||||
selectedValue={selectedValue}
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
users={users}/>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>Call Favourites</Typography>
|
||||
users={users}
|
||||
/>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
Call Favourites
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item sm={6}>
|
||||
<Button className="tile">
|
||||
<CircleIcon className="icon" />
|
||||
</Button>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>Recordings</Typography>
|
||||
<Typography className="mylabel" sx={{ ml: 1 }}>
|
||||
Recordings
|
||||
</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
@ -1,14 +1,25 @@
|
||||
import { FormControl, InputLabel, MenuItem, Select, SelectChangeEvent, Typography } from "@mui/material";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
FormControl,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Select,
|
||||
SelectChangeEvent,
|
||||
Typography,
|
||||
} from "@mui/material";
|
||||
import React from "react";
|
||||
|
||||
// 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) => {
|
||||
setGroup(event.target.value);
|
||||
const GroupSelect: React.FC<Props> = ({ group, setGroup }) => {
|
||||
const handleChange = (e: SelectChangeEvent) => {
|
||||
setGroup(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
@ -23,15 +34,11 @@ const GroupSelect: React.FC = () => {
|
||||
label="Group"
|
||||
onChange={handleChange}
|
||||
>
|
||||
<MenuItem value={0}>
|
||||
<Typography color="black">Favourites</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem value={1}>
|
||||
<Typography color="black">Contacts</Typography>
|
||||
</MenuItem>
|
||||
<MenuItem value={2}>
|
||||
<Typography color="black">Office</Typography>
|
||||
</MenuItem>
|
||||
{groups.map((group, i) => (
|
||||
<MenuItem value={group} key={i}>
|
||||
<Typography color="black">{group}</Typography>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
);
|
||||
|
||||
@ -1,12 +1,31 @@
|
||||
import { Settings } from "@mui/icons-material";
|
||||
import { IconButton, Menu, MenuItem } from "@mui/material";
|
||||
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 = () => {
|
||||
|
||||
const SettingsButton: React.FC<Props> = ({ user, status }: Props) => {
|
||||
const navigate = useNavigate();
|
||||
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 handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
@ -22,7 +41,8 @@ const SettingsButton: React.FC = () => {
|
||||
aria-controls={open ? "context-menu" : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
onClick={handleClick}>
|
||||
onClick={handleClick}
|
||||
>
|
||||
<Settings />
|
||||
</IconButton>
|
||||
<Menu
|
||||
@ -32,9 +52,43 @@ const SettingsButton: React.FC = () => {
|
||||
onClose={handleClose}
|
||||
MenuListProps={{ "aria-labelledby": "basic-button" }}
|
||||
>
|
||||
<MenuItem onClick={handleClose}>Remove from Favourites</MenuItem>
|
||||
<MenuItem onClick={handleClose}>View current meeting</MenuItem>
|
||||
<MenuItem onClick={handleClose}>View upcoming meetings</MenuItem>
|
||||
{favoritesUuids.includes(user.uuid) ? (
|
||||
<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>
|
||||
</Menu>
|
||||
</div>
|
||||
|
||||
@ -6,18 +6,35 @@ import {
|
||||
ListSubheader,
|
||||
Toolbar,
|
||||
} 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 SidebarUser, { SidebarUserObj } from "./SidebarUser";
|
||||
import SidebarUser from "./SidebarUser";
|
||||
|
||||
// TODO: toolbar on top of sidebar since it goes under the navbar
|
||||
|
||||
const drawerWidth = 200;
|
||||
|
||||
interface Props {
|
||||
users: SidebarUserObj[];
|
||||
}
|
||||
const Sidebar: React.FC = () => {
|
||||
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 (
|
||||
<Drawer
|
||||
sx={{
|
||||
@ -32,11 +49,11 @@ const Sidebar: React.FC<Props> = ({ users }: Props) => {
|
||||
<Toolbar />
|
||||
<List disablePadding sx={{ height: "80%", overflow: "auto" }}>
|
||||
<ListSubheader disableGutters>
|
||||
<GroupSelect />
|
||||
<GroupSelect group={group} setGroup={setGroup} />
|
||||
</ListSubheader>
|
||||
{users.map((user) => (
|
||||
<ListItem disablePadding key={user.id}>
|
||||
<SidebarUser user={user} />
|
||||
{groupMembers.map((user, i) => (
|
||||
<ListItem disablePadding key={user.uuid}>
|
||||
<SidebarUser user={user} status={groupMemberStatuses[i]} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
@ -1,68 +1,34 @@
|
||||
import { Box, Typography } from "@mui/material";
|
||||
import UserLite from "../../api-bodies/UserLite";
|
||||
import UserStatus from "../../api-bodies/UserStatus";
|
||||
import SettingsButton from "./SettingsButton";
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
};
|
||||
import { MeetingStatus, getStatusColor } from "../../utils";
|
||||
|
||||
interface Props {
|
||||
user: SidebarUserObj;
|
||||
user: UserLite;
|
||||
status: UserStatus;
|
||||
}
|
||||
|
||||
const SidebarUser: React.FC<Props> = ({ user }: Props) => {
|
||||
const SidebarUser: React.FC<Props> = ({ user, status }: Props) => {
|
||||
return (
|
||||
<Box sx={{ display: "flex", flexDirection: "row", p: 1 }}>
|
||||
<Box
|
||||
bgcolor={getStatusColor(user.meetingStatus)}
|
||||
sx={{ p: 1 }} />
|
||||
bgcolor={getStatusColor(
|
||||
status.inMeeting
|
||||
? MeetingStatus.IN_MEETING
|
||||
: MeetingStatus.NOT_IN_MEETING
|
||||
)}
|
||||
sx={{ p: 1 }}
|
||||
/>
|
||||
<Box sx={{ display: "flex", flexDirection: "column", ml: 1 }}>
|
||||
<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>
|
||||
<SettingsButton />
|
||||
<SettingsButton user={user} status={status} />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
@ -19,6 +19,7 @@ export const meetingDetailsOpenSlice = createSlice({
|
||||
open: (state, action) => {
|
||||
state.open = true;
|
||||
state.meeting = action.payload;
|
||||
console.log(action.payload);
|
||||
},
|
||||
close: (state) => {
|
||||
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;
|
||||
|
||||
@ -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[] => {
|
||||
const users: UserLite[] = [];
|
||||
uuids.forEach((uuid) => users.push(state.users.users[uuid]));
|
||||
return users;
|
||||
};
|
||||
export const selectTeam = (state: RootState) => {
|
||||
const team: UserLite[] = [];
|
||||
if (state.users.team.manager !== null)
|
||||
team.push(state.users.users[state.users.team.manager]);
|
||||
state.users.team.sameManager.forEach((u) => team.push(state.users.users[u]));
|
||||
state.users.team.directReports.forEach((u) =>
|
||||
team.push(state.users.users[u])
|
||||
);
|
||||
const team: string[] = [];
|
||||
if (state.users.team.manager !== null) team.push(state.users.team.manager);
|
||||
state.users.team.sameManager.forEach((u) => team.push(u));
|
||||
state.users.team.directReports.forEach((u) => team.push(u));
|
||||
return team;
|
||||
};
|
||||
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