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:
Carter 2024-11-29 18:28:38 -06:00
parent 091634161f
commit 0e98d7329f
7 changed files with 63 additions and 23 deletions

View file

@ -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>
); );
} }

View file

@ -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 ?

View file

@ -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"

View file

@ -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>}

View file

@ -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 }}>

View file

@ -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
}}
/> />
); );
} }

View file

@ -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