Skip to content
This repository has been archived by the owner on Sep 12, 2023. It is now read-only.

Commit

Permalink
Merge pull request #742 from Northeastern-Electric-Racing/#729-home-a…
Browse files Browse the repository at this point in the history
…djustments

#729 home adjustments
  • Loading branch information
jamescd18 authored Jun 19, 2022
2 parents 20dfca9 + 388228f commit 8e4c717
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 122 deletions.
50 changes: 14 additions & 36 deletions src/backend/functions/work-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const wpQueryArgs = Prisma.validator<Prisma.Work_PackageArgs>()({
include: {
projectLead: true,
projectManager: true,
changes: { include: { implementer: true } }
changes: { include: { implementer: true }, orderBy: { dateImplemented: 'asc' } }
}
},
expectedActivities: true,
Expand Down Expand Up @@ -104,18 +104,22 @@ const workPackageTransformer = (wpInput: Prisma.Work_PackageGetPayload<typeof wp
} as WorkPackage;
};

// Fetch all work packages
// Fetch all work packages, optionally filtered by query parameters
const getAllWorkPackages: ApiRouteFunction = async (_, event) => {
const { queryStringParameters: eQSP } = event;
const workPackages = await prisma.work_Package.findMany(wpQueryArgs);
return buildSuccessResponse(
workPackages.map(workPackageTransformer).filter((wp) => {
let passes = true;
if (eQSP?.status) passes &&= wp.status === eQSP?.status;
if (eQSP?.timelineStatus) passes &&= wp.timelineStatus === eQSP?.timelineStatus;
return passes;
})
);
const outputWorkPackages = workPackages.map(workPackageTransformer).filter((wp) => {
let passes = true;
if (eQSP?.status) passes &&= wp.status === eQSP?.status;
if (eQSP?.timelineStatus) passes &&= wp.timelineStatus === eQSP?.timelineStatus;
if (eQSP?.daysUntilDeadline) {
const daysToDeadline = Math.round((wp.endDate.getTime() - new Date().getTime()) / 86400000);
passes &&= daysToDeadline <= parseInt(eQSP?.daysUntilDeadline);
}
return passes;
});
outputWorkPackages.sort((wpA, wpB) => wpA.endDate.getTime() - wpB.endDate.getTime());
return buildSuccessResponse(outputWorkPackages);
};

// Fetch the work package for the specified WBS number
Expand All @@ -138,39 +142,13 @@ const getSingleWorkPackage: ApiRouteFunction = async (params: { wbsNum: string }
return buildSuccessResponse(workPackageTransformer(wp));
};

// Fetch all work packages with an end date in the next 2 weeks
const getAllWorkPackagesUpcomingDeadlines: ApiRouteFunction = async () => {
const workPackages = await prisma.work_Package.findMany({
where: {
wbsElement: {
status: WBS_Element_Status.ACTIVE
}
},
...wpQueryArgs
});
const outputWorkPackages = workPackages
.filter((wp) => {
const endDate = calculateEndDate(wp.startDate, wp.duration);
const daysFromNow = Math.round((endDate.getTime() - new Date().getTime()) / 86400000);
return daysFromNow <= 14;
})
.map(workPackageTransformer);
outputWorkPackages.sort((wpA, wpB) => wpA.endDate.getTime() - wpB.endDate.getTime());
return buildSuccessResponse(outputWorkPackages);
};

// Define all valid routes for the endpoint
const routes: ApiRoute[] = [
{
path: `${API_URL}${apiRoutes.WORK_PACKAGES}`,
httpMethod: 'GET',
func: getAllWorkPackages
},
{
path: `${API_URL}${apiRoutes.WORK_PACKAGES_UPCOMING_DEADLINES}`,
httpMethod: 'GET',
func: getAllWorkPackagesUpcomingDeadlines
},
{
path: `${API_URL}${apiRoutes.WORK_PACKAGES_BY_WBS}`,
httpMethod: 'GET',
Expand Down
10 changes: 6 additions & 4 deletions src/frontend/layouts/nav-top-bar/nav-top-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
import { Dropdown, Nav, Navbar } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { routes } from '../../../shared/routes';
import NavUserMenu from './nav-user-menu/nav-user-menu';
import NavNotificationsMenu from './nav-notifications-menu/nav-notifications-menu';
import styles from './nav-top-bar.module.css';
import { useAuth } from '../../../services/auth.hooks';
import { fullNamePipe } from '../../../shared/pipes';
import { useTheme } from '../../../services/theme.hooks';
import themes from '../../../shared/themes';
import NavUserMenu from './nav-user-menu/nav-user-menu';
import NavNotificationsMenu from './nav-notifications-menu/nav-notifications-menu';
import styles from './nav-top-bar.module.css';

const NavTopBar: React.FC = () => {
const auth = useAuth();
Expand All @@ -39,7 +39,9 @@ const NavTopBar: React.FC = () => {
{themes
.filter((t) => t.name !== theme.name)
.map((t) => (
<Dropdown.Item onClick={() => theme.toggleTheme!(t.name)}>{t.name}</Dropdown.Item>
<Dropdown.Item key={t.name} onClick={() => theme.toggleTheme!(t.name)}>
{t.name}
</Dropdown.Item>
))}
</Dropdown.Menu>
</Dropdown>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import { UseQueryResult } from 'react-query';
import { WorkPackage } from 'utils';
import { useAllWorkPackagesUpcomingDeadlines } from '../../../../services/work-packages.hooks';
import { useAllWorkPackages } from '../../../../services/work-packages.hooks';
import { datePipe, fullNamePipe } from '../../../../shared/pipes';
import { mockUseQueryResult } from '../../../../test-support/test-data/test-utils.stub';
import { exampleAllWorkPackages } from '../../../../test-support/test-data/work-packages.stub';
Expand All @@ -14,9 +14,7 @@ import UpcomingDeadlines from './upcoming-deadlines';

jest.mock('../../../../services/work-packages.hooks');

const mockedUseAllWPs = useAllWorkPackagesUpcomingDeadlines as jest.Mock<
UseQueryResult<WorkPackage[]>
>;
const mockedUseAllWPs = useAllWorkPackages as jest.Mock<UseQueryResult<WorkPackage[]>>;

const mockHook = (isLoading: boolean, isError: boolean, data?: WorkPackage[], error?: Error) => {
mockedUseAllWPs.mockReturnValue(
Expand All @@ -40,7 +38,7 @@ describe('upcoming deadlines component', () => {
it('renders headers', () => {
mockHook(false, false, []);
renderComponent();
expect(screen.getByText('Upcoming Deadlines')).toBeInTheDocument();
expect(screen.getByText('Upcoming Deadlines (0)')).toBeInTheDocument();
});

it('renders loading indicator', () => {
Expand Down Expand Up @@ -79,9 +77,11 @@ describe('upcoming deadlines component', () => {
expect(screen.getByText('No upcoming deadlines')).toBeInTheDocument();
});

it('renders work package count', () => {
it('renders time period selector', () => {
mockHook(false, false, exampleAllWorkPackages);
renderComponent();
expect(screen.getByText('3 Work Packages')).toBeInTheDocument();
expect(screen.getByText('Next')).toBeInTheDocument();
expect(screen.getByText('14')).toBeInTheDocument();
expect(screen.getByText('Days')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
* See the LICENSE file in the repository root folder for details.
*/

import { Card, Container, Row } from 'react-bootstrap';
import { useState } from 'react';
import { Card, Container, Form, InputGroup, Row } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { WbsElementStatus } from 'utils';
import { useTheme } from '../../../../services/theme.hooks';
import { useAllWorkPackagesUpcomingDeadlines } from '../../../../services/work-packages.hooks';
import { useAllWorkPackages } from '../../../../services/work-packages.hooks';
import { datePipe, wbsPipe, fullNamePipe, percentPipe } from '../../../../shared/pipes';
import { routes } from '../../../../shared/routes';
import LoadingIndicator from '../../../components/loading-indicator/loading-indicator';
Expand All @@ -15,8 +17,9 @@ import ErrorPage from '../../ErrorPage/error-page';
import styles from './upcoming-deadlines.module.css';

const UpcomingDeadlines: React.FC = () => {
const [daysUntilDeadline, setDaysUntilDeadline] = useState<string>('14');
const theme = useTheme();
const workPackages = useAllWorkPackagesUpcomingDeadlines();
const workPackages = useAllWorkPackages({ status: WbsElementStatus.Active, daysUntilDeadline });

if (workPackages.isError) {
return <ErrorPage message={workPackages.error.message} error={workPackages.error} />;
Expand All @@ -28,6 +31,7 @@ const UpcomingDeadlines: React.FC = () => {
? 'No upcoming deadlines'
: workPackages.data?.map((wp) => (
<Card
key={wbsPipe(wp.wbsNum)}
className={styles.upcomingDeadlineCard}
border={theme.cardBorder}
bg={theme.cardBg}
Expand Down Expand Up @@ -60,8 +64,29 @@ const UpcomingDeadlines: React.FC = () => {

return (
<PageBlock
title={'Upcoming Deadlines'}
headerRight={workPackages.isLoading ? <></> : <>{workPackages.data?.length} Work Packages</>}
title={`Upcoming Deadlines (${workPackages.data?.length})`}
headerRight={
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text>Next</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
custom
as="select"
value={daysUntilDeadline}
onChange={(e) => setDaysUntilDeadline(e.target.value)}
>
{['1', '2', '5', '7', '14', '21', '30'].map((days) => (
<option key={days} value={days}>
{days}
</option>
))}
</Form.Control>
<InputGroup.Append>
<InputGroup.Text>Days</InputGroup.Text>
</InputGroup.Append>
</InputGroup>
}
body={
<Container fluid>{workPackages.isLoading ? <LoadingIndicator /> : fullDisplay}</Container>
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ describe('upcoming deadlines component', () => {
it('renders headers', () => {
mockHook(false, false, []);
renderComponent();
expect(screen.getByText('Work Packages By Timeline Status')).toBeInTheDocument();
expect(screen.getByText('Work Packages By Timeline Status (0)')).toBeInTheDocument();
});

it('renders loading indicator', () => {
Expand Down Expand Up @@ -80,7 +80,7 @@ describe('upcoming deadlines component', () => {
it('renders timeline status selector', () => {
mockHook(false, false, exampleAllWorkPackages);
renderComponent();
expect(screen.getByText('Status:')).toBeInTheDocument();
expect(screen.getByText('Timeline Status')).toBeInTheDocument();
expect(screen.getByText('VERY_BEHIND')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

import { useState, useEffect } from 'react';
import { Card, Container, Form, Row } from 'react-bootstrap';
import { Card, Container, Form, InputGroup, Row } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { TimelineStatus, WbsElementStatus } from 'utils';
import { useTheme } from '../../../../services/theme.hooks';
Expand All @@ -19,7 +19,7 @@ import styles from './work-packages-by-timeline-status.module.css';
const WorkPackagesByTimelineStatus: React.FC = () => {
const [timelineStatus, setTimelineStatus] = useState<TimelineStatus>(TimelineStatus.VeryBehind);
const theme = useTheme();
const workPackages = useAllWorkPackages(WbsElementStatus.Active, timelineStatus);
const workPackages = useAllWorkPackages({ status: WbsElementStatus.Active, timelineStatus });

useEffect(() => {
workPackages.refetch();
Expand All @@ -35,7 +35,12 @@ const WorkPackagesByTimelineStatus: React.FC = () => {
{workPackages.data?.length === 0
? `No ${timelineStatus} work packages`
: workPackages.data?.map((wp) => (
<Card className={styles.workPackageCard} border={theme.cardBorder} bg={theme.cardBg}>
<Card
key={wbsPipe(wp.wbsNum)}
className={styles.workPackageCard}
border={theme.cardBorder}
bg={theme.cardBg}
>
<Card.Body className="p-3">
<Card.Title className="mb-2">
<Link to={`${routes.PROJECTS}/${wbsPipe(wp.wbsNum)}`}>
Expand Down Expand Up @@ -64,27 +69,26 @@ const WorkPackagesByTimelineStatus: React.FC = () => {

return (
<PageBlock
title={'Work Packages By Timeline Status'}
title={`Work Packages By Timeline Status (${workPackages.data?.length})`}
headerRight={
<div className="d-flex flex-row align-items-center">
<div className="pr-2">Status:</div>
<InputGroup>
<InputGroup.Prepend>
<InputGroup.Text id="selectTimelineStatus">Timeline Status</InputGroup.Text>
</InputGroup.Prepend>
<Form.Control
as="select"
aria-describedby="selectTimelineStatus"
value={timelineStatus}
onChange={(e) => setTimelineStatus(e.target.value as TimelineStatus)}
custom
>
<option key={timelineStatus} value={timelineStatus}>
{timelineStatus}
</option>
{Object.values(TimelineStatus)
.filter((status) => status !== timelineStatus)
.map((status) => (
<option key={status} value={status}>
{status}
</option>
))}
{Object.values(TimelineStatus).map((status) => (
<option key={status} value={status}>
{status}
</option>
))}
</Form.Control>
</div>
</InputGroup>
}
body={
<Container fluid>{workPackages.isLoading ? <LoadingIndicator /> : fullDisplay}</Container>
Expand Down
1 change: 0 additions & 1 deletion src/frontend/pages/LoginPage/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const Login: React.FC<LoginProps> = ({ postLoginRedirect }) => {
if (auth.isLoading) return <LoadingIndicator />;

const redirectAfterLogin = () => {
console.log(postLoginRedirect);
if (postLoginRedirect.url === routes.LOGIN) {
history.push(routes.HOME);
} else {
Expand Down
22 changes: 3 additions & 19 deletions src/services/work-packages.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,16 @@
*/

import axios from 'axios';
import {
CreateWorkPackagePayload,
EditWorkPackagePayload,
TimelineStatus,
WbsElementStatus,
WbsNumber,
WorkPackage
} from 'utils';
import { CreateWorkPackagePayload, EditWorkPackagePayload, WbsNumber, WorkPackage } from 'utils';
import { wbsPipe } from '../shared/pipes';
import { apiUrls } from '../shared/urls';
import { workPackageTransformer } from './transformers/work-packages.transformers';

/**
* Fetch all work packages.
*/
export const getAllWorkPackages = (status?: WbsElementStatus, timelineStatus?: TimelineStatus) => {
return axios.get<WorkPackage[]>(apiUrls.workPackages(status, timelineStatus), {
export const getAllWorkPackages = (queryParams?: { [field: string]: string }) => {
return axios.get<WorkPackage[]>(apiUrls.workPackages(queryParams), {
transformResponse: (data) => JSON.parse(data).map(workPackageTransformer)
});
};
Expand Down Expand Up @@ -58,12 +51,3 @@ export const editWorkPackage = (payload: EditWorkPackagePayload) => {
...payload
});
};

/**
* Fetch all work packages with upcoming deadlines.
*/
export const getAllWorkPackagesUpcomingDeadlines = () => {
return axios.get<WorkPackage[]>(apiUrls.workPackagesUpcomingDeadlines(), {
transformResponse: (data) => JSON.parse(data).map(workPackageTransformer)
});
};
Loading

0 comments on commit 8e4c717

Please sign in to comment.