360 lines
11 KiB
TypeScript
360 lines
11 KiB
TypeScript
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>
|
||
);
|
||
}
|