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.
 
 

219 line
5.8 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, Subsidiary } from "@/app/api/customer";
  38. export interface Props {
  39. allTasks: Task[];
  40. projectCategories: ProjectCategory[];
  41. taskTemplates: TaskTemplate[];
  42. teamLeads: StaffResult[];
  43. allCustomers: Customer[];
  44. allSubsidiaries: Subsidiary[];
  45. fundingTypes: FundingType[];
  46. serviceTypes: ServiceType[];
  47. contractTypes: ContractType[];
  48. locationTypes: LocationType[];
  49. buildingTypes: BuildingType[];
  50. workNatures: WorkNature[];
  51. allStaffs: StaffResult[];
  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. allSubsidiaries,
  75. contractTypes,
  76. fundingTypes,
  77. locationTypes,
  78. serviceTypes,
  79. buildingTypes,
  80. workNatures,
  81. allStaffs,
  82. }) => {
  83. const [serverError, setServerError] = useState("");
  84. const [tabIndex, setTabIndex] = useState(0);
  85. const { t } = useTranslation();
  86. const router = useRouter();
  87. const handleCancel = () => {
  88. router.back();
  89. };
  90. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  91. (_e, newValue) => {
  92. setTabIndex(newValue);
  93. },
  94. [],
  95. );
  96. const onSubmit = useCallback<SubmitHandler<CreateProjectInputs>>(
  97. async (data) => {
  98. try {
  99. setServerError("");
  100. await saveProject(data);
  101. router.replace("/projects");
  102. } catch (e) {
  103. setServerError(t("An error has occurred. Please try again later."));
  104. }
  105. },
  106. [router, t],
  107. );
  108. const onSubmitError = useCallback<SubmitErrorHandler<CreateProjectInputs>>(
  109. (errors) => {
  110. // Set the tab so that the focus will go there
  111. if (
  112. errors.projectName ||
  113. errors.projectDescription ||
  114. errors.projectCode
  115. ) {
  116. setTabIndex(0);
  117. }
  118. },
  119. [],
  120. );
  121. const formProps = useForm<CreateProjectInputs>({
  122. defaultValues: {
  123. taskGroups: {},
  124. allocatedStaffIds: [],
  125. milestones: {},
  126. totalManhour: 0,
  127. manhourPercentageByGrade: grades.reduce((acc, grade) => {
  128. return { ...acc, [grade.id]: 1 / grades.length };
  129. }, {}),
  130. },
  131. });
  132. const errors = formProps.formState.errors;
  133. return (
  134. <FormProvider {...formProps}>
  135. <Stack
  136. spacing={2}
  137. component="form"
  138. onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
  139. >
  140. <Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
  141. <Tab
  142. label={t("Project and Client Details")}
  143. icon={
  144. hasErrorsInTab(0, errors) ? (
  145. <Error sx={{ marginInlineEnd: 1 }} color="error" />
  146. ) : undefined
  147. }
  148. iconPosition="end"
  149. />
  150. <Tab label={t("Project Task Setup")} iconPosition="end" />
  151. <Tab label={t("Staff Allocation and Resource")} iconPosition="end" />
  152. <Tab label={t("Milestone")} iconPosition="end" />
  153. </Tabs>
  154. {
  155. <ProjectClientDetails
  156. buildingTypes={buildingTypes}
  157. workNatures={workNatures}
  158. contractTypes={contractTypes}
  159. fundingTypes={fundingTypes}
  160. locationTypes={locationTypes}
  161. serviceTypes={serviceTypes}
  162. allCustomers={allCustomers}
  163. allSubsidiaries={allSubsidiaries}
  164. projectCategories={projectCategories}
  165. teamLeads={teamLeads}
  166. isActive={tabIndex === 0}
  167. />
  168. }
  169. {
  170. <TaskSetup
  171. allTasks={allTasks}
  172. taskTemplates={taskTemplates}
  173. isActive={tabIndex === 1}
  174. />
  175. }
  176. {
  177. <StaffAllocation
  178. isActive={tabIndex === 2}
  179. allTasks={allTasks}
  180. grades={grades}
  181. allStaffs={allStaffs}
  182. />
  183. }
  184. {<Milestone allTasks={allTasks} isActive={tabIndex === 3} />}
  185. {serverError && (
  186. <Typography variant="body2" color="error" alignSelf="flex-end">
  187. {serverError}
  188. </Typography>
  189. )}
  190. <Stack direction="row" justifyContent="flex-end" gap={1}>
  191. <Button
  192. variant="outlined"
  193. startIcon={<Close />}
  194. onClick={handleCancel}
  195. >
  196. {t("Cancel")}
  197. </Button>
  198. <Button variant="contained" startIcon={<Check />} type="submit">
  199. {t("Confirm")}
  200. </Button>
  201. </Stack>
  202. </Stack>
  203. </FormProvider>
  204. );
  205. };
  206. export default CreateProject;