304 lines
9.4 KiB
TypeScript
304 lines
9.4 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
Theme,
|
|
ListItem,
|
|
ListItemAvatar,
|
|
Avatar,
|
|
ListItemText,
|
|
Typography,
|
|
ListItemButton,
|
|
Box,
|
|
IconButton,
|
|
Grid2,
|
|
} from "@mui/material";
|
|
import { pond } from "protobuf-ts/pond";
|
|
import { useGlobalState, useSnackbar, useTeamAPI, useUserAPI } from "providers";
|
|
import { Team, teamScope } from "models";
|
|
import { useNavigate } from "react-router";
|
|
import { cloneDeep } from "lodash";
|
|
import { getContextKeys, getContextTypes } from "pbHelpers/Context";
|
|
import { makeStyles } from "@mui/styles";
|
|
import TeamActions from "./TeamActions";
|
|
import ResponsiveTable, { Column } from "common/ResponsiveTable";
|
|
import { useMobile } from "hooks";
|
|
import { AddBox } from "@mui/icons-material";
|
|
import TeamSettings from "./TeamSettings";
|
|
|
|
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)
|
|
},
|
|
button: {
|
|
width: theme.spacing(6),
|
|
height: theme.spacing(6),
|
|
marginTop: "auto",
|
|
marginBottom: "auto",
|
|
},
|
|
titleBox: {
|
|
display: "flex",
|
|
alignItems: "center",
|
|
marginLeft: theme.spacing(1),
|
|
width: "100%"
|
|
}
|
|
}))
|
|
|
|
export default function TeamList() {
|
|
const classes = useStyles();
|
|
const [teams, setTeams] = useState<Team[]>([]);
|
|
const teamAPI = useTeamAPI();
|
|
const userAPI = useUserAPI();
|
|
const history = useNavigate();
|
|
const isMobile = useMobile()
|
|
const snackbar = useSnackbar();
|
|
const [{ user, as, team }] = useGlobalState();
|
|
const [teamPerms, setTeamPerms] = useState<Map<string | number, pond.Permission[]>>(new Map());
|
|
const [teamPrefs, setTeamPrefs] = useState<Map<string | number, pond.TeamPreferences> >(new Map());
|
|
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 loadTeams = () => {
|
|
setLoading(true);
|
|
let newTeamPerms: Map<string | number, pond.Permission[]> = new Map();
|
|
let newTeamPrefs: Map<string | number, pond.TeamPreferences> = new Map();
|
|
let viewAs: string | undefined = as
|
|
if (as === team.key()) {
|
|
viewAs = undefined
|
|
}
|
|
teamAPI
|
|
.listTeams(limit, limit * page, undefined, "name", undefined, undefined, " ", search)
|
|
.then(resp => {
|
|
setTotal(resp.data.total ? resp.data.total : 0);
|
|
let newTeams: Team[] = []
|
|
resp.data.teams.forEach(team => {
|
|
let u = viewAs ? viewAs : user.id();
|
|
let t = Team.create(team)
|
|
if (team.settings && u !== "") {
|
|
userAPI
|
|
.getUser(u, teamScope(team.settings.key))
|
|
.then(resp => {
|
|
newTeamPerms.set(t.key(), resp.permissions)
|
|
newTeamPrefs.set(t.key(), resp.preferences)
|
|
t.preferences = cloneDeep(resp.preferences)
|
|
t.permissions = cloneDeep(resp.permissions)
|
|
}).catch(() => {
|
|
snackbar.error("Error loading teams.")
|
|
})
|
|
.finally(() => {
|
|
setTeamPerms(newTeamPerms);
|
|
setTeamPrefs(newTeamPrefs);
|
|
});
|
|
}
|
|
newTeams.push(t)
|
|
});
|
|
setTeams(newTeams);
|
|
})
|
|
.finally(() => {
|
|
setLoading(false);
|
|
});
|
|
};
|
|
|
|
useEffect(() => {
|
|
loadTeams();
|
|
}, [limit, page, search, as]);
|
|
|
|
const columns = (): Column<Team>[] => {
|
|
return [
|
|
{
|
|
title: "",
|
|
render: team => {
|
|
let permissions = teamPerms.get(team.settings?.key);
|
|
if (!permissions) {
|
|
permissions = [];
|
|
}
|
|
let preferences = teamPrefs.get(team.id());
|
|
if (!preferences) {
|
|
preferences = pond.TeamPreferences.create();
|
|
}
|
|
return (
|
|
<ListItem
|
|
alignItems="flex-start"
|
|
disablePadding={!isMobile}
|
|
secondaryAction={
|
|
<TeamActions
|
|
team={team}
|
|
permissions={permissions}
|
|
refreshCallback={loadTeams}
|
|
removeSelfCallback={loadTeams}
|
|
preferences={preferences}
|
|
toggleNotificationPreference={() => {
|
|
if (preferences) {
|
|
let newTeamPrefs = cloneDeep(teamPrefs);
|
|
let prefs = newTeamPrefs.get(team.id()) ?? pond.TeamPreferences.create()
|
|
prefs.notify = !newTeamPrefs.get(team.id())?.notify
|
|
newTeamPrefs.set(team.id(), prefs)
|
|
teamAPI
|
|
.updatePreferences(
|
|
team.key(),
|
|
prefs,
|
|
getContextKeys(),
|
|
getContextTypes()
|
|
)
|
|
.then(() => {
|
|
setTeamPrefs(newTeamPrefs);
|
|
});
|
|
}
|
|
}}
|
|
/>
|
|
}
|
|
>
|
|
<ListItemButton
|
|
onClick={() => {
|
|
if (team.settings) history(team.settings.key, { state: {team: team} });
|
|
}}
|
|
>
|
|
<ListItemAvatar sx={{ marginLeft: isMobile ? -2 : 0 }}>
|
|
<Avatar
|
|
onClick={() => {
|
|
if (team.settings) history(team.settings.key);
|
|
}}
|
|
src={team.settings?.avatar}
|
|
alt=""
|
|
className={classes.clickable}
|
|
/>
|
|
</ListItemAvatar>
|
|
<ListItemText
|
|
primary={
|
|
<Typography fontWeight={"bold"}>
|
|
{team.settings?.name}
|
|
</Typography>
|
|
}
|
|
secondary={
|
|
!isMobile && <React.Fragment>
|
|
<Typography component="span" variant="body2" color="textSecondary">
|
|
{team.settings?.info}
|
|
</Typography>
|
|
</React.Fragment>
|
|
}
|
|
/>
|
|
</ListItemButton>
|
|
</ListItem>
|
|
)
|
|
}
|
|
}
|
|
]
|
|
}
|
|
|
|
const handleChange = (event: any) => {
|
|
setLimit(event.target.value);
|
|
};
|
|
|
|
const renderGutter = (team: Team) => {
|
|
return(
|
|
<Typography variant="body2" color="textSecondary" sx={{ padding: 1 }}>
|
|
{team.settings.info}
|
|
</Typography>
|
|
)
|
|
}
|
|
|
|
const renderTitle = () => {
|
|
return (
|
|
<Box className={classes.titleBox}>
|
|
<Avatar
|
|
src={user.settings.avatar}
|
|
/>
|
|
<Grid2 container direction="column" marginLeft={1}>
|
|
<Grid2 marginBottom={-0.25}>
|
|
<Typography variant="h6">
|
|
{user.name()}'s Teams
|
|
</Typography>
|
|
</Grid2>
|
|
<Grid2 marginTop={-0.25}>
|
|
<Typography variant="body2">
|
|
All teams you are a member of.
|
|
</Typography>
|
|
</Grid2>
|
|
</Grid2>
|
|
|
|
<IconButton onClick={() => setTeamDialogOpen(true)} color="primary" className={classes.button}>
|
|
<AddBox/>
|
|
</IconButton>
|
|
</Box>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<ResponsiveTable<Team>
|
|
title={renderTitle()}
|
|
rows={teams}
|
|
columns={columns()}
|
|
handleRowsPerPageChange={handleChange}
|
|
total={total}
|
|
pageSize={limit}
|
|
page={page}
|
|
setPage={setPage}
|
|
setSearchText={setSearch}
|
|
isLoading={loading}
|
|
renderGutter={isMobile ? renderGutter : undefined}
|
|
hideKeys
|
|
loadMore={() => {
|
|
let current = cloneDeep(teams)
|
|
setLoading(true);
|
|
let newTeamPerms: Map<string | number, pond.Permission[]> = new Map();
|
|
let newTeamPrefs: Map<string | number, pond.TeamPreferences> = new Map();
|
|
let viewAs: string | undefined = as
|
|
if (as === team.key()) {
|
|
viewAs = undefined
|
|
}
|
|
teamAPI
|
|
.listTeams(limit, current.length, undefined, "name", undefined, undefined, " ", search)
|
|
.then(resp => {
|
|
setTotal(resp.data.total ? resp.data.total : 0);
|
|
let newTeams: Team[] = []
|
|
resp.data.teams.forEach(team => {
|
|
let u = viewAs ? viewAs : user.id();
|
|
let t = Team.create(team)
|
|
if (team.settings && u !== "") {
|
|
userAPI
|
|
.getUser(u, teamScope(team.settings.key))
|
|
.then(resp => {
|
|
newTeamPerms.set(t.key(), resp.permissions)
|
|
newTeamPrefs.set(t.key(), resp.preferences)
|
|
t.preferences = cloneDeep(resp.preferences)
|
|
t.permissions = cloneDeep(resp.permissions)
|
|
}).catch(() => {
|
|
snackbar.error("Error loading teams.")
|
|
})
|
|
.finally(() => {
|
|
setTeamPerms(newTeamPerms);
|
|
setTeamPrefs(newTeamPrefs);
|
|
});
|
|
}
|
|
newTeams.push(t)
|
|
});
|
|
setTeams(current.concat(newTeams));
|
|
})
|
|
.finally(() => {
|
|
setLoading(false);
|
|
});
|
|
}}
|
|
/>
|
|
<TeamSettings
|
|
teamDialogOpen={teamDialogOpen}
|
|
closeTeamDialogCallback={() => setTeamDialogOpen(false)}
|
|
refreshCallback={loadTeams}
|
|
/>
|
|
</>
|
|
)
|
|
|
|
}
|