|
@@ -1,232 +1,289 @@ |
|
|
import { Task } from "@/app/api/tasks"; |
|
|
|
|
|
|
|
|
import { Task, TaskGroup } from "@/app/api/tasks"; |
|
|
import { |
|
|
import { |
|
|
Box, |
|
|
Box, |
|
|
Typography, |
|
|
Typography, |
|
|
Grid, |
|
|
|
|
|
Paper, |
|
|
|
|
|
List, |
|
|
|
|
|
ListItemButton, |
|
|
|
|
|
ListItemText, |
|
|
|
|
|
TextField, |
|
|
TextField, |
|
|
Alert, |
|
|
Alert, |
|
|
|
|
|
TableContainer, |
|
|
|
|
|
Table, |
|
|
|
|
|
TableBody, |
|
|
|
|
|
TableCell, |
|
|
|
|
|
TableHead, |
|
|
|
|
|
TableRow, |
|
|
|
|
|
Stack, |
|
|
|
|
|
SxProps, |
|
|
} from "@mui/material"; |
|
|
} from "@mui/material"; |
|
|
import { useState, useCallback, useEffect, useMemo } from "react"; |
|
|
|
|
|
|
|
|
import React, { useCallback, useMemo } from "react"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { Props as StaffAllocationProps } from "./StaffAllocation"; |
|
|
import { Props as StaffAllocationProps } from "./StaffAllocation"; |
|
|
import StyledDataGrid from "../StyledDataGrid"; |
|
|
|
|
|
import { useForm, useFormContext } from "react-hook-form"; |
|
|
|
|
|
import { GridColDef, GridRowModel, useGridApiRef } from "@mui/x-data-grid"; |
|
|
|
|
|
import { |
|
|
|
|
|
CreateProjectInputs, |
|
|
|
|
|
ManhourAllocation, |
|
|
|
|
|
} from "@/app/api/projects/actions"; |
|
|
|
|
|
import isEmpty from "lodash/isEmpty"; |
|
|
|
|
|
import _reduce from "lodash/reduce"; |
|
|
|
|
|
|
|
|
|
|
|
const mockGrades = [1, 2, 3, 4, 5]; |
|
|
|
|
|
|
|
|
import { useFormContext } from "react-hook-form"; |
|
|
|
|
|
import { CreateProjectInputs } from "@/app/api/projects/actions"; |
|
|
|
|
|
import uniqBy from "lodash/uniqBy"; |
|
|
|
|
|
import { Grade } from "@/app/api/grades"; |
|
|
|
|
|
import { manhourFormatter, percentFormatter } from "@/app/utils/formatUtil"; |
|
|
|
|
|
import TableCellEdit from "../TableCellEdit"; |
|
|
|
|
|
|
|
|
interface Props { |
|
|
interface Props { |
|
|
allTasks: Task[]; |
|
|
allTasks: Task[]; |
|
|
manhourBreakdownByGrade: StaffAllocationProps["defaultManhourBreakdownByGrade"]; |
|
|
manhourBreakdownByGrade: StaffAllocationProps["defaultManhourBreakdownByGrade"]; |
|
|
|
|
|
grades: Grade[]; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
type Row = ManhourAllocation & { id: "manhourAllocation" }; |
|
|
|
|
|
|
|
|
const leftBorderCellSx: SxProps = { |
|
|
|
|
|
borderLeft: "1px solid", |
|
|
|
|
|
borderColor: "divider", |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const parseValidManhours = (value: number | string): number => { |
|
|
|
|
|
const inputNumber = Number(value); |
|
|
|
|
|
return isNaN(inputNumber) || inputNumber < 0 ? 0 : inputNumber; |
|
|
|
|
|
|
|
|
const rightBorderCellSx: SxProps = { |
|
|
|
|
|
borderRight: "1px solid", |
|
|
|
|
|
borderColor: "divider", |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
const ResourceAllocation: React.FC<Props> = ({ |
|
|
|
|
|
allTasks, |
|
|
|
|
|
manhourBreakdownByGrade = mockGrades.reduce< |
|
|
|
|
|
NonNullable<Props["manhourBreakdownByGrade"]> |
|
|
|
|
|
>((acc, grade) => { |
|
|
|
|
|
return { ...acc, [grade]: 1 }; |
|
|
|
|
|
}, {}), |
|
|
|
|
|
}) => { |
|
|
|
|
|
|
|
|
const leftRightBorderCellSx: SxProps = { |
|
|
|
|
|
borderLeft: "1px solid", |
|
|
|
|
|
borderRight: "1px solid", |
|
|
|
|
|
borderColor: "divider", |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const ResourceAllocationByGrade: React.FC<Props> = ({ grades }) => { |
|
|
const { t } = useTranslation(); |
|
|
const { t } = useTranslation(); |
|
|
const { watch } = useFormContext<CreateProjectInputs>(); |
|
|
|
|
|
const currentTasks = watch("tasks"); |
|
|
|
|
|
const tasks = useMemo( |
|
|
|
|
|
() => allTasks.filter((task) => currentTasks[task.id]), |
|
|
|
|
|
[allTasks, currentTasks], |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
const { watch, register, setValue } = useFormContext<CreateProjectInputs>(); |
|
|
|
|
|
|
|
|
const [selectedTaskId, setSelectedTaskId] = useState(tasks[0].id); |
|
|
|
|
|
const makeOnTaskSelect = useCallback( |
|
|
|
|
|
(taskId: Task["id"]): React.MouseEventHandler => |
|
|
|
|
|
() => { |
|
|
|
|
|
return setSelectedTaskId(taskId); |
|
|
|
|
|
}, |
|
|
|
|
|
[], |
|
|
|
|
|
|
|
|
const manhourPercentageByGrade = watch("manhourPercentageByGrade"); |
|
|
|
|
|
const totalManhour = watch("totalManhour"); |
|
|
|
|
|
const totalPercentage = Object.values(manhourPercentageByGrade).reduce( |
|
|
|
|
|
(acc, percent) => acc + percent, |
|
|
|
|
|
0, |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
setSelectedTaskId(tasks[0].id); |
|
|
|
|
|
}, [tasks]); |
|
|
|
|
|
|
|
|
|
|
|
const { getValues, setValue } = useFormContext<CreateProjectInputs>(); |
|
|
|
|
|
|
|
|
|
|
|
const updateTaskAllocations = useCallback( |
|
|
|
|
|
(newAllocations: ManhourAllocation) => { |
|
|
|
|
|
setValue("tasks", { |
|
|
|
|
|
...getValues("tasks"), |
|
|
|
|
|
[selectedTaskId]: { |
|
|
|
|
|
manhourAllocation: newAllocations, |
|
|
|
|
|
}, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const makeUpdatePercentage = useCallback( |
|
|
|
|
|
(gradeId: Grade["id"]) => (percentage?: number) => { |
|
|
|
|
|
if (percentage !== undefined) { |
|
|
|
|
|
setValue("manhourPercentageByGrade", { |
|
|
|
|
|
...manhourPercentageByGrade, |
|
|
|
|
|
[gradeId]: percentage, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
[getValues, selectedTaskId, setValue], |
|
|
|
|
|
|
|
|
[manhourPercentageByGrade, setValue], |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
const gridApiRef = useGridApiRef(); |
|
|
|
|
|
const columns = useMemo(() => { |
|
|
|
|
|
return mockGrades.map<GridColDef>((grade) => ({ |
|
|
|
|
|
field: grade.toString(), |
|
|
|
|
|
editable: true, |
|
|
|
|
|
sortable: false, |
|
|
|
|
|
width: 120, |
|
|
|
|
|
headerName: t("Grade {{grade}}", { grade }), |
|
|
|
|
|
type: "number", |
|
|
|
|
|
valueParser: parseValidManhours, |
|
|
|
|
|
valueFormatter(params) { |
|
|
|
|
|
return Number(params.value).toFixed(2); |
|
|
|
|
|
}, |
|
|
|
|
|
})); |
|
|
|
|
|
}, [t]); |
|
|
|
|
|
|
|
|
|
|
|
const rows = useMemo<Row[]>(() => { |
|
|
|
|
|
const initialAllocation = |
|
|
|
|
|
getValues("tasks")[selectedTaskId]?.manhourAllocation; |
|
|
|
|
|
if (!isEmpty(initialAllocation)) { |
|
|
|
|
|
return [{ ...initialAllocation, id: "manhourAllocation" }]; |
|
|
|
|
|
} |
|
|
|
|
|
return [ |
|
|
|
|
|
mockGrades.reduce( |
|
|
|
|
|
(acc, grade) => { |
|
|
|
|
|
return { ...acc, [grade]: 0 }; |
|
|
|
|
|
}, |
|
|
|
|
|
{ id: "manhourAllocation" }, |
|
|
|
|
|
), |
|
|
|
|
|
]; |
|
|
|
|
|
}, [getValues, selectedTaskId]); |
|
|
|
|
|
|
|
|
|
|
|
const initialManhours = useMemo(() => { |
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars |
|
|
|
|
|
const { id, ...allocations } = rows[0]; |
|
|
|
|
|
return Object.values(allocations).reduce((acc, hours) => acc + hours, 0); |
|
|
|
|
|
}, [rows]); |
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
<Box> |
|
|
|
|
|
<Typography variant="overline" display="block" marginBlockEnd={1}> |
|
|
|
|
|
{t("Manhour Allocation By Grade")} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
<TextField |
|
|
|
|
|
label={t("Total Project Manhour")} |
|
|
|
|
|
fullWidth |
|
|
|
|
|
type="number" |
|
|
|
|
|
{...register("totalManhour", { |
|
|
|
|
|
valueAsNumber: true, |
|
|
|
|
|
})} |
|
|
|
|
|
/> |
|
|
|
|
|
<Box |
|
|
|
|
|
sx={(theme) => ({ |
|
|
|
|
|
marginBlockStart: 2, |
|
|
|
|
|
marginInline: -3, |
|
|
|
|
|
borderBottom: `1px solid ${theme.palette.divider}`, |
|
|
|
|
|
})} |
|
|
|
|
|
> |
|
|
|
|
|
<TableContainer sx={{ maxHeight: 440 }}> |
|
|
|
|
|
<Table> |
|
|
|
|
|
<TableHead> |
|
|
|
|
|
<TableRow> |
|
|
|
|
|
<TableCell sx={rightBorderCellSx}> |
|
|
|
|
|
{t("Allocation Type")} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
{grades.map((column, idx) => ( |
|
|
|
|
|
<TableCell key={`${column.id}${idx}`}> |
|
|
|
|
|
{column.name} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
))} |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}>{t("Total")}</TableCell> |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
</TableHead> |
|
|
|
|
|
<TableBody> |
|
|
|
|
|
<TableRow> |
|
|
|
|
|
<TableCell sx={rightBorderCellSx}>{t("Percentage")}</TableCell> |
|
|
|
|
|
{grades.map((column, idx) => ( |
|
|
|
|
|
<TableCellEdit |
|
|
|
|
|
key={`${column.id}${idx}`} |
|
|
|
|
|
value={manhourPercentageByGrade[column.id]} |
|
|
|
|
|
renderValue={(val) => percentFormatter.format(val)} |
|
|
|
|
|
onChange={makeUpdatePercentage(column.id)} |
|
|
|
|
|
convertValue={(inputValue) => Number(inputValue)} |
|
|
|
|
|
cellSx={{ backgroundColor: "primary.lightest" }} |
|
|
|
|
|
inputSx={{ width: "3rem" }} |
|
|
|
|
|
/> |
|
|
|
|
|
))} |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}> |
|
|
|
|
|
{percentFormatter.format(totalPercentage)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
<TableRow> |
|
|
|
|
|
<TableCell sx={rightBorderCellSx}>{t("Manhour")}</TableCell> |
|
|
|
|
|
{grades.map((column, idx) => ( |
|
|
|
|
|
<TableCell key={`${column.id}${idx}`}> |
|
|
|
|
|
{manhourFormatter.format( |
|
|
|
|
|
manhourPercentageByGrade[column.id] * totalManhour, |
|
|
|
|
|
)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
))} |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}> |
|
|
|
|
|
{manhourFormatter.format(totalManhour)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
</TableBody> |
|
|
|
|
|
</Table> |
|
|
|
|
|
</TableContainer> |
|
|
|
|
|
</Box> |
|
|
|
|
|
</Box> |
|
|
|
|
|
); |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
const { |
|
|
|
|
|
register, |
|
|
|
|
|
reset, |
|
|
|
|
|
getValues: getManhourFormValues, |
|
|
|
|
|
setValue: setManhourFormValue, |
|
|
|
|
|
} = useForm<{ manhour: number }>({ |
|
|
|
|
|
defaultValues: { |
|
|
|
|
|
manhour: initialManhours, |
|
|
|
|
|
}, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
const ResourceAllocationByStage: React.FC<Props> = ({ grades, allTasks }) => { |
|
|
|
|
|
const { t } = useTranslation(); |
|
|
|
|
|
const { watch, setValue } = useFormContext<CreateProjectInputs>(); |
|
|
|
|
|
|
|
|
// Reset man hour input when task changes |
|
|
|
|
|
useEffect(() => { |
|
|
|
|
|
reset({ manhour: initialManhours }); |
|
|
|
|
|
}, [initialManhours, reset, selectedTaskId]); |
|
|
|
|
|
|
|
|
const currentTaskGroups = watch("taskGroups"); |
|
|
|
|
|
const taskGroups = useMemo( |
|
|
|
|
|
() => |
|
|
|
|
|
uniqBy( |
|
|
|
|
|
allTasks.reduce<TaskGroup[]>((acc, task) => { |
|
|
|
|
|
if (currentTaskGroups[task.taskGroup.id]) { |
|
|
|
|
|
return [...acc, task.taskGroup]; |
|
|
|
|
|
} |
|
|
|
|
|
return acc; |
|
|
|
|
|
}, []), |
|
|
|
|
|
"id", |
|
|
|
|
|
), |
|
|
|
|
|
[allTasks, currentTaskGroups], |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
const updateAllocation = useCallback(() => { |
|
|
|
|
|
const inputHour = getManhourFormValues("manhour"); |
|
|
|
|
|
const ratioSum = Object.values(manhourBreakdownByGrade).reduce( |
|
|
|
|
|
(acc, ratio) => acc + ratio, |
|
|
|
|
|
0, |
|
|
|
|
|
); |
|
|
|
|
|
const newAllocations = _reduce( |
|
|
|
|
|
manhourBreakdownByGrade, |
|
|
|
|
|
(acc, value, key) => { |
|
|
|
|
|
return { ...acc, [key]: (inputHour / ratioSum) * value }; |
|
|
|
|
|
}, |
|
|
|
|
|
{}, |
|
|
|
|
|
); |
|
|
|
|
|
gridApiRef.current.updateRows([ |
|
|
|
|
|
{ id: "manhourAllocation", ...newAllocations }, |
|
|
|
|
|
]); |
|
|
|
|
|
updateTaskAllocations(newAllocations); |
|
|
|
|
|
}, [ |
|
|
|
|
|
getManhourFormValues, |
|
|
|
|
|
gridApiRef, |
|
|
|
|
|
manhourBreakdownByGrade, |
|
|
|
|
|
updateTaskAllocations, |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
const manhourPercentageByGrade = watch("manhourPercentageByGrade"); |
|
|
|
|
|
const totalManhour = watch("totalManhour"); |
|
|
|
|
|
|
|
|
const processRowUpdate = useCallback( |
|
|
|
|
|
(newRow: GridRowModel) => { |
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars |
|
|
|
|
|
const { id: rowId, ...newAllocations } = newRow; |
|
|
|
|
|
const totalHours = Object.values( |
|
|
|
|
|
newAllocations as ManhourAllocation, |
|
|
|
|
|
).reduce<number>((acc, hour) => acc + hour, 0); |
|
|
|
|
|
setManhourFormValue("manhour", totalHours); |
|
|
|
|
|
updateTaskAllocations(newAllocations); |
|
|
|
|
|
return newRow; |
|
|
|
|
|
|
|
|
const makeUpdatePercentage = useCallback( |
|
|
|
|
|
(taskGroupId: TaskGroup["id"]) => (percentage?: number) => { |
|
|
|
|
|
if (percentage !== undefined) { |
|
|
|
|
|
setValue("taskGroups", { |
|
|
|
|
|
...currentTaskGroups, |
|
|
|
|
|
[taskGroupId]: { |
|
|
|
|
|
...currentTaskGroups[taskGroupId], |
|
|
|
|
|
percentAllocation: percentage, |
|
|
|
|
|
}, |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
}, |
|
|
}, |
|
|
[setManhourFormValue, updateTaskAllocations], |
|
|
|
|
|
|
|
|
[currentTaskGroups, setValue], |
|
|
); |
|
|
); |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<Box> |
|
|
<Box> |
|
|
<Typography variant="overline" display="block" marginBlockEnd={1}> |
|
|
<Typography variant="overline" display="block" marginBlockEnd={1}> |
|
|
{t("Task Breakdown")} |
|
|
|
|
|
|
|
|
{t("Manhour Allocation By Stage By Grade")} |
|
|
</Typography> |
|
|
</Typography> |
|
|
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> |
|
|
|
|
|
<Grid item xs={6}> |
|
|
|
|
|
<Paper elevation={2}> |
|
|
|
|
|
<List dense sx={{ maxHeight: 300, overflow: "auto" }}> |
|
|
|
|
|
{tasks.map((task, index) => { |
|
|
|
|
|
return ( |
|
|
|
|
|
<ListItemButton |
|
|
|
|
|
selected={selectedTaskId === task.id} |
|
|
|
|
|
key={`${task.id}-${index}`} |
|
|
|
|
|
onClick={makeOnTaskSelect(task.id)} |
|
|
|
|
|
> |
|
|
|
|
|
<ListItemText primary={task.name} /> |
|
|
|
|
|
</ListItemButton> |
|
|
|
|
|
); |
|
|
|
|
|
})} |
|
|
|
|
|
</List> |
|
|
|
|
|
</Paper> |
|
|
|
|
|
</Grid> |
|
|
|
|
|
<Grid item xs={6}> |
|
|
|
|
|
<TextField |
|
|
|
|
|
label={t("Mahours Allocated to Task")} |
|
|
|
|
|
fullWidth |
|
|
|
|
|
type="number" |
|
|
|
|
|
{...register("manhour", { |
|
|
|
|
|
valueAsNumber: true, |
|
|
|
|
|
onBlur: updateAllocation, |
|
|
|
|
|
})} |
|
|
|
|
|
/> |
|
|
|
|
|
<Paper |
|
|
|
|
|
elevation={2} |
|
|
|
|
|
sx={{ |
|
|
|
|
|
marginBlockStart: 2, |
|
|
|
|
|
".MuiDataGrid-root .MuiDataGrid-columnHeader:focus-within": { |
|
|
|
|
|
outlineOffset: -2, |
|
|
|
|
|
}, |
|
|
|
|
|
}} |
|
|
|
|
|
> |
|
|
|
|
|
<StyledDataGrid |
|
|
|
|
|
apiRef={gridApiRef} |
|
|
|
|
|
disableColumnMenu |
|
|
|
|
|
hideFooter |
|
|
|
|
|
disableRowSelectionOnClick |
|
|
|
|
|
rows={rows} |
|
|
|
|
|
columns={columns} |
|
|
|
|
|
processRowUpdate={processRowUpdate} |
|
|
|
|
|
sx={{ paddingBlockEnd: 2 }} |
|
|
|
|
|
/> |
|
|
|
|
|
</Paper> |
|
|
|
|
|
</Grid> |
|
|
|
|
|
</Grid> |
|
|
|
|
|
|
|
|
<Box |
|
|
|
|
|
sx={(theme) => ({ |
|
|
|
|
|
marginBlockStart: 2, |
|
|
|
|
|
marginInline: -3, |
|
|
|
|
|
borderBottom: `1px solid ${theme.palette.divider}`, |
|
|
|
|
|
})} |
|
|
|
|
|
> |
|
|
|
|
|
<TableContainer sx={{ maxHeight: 440 }}> |
|
|
|
|
|
<Table> |
|
|
|
|
|
<TableHead> |
|
|
|
|
|
<TableRow> |
|
|
|
|
|
<TableCell>{t("Stage")}</TableCell> |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}>{t("Task Count")}</TableCell> |
|
|
|
|
|
<TableCell colSpan={2} sx={leftRightBorderCellSx}> |
|
|
|
|
|
{t("Total Manhour")} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
{grades.map((column, idx) => ( |
|
|
|
|
|
<TableCell key={`${column.id}${idx}`}> |
|
|
|
|
|
{column.name} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
))} |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
</TableHead> |
|
|
|
|
|
<TableBody> |
|
|
|
|
|
{taskGroups.map((tg, idx) => ( |
|
|
|
|
|
<TableRow key={`${tg.id}${idx}`}> |
|
|
|
|
|
<TableCell>{tg.name}</TableCell> |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}> |
|
|
|
|
|
{currentTaskGroups[tg.id].taskIds.length} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCellEdit |
|
|
|
|
|
value={currentTaskGroups[tg.id].percentAllocation} |
|
|
|
|
|
renderValue={(val) => percentFormatter.format(val)} |
|
|
|
|
|
onChange={makeUpdatePercentage(tg.id)} |
|
|
|
|
|
convertValue={(inputValue) => Number(inputValue)} |
|
|
|
|
|
cellSx={{ backgroundColor: "primary.lightest" }} |
|
|
|
|
|
inputSx={{ width: "3rem" }} |
|
|
|
|
|
/> |
|
|
|
|
|
<TableCell sx={rightBorderCellSx}> |
|
|
|
|
|
{manhourFormatter.format( |
|
|
|
|
|
currentTaskGroups[tg.id].percentAllocation * totalManhour, |
|
|
|
|
|
)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
{grades.map((column, idx) => { |
|
|
|
|
|
const stageHours = |
|
|
|
|
|
currentTaskGroups[tg.id].percentAllocation * totalManhour; |
|
|
|
|
|
return ( |
|
|
|
|
|
<TableCell key={`${column.id}${idx}`}> |
|
|
|
|
|
{manhourFormatter.format( |
|
|
|
|
|
manhourPercentageByGrade[column.id] * stageHours, |
|
|
|
|
|
)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
); |
|
|
|
|
|
})} |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
))} |
|
|
|
|
|
<TableRow> |
|
|
|
|
|
<TableCell>{t("Total")}</TableCell> |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}> |
|
|
|
|
|
{Object.values(currentTaskGroups).reduce( |
|
|
|
|
|
(acc, tg) => acc + tg.taskIds.length, |
|
|
|
|
|
0, |
|
|
|
|
|
)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell sx={leftBorderCellSx}> |
|
|
|
|
|
{percentFormatter.format( |
|
|
|
|
|
Object.values(currentTaskGroups).reduce( |
|
|
|
|
|
(acc, tg) => acc + tg.percentAllocation, |
|
|
|
|
|
0, |
|
|
|
|
|
), |
|
|
|
|
|
)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell sx={rightBorderCellSx}> |
|
|
|
|
|
{manhourFormatter.format( |
|
|
|
|
|
Object.values(currentTaskGroups).reduce( |
|
|
|
|
|
(acc, tg) => acc + tg.percentAllocation * totalManhour, |
|
|
|
|
|
0, |
|
|
|
|
|
), |
|
|
|
|
|
)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
{grades.map((column, idx) => { |
|
|
|
|
|
const hours = Object.values(currentTaskGroups).reduce( |
|
|
|
|
|
(acc, tg) => |
|
|
|
|
|
acc + |
|
|
|
|
|
tg.percentAllocation * |
|
|
|
|
|
totalManhour * |
|
|
|
|
|
manhourPercentageByGrade[column.id], |
|
|
|
|
|
0, |
|
|
|
|
|
); |
|
|
|
|
|
return ( |
|
|
|
|
|
<TableCell key={`${column.id}${idx}`}> |
|
|
|
|
|
{manhourFormatter.format(hours)} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
); |
|
|
|
|
|
})} |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
</TableBody> |
|
|
|
|
|
</Table> |
|
|
|
|
|
</TableContainer> |
|
|
|
|
|
</Box> |
|
|
</Box> |
|
|
</Box> |
|
|
); |
|
|
); |
|
|
}; |
|
|
}; |
|
@@ -248,11 +305,16 @@ const NoTaskState: React.FC = () => { |
|
|
const ResourceAllocationWrapper: React.FC<Props> = (props) => { |
|
|
const ResourceAllocationWrapper: React.FC<Props> = (props) => { |
|
|
const { getValues } = useFormContext<CreateProjectInputs>(); |
|
|
const { getValues } = useFormContext<CreateProjectInputs>(); |
|
|
|
|
|
|
|
|
if (Object.keys(getValues("tasks")).length === 0) { |
|
|
|
|
|
|
|
|
if (Object.keys(getValues("taskGroups")).length === 0) { |
|
|
return <NoTaskState />; |
|
|
return <NoTaskState />; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
return <ResourceAllocation {...props} />; |
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
<Stack spacing={4}> |
|
|
|
|
|
<ResourceAllocationByGrade {...props} /> |
|
|
|
|
|
<ResourceAllocationByStage {...props} /> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
); |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
export default ResourceAllocationWrapper; |
|
|
export default ResourceAllocationWrapper; |