"use client"; import DoneIcon from "@mui/icons-material/Done"; import Check from "@mui/icons-material/Check"; import Close from "@mui/icons-material/Close"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import Tab from "@mui/material/Tab"; import Tabs, { TabsProps } from "@mui/material/Tabs"; import { useRouter } from "next/navigation"; import React, { useCallback, useState } from "react"; import { useTranslation } from "react-i18next"; import ProjectClientDetails from "./ProjectClientDetails"; import TaskSetup from "./TaskSetup"; import StaffAllocation from "./StaffAllocation"; import Milestone from "./Milestone"; import { Task, TaskTemplate } from "@/app/api/tasks"; import { FieldErrors, FormProvider, SubmitErrorHandler, SubmitHandler, useForm, } from "react-hook-form"; import { CreateProjectInputs, deleteProject, saveProject, } from "@/app/api/projects/actions"; import { Delete, Error, PlayArrow } from "@mui/icons-material"; import { BuildingType, ContractType, FundingType, LocationType, ProjectCategory, ServiceType, WorkNature, } from "@/app/api/projects"; 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 { isEmpty } from "lodash"; import { deleteDialog, errorDialog, submitDialog, successDialog, } from "../Swal/CustomAlerts"; import dayjs from "dayjs"; export interface Props { isEditMode: boolean; defaultInputs?: CreateProjectInputs; allTasks: Task[]; projectCategories: ProjectCategory[]; taskTemplates: TaskTemplate[]; teamLeads: StaffResult[]; allCustomers: Customer[]; allSubsidiaries: Subsidiary[]; fundingTypes: FundingType[]; serviceTypes: ServiceType[]; contractTypes: ContractType[]; locationTypes: LocationType[]; buildingTypes: BuildingType[]; workNatures: WorkNature[]; allStaffs: StaffResult[]; grades: Grade[]; } const hasErrorsInTab = ( tabIndex: number, errors: FieldErrors, ) => { switch (tabIndex) { case 0: return ( errors.projectName || errors.projectCode || errors.projectDescription ); case 2: return ( errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups ); case 3: return ( errors.milestones ) default: false; } }; const CreateProject: React.FC = ({ isEditMode, defaultInputs, allTasks, projectCategories, taskTemplates, teamLeads, grades, allCustomers, allSubsidiaries, contractTypes, fundingTypes, locationTypes, serviceTypes, buildingTypes, workNatures, allStaffs, }) => { const [serverError, setServerError] = useState(""); const [tabIndex, setTabIndex] = useState(0); const { t } = useTranslation(); const router = useRouter(); const handleCancel = () => { router.replace("/projects"); }; const handleDelete = () => { deleteDialog(async () => { await deleteProject(formProps.getValues("projectId")!); const clickSuccessDialog = await successDialog("Delete Success", t); if (clickSuccessDialog) { router.replace("/projects"); } }, t); }; const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); }, [], ); const onSubmit = useCallback>( async (data, event) => { try { console.log(data); // detect errors 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 } 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 } // Tab - Milestone let projectTotal = 0 const milestonesKeys = Object.keys(data.milestones) milestonesKeys.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 } projectTotal += payments.reduce((acc, payment) => acc + payment.amount, 0) }) if (projectTotal !== data.expectedProjectFee) { formProps.setError("milestones", {message: "milestones is not valid", type: "invalid"}) setTabIndex(3) hasErrors = true } if (hasErrors) return false // save project setServerError(""); let title = t("Do you want to submit?"); let confirmButtonText = t("Submit"); let successTitle = t("Submit Success"); let errorTitle = t("Submit Fail"); const buttonName = (event?.nativeEvent as any).submitter.name; if (buttonName === "start") { title = t("Do you want to start?"); confirmButtonText = t("Start"); successTitle = t("Start Success"); errorTitle = t("Start Fail"); } else if (buttonName === "complete") { title = t("Do you want to complete?"); confirmButtonText = t("Complete"); successTitle = t("Complete Success"); errorTitle = t("Complete Fail"); } submitDialog( async () => { if (buttonName === "start") { data.projectActualStart = dayjs().format("YYYY-MM-DD"); } else if (buttonName === "complete") { data.projectActualEnd = dayjs().format("YYYY-MM-DD"); } const response = await saveProject(data); if (response.id > 0) { successDialog(successTitle, t).then(() => { router.replace("/projects"); }); } else { errorDialog(errorTitle, t).then(() => { return false; }); } }, t, { title: title, confirmButtonText: confirmButtonText }, ); } catch (e) { setServerError(t("An error has occurred. Please try again later.")); } }, [router, t], ); const onSubmitError = useCallback>( (errors) => { console.log(errors) // Set the tab so that the focus will go there if ( errors.projectName || errors.projectDescription || errors.projectCode ) { setTabIndex(0); } else if (errors.totalManhour || errors.manhourPercentageByGrade || errors.taskGroups) { setTabIndex(2) } else if (errors.milestones) { setTabIndex(3) } }, [], ); const formProps = useForm({ defaultValues: { taskGroups: {}, allocatedStaffIds: [], milestones: {}, totalManhour: 0, ...defaultInputs, // manhourPercentageByGrade should have a sensible default manhourPercentageByGrade: isEmpty(defaultInputs?.manhourPercentageByGrade) ? grades.reduce((acc, grade) => { return { ...acc, [grade.id]: 100 / grades.length }; }, {}) : defaultInputs?.manhourPercentageByGrade, }, }); const errors = formProps.formState.errors; return ( <> {isEditMode && !(formProps.getValues("projectDeleted") === true) && ( {!formProps.getValues("projectActualStart") && ( )} {formProps.getValues("projectActualStart") && !formProps.getValues("projectActualEnd") && ( )} {!( formProps.getValues("projectActualStart") && formProps.getValues("projectActualEnd") ) && ( )} )} ) : undefined } iconPosition="end" /> ) : undefined } iconPosition="end" /> ) : undefined} iconPosition="end" /> { } { } { } {} {serverError && ( {serverError} )} ); }; export default CreateProject;