team page added, uses data from previous page so that the team isn't loaded again if it's clicked from the team list
This commit is contained in:
parent
091634161f
commit
0e98d7329f
7 changed files with 63 additions and 23 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { CircularProgress, Typography, useTheme } from "@mui/material";
|
import { Box, CircularProgress, Typography, useTheme } from "@mui/material";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
message?: string;
|
message?: string;
|
||||||
|
|
@ -9,7 +9,7 @@ export default function LoadingScreen(props: Props) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Box sx={{ width: "100%", height: "100%", justifyContent: "center", alignContent: "center", textAlign: "center" }}>
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={150}
|
size={150}
|
||||||
style={{ marginBottom: theme.spacing(4) }}
|
style={{ marginBottom: theme.spacing(4) }}
|
||||||
|
|
@ -18,6 +18,6 @@ export default function LoadingScreen(props: Props) {
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
{message ? message : "Loading..."}
|
{message ? message : "Loading..."}
|
||||||
</Typography>
|
</Typography>
|
||||||
</>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -22,6 +22,10 @@ const useStyles = makeStyles((theme: Theme) => {
|
||||||
title: {
|
title: {
|
||||||
padding: theme.spacing(1)
|
padding: theme.spacing(1)
|
||||||
},
|
},
|
||||||
|
subtitle: {
|
||||||
|
padding: theme.spacing(1),
|
||||||
|
marginTop: theme.spacing(-2),
|
||||||
|
},
|
||||||
titleContainer: {
|
titleContainer: {
|
||||||
border: "none",
|
border: "none",
|
||||||
},
|
},
|
||||||
|
|
@ -51,6 +55,7 @@ interface Props<T> {
|
||||||
rows: T[],
|
rows: T[],
|
||||||
columns?: Column<T>[],
|
columns?: Column<T>[],
|
||||||
title?: string | JSX.Element;
|
title?: string | JSX.Element;
|
||||||
|
subtitle?: string | JSX.Element;
|
||||||
total: number,
|
total: number,
|
||||||
pageSize: number,
|
pageSize: number,
|
||||||
page: number,
|
page: number,
|
||||||
|
|
@ -75,6 +80,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
setPage,
|
setPage,
|
||||||
handleRowsPerPageChange,
|
handleRowsPerPageChange,
|
||||||
title,
|
title,
|
||||||
|
subtitle,
|
||||||
renderGutter,
|
renderGutter,
|
||||||
setSearchText,
|
setSearchText,
|
||||||
isLoading,
|
isLoading,
|
||||||
|
|
@ -177,6 +183,20 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const renderSubtitle = () => {
|
||||||
|
if (typeof(subtitle) === "string" ) return (
|
||||||
|
<Typography variant="body2" color="textSecondary" className={classes.subtitle}>
|
||||||
|
{subtitle}
|
||||||
|
</Typography>
|
||||||
|
)
|
||||||
|
if (subtitle) return (
|
||||||
|
<Box className={classes.subtitle}>
|
||||||
|
{subtitle} haha lol
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
function capitalize(val: any) {
|
function capitalize(val: any) {
|
||||||
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
return String(val).charAt(0).toUpperCase() + String(val).slice(1);
|
||||||
}
|
}
|
||||||
|
|
@ -231,6 +251,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
<Box>
|
<Box>
|
||||||
<Box className={classes.mobileTitleBox}>
|
<Box className={classes.mobileTitleBox}>
|
||||||
{renderTitle()}
|
{renderTitle()}
|
||||||
|
{renderSubtitle()}
|
||||||
{setSearchText && searchBar()}
|
{setSearchText && searchBar()}
|
||||||
</Box>
|
</Box>
|
||||||
{rows.map((row, index) => {
|
{rows.map((row, index) => {
|
||||||
|
|
@ -290,15 +311,10 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
<TableCell colSpan={10000} sx={{ border: "none" }}>
|
<TableCell colSpan={10000} sx={{ border: "none" }}>
|
||||||
<Grid2 container justifyContent="space-between">
|
<Grid2 container justifyContent="space-between">
|
||||||
<Grid2>
|
<Grid2>
|
||||||
{typeof(title) === "string" ?
|
<Box sx={{ marginTop: -1 }}>
|
||||||
<Typography variant="h5" className={classes.title}>
|
{renderTitle()}
|
||||||
{title}
|
{renderSubtitle()}
|
||||||
</Typography>
|
|
||||||
:
|
|
||||||
title && <Box className={classes.title}>
|
|
||||||
{title}
|
|
||||||
</Box>
|
</Box>
|
||||||
}
|
|
||||||
</Grid2>
|
</Grid2>
|
||||||
<Grid2>
|
<Grid2>
|
||||||
{setSearchText &&<Box className={classes.searchContainer}>
|
{setSearchText &&<Box className={classes.searchContainer}>
|
||||||
|
|
@ -308,7 +324,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
</Grid2>
|
</Grid2>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
{ !hideKeys && <TableRow>
|
<TableRow sx={ hideKeys ? { display: "none" } : undefined } >
|
||||||
{ renderGutter && <TableCell></TableCell>}
|
{ renderGutter && <TableCell></TableCell>}
|
||||||
{ columns ?
|
{ columns ?
|
||||||
columns.map((column, index) => {
|
columns.map((column, index) => {
|
||||||
|
|
@ -327,7 +343,7 @@ export default function ResponsiveTable<T extends Object>(props: Props<T>) {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</TableRow>}
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{ isLoading ?
|
{ isLoading ?
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import SideNavigator from "./SideNavigator";
|
||||||
import Users from "pages/Users";
|
import Users from "pages/Users";
|
||||||
import { useMobile } from "hooks";
|
import { useMobile } from "hooks";
|
||||||
import BottomNavigator from "./BottomNavigator";
|
import BottomNavigator from "./BottomNavigator";
|
||||||
|
import TeamPage from "pages/Team";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
open: boolean,
|
open: boolean,
|
||||||
|
|
@ -52,10 +53,12 @@ export default function Router(props: Props) {
|
||||||
|
|
||||||
{/* Redirects */}
|
{/* Redirects */}
|
||||||
<Route path="/callback" element={<Navigate to="/" />} />
|
<Route path="/callback" element={<Navigate to="/" />} />
|
||||||
|
<Route path="/team/:teamID" element={<Navigate to="/teams/:teamID" />} />
|
||||||
|
|
||||||
{/* Page routes */}
|
{/* Page routes */}
|
||||||
<Route index element={hello()} />
|
<Route index element={hello()} />
|
||||||
<Route path="/teams" element={<Teams/>} />
|
<Route path="/teams" element={<Teams/>} />
|
||||||
|
<Route path="/teams/:teamID" element={<TeamPage/>} />
|
||||||
<Route path="/users" element={<Users/>} />
|
<Route path="/users" element={<Users/>} />
|
||||||
{/* <Route
|
{/* <Route
|
||||||
path="/teams/:teamID"
|
path="/teams/:teamID"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Container, Theme } from "@mui/material";
|
import { Container, SxProps, Theme } from "@mui/material";
|
||||||
import { makeStyles } from "@mui/styles";
|
import { makeStyles } from "@mui/styles";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import React, { PropsWithChildren } from "react";
|
import React, { PropsWithChildren } from "react";
|
||||||
|
|
@ -37,11 +37,12 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
interface Props extends PropsWithChildren{
|
interface Props extends PropsWithChildren{
|
||||||
fullViewport?: boolean;
|
fullViewport?: boolean;
|
||||||
isCenterCenter?: boolean;
|
isCenterCenter?: boolean;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PageContainer: React.FunctionComponent<Props> = props => {
|
export const PageContainer: React.FunctionComponent<Props> = props => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { children, fullViewport, isCenterCenter } = props;
|
const { children, fullViewport, isCenterCenter, sx } = props;
|
||||||
// let fullViewport = false
|
// let fullViewport = false
|
||||||
// let isCenterCenter = false
|
// let isCenterCenter = false
|
||||||
return (
|
return (
|
||||||
|
|
@ -50,6 +51,7 @@ export const PageContainer: React.FunctionComponent<Props> = props => {
|
||||||
fullViewport ? classes.fullViewportContainer : classes.pageContainer,
|
fullViewport ? classes.fullViewportContainer : classes.pageContainer,
|
||||||
isCenterCenter && classes.centerCenter
|
isCenterCenter && classes.centerCenter
|
||||||
)}
|
)}
|
||||||
|
sx={sx}
|
||||||
disableGutters
|
disableGutters
|
||||||
maxWidth={false}
|
maxWidth={false}
|
||||||
children={<React.Fragment>{children}</React.Fragment>}
|
children={<React.Fragment>{children}</React.Fragment>}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import {
|
||||||
IconButton,
|
IconButton,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import { useGlobalState, useTeamAPI, useUserAPI } from "providers";
|
import { useGlobalState, useSnackbar, useTeamAPI, useUserAPI } from "providers";
|
||||||
import { Team, teamScope } from "models";
|
import { Team, teamScope } from "models";
|
||||||
import { useNavigate } from "react-router";
|
import { useNavigate } from "react-router";
|
||||||
import { cloneDeep } from "lodash";
|
import { cloneDeep } from "lodash";
|
||||||
|
|
@ -57,6 +57,7 @@ export default function TeamList() {
|
||||||
const userAPI = useUserAPI();
|
const userAPI = useUserAPI();
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const isMobile = useMobile()
|
const isMobile = useMobile()
|
||||||
|
const snackbar = useSnackbar();
|
||||||
const [{ user, as }] = useGlobalState();
|
const [{ user, as }] = useGlobalState();
|
||||||
const [teamPerms, setTeamPerms] = useState<Map<string | number, pond.Permission[]>>(new Map());
|
const [teamPerms, setTeamPerms] = useState<Map<string | number, pond.Permission[]>>(new Map());
|
||||||
const [teamPrefs, setTeamPrefs] = useState<Map<string | number, pond.TeamPreferences> >(new Map());
|
const [teamPrefs, setTeamPrefs] = useState<Map<string | number, pond.TeamPreferences> >(new Map());
|
||||||
|
|
@ -91,13 +92,17 @@ export default function TeamList() {
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
newTeamPerms.set(t.key(), resp.permissions)
|
newTeamPerms.set(t.key(), resp.permissions)
|
||||||
newTeamPrefs.set(t.key(), resp.preferences)
|
newTeamPrefs.set(t.key(), resp.preferences)
|
||||||
|
t.preferences = cloneDeep(resp.preferences)
|
||||||
|
t.permissions = cloneDeep(resp.permissions)
|
||||||
|
}).catch(() => {
|
||||||
|
snackbar.error("Error loading teams.")
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setTeamPerms(newTeamPerms);
|
setTeamPerms(newTeamPerms);
|
||||||
setTeamPrefs(newTeamPrefs);
|
setTeamPrefs(newTeamPrefs);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
newTeams.push(Team.create(team))
|
newTeams.push(t)
|
||||||
});
|
});
|
||||||
setTeams(newTeams);
|
setTeams(newTeams);
|
||||||
})
|
})
|
||||||
|
|
@ -158,7 +163,7 @@ export default function TeamList() {
|
||||||
>
|
>
|
||||||
<ListItemButton
|
<ListItemButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (team.settings) history(team.settings.key);
|
if (team.settings) history(team.settings.key, { state: {team: team} });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ListItemAvatar sx={{ marginLeft: isMobile ? -2 : 0 }}>
|
<ListItemAvatar sx={{ marginLeft: isMobile ? -2 : 0 }}>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Avatar } from "@mui/material";
|
import { Avatar, SxProps, Theme, useTheme } from "@mui/material";
|
||||||
import { Team, User } from "models";
|
import { Team, User } from "models";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
|
|
@ -40,18 +40,34 @@ interface Props {
|
||||||
user: User | Team;
|
user: User | Team;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: React.CSSProperties;
|
style?: React.CSSProperties;
|
||||||
|
sx?: SxProps<Theme>;
|
||||||
|
size?: "small" | "medium" | "large";
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function UserSettings(props: Props) {
|
export default function UserAvatar(props: Props) {
|
||||||
const { user, className, style } = props;
|
const theme = useTheme()
|
||||||
|
const { user, className, style, sx, size } = props;
|
||||||
|
|
||||||
|
const getSize = () => {
|
||||||
|
if (size==="large") return theme.spacing(8)
|
||||||
|
if (size==="medium") return theme.spacing(6)
|
||||||
|
if (size==="small") return theme.spacing(4)
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={user.name()}
|
alt={user.name()}
|
||||||
|
// sx={sx}
|
||||||
src={user.settings.avatar}
|
src={user.settings.avatar}
|
||||||
className={className}
|
className={className}
|
||||||
style={style}
|
style={style}
|
||||||
{...stringAvatar(user.name())}
|
{...stringAvatar(user.name())}
|
||||||
|
sx={{
|
||||||
|
...sx, // Spread the existing styles
|
||||||
|
width: getSize(), // Override the width
|
||||||
|
height: getSize(), // Override the height
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,6 @@ export default function UserSettings(props: Props) {
|
||||||
const changeTimezone = (tz: string) => {
|
const changeTimezone = (tz: string) => {
|
||||||
let updatedUser = User.clone(user);
|
let updatedUser = User.clone(user);
|
||||||
updatedUser.settings.timezone = tz;
|
updatedUser.settings.timezone = tz;
|
||||||
|
|
||||||
setUser(updatedUser);
|
setUser(updatedUser);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -183,7 +182,6 @@ export default function UserSettings(props: Props) {
|
||||||
<MuiTelInput value={phoneNumber} onChange={changePhoneNumber} />
|
<MuiTelInput value={phoneNumber} onChange={changePhoneNumber} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Grid2>
|
</Grid2>
|
||||||
|
|
||||||
<Grid2 size={{ xs: 12 }}>
|
<Grid2 size={{ xs: 12 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<SearchSelect
|
<SearchSelect
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue