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

StaffInfo.tsx 20 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年前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. "use client";
  2. import Stack from "@mui/material/Stack";
  3. import Box from "@mui/material/Box";
  4. import Card from "@mui/material/Card";
  5. import CardContent from "@mui/material/CardContent";
  6. import Grid from "@mui/material/Grid";
  7. import TextField from "@mui/material/TextField";
  8. import Typography from "@mui/material/Typography";
  9. import { CreateGroupInputs } from "@/app/api/group/actions";
  10. import { Controller, useFormContext } from "react-hook-form";
  11. import { useTranslation } from "react-i18next";
  12. import { useCallback, useEffect, useMemo, useState } from "react";
  13. import { CreateStaffInputs } from "@/app/api/staff/actions";
  14. import {
  15. Button,
  16. Checkbox,
  17. FormControl,
  18. InputLabel,
  19. ListItemText,
  20. MenuItem,
  21. Select,
  22. } from "@mui/material";
  23. import { comboItem } from "./EditStaff";
  24. import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
  25. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  26. import { DemoItem } from "@mui/x-date-pickers/internals/demo";
  27. import dayjs from "dayjs";
  28. import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  29. import SalaryEffectiveModel from "./SalaryEffectiveModel";
  30. import { SalaryEffectiveInfo } from "@/app/api/staff";
  31. interface Props {
  32. combos: comboItem;
  33. }
  34. const StaffInfo: React.FC<Props> = ({ combos }) => {
  35. const {
  36. t,
  37. i18n: { language },
  38. } = useTranslation();
  39. const {
  40. register,
  41. formState: { errors, defaultValues },
  42. control,
  43. reset,
  44. resetField,
  45. setValue,
  46. getValues,
  47. watch,
  48. clearErrors,
  49. } = useFormContext<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] }>();
  50. const employType = [
  51. { id: 1, label: "FT" },
  52. { id: 2, label: "PT" },
  53. ];
  54. const skillIdNameMap = combos.skill.reduce<{ [id: number]: string }>(
  55. (acc, skill) => ({ ...acc, [skill.id]: skill.label }),
  56. {}
  57. );
  58. // Salary Effiective History edit modal related
  59. const [salaryEffectiveModelOpen, setSalaaryEffectiveModelOpen] = useState(false);
  60. const closeSalaryEffectiveModel = useCallback(() => {
  61. setSalaaryEffectiveModelOpen(false);
  62. }, []);
  63. const openSalaryEffectiveModel = useCallback(() => {
  64. setSalaaryEffectiveModelOpen(true);
  65. }, []);
  66. const onSalaryEffectiveSave = useCallback(async () => {
  67. console.log(getValues())
  68. setSalaaryEffectiveModelOpen(false);
  69. }, []);
  70. const resetStaff = useCallback(() => {
  71. console.log(defaultValues);
  72. if (defaultValues !== undefined) {
  73. resetField("joinDate");
  74. resetField("departDate");
  75. }
  76. }, [defaultValues]);
  77. useEffect(() => {
  78. resetStaff()
  79. }, [defaultValues]);
  80. const joinDate = watch("joinDate");
  81. const departDate = watch("departDate");
  82. useEffect(() => {
  83. if (joinDate) clearErrors("joinDate");
  84. if (departDate) clearErrors("departDate");
  85. }, [joinDate, departDate]);
  86. const salaryCols = useMemo(
  87. () => [
  88. {
  89. field: 'salaryPoint',
  90. headerName: 'salaryPoint',
  91. flex: 1,
  92. editable: true,
  93. type: 'singleSelect',
  94. valueOptions: combos?.salary.map(item => item.label),
  95. // valueOptions: [],
  96. // width: 150
  97. },
  98. {
  99. field: 'date',
  100. headerName: 'date',
  101. flex: 1,
  102. editable: true,
  103. type: 'date',
  104. // width: 150
  105. },
  106. ], [combos])
  107. return (
  108. <Card sx={{ display: "block" }}>
  109. <CardContent component={Stack} spacing={4}>
  110. <Box>
  111. <Typography variant="overline" display="block" marginBlockEnd={1}>
  112. {t("Staff")}
  113. </Typography>
  114. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
  115. <Grid item xs={6}>
  116. <TextField
  117. label={t("Staff ID")}
  118. fullWidth
  119. required
  120. {...register("staffId", {
  121. required: "Staff Id required!",
  122. })}
  123. error={Boolean(errors.name)}
  124. helperText={
  125. Boolean(errors.name) &&
  126. (errors.name?.message
  127. ? t(errors.name.message)
  128. : t("Please input correct staffId"))
  129. }
  130. />
  131. </Grid>
  132. <Grid item xs={6}>
  133. <TextField
  134. label={t("Staff Name")}
  135. fullWidth
  136. required
  137. {...register("name", {
  138. required: "Staff Name required!",
  139. })}
  140. error={Boolean(errors.name)}
  141. helperText={
  142. Boolean(errors.name) &&
  143. (errors.name?.message
  144. ? t(errors.name.message)
  145. : t("Please input correct name"))
  146. }
  147. />
  148. </Grid>
  149. <Grid item xs={6}>
  150. <FormControl fullWidth>
  151. <InputLabel required>{t("Company")}</InputLabel>
  152. <Controller
  153. control={control}
  154. name="companyId"
  155. render={({ field }) => (
  156. <Select
  157. label={t("Company")}
  158. {...field}
  159. error={Boolean(errors.companyId)}
  160. >
  161. {combos.company.map((company, index) => (
  162. <MenuItem
  163. key={`${company.id}-${index}`}
  164. value={company.id}
  165. >
  166. {t(company.label)}
  167. </MenuItem>
  168. ))}
  169. </Select>
  170. )}
  171. />
  172. </FormControl>
  173. </Grid>
  174. <Grid item xs={6}>
  175. <FormControl fullWidth>
  176. <InputLabel>{t("Team")}</InputLabel>
  177. <Controller
  178. control={control}
  179. name="teamId"
  180. render={({ field }) => (
  181. <Select
  182. label={t("Team")}
  183. {...field}
  184. // error={Boolean(errors.teamId)}
  185. >
  186. {combos.team.map((team, index) => (
  187. <MenuItem key={`${team.id}-${index}`} value={team.id}>
  188. {t(team.label)}
  189. </MenuItem>
  190. ))}
  191. </Select>
  192. )}
  193. />
  194. </FormControl>
  195. </Grid>
  196. <Grid item xs={6}>
  197. <FormControl fullWidth>
  198. <InputLabel>{t("Department")}</InputLabel>
  199. <Controller
  200. control={control}
  201. name="departmentId"
  202. render={({ field }) => (
  203. <Select
  204. label={t("Department")}
  205. {...field}
  206. // error={Boolean(errors.departmentId)}
  207. >
  208. {combos.department.map((department, index) => (
  209. <MenuItem
  210. key={`${department.id}-${index}`}
  211. value={department.id}
  212. >
  213. {t(department.label)}
  214. </MenuItem>
  215. ))}
  216. </Select>
  217. )}
  218. />
  219. </FormControl>
  220. </Grid>
  221. <Grid item xs={6}>
  222. <FormControl fullWidth>
  223. <InputLabel>{t("Grade")}</InputLabel>
  224. <Controller
  225. control={control}
  226. name="gradeId"
  227. render={({ field }) => (
  228. <Select
  229. label={t("Grade")}
  230. {...field}
  231. error={Boolean(errors.gradeId)}
  232. >
  233. {combos.grade.map((grade, index) => (
  234. <MenuItem key={`${grade.id}-${index}`} value={grade.id}>
  235. {t(grade.label)}
  236. </MenuItem>
  237. ))}
  238. </Select>
  239. )}
  240. />
  241. </FormControl>
  242. </Grid>
  243. <Grid item xs={6}>
  244. <FormControl fullWidth>
  245. <InputLabel>{t("Skillset")}</InputLabel>
  246. <Controller
  247. defaultValue={[]}
  248. control={control}
  249. name="skillSetId"
  250. render={({ field }) => (
  251. <Select
  252. // error={Boolean(errors.skillSetId)}
  253. renderValue={(types) =>
  254. types.map((type) => skillIdNameMap[type]).join(", ")
  255. }
  256. multiple
  257. label={t("Skillset")}
  258. {...field}
  259. >
  260. {combos.skill.map((skill, index) => {
  261. // console.log(field)
  262. return (
  263. <MenuItem
  264. key={`${skill.id}-${index}`}
  265. value={skill.id}
  266. >
  267. <Checkbox
  268. checked={field.value!.indexOf(skill.id) > -1}
  269. />
  270. <ListItemText primary={skill.label} />
  271. </MenuItem>
  272. );
  273. })}
  274. </Select>
  275. )}
  276. />
  277. </FormControl>
  278. </Grid>
  279. <Grid item xs={6}>
  280. <FormControl fullWidth>
  281. <InputLabel required>{t("Current Position")}</InputLabel>
  282. <Controller
  283. control={control}
  284. name="currentPositionId"
  285. render={({ field }) => (
  286. <Select
  287. label={t("Current Position")}
  288. {...field}
  289. error={Boolean(errors.currentPositionId)}
  290. >
  291. {combos.position.map((position, index) => (
  292. <MenuItem
  293. key={`${position.id}-${index}`}
  294. value={position.id}
  295. >
  296. {t(position.label)}
  297. </MenuItem>
  298. ))}
  299. </Select>
  300. )}
  301. />
  302. </FormControl>
  303. </Grid>
  304. <Grid item xs={6}>
  305. <FormControl fullWidth>
  306. <InputLabel required>{t("Salary Point")}</InputLabel>
  307. <Controller
  308. control={control}
  309. name="salaryId"
  310. render={({ field }) => (
  311. <Box display="flex" justifyContent="space-between" alignItems="center">
  312. <Select
  313. label={t("Salary Point")}
  314. {...field}
  315. error={Boolean(errors.salaryId)}
  316. style={{ flex: 1, marginRight: '8px' }}
  317. disabled
  318. >
  319. {combos.salary.map((salary, index) => (
  320. <MenuItem
  321. key={`${salary.id}-${index}`}
  322. value={salary.id}
  323. >
  324. {t(salary.label)}
  325. </MenuItem>
  326. ))}
  327. </Select>
  328. <Button variant="contained" size="small" onClick={openSalaryEffectiveModel}>
  329. {t("Edit")}
  330. </Button>
  331. </Box>
  332. )}
  333. />
  334. </FormControl>
  335. </Grid>
  336. <Grid item xs={6}>
  337. <FormControl fullWidth>
  338. <InputLabel required>{t("Employ Type")}</InputLabel>
  339. <Controller
  340. control={control}
  341. name="employType"
  342. render={({ field }) => (
  343. <Select
  344. label={t("Employ Type")}
  345. {...field}
  346. error={Boolean(errors.employType)}
  347. >
  348. {employType.map((type, index) => (
  349. <MenuItem
  350. key={`${type.id}-${index}`}
  351. value={type.label}
  352. >
  353. {t(type.label)}
  354. </MenuItem>
  355. ))}
  356. </Select>
  357. )}
  358. />
  359. </FormControl>
  360. </Grid>
  361. <Grid item xs={6}>
  362. <TextField
  363. label={t("Email")}
  364. fullWidth
  365. required
  366. {...register("email", {
  367. required: "Email required!",
  368. })}
  369. error={Boolean(errors.email)}
  370. helperText={
  371. Boolean(errors.email) &&
  372. (errors.email?.message
  373. ? t(errors.email.message)
  374. : t("Please input correct email"))
  375. }
  376. />
  377. </Grid>
  378. <Grid item xs={6}>
  379. <TextField
  380. label={t("Phone1")}
  381. fullWidth
  382. required
  383. {...register("phone1", {
  384. required: "phone1 required!",
  385. })}
  386. error={Boolean(errors.phone1)}
  387. helperText={
  388. Boolean(errors.phone1) &&
  389. (errors.phone1?.message
  390. ? t(errors.phone1.message)
  391. : t("Please input correct phone1"))
  392. }
  393. />
  394. </Grid>
  395. <Grid item xs={6}>
  396. <TextField
  397. label={t("Phone2")}
  398. fullWidth
  399. {...register("phone2")}
  400. error={Boolean(errors.phone2)}
  401. helperText={
  402. Boolean(errors.phone2) &&
  403. (errors.phone2?.message
  404. ? t(errors.phone2.message)
  405. : t("Please input correct phone2"))
  406. }
  407. />
  408. </Grid>
  409. </Grid>
  410. <Grid container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}>
  411. <Grid item xs={6}>
  412. <TextField
  413. label={t("Emergency Contact Name")}
  414. fullWidth
  415. {...register("emergContactName"
  416. // , {
  417. // required: "Emergency Contact Name required!",
  418. // }
  419. )}
  420. // error={Boolean(errors.emergContactName)}
  421. helperText={
  422. Boolean(errors.emergContactName) &&
  423. (errors.emergContactName?.message
  424. ? t(errors.emergContactName.message)
  425. : t("Please input correct Emergency Contact Name"))
  426. }
  427. />
  428. </Grid>
  429. <Grid item xs={6}>
  430. <TextField
  431. label={t("Emergency Contact Phone")}
  432. fullWidth
  433. {...register("emergContactPhone"
  434. // , {
  435. // required: "Emergency Contact Phone required!",
  436. // }
  437. )}
  438. // error={Boolean(errors.emergContactPhone)}
  439. helperText={
  440. Boolean(errors.emergContactPhone) &&
  441. (errors.emergContactPhone?.message
  442. ? t(errors.emergContactPhone.message)
  443. : t("Please input correct Emergency Contact Phone"))
  444. }
  445. />
  446. </Grid>
  447. <Grid item xs={6}>
  448. <LocalizationProvider
  449. dateAdapter={AdapterDayjs}
  450. adapterLocale={`${language}-hk`}
  451. >
  452. <DatePicker
  453. sx={{ width: "100%" }}
  454. label={t("Join Date")}
  455. // defaultValue={dayjs(getValues("joinDate"))}
  456. value={joinDate ? dayjs(joinDate) : null}
  457. onChange={(date) => {
  458. if (!date) return;
  459. setValue("joinDate", date.format(INPUT_DATE_FORMAT));
  460. }}
  461. slotProps={{
  462. textField: {
  463. // required: true,
  464. error:
  465. joinDate === "Invalid Date",
  466. // value: errors.joinDate?.message,
  467. },
  468. }}
  469. />
  470. </LocalizationProvider>
  471. </Grid>
  472. <Grid item xs={6}>
  473. <FormControl fullWidth>
  474. <InputLabel>{t("Join Position")}</InputLabel>
  475. <Controller
  476. control={control}
  477. name="joinPositionId"
  478. render={({ field }) => (
  479. <Select
  480. label={t("Join Position")}
  481. {...field}
  482. error={Boolean(errors.joinPositionId)}
  483. >
  484. {combos.position.map((position, index) => (
  485. <MenuItem
  486. key={`${position.id}-${index}`}
  487. value={position.id}
  488. >
  489. {t(position.label)}
  490. </MenuItem>
  491. ))}
  492. </Select>
  493. )}
  494. />
  495. </FormControl>
  496. </Grid>
  497. <Grid item xs={6}>
  498. <LocalizationProvider
  499. dateAdapter={AdapterDayjs}
  500. adapterLocale={`${language}-hk`}
  501. >
  502. <DatePicker
  503. sx={{ width: "100%" }}
  504. label={t("Depart Date")}
  505. value={departDate ? dayjs(departDate) : null}
  506. onChange={(date) => {
  507. if (!date) return;
  508. setValue("departDate", date.format(INPUT_DATE_FORMAT));
  509. }}
  510. slotProps={{
  511. textField: {
  512. error: joinDate && departDate
  513. ? new Date(joinDate) > new Date(departDate)
  514. : false,
  515. },
  516. }}
  517. />
  518. </LocalizationProvider>
  519. </Grid>
  520. <Grid item xs={6}>
  521. <TextField
  522. label={t("Depart Reason")}
  523. fullWidth
  524. {...register("departReason")}
  525. error={Boolean(errors.departReason)}
  526. helperText={
  527. Boolean(errors.departReason) &&
  528. (errors.departReason?.message
  529. ? t(errors.departReason.message)
  530. : t("Please input correct departReason"))
  531. }
  532. />
  533. </Grid>
  534. <Grid item xs={12}>
  535. <TextField
  536. label={t("Remark")}
  537. fullWidth
  538. multiline
  539. rows={4}
  540. {...register("remark")}
  541. error={Boolean(errors.remark)}
  542. helperText={
  543. Boolean(errors.remark) &&
  544. (errors.remark?.message
  545. ? t(errors.remark.message)
  546. : t("Please input correct remark"))
  547. }
  548. />
  549. </Grid>
  550. </Grid>
  551. </Box>
  552. </CardContent>
  553. <SalaryEffectiveModel
  554. open={salaryEffectiveModelOpen}
  555. onClose={closeSalaryEffectiveModel}
  556. onSave={onSalaryEffectiveSave}
  557. columns={salaryCols}
  558. />
  559. </Card>
  560. );
  561. };
  562. export default StaffInfo;