diff --git a/src/backend/src/prisma-query-args/projects.query-args.ts b/src/backend/src/prisma-query-args/projects.query-args.ts index 14721b03f4..74964cf2e9 100644 --- a/src/backend/src/prisma-query-args/projects.query-args.ts +++ b/src/backend/src/prisma-query-args/projects.query-args.ts @@ -5,6 +5,7 @@ import { getDescriptionBulletQueryArgs } from './description-bullets.query-args' import { getTeamQueryArgs } from './teams.query-args'; import { getMaterialQueryArgs, getAssemblyQueryArgs } from './bom.query-args'; import { getTaskQueryArgs } from './tasks.query-args'; +import { getDesignReviewQueryArgs } from './design-reviews.query-args'; export type ProjectQueryArgs = ReturnType; @@ -58,7 +59,8 @@ export const getProjectQueryArgs = (organizationId: string) => where: { dateDeleted: null }, ...getAssemblyQueryArgs(organizationId) }, - blocking: { where: { wbsElement: { dateDeleted: null } }, include: { wbsElement: true } } + blocking: { where: { wbsElement: { dateDeleted: null } }, include: { wbsElement: true } }, + designReviews: { where: { dateDeleted: null }, ...getDesignReviewQueryArgs(organizationId) } } }, blockedBy: { where: { dateDeleted: null } } diff --git a/src/backend/src/prisma-query-args/work-packages.query-args.ts b/src/backend/src/prisma-query-args/work-packages.query-args.ts index f1027c4d17..858af38368 100644 --- a/src/backend/src/prisma-query-args/work-packages.query-args.ts +++ b/src/backend/src/prisma-query-args/work-packages.query-args.ts @@ -2,6 +2,7 @@ import { Prisma } from '@prisma/client'; import { getUserQueryArgs } from './user.query-args'; import { getTaskQueryArgs } from './tasks.query-args'; import { getDescriptionBulletQueryArgs } from './description-bullets.query-args'; +import { getDesignReviewQueryArgs } from './design-reviews.query-args'; export type WorkPackageQueryArgs = ReturnType; @@ -29,7 +30,8 @@ export const getWorkPackageQueryArgs = (organizationId: string) => }, blocking: { where: { wbsElement: { dateDeleted: null } }, include: { wbsElement: true } }, tasks: { where: { dateDeleted: null }, ...getTaskQueryArgs(organizationId) }, - descriptionBullets: { where: { dateDeleted: null }, ...getDescriptionBulletQueryArgs(organizationId) } + descriptionBullets: { where: { dateDeleted: null }, ...getDescriptionBulletQueryArgs(organizationId) }, + designReviews: { where: { dateDeleted: null }, ...getDesignReviewQueryArgs(organizationId) } } }, blockedBy: { where: { dateDeleted: null } } diff --git a/src/backend/src/services/notifications.services.ts b/src/backend/src/services/notifications.services.ts index cc8eb9c8e5..1e24911a26 100644 --- a/src/backend/src/services/notifications.services.ts +++ b/src/backend/src/services/notifications.services.ts @@ -41,7 +41,8 @@ export default class NotificationsService { }, status: { not: 'DONE' - } + }, + dateDeleted: null }, include: { assignees: { diff --git a/src/backend/src/transformers/projects.transformer.ts b/src/backend/src/transformers/projects.transformer.ts index f73a1c687e..e5b079277d 100644 --- a/src/backend/src/transformers/projects.transformer.ts +++ b/src/backend/src/transformers/projects.transformer.ts @@ -17,6 +17,7 @@ import { assemblyTransformer, materialTransformer } from './material.transformer import { userTransformer } from './user.transformer'; import { ProjectQueryArgs } from '../prisma-query-args/projects.query-args'; import teamTransformer from './teams.transformer'; +import { designReviewTransformer } from './design-reviews.transformer'; const projectTransformer = (project: Prisma.ProjectGetPayload): Project => { const { wbsElement } = project; @@ -87,6 +88,7 @@ const projectTransformer = (project: Prisma.ProjectGetPayload) materials: workPackage.wbsElement?.materials.map(materialTransformer), assemblies: workPackage.wbsElement?.assemblies.map(assemblyTransformer), blocking: workPackage.wbsElement.blocking.map((blocking) => wbsNumOf(blocking.wbsElement)), + designReviews: workPackage.wbsElement.designReviews.map(designReviewTransformer), deleted: workPackage.wbsElement.dateDeleted !== null }; }) diff --git a/src/backend/src/transformers/work-packages.transformer.ts b/src/backend/src/transformers/work-packages.transformer.ts index 9876a14076..83a653c8e1 100644 --- a/src/backend/src/transformers/work-packages.transformer.ts +++ b/src/backend/src/transformers/work-packages.transformer.ts @@ -4,6 +4,7 @@ import descriptionBulletTransformer from '../transformers/description-bullets.tr import { convertStatus, wbsNumOf } from '../utils/utils'; import { userTransformer } from './user.transformer'; import { WorkPackageQueryArgs } from '../prisma-query-args/work-packages.query-args'; +import { designReviewTransformer } from './design-reviews.transformer'; const workPackageTransformer = (wpInput: Prisma.Work_PackageGetPayload): WorkPackage => { const wbsNum = wbsNumOf(wpInput.wbsElement); @@ -37,6 +38,7 @@ const workPackageTransformer = (wpInput: Prisma.Work_PackageGetPayload wbsNumOf(wp.wbsElement)), + designReviews: wpInput.wbsElement.designReviews.map(designReviewTransformer), deleted: wpInput.wbsElement.dateDeleted !== null }; }; diff --git a/src/frontend/src/apis/transformers/work-packages.transformers.ts b/src/frontend/src/apis/transformers/work-packages.transformers.ts index fa8814e4f1..d8fddf5057 100644 --- a/src/frontend/src/apis/transformers/work-packages.transformers.ts +++ b/src/frontend/src/apis/transformers/work-packages.transformers.ts @@ -5,6 +5,7 @@ import { WorkPackage } from 'shared'; import { implementedChangeTransformer } from './change-requests.transformers'; +import { designReviewTransformer } from './design-reviews.tranformers'; import { descriptionBulletTransformer } from './projects.transformers'; /** @@ -20,6 +21,7 @@ export const workPackageTransformer = (workPackage: WorkPackage): WorkPackage => startDate: new Date(workPackage.startDate), endDate: new Date(workPackage.endDate), descriptionBullets: workPackage.descriptionBullets.map(descriptionBulletTransformer), - changes: workPackage.changes.map(implementedChangeTransformer) + changes: workPackage.changes.map(implementedChangeTransformer), + designReviews: workPackage.designReviews.map(designReviewTransformer) }; }; diff --git a/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttChartColorLegend.tsx b/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttChartColorLegend.tsx index 56d4985222..5f89c86327 100644 --- a/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttChartColorLegend.tsx +++ b/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttChartColorLegend.tsx @@ -4,9 +4,13 @@ */ import { Box, Card, Tooltip, Typography } from '@mui/material'; -import { WbsElementStatus, WorkPackageStage } from 'shared'; -import { GanttWorkPackageStageColorPipe, GanttWorkPackageTextColorPipe } from '../../../utils/gantt.utils'; -import { WbsElementStatusTextPipe, WorkPackageStageTextPipe } from '../../../utils/enum-pipes'; +import { DesignReviewStatus, WbsElementStatus, WorkPackageStage } from 'shared'; +import { + GanttDesignReviewStatusColorPipe, + GanttWorkPackageStageColorPipe, + GanttWorkPackageTextColorPipe +} from '../../../utils/gantt.utils'; +import { DesignReviewStatusTextPipe, WbsElementStatusTextPipe, WorkPackageStageTextPipe } from '../../../utils/enum-pipes'; const LEGEND_POPUPS_MAP = new Map(); @@ -48,6 +52,43 @@ Object.values(WorkPackageStage).map((stage) => ) ); +const DesignReviewToolTipPopUp = () => { + return ( + + { + // map through all the Wbs Element Statuses + [DesignReviewStatus.UNCONFIRMED, DesignReviewStatus.SCHEDULED].map((status) => { + return ( + + + {DesignReviewStatusTextPipe(status)} + + + ); + }) + } + + ); +}; + const GanttChartColorLegend = () => { return ( { ); }) } + + } + slotProps={{ + tooltip: { sx: { background: 'transparent', width: 'fit-content' } } + }} + > + + Design Review + + + ); }; diff --git a/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttTaskBar/GanttTaskBarDisplay.tsx b/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttTaskBar/GanttTaskBarDisplay.tsx index 290a3bb81c..e4151273b3 100644 --- a/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttTaskBar/GanttTaskBarDisplay.tsx +++ b/src/frontend/src/pages/GanttPage/GanttChartComponents/GanttTaskBar/GanttTaskBarDisplay.tsx @@ -3,13 +3,15 @@ import { grey } from '@mui/material/colors'; import { ArrowDropDownIcon } from '@mui/x-date-pickers'; import { useHistory } from 'react-router-dom'; import { + GanttDesignReviewStatusColorPipe, GanttTask, isHighlightedChangeOnGanttTask, RequestEventChange, + transformDesignReviewToGanttTask, transformWorkPackageToGanttTask } from '../../../../utils/gantt.utils'; import { routes } from '../../../../utils/routes'; -import { wbsPipe } from 'shared'; +import { addWeeksToDate, DesignReview, wbsPipe } from 'shared'; import { ganttTaskBarBackgroundStyles, ganttTaskBarContainerStyles, @@ -19,6 +21,7 @@ import { } from './GanttTaskBarDisplayStyles'; import { CSSProperties } from 'react'; import { ArcherElement } from 'react-archer'; +import { datePipe } from '../../../../utils/pipes'; interface GanttTaskBarDisplayProps { days: Date[]; @@ -87,6 +90,20 @@ const GanttTaskBarDisplay = ({ }; }; + const ganttTaskBarDesignReviewOverlayStyles = (designReview: DesignReview): CSSProperties => { + return { + gridColumnStart: getStartCol(designReview.dateScheduled), + gridColumnEnd: getEndCol(addWeeksToDate(designReview.dateScheduled, 1)), + height: '2rem', + border: `1px solid ${theme.palette.divider}`, + borderRadius: '0.25rem', + backgroundColor: GanttDesignReviewStatusColorPipe(designReview.status), + cursor: 'pointer', + gridRow: 1, + zIndex: 2 + }; + }; + const highlightedChangeBoxStyles = (highlightedChange: RequestEventChange): CSSProperties => { return { paddingTop: '2px', @@ -159,6 +176,24 @@ const GanttTaskBarDisplay = ({ /> ); })} + {task.designReviews.map((designReview) => { + return ( +
handleOnMouseOver(e, transformDesignReviewToGanttTask(designReview))} + onMouseLeave={handleOnMouseLeave} + onClick={() => history.push(`${routes.CALENDAR}/${designReview.designReviewId}`)} + > + history.push(`${routes.CALENDAR}/${designReview.designReviewId}`)} + > + {datePipe(designReview.dateScheduled, false)} + +
+ ); + })} {highlightedChange && isHighlightedChangeOnGanttTask(highlightedChange, task) && (
string = (stage) => { @@ -79,3 +79,16 @@ export const WbsElementStatusTextPipe: (status: WbsElementStatus) => string = (s return 'Complete'; } }; + +export const DesignReviewStatusTextPipe: (status: DesignReviewStatus) => string = (status) => { + switch (status) { + case DesignReviewStatus.UNCONFIRMED: + return 'Unconfirmed'; + case DesignReviewStatus.CONFIRMED: + return 'Confirmed'; + case DesignReviewStatus.DONE: + return 'Done'; + case DesignReviewStatus.SCHEDULED: + return 'Scheduled'; + } +}; diff --git a/src/frontend/src/utils/gantt.utils.ts b/src/frontend/src/utils/gantt.utils.ts index eae5ce2ca5..bdda2e7438 100644 --- a/src/frontend/src/utils/gantt.utils.ts +++ b/src/frontend/src/utils/gantt.utils.ts @@ -4,6 +4,8 @@ */ import { + DesignReview, + DesignReviewStatus, isProject, isWorkPackage, Project, @@ -38,6 +40,7 @@ export interface GanttTaskData { allWorkPackages: WorkPackage[]; unblockedWorkPackages: WorkPackage[]; blocking: WbsNumber[]; + designReviews: DesignReview[]; // Optional Values styles?: { @@ -85,6 +88,29 @@ export const getProjectEndDate = (project: ProjectPreview): Date => { }, new Date(0)); }; +export const transformDesignReviewToGanttTask = (designReview: DesignReview): GanttTask => { + return { + id: designReview.designReviewId, + name: designReview.wbsName + ' - Design Review', + start: designReview.dateScheduled, + end: designReview.dateScheduled, + projectNumber: 0, + carNumber: 0, + workPackageNumber: 0, + allWorkPackages: [], + lead: designReview.userCreated, + manager: designReview.userCreated, + teamName: NO_TEAM, + stage: WorkPackageStage.Design, + unblockedWorkPackages: [], + blocking: [], + onClick: () => { + window.open(`/design-reviews-calendar/${designReview.designReviewId}`, '_blank'); + }, + designReviews: [] + }; +}; + export const transformProjectPreviewToProject = (projectPreview: ProjectPreview, team: Team): Project => { return { ...projectPreview, @@ -132,7 +158,8 @@ export const transformGanttTaskToWorkPackage = (task: GanttTask): WorkPackage => blocking: task.blocking, deleted: false, projectName: '', - duration: dayjs(task.end).diff(dayjs(task.start), 'week') + duration: dayjs(task.end).diff(dayjs(task.start), 'week'), + designReviews: task.designReviews }; }; @@ -340,7 +367,8 @@ export const transformWorkPackageToGanttTask = ( window.open(`/projects/${wbsPipe(workPackage.wbsNum)}`, '_blank'); }, lead: workPackage.lead, - manager: workPackage.manager + manager: workPackage.manager, + designReviews: workPackage.designReviews }; }; @@ -365,7 +393,8 @@ export const transformProjectToGanttTask = (project: ProjectPreview, teamName: s blocking: [], onClick: () => { window.open(`/projects/${wbsPipe(project.wbsNum)}`, '_blank'); - } + }, + designReviews: project.workPackages.flatMap((wp) => wp.designReviews) }; }; @@ -401,6 +430,10 @@ export const sortTeamList = (a: Team, b: Team, ganttFilters: GanttFilters, searc return a.teamName.localeCompare(b.teamName); }; +export const GanttDesignReviewStatusColorPipe = (status: DesignReviewStatus) => { + return status !== DesignReviewStatus.UNCONFIRMED ? '#712f99' : '#876e96'; +}; + // maps stage and status to the desired color for Gantt Chart export const GanttWorkPackageStageColorPipe: (stage: WorkPackageStage | undefined, status: WbsElementStatus) => string = ( stage, diff --git a/src/frontend/src/utils/pipes.ts b/src/frontend/src/utils/pipes.ts index 56f0bcefec..cb813a7362 100644 --- a/src/frontend/src/utils/pipes.ts +++ b/src/frontend/src/utils/pipes.ts @@ -69,13 +69,13 @@ export const emDashPipe = (str: string) => { * so to get around that we do the toDateString() of the time and pass it into the Date constructor * where the constructor assumes it's in UTC and makes the correct Date object finally */ -export const datePipe = (date?: Date) => { +export const datePipe = (date?: Date, includeYear = true) => { if (!date) return ''; date = typeof date == 'string' ? new Date(date) : new Date(date.toDateString()); return date.toLocaleDateString('en-US', { day: '2-digit', month: '2-digit', - year: 'numeric', + year: includeYear ? 'numeric' : undefined, timeZone: 'UTC' }); }; diff --git a/src/shared/src/types/project-types.ts b/src/shared/src/types/project-types.ts index 236d66ecb9..afc9a80929 100644 --- a/src/shared/src/types/project-types.ts +++ b/src/shared/src/types/project-types.ts @@ -7,7 +7,7 @@ import { User, UserPreview } from './user-types'; import { ImplementedChange } from './change-request-types'; import { WorkPackageStage } from './work-package-types'; import { TeamPreview } from './team-types'; -import { Assembly, Material, Task, TeamType } from 'shared'; +import { Assembly, DesignReview, Material, Task, TeamType } from 'shared'; export interface WbsNumber { carNumber: number; @@ -65,6 +65,7 @@ export interface WorkPackage extends WbsElement { projectName: string; stage?: WorkPackageStage; teamTypes: TeamType[]; + designReviews: DesignReview[]; } export interface DescriptionBullet {