Skip to content

Commit d48d92c

Browse files
committed
Merge branch 'develop' into 2136-design-review-calendar-design-review-edit-page
2 parents dfa15ed + 45dc328 commit d48d92c

29 files changed

Lines changed: 448 additions & 154 deletions

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

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,11 @@ export default class DesignReviewsService {
162162
for (const memberUserSetting of memberUserSettings) {
163163
if (memberUserSetting.slackId) {
164164
try {
165-
await sendSlackDesignReviewNotification(memberUserSetting.slackId, designReview.designReviewId);
165+
await sendSlackDesignReviewNotification(
166+
memberUserSetting.slackId,
167+
designReview.designReviewId,
168+
designReview.wbsElement.name
169+
);
166170
} catch (err: unknown) {
167171
if (err instanceof Error) {
168172
throw new HttpException(500, `Failed to send slack notification: ${err.message}`);
@@ -321,19 +325,22 @@ export default class DesignReviewsService {
321325
if (!isUserOnDesignReview(submitter, designReviewTransformer(designReview)))
322326
throw new HttpException(400, 'Current user is not in the list of this design reviews members');
323327

324-
// Update user schedule settings
325-
const validAvailability = validateMeetingTimes(availability);
328+
availability.forEach((time) => {
329+
if (time < 0 || time > 83) {
330+
throw new HttpException(400, 'Availability times have to be in range 0-83');
331+
}
332+
});
326333

327334
await prisma.schedule_Settings.upsert({
328335
where: { userId: submitter.userId },
329336
update: {
330-
availability: validAvailability
337+
availability
331338
},
332339
create: {
333340
userId: submitter.userId,
334341
personalGmail: '',
335342
personalZoomLink: '',
336-
availability: validAvailability
343+
availability
337344
}
338345
});
339346

@@ -353,7 +360,7 @@ export default class DesignReviewsService {
353360

354361
// If all requested attendees have confirmed their schedule, mark design review as confirmed
355362
if (
356-
designReview.confirmedMembers.length ===
363+
updatedDesignReview.confirmedMembers.length ===
357364
designReview.requiredMembers.length + designReview.optionalMembers.length
358365
) {
359366
await prisma.design_Review.update({

src/backend/src/utils/slack.utils.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,15 @@ export const sendReimbursementRequestDeniedNotification = async (slackId: string
9191
}
9292
};
9393

94-
export const sendSlackDesignReviewNotification = async (slackId: string, designReviewId: string) => {
94+
export const sendSlackDesignReviewNotification = async (
95+
slackId: string,
96+
designReviewId: string,
97+
designReviewName: string
98+
) => {
9599
if (process.env.NODE_ENV !== 'production') return; // don't send msgs unless in prod
96-
const msg = `You have been invited to a Design Review!`;
97-
const fullLink = `https://finishlinebyner.com/design-reviews/${designReviewId}`;
98-
const linkButtonText = 'RSVP for the Design Review';
100+
const msg = `You have been invited to the ${designReviewName} Design Review!`;
101+
const fullLink = `https://finishlinebyner.com/settings/preferences?drId=${designReviewId}`;
102+
const linkButtonText = 'Confirm Availability';
99103

100104
try {
101105
await sendMessage(slackId, msg, fullLink, linkButtonText);

src/backend/tests/design-reviews.test.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -535,14 +535,7 @@ describe('Design Reviews', () => {
535535
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(prismaDesignReview5);
536536
await expect(() =>
537537
DesignReviewsService.markUserConfirmed(prismaDesignReview5.designReviewId, [0, 85], batman)
538-
).rejects.toThrow(new HttpException(400, 'Meeting times have to be in range 0-83'));
539-
});
540-
541-
test('Availabilities were invalid - non-consecutive', async () => {
542-
vi.spyOn(prisma.design_Review, 'findUnique').mockResolvedValue(prismaDesignReview5);
543-
await expect(() =>
544-
DesignReviewsService.markUserConfirmed(prismaDesignReview5.designReviewId, [1, 3], batman)
545-
).rejects.toThrow(new HttpException(400, 'Meeting times have to be consecutive'));
538+
).rejects.toThrow(new HttpException(400, 'Availability times have to be in range 0-83'));
546539
});
547540
});
548541
});

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,15 @@ export const getSingleDesignReview = async (id: string) => {
5757
transformResponse: (data) => designReviewTransformer(JSON.parse(data))
5858
});
5959
};
60+
61+
/**
62+
* Deletes a design review
63+
* @param id the ID of the design review to delete
64+
*/
65+
export const deleteDesignReview = async (id: string) => {
66+
return axios.delete(apiUrls.designReviewDelete(id));
67+
};
68+
69+
export const markUserConfirmed = async (id: string, payload: { availability: number[] }) => {
70+
return axios.post<DesignReview>(apiUrls.designReviewMarkUserConfirmed(id), payload);
71+
};

src/frontend/src/components/Tabs.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,10 @@ interface TabProps {
1313
baseUrl: string; //the URL that all the tab URLs extend
1414
defaultTab: string; //tab that the tabs component defaults to
1515
id: string;
16+
noUnderline?: boolean;
1617
}
1718

18-
const NERTabs = ({ setTab, tabsLabels, baseUrl, defaultTab, id }: TabProps) => {
19+
const NERTabs = ({ setTab, tabsLabels, baseUrl, defaultTab, id, noUnderline = false }: TabProps) => {
1920
const tabUrlValues = tabsLabels.map((tab) => tab.tabUrlValue);
2021
const match = useRouteMatch<{ tabValueString: string }>(`${baseUrl}/:tabValueString`);
2122
const tabValueString = match?.params?.tabValueString;
@@ -39,7 +40,7 @@ const NERTabs = ({ setTab, tabsLabels, baseUrl, defaultTab, id }: TabProps) => {
3940
<Tabs value={tabValue} onChange={handleTabChange} aria-label={`${id}-tabs`}>
4041
{tabsLabels.map((tab, idx) => (
4142
<Tab
42-
sx={{ borderBottom: 1, borderColor: 'divider' }}
43+
sx={noUnderline ? {} : { borderBottom: 1, borderColor: 'divider' }}
4344
label={tab.tabName}
4445
aria-label={tab.tabUrlValue}
4546
value={idx}

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

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import { useMutation, useQuery, useQueryClient } from 'react-query';
66
import { DesignReview, TeamType, WbsNumber, DesignReviewStatus } from 'shared';
77
import {
8+
deleteDesignReview,
89
editDesignReview,
910
createDesignReviews,
1011
getAllDesignReviews,
1112
getAllTeamTypes,
12-
getSingleDesignReview
13+
getSingleDesignReview,
14+
markUserConfirmed
1315
} from '../apis/design-reviews.api';
16+
import { useCurrentUser } from './users.hooks';
1417

1518
export interface CreateDesignReviewsPayload {
1619
dateScheduled: Date;
@@ -96,14 +99,56 @@ export const useAllTeamTypes = () => {
9699
});
97100
};
98101

102+
/**
103+
* Custom react hook to delete a design review
104+
*/
105+
106+
export const useDeleteDesignReview = (id: string) => {
107+
const queryClient = useQueryClient();
108+
return useMutation<DesignReview, Error>(
109+
['design-reviews', 'delete'],
110+
async () => {
111+
const { data } = await deleteDesignReview(id);
112+
return data;
113+
},
114+
{
115+
onSuccess: () => {
116+
queryClient.invalidateQueries(['design-reviews']);
117+
}
118+
}
119+
);
120+
};
121+
99122
/**
100123
* Custom react hook to get a single design review
101124
*
102125
* @returns a single design review
103126
*/
104-
export const useSingleDesignReview = (id: string) => {
105-
return useQuery<DesignReview, Error>(['design-reviews', id], async () => {
106-
const { data } = await getSingleDesignReview(id);
107-
return data;
108-
});
127+
export const useSingleDesignReview = (id?: string) => {
128+
return useQuery<DesignReview, Error>(
129+
['design-reviews', id],
130+
async () => {
131+
const { data } = await getSingleDesignReview(id!);
132+
return data;
133+
},
134+
{ enabled: !!id }
135+
);
136+
};
137+
138+
export const useMarkUserConfirmed = (id: string) => {
139+
const user = useCurrentUser();
140+
const queryClient = useQueryClient();
141+
return useMutation<DesignReview, Error, { availability: number[] }>(
142+
['design-reviews', 'mark-confirmed'],
143+
async (designReviewPayload: { availability: number[] }) => {
144+
const { data } = await markUserConfirmed(id, designReviewPayload);
145+
return data;
146+
},
147+
{
148+
onSuccess: () => {
149+
queryClient.invalidateQueries(['design-reviews']);
150+
queryClient.invalidateQueries(['users', user.userId, 'schedule-settings']);
151+
}
152+
}
153+
);
109154
};

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

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
import React, { useState } from 'react';
2-
import PageBlock from '../../layouts/PageBlock';
3-
import { TextField, FormControl, FormLabel, Select, MenuItem, SelectChangeEvent, TableCell, TableRow } from '@mui/material';
2+
import {
3+
TextField,
4+
FormControl,
5+
FormLabel,
6+
Select,
7+
MenuItem,
8+
SelectChangeEvent,
9+
TableCell,
10+
TableRow,
11+
Grid,
12+
Typography
13+
} from '@mui/material';
414
import AdminToolTable from './AdminToolTable';
515

616
const AdminToolsAttendeeDesignReviewInfo: React.FC = () => {
@@ -37,7 +47,10 @@ const AdminToolsAttendeeDesignReviewInfo: React.FC = () => {
3747
));
3848

3949
return (
40-
<PageBlock title="Attendee Design Review Information">
50+
<Grid>
51+
<Typography variant="h5" color="red" borderBottom={1} borderColor={'white'} gutterBottom>
52+
Design Review Attendee Info
53+
</Typography>
4154
<FormControl fullWidth sx={{ marginBottom: 2 }}>
4255
<FormLabel htmlFor="search-by-name">Search by team member name</FormLabel>
4356
<TextField id="search-by-name" variant="outlined" value={searchQuery} onChange={handleSearchChange} fullWidth />
@@ -68,7 +81,7 @@ const AdminToolsAttendeeDesignReviewInfo: React.FC = () => {
6881
]}
6982
rows={attendeeRows}
7083
/>
71-
</PageBlock>
84+
</Grid>
7285
);
7386
};
7487

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1-
import { Grid } from '@mui/material';
2-
import PageBlock from '../../layouts/PageBlock';
1+
import { Grid, Typography } from '@mui/material';
32
import ManufacturerTable from './BOMConfig/ManufacturerTable';
43
import MaterialTypeTable from './BOMConfig/MaterialTypeTable';
54
import UnitTable from './BOMConfig/UnitTable';
5+
import { Box } from '@mui/system';
66

77
const AdminToolsBOMConfig: React.FC = () => {
88
return (
9-
<PageBlock title="Bill of Material Config">
9+
<Box>
10+
<Typography variant="h5" gutterBottom borderBottom={1} color="red" borderColor={'white'}>
11+
Bill of Materials Config
12+
</Typography>
1013
<Grid container spacing="3%">
1114
<Grid item direction="column" xs={12} md={6}>
1215
<ManufacturerTable />
@@ -18,7 +21,7 @@ const AdminToolsBOMConfig: React.FC = () => {
1821
<UnitTable />
1922
</Grid>
2023
</Grid>
21-
</PageBlock>
24+
</Box>
2225
);
2326
};
2427

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import { Grid } from '@mui/material';
2-
import PageBlock from '../../layouts/PageBlock';
1+
import { Box, Grid, Typography } from '@mui/material';
32
import VendorsTable from './FinanceConfig/VendorsTable';
43
import AccountCodesTable from './FinanceConfig/AccountCodesTable';
54

65
const AdminToolsFinanceConfig: React.FC = () => {
76
return (
8-
<PageBlock title="Finance Config">
7+
<Box padding="5px">
8+
<Typography variant="h5" gutterBottom borderBottom={1} color="red" borderColor={'white'}>
9+
Finance Config
10+
</Typography>
911
<Grid container spacing="3%">
1012
<Grid item direction="column" xs={12} md={6}>
1113
<VendorsTable />
@@ -14,7 +16,7 @@ const AdminToolsFinanceConfig: React.FC = () => {
1416
<AccountCodesTable />
1517
</Grid>
1618
</Grid>
17-
</PageBlock>
19+
</Box>
1820
);
1921
};
2022

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

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import AdminToolsProjectsConfig from './AdminToolsProjectsConfig';
1616
import { useState } from 'react';
1717
import NERTabs from '../../components/Tabs';
1818
import { routes } from '../../utils/routes';
19+
import { Box } from '@mui/system';
1920

2021
const AdminToolsPage: React.FC = () => {
2122
const currentUser = useCurrentUser();
@@ -42,10 +43,12 @@ const AdminToolsPage: React.FC = () => {
4243

4344
const UserManagementTab = () => {
4445
return isUserAdmin ? (
45-
<>
46-
<AdminToolsUserManagement />
46+
<Box>
47+
<Box mb={2}>
48+
<AdminToolsUserManagement />
49+
</Box>
4750
<TeamsTools />
48-
</>
51+
</Box>
4952
) : (
5053
<AdminToolsUserManagement />
5154
);
@@ -66,13 +69,16 @@ const AdminToolsPage: React.FC = () => {
6669
<PageLayout
6770
title="Admin Tools"
6871
tabs={
69-
<NERTabs
70-
setTab={setTabIndex}
71-
tabsLabels={tabs}
72-
baseUrl={routes.ADMIN_TOOLS}
73-
defaultTab={defaultTab}
74-
id="admin-tools-tabs"
75-
/>
72+
<Box borderBottom={1} borderColor={'divider'} width={'100%'}>
73+
<NERTabs
74+
noUnderline
75+
setTab={setTabIndex}
76+
tabsLabels={tabs}
77+
baseUrl={routes.ADMIN_TOOLS}
78+
defaultTab={defaultTab}
79+
id="admin-tools-tabs"
80+
/>
81+
</Box>
7682
}
7783
>
7884
{tabIndex === 0 ? (
@@ -82,10 +88,12 @@ const AdminToolsPage: React.FC = () => {
8288
) : tabIndex === 2 ? (
8389
<AdminToolsFinanceConfig />
8490
) : (
85-
<>
86-
<AdminToolsSlackUpcomingDeadlines />
91+
<Box>
92+
<Box pb={2}>
93+
<AdminToolsSlackUpcomingDeadlines />
94+
</Box>
8795
<AdminToolsAttendeeDesignReviewInfo />
88-
</>
96+
</Box>
8997
)}
9098
</PageLayout>
9199
);

0 commit comments

Comments
 (0)