| @@ -15,25 +15,25 @@ export interface CreateCustomInputs { | |||
| export interface CreateStaffInputs { | |||
| name: string; | |||
| currentPositionId: number; | |||
| joinPositionId: number; | |||
| staffId: string; | |||
| companyId: number; | |||
| gradeId: number; | |||
| teamId: number; | |||
| salaryId: number; | |||
| email: string; | |||
| skillSetId?: number[]; | |||
| joinDate: string; | |||
| currentPositionId: number; | |||
| joinPositionId: number; | |||
| gradeId?: number; | |||
| teamId?: number | |||
| departmentId: number; | |||
| phone1: string; | |||
| phone2: string; | |||
| hourlyRate: string | number; | |||
| phone2?: string; | |||
| email: string; | |||
| emergContactName: string; | |||
| emergContactPhone: string; | |||
| employType: string; | |||
| joinDate: string | null; | |||
| departDate?: string | null; | |||
| departReason?: string | null; | |||
| remark?: string | null; | |||
| staffId: string | null; | |||
| skillSetId?: number[] | number | null | undefined | |||
| departDate?: string; | |||
| departReason?: string; | |||
| remark?: string; | |||
| } | |||
| export interface records { | |||
| @@ -53,8 +53,8 @@ const ChangePassword: React.FC = () => { | |||
| password: data.password, | |||
| newPassword: data.newPassword | |||
| } | |||
| // await changePassword(postData) | |||
| // router.replace("/home") | |||
| await changePassword(postData) | |||
| router.replace("/home") | |||
| } catch (e) { | |||
| console.log(e) | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| @@ -11,7 +11,7 @@ import { | |||
| useForm, | |||
| } from "react-hook-form"; | |||
| import { CreateStaffInputs, saveStaff, testing } from "@/app/api/staff/actions"; | |||
| import { Typography } from "@mui/material"; | |||
| import { Button, Stack, Typography } from "@mui/material"; | |||
| import CreateStaffForm from "../CreateStaffForm"; | |||
| import { comboProp, fetchCompanyCombo } from "@/app/api/companys/actions"; | |||
| import { fetchTeamCombo } from "@/app/api/team/actions"; | |||
| @@ -20,6 +20,8 @@ import { fetchPositionCombo } from "@/app/api/positions/actions"; | |||
| import { fetchGradeCombo } from "@/app/api/grades/actions"; | |||
| import { fetchSkillCombo } from "@/app/api/skill/actions"; | |||
| import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | |||
| import StaffInfo from "./StaffInfo"; | |||
| import { Check, Close } from "@mui/icons-material"; | |||
| interface Field { | |||
| id: string; | |||
| @@ -43,171 +45,133 @@ export interface comboItem { | |||
| } | |||
| interface formProps { | |||
| Title?: string[]; | |||
| // Title?: string[]; | |||
| combos: comboItem; | |||
| } | |||
| const CreateStaff: React.FC<formProps> = ({ Title, combos }) => { | |||
| const CreateStaff: React.FC<formProps> = ({ combos }) => { | |||
| const { t } = useTranslation(); | |||
| const formProps = useForm<CreateStaffInputs>(); | |||
| const [serverError, setServerError] = useState(""); | |||
| const router = useRouter(); | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const errors = formProps.formState.errors; | |||
| const onSubmit = useCallback<SubmitHandler<CreateStaffInputs>>( | |||
| async (data) => { | |||
| try { | |||
| console.log(data); | |||
| let haveError = false; | |||
| let regex_email = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/ | |||
| let regex_phone = /^\d{8}$/ | |||
| if (!regex_email.test(data.email)) { | |||
| haveError = true | |||
| formProps.setError("email", { message: t("Please Enter Correct Email."), type: "required" }) | |||
| } | |||
| if(!regex_phone.test(data.phone1)) { | |||
| haveError = true | |||
| formProps.setError("phone1", { message: t("Please Enter Correct Phone No.."), type: "required" }) | |||
| } | |||
| if(!regex_phone.test(data.emergContactPhone)) { | |||
| haveError = true | |||
| formProps.setError("emergContactPhone", { message: t("Please Enter Correct Phone No.."), type: "required" }) | |||
| } | |||
| if (data.phone2 && data.phone2?.length > 0) { | |||
| if(!regex_phone.test(data.phone2)) { | |||
| haveError = true | |||
| formProps.setError("phone2", { message: t("Please Enter Correct Phone No.."), type: "required" }) | |||
| } | |||
| } | |||
| if (!regex_email.test(data.email)) { | |||
| haveError = true | |||
| formProps.setError("email", { message: t("Please Enter Correct Email."), type: "required" }) | |||
| } | |||
| if (!data.companyId) { | |||
| haveError = true | |||
| formProps.setError("companyId", { message: t("Please Enter Company."), type: "required" }) | |||
| } | |||
| if (!data.gradeId) { | |||
| haveError = true | |||
| formProps.setError("gradeId", { message: t("Please Enter grade."), type: "required" }) | |||
| } | |||
| if (!data.employType) { | |||
| haveError = true | |||
| formProps.setError("employType", { message: t("Please Enter Employ Type."), type: "required" }) | |||
| } | |||
| if (!data.departmentId) { | |||
| haveError = true | |||
| formProps.setError("departmentId", { message: t("Please Enter Department."), type: "required" }) | |||
| } | |||
| if (!data.salaryId) { | |||
| haveError = true | |||
| formProps.setError("salaryId", { message: t("Please Enter Salary."), type: "required" }) | |||
| } | |||
| if (!data.joinDate) { | |||
| haveError = true | |||
| formProps.setError("joinDate", { message: t("Please Enter Join Date."), type: "required" }) | |||
| } | |||
| if (data.departDate && new Date(data.departDate) <= new Date(data.joinDate)) { | |||
| haveError = true | |||
| formProps.setError("departDate", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" }) | |||
| } | |||
| // if (!data.joinPositionId) { | |||
| // haveError = true | |||
| // formProps.setError("joinPositionId", { message: t("Depart Date cannot be earlier than Join Date."), type: "required" }) | |||
| // } | |||
| if (haveError) { | |||
| return | |||
| } | |||
| console.log("passed") | |||
| await saveStaff(data) | |||
| router.replace("/settings/staff") | |||
| } catch (e) { | |||
| console.log(e); | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| } | |||
| }, | |||
| [router] | |||
| ); | |||
| const handleCancel = () => { | |||
| router.back(); | |||
| }; | |||
| const fieldLists: Field[][] = [ | |||
| [ | |||
| { | |||
| id: "staffId", | |||
| label: t("Staff ID"), | |||
| type: "text", | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "name", | |||
| label: t("Staff Name"), | |||
| type: "text", | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "companyId", | |||
| label: t("Company"), | |||
| type: "combo-Obj", | |||
| options: combos.company || [], | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "teamId", | |||
| label: t("Team"), | |||
| type: "combo-Obj", | |||
| options: combos.team || [], | |||
| required: false, | |||
| }, | |||
| { | |||
| id: "departmentId", | |||
| label: t("Department"), | |||
| type: "combo-Obj", | |||
| options: combos.department || [], | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "gradeId", | |||
| label: t("Grade"), | |||
| type: "combo-Obj", | |||
| options: combos.grade || [], | |||
| required: false, | |||
| }, | |||
| { | |||
| id: "skillSetId", | |||
| label: t("Skillset"), | |||
| type: "multiSelect-Obj", | |||
| options: combos.skill || [], | |||
| required: false, | |||
| }, | |||
| { | |||
| id: "currentPositionId", | |||
| label: t("Current Position"), | |||
| type: "combo-Obj", | |||
| options: combos.position || [], | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "salaryId", | |||
| label: t("Salary Point"), | |||
| type: "combo-Obj", | |||
| options: combos.salary || [], | |||
| required: true, | |||
| }, | |||
| // { | |||
| // id: "hourlyRate", | |||
| // label: t("Hourly Rate"), | |||
| // type: "numeric-testing", | |||
| // value: "", | |||
| // required: false, | |||
| // }, | |||
| // { | |||
| // id: "hourlyRate", | |||
| // label: t("Hourly Rate"), | |||
| // type: "numeric-testing", | |||
| // required: true, | |||
| // }, | |||
| { | |||
| id: "employType", | |||
| label: t("Employ Type"), | |||
| type: "combo-Obj", | |||
| options: [{id: "FT", label: t("FT")}, {id: "PT", label: t("PT")}], | |||
| value: "", | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "email", | |||
| label: t("Email"), | |||
| type: "text", | |||
| pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", | |||
| message: t("input matching format"), | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "phone1", | |||
| label: t("Phone1"), | |||
| type: "text", | |||
| pattern: "^\\d{8}$", | |||
| message: t("input correct phone no."), | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "phone2", | |||
| label: t("Phone2"), | |||
| type: "text", | |||
| pattern: "^\\d{8}$", | |||
| message: t("input correct phone no."), | |||
| required: false, | |||
| }, | |||
| ], | |||
| [ | |||
| { | |||
| id: "emergContactName", | |||
| label: t("Emergency Contact Name"), | |||
| type: "text", | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "emergContactPhone", | |||
| label: t("Emergency Contact Phone"), | |||
| type: "text", | |||
| pattern: "^\\d{8}$", | |||
| message: t("input correct phone no."), | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "joinDate", | |||
| label: t("Join Date"), | |||
| type: "multiDate", | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "joinPositionId", | |||
| label: t("Join Position"), | |||
| type: "combo-Obj", | |||
| options: combos.position || [], | |||
| required: true, | |||
| }, | |||
| { | |||
| id: "departDate", | |||
| label: t("Depart Date"), | |||
| type: "multiDate", | |||
| }, | |||
| { | |||
| id: "departReason", | |||
| label: t("Depart Reason"), | |||
| type: "text", | |||
| }, | |||
| { | |||
| id: "remark", | |||
| label: t("Remark"), | |||
| type: "remarks", | |||
| }, | |||
| ] | |||
| ]; | |||
| return ( | |||
| <> | |||
| <CreateStaffForm Title={Title} fieldLists={fieldLists}/> | |||
| <FormProvider {...formProps}> | |||
| <Stack | |||
| spacing={2} | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| {serverError && ( | |||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||
| {serverError} | |||
| </Typography> | |||
| )} | |||
| <StaffInfo combos={combos}/> | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| variant="outlined" | |||
| startIcon={<Close />} | |||
| onClick={handleCancel} | |||
| > | |||
| {t("Cancel")} | |||
| </Button> | |||
| <Button | |||
| variant="contained" | |||
| startIcon={<Check />} | |||
| type="submit" | |||
| // disabled={Boolean(formProps.watch("isGridEditing"))} | |||
| > | |||
| {t("Confirm")} | |||
| </Button> | |||
| </Stack> | |||
| </Stack> | |||
| </FormProvider> | |||
| </> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,514 @@ | |||
| "use client"; | |||
| import Stack from "@mui/material/Stack"; | |||
| import Box from "@mui/material/Box"; | |||
| import Card from "@mui/material/Card"; | |||
| import CardContent from "@mui/material/CardContent"; | |||
| import Grid from "@mui/material/Grid"; | |||
| import TextField from "@mui/material/TextField"; | |||
| import Typography from "@mui/material/Typography"; | |||
| import { CreateGroupInputs } from "@/app/api/group/actions"; | |||
| import { Controller, useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useCallback, useEffect } from "react"; | |||
| import { CreateStaffInputs } from "@/app/api/staff/actions"; | |||
| import { | |||
| Checkbox, | |||
| FormControl, | |||
| InputLabel, | |||
| ListItemText, | |||
| MenuItem, | |||
| Select, | |||
| } from "@mui/material"; | |||
| import { comboItem } from "./CreateStaff"; | |||
| import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { DemoItem } from "@mui/x-date-pickers/internals/demo"; | |||
| import dayjs from "dayjs"; | |||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| interface Props { | |||
| combos: comboItem; | |||
| } | |||
| const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| const { | |||
| t, | |||
| i18n: { language }, | |||
| } = useTranslation(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues }, | |||
| control, | |||
| reset, | |||
| resetField, | |||
| setValue, | |||
| getValues, | |||
| clearErrors | |||
| } = useFormContext<CreateStaffInputs>(); | |||
| const employType = [ | |||
| { id: 1, label: "FT" }, | |||
| { id: 2, label: "PT" }, | |||
| ]; | |||
| const skillIdNameMap = combos.skill.reduce<{ [id: number]: string }>( | |||
| (acc, skill) => ({ ...acc, [skill.id]: skill.label }), | |||
| {} | |||
| ); | |||
| const resetStaff = useCallback(() => { | |||
| console.log(defaultValues); | |||
| if (defaultValues !== undefined) { | |||
| // resetField("description"); | |||
| } | |||
| }, [defaultValues]); | |||
| const joinDate = getValues("joinDate"); | |||
| const departDate = getValues("departDate"); | |||
| useEffect(() => { | |||
| if(joinDate) | |||
| clearErrors("joinDate") | |||
| if(departDate) | |||
| clearErrors("departDate") | |||
| }, [joinDate, departDate]) | |||
| return ( | |||
| <Card sx={{ display: "block" }}> | |||
| <CardContent component={Stack} spacing={4}> | |||
| <Box> | |||
| <Typography variant="overline" display="block" marginBlockEnd={1}> | |||
| {t("Staff")} | |||
| </Typography> | |||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Staff ID")} | |||
| fullWidth | |||
| required | |||
| {...register("staffId", { | |||
| required: "Staff Id required!", | |||
| })} | |||
| error={Boolean(errors.name)} | |||
| helperText={ | |||
| Boolean(errors.name) && | |||
| (errors.name?.message | |||
| ? t(errors.name.message) | |||
| : t("Please input correct staffId")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Staff Name")} | |||
| fullWidth | |||
| required | |||
| {...register("name", { | |||
| required: "Staff Name required!", | |||
| })} | |||
| error={Boolean(errors.name)} | |||
| helperText={ | |||
| Boolean(errors.name) && | |||
| (errors.name?.message | |||
| ? t(errors.name.message) | |||
| : t("Please input correct name")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel required>{t("Company")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="companyId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Company")} | |||
| {...field} | |||
| error={Boolean(errors.companyId)} | |||
| > | |||
| {combos.company.map((company, index) => ( | |||
| <MenuItem | |||
| key={`${company.id}-${index}`} | |||
| value={company.id} | |||
| > | |||
| {t(company.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Team")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="teamId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Team")} | |||
| {...field} | |||
| // error={Boolean(errors.teamId)} | |||
| > | |||
| {combos.team.map((team, index) => ( | |||
| <MenuItem key={`${team.id}-${index}`} value={team.id}> | |||
| {t(team.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Department")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="departmentId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Department")} | |||
| {...field} | |||
| error={Boolean(errors.departmentId)} | |||
| > | |||
| {combos.department.map((department, index) => ( | |||
| <MenuItem | |||
| key={`${department.id}-${index}`} | |||
| value={department.id} | |||
| > | |||
| {t(department.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel required>{t("Grade")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="gradeId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Grade")} | |||
| {...field} | |||
| error={Boolean(errors.gradeId)} | |||
| > | |||
| {combos.grade.map((grade, index) => ( | |||
| <MenuItem key={`${grade.id}-${index}`} value={grade.id}> | |||
| {t(grade.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Skillset")}</InputLabel> | |||
| <Controller | |||
| defaultValue={[]} | |||
| control={control} | |||
| name="skillSetId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| // error={Boolean(errors.skillSetId)} | |||
| renderValue={(types) => | |||
| types.map((type) => skillIdNameMap[type]).join(", ") | |||
| } | |||
| multiple | |||
| label={t("Skillset")} | |||
| {...field} | |||
| > | |||
| {combos.skill.map((skill, index) => { | |||
| // console.log(field) | |||
| return ( | |||
| <MenuItem | |||
| key={`${skill.id}-${index}`} | |||
| value={skill.id} | |||
| > | |||
| <Checkbox | |||
| checked={field.value!.indexOf(skill.id) > -1} | |||
| /> | |||
| <ListItemText primary={skill.label} /> | |||
| </MenuItem> | |||
| ); | |||
| })} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel required>{t("Current Position")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="currentPositionId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Current Position")} | |||
| {...field} | |||
| error={Boolean(errors.currentPositionId)} | |||
| > | |||
| {combos.position.map((position, index) => ( | |||
| <MenuItem | |||
| key={`${position.id}-${index}`} | |||
| value={position.id} | |||
| > | |||
| {t(position.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Salary Point")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="salaryId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Salary Point")} | |||
| {...field} | |||
| error={Boolean(errors.salaryId)} | |||
| > | |||
| {combos.salary.map((salary, index) => ( | |||
| <MenuItem | |||
| key={`${salary.id}-${index}`} | |||
| value={salary.id} | |||
| > | |||
| {t(salary.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Employ Type")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="employType" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Employ Type")} | |||
| {...field} | |||
| error={Boolean(errors.employType)} | |||
| > | |||
| {employType.map((type, index) => ( | |||
| <MenuItem | |||
| key={`${type.id}-${index}`} | |||
| value={type.label} | |||
| > | |||
| {t(type.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Email")} | |||
| fullWidth | |||
| required | |||
| {...register("email", { | |||
| required: "Email required!", | |||
| })} | |||
| error={Boolean(errors.email)} | |||
| helperText={ | |||
| Boolean(errors.email) && | |||
| (errors.email?.message | |||
| ? t(errors.email.message) | |||
| : t("Please input correct email")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Phone1")} | |||
| fullWidth | |||
| required | |||
| {...register("phone1", { | |||
| required: "phone1 required!", | |||
| })} | |||
| error={Boolean(errors.phone1)} | |||
| helperText={ | |||
| Boolean(errors.phone1) && | |||
| (errors.phone1?.message | |||
| ? t(errors.phone1.message) | |||
| : t("Please input correct phone1")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Phone2")} | |||
| fullWidth | |||
| {...register("phone2")} | |||
| error={Boolean(errors.phone2)} | |||
| helperText={ | |||
| Boolean(errors.phone2) && | |||
| (errors.phone2?.message | |||
| ? t(errors.phone2.message) | |||
| : t("Please input correct phone2")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Emergency Contact Name")} | |||
| fullWidth | |||
| required | |||
| {...register("emergContactName", { | |||
| required: "Emergency Contact Name required!", | |||
| })} | |||
| error={Boolean(errors.emergContactName)} | |||
| helperText={ | |||
| Boolean(errors.emergContactName) && | |||
| (errors.emergContactName?.message | |||
| ? t(errors.emergContactName.message) | |||
| : t("Please input correct Emergency Contact Name")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Emergency Contact Phone")} | |||
| fullWidth | |||
| required | |||
| {...register("emergContactPhone", { | |||
| required: "Emergency Contact Phone required!", | |||
| })} | |||
| error={Boolean(errors.emergContactPhone)} | |||
| helperText={ | |||
| Boolean(errors.emergContactPhone) && | |||
| (errors.emergContactPhone?.message | |||
| ? t(errors.emergContactPhone.message) | |||
| : t("Please input correct Emergency Contact Phone")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={`${language}-hk`} | |||
| > | |||
| <DatePicker | |||
| sx={{ width: "100%" }} | |||
| label={t("Join Date")} | |||
| value={joinDate ? dayjs(joinDate) : null} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| setValue("joinDate", date.format(INPUT_DATE_FORMAT)); | |||
| }} | |||
| slotProps={{ | |||
| textField: { | |||
| error: | |||
| joinDate === "Invalid Date" || Boolean(errors.joinDate), | |||
| // value: errors.joinDate?.message, | |||
| }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <FormControl fullWidth> | |||
| <InputLabel>{t("Join Position")}</InputLabel> | |||
| <Controller | |||
| control={control} | |||
| name="joinPositionId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| label={t("Join Position")} | |||
| {...field} | |||
| error={Boolean(errors.joinPositionId)} | |||
| > | |||
| {combos.position.map((position, index) => ( | |||
| <MenuItem | |||
| key={`${position.id}-${index}`} | |||
| value={position.label} | |||
| > | |||
| {t(position.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={`${language}-hk`} | |||
| > | |||
| <DatePicker | |||
| sx={{ width: "100%" }} | |||
| label={t("Depart Date")} | |||
| value={departDate ? dayjs(departDate) : null} | |||
| onChange={(date) => { | |||
| if (!date) return; | |||
| setValue("departDate", date.format(INPUT_DATE_FORMAT)); | |||
| }} | |||
| slotProps={{ | |||
| textField: { | |||
| error: departDate === "Invalid Date", | |||
| // value: errors.departDate?.message, | |||
| }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Depart Reason")} | |||
| fullWidth | |||
| {...register("departReason")} | |||
| error={Boolean(errors.departReason)} | |||
| helperText={ | |||
| Boolean(errors.departReason) && | |||
| (errors.departReason?.message | |||
| ? t(errors.departReason.message) | |||
| : t("Please input correct departReason")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={12}> | |||
| <TextField | |||
| label={t("Remark")} | |||
| fullWidth | |||
| multiline | |||
| rows={4} | |||
| {...register("remark")} | |||
| error={Boolean(errors.remark)} | |||
| helperText={ | |||
| Boolean(errors.remark) && | |||
| (errors.remark?.message | |||
| ? t(errors.remark.message) | |||
| : t("Please input correct remark")) | |||
| } | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Box> | |||
| </CardContent> | |||
| </Card> | |||
| ); | |||
| }; | |||
| export default StaffInfo; | |||
| @@ -1,120 +1,120 @@ | |||
| import { useCallback, useState } from "react"; | |||
| import CustomInputForm from "../CustomInputForm"; | |||
| import { useRouter } from "next/navigation"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { | |||
| FieldErrors, | |||
| FormProvider, | |||
| SubmitErrorHandler, | |||
| SubmitHandler, | |||
| useForm, | |||
| } from "react-hook-form"; | |||
| import { CreateStaffInputs, saveStaff, testing } from "@/app/api/staff/actions"; | |||
| import { Typography } from "@mui/material"; | |||
| // import { useCallback, useState } from "react"; | |||
| // import CustomInputForm from "../CustomInputForm"; | |||
| // import { useRouter } from "next/navigation"; | |||
| // import { useTranslation } from "react-i18next"; | |||
| // import { | |||
| // FieldErrors, | |||
| // FormProvider, | |||
| // SubmitErrorHandler, | |||
| // SubmitHandler, | |||
| // useForm, | |||
| // } from "react-hook-form"; | |||
| // import { CreateStaffInputs, saveStaff, testing } from "@/app/api/staff/actions"; | |||
| // import { Typography } from "@mui/material"; | |||
| interface Field { | |||
| // subtitle: string; | |||
| id: string; | |||
| label: string; | |||
| type: string; | |||
| value?: any; | |||
| required?: boolean; | |||
| options?: any[]; | |||
| readOnly?: boolean; | |||
| } | |||
| // interface Field { | |||
| // // subtitle: string; | |||
| // id: string; | |||
| // label: string; | |||
| // type: string; | |||
| // value?: any; | |||
| // required?: boolean; | |||
| // options?: any[]; | |||
| // readOnly?: boolean; | |||
| // } | |||
| interface formProps { | |||
| Title?: string[]; | |||
| fieldLists: Field[][]; | |||
| } | |||
| // interface formProps { | |||
| // Title?: string[]; | |||
| // fieldLists: Field[][]; | |||
| // } | |||
| const CreateStaffForm: React.FC<formProps> = ({ Title, fieldLists }) => { | |||
| const router = useRouter(); | |||
| const { t } = useTranslation(); | |||
| const [serverError, setServerError] = useState(""); | |||
| // const CreateStaffForm: React.FC<formProps> = ({ Title, fieldLists }) => { | |||
| // const router = useRouter(); | |||
| // const { t } = useTranslation(); | |||
| // const [serverError, setServerError] = useState(""); | |||
| const handleCancel = () => { | |||
| router.back(); | |||
| }; | |||
| const onSubmit = useCallback<SubmitHandler<CreateStaffInputs>>( | |||
| async (data) => { | |||
| try { | |||
| console.log(data); | |||
| let haveError = false; | |||
| //check if joinDate exist | |||
| if (data.joinDate == null && data.joinDate == "Invalid Date") { | |||
| haveError = true; | |||
| return haveError; | |||
| } | |||
| //check if joinDate > departDate | |||
| if (data.departDate != null && data.departDate != "Invalid Date" && data.departDate.length != 0) { | |||
| if (data.joinDate != null) { | |||
| const joinDate = new Date(data.joinDate); | |||
| const departDate = new Date(data.departDate); | |||
| if (joinDate.getTime() > departDate.getTime()) { | |||
| haveError = true; | |||
| return haveError; | |||
| } | |||
| } | |||
| if (data.departReason == null || data.departReason.length == 0) { | |||
| haveError = true; | |||
| return haveError; | |||
| } | |||
| } | |||
| // const handleCancel = () => { | |||
| // router.back(); | |||
| // }; | |||
| // const onSubmit = useCallback<SubmitHandler<CreateStaffInputs>>( | |||
| // async (data) => { | |||
| // try { | |||
| // console.log(data); | |||
| // let haveError = false; | |||
| // //check if joinDate exist | |||
| // if (data.joinDate == null && data.joinDate == "Invalid Date") { | |||
| // haveError = true; | |||
| // return haveError; | |||
| // } | |||
| // //check if joinDate > departDate | |||
| // if (data.departDate != null && data.departDate != "Invalid Date" && data.departDate.length != 0) { | |||
| // if (data.joinDate != null) { | |||
| // const joinDate = new Date(data.joinDate); | |||
| // const departDate = new Date(data.departDate); | |||
| // if (joinDate.getTime() > departDate.getTime()) { | |||
| // haveError = true; | |||
| // return haveError; | |||
| // } | |||
| // } | |||
| // if (data.departReason == null || data.departReason.length == 0) { | |||
| // haveError = true; | |||
| // return haveError; | |||
| // } | |||
| // } | |||
| if (haveError) { | |||
| return | |||
| } | |||
| const postData = { | |||
| ...data, | |||
| skillSetId: typeof data.skillSetId === "number" ? [data.skillSetId] : data.skillSetId, | |||
| emergContactPhone: data.emergContactPhone.toString(), | |||
| phone1: data.phone1.toString(), | |||
| phone2: data.phone2.toString(), | |||
| hourlyRate: typeof data.hourlyRate === 'string' ? parseInt(data.hourlyRate.replace("$", "").replace(",", "")) : 0 | |||
| }; | |||
| if (postData.departDate?.length === 0 && postData.departReason?.length === 0) { | |||
| delete postData.departDate; | |||
| delete postData.departReason; | |||
| } | |||
| if (postData.remark?.length === 0) { | |||
| delete postData.remark; | |||
| } | |||
| console.log(postData); | |||
| setServerError(""); | |||
| await saveStaff(postData); | |||
| router.replace("/settings/staff"); | |||
| } catch (e) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| } | |||
| }, | |||
| [router, t] | |||
| ); | |||
| // if (haveError) { | |||
| // return | |||
| // } | |||
| // const postData = { | |||
| // ...data, | |||
| // skillSetId: typeof data.skillSetId === "number" ? [data.skillSetId] : data.skillSetId, | |||
| // emergContactPhone: data.emergContactPhone.toString(), | |||
| // phone1: data.phone1.toString(), | |||
| // phone2: data.phone2.toString(), | |||
| // hourlyRate: typeof data.hourlyRate === 'string' ? parseInt(data.hourlyRate.replace("$", "").replace(",", "")) : 0 | |||
| // }; | |||
| // if (postData.departDate?.length === 0 && postData.departReason?.length === 0) { | |||
| // delete postData.departDate; | |||
| // delete postData.departReason; | |||
| // } | |||
| // if (postData.remark?.length === 0) { | |||
| // delete postData.remark; | |||
| // } | |||
| // console.log(postData); | |||
| // setServerError(""); | |||
| // await saveStaff(postData); | |||
| // router.replace("/settings/staff"); | |||
| // } catch (e) { | |||
| // setServerError(t("An error has occurred. Please try again later.")); | |||
| // } | |||
| // }, | |||
| // [router, t] | |||
| // ); | |||
| const onSubmitError = useCallback<SubmitErrorHandler<CreateStaffInputs>>( | |||
| (errors) => { | |||
| console.log(errors); | |||
| }, | |||
| [] | |||
| ); | |||
| // const onSubmitError = useCallback<SubmitErrorHandler<CreateStaffInputs>>( | |||
| // (errors) => { | |||
| // console.log(errors); | |||
| // }, | |||
| // [] | |||
| // ); | |||
| return ( | |||
| <> | |||
| {serverError && ( | |||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||
| {serverError} | |||
| </Typography> | |||
| )} | |||
| <CustomInputForm | |||
| Title={Title} | |||
| fieldLists={fieldLists} | |||
| isActive={true} | |||
| onSubmit={onSubmit} | |||
| onSubmitError={onSubmitError} | |||
| onCancel={handleCancel} | |||
| /> | |||
| </> | |||
| ); | |||
| }; | |||
| // return ( | |||
| // <> | |||
| // {serverError && ( | |||
| // <Typography variant="body2" color="error" alignSelf="flex-end"> | |||
| // {serverError} | |||
| // </Typography> | |||
| // )} | |||
| // <CustomInputForm | |||
| // Title={Title} | |||
| // fieldLists={fieldLists} | |||
| // isActive={true} | |||
| // onSubmit={onSubmit} | |||
| // onSubmitError={onSubmitError} | |||
| // onCancel={handleCancel} | |||
| // /> | |||
| // </> | |||
| // ); | |||
| // }; | |||
| export default CreateStaffForm; | |||
| // export default CreateStaffForm; | |||