|
- "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<CreateProjectInputs>,
- ) => {
- 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<Props> = ({
- 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<NonNullable<TabsProps["onChange"]>>(
- (_e, newValue) => {
- setTabIndex(newValue);
- },
- [],
- );
-
- const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>(
- 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<SubmitErrorHandler<CreateProjectInputs>>(
- (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<CreateProjectInputs>({
- 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 (
- <>
- <FormProvider {...formProps}>
- <Stack
- spacing={2}
- component="form"
- onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
- >
- {isEditMode && !(formProps.getValues("projectDeleted") === true) && (
- <Stack direction="row" gap={1}>
- {!formProps.getValues("projectActualStart") && (
- <Button
- name="start"
- type="submit"
- variant="contained"
- startIcon={<PlayArrow />}
- color="success"
- >
- {t("Start Project")}
- </Button>
- )}
- {formProps.getValues("projectActualStart") &&
- !formProps.getValues("projectActualEnd") && (
- <Button
- name="complete"
- type="submit"
- variant="contained"
- startIcon={<DoneIcon />}
- color="info"
- >
- {t("Complete Project")}
- </Button>
- )}
- {!(
- formProps.getValues("projectActualStart") &&
- formProps.getValues("projectActualEnd")
- ) && (
- <Button
- variant="outlined"
- startIcon={<Delete />}
- color="error"
- onClick={handleDelete}
- >
- {t("Delete Project")}
- </Button>
- )}
- </Stack>
- )}
- <Tabs
- value={tabIndex}
- onChange={handleTabChange}
- variant="scrollable"
- >
- <Tab
- label={t("Project and Client Details")}
- sx={{ marginInlineEnd: !hasErrorsInTab(1, errors) && (hasErrorsInTab(2, errors) || hasErrorsInTab(3, errors)) ? 1 : undefined }}
- icon={
- hasErrorsInTab(0, errors) ? (
- <Error sx={{ marginInlineEnd: 1 }} color="error" />
- ) : undefined
- }
- iconPosition="end"
- />
- <Tab
- label={t("Project Task Setup")}
- sx={{ marginInlineEnd: hasErrorsInTab(2, errors) || hasErrorsInTab(3, errors) ? 1 : undefined }}
- iconPosition="end" />
- <Tab
- label={t("Staff Allocation and Resource")}
- sx={{ marginInlineEnd: !hasErrorsInTab(2, errors) && hasErrorsInTab(3, errors) ? 1 : undefined }}
- icon={
- hasErrorsInTab(2, errors) ? (
- <Error sx={{ marginInlineEnd: 1 }} color="error" />
- ) : undefined
- }
- iconPosition="end"
- />
- <Tab label={t("Milestone")}
- icon={
- hasErrorsInTab(3, errors) ? (
- <Error sx={{ marginInlineEnd: 1 }} color="error" />)
- : undefined}
- iconPosition="end" />
- </Tabs>
- {
- <ProjectClientDetails
- buildingTypes={buildingTypes}
- workNatures={workNatures}
- contractTypes={contractTypes}
- fundingTypes={fundingTypes}
- locationTypes={locationTypes}
- serviceTypes={serviceTypes}
- allCustomers={allCustomers}
- allSubsidiaries={allSubsidiaries}
- projectCategories={projectCategories}
- teamLeads={teamLeads}
- isActive={tabIndex === 0}
- />
- }
- {
- <TaskSetup
- allTasks={allTasks}
- taskTemplates={taskTemplates}
- isActive={tabIndex === 1}
- />
- }
- {
- <StaffAllocation
- isActive={tabIndex === 2}
- allTasks={allTasks}
- grades={grades}
- allStaffs={allStaffs}
- />
- }
- {<Milestone allTasks={allTasks} isActive={tabIndex === 3} />}
- {serverError && (
- <Typography variant="body2" color="error" alignSelf="flex-end">
- {serverError}
- </Typography>
- )}
- <Stack direction="row" justifyContent="flex-end" gap={1}>
- <Button
- variant="outlined"
- startIcon={<Close />}
- onClick={handleCancel}
- >
- {t("Cancel")}
- </Button>
- <Button
- variant="contained"
- startIcon={<Check />}
- type="submit"
- disabled={
- formProps.getValues("projectDeleted") === true ||
- (!!formProps.getValues("projectActualStart") &&
- !!formProps.getValues("projectActualEnd"))
- }
- >
- {isEditMode ? t("Save") : t("Confirm")}
- </Button>
- </Stack>
- </Stack>
- </FormProvider>
- </>
- );
- };
-
- export default CreateProject;
|