diff --git a/src/app/(main)/projects/create/page.tsx b/src/app/(main)/projects/create/page.tsx index d8978f8..f11193a 100644 --- a/src/app/(main)/projects/create/page.tsx +++ b/src/app/(main)/projects/create/page.tsx @@ -1,4 +1,4 @@ -import { fetchAllCustomers, fetchAllSubsidiaries } from "@/app/api/customer"; +import { fetchAllCustomers, fetchAllSubsidiaries, fetchCustomerTypes } from "@/app/api/customer"; import { fetchGrades } from "@/app/api/grades"; import { fetchProjectBuildingTypes, @@ -26,9 +26,9 @@ export const metadata: Metadata = { const Projects: React.FC = async () => { const { t } = await getServerI18n("projects"); - const abilities = await getUserAbilities() + const abilities = await getUserAbilities(); - if (![MAINTAIN_PROJECT].some(ability => abilities.includes(ability))) { + if (![MAINTAIN_PROJECT].some((ability) => abilities.includes(ability))) { notFound(); } @@ -44,6 +44,7 @@ const Projects: React.FC = async () => { fetchProjectWorkNatures(); fetchAllCustomers(); fetchAllSubsidiaries(); + fetchCustomerTypes(); fetchGrades(); preloadTeamLeads(); preloadStaff(); diff --git a/src/app/api/projects/actions.ts b/src/app/api/projects/actions.ts index 94965c5..795fbc5 100644 --- a/src/app/api/projects/actions.ts +++ b/src/app/api/projects/actions.ts @@ -40,6 +40,7 @@ export interface CreateProjectInputs { clientSubsidiaryId?: number | null; subsidiaryContactId?: number; isSubsidiaryContact?: boolean; + clientTypeId?: number; // Allocation totalManhour: number; @@ -117,12 +118,12 @@ export const deleteProject = async (id: number) => { }; export const importProjects = async (data: FormData) => { - const importProjects = await serverFetchString( - `${BASE_API_URL}/projects/import`, - { - method: "POST", - body: data, - }, + const importProjects = await serverFetchString( + `${BASE_API_URL}/projects/import`, + { + method: "POST", + body: data, + }, ); return importProjects; diff --git a/src/components/CreateProject/CreateProject.tsx b/src/components/CreateProject/CreateProject.tsx index 27d3ba1..e8d6cd5 100644 --- a/src/components/CreateProject/CreateProject.tsx +++ b/src/components/CreateProject/CreateProject.tsx @@ -41,7 +41,7 @@ import { import { StaffResult } from "@/app/api/staff"; import { Typography } from "@mui/material"; import { Grade } from "@/app/api/grades"; -import { Customer, Subsidiary } from "@/app/api/customer"; +import { Customer, CustomerType, Subsidiary } from "@/app/api/customer"; import { isEmpty } from "lodash"; import { deleteDialog, @@ -70,6 +70,7 @@ export interface Props { buildingTypes: BuildingType[]; workNatures: WorkNature[]; allStaffs: StaffResult[]; + customerTypes: CustomerType[]; grades: Grade[]; abilities: string[]; } @@ -81,16 +82,19 @@ const hasErrorsInTab = ( switch (tabIndex) { case 0: return ( - errors.projectName || errors.projectDescription || errors.clientId || errors.projectCode + errors.projectName || + errors.projectDescription || + errors.clientId || + errors.projectCode ); case 2: return ( - errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups + errors.totalManhour || + errors.manhourPercentageByGrade || + errors.taskGroups ); case 3: - return ( - errors.milestones - ) + return errors.milestones; default: false; } @@ -115,6 +119,7 @@ const CreateProject: React.FC = ({ buildingTypes, workNatures, allStaffs, + customerTypes, abilities, }) => { const [serverError, setServerError] = useState(""); @@ -151,53 +156,102 @@ const CreateProject: React.FC = ({ console.log(data); // detect errors - let hasErrors = false + let hasErrors = false; // Tab - Staff Allocation and Resource if (data.totalManhour === null || data.totalManhour <= 0) { - formProps.setError("totalManhour", { message: "totalManhour value is not valid", type: "required" }) - setTabIndex(2) - hasErrors = true + formProps.setError("totalManhour", { + message: "totalManhour value is not valid", + type: "required", + }); + setTabIndex(2); + hasErrors = true; } - const manhourPercentageByGradeKeys = Object.keys(data.manhourPercentageByGrade) - if (manhourPercentageByGradeKeys.filter(k => data.manhourPercentageByGrade[k as any] < 0).length > 0 || - manhourPercentageByGradeKeys.reduce((acc, value) => acc + data.manhourPercentageByGrade[value as any], 0) !== 100) { - formProps.setError("manhourPercentageByGrade", { message: "manhourPercentageByGrade value is not valid", type: "invalid" }) - setTabIndex(2) - hasErrors = true + const manhourPercentageByGradeKeys = Object.keys( + data.manhourPercentageByGrade, + ); + if ( + manhourPercentageByGradeKeys.filter( + (k) => data.manhourPercentageByGrade[k as any] < 0, + ).length > 0 || + manhourPercentageByGradeKeys.reduce( + (acc, value) => acc + data.manhourPercentageByGrade[value as any], + 0, + ) !== 100 + ) { + formProps.setError("manhourPercentageByGrade", { + message: "manhourPercentageByGrade value is not valid", + type: "invalid", + }); + setTabIndex(2); + hasErrors = true; } - const taskGroupKeys = Object.keys(data.taskGroups) - if (taskGroupKeys.filter(k => data.taskGroups[k as any].percentAllocation < 0).length > 0 || - taskGroupKeys.reduce((acc, value) => acc + data.taskGroups[value as any].percentAllocation, 0) !== 100) { - formProps.setError("taskGroups", { message: "Task Groups value is not invalid", type: "invalid" }) - setTabIndex(2) - hasErrors = true + const taskGroupKeys = Object.keys(data.taskGroups); + if ( + taskGroupKeys.filter( + (k) => data.taskGroups[k as any].percentAllocation < 0, + ).length > 0 || + taskGroupKeys.reduce( + (acc, value) => + acc + data.taskGroups[value as any].percentAllocation, + 0, + ) !== 100 + ) { + formProps.setError("taskGroups", { + message: "Task Groups value is not invalid", + type: "invalid", + }); + setTabIndex(2); + hasErrors = true; } // Tab - Milestone - let projectTotal = 0 - const milestonesKeys = Object.keys(data.milestones).filter(key => taskGroupKeys.includes(key)) - milestonesKeys.filter(key => Object.keys(data.taskGroups).includes(key)).forEach(key => { - const { startDate, endDate, payments } = data.milestones[parseFloat(key)] + let projectTotal = 0; + const milestonesKeys = Object.keys(data.milestones).filter((key) => + taskGroupKeys.includes(key), + ); + milestonesKeys + .filter((key) => Object.keys(data.taskGroups).includes(key)) + .forEach((key) => { + const { startDate, endDate, payments } = + data.milestones[parseFloat(key)]; - if (!Boolean(startDate) || startDate === "Invalid Date" || !Boolean(endDate) || endDate === "Invalid Date" || new Date(startDate) > new Date(endDate)) { - formProps.setError("milestones", { message: "milestones is not valid", type: "invalid" }) - setTabIndex(3) - hasErrors = true - } + if ( + !Boolean(startDate) || + startDate === "Invalid Date" || + !Boolean(endDate) || + endDate === "Invalid Date" || + new Date(startDate) > new Date(endDate) + ) { + formProps.setError("milestones", { + message: "milestones is not valid", + type: "invalid", + }); + setTabIndex(3); + hasErrors = true; + } - projectTotal += payments.reduce((acc, payment) => acc + payment.amount, 0) - }) + projectTotal += payments.reduce( + (acc, payment) => acc + payment.amount, + 0, + ); + }); - if (projectTotal !== data.expectedProjectFee || milestonesKeys.length !== taskGroupKeys.length) { - formProps.setError("milestones", { message: "milestones is not valid", type: "invalid" }) - setTabIndex(3) - hasErrors = true + if ( + projectTotal !== data.expectedProjectFee || + milestonesKeys.length !== taskGroupKeys.length + ) { + formProps.setError("milestones", { + message: "milestones is not valid", + type: "invalid", + }); + setTabIndex(3); + hasErrors = true; } - if (hasErrors) return false + if (hasErrors) return false; // save project setServerError(""); @@ -227,18 +281,29 @@ const CreateProject: React.FC = ({ data.projectActualEnd = dayjs().format("YYYY-MM-DD"); } - data.taskTemplateId = data.taskTemplateId === "All" ? undefined : data.taskTemplateId; + data.taskTemplateId = + data.taskTemplateId === "All" ? undefined : data.taskTemplateId; const response = await saveProject(data); - if (response.id > 0 && response.message?.toLowerCase() === "success" && response.errorPosition === null) { + if ( + response.id > 0 && + response.message?.toLowerCase() === "success" && + response.errorPosition === null + ) { successDialog(successTitle, t).then(() => { router.replace("/projects"); }); } else { errorDialog(response.message ?? errorTitle, t).then(() => { - if (response.errorPosition !== null && response.errorPosition === "projectCode") { - formProps.setError("projectCode", { message: response.message, type: "invalid" }) - setTabIndex(0) + if ( + response.errorPosition !== null && + response.errorPosition === "projectCode" + ) { + formProps.setError("projectCode", { + message: response.message, + type: "invalid", + }); + setTabIndex(0); } return false; @@ -257,7 +322,7 @@ const CreateProject: React.FC = ({ const onSubmitError = useCallback>( (errors) => { - console.log(errors) + console.log(errors); // Set the tab so that the focus will go there if ( errors.projectName || @@ -266,10 +331,14 @@ const CreateProject: React.FC = ({ errors.clientId ) { setTabIndex(0); - } else if (errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups) { - setTabIndex(2) + } else if ( + errors.totalManhour || + errors.manhourPercentageByGrade || + errors.taskGroups + ) { + setTabIndex(2); } else if (errors.milestones) { - setTabIndex(3) + setTabIndex(3); } }, [], @@ -282,18 +351,26 @@ const CreateProject: React.FC = ({ milestones: {}, totalManhour: 0, taskTemplateId: "All", - projectName: mainProjects !== undefined ? mainProjects[0].projectName : undefined, - projectDescription: mainProjects !== undefined ? mainProjects[0].projectDescription : undefined, - expectedProjectFee: mainProjects !== undefined ? mainProjects[0].expectedProjectFee : undefined, - subContractFee: mainProjects !== undefined ? mainProjects[0].subContractFee : undefined, + projectName: + mainProjects !== undefined ? mainProjects[0].projectName : undefined, + projectDescription: + mainProjects !== undefined + ? mainProjects[0].projectDescription + : undefined, + expectedProjectFee: + mainProjects !== undefined + ? mainProjects[0].expectedProjectFee + : undefined, + subContractFee: + mainProjects !== undefined ? mainProjects[0].subContractFee : undefined, clientId: allCustomers !== undefined ? allCustomers[0].id : undefined, ...defaultInputs, // manhourPercentageByGrade should have a sensible default manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade) ? grades.reduce((acc, grade) => { - return { ...acc, [grade.id]: 100 / grades.length }; - }, {}) + return { ...acc, [grade.id]: 100 / grades.length }; + }, {}) : defaultInputs?.manhourPercentageByGrade, }, }); @@ -311,7 +388,8 @@ const CreateProject: React.FC = ({ {isEditMode && !(formProps.getValues("projectDeleted") === true) && ( {/* {!formProps.getValues("projectActualStart") && ( */} - {formProps.getValues("projectStatus")?.toLowerCase() === "pending to start" && ( + {formProps.getValues("projectStatus")?.toLowerCase() === + "pending to start" && (