Skip to content

Commit 3f7b2cc

Browse files
committed
#2131-required-optional-wbs-meeting
1 parent 879edd8 commit 3f7b2cc

7 files changed

Lines changed: 155 additions & 59 deletions

File tree

src/backend/src/controllers/design-reviews.controllers.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,6 @@ export default class DesignReviewsController {
4141
teamTypeId,
4242
requiredMemberIds,
4343
optionalMemberIds,
44-
location,
45-
isOnline,
46-
isInPerson,
47-
zoomLink,
48-
docTemplateLink,
4944
wbsNum,
5045
meetingTimes
5146
} = req.body;
@@ -56,13 +51,8 @@ export default class DesignReviewsController {
5651
teamTypeId,
5752
requiredMemberIds,
5853
optionalMemberIds,
59-
isOnline,
60-
isInPerson,
61-
docTemplateLink,
6254
wbsNum,
63-
meetingTimes,
64-
zoomLink,
65-
location
55+
meetingTimes
6656
);
6757
return res.status(200).json(createdDesignReview);
6858
} catch (error: unknown) {

src/backend/src/routes/design-reviews.routes.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ designReviewsRouter.post(
2020
intMinZero(body('requiredMemberIds.*')),
2121
body('optionalMemberIds').isArray(),
2222
intMinZero(body('optionalMemberIds.*')),
23-
nonEmptyString(body('location').optional()),
24-
body('isOnline').isBoolean(),
25-
body('isInPerson').isBoolean(),
26-
nonEmptyString(body('zoomLink').optional()),
27-
nonEmptyString(body('docTemplateLink')).optional(),
2823
body('wbsNum'),
2924
body('meetingTimes').isArray(),
3025
intMinZero(body('meetingTimes.*')),

src/backend/src/services/design-reviews.services.ts

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -60,34 +60,24 @@ export default class DesignReviewsService {
6060
}
6161

6262
/**
63-
* Creates a design review
64-
* @param submitter user who submitted the design review
63+
* Create a design review
64+
* @param submitter User submitting the design review
6565
* @param dateScheduled when the design review is scheduled for
66-
* @param teamTypeId team type id of the design review
67-
* @param requiredMemberIds ids of the required members to attend the design review
68-
* @param optionalMemberIds ids of the optional members to attend the design reivew
69-
* @param isOnline if design review is online
70-
* @param isInPerson if design review is in person
71-
* @param docTemplateLink link to the doc template
72-
* @param wbsNum wbs number for the design review
73-
* @param meetingTimes the meeting times for the design review
74-
* @param zoomLink link for the zoom if design review is online
75-
* @param location location of the design review if in person
76-
* @returns a design review
66+
* @param teamTypeId team type id
67+
* @param requiredMemberIds ids of members who are required to go
68+
* @param optionalMemberIds ids of members who do not have to go
69+
* @param wbsNum wbs num related to the design review
70+
* @param meetingTimes meeting times of the design review
71+
* @returns a new design review
7772
*/
7873
static async createDesignReview(
7974
submitter: User,
8075
dateScheduled: Date,
8176
teamTypeId: string,
8277
requiredMemberIds: number[],
8378
optionalMemberIds: number[],
84-
isOnline: boolean,
85-
isInPerson: boolean,
86-
docTemplateLink: string,
8779
wbsNum: WbsNumber,
8880
meetingTimes: number[],
89-
zoomLink?: string,
90-
location?: string
9181
): Promise<DesignReview> {
9282
if (!isLeadership(submitter.role)) throw new AccessDeniedException('create design review');
9383

@@ -130,14 +120,6 @@ export default class DesignReviewsService {
130120
}
131121
}
132122

133-
if (isOnline && !zoomLink) {
134-
throw new HttpException(400, 'If the design review is online then there needs to be a zoom link');
135-
}
136-
137-
if (isInPerson && !location) {
138-
throw new HttpException(400, 'If the design review is in person then there needs to be a location');
139-
}
140-
141123
if (dateScheduled.valueOf() < new Date().valueOf()) {
142124
throw new HttpException(400, 'Design review cannot be scheduled for a past day');
143125
}
@@ -147,11 +129,8 @@ export default class DesignReviewsService {
147129
dateScheduled,
148130
dateCreated: new Date(),
149131
status: Design_Review_Status.UNCONFIRMED,
150-
location,
151-
isOnline,
152-
isInPerson,
153-
zoomLink,
154-
docTemplateLink,
132+
isOnline: false,
133+
isInPerson: false,
155134
userCreated: { connect: { userId: submitter.userId } },
156135
teamType: { connect: { teamTypeId: teamType.teamTypeId } },
157136
requiredMembers: { connect: requiredMemberIds.map((memberId) => ({ userId: memberId })) },

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import axios from '../utils/axios';
66
import { DesignReview } from 'shared';
77
import { apiUrls } from '../utils/urls';
88
import { CreateDesignReviewsPayload } from '../hooks/design-reviews.hooks';
9+
import { designReviewTransformer } from './transformers/design-reviews.tranformers';
910

1011
/**
1112
* Create a design review
@@ -20,7 +21,7 @@ export const createDesignReviews = async (payload: CreateDesignReviewsPayload) =
2021
*/
2122
export const getAllDesignReviews = () => {
2223
return axios.get(apiUrls.designReviews(), {
23-
transformResponse: (data) => JSON.parse(data)
24+
transformResponse: (data) => JSON.parse(data).map(designReviewTransformer)
2425
});
2526
};
2627

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

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@ export interface CreateDesignReviewsPayload {
1111
teamTypeId: string;
1212
requiredMemberIds: number[];
1313
optionalMemberIds: number[];
14-
location?: string;
15-
isOnline: boolean;
16-
isInPerson: boolean;
17-
zoomLink?: string;
18-
docTemplateLink?: string;
1914
wbsNum: WbsNumber;
2015
meetingTimes: number[];
2116
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ import LoadingIndicator from '../../components/LoadingIndicator';
2020

2121
const CalendarPage = () => {
2222
const theme = useTheme();
23-
const { data: allTeamTypes, isLoading: allTeamTypesLoading, isError: allTeamTypesIsError, error: allTeamTypesError } = useAllTeamTypes();
23+
const {
24+
data: allTeamTypes,
25+
isLoading: allTeamTypesLoading,
26+
isError: allTeamTypesIsError,
27+
error: allTeamTypesError
28+
} = useAllTeamTypes();
2429

2530
const [displayMonthYear, setDisplayMonthYear] = useState<Date>(new Date());
2631
const { isLoading, isError, error, data: allDesignReviews } = useAllDesignReviews();
@@ -32,6 +37,7 @@ const CalendarPage = () => {
3237
const confirmedDesignReviews = allDesignReviews.filter(isConfirmed);
3338

3439
const eventDict = new Map<string, DesignReview[]>();
40+
console.log(confirmedDesignReviews);
3541
confirmedDesignReviews.forEach((designReview) => {
3642
// Accessing the date actually converts it to local time, which causes the date to be off. This is a workaround.
3743
const date = datePipe(

src/frontend/src/pages/CalendarPage/DesignReviewCreateModal.tsx

Lines changed: 135 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,31 @@ import NERFormModal from '../../components/NERFormModal';
22
import * as yup from 'yup';
33
import { yupResolver } from '@hookform/resolvers/yup';
44
import { Controller, useForm } from 'react-hook-form';
5-
import { Box, FormControl, FormLabel, MenuItem, Select, SelectChangeEvent, TextField, Typography } from '@mui/material';
5+
import {
6+
Autocomplete,
7+
Box,
8+
FormControl,
9+
FormLabel,
10+
Grid,
11+
MenuItem,
12+
Select,
13+
SelectChangeEvent,
14+
TextField,
15+
Typography
16+
} from '@mui/material';
617
import { DatePicker } from '@mui/x-date-pickers';
718
import { useToast } from '../../hooks/toasts.hooks';
819
import { useState } from 'react';
9-
import { meetingStartTimePipe } from '../../utils/pipes';
10-
import { TeamType } from 'shared';
20+
import { meetingStartTimePipe, wbsNamePipe } from '../../utils/pipes';
21+
import { Project, TeamType, WbsNumber, WorkPackage, validateWBS, wbsPipe } from 'shared';
22+
import { useCreateDesignReviews } from '../../hooks/design-reviews.hooks';
23+
import { useAllUsers } from '../../hooks/users.hooks';
24+
import ErrorPage from '../ErrorPage';
25+
import LoadingIndicator from '../../components/LoadingIndicator';
26+
import { userToAutocompleteOption } from '../../utils/teams.utils';
27+
import { useQuery } from '../../hooks/utils.hooks';
28+
import NERAutocomplete from '../../components/NERAutocomplete';
29+
import { useAllProjects } from '../../hooks/projects.hooks';
1130

1231
const schema = yup.object().shape({
1332
date: yup.date().required('Date is required'),
@@ -21,6 +40,9 @@ interface CreateDesignReviewFormInput {
2140
startTime: number;
2241
endTime: number;
2342
teamTypeId: string;
43+
requiredMemberIds: number[];
44+
optionalMemberIds: number[];
45+
wbsNum: WbsNumber;
2446
}
2547

2648
interface DesignReviewCreateModalProps {
@@ -31,15 +53,35 @@ interface DesignReviewCreateModalProps {
3153

3254
export const DesignReviewCreateModal: React.FC<DesignReviewCreateModalProps> = ({ showModal, handleClose, teamTypes }) => {
3355
const HOURS: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
56+
const query = useQuery();
3457

3558
const toast = useToast();
3659
const [datePickerOpen, setDatePickerOpen] = useState(false);
60+
const [requiredMembers, setRequiredMembers] = useState([].map(userToAutocompleteOption));
61+
const [optionalMembers, setOptionalMembers] = useState([].map(userToAutocompleteOption));
62+
const [wbsNum, setWbsNum] = useState(query.get('wbsNum') || '');
63+
const { isLoading: allUsersIsLoading, isError: allUsersIsError, error: allUsersError, data: users } = useAllUsers();
64+
const { data: projects } = useAllProjects();
3765

38-
// create design review hook
66+
const { mutateAsync } = useCreateDesignReviews();
3967

4068
const onSubmit = async (data: CreateDesignReviewFormInput) => {
69+
const day = data.date.getDay();
70+
console.log('Day: ' + day);
71+
const times = [];
72+
for (let i = day * 12 + data.startTime; i <= day * 12 + data.endTime; i++) {
73+
times.push(i);
74+
}
75+
console.log('times: ' + times);
4176
try {
42-
// await mutateAsync(data);
77+
await mutateAsync({
78+
dateScheduled: data.date,
79+
teamTypeId: data.teamTypeId,
80+
requiredMemberIds: requiredMembers.map((member) => parseInt(member.id)),
81+
optionalMemberIds: optionalMembers.map((member) => parseInt(member.id)),
82+
wbsNum: validateWBS(wbsNum),
83+
meetingTimes: times
84+
});
4385
} catch (error: unknown) {
4486
if (error instanceof Error) {
4587
toast.error(error.message);
@@ -63,6 +105,43 @@ export const DesignReviewCreateModal: React.FC<DesignReviewCreateModalProps> = (
63105
}
64106
});
65107

108+
if (allUsersIsError) return <ErrorPage message={allUsersError?.message} />;
109+
if (allUsersIsLoading || !users || !projects) return <LoadingIndicator />;
110+
111+
const memberOptions = users.map(userToAutocompleteOption);
112+
113+
const projectOptions: { label: string; id: string }[] = [];
114+
115+
const wbsDropdownOptions: { label: string; id: string }[] = [];
116+
117+
projects.forEach((project: Project) => {
118+
wbsDropdownOptions.push({
119+
label: `${wbsNamePipe(project)}`,
120+
id: wbsPipe(project.wbsNum)
121+
});
122+
projectOptions.push({
123+
label: `${wbsNamePipe(project)}`,
124+
id: wbsPipe(project.wbsNum)
125+
});
126+
project.workPackages.forEach((workPackage: WorkPackage) => {
127+
wbsDropdownOptions.push({
128+
label: `${wbsNamePipe(workPackage)}`,
129+
id: wbsPipe(workPackage.wbsNum)
130+
});
131+
});
132+
});
133+
134+
const wbsAutocompleteOnChange = (
135+
_event: React.SyntheticEvent<Element, Event>,
136+
value: { label: string; id: string } | null
137+
) => {
138+
if (value) {
139+
setWbsNum(value.id);
140+
} else {
141+
setWbsNum('');
142+
}
143+
};
144+
66145
return (
67146
<NERFormModal
68147
open={showModal}
@@ -224,6 +303,57 @@ export const DesignReviewCreateModal: React.FC<DesignReviewCreateModalProps> = (
224303
/>
225304
</FormControl>
226305
</Box>
306+
<Box>
307+
<Typography sx={{ fontWeight: 'bold' }} display="inline">
308+
Required Members:
309+
</Typography>
310+
<Grid container direction={'row'}>
311+
<Grid item xs={9} md={10} lg={11}>
312+
<Autocomplete
313+
isOptionEqualToValue={(option, value) => option.id === value.id}
314+
filterSelectedOptions
315+
multiple
316+
id="tags-standard"
317+
options={memberOptions}
318+
value={requiredMembers}
319+
onChange={(_event, newValue) => setRequiredMembers(newValue)}
320+
getOptionLabel={(option) => option.label}
321+
renderInput={(params) => <TextField {...params} variant="standard" placeholder="Select A User" />}
322+
/>
323+
</Grid>
324+
</Grid>
325+
</Box>
326+
<Box>
327+
<Typography sx={{ fontWeight: 'bold' }} display="inline">
328+
Optional Members:
329+
</Typography>
330+
<Grid container direction={'row'}>
331+
<Grid item xs={9} md={10} lg={11}>
332+
<Autocomplete
333+
isOptionEqualToValue={(option, value) => option.id === value.id}
334+
filterSelectedOptions
335+
multiple
336+
id="tags-standard"
337+
options={memberOptions}
338+
value={optionalMembers}
339+
onChange={(_event, newValue) => setOptionalMembers(newValue)}
340+
getOptionLabel={(option) => option.label}
341+
renderInput={(params) => <TextField {...params} variant="standard" placeholder="Select A User" />}
342+
/>
343+
</Grid>
344+
</Grid>
345+
</Box>
346+
<Grid item xs={12}>
347+
<FormLabel>WBS</FormLabel>
348+
<NERAutocomplete
349+
id="wbs-autocomplete"
350+
onChange={wbsAutocompleteOnChange}
351+
options={wbsDropdownOptions}
352+
size="small"
353+
placeholder="Select a project or work package"
354+
value={wbsDropdownOptions.find((element) => element.id === wbsNum) || null}
355+
/>
356+
</Grid>
227357
</NERFormModal>
228358
);
229359
};

0 commit comments

Comments
 (0)