Browse Source

1. Project - Add auto calculation for total manhour

tags/Baseline_30082024_FRONTEND_UAT
cyril.tsui 1 year ago
parent
commit
3ba98b40b7
3 changed files with 73 additions and 22 deletions
  1. +1
    -0
      src/app/api/projects/actions.ts
  2. +35
    -3
      src/components/CreateProject/CreateProject.tsx
  3. +37
    -19
      src/components/CreateProject/ResourceAllocation.tsx

+ 1
- 0
src/app/api/projects/actions.ts View File

@@ -52,6 +52,7 @@ export interface CreateProjectInputs {
}; };
}; };
allocatedStaffIds: number[]; allocatedStaffIds: number[];
ratePerManhour: number;


// Milestones // Milestones
milestones: { milestones: {


+ 35
- 3
src/components/CreateProject/CreateProject.tsx View File

@@ -91,7 +91,8 @@ const hasErrorsInTab = (
return ( return (
errors.totalManhour || errors.totalManhour ||
errors.manhourPercentageByGrade || errors.manhourPercentageByGrade ||
errors.taskGroups
errors.taskGroups ||
errors.ratePerManhour
); );
case 3: case 3:
return errors.milestones; return errors.milestones;
@@ -159,7 +160,7 @@ const CreateProject: React.FC<Props> = ({
let hasErrors = false; let hasErrors = false;


// Tab - Staff Allocation and Resource // 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", { formProps.setError("totalManhour", {
message: "totalManhour value is not valid", message: "totalManhour value is not valid",
type: "required", type: "required",
@@ -168,6 +169,15 @@ const CreateProject: React.FC<Props> = ({
hasErrors = true; 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( const manhourPercentageByGradeKeys = Object.keys(
data.manhourPercentageByGrade, data.manhourPercentageByGrade,
); );
@@ -334,7 +344,8 @@ const CreateProject: React.FC<Props> = ({
} else if ( } else if (
errors.totalManhour || errors.totalManhour ||
errors.manhourPercentageByGrade || errors.manhourPercentageByGrade ||
errors.taskGroups
errors.taskGroups ||
errors.ratePerManhour
) { ) {
setTabIndex(2); setTabIndex(2);
} else if (errors.milestones) { } else if (errors.milestones) {
@@ -364,6 +375,7 @@ const CreateProject: React.FC<Props> = ({
subContractFee: subContractFee:
mainProjects !== undefined ? mainProjects[0].subContractFee : undefined, mainProjects !== undefined ? mainProjects[0].subContractFee : undefined,
clientId: allCustomers !== undefined ? allCustomers[0].id : undefined, clientId: allCustomers !== undefined ? allCustomers[0].id : undefined,
ratePerManhour: 250,
...defaultInputs, ...defaultInputs,


// manhourPercentageByGrade should have a sensible default // manhourPercentageByGrade should have a sensible default
@@ -377,6 +389,26 @@ const CreateProject: React.FC<Props> = ({


const errors = formProps.formState.errors; 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 ( return (
<> <>
<FormProvider {...formProps}> <FormProvider {...formProps}>


+ 37
- 19
src/components/CreateProject/ResourceAllocation.tsx View File

@@ -12,6 +12,7 @@ import {
TableRow, TableRow,
Stack, Stack,
SxProps, SxProps,
Grid,
} from "@mui/material"; } from "@mui/material";
import React, { useCallback, useMemo } from "react"; import React, { useCallback, useMemo } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -78,12 +79,12 @@ const ResourceAllocationByGrade: React.FC<Props> = ({ grades }) => {


const keys = Object.keys(updatedManhourPercentageByGrade) const keys = Object.keys(updatedManhourPercentageByGrade)
if (keys.filter(k => updatedManhourPercentageByGrade[k as any] < 0).length > 0 || 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 { } else {
clearErrors("manhourPercentageByGrade") clearErrors("manhourPercentageByGrade")
} }
} }
}, },
[manhourPercentageByGrade, setValue], [manhourPercentageByGrade, setValue],
@@ -94,17 +95,34 @@ const ResourceAllocationByGrade: React.FC<Props> = ({ grades }) => {
<Typography variant="overline" display="block" marginBlockEnd={1}> <Typography variant="overline" display="block" marginBlockEnd={1}>
{t("Manhour Allocation By Grade")} {t("Manhour Allocation By Grade")}
</Typography> </Typography>
<TextField
label={t("Total Project Manhour")}
fullWidth
type="number"
{...register("totalManhour", {
valueAsNumber: true,
required: "totalManhour code required!",
min: 1,
})}
error={Boolean(errors.totalManhour)}
/>
<Grid container columnSpacing={2}>
<Grid item xs={8}>
<TextField
label={t("Total Project Manhour")}
fullWidth
type="number"
{...register("totalManhour", {
valueAsNumber: true,
required: "totalManhour code required!",
min: 1,
})}
error={Boolean(errors.totalManhour)}
/>
</Grid>
<Grid item xs={4}>
<TextField
label={t("Rate Per Manhour")}
fullWidth
type="number"
{...register("ratePerManhour", {
valueAsNumber: true,
required: "ratePerManhour code required!",
min: 1,
})}
error={Boolean(errors.ratePerManhour)}
/>
</Grid>
</Grid>
<Box <Box
sx={(theme) => ({ sx={(theme) => ({
marginBlockStart: 2, marginBlockStart: 2,
@@ -143,7 +161,7 @@ const ResourceAllocationByGrade: React.FC<Props> = ({ grades }) => {
error={manhourPercentageByGrade[column.id] < 0} error={manhourPercentageByGrade[column.id] < 0}
/> />
))} ))}
<TableCell sx={{ ...(totalPercentage === 100 && leftBorderCellSx), ...(totalPercentage !== 100 && {...errorCellSx, borderRight: "1px solid", borderColor: "error.main"})}}>
<TableCell sx={{ ...(totalPercentage === 100 && leftBorderCellSx), ...(totalPercentage !== 100 && { ...errorCellSx, borderRight: "1px solid", borderColor: "error.main" }) }}>
{totalPercentage + "%"} {totalPercentage + "%"}
{/* {percentFormatter.format(totalPercentage)} */} {/* {percentFormatter.format(totalPercentage)} */}
</TableCell> </TableCell>
@@ -205,8 +223,8 @@ const ResourceAllocationByStage: React.FC<Props> = ({ grades, allTasks }) => {


const keys = Object.keys(updatedTaskGroups) const keys = Object.keys(updatedTaskGroups)
if (keys.filter(k => updatedTaskGroups[k as any].percentAllocation < 0).length > 0 || 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 { } else {
clearErrors("taskGroups") clearErrors("taskGroups")
} }
@@ -312,9 +330,9 @@ const ResourceAllocationByStage: React.FC<Props> = ({ grades, allTasks }) => {
const hours = Object.values(currentTaskGroups).reduce( const hours = Object.values(currentTaskGroups).reduce(
(acc, tg) => (acc, tg) =>
acc + acc +
tg.percentAllocation / 100 *
tg.percentAllocation / 100 *
totalManhour * totalManhour *
manhourPercentageByGrade[column.id] / 100 ,
manhourPercentageByGrade[column.id] / 100,
0, 0,
); );
return ( return (


Loading…
Cancel
Save