Skip to content

Commit cdb2f09

Browse files
committed
Merge branch 'develop' into #2131-drc-create-modal
2 parents 3f7b2cc + 678fa36 commit cdb2f09

10 files changed

Lines changed: 154 additions & 14 deletions

File tree

src/frontend/src/apis/design-reviews.api.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,14 @@ export const getAllTeamTypes = () => {
3333
transformResponse: (data) => JSON.parse(data)
3434
});
3535
};
36+
37+
/**
38+
* Gets a single design review
39+
* @param id the ID of the design review to return
40+
* @returns the request design review
41+
*/
42+
export const getSingleDesignReview = async (id: string) => {
43+
return axios.get(apiUrls.designReviewById(id), {
44+
transformResponse: (data) => designReviewTransformer(JSON.parse(data))
45+
});
46+
};

src/frontend/src/hooks/design-reviews.hooks.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44
*/
55
import { useMutation, useQuery, useQueryClient } from 'react-query';
66
import { DesignReview, TeamType, WbsNumber } from 'shared';
7-
import { createDesignReviews, getAllDesignReviews, getAllTeamTypes } from '../apis/design-reviews.api';
7+
import {
8+
createDesignReviews,
9+
getAllDesignReviews,
10+
getAllTeamTypes,
11+
getSingleDesignReview
12+
} from '../apis/design-reviews.api';
813

914
export interface CreateDesignReviewsPayload {
1015
dateScheduled: Date;
@@ -54,3 +59,15 @@ export const useAllTeamTypes = () => {
5459
return data;
5560
});
5661
};
62+
63+
/**
64+
* Custom react hook to get a single design review
65+
*
66+
* @returns a single design review
67+
*/
68+
export const useSingleDesignReview = (id: string) => {
69+
return useQuery<DesignReview, Error>(['design-reviews', id], async () => {
70+
const { data } = await getSingleDesignReview(id);
71+
return data;
72+
});
73+
};
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import React, { useState } from 'react';
2+
import PageBlock from '../../layouts/PageBlock';
3+
import { TextField, FormControl, FormLabel, Select, MenuItem, SelectChangeEvent, TableCell, TableRow } from '@mui/material';
4+
import AdminToolTable from './AdminToolTable';
5+
6+
const AdminToolsAttendeeDesignReviewInfo: React.FC = () => {
7+
const [selectedTeam, setSelectedTeam] = useState('');
8+
const [searchQuery, setSearchQuery] = useState('');
9+
10+
// TODO: to be deleted later, this is just stub data for filter options
11+
const teams = ['All', 'Team A', 'Team B', 'Team C'];
12+
13+
// TODO: Stub data for team members, replace with dynamic data here
14+
const teamMembers = [
15+
{ name: 'Batman', reviewsAttended: 2, missedReviews: 4 },
16+
{ name: 'Superman', reviewsAttended: 4, missedReviews: 1 }
17+
];
18+
19+
// TODO: Filtering team members based on search query here
20+
const filteredMembers = teamMembers.filter((member) => member.name.toLowerCase().includes(searchQuery.toLowerCase()));
21+
22+
// TODO: Filtering for teams backend logic comes here
23+
const handleTeamChange = (event: SelectChangeEvent) => {
24+
setSelectedTeam(event.target.value as string);
25+
};
26+
27+
const handleSearchChange = (event: React.ChangeEvent<HTMLInputElement>) => {
28+
setSearchQuery(event.target.value);
29+
};
30+
31+
const attendeeRows = filteredMembers.map((member, index) => (
32+
<TableRow key={index}>
33+
<TableCell sx={{ border: '2px solid black' }}>{member.name}</TableCell>
34+
<TableCell sx={{ border: '2px solid black' }}>{member.reviewsAttended}</TableCell>
35+
<TableCell sx={{ border: '2px solid black' }}>{member.missedReviews}</TableCell>
36+
</TableRow>
37+
));
38+
39+
return (
40+
<PageBlock title="Attendee Design Review Information">
41+
<FormControl fullWidth sx={{ marginBottom: 2 }}>
42+
<FormLabel htmlFor="search-by-name">Search by team member name</FormLabel>
43+
<TextField id="search-by-name" variant="outlined" value={searchQuery} onChange={handleSearchChange} fullWidth />
44+
</FormControl>
45+
<FormControl fullWidth sx={{ marginBottom: 2 }}>
46+
<FormLabel id="team-select-label">Team</FormLabel>
47+
<Select
48+
labelId="team-select-label"
49+
id="team-select"
50+
value={selectedTeam}
51+
onChange={handleTeamChange}
52+
displayEmpty
53+
fullWidth
54+
>
55+
{teams.map((team) => (
56+
<MenuItem key={team} value={team}>
57+
{team}
58+
</MenuItem>
59+
))}
60+
{/* TODO: we'll have to change this here as well for backend logic, above is just a stub implementation. */}
61+
</Select>
62+
</FormControl>
63+
<AdminToolTable
64+
columns={[
65+
{ name: 'Team Member Name', width: '33%' },
66+
{ name: 'No. Of Design Reviews Attended', width: '33%' },
67+
{ name: 'Required to come but did not', width: '34%' }
68+
]}
69+
rows={attendeeRows}
70+
/>
71+
</PageBlock>
72+
);
73+
};
74+
75+
export default AdminToolsAttendeeDesignReviewInfo;

src/frontend/src/pages/AdminToolsPage/AdminToolsPage.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
import AdminToolsUserManagement from './AdminToolsUserManagement';
77
import AdminToolsSlackUpcomingDeadlines from './AdminToolsSlackUpcomingDeadlines';
8+
import AdminToolsAttendeeDesignReviewInfo from './AdminToolsAttendeeDesignReviewInfo';
89
import { useCurrentUser } from '../../hooks/users.hooks';
910
import { isAdmin, isHead } from 'shared';
1011
import PageLayout from '../../components/PageLayout';
@@ -81,7 +82,10 @@ const AdminToolsPage: React.FC = () => {
8182
) : tabIndex === 2 ? (
8283
<AdminToolsFinanceConfig />
8384
) : (
84-
<AdminToolsSlackUpcomingDeadlines />
85+
<>
86+
<AdminToolsSlackUpcomingDeadlines />
87+
<AdminToolsAttendeeDesignReviewInfo />
88+
</>
8589
)}
8690
</PageLayout>
8791
);

src/frontend/src/pages/CalendarPage/Calendar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import { Route, Switch } from 'react-router-dom';
66
import { routes } from '../../utils/routes';
77
import CalendarPage from './CalendarPage';
8-
import DesignReviewDetailPage from './DesignReviewDetailPage/DesignReviewDetailPage';
8+
import DesignReviewDetails from './DesignReviewDetailPage/DesignReviewDetails';
99

1010
const Calendar: React.FC = () => {
1111
return (
1212
<Switch>
13-
<Route path={routes.DESIGN_REVIEW_BY_ID} component={DesignReviewDetailPage} />
13+
<Route path={routes.DESIGN_REVIEW_BY_ID} component={DesignReviewDetails} />
1414
<Route path={routes.CALENDAR} component={CalendarPage} />
1515
</Switch>
1616
);

src/frontend/src/pages/CalendarPage/DesignReviewDetailPage/DesignReviewDetailPage.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,19 +10,20 @@ import { useState } from 'react';
1010
import CheckBoxIcon from '@mui/icons-material/CheckBox';
1111
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
1212
import { routes } from '../../../utils/routes';
13+
import { DesignReview, wbsPipe } from 'shared';
1314

1415
interface DesignReviewDetailPageProps {
15-
name: string;
16+
designReview: DesignReview;
1617
}
1718

18-
const DesignReviewDetailPage: React.FC<DesignReviewDetailPageProps> = ({ name }) => {
19+
const DesignReviewDetailPage: React.FC<DesignReviewDetailPageProps> = ({ designReview, designReview: { teamType } }) => {
1920
const theme = useTheme();
2021
const { isLoading: allUsersIsLoading, isError: allUsersIsError, error: allUsersError, data: allUsers } = useAllUsers();
2122
const [requiredUsers, setRequiredUsers] = useState([].map(userToAutocompleteOption));
2223
const [optionalUsers, setOptionalUsers] = useState([].map(userToAutocompleteOption));
2324
if (allUsersIsError) return <ErrorPage message={allUsersError?.message} />;
2425
if (allUsersIsLoading || !allUsers) return <LoadingIndicator />;
25-
26+
const designReviewName = `${wbsPipe(designReview.wbsNum)} - ${designReview.wbsName}`;
2627
const users = allUsers.map(userToAutocompleteOption);
2728

2829
return (
@@ -52,7 +53,7 @@ const DesignReviewDetailPage: React.FC<DesignReviewDetailPageProps> = ({ name })
5253
</Grid>
5354
<Grid item xs={3}>
5455
<Box sx={{ padding: 1.5, fontSize: '1.2em', backgroundColor: 'grey', borderRadius: 3, textAlign: 'center' }}>
55-
{name}
56+
{designReviewName}
5657
</Box>
5758
</Grid>
5859
<Grid item xs={1}>
@@ -74,7 +75,7 @@ const DesignReviewDetailPage: React.FC<DesignReviewDetailPageProps> = ({ name })
7475
<Grid item xs={3}>
7576
<Box sx={{ padding: 1, backgroundColor: 'grey', borderRadius: 3, textAlign: 'center' }}>
7677
<Autocomplete
77-
isOptionEqualToValue={(option, value) => option.id === value.id} // What is this for
78+
isOptionEqualToValue={(option, value) => option.id === value.id}
7879
multiple
7980
disableCloseOnSelect
8081
limitTags={1}
@@ -120,7 +121,7 @@ const DesignReviewDetailPage: React.FC<DesignReviewDetailPageProps> = ({ name })
120121
<Grid item xs={3}>
121122
<Box sx={{ padding: 1, backgroundColor: 'grey', borderRadius: 3, textAlign: 'center' }}>
122123
<Autocomplete
123-
isOptionEqualToValue={(option, value) => option.id === value.id} // What is this for
124+
isOptionEqualToValue={(option, value) => option.id === value.id}
124125
multiple
125126
disableCloseOnSelect
126127
limitTags={1}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* This file is part of NER's FinishLine and licensed under GNU AGPLv3.
3+
* See the LICENSE file in the repository root folder for details.
4+
*/
5+
import LoadingIndicator from '../../../components/LoadingIndicator';
6+
import { useParams } from 'react-router-dom';
7+
import ErrorPage from '../../ErrorPage';
8+
import DesignReviewDetailPage from './DesignReviewDetailPage';
9+
import { useSingleDesignReview } from '../../../hooks/design-reviews.hooks';
10+
11+
const DesignReviewDetails: React.FC = () => {
12+
const { id } = useParams<{ id: string }>();
13+
const { data: designReview, isError, error, isLoading } = useSingleDesignReview(id);
14+
15+
if (isError) return <ErrorPage error={error} />;
16+
if (!designReview || isLoading) return <LoadingIndicator />;
17+
18+
return <DesignReviewDetailPage designReview={designReview} />;
19+
};
20+
21+
export default DesignReviewDetails;

src/frontend/src/pages/CalendarPage/SummaryComponents/DesignReviewSummaryModalButtons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const DesignReviewSummaryModalButtons: React.FC<DesignReviewSummaryModalButtonsP
7474
textDecoration: 'none'
7575
}}
7676
component={RouterLink}
77-
to={`${routes.CALENDAR}/1`}
77+
to={`${routes.CALENDAR}/${designReview.designReviewId}`}
7878
>
7979
<NERFailButton
8080
sx={{

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

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import { AxiosResponse } from 'axios';
88
import { DesignReview } from 'shared';
99
import wrapper from '../../app/AppContextQuery';
1010
import { mockPromiseAxiosResponse } from '../test-support/test-data/test-utils.stub';
11-
import { exampleAllDesignReviews } from '../test-support/test-data/design-reviews.stub';
12-
import { getAllDesignReviews } from '../../apis/design-reviews.api';
13-
import { useAllDesignReviews } from '../../hooks/design-reviews.hooks';
11+
import { exampleAllDesignReviews, exampleDesignReview1 } from '../test-support/test-data/design-reviews.stub';
12+
import { getAllDesignReviews, getSingleDesignReview } from '../../apis/design-reviews.api';
13+
import { useAllDesignReviews, useSingleDesignReview } from '../../hooks/design-reviews.hooks';
1414

1515
vi.mock('../../apis/design-reviews.api');
1616

@@ -23,4 +23,13 @@ describe('design review hooks', () => {
2323
await waitFor(() => result.current.isSuccess);
2424
expect(result.current.data).toEqual(exampleAllDesignReviews);
2525
});
26+
27+
it('handles getting a single design review', async () => {
28+
const mockedGetSingleDesignReview = getSingleDesignReview as jest.Mock<Promise<AxiosResponse<DesignReview>>>;
29+
mockedGetSingleDesignReview.mockReturnValue(mockPromiseAxiosResponse<DesignReview>(exampleDesignReview1));
30+
31+
const { result, waitFor } = renderHook(() => useSingleDesignReview('1'), { wrapper });
32+
await waitFor(() => result.current.isSuccess);
33+
expect(result.current.data).toEqual(exampleDesignReview1);
34+
});
2635
});

src/frontend/src/utils/urls.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ const bomCreateUnit = () => `${bomEndpoints()}/units/create`;
135135
const designReviews = () => `${API_URL}/design-reviews`;
136136
const designReviewsCreate = () => `${designReviews()}/create`;
137137
const teamTypes = () => `${designReviews()}/teamType/all`;
138+
const designReviewById = (id: string) => `${designReviews()}/${id}`;
138139

139140
/**************** Other Endpoints ****************/
140141
const version = () => `https://api.github.com/repos/Northeastern-Electric-Racing/FinishLine/releases/latest`;
@@ -244,6 +245,7 @@ export const apiUrls = {
244245

245246
designReviews,
246247
designReviewsCreate,
248+
designReviewById,
247249
teamTypes,
248250

249251
version

0 commit comments

Comments
 (0)