frontend/src/teams/TeamList.tsx
2024-11-18 10:10:29 -06:00

360 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useCallback, useEffect, useState } from "react";
import {
Card,
Theme,
List,
ListItem,
ListItemAvatar,
Avatar,
ListItemText,
Typography,
ListItemSecondaryAction,
Divider,
useTheme,
TextField,
InputAdornment,
CircularProgress,
IconButton,
MenuItem,
} from "@mui/material";
import { pond } from "protobuf-ts/pond";
import { useGlobalState, useTeamAPI, useUserAPI } from "providers";
import { Team, teamScope } from "models";
import { useNavigate } from "react-router";
// import TeamActions from "./TeamActions";
import SearchIcon from '@mui/icons-material/Search';
import NextIcon from "@mui/icons-material/NavigateNext";
import PrevIcon from "@mui/icons-material/NavigateBefore";
import LastIcon from "@mui/icons-material/SkipNext";
import FirstIcon from "@mui/icons-material/SkipPrevious";
import TeamSettings from "./TeamSettings";
import { Add } from "@mui/icons-material";
import { cloneDeep } from "lodash";
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
import { makeStyles, styled } from "@mui/styles";
const useStyles = makeStyles((theme: Theme) => ({
cardContent: {
padding: theme.spacing(1)
},
clickable: {
"&:hover": {
textDecoration: "underline",
cursor: "pointer"
}
},
input: {
marginLeft: "auto",
marginRight: theme.spacing(2),
marginTop: theme.spacing(1)
}
}))
const limits = [
{
value: 5,
label: "5"
},
{
value: 10,
label: "10"
},
{
value: 15,
label: "15"
},
{
value: 20,
label: "20"
}
];
interface Props {}
export default function TeamList(props: Props) {
const classes = useStyles();
const [teams, setTeams] = useState<pond.Team[]>([]);
const teamAPI = useTeamAPI();
const userAPI = useUserAPI();
const history = useNavigate();
const theme = useTheme();
const [{ user, as }] = useGlobalState();
const [teamPerms, setTeamPerms] = useState<pond.Permission[][]>([]);
const [teamPrefs, setTeamPrefs] = useState<pond.TeamPreferences[]>([]);
const [total, setTotal] = useState<number>(0);
const [limit, setLimit] = useState<number>(5);
const [page, setPage] = useState<number>(0);
const [loading, setLoading] = useState<boolean>(true);
const [search, setSearch] = useState<string>("");
const [teamDialogOpen, setTeamDialogOpen] = useState<boolean>(false);
const [asUser, setAsUser] = useState<string | undefined>(undefined);
const [userId, setUserId] = useState<string | undefined>(undefined);
const [isUser, setIsUser] = useState<boolean>(true);
// const ColorAdd = styled(Add)((theme: Theme) => ({
// color: theme.palette.getContrastText(theme.palette.primary.main),
// backgroundColor: theme.palette.primary.main,
// marginTop: 'auto',
// marginBottom: 'auto',
// borderRadius: '4px',
// '&:hover': {
// backgroundColor: theme.palette.primary.light,
// cursor: 'pointer',
// },
// '&:disabled': {
// backgroundColor: 'rgba(0,0,0,0)',
// color: theme.palette.getContrastText(theme.palette.primary.main),
// },
// }));
useEffect(() => {
if (as.includes("auth0")) setAsUser(as);
}, [as]);
useEffect(() => {
setUserId(user.id());
}, [user]);
useEffect(() => {
if (!asUser) {
setIsUser(true);
return;
}
setIsUser(userId === asUser);
}, [userId, asUser]);
const loadTeams = useCallback(() => {
setLoading(true);
let newTeamPerms: pond.Permission[][] = [];
let newTeamPrefs: pond.TeamPreferences[] = [];
let searchString = search;
teamAPI
.listTeams(limit, limit * page, undefined, "name", searchString, undefined, asUser)
.then(resp => {
setTotal(resp.data.total);
resp.data.teams.forEach((team, index) => {
let u = asUser ? asUser : user.id();
if (team.settings && u !== "") {
userAPI
.getUser(u, teamScope(team.settings?.key))
.then(resp => {
newTeamPerms[index] = resp.permissions;
newTeamPrefs[index] = resp.preferences;
})
.finally(() => {
if (newTeamPerms[index]) setTeamPerms([...newTeamPerms]);
if (newTeamPrefs[index]) setTeamPrefs(newTeamPrefs);
});
}
});
setTeams(resp.data.teams);
})
.finally(() => {
setLoading(false);
});
}, [user, teamAPI, userAPI, limit, page, search, asUser]);
useEffect(() => {
loadTeams();
}, [user, teamAPI, userAPI, limit, page, search, asUser, loadTeams]);
const drawTeams = () => {
return (
<List>
{teams.map((team, index) => {
let permissions = teamPerms[index];
if (!permissions) {
permissions = [];
}
let preferences = teamPrefs[index];
if (!preferences) {
preferences = pond.TeamPreferences.create();
}
return (
<React.Fragment key={index}>
<ListItem alignItems="flex-start">
<ListItemAvatar>
<Avatar
onClick={() => {
if (team.settings) history("teams/" + team.settings.key);
}}
src={team.settings?.avatar}
alt=""
className={classes.clickable}
/>
</ListItemAvatar>
<ListItemText
primary={
<div
className={classes.clickable}
onClick={() => {
if (team.settings) history("teams/" + team.settings.key);
}}>
{team.settings?.name}
</div>
}
secondary={
<React.Fragment>
<Typography component="span" variant="body2" color="textPrimary">
{team.settings?.info}
</Typography>
</React.Fragment>
}
/>
{/* <ListItemSecondaryAction>
<TeamActions
team={Team.create(team)}
permissions={permissions}
refreshCallback={loadTeams}
preferences={preferences}
toggleNotificationPreference={() => {
if (preferences) {
let newTeamPrefs = cloneDeep(teamPrefs);
newTeamPrefs[index].notify = !newTeamPrefs[index].notify;
console.log(newTeamPrefs[index]);
teamAPI
.updatePreferences(
Team.create(team).key(),
newTeamPrefs[index],
getContextKeys(),
getContextTypes()
)
.then(resp => {
setTeamPrefs(newTeamPrefs);
});
// setTeamPrefs(newTeamPrefs)
}
}}
/>
</ListItemSecondaryAction> */}
</ListItem>
<Divider variant="inset" component="li" />
</React.Fragment>
);
})}
</List>
);
};
const showProgress = () => {
return (
<div
style={{
width: "100%",
textAlign: "center"
}}>
<CircularProgress
style={{
textAlign: "center",
marginTop: theme.spacing(12),
marginBottom: theme.spacing(12)
}}
/>
</div>
);
};
const firstPage = () => {
setPage(0);
};
const lastPage = () => {
setPage(Math.round(total / limit));
};
const nextPage = () => {
if (limit * (page + 1) < total) setPage(page + 1);
};
const prevPage = () => {
if (page > 0) setPage(page - 1);
};
const handleChange = (event: any) => {
setLimit(event.target.value);
};
const showPageButtons = () => {
return (
<div style={{ display: "flex", float: "right", alignItems: "center" }}>
<Typography style={{ marginRight: theme.spacing(2) }}>Teams per page:</Typography>
<TextField
select
value={limit}
onChange={handleChange}
style={{ marginRight: theme.spacing(1) }}>
{limits.map(option => (
<MenuItem key={option.value} value={option.value}>
{option.label}
</MenuItem>
))}
</TextField>
<IconButton disabled={page < 1} size="small" color="inherit" onClick={firstPage}>
<FirstIcon />
</IconButton>
<IconButton disabled={page < 1} size="small" color="inherit" onClick={prevPage}>
<PrevIcon />
</IconButton>
<Typography
variant="subtitle1"
style={{ marginRight: theme.spacing(1), marginLeft: theme.spacing(1) }}>
{limit * page + 1} {limit * page + limit <= limit ? limit * page + limit : limit + 1} of{" "}
{total}
</Typography>
<IconButton
disabled={limit * (page + 1) >= total}
size="small"
color="inherit"
onClick={nextPage}>
<NextIcon />
</IconButton>
<IconButton
disabled={limit * (page + 1) >= total}
size="small"
color="inherit"
onClick={lastPage}>
<LastIcon />
</IconButton>
</div>
);
};
return (
<Card className={classes.cardContent}>
<div style={{ display: "flex", flexDirection: "row" }}>
<Typography
variant={"h6"}
style={{
marginLeft: theme.spacing(1),
marginTop: "auto",
marginBottom: "auto",
marginRight: theme.spacing(2)
}}>
{isUser ? user.name() + "'s " : "Someone's "} Teams
</Typography>
<Add onClick={() => setTeamDialogOpen(true)} />
<TextField
className={classes.input}
value={search}
onChange={event => setSearch(event?.target.value)}
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
)
}}></TextField>
</div>
<Typography variant={"subtitle2"} style={{ marginLeft: theme.spacing(2) }}>
{isUser ? "Viewing all teams that you are a part of" : "These are someone else's teams"}
</Typography>
{!loading ? drawTeams() : showProgress()}
{showPageButtons()}
<TeamSettings
teamDialogOpen={teamDialogOpen}
closeTeamDialogCallback={() => setTeamDialogOpen(false)}
refreshCallback={loadTeams}
/>
</Card>
);
}