您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

EditStaff.tsx 12 KiB

1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
1年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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;