Skip to content

Commit ea12487

Browse files
committed
Merge branch 'develop' into feature/global-car-filtering
2 parents 8262627 + 3932dc3 commit ea12487

9 files changed

Lines changed: 210 additions & 40 deletions

File tree

src/frontend/src/layouts/Sidebar/Sidebar.tsx

Lines changed: 26 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@ import { useHomePageContext } from '../../app/HomePageContext';
2727
// once divisions developed, import TeamType from shared
2828
import { isGuest } from 'shared';
2929
// To be uncommented after divisions page is developed
30-
// import * as MuiIcons from '@mui/icons-material';
31-
// import { useAllTeamTypes } from '../../hooks/team-types.hooks';
32-
// import ErrorPage from '../../pages/ErrorPage';
30+
import * as MuiIcons from '@mui/icons-material';
31+
import { useAllTeamTypes } from '../../hooks/team-types.hooks';
32+
import { TeamType } from 'shared';
33+
import ErrorPage from '../../pages/ErrorPage';
3334
import BarChartIcon from '@mui/icons-material/BarChart';
3435
import { useCurrentUser } from '../../hooks/users.hooks';
3536
import QueryStatsIcon from '@mui/icons-material/QueryStats';
@@ -52,19 +53,18 @@ const Sidebar = ({ drawerOpen, setDrawerOpen, moveContent, setMoveContent }: Sid
5253
const { onPNMHomePage, onOnboardingHomePage } = useHomePageContext();
5354
const user = useCurrentUser();
5455
const { onGuestHomePage } = useHomePageContext();
55-
// const { isError: teamsError, error: teamsErrorMsg, data: teams } = useAllTeamTypes();
56+
const { isError: teamsError, error: teamsErrorMsg, data: teams } = useAllTeamTypes();
5657

57-
// To be uncommented once guest divisions pages are developed
58-
// const allTeams: LinkItem[] = (teams ?? []).map((team: TeamType) => {
59-
// const IconComponent = MuiIcons[(team.iconName in MuiIcons ? team.iconName : 'Circle') as keyof typeof MuiIcons];
60-
// return {
61-
// name: team.name,
62-
// icon: <IconComponent />,
63-
// route: routes.TEAMS + '/' + team.teamTypeId
64-
// };
65-
// });
58+
const allTeams: LinkItem[] = (teams ?? []).map((team: TeamType) => {
59+
const IconComponent = MuiIcons[(team.iconName in MuiIcons ? team.iconName : 'Circle') as keyof typeof MuiIcons];
60+
return {
61+
name: team.name,
62+
icon: <IconComponent />,
63+
route: routes.TEAMS + '/' + team.teamTypeId
64+
};
65+
});
6666

67-
// if (teamsError) return <ErrorPage error={teamsErrorMsg} />;
67+
if (teamsError) return <ErrorPage error={teamsErrorMsg} />;
6868
const memberLinkItems: LinkItem[] = [
6969
{
7070
name: 'Home',
@@ -138,23 +138,18 @@ const Sidebar = ({ drawerOpen, setDrawerOpen, moveContent, setMoveContent }: Sid
138138
},
139139

140140
// Teams tab here to be replaced with below code once guest divisions is developed
141-
!onGuestHomePage && {
142-
name: 'Teams',
143-
icon: <GroupIcon />,
144-
route: routes.TEAMS
145-
},
146-
// !onGuestHomePage
147-
// ? {
148-
// name: 'Teams',
149-
// icon: <GroupIcon />,
150-
// route: routes.TEAMS
151-
// }
152-
// : {
153-
// name: 'Divisions',
154-
// icon: <GroupIcon />,
155-
// route: routes.TEAMS,
156-
// subItems: allTeams
157-
// },
141+
!onGuestHomePage
142+
? {
143+
name: 'Teams',
144+
icon: <GroupIcon />,
145+
route: routes.TEAMS
146+
}
147+
: {
148+
name: 'Divisions',
149+
icon: <GroupIcon />,
150+
route: routes.TEAMS,
151+
subItems: allTeams
152+
},
158153
!onGuestHomePage && {
159154
name: 'Calendar',
160155
icon: <CalendarTodayIcon />,
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Box, Card, CardContent, Stack, Typography, useTheme, Link } from '@mui/material';
2+
import { TeamPreview } from 'shared';
3+
import { NERButton } from '../../components/NERButton';
4+
import { Link as RouterLink } from 'react-router-dom';
5+
6+
interface GuestSubteamCardProps {
7+
team: TeamPreview;
8+
}
9+
10+
const GuestSubteamCard: React.FC<GuestSubteamCardProps> = ({ team }) => {
11+
const theme = useTheme();
12+
13+
return (
14+
<Card
15+
variant="outlined"
16+
sx={{
17+
width: '100%',
18+
height: '100%',
19+
display: 'flex',
20+
flexDirection: 'column',
21+
background: theme.palette.background.paper,
22+
borderRadius: 2
23+
}}
24+
>
25+
<CardContent sx={{ padding: 2, display: 'flex', flexDirection: 'column', flexGrow: 1 }}>
26+
<Stack direction="row" justifyContent="space-between">
27+
<Box width={'100%'}>
28+
<Box display="flex" justifyContent="space-between" alignItems="center">
29+
<Typography
30+
fontWeight={'regular'}
31+
variant="h5"
32+
sx={{ marginBottom: '0.2rem', fontSize: { xs: '1.15rem', sm: '1.5rem' }, flexGrow: 1 }}
33+
>
34+
{team.teamName}
35+
</Typography>
36+
</Box>
37+
<Typography fontSize={12} color="text.secondary" sx={{ marginBottom: 1 }}>
38+
Project Lead:{' '}
39+
{team.head?.firstName && team.head?.lastName ? `${team.head.firstName} ${team.head.lastName}` : 'N/A'}
40+
{' • '}
41+
{team.leads.length} {team.leads.length === 1 ? 'lead' : 'leads'}
42+
{' • '}
43+
{team.members.length} {team.members.length === 1 ? 'member' : 'members'}
44+
</Typography>
45+
</Box>
46+
</Stack>
47+
<Typography
48+
sx={{
49+
fontSize: 15,
50+
lineHeight: 1.4,
51+
flexGrow: 1,
52+
overflow: 'hidden',
53+
textOverflow: 'ellipsis',
54+
display: '-webkit-box',
55+
WebkitLineClamp: 3,
56+
WebkitBoxOrient: 'vertical'
57+
}}
58+
>
59+
{team.description}
60+
</Typography>
61+
<Link component={RouterLink} to={`/teams/${team.teamId}`} sx={{ width: '100%', textDecoration: 'none' }}>
62+
<NERButton
63+
fullWidth
64+
sx={{
65+
marginTop: 2,
66+
backgroundColor: theme.palette.error.main,
67+
color: theme.palette.error.contrastText,
68+
'&:hover': {
69+
backgroundColor: theme.palette.error.dark
70+
}
71+
}}
72+
>
73+
Learn more
74+
</NERButton>
75+
</Link>
76+
</CardContent>
77+
</Card>
78+
);
79+
};
80+
81+
export default GuestSubteamCard;
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import LoadingIndicator from '../../components/LoadingIndicator';
2+
import ErrorPage from '../ErrorPage';
3+
import { Box, useMediaQuery } from '@mui/system';
4+
import PageLayout from '../../components/PageLayout';
5+
import GuestSubteamCard from './GuestSubteamCard';
6+
import { useAllTeams } from '../../hooks/teams.hooks';
7+
import { useAllTeamTypes } from '../../hooks/team-types.hooks';
8+
import { Typography } from '@mui/material';
9+
10+
interface GuestTeamPageProps {
11+
teamTypeId: string;
12+
}
13+
14+
const GuestTeamPage: React.FC<GuestTeamPageProps> = ({ teamTypeId }) => {
15+
const isMobilePortrait = useMediaQuery('(max-width:480px)');
16+
const { isLoading: teamsIsLoading, isError: teamsIsError, data: allTeams, error: teamsError } = useAllTeams();
17+
const {
18+
isLoading: teamTypesIsLoading,
19+
isError: teamTypesIsError,
20+
data: allTeamTypes,
21+
error: teamTypesError
22+
} = useAllTeamTypes();
23+
24+
if (teamsIsError) return <ErrorPage message={teamsError.message} />;
25+
if (teamTypesIsError) return <ErrorPage message={teamTypesError.message} />;
26+
if (teamsIsLoading || !allTeams || teamTypesIsLoading || !allTeamTypes) return <LoadingIndicator />;
27+
28+
const teams = allTeams.filter((team) => team.teamType?.teamTypeId === teamTypeId);
29+
const teamTypeName = allTeamTypes.find((tt) => tt.teamTypeId === teamTypeId)?.name ?? '';
30+
31+
if (teams.length === 0) {
32+
return (
33+
<PageLayout title={teamTypeName}>
34+
<Box
35+
sx={{
36+
display: 'flex',
37+
justifyContent: 'center',
38+
alignItems: 'center',
39+
height: '70vh'
40+
}}
41+
>
42+
<Typography>No Teams found for this Division</Typography>
43+
</Box>
44+
</PageLayout>
45+
);
46+
}
47+
48+
return (
49+
<PageLayout title={teamTypeName}>
50+
<Box
51+
sx={{
52+
display: 'grid',
53+
gridTemplateColumns: isMobilePortrait ? '1fr' : 'repeat(3, 1fr)',
54+
gap: isMobilePortrait ? 2 : 3,
55+
width: '100%',
56+
px: isMobilePortrait ? 1 : 0
57+
}}
58+
>
59+
{teams.map((team) => (
60+
<GuestSubteamCard key={team.teamId} team={team} />
61+
))}
62+
</Box>
63+
</PageLayout>
64+
);
65+
};
66+
67+
export default GuestTeamPage;

src/frontend/src/pages/GuestProjectsPage/GuestProjectsCard.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ const GuestProjectsCard: React.FC<ProjectCardProps> = ({ project }) => {
7878
</Stack>
7979
<Typography
8080
sx={{
81+
fontSize: 15,
82+
lineHeight: 1.4,
8183
flexGrow: 1,
8284
overflow: 'hidden',
8385
textOverflow: 'ellipsis',

src/frontend/src/pages/TeamsPage/TeamSpecificPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,11 @@ const TeamSpecificPage: React.FC = () => {
194194
</Box>
195195
) : null
196196
}
197-
previousPages={[{ name: 'Teams', route: routes.TEAMS }]}
197+
previousPages={
198+
isGuest(user.role) && data.teamType
199+
? [{ name: data.teamType.name, route: `${routes.TEAMS}/${data.teamType.teamTypeId}` }]
200+
: [{ name: 'Teams', route: routes.TEAMS }]
201+
}
198202
>
199203
<Grid container spacing={2}>
200204
<Grid item xs={12}>

src/frontend/src/pages/TeamsPage/Teams.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,32 @@ import { Route, Switch } from 'react-router-dom';
77
import { routes } from '../../utils/routes';
88
import TeamsPage from './TeamsPage';
99
import TeamSpecificPage from './TeamSpecificPage';
10+
import { useParams } from 'react-router-dom';
11+
import { useCurrentUser } from '../../hooks/users.hooks';
12+
import { isGuest } from 'shared';
13+
import { useAllTeamTypes } from '../../hooks/team-types.hooks';
14+
import GuestTeamPage from '../GuestDivisionPage/GuestTeamPage';
15+
import LoadingIndicator from '../../components/LoadingIndicator';
16+
import ErrorPage from '../ErrorPage';
17+
18+
const TeamOrDivisionPage: React.FC = () => {
19+
const { teamId } = useParams<{ teamId: string }>();
20+
const user = useCurrentUser();
21+
const { isLoading: teamsLoading, isError: isTeamsError, data: teamTypes, error: teamsError } = useAllTeamTypes();
22+
23+
if (isTeamsError) return <ErrorPage message={teamsError.message} />;
24+
if (teamsLoading || !teamTypes) return <LoadingIndicator />;
25+
26+
if (isGuest(user.role) && teamTypes?.some((t) => t.teamTypeId === teamId)) {
27+
return <GuestTeamPage teamTypeId={teamId} />;
28+
}
29+
return <TeamSpecificPage />;
30+
};
1031

1132
const Teams: React.FC = () => {
1233
return (
1334
<Switch>
14-
<Route path={routes.TEAMS_BY_ID} component={TeamSpecificPage} />
35+
<Route path={routes.TEAMS_BY_ID} component={TeamOrDivisionPage} />
1536
<Route path={routes.TEAMS} component={TeamsPage} />
1637
</Switch>
1738
);

src/frontend/src/tests/hooks/ChangeRequests.hooks.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ describe('change request hooks', () => {
2929
mockedGetAllChangeRequests.mockReturnValue(mockPromiseAxiosResponse<ChangeRequest[]>(exampleAllChangeRequests));
3030

3131
const { result } = renderHook(() => useAllChangeRequests(), { wrapper });
32-
await waitFor(() => result.current.isSuccess);
32+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
3333
expect(result.current.data).toEqual(exampleAllChangeRequests);
3434
});
3535

@@ -38,7 +38,7 @@ describe('change request hooks', () => {
3838
mockedGetSingleChangeRequest.mockReturnValue(mockPromiseAxiosResponse<ChangeRequest>(exampleStageGateChangeRequest));
3939

4040
const { result } = renderHook(() => useSingleChangeRequest('1'), { wrapper });
41-
await waitFor(() => result.current.isSuccess);
41+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
4242
expect(result.current.data).toEqual(exampleStageGateChangeRequest);
4343
});
4444
});

src/frontend/src/tests/hooks/Projects.hooks.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ describe('project hooks', () => {
2323
mockedGetAllProjects.mockReturnValue(mockPromiseAxiosResponse<Project[]>(exampleAllProjects));
2424

2525
const { result } = renderHook(() => useAllProjectsGantt(), { wrapper });
26-
await waitFor(() => result.current.isSuccess);
26+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
2727
expect(result.current.data).toEqual(exampleAllProjects);
2828
});
2929

@@ -32,7 +32,7 @@ describe('project hooks', () => {
3232
mockedGetSingleProject.mockReturnValue(mockPromiseAxiosResponse<Project>(exampleProject1));
3333

3434
const { result } = renderHook(() => useSingleProject(exampleWbsProject1), { wrapper });
35-
await waitFor(() => result.current.isSuccess);
35+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
3636
expect(result.current.data).toEqual(exampleProject1);
3737
});
3838
});

src/frontend/src/tests/hooks/Users.hooks.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('user hooks', () => {
2222
mockedGetAllOrgUsers.mockReturnValue(mockPromiseAxiosResponse<User[]>(exampleAllUsers));
2323

2424
const { result } = renderHook(() => useAllUsers(), { wrapper });
25-
await waitFor(() => result.current.isSuccess);
25+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
2626
expect(result.current.data).toEqual(exampleAllUsers);
2727
});
2828

@@ -31,7 +31,7 @@ describe('user hooks', () => {
3131
mockedGetSingleUser.mockReturnValue(mockPromiseAxiosResponse<User>(exampleAdminUser));
3232

3333
const { result } = renderHook(() => useSingleUser('1'), { wrapper });
34-
await waitFor(() => result.current.isSuccess);
34+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
3535
expect(result.current.data).toEqual(exampleAdminUser);
3636
});
3737

@@ -47,7 +47,7 @@ describe('user hooks', () => {
4747
result.current.mutate(exampleAdminUser.email);
4848
});
4949

50-
await waitFor(() => result.current.isSuccess);
50+
await waitFor(() => expect(result.current.isSuccess).toBe(true));
5151
expect(result.current.data).toEqual(exampleAdminUser);
5252
});
5353
});

0 commit comments

Comments
 (0)