Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 

322 řádky
12 KiB

  1. "use client";
  2. import { useCallback, useEffect, useState } from "react";
  3. import CustomInputForm from "../CustomInputForm";
  4. import { useRouter, useSearchParams } from "next/navigation";
  5. import { useTranslation } from "react-i18next";
  6. import {
  7. FieldErrors,
  8. FormProvider,
  9. SubmitErrorHandler,
  10. SubmitHandler,
  11. useForm,
  12. } from "react-hook-form";
  13. import { CreateStaffInputs, saveStaff, teamHistory } from "@/app/api/staff/actions";
  14. import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
  15. // import CreateStaffForm from "../CreateStaffForm";
  16. import { comboProp } from "@/app/api/companys/actions";
  17. // import StaffInfo from "./StaffInfo";
  18. import { Check, Close, ConstructionOutlined, RestartAlt } from "@mui/icons-material";
  19. import StaffInfo from "./StaffInfo";
  20. import { IndividualStaff, projects, SalaryEffectiveInfo } from "@/app/api/staff";
  21. import dayjs from "dayjs";
  22. import ProjectHistory from "./ProjectHistory";
  23. import { InfoHistory } from "./EditStaffWrapper";
  24. import { fetchIndivTeam } from "@/app/api/team";
  25. // import { useGridApiContext } from '@mui/x-data-grid';
  26. export interface comboItem {
  27. company: comboProp[];
  28. team: comboProp[];
  29. department: comboProp[];
  30. position: comboProp[];
  31. grade: comboProp[];
  32. skill: comboProp[];
  33. salary: comboProp[];
  34. }
  35. interface formProps {
  36. Staff: IndividualStaff
  37. combos: comboItem;
  38. SalaryEffectiveInfo: SalaryEffectiveInfo[];
  39. InvolvedProject?: projects[]
  40. InfoHistory: InfoHistory
  41. }
  42. const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, InvolvedProject, InfoHistory }) => {
  43. const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id)
  44. const { t } = useTranslation();
  45. const searchParams = useSearchParams()
  46. const [tabIndex, setTabIndex] = useState(0);
  47. const id = parseInt(searchParams.get("id") || "0");
  48. const formProps = useForm<CreateStaffInputs
  49. & { salaryEffectiveInfo: SalaryEffectiveInfo[] }
  50. & { delSalaryEffectiveInfo: number[] }>({
  51. defaultValues: {
  52. staffId: Staff.staffId,
  53. name: Staff.name,
  54. companyId: Staff.company.id,
  55. teamId: Staff.team?.id,
  56. departmentId: Staff.department?.id,
  57. gradeId: Staff.grade?.id,
  58. skillSetId: defaultSkillset,
  59. // removeSkillSetId: [],
  60. currentPositionId: Staff.currentPosition?.id,
  61. salaryId: Staff.salary.salaryPoint,
  62. employType: Staff.employType,
  63. email: Staff.email,
  64. phone1: Staff.phone1,
  65. phone2: Staff.phone2,
  66. emergContactName: Staff.emergContactName,
  67. emergContactPhone: Staff.emergContactPhone,
  68. joinDate: Staff.joinDate ? dayjs(Staff.joinDate as string).format("YYYY-MM-DD") : null,
  69. joinPositionId: Staff.joinPosition?.id || null,
  70. departDate: Staff.departDate ? dayjs(Staff.departDate as string).format("YYYY-MM-DD") : null,
  71. departReason: Staff.departReason,
  72. remark: Staff.remark,
  73. salaryEffectiveInfo: SalaryEffectiveInfo.map(item => {
  74. return ({
  75. id: item.id,
  76. salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label,
  77. date: dayjs(item.date).toDate(),
  78. })}),
  79. delSalaryEffectiveInfo: [],
  80. teamHistory: InfoHistory.teamLog ? InfoHistory.teamLog.map(item => {
  81. return ({
  82. id: item.id,
  83. team: item.team.name,
  84. from: dayjs(item.from.join()).toDate(),
  85. to: item.to ? dayjs(item.to.join()).toDate() : "",
  86. })
  87. }) : [],
  88. delTeamHistory: [],
  89. gradeHistory: InfoHistory.gradeLog ? InfoHistory.gradeLog.map(item => {
  90. return ({
  91. id: item.id,
  92. grade: item.grade.name,
  93. from: dayjs(item.from.join()).toDate(),
  94. to: item.to ? dayjs(item.to.join()).toDate() : "",
  95. })
  96. }) : [],
  97. delGradeHistory: [],
  98. positionHistory: InfoHistory.positionLog ? InfoHistory.positionLog.map(item => {
  99. return ({
  100. id: item.id,
  101. position: item.position.name,
  102. from: dayjs(item.from.join()).toDate(),
  103. to: item.to ? dayjs(item.to.join()).toDate() : "",
  104. })
  105. }) : [],
  106. delPositionHistory: [],
  107. }});
  108. const [serverError, setServerError] = useState("");
  109. const router = useRouter();
  110. const errors = formProps.formState.errors;
  111. const onSubmit = useCallback<SubmitHandler<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } >>(
  112. async (data) => {
  113. try {
  114. // console.log(data);
  115. let haveError = false;
  116. const regex_email = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/
  117. const regex_phone = /^\d{8}$/
  118. if (!regex_email.test(data.email)) {
  119. haveError = true
  120. formProps.setError("email", { message: t("Please Enter Correct Email."), type: "required" })
  121. }
  122. if (!regex_phone.test(data.phone1)) {
  123. haveError = true
  124. formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" })
  125. }
  126. if (data.emergContactPhone && !regex_phone.test(data.emergContactPhone)) {
  127. haveError = true
  128. formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" })
  129. }
  130. if (data.phone2 && data.phone2?.length > 0) {
  131. if(!regex_phone.test(data.phone2)) {
  132. haveError = true
  133. formProps.setError("phone2", { message: t("Please Enter Correct Phone No.."), type: "required" })
  134. }
  135. }
  136. if (data.phone1 === data.phone2 || data.phone1 === data.emergContactPhone || data.phone2 && data.phone2 === data.emergContactPhone && data.phone2.length > 0) {
  137. haveError = true
  138. formProps.setError("phone1", { message: t("Please Enter Different Phone No.."), type: "required" })
  139. if (data.phone2!.length > 0) {
  140. formProps.setError("phone2", { message: t("Please Enter Different Phone No.."), type: "required" })
  141. }
  142. formProps.setError("emergContactPhone", { message: t("Please Enter Different Phone No.."), type: "required" })
  143. }
  144. if (!regex_email.test(data.email)) {
  145. haveError = true
  146. formProps.setError("email", { message: t("Please Enter Correct Email."), type: "required" })
  147. }
  148. if (!data.companyId) {
  149. haveError = true
  150. formProps.setError("companyId", { message: t("Please Enter Company."), type: "required" })
  151. }
  152. if (!data.employType) {
  153. haveError = true
  154. formProps.setError("employType", { message: t("Please Enter Employ Type."), type: "required" })
  155. }
  156. if (!data.salaryId) {
  157. haveError = true
  158. formProps.setError("salaryId", { message: t("Please Enter Salary."), type: "required" })
  159. }
  160. // if (data.joinDate && data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) {
  161. // haveError = true
  162. // formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" })
  163. // }
  164. if (haveError) {
  165. return
  166. }
  167. const teamHistory = data.teamHistory.map((item) => ({
  168. id: item.id,
  169. team: combos.team.filter(team => team.label === item.team)[0].id,
  170. from: dayjs(item.from).format('YYYY-MM-DD'),
  171. to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined,
  172. }))
  173. const gradeHistory = data.gradeHistory.map((item) => ({
  174. id: item.id,
  175. grade: combos.grade.filter(grade => grade.label === item.grade)[0].id,
  176. from: dayjs(item.from).format('YYYY-MM-DD'),
  177. to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined,
  178. }))
  179. const positionHistory = data.positionHistory.map((item) => ({
  180. id: item.id,
  181. position: combos.position.filter(position => position.label === item.position)[0].id,
  182. from: dayjs(item.from).format('YYYY-MM-DD'),
  183. to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined,
  184. }))
  185. const salaryEffectiveInfo = data.salaryEffectiveInfo.map((item: SalaryEffectiveInfo) => ({
  186. id: item.id,
  187. salaryPoint: chopSalaryPoints(item.salaryPoint),
  188. date: dayjs(item.date).format('YYYY-MM-DD').toString()
  189. }))
  190. const postData: CreateStaffInputs = {
  191. id: id,
  192. ...data,
  193. salaryEffectiveInfo: salaryEffectiveInfo,
  194. teamHistory: teamHistory ?? [],
  195. gradeHistory: gradeHistory ?? [],
  196. positionHistory: positionHistory ?? [],
  197. delTeamHistory: data.delTeamHistory ? data.delTeamHistory : [],
  198. delGradeHistory: data.delGradeHistory ? data.delGradeHistory : [],
  199. delPositionHistory: data.delPositionHistory ? data.delPositionHistory : [],
  200. }
  201. if (postData.joinDate) {
  202. postData.joinDate = dayjs(postData.joinDate).format("YYYY-MM-DD")
  203. }
  204. if (postData.departDate) {
  205. postData.departDate = dayjs(postData.departDate).format("YYYY-MM-DD")
  206. }
  207. console.log(postData)
  208. await saveStaff(postData)
  209. router.replace("/settings/staff")
  210. } catch (e: any) {
  211. console.log(e);
  212. formProps.setError("staffId", { message: t("Please Enter Employ Type."), type: "required" })
  213. let msg = ""
  214. if (e.message === "Duplicated StaffId Found") {
  215. msg = t("Duplicated StaffId Found")
  216. }
  217. setServerError(`${t("An error has occurred. Please try again later.")} ${msg} `);
  218. }
  219. },
  220. [router]
  221. );
  222. const handleCancel = () => {
  223. router.back();
  224. };
  225. function chopSalaryPoints(input: string | number): number | null {
  226. if (typeof input === 'string') {
  227. const match = input.match(/(\d+) \((\d+) - (\d+)\)/);
  228. if (match) {
  229. return parseInt(match[1], 10);
  230. }
  231. } else if (typeof input === 'number') {
  232. return input;
  233. }
  234. return null;
  235. }
  236. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  237. (_e, newValue) => {
  238. setTabIndex(newValue);
  239. },
  240. []
  241. );
  242. return (
  243. <>
  244. <FormProvider {...formProps}>
  245. <Stack
  246. spacing={2}
  247. component="form"
  248. onSubmit={formProps.handleSubmit(onSubmit)}
  249. >
  250. {serverError && (
  251. <Typography variant="body2" color="error" alignSelf="flex-end">
  252. {serverError}
  253. </Typography>
  254. )}
  255. <Stack
  256. direction="row"
  257. justifyContent="space-between"
  258. flexWrap="wrap"
  259. rowGap={2}
  260. >
  261. <Tabs
  262. value={tabIndex}
  263. onChange={handleTabChange}
  264. variant="scrollable"
  265. >
  266. <Tab label={t("Info")}/>
  267. <Tab label={t("Info History")} />
  268. <Tab label={t("Involved Project History")} />
  269. </Tabs>
  270. </Stack>
  271. {tabIndex == 0 && Staff && <StaffInfo combos={combos} />}
  272. {tabIndex == 2 && <ProjectHistory InvolvedProject={InvolvedProject}/>}
  273. {tabIndex == 0 &&
  274. <Stack direction="row" justifyContent="flex-end" gap={1}>
  275. <Button
  276. variant="text"
  277. startIcon={<RestartAlt />}
  278. onClick={()=> window.location.reload()}
  279. >
  280. {t("Reset")}
  281. </Button>
  282. <Button
  283. variant="outlined"
  284. startIcon={<Close />}
  285. onClick={handleCancel}
  286. >
  287. {t("Cancel")}
  288. </Button>
  289. <Button
  290. variant="contained"
  291. startIcon={<Check />}
  292. type="submit"
  293. // disabled={Boolean(formProps.watch("isGridEditing"))}
  294. >
  295. {t("Confirm")}
  296. </Button>
  297. </Stack>
  298. }
  299. </Stack>
  300. </FormProvider>
  301. </>
  302. );
  303. };
  304. export default EditStaff;