Skip to content

Commit 80d2870

Browse files
authored
Merge branch 'develop' into #2062-single-design-review-hook
2 parents 6ce80ff + c70c875 commit 80d2870

7 files changed

Lines changed: 212 additions & 110 deletions

File tree

src/frontend/src/apis/transformers/design-reviews.tranformers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export const designReviewTransformer = (designReview: DesignReview) => {
44
return {
55
...designReview,
66
dateCreated: new Date(designReview.dateCreated),
7-
dateDeleted: designReview.dateDeleted ? new Date(designReview.dateDeleted) : undefined
7+
dateDeleted: designReview.dateDeleted ? new Date(designReview.dateDeleted) : undefined,
8+
dateScheduled: designReview.dateScheduled ? new Date(designReview.dateScheduled) : undefined
89
};
910
};

src/frontend/src/hooks/change-requests.hooks.ts

Lines changed: 66 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,22 @@ export const useSingleChangeRequest = (id: number) => {
3939
});
4040
};
4141

42+
export interface ReviewPayload {
43+
reviewerId: number;
44+
crId: number;
45+
accepted: boolean;
46+
reviewNotes: string;
47+
psId: string;
48+
}
49+
4250
/**
4351
* Custom React Hook to review a change request.
4452
*/
4553
export const useReviewChangeRequest = () => {
4654
const queryClient = useQueryClient();
47-
return useMutation<{ message: string }, Error, any>(
55+
return useMutation<{ message: string }, Error, ReviewPayload>(
4856
['change requests', 'review'],
49-
async (reviewPayload: any) => {
57+
async (reviewPayload: ReviewPayload) => {
5058
const { data } = await reviewChangeRequest(
5159
reviewPayload.reviewerId,
5260
reviewPayload.crId,
@@ -69,7 +77,7 @@ export const useReviewChangeRequest = () => {
6977
*/
7078
export const useDeleteChangeRequest = () => {
7179
const queryClient = useQueryClient();
72-
return useMutation<{ message: string }, Error, any>(
80+
return useMutation<{ message: string }, Error, number>(
7381
['change requests', 'delete'],
7482
async (id: number) => {
7583
const { data } = await deleteChangeRequest(id);
@@ -103,41 +111,73 @@ export const useCreateStandardChangeRequest = () => {
103111
);
104112
};
105113

114+
export interface CreateActivationChangeRequestPayload {
115+
submitterId: number;
116+
wbsNum: WbsNumber;
117+
projectLeadId: number;
118+
projectManagerId: number;
119+
startDate: string;
120+
confirmDetails: boolean;
121+
type: string;
122+
}
123+
124+
export interface CreateStageGateChangeRequestPayload {
125+
submitterId: number;
126+
wbsNum: WbsNumber;
127+
confirmDone: boolean;
128+
type: string;
129+
}
130+
131+
export interface CreateProposeSolutionPayload {
132+
submitterId: number;
133+
crId: number;
134+
description: string;
135+
scopeImpact: string;
136+
timelineImpact: number;
137+
budgetImpact: number;
138+
}
139+
106140
/**
107141
* Custom React Hook to create an activation change request.
108142
*/
109143
export const useCreateActivationChangeRequest = () => {
110-
return useMutation<{ message: string }, Error, any>(['change requests', 'create', 'activation'], async (payload: any) => {
111-
const { data } = await createActivationChangeRequest(
112-
payload.submitterId,
113-
payload.wbsNum,
114-
payload.projectLeadId,
115-
payload.projectManagerId,
116-
payload.startDate,
117-
payload.confirmDetails
118-
);
119-
return data;
120-
});
144+
return useMutation<{ message: string }, Error, CreateActivationChangeRequestPayload>(
145+
['change requests', 'create', 'activation'],
146+
async (payload: CreateActivationChangeRequestPayload) => {
147+
const { data } = await createActivationChangeRequest(
148+
payload.submitterId,
149+
payload.wbsNum,
150+
payload.projectLeadId,
151+
payload.projectManagerId,
152+
payload.startDate,
153+
payload.confirmDetails
154+
);
155+
return data;
156+
}
157+
);
121158
};
122159

123160
/**
124161
* Custom React Hook to create a stage gate change request.
125162
*/
126163
export const useCreateStageGateChangeRequest = () => {
127-
return useMutation<{ message: string }, Error, any>(['change requests', 'create', 'stage gate'], async (payload: any) => {
128-
const { data } = await createStageGateChangeRequest(payload.submitterId, payload.wbsNum, payload.confirmDone);
129-
return data;
130-
});
164+
return useMutation<{ message: string }, Error, CreateStageGateChangeRequestPayload>(
165+
['change requests', 'create', 'stage gate'],
166+
async (payload: CreateStageGateChangeRequestPayload) => {
167+
const { data } = await createStageGateChangeRequest(payload.submitterId, payload.wbsNum, payload.confirmDone);
168+
return data;
169+
}
170+
);
131171
};
132172

133173
/**
134174
* Custom React Hook to create a proposed solution
135175
*/
136176
export const useCreateProposeSolution = () => {
137177
const queryClient = useQueryClient();
138-
return useMutation<{ message: string }, Error, any>(
178+
return useMutation<{ message: string }, Error, CreateProposeSolutionPayload>(
139179
['change requests', 'create', 'propose solution'],
140-
async (payload: any) => {
180+
async (payload: CreateProposeSolutionPayload) => {
141181
const { data } = await addProposedSolution(
142182
payload.submitterId,
143183
payload.crId,
@@ -156,14 +196,18 @@ export const useCreateProposeSolution = () => {
156196
);
157197
};
158198

199+
export interface CRReviewPayload {
200+
userIds: number[];
201+
}
202+
159203
/**
160204
* Custom React hook to request cr reviewers
161205
*/
162206
export const useRequestCRReview = (crId: string) => {
163207
const queryClient = useQueryClient();
164-
return useMutation<{ message: string }, Error, any>(
208+
return useMutation<{ message: string }, Error, CRReviewPayload>(
165209
['change requests', 'review'],
166-
async (crReviewPayload: { userIds: number[] }) => {
210+
async (crReviewPayload: CRReviewPayload) => {
167211
const { data } = await requestCRReview(crId, crReviewPayload);
168212
return data;
169213
},

src/frontend/src/pages/CalendarPage/CalendarComponents/CalendarDayCard.tsx

Lines changed: 87 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Box, Card, CardContent, Grid, IconButton, Stack, Typography } from '@mui/material';
1+
import { Box, Card, CardContent, Grid, IconButton, Link, Stack, Tooltip, Typography } from '@mui/material';
22
import AddCircleOutlineIcon from '@mui/icons-material/AddCircleOutline';
33
import { DesignReview } from 'shared';
44
import { meetingStartTimePipe } from '../../../utils/pipes';
@@ -8,15 +8,16 @@ import ElectricalServicesIcon from '@mui/icons-material/ElectricalServices';
88
import TerminalIcon from '@mui/icons-material/Terminal';
99
import { useState } from 'react';
1010
import DRCSummaryModal from '../DesignReviewSummaryModal';
11+
import DynamicTooltip from '../../../components/DynamicTooltip';
1112

12-
export const getTeamTypeIcon = (teamTypeId: string, isLarge?: boolean) => {
13+
export const getTeamTypeIcon = (teamTypeName: string, isLarge?: boolean) => {
1314
const teamIcons: Map<string, JSX.Element> = new Map([
1415
['Software', <TerminalIcon fontSize={isLarge ? 'large' : 'small'} />],
1516
['Business', <WorkOutlineIcon fontSize={isLarge ? 'large' : 'small'} />],
1617
['Electrical', <ElectricalServicesIcon fontSize={isLarge ? 'large' : 'small'} />],
1718
['Mechanical', <ConstructionIcon fontSize={isLarge ? 'large' : 'small'} />]
1819
]);
19-
return teamIcons.get(teamTypeId);
20+
return teamIcons.get(teamTypeName);
2021
};
2122

2223
interface CalendarDayCardProps {
@@ -40,7 +41,7 @@ const CalendarDayCard: React.FC<CalendarDayCardProps> = ({ cardDate, events }) =
4041
</Grid>
4142
);
4243

43-
const EventCard = (event: DesignReview) => {
44+
const EventCard = ({ event }: { event: DesignReview }) => {
4445
const [isSummaryModalOpen, setIsSummaryModalOpen] = useState(false);
4546
const name = event.wbsName;
4647
return (
@@ -49,24 +50,94 @@ const CalendarDayCard: React.FC<CalendarDayCardProps> = ({ cardDate, events }) =
4950
<Box marginLeft={0.5} marginBottom={0.5} onClick={() => setIsSummaryModalOpen(true)} sx={{ cursor: 'pointer' }}>
5051
<Card sx={{ backgroundColor: 'red', borderRadius: 1, minWidth: 140, maxWidth: 140, minHeight: 20, maxHeight: 20 }}>
5152
<Stack direction="row">
52-
{getTeamTypeIcon(event.teamType.teamTypeId)}
53-
<Typography marginLeft={0.5} marginBottom={0.3} fontSize={14}>
54-
{name + ' ' + meetingStartTimePipe(event.meetingTimes)}
55-
</Typography>
53+
{getTeamTypeIcon(event.teamType.name)}
54+
<DynamicTooltip
55+
title={name + (event.meetingTimes.length > 0 ? ' - ' + meetingStartTimePipe(event.meetingTimes) : '')}
56+
>
57+
<Typography marginLeft={0.5} marginBottom={0.3} fontSize={14} noWrap>
58+
{name + (event.meetingTimes.length > 0 ? ' ' + meetingStartTimePipe(event.meetingTimes) : '')}
59+
</Typography>
60+
</DynamicTooltip>
5661
</Stack>
5762
</Card>
5863
</Box>
5964
</>
6065
);
6166
};
6267

63-
const ExtraEventsCard = (extraEvents: number) => {
68+
const ExtraEventNote = ({ event }: { event: DesignReview }) => {
69+
const [isSummaryModalOpen, setIsSummaryModalOpen] = useState(false);
70+
71+
return (
72+
<>
73+
<DRCSummaryModal open={isSummaryModalOpen} onHide={() => setIsSummaryModalOpen(false)} designReview={event} />
74+
<Link
75+
style={{ cursor: 'pointer' }}
76+
fontSize={15}
77+
onClick={() => {
78+
setIsSummaryModalOpen(true);
79+
}}
80+
>
81+
{event.wbsName + (event.meetingTimes.length > 0 ? ' - ' + meetingStartTimePipe(event.meetingTimes) : '')}
82+
</Link>
83+
</>
84+
);
85+
};
86+
87+
const ExtraEventsCard = ({ extraEvents }: { extraEvents: DesignReview[] }) => {
88+
const [showTooltip, setShowTooltip] = useState(false);
6489
return (
6590
<Box marginLeft={0.5} marginBottom={0.2}>
66-
<Card sx={{ backgroundColor: 'grey', borderRadius: 1, minWidth: 140, maxWidth: 140, minHeight: 20, maxHeight: 20 }}>
67-
<Typography marginLeft={0.5} marginBottom={0.3} align="center">
68-
{'+' + extraEvents}
69-
</Typography>
91+
<Card
92+
sx={{
93+
backgroundColor: 'grey',
94+
borderRadius: 1,
95+
minWidth: 140,
96+
maxWidth: 140,
97+
minHeight: 20,
98+
maxHeight: 20
99+
}}
100+
>
101+
<Tooltip
102+
id="tooltip"
103+
open={showTooltip}
104+
disableHoverListener
105+
onClick={() => setShowTooltip(!showTooltip)}
106+
placement="right"
107+
sx={{ cursor: 'pointer' }}
108+
PopperProps={{
109+
popperOptions: {
110+
modifiers: [
111+
{
112+
name: 'flip',
113+
options: {
114+
fallbackPlacements: ['top', 'bottom'],
115+
padding: -1,
116+
rootBoundary: 'document'
117+
}
118+
},
119+
{
120+
name: 'offset',
121+
options: {
122+
offset: [0, -1]
123+
}
124+
}
125+
]
126+
}
127+
}}
128+
arrow
129+
title={
130+
<Stack direction="column">
131+
{extraEvents.map((event) => (
132+
<ExtraEventNote event={event} />
133+
))}
134+
</Stack>
135+
}
136+
>
137+
<Typography marginLeft={0.5} marginBottom={0.3} align="center">
138+
{'+' + extraEvents.length}
139+
</Typography>
140+
</Tooltip>
70141
</Card>
71142
</Box>
72143
);
@@ -77,11 +148,11 @@ const CalendarDayCard: React.FC<CalendarDayCardProps> = ({ cardDate, events }) =
77148
<CardContent sx={{ padding: 0 }}>
78149
<DayCardTitle />
79150
{events.length < 3 ? (
80-
events.map((event) => EventCard(event))
151+
events.map((event) => <EventCard event={event} />)
81152
) : (
82153
<>
83-
{EventCard(events[1])}
84-
{ExtraEventsCard(events.length - 1)}
154+
<EventCard event={events[0]} />
155+
<ExtraEventsCard extraEvents={events.slice(1)} />
85156
</>
86157
)}
87158
</CardContent>

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

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,43 @@ import { Box, Grid, Stack, Typography, useTheme } from '@mui/material';
77
import PageLayout from '../../components/PageLayout';
88
import { DesignReview } from 'shared';
99
import MonthSelector from './CalendarComponents/MonthSelector';
10-
import CalendarDayCard from './CalendarComponents/CalendarDayCard';
10+
import CalendarDayCard, { getTeamTypeIcon } from './CalendarComponents/CalendarDayCard';
1111
import FillerCalendarDayCard from './CalendarComponents/FillerCalendarDayCard';
12-
import {
13-
DAY_NAMES,
14-
EnumToArray,
15-
calendarPaddingDays,
16-
daysInMonth,
17-
exampleDesignReview1,
18-
testDesignReview1
19-
} from '../../utils/design-review.utils';
12+
import { DAY_NAMES, EnumToArray, calendarPaddingDays, daysInMonth, isConfirmed } from '../../utils/design-review.utils';
2013
import ActionsMenu from '../../components/ActionsMenu';
14+
import { useAllDesignReviews } from '../../hooks/design-reviews.hooks';
15+
import LoadingIndicator from '../../components/LoadingIndicator';
16+
import ErrorPage from '../ErrorPage';
17+
import { useCurrentUser } from '../../hooks/users.hooks';
18+
import { datePipe } from '../../utils/pipes';
2119

2220
const CalendarPage = () => {
2321
const theme = useTheme();
24-
2522
const [displayMonthYear, setDisplayMonthYear] = useState<Date>(new Date());
23+
const { isLoading, isError, error, data: allDesignReviews } = useAllDesignReviews();
24+
const user = useCurrentUser();
25+
26+
if (isLoading || !allDesignReviews) return <LoadingIndicator />;
27+
if (isError) return <ErrorPage message={error.message} />;
28+
29+
const confirmedDesignReviews = allDesignReviews.filter(isConfirmed);
2630

27-
const EventDict = new Map<Number, DesignReview[]>();
28-
// TODO remove during wire up ticket
29-
EventDict.set(new Date().getDate(), [exampleDesignReview1]);
30-
EventDict.set(new Date().getDate() + 3, [testDesignReview1, testDesignReview1]);
31-
EventDict.set(new Date().getDate() + 4, [testDesignReview1, testDesignReview1, testDesignReview1]);
32-
const designReviewData: DesignReview[] = [testDesignReview1, testDesignReview1];
31+
const eventDict = new Map<string, DesignReview[]>();
32+
confirmedDesignReviews.forEach((designReview) => {
33+
// Accessing the date actually converts it to local time, which causes the date to be off. This is a workaround.
34+
const date = datePipe(
35+
new Date(designReview.dateScheduled.getTime() - designReview.dateScheduled.getTimezoneOffset() * -60000)
36+
);
37+
if (eventDict.has(date)) {
38+
eventDict.get(date)?.push(designReview);
39+
} else {
40+
eventDict.set(date, [designReview]);
41+
}
42+
});
43+
44+
const unconfirmedDesignReviews = allDesignReviews.filter(
45+
(designReview) => designReview.userCreated.userId === user.userId && !isConfirmed(designReview)
46+
);
3347

3448
const startOfEachWeek = [0, 7, 14, 21, 28, 35];
3549

@@ -40,6 +54,7 @@ const CalendarPage = () => {
4054
const designReviewButtons = (designReviews: DesignReview[]) => {
4155
return designReviews.map((designReview) => {
4256
return {
57+
icon: getTeamTypeIcon(designReview.teamType.name),
4358
title: designReview.wbsName,
4459
onClick: () => {},
4560
disabled: false
@@ -62,7 +77,7 @@ const CalendarPage = () => {
6277
.concat(paddingArrayEnd.length < 7 ? paddingArrayEnd : []);
6378

6479
const unconfirmedDRSDropdown = (
65-
<ActionsMenu title="My Unconfirmed DRS" buttons={designReviewButtons(designReviewData)}>
80+
<ActionsMenu title="My Unconfirmed DRS" buttons={designReviewButtons(unconfirmedDesignReviews)}>
6681
My Unconfirmed DRs
6782
</ActionsMenu>
6883
);
@@ -98,7 +113,13 @@ const CalendarPage = () => {
98113
{isDayInDifferentMonth(day, week) ? (
99114
<FillerCalendarDayCard day={day} />
100115
) : (
101-
<CalendarDayCard cardDate={cardDate} events={EventDict.get(cardDate.getDate()) ?? []} />
116+
<CalendarDayCard
117+
cardDate={cardDate}
118+
events={
119+
eventDict.get(datePipe(new Date(cardDate.getTime() - cardDate.getTimezoneOffset() * -60000))) ??
120+
[]
121+
}
122+
/>
102123
)}
103124
</Box>
104125
</Grid>

0 commit comments

Comments
 (0)