Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#2131 Create DRC create modal #2208

Merged
merged 18 commits into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 11 additions & 19 deletions src/backend/src/controllers/design-reviews.controllers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ export default class DesignReviewsController {
}
}

static async getAllTeamTypes(req: Request, res: Response, next: NextFunction) {
try {
const teamTypes = await DesignReviewsService.getAllTeamTypes();
return res.status(200).json(teamTypes);
} catch (error: unknown) {
next(error);
}
}

static async deleteDesignReview(req: Request, res: Response, next: NextFunction) {
try {
const drId: string = req.params.designReviewId;
Expand All @@ -27,33 +36,16 @@ export default class DesignReviewsController {
static async createDesignReview(req: Request, res: Response, next: NextFunction) {
try {
const submitter: User = await getCurrentUser(res);
const {
dateScheduled,
teamTypeId,
requiredMemberIds,
optionalMemberIds,
location,
isOnline,
isInPerson,
zoomLink,
docTemplateLink,
wbsNum,
meetingTimes
} = req.body;
const { dateScheduled, teamTypeId, requiredMemberIds, optionalMemberIds, wbsNum, meetingTimes } = req.body;

const createdDesignReview = await DesignReviewsService.createDesignReview(
submitter,
dateScheduled,
teamTypeId,
requiredMemberIds,
optionalMemberIds,
isOnline,
isInPerson,
docTemplateLink,
wbsNum,
meetingTimes,
zoomLink,
location
meetingTimes
);
return res.status(200).json(createdDesignReview);
} catch (error: unknown) {
Expand Down
11 changes: 5 additions & 6 deletions src/backend/src/routes/design-reviews.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const designReviewsRouter = express.Router();

designReviewsRouter.get('/', DesignReviewsController.getAllDesignReviews);

designReviewsRouter.get('/teamType/all', DesignReviewsController.getAllTeamTypes);

designReviewsRouter.delete('/:designReviewId/delete', DesignReviewsController.deleteDesignReview);
designReviewsRouter.get('/:designReviewId', DesignReviewsController.getSingleDesignReview);

Expand All @@ -18,12 +20,9 @@ designReviewsRouter.post(
intMinZero(body('requiredMemberIds.*')),
body('optionalMemberIds').isArray(),
intMinZero(body('optionalMemberIds.*')),
nonEmptyString(body('location').optional()),
body('isOnline').isBoolean(),
body('isInPerson').isBoolean(),
nonEmptyString(body('zoomLink').optional()),
nonEmptyString(body('docTemplateLink')).optional(),
body('wbsNum'),
intMinZero(body('wbsNum.carNumber')),
intMinZero(body('wbsNum.projectNumber')),
intMinZero(body('wbsNum.workPackageNumber')),
body('meetingTimes').isArray(),
intMinZero(body('meetingTimes.*')),
validateInputs,
Expand Down
50 changes: 17 additions & 33 deletions src/backend/src/services/design-reviews.services.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Design_Review_Status, User } from '@prisma/client';
import { DesignReview, WbsNumber, isAdmin, isLeadership, isNotLeadership } from 'shared';
import { DesignReview, TeamType, WbsNumber, isAdmin, isLeadership, isNotLeadership } from 'shared';
import prisma from '../prisma/prisma';
import {
NotFoundException,
Expand Down Expand Up @@ -27,6 +27,11 @@ export default class DesignReviewsService {
return designReviews.map(designReviewTransformer);
}

static async getAllTeamTypes(): Promise<TeamType[]> {
const teamTypes = await prisma.teamType.findMany();
return teamTypes;
}

/**
* Deletes a design review
* @param submitter the user who deleted the design review
Expand Down Expand Up @@ -55,34 +60,24 @@ export default class DesignReviewsService {
}

/**
* Creates a design review
* @param submitter user who submitted the design review
* Create a design review
* @param submitter User submitting the design review
* @param dateScheduled when the design review is scheduled for
* @param teamTypeId team type id of the design review
* @param requiredMemberIds ids of the required members to attend the design review
* @param optionalMemberIds ids of the optional members to attend the design reivew
* @param isOnline if design review is online
* @param isInPerson if design review is in person
* @param docTemplateLink link to the doc template
* @param wbsNum wbs number for the design review
* @param meetingTimes the meeting times for the design review
* @param zoomLink link for the zoom if design review is online
* @param location location of the design review if in person
* @returns a design review
* @param teamTypeId team type id
* @param requiredMemberIds ids of members who are required to go
* @param optionalMemberIds ids of members who do not have to go
* @param wbsNum wbs num related to the design review
* @param meetingTimes meeting times of the design review
* @returns a new design review
*/
static async createDesignReview(
submitter: User,
dateScheduled: Date,
teamTypeId: string,
requiredMemberIds: number[],
optionalMemberIds: number[],
isOnline: boolean,
isInPerson: boolean,
docTemplateLink: string,
wbsNum: WbsNumber,
meetingTimes: number[],
zoomLink?: string,
location?: string
meetingTimes: number[]
): Promise<DesignReview> {
if (!isLeadership(submitter.role)) throw new AccessDeniedException('create design review');

Expand Down Expand Up @@ -125,14 +120,6 @@ export default class DesignReviewsService {
}
}

if (isOnline && !zoomLink) {
throw new HttpException(400, 'If the design review is online then there needs to be a zoom link');
}

if (isInPerson && !location) {
throw new HttpException(400, 'If the design review is in person then there needs to be a location');
}

if (dateScheduled.valueOf() < new Date().valueOf()) {
throw new HttpException(400, 'Design review cannot be scheduled for a past day');
}
Expand All @@ -142,11 +129,8 @@ export default class DesignReviewsService {
dateScheduled,
dateCreated: new Date(),
status: Design_Review_Status.UNCONFIRMED,
location,
isOnline,
isInPerson,
zoomLink,
docTemplateLink,
isOnline: false,
isInPerson: false,
userCreated: { connect: { userId: submitter.userId } },
teamType: { connect: { teamTypeId: teamType.teamTypeId } },
requiredMembers: { connect: requiredMemberIds.map((memberId) => ({ userId: memberId })) },
Expand Down
24 changes: 4 additions & 20 deletions src/backend/tests/design-reviews.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,16 +426,12 @@ describe('Design Reviews', () => {
'1',
[],
[],
true,
false,
'doc temp',
{
carNumber: 1,
projectNumber: 2,
workPackageNumber: 0
},
[0, 1, 2, 3],
'zoooooom'
[0, 1, 2, 3]
);

expect(res.teamType).toBe(teamType1);
Expand All @@ -449,16 +445,12 @@ describe('Design Reviews', () => {
'1',
[],
[],
true,
false,
'doc temp',
{
carNumber: 1,
projectNumber: 2,
workPackageNumber: 0
},
[0, 1, 2, 3],
'zoom'
[0, 1, 2, 3]
)
).rejects.toThrow(new AccessDeniedException('create design review'));
});
Expand All @@ -472,16 +464,12 @@ describe('Design Reviews', () => {
'15',
[],
[],
true,
false,
'doc temp',
{
carNumber: 1,
projectNumber: 2,
workPackageNumber: 0
},
[0, 1, 2, 3],
'zoom'
[0, 1, 2, 3]
)
).rejects.toThrow(new NotFoundException('Team Type', '15'));
});
Expand All @@ -497,16 +485,12 @@ describe('Design Reviews', () => {
'1',
[],
[],
true,
false,
'doc temp',
{
carNumber: 15,
projectNumber: 2,
workPackageNumber: 0
},
[0, 1, 2, 3],
'zoom'
[0, 1, 2, 3]
)
).rejects.toThrow(new NotFoundException('WBS Element', 15));
});
Expand Down
19 changes: 19 additions & 0 deletions src/frontend/src/apis/design-reviews.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,19 @@
*/
import { EditDesignReviewPayload } from '../hooks/design-reviews.hooks';
import axios from '../utils/axios';
import { DesignReview } from 'shared';
import { apiUrls } from '../utils/urls';
import { CreateDesignReviewsPayload } from '../hooks/design-reviews.hooks';
import { designReviewTransformer } from './transformers/design-reviews.tranformers';

/**
* Create a design review
* @param payload all info needed to create a design review
*/
export const createDesignReviews = async (payload: CreateDesignReviewsPayload) => {
return axios.post<DesignReview>(apiUrls.designReviewsCreate(), payload);
};

/**
* Gets all the design reviews
*/
Expand All @@ -16,6 +26,15 @@ export const getAllDesignReviews = () => {
});
};

/**
* Gets all the team types
*/
export const getAllTeamTypes = () => {
return axios.get(apiUrls.teamTypes(), {
transformResponse: (data) => JSON.parse(data)
});
};

/**
* Edit a design review
*
Expand Down
47 changes: 45 additions & 2 deletions src/frontend/src/hooks/design-reviews.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,39 @@
* See the LICENSE file in the repository root folder for details.
*/
import { useMutation, useQuery, useQueryClient } from 'react-query';
import { editDesignReview, getAllDesignReviews, getSingleDesignReview } from '../apis/design-reviews.api';
import { DesignReview, DesignReviewStatus } from 'shared';
import { DesignReview, TeamType, WbsNumber, DesignReviewStatus } from 'shared';
import {
editDesignReview,
createDesignReviews,
getAllDesignReviews,
getAllTeamTypes,
getSingleDesignReview
} from '../apis/design-reviews.api';

export interface CreateDesignReviewsPayload {
dateScheduled: Date;
teamTypeId: string;
requiredMemberIds: number[];
optionalMemberIds: number[];
wbsNum: WbsNumber;
meetingTimes: number[];
}

export const useCreateDesignReviews = () => {
const queryClient = useQueryClient();
return useMutation<DesignReview, Error, CreateDesignReviewsPayload>(
['design reviews', 'create'],
async (formData: CreateDesignReviewsPayload) => {
const { data } = await createDesignReviews(formData);
return data;
},
{
onSuccess: () => {
queryClient.invalidateQueries(['design-reviews']);
}
}
);
};

/**
* Custom react hook to get all design reviews
Expand Down Expand Up @@ -53,6 +84,18 @@ export const useEditDesignReview = (designReviewId: string) => {
);
};

/**
* Custom react hook to get all team types
*
* @returns all the team types
*/
export const useAllTeamTypes = () => {
return useQuery<TeamType[], Error>(['teamTypes'], async () => {
const { data } = await getAllTeamTypes();
return data;
});
};

/**
* Custom react hook to get a single design review
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { Box, Card, CardContent, Grid, IconButton, Link, Stack, Tooltip, Typography } from '@mui/material';
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
import { DesignReview } from 'shared';
import { DesignReview, TeamType } from 'shared';
import { meetingStartTimePipe } from '../../../utils/pipes';
import ConstructionIcon from '@mui/icons-material/Construction';
import WorkOutlineIcon from '@mui/icons-material/WorkOutline';
import ElectricalServicesIcon from '@mui/icons-material/ElectricalServices';
import TerminalIcon from '@mui/icons-material/Terminal';
import { useState } from 'react';
import DRCSummaryModal from '../DesignReviewSummaryModal';
import { DesignReviewCreateModal } from '../DesignReviewCreateModal';
import DynamicTooltip from '../../../components/DynamicTooltip';

export const getTeamTypeIcon = (teamTypeName: string, isLarge?: boolean) => {
Expand All @@ -23,13 +24,16 @@ export const getTeamTypeIcon = (teamTypeName: string, isLarge?: boolean) => {
interface CalendarDayCardProps {
cardDate: Date;
events: DesignReview[];
teamTypes: TeamType[];
}

const CalendarDayCard: React.FC<CalendarDayCardProps> = ({ cardDate, events }) => {
const CalendarDayCard: React.FC<CalendarDayCardProps> = ({ cardDate, events, teamTypes }) => {
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);

const DayCardTitle = () => (
<Grid container alignItems="center" margin={0} padding={0}>
<Grid item>
<IconButton>
<IconButton onClick={() => setIsCreateModalOpen(true)}>
<AddCircleOutlineIcon fontSize="small" />
</IconButton>
</Grid>
Expand Down Expand Up @@ -145,6 +149,14 @@ const CalendarDayCard: React.FC<CalendarDayCardProps> = ({ cardDate, events }) =

return (
<Card sx={{ borderRadius: 2, minWidth: 150, maxWidth: 150, minHeight: 90, maxHeight: 90 }}>
<DesignReviewCreateModal
showModal={isCreateModalOpen}
handleClose={() => {
setIsCreateModalOpen(false);
}}
teamTypes={teamTypes}
defaultDate={cardDate}
/>
<CardContent sx={{ padding: 0 }}>
<DayCardTitle />
{events.length < 3 ? (
Expand Down
Loading
Loading