diff --git a/src/frontend/src/hooks/work-packages.hooks.ts b/src/frontend/src/hooks/work-packages.hooks.ts index acf0fa7d92..959fcd0ddb 100644 --- a/src/frontend/src/hooks/work-packages.hooks.ts +++ b/src/frontend/src/hooks/work-packages.hooks.ts @@ -36,7 +36,17 @@ export const useAllWorkPackages = (queryParams?: { [field: string]: string }) => export const useAllWorkPackagesPreview = (status?: string) => { return useQuery(['work packages', 'preview', status], async () => { const { data } = await getAllWorkPackagesPreview(status); - return data; + + const seen = new Set(); + + const uniqueData = data.filter((item: WorkPackagePreview) => { + const key = `${item.wbsNum.carNumber}-${item.wbsNum.projectNumber}-${item.wbsNum.workPackageNumber}`; + const isDuplicate = seen.has(key); + seen.add(key); + return !isDuplicate; + }); + + return uniqueData; }); }; diff --git a/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/BlockedTaskBarView.tsx b/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/BlockedTaskBarView.tsx new file mode 100644 index 0000000000..38ff3829fc --- /dev/null +++ b/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/BlockedTaskBarView.tsx @@ -0,0 +1,103 @@ +import { + GanttTask, + HighlightTaskComparator, + OnMouseOverOptions, + RequestEventChange +} from '../../../../../utils/gantt.utils'; +import { wbsPipe, WbsNumber } from 'shared'; +import GanttTaskBarDisplay from './GanttTaskBarDisplay'; + +interface BlockedGanttTaskViewProps { + parentTask: GanttTask; + task: GanttTask; + days: Date[]; + getStartCol: (start: Date) => number; + getEndCol: (end: Date) => number; + handleOnMouseOver: (e: React.MouseEvent, task: OnMouseOverOptions) => void; + handleOnMouseLeave: () => void; + onShowChildrenToggle: () => void; + highlightedChange?: RequestEventChange; + highlightTaskComparator: HighlightTaskComparator; + highlightSubtaskComparator: HighlightTaskComparator; +} + +interface TaskWithBlockingInfo { + blockedBy: WbsNumber[]; + wbsNum: WbsNumber; +} + +const hasBlockingInfo = (value: unknown): value is TaskWithBlockingInfo => { + return ( + typeof value === 'object' && + value !== null && + 'blockedBy' in value && + 'wbsNum' in value && + Array.isArray((value as { blockedBy: unknown }).blockedBy) + ); +}; + +const shouldRenderUnderParent = (parentTask: GanttTask, task: GanttTask): boolean => { + if (!hasBlockingInfo(task.element) || !hasBlockingInfo(parentTask.element)) { + return true; + } + + const parentWbs = wbsPipe(parentTask.element.wbsNum); + const [canonicalBlockedByParent] = task.element.blockedBy.map(wbsPipe).sort(); + return canonicalBlockedByParent === parentWbs; +}; + +const BlockedGanttTaskView = ({ + parentTask, + task, + days, + getStartCol, + getEndCol, + handleOnMouseOver, + handleOnMouseLeave, + onShowChildrenToggle, + highlightedChange, + highlightSubtaskComparator, + highlightTaskComparator +}: BlockedGanttTaskViewProps) => { + if (!shouldRenderUnderParent(parentTask, task)) { + return null; + } + + return ( + <> + + {task.blocking.map((child) => { + return ( + + ); + })} + + ); +}; + +export default BlockedGanttTaskView; diff --git a/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/GanttTaskBarView.tsx b/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/GanttTaskBarView.tsx index 05cba3506e..21e9260270 100644 --- a/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/GanttTaskBarView.tsx +++ b/src/frontend/src/pages/GanttPage/GanttChart/GanttChartComponents/GanttTaskBar/GanttTaskBarView.tsx @@ -7,6 +7,7 @@ import { import { Collapse } from '@mui/material'; import GanttTaskBar from './GanttTaskBar'; import GanttTaskBarDisplay from './GanttTaskBarDisplay'; +import BlockedGanttTaskView from './BlockedTaskBarView'; import { useState } from 'react'; interface GanttTaskBarViewProps { @@ -76,6 +77,24 @@ const GanttTaskBarView = ({ /> ))} + {task.blocking.map((blocking) => { + return ( + + ); + })} ); }; diff --git a/src/frontend/src/pages/GanttPage/GanttChart/GanttChartSection.tsx b/src/frontend/src/pages/GanttPage/GanttChart/GanttChartSection.tsx index 02d8b45e1d..9e8022b032 100644 --- a/src/frontend/src/pages/GanttPage/GanttChart/GanttChartSection.tsx +++ b/src/frontend/src/pages/GanttPage/GanttChart/GanttChartSection.tsx @@ -95,7 +95,7 @@ const GanttChartSection = ({ ); return tasks.length > 0 ? ( - + {tasks.map((task) => { diff --git a/src/frontend/src/utils/gantt.utils.tsx b/src/frontend/src/utils/gantt.utils.tsx index 9241d0cabc..b949a1ebfb 100644 --- a/src/frontend/src/utils/gantt.utils.tsx +++ b/src/frontend/src/utils/gantt.utils.tsx @@ -411,7 +411,7 @@ const getBlockingGanttTasks = ( export const transformTaskToGanttTask = (task: T, end: Date): GanttTask => { return { - id: task.taskId, + id: `task-${task.taskId}`, element: task, name: task.title, @@ -441,7 +441,7 @@ export const transformWorkPackageToGanttTask = ( allWorkPackages: T[] ): GanttTask => { return { - id: workPackage.id, + id: `work-package-${workPackage.id}`, element: workPackage, name: workPackage.name, @@ -477,7 +477,7 @@ export const transformProjectToGanttTask = ( const taskList = hideTasks ? [] : project.tasks; return { - id: project.id, + id: `project-${project.id}`, element: project, name: project.name, @@ -485,7 +485,9 @@ export const transformProjectToGanttTask = ( end: endDate, blocking: [], children: [ - ...project.workPackages.map((workPackage) => transformWorkPackageToGanttTask(workPackage, project.workPackages)), + ...project.workPackages + .filter((wp) => wp.blockedBy.length === 0) + .map((workPackage) => transformWorkPackageToGanttTask(workPackage, project.workPackages)), ...taskList.map((task) => transformTaskToGanttTask(task, endDate)) ], overlays: [