diff --git a/src/app/api/projects/actions.ts b/src/app/api/projects/actions.ts index 795fbc5..ea0d719 100644 --- a/src/app/api/projects/actions.ts +++ b/src/app/api/projects/actions.ts @@ -52,6 +52,7 @@ export interface CreateProjectInputs { }; }; allocatedStaffIds: number[]; + ratePerManhour: number; // Milestones milestones: { diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index e8d6cd5..541eb83 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -91,7 +91,8 @@ const hasErrorsInTab = ( return ( errors.totalManhour || errors.manhourPercentageByGrade || - errors.taskGroups + errors.taskGroups || + errors.ratePerManhour ); case 3: return errors.milestones; @@ -159,7 +160,7 @@ const CreateProject: React.FC = ({ let hasErrors = false; // Tab - Staff Allocation and Resource - if (data.totalManhour === null || data.totalManhour <= 0) { + if (data.totalManhour === null || data.totalManhour <= 0 || Number.isNaN(data.totalManhour)) { formProps.setError("totalManhour", { message: "totalManhour value is not valid", type: "required", @@ -168,6 +169,15 @@ const CreateProject: React.FC = ({ hasErrors = true; } + if (data.ratePerManhour === null || data.ratePerManhour <= 0 || Number.isNaN(data.ratePerManhour)) { + formProps.setError("ratePerManhour", { + message: "ratePerManhour value is not valid", + type: "required", + }); + setTabIndex(2); + hasErrors = true; + } + const manhourPercentageByGradeKeys = Object.keys( data.manhourPercentageByGrade, ); @@ -334,7 +344,8 @@ const CreateProject: React.FC = ({ } else if ( errors.totalManhour || errors.manhourPercentageByGrade || - errors.taskGroups + errors.taskGroups || + errors.ratePerManhour ) { setTabIndex(2); } else if (errors.milestones) { @@ -364,6 +375,7 @@ const CreateProject: React.FC = ({ subContractFee: mainProjects !== undefined ? mainProjects[0].subContractFee : undefined, clientId: allCustomers !== undefined ? allCustomers[0].id : undefined, + ratePerManhour: 250, ...defaultInputs, // manhourPercentageByGrade should have a sensible default @@ -377,6 +389,26 @@ const CreateProject: React.FC = ({ const errors = formProps.formState.errors; + // auto calculate the total project manhour + const expectedProjectFee = formProps.watch("expectedProjectFee") + const ratePerManhour = formProps.watch("ratePerManhour") + const totalManhour = formProps.watch("totalManhour") + const [firstLoaded, setFirstLoaded] = useState(false) + React.useMemo(() => { + if ((firstLoaded && expectedProjectFee > 0 && ratePerManhour > 0)) { + console.log(ratePerManhour, formProps.watch("totalManhour")) + formProps.setValue("totalManhour", Math.ceil(expectedProjectFee / ratePerManhour)) + } else { + setFirstLoaded(true) + } + }, [expectedProjectFee, ratePerManhour]) + + React.useMemo(() => { + if ((expectedProjectFee > 0 && ratePerManhour > 0) && (totalManhour === null || Number.isNaN(totalManhour) || totalManhour <= 0)) { + formProps.setValue("totalManhour", Math.ceil(expectedProjectFee / ratePerManhour)) + } + }, [totalManhour]) + return ( <> diff --git a/src/components/CreateProject/ResourceAllocation.tsx b/src/components/CreateProject/ResourceAllocation.tsx index b1a79b9..b56ced8 100644 --- a/src/components/CreateProject/ResourceAllocation.tsx +++ b/src/components/CreateProject/ResourceAllocation.tsx @@ -12,6 +12,7 @@ import { TableRow, Stack, SxProps, + Grid, } from "@mui/material"; import React, { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; @@ -78,12 +79,12 @@ const ResourceAllocationByGrade: React.FC = ({ grades }) => { const keys = Object.keys(updatedManhourPercentageByGrade) if (keys.filter(k => updatedManhourPercentageByGrade[k as any] < 0).length > 0 || - keys.reduce((acc, value) => acc + updatedManhourPercentageByGrade[value as any], 0) !== 100) { - setError("manhourPercentageByGrade", {message: "manhourPercentageByGrade value is not valid", type: "invalid"}) + keys.reduce((acc, value) => acc + updatedManhourPercentageByGrade[value as any], 0) !== 100) { + setError("manhourPercentageByGrade", { message: "manhourPercentageByGrade value is not valid", type: "invalid" }) } else { clearErrors("manhourPercentageByGrade") } - + } }, [manhourPercentageByGrade, setValue], @@ -94,17 +95,34 @@ const ResourceAllocationByGrade: React.FC = ({ grades }) => { {t("Manhour Allocation By Grade")} - + + + + + + + + ({ marginBlockStart: 2, @@ -143,7 +161,7 @@ const ResourceAllocationByGrade: React.FC = ({ grades }) => { error={manhourPercentageByGrade[column.id] < 0} /> ))} - + {totalPercentage + "%"} {/* {percentFormatter.format(totalPercentage)} */} @@ -205,8 +223,8 @@ const ResourceAllocationByStage: React.FC = ({ grades, allTasks }) => { const keys = Object.keys(updatedTaskGroups) if (keys.filter(k => updatedTaskGroups[k as any].percentAllocation < 0).length > 0 || - keys.reduce((acc, value) => acc + updatedTaskGroups[value as any].percentAllocation, 0) !== 100) { - setError("taskGroups", {message: "Task Groups value is not invalid", type: "invalid"}) + keys.reduce((acc, value) => acc + updatedTaskGroups[value as any].percentAllocation, 0) !== 100) { + setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" }) } else { clearErrors("taskGroups") } @@ -312,9 +330,9 @@ const ResourceAllocationByStage: React.FC = ({ grades, allTasks }) => { const hours = Object.values(currentTaskGroups).reduce( (acc, tg) => acc + - tg.percentAllocation / 100 * + tg.percentAllocation / 100 * totalManhour * - manhourPercentageByGrade[column.id] / 100 , + manhourPercentageByGrade[column.id] / 100, 0, ); return (