You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

218 line
5.7 KiB

  1. "use client";
  2. import Check from "@mui/icons-material/Check";
  3. import Close from "@mui/icons-material/Close";
  4. import Button from "@mui/material/Button";
  5. import Stack from "@mui/material/Stack";
  6. import Tab from "@mui/material/Tab";
  7. import Tabs, { TabsProps } from "@mui/material/Tabs";
  8. import { useRouter } from "next/navigation";
  9. import React, { useCallback, useState } from "react";
  10. import { useTranslation } from "react-i18next";
  11. import ProjectClientDetails from "./ProjectClientDetails";
  12. import TaskSetup from "./TaskSetup";
  13. import StaffAllocation from "./StaffAllocation";
  14. import Milestone from "./Milestone";
  15. import { Task, TaskTemplate } from "@/app/api/tasks";
  16. import {
  17. FieldErrors,
  18. FormProvider,
  19. SubmitErrorHandler,
  20. SubmitHandler,
  21. useForm,
  22. } from "react-hook-form";
  23. import { CreateProjectInputs, saveProject } from "@/app/api/projects/actions";
  24. import { Error } from "@mui/icons-material";
  25. import {
  26. BuildingType,
  27. ContractType,
  28. FundingType,
  29. LocationType,
  30. ProjectCategory,
  31. ServiceType,
  32. WorkNature,
  33. } from "@/app/api/projects";
  34. import { StaffResult } from "@/app/api/staff";
  35. import { Typography } from "@mui/material";
  36. import { Grade } from "@/app/api/grades";
  37. import { Customer } from "@/app/api/customer";
  38. export interface Props {
  39. allTasks: Task[];
  40. projectCategories: ProjectCategory[];
  41. taskTemplates: TaskTemplate[];
  42. teamLeads: StaffResult[];
  43. allCustomers: Customer[];
  44. fundingTypes: FundingType[];
  45. serviceTypes: ServiceType[];
  46. contractTypes: ContractType[];
  47. locationTypes: LocationType[];
  48. buildingTypes: BuildingType[];
  49. workNatures: WorkNature[];
  50. allStaffs: StaffResult[];
  51. // Mocked
  52. grades: Grade[];
  53. }
  54. const hasErrorsInTab = (
  55. tabIndex: number,
  56. errors: FieldErrors<CreateProjectInputs>,
  57. ) => {
  58. switch (tabIndex) {
  59. case 0:
  60. return (
  61. errors.projectName || errors.projectCode || errors.projectDescription
  62. );
  63. default:
  64. false;
  65. }
  66. };
  67. const CreateProject: React.FC<Props> = ({
  68. allTasks,
  69. projectCategories,
  70. taskTemplates,
  71. teamLeads,
  72. grades,
  73. allCustomers,
  74. contractTypes,
  75. fundingTypes,
  76. locationTypes,
  77. serviceTypes,
  78. buildingTypes,
  79. workNatures,
  80. allStaffs,
  81. }) => {
  82. const [serverError, setServerError] = useState("");
  83. const [tabIndex, setTabIndex] = useState(0);
  84. const { t } = useTranslation();
  85. const router = useRouter();
  86. const handleCancel = () => {
  87. router.back();
  88. };
  89. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  90. (_e, newValue) => {
  91. setTabIndex(newValue);
  92. },
  93. [],
  94. );
  95. const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>(
  96. async (data) => {
  97. try {
  98. setServerError("");
  99. await saveProject(data);
  100. router.replace("/projects");
  101. } catch (e) {
  102. setServerError(t("An error has occurred. Please try again later."));
  103. }
  104. },
  105. [router, t],
  106. );
  107. const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>(
  108. (errors) => {
  109. // Set the tab so that the focus will go there
  110. if (
  111. errors.projectName ||
  112. errors.projectDescription ||
  113. errors.projectCode
  114. ) {
  115. setTabIndex(0);
  116. }
  117. },
  118. [],
  119. );
  120. const formProps = useForm<CreateProjectInputs>({
  121. defaultValues: {
  122. taskGroups: {},
  123. allocatedStaffIds: [],
  124. milestones: {},
  125. totalManhour: 0,
  126. manhourPercentageByGrade: grades.reduce((acc, grade) => {
  127. return { ...acc, [grade.id]: 1 / grades.length };
  128. }, {}),
  129. },
  130. });
  131. const errors = formProps.formState.errors;
  132. return (
  133. <FormProvider {...formProps}>
  134. <Stack
  135. spacing={2}
  136. component="form"
  137. onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
  138. >
  139. <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
  140. <Tab
  141. label={t("Project and Client Details")}
  142. icon={
  143. hasErrorsInTab(0, errors) ? (
  144. <Error sx={{ marginInlineEnd: 1 }} color="error" />
  145. ) : undefined
  146. }
  147. iconPosition="end"
  148. />
  149. <Tab label={t("Project Task Setup")} iconPosition="end" />
  150. <Tab label={t("Staff Allocation and Resource")} iconPosition="end" />
  151. <Tab label={t("Milestone")} iconPosition="end" />
  152. </Tabs>
  153. {
  154. <ProjectClientDetails
  155. buildingTypes={buildingTypes}
  156. workNatures={workNatures}
  157. contractTypes={contractTypes}
  158. fundingTypes={fundingTypes}
  159. locationTypes={locationTypes}
  160. serviceTypes={serviceTypes}
  161. allCustomers={allCustomers}
  162. projectCategories={projectCategories}
  163. teamLeads={teamLeads}
  164. isActive={tabIndex === 0}
  165. />
  166. }
  167. {
  168. <TaskSetup
  169. allTasks={allTasks}
  170. taskTemplates={taskTemplates}
  171. isActive={tabIndex === 1}
  172. />
  173. }
  174. {
  175. <StaffAllocation
  176. isActive={tabIndex === 2}
  177. allTasks={allTasks}
  178. grades={grades}
  179. allStaffs={allStaffs}
  180. />
  181. }
  182. {<Milestone allTasks={allTasks} isActive={tabIndex === 3} />}
  183. {serverError && (
  184. <Typography variant="body2" color="error" alignSelf="flex-end">
  185. {serverError}
  186. </Typography>
  187. )}
  188. <Stack direction="row" justifyContent="flex-end" gap={1}>
  189. <Button
  190. variant="outlined"
  191. startIcon={<Close />}
  192. onClick={handleCancel}
  193. >
  194. {t("Cancel")}
  195. </Button>
  196. <Button variant="contained" startIcon={<Check />} type="submit">
  197. {t("Confirm")}
  198. </Button>
  199. </Stack>
  200. </Stack>
  201. </FormProvider>
  202. );
  203. };
  204. export default CreateProject;