| @@ -1,5 +1,4 @@ | |||||
| import { Edit } from "@mui/icons-material"; | import { Edit } from "@mui/icons-material"; | ||||
| import { useSearchParams } from "next/navigation"; | |||||
| // import EditStaff from "@/components/EditStaff"; | // import EditStaff from "@/components/EditStaff"; | ||||
| import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
| import { I18nProvider, getServerI18n } from "@/i18n"; | import { I18nProvider, getServerI18n } from "@/i18n"; | ||||
| @@ -8,10 +7,7 @@ import { Metadata } from "next"; | |||||
| import EditSkill from "@/components/EditSkill"; | import EditSkill from "@/components/EditSkill"; | ||||
| import { Typography } from "@mui/material"; | import { Typography } from "@mui/material"; | ||||
| import { fetchSkill } from "@/app/api/skill"; | import { fetchSkill } from "@/app/api/skill"; | ||||
| export interface searchParamsProps { | |||||
| searchParams: { [key: string]: string | string[] | undefined }; | |||||
| } | |||||
| import { searchParamsProps } from "@/app/utils/fetchUtil"; | |||||
| const EditSkillPage: React.FC<searchParamsProps> = async ({ | const EditSkillPage: React.FC<searchParamsProps> = async ({ | ||||
| searchParams, | searchParams, | ||||
| @@ -6,12 +6,15 @@ import { Suspense } from "react"; | |||||
| import { I18nProvider } from "@/i18n"; | import { I18nProvider } from "@/i18n"; | ||||
| import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; | import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; | ||||
| import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
| import { searchParamsProps } from "@/app/utils/fetchUtil"; | |||||
| // export const metadata: Metadata = { | // export const metadata: Metadata = { | ||||
| // title: "staff-edit", | // title: "staff-edit", | ||||
| // }; | // }; | ||||
| const EditStaffPage: React.FC = () => { | |||||
| const EditStaffPage: React.FC<searchParamsProps> = async ({ | |||||
| searchParams, | |||||
| }) => { | |||||
| // const searchParams = useSearchParams(); | // const searchParams = useSearchParams(); | ||||
| // const userId = searchParams.get('param'); | // const userId = searchParams.get('param'); | ||||
| // console.log(userId); // Access the value of the "user_id" parameter | // console.log(userId); // Access the value of the "user_id" parameter | ||||
| @@ -20,7 +23,7 @@ const EditStaffPage: React.FC = () => { | |||||
| <> | <> | ||||
| <I18nProvider namespaces={["staff", "common"]}> | <I18nProvider namespaces={["staff", "common"]}> | ||||
| <Suspense fallback={<EditStaff.Loading />}> | <Suspense fallback={<EditStaff.Loading />}> | ||||
| <EditStaff /> | |||||
| <EditStaff id={parseInt(searchParams.id as string)}/> | |||||
| </Suspense> | </Suspense> | ||||
| </I18nProvider> | </I18nProvider> | ||||
| {/* <EditStaff /> */} | {/* <EditStaff /> */} | ||||
| @@ -26,6 +26,35 @@ export interface StaffTeamTable { | |||||
| currentPosition: string; | currentPosition: string; | ||||
| } | } | ||||
| export type IndivStaff = { | |||||
| data: IndividualStaff | |||||
| } | |||||
| export type IndividualStaff = { | |||||
| id: number | |||||
| staffId: string | |||||
| name: string | |||||
| company: data | |||||
| team: data | |||||
| department: data | |||||
| grade: data | |||||
| skill: data | |||||
| skillset: any | |||||
| currentPosition: data | |||||
| salary: data | |||||
| employType: string | |||||
| email: string | |||||
| phone1: string | |||||
| phone2?: string | |||||
| emergContactName: string; | |||||
| emergContactPhone: string; | |||||
| joinDate: string; | |||||
| joinPosition: data; | |||||
| departDate?: string; | |||||
| departReason?: string; | |||||
| remark?: string; | |||||
| } | |||||
| export interface StaffResult { | export interface StaffResult { | ||||
| action: any; | action: any; | ||||
| id: number; | id: number; | ||||
| @@ -35,10 +64,11 @@ export interface StaffResult { | |||||
| grade: string; | grade: string; | ||||
| joinPosition: string; | joinPosition: string; | ||||
| currentPosition: string; | currentPosition: string; | ||||
| data: data; | |||||
| teamId: number; | teamId: number; | ||||
| staffName: string; | staffName: string; | ||||
| userId: number; | userId: number; | ||||
| companyId: number; | |||||
| data: data; | |||||
| } | } | ||||
| export interface searchInput { | export interface searchInput { | ||||
| staffId: string; | staffId: string; | ||||
| @@ -68,6 +98,12 @@ export const fetchStaff = cache(async () => { | |||||
| }); | }); | ||||
| }); | }); | ||||
| export const fetchIndivStaff = cache(async (id: number) => { | |||||
| return serverFetchJson<IndivStaff>(`${BASE_API_URL}/staffs/${id}`, { | |||||
| next: { tags: ["staffs"] }, | |||||
| }); | |||||
| }); | |||||
| export const fetchStaffWithoutTeam = cache(async () => { | export const fetchStaffWithoutTeam = cache(async () => { | ||||
| return serverFetchJson<StaffResult[]>(`${BASE_API_URL}/staffs/noteam`, { | return serverFetchJson<StaffResult[]>(`${BASE_API_URL}/staffs/noteam`, { | ||||
| next: { tags: ["staffs"] }, | next: { tags: ["staffs"] }, | ||||
| @@ -3,6 +3,10 @@ import { getServerSession } from "next-auth"; | |||||
| import { headers } from "next/headers"; | import { headers } from "next/headers"; | ||||
| import { redirect } from "next/navigation"; | import { redirect } from "next/navigation"; | ||||
| export interface searchParamsProps { | |||||
| searchParams: { [key: string]: string | string[] | undefined }; | |||||
| } | |||||
| export class ServerFetchError extends Error { | export class ServerFetchError extends Error { | ||||
| public readonly response: Response | undefined; | public readonly response: Response | undefined; | ||||
| constructor(message?: string, response?: Response) { | constructor(message?: string, response?: Response) { | ||||
| @@ -55,6 +55,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| (acc, skill) => ({ ...acc, [skill.id]: skill.label }), | (acc, skill) => ({ ...acc, [skill.id]: skill.label }), | ||||
| {} | {} | ||||
| ); | ); | ||||
| console.log(skillIdNameMap) | |||||
| const resetStaff = useCallback(() => { | const resetStaff = useCallback(() => { | ||||
| console.log(defaultValues); | console.log(defaultValues); | ||||
| @@ -235,6 +236,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| > | > | ||||
| <Checkbox | <Checkbox | ||||
| checked={field.value!.indexOf(skill.id) > -1} | checked={field.value!.indexOf(skill.id) > -1} | ||||
| // checked={true} | |||||
| /> | /> | ||||
| <ListItemText primary={skill.label} /> | <ListItemText primary={skill.label} /> | ||||
| </MenuItem> | </MenuItem> | ||||
| @@ -1,11 +1,18 @@ | |||||
| "use client"; | "use client"; | ||||
| import EditStaffForm from "../EditStaffForm"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| import { useEffect, useState } from "react"; | |||||
| import { BASE_API_URL } from "@/config/api"; | |||||
| import { fetchStaffEdit } from "@/app/api/staff/actions"; | |||||
| import { getServerI18n } from "@/i18n"; | |||||
| import { useCallback, useEffect, useState } from "react"; | |||||
| import CustomInputForm from "../CustomInputForm"; | |||||
| import { useRouter, useSearchParams } from "next/navigation"; | |||||
| import { useTranslation } from "react-i18next"; | 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 { Button, Stack, Typography } from "@mui/material"; | |||||
| // import CreateStaffForm from "../CreateStaffForm"; | |||||
| import { comboProp, fetchCompanyCombo } from "@/app/api/companys/actions"; | import { comboProp, fetchCompanyCombo } from "@/app/api/companys/actions"; | ||||
| import { fetchTeamCombo } from "@/app/api/team/actions"; | import { fetchTeamCombo } from "@/app/api/team/actions"; | ||||
| import { fetchDepartmentCombo } from "@/app/api/departments/actions"; | import { fetchDepartmentCombo } from "@/app/api/departments/actions"; | ||||
| @@ -13,346 +20,234 @@ import { fetchPositionCombo } from "@/app/api/positions/actions"; | |||||
| import { fetchGradeCombo } from "@/app/api/grades/actions"; | import { fetchGradeCombo } from "@/app/api/grades/actions"; | ||||
| import { fetchSkillCombo } from "@/app/api/skill/actions"; | import { fetchSkillCombo } from "@/app/api/skill/actions"; | ||||
| import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | ||||
| // import { Field } from "react-hook-form"; | |||||
| // import StaffInfo from "./StaffInfo"; | |||||
| import { Check, Close } from "@mui/icons-material"; | |||||
| import { ServerFetchError } from "@/app/utils/fetchUtil"; | |||||
| import StaffInfo from "./StaffInfo"; | |||||
| import { IndividualStaff } from "@/app/api/staff"; | |||||
| import dayjs from "dayjs"; | |||||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| import { List, differenceBy } from "lodash"; | |||||
| interface skill { | |||||
| id: number; | |||||
| name: string; | |||||
| code: string; | |||||
| } | |||||
| interface skillObj { | |||||
| id: number; | |||||
| skill: skill; | |||||
| export interface comboItem { | |||||
| company: comboProp[]; | |||||
| team: comboProp[]; | |||||
| department: comboProp[]; | |||||
| position: comboProp[]; | |||||
| grade: comboProp[]; | |||||
| skill: comboProp[]; | |||||
| salary: comboProp[]; | |||||
| } | } | ||||
| interface Options { | |||||
| id: any; | |||||
| label: string; | |||||
| [key: string]: any; | |||||
| interface formProps { | |||||
| Staff: IndividualStaff | |||||
| combos: comboItem; | |||||
| } | } | ||||
| // interface Field { | |||||
| // id: string; | |||||
| // label: string; | |||||
| // type: string; | |||||
| // value: any; | |||||
| // required?: boolean; | |||||
| // options?: comboProp[] | undefined | null; | |||||
| // readOnly?: boolean; | |||||
| // } | |||||
| export interface Field { | |||||
| // subtitle: string; | |||||
| id: string; | |||||
| label: string; | |||||
| type: string; | |||||
| value?: any; | |||||
| required?: boolean; | |||||
| pattern?: string; | |||||
| message?: string; | |||||
| options?: Options[] | null; | |||||
| readOnly?: boolean; | |||||
| size?: number; | |||||
| setValue?: any[]; | |||||
| } | |||||
| const EditStaff: React.FC = async () => { | |||||
| const searchParams = useSearchParams(); | |||||
| const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id) | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const idString = searchParams.get("id"); | |||||
| const [fieldLists, setFieldLists] = useState<Field[][]>(); | |||||
| const [companyCombo, setCompanyCombo] = useState<comboProp[]>(); | |||||
| const [teamCombo, setTeamCombo] = useState<comboProp[]>(); | |||||
| const [departmentCombo, setDepartmentCombo] = useState<comboProp[]>(); | |||||
| const [positionCombo, setPositionCombo] = useState<comboProp[]>(); | |||||
| const [gradeCombo, setGradeCombo] = useState<comboProp[]>(); | |||||
| const [skillCombo, setSkillCombo] = useState<comboProp[]>(); | |||||
| const [salaryCombo, setSalaryCombo] = useState<comboProp[]>(); | |||||
| // const employTypeCombo = [{id: "FT", label: t("FT")}, {id: "PT", label: t("PT")}]; | |||||
| const title = ["", t('Additional Info')] | |||||
| const employTypeCombo = [ | |||||
| { id: "FT", label: t("FT") }, | |||||
| { id: "PT", label: t("PT") }, | |||||
| ]; | |||||
| const keyOrder1 = [ | |||||
| "staffId", | |||||
| "name", | |||||
| "company", | |||||
| "team", | |||||
| "department", | |||||
| "grade", | |||||
| "skill", | |||||
| "currentPosition", | |||||
| "salary", | |||||
| "hourlyRate", | |||||
| "employType", | |||||
| "email", | |||||
| "phone1", | |||||
| "phone2", | |||||
| ]; | |||||
| const searchParams = useSearchParams() | |||||
| const id = parseInt(searchParams.get("id") || "0"); | |||||
| const formProps = useForm<CreateStaffInputs>({ | |||||
| defaultValues: { | |||||
| staffId: Staff.staffId, | |||||
| name: Staff.name, | |||||
| companyId: Staff.company.id, | |||||
| teamId: Staff.team?.id, | |||||
| departmentId: Staff.department.id, | |||||
| gradeId: Staff.department.id, | |||||
| skillSetId: defaultSkillset, | |||||
| // removeSkillSetId: [], | |||||
| currentPositionId: Staff.currentPosition.id, | |||||
| salaryId: Staff.salary.id, | |||||
| employType: Staff.employType, | |||||
| email: Staff.email, | |||||
| phone1: Staff.phone1, | |||||
| phone2: Staff.phone2, | |||||
| emergContactName: Staff.emergContactName, | |||||
| emergContactPhone: Staff.emergContactPhone, | |||||
| joinDate: dayjs(Staff.joinDate).toString() || "", | |||||
| joinPositionId: Staff.joinPosition.id, | |||||
| departDate: dayjs(Staff.departDate).toString() || "", | |||||
| departReason: Staff.departReason, | |||||
| remark: Staff.remark, | |||||
| }}); | |||||
| const [serverError, setServerError] = useState(""); | |||||
| const router = useRouter(); | |||||
| // const [tabIndex, setTabIndex] = useState(0); | |||||
| const keyOrder2 = [ | |||||
| "emergContactName", | |||||
| "emergContactPhone", | |||||
| "joinDate", | |||||
| "joinPosition", | |||||
| "departDate", | |||||
| "departPosition", | |||||
| "departReason", | |||||
| "remark", | |||||
| ]; | |||||
| const errors = formProps.formState.errors; | |||||
| //fetch all combo | |||||
| useEffect(() => { | |||||
| fetchCompanyCombo().then((data) => { | |||||
| if (data) setCompanyCombo(data.records); | |||||
| }); | |||||
| fetchTeamCombo().then((data) => { | |||||
| if (data) setTeamCombo(data.records); | |||||
| }); | |||||
| fetchDepartmentCombo().then((data) => { | |||||
| if (data) setDepartmentCombo(data.records); | |||||
| }); | |||||
| fetchPositionCombo().then((data) => { | |||||
| if (data) setPositionCombo(data.records); | |||||
| }); | |||||
| fetchGradeCombo().then((data) => { | |||||
| if (data) setGradeCombo(data.records); | |||||
| }); | |||||
| fetchSkillCombo().then((data) => { | |||||
| if (data) { | |||||
| }setSkillCombo(data.records); | |||||
| console.log(data.records) | |||||
| }); | |||||
| fetchSalaryCombo().then((data) => { | |||||
| if (data) setSalaryCombo(data.records); | |||||
| }); | |||||
| }, [searchParams]); | |||||
| const checkDuplicates = (str1: string, str2: string, str3: string) => { | |||||
| return str1 === str2 || str1 === str3 || str2 === str3; | |||||
| } | |||||
| 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}$/ | |||||
| // let removeSkillSetId: List<number> = [] | |||||
| // if (data.skillSetId && defaultSkillset.length > data.skillSetId) { | |||||
| // removeSkillSetId = differenceBy(defaultSkillset, data.skillSetId) | |||||
| // } | |||||
| console.log(data.skillSetId) | |||||
| console.log(defaultSkillset) | |||||
| console.log(differenceBy(data.skillSetId, defaultSkillset)) | |||||
| 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 (data.phone1 === data.phone2 || data.phone1 === data.emergContactPhone || data.phone2 === data.emergContactPhone) { | |||||
| haveError = true | |||||
| formProps.setError("phone1", { message: t("Please Enter Different Phone No.."), type: "required" }) | |||||
| if (data.phone2!.length > 0) { | |||||
| formProps.setError("phone2", { message: t("Please Enter Different Phone No.."), type: "required" }) | |||||
| } | |||||
| formProps.setError("emergContactPhone", { message: t("Please Enter Different 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.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 (haveError) { | |||||
| return | |||||
| } | |||||
| console.log("passed") | |||||
| const postData = { | |||||
| id: id, | |||||
| ...data, | |||||
| // removeSkillSetId: removeSkillSetId | |||||
| } | |||||
| await saveStaff(postData) | |||||
| router.replace("/settings/staff") | |||||
| } catch (e: any) { | |||||
| console.log(e); | |||||
| formProps.setError("staffId", { message: t("Please Enter Employ Type."), type: "required" }) | |||||
| let msg = "" | |||||
| if (e.message === "Duplicated StaffId Found") { | |||||
| msg = t("Duplicated StaffId Found") | |||||
| } | |||||
| setServerError(`${t("An error has occurred. Please try again later.")} ${msg} `); | |||||
| } | |||||
| }, | |||||
| [router] | |||||
| ); | |||||
| const handleCancel = () => { | |||||
| router.back(); | |||||
| }; | |||||
| const resetStaff = useCallback(() => { | |||||
| formProps.reset({ | |||||
| staffId: Staff.staffId, | |||||
| name: Staff.name, | |||||
| companyId: Staff.company.id, | |||||
| teamId: Staff.team?.id, | |||||
| departmentId: Staff.department.id, | |||||
| gradeId: Staff.department.id, | |||||
| skillSetId: defaultSkillset, | |||||
| // removeSkillSetId: [], | |||||
| currentPositionId: Staff.currentPosition.id, | |||||
| salaryId: Staff.salary.id, | |||||
| employType: Staff.employType, | |||||
| email: Staff.email, | |||||
| phone1: Staff.phone1, | |||||
| phone2: Staff.phone2, | |||||
| emergContactName: Staff.emergContactName, | |||||
| emergContactPhone: Staff.emergContactPhone, | |||||
| joinDate: dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT) || "", | |||||
| joinPositionId: Staff.joinPosition.id, | |||||
| departDate: !Staff.departDate ? "" : dayjs(Staff.departDate).format(INPUT_DATE_FORMAT), | |||||
| departReason: Staff.departReason, | |||||
| remark: Staff.remark, | |||||
| }); | |||||
| }, []); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| let id = 0; | |||||
| if (idString) { | |||||
| id = parseInt(idString); | |||||
| console.log(id) | |||||
| fetchStaffEdit(id).then((staff) => { | |||||
| console.log(staff.data); | |||||
| const skillset = staff.data.skillset | |||||
| console.log(skillset); | |||||
| const skillIds = skillset.map((item: skillObj) => item.skill.id); | |||||
| console.log(skillIds) | |||||
| const data = staff.data; | |||||
| ///////////////////// list 1 ///////////////////// | |||||
| const list1 = keyOrder1 | |||||
| .map((key) => { | |||||
| switch (key) { | |||||
| case "staffId": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Staff ID`), | |||||
| type: "text", | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| }; | |||||
| case "name": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Staff Name`), | |||||
| type: "text", | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| }; | |||||
| case "company": | |||||
| return { | |||||
| id: `${key}Id`, | |||||
| label: t(`Company`), | |||||
| type: "combo-Obj", | |||||
| options: companyCombo, | |||||
| value: data[key].id ?? "", | |||||
| required: true, | |||||
| }; | |||||
| case "team": | |||||
| return { | |||||
| id: `${key}Id`, | |||||
| label: t(`Team`), | |||||
| type: "combo-Obj", | |||||
| options: teamCombo, | |||||
| value: data[key]?.id ?? "", | |||||
| }; | |||||
| case "department": | |||||
| return { | |||||
| id: `${key}Id`, | |||||
| label: t(`Department`), | |||||
| type: "combo-Obj", | |||||
| options: departmentCombo, | |||||
| value: data[key]?.id ?? "", | |||||
| required: true, | |||||
| // later check | |||||
| }; | |||||
| case "grade": | |||||
| return { | |||||
| id: `${key}Id`, | |||||
| label: t(`Grade`), | |||||
| type: "combo-Obj", | |||||
| options: gradeCombo, | |||||
| value: data[key]?.id ?? "", | |||||
| }; | |||||
| case "skill": | |||||
| console.log(skillIds) | |||||
| return { | |||||
| id: `${key}SetId`, | |||||
| label: t(`Skillset`), | |||||
| type: "multiSelect-Obj", | |||||
| options: skillCombo, | |||||
| value: skillIds ?? [], | |||||
| //array problem | |||||
| }; | |||||
| case "currentPosition": | |||||
| return { | |||||
| id: `${key}Id`, | |||||
| label: t(`Current Position`), | |||||
| type: "combo-Obj", | |||||
| options: positionCombo, | |||||
| value: data[key].id ?? "", | |||||
| required: true, | |||||
| }; | |||||
| case "salary": | |||||
| // console.log("salary", data[key]) | |||||
| return { | |||||
| id: `salaryId`, | |||||
| label: t(`Salary Point`), | |||||
| type: "combo-Obj", | |||||
| options: salaryCombo, | |||||
| value: data[key]?.salaryPoint ?? "", | |||||
| required: true, | |||||
| }; | |||||
| // case "hourlyRate": | |||||
| // return { | |||||
| // id: `${key}`, | |||||
| // label: t(`hourlyRate`), | |||||
| // type: "text", | |||||
| // value: "", | |||||
| // // value: data[key], | |||||
| // readOnly: true, | |||||
| // }; | |||||
| case "employType": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Employ Type`), | |||||
| type: "combo-Obj", | |||||
| options: employTypeCombo, | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| }; | |||||
| case "email": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Email`), | |||||
| type: "text", | |||||
| value: data[key] ?? "", | |||||
| pattern: "^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$", | |||||
| message: t("input matching format"), | |||||
| required: true, | |||||
| }; | |||||
| case "phone1": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Phone1`), | |||||
| type: "text", | |||||
| // pattern: "^\\d{8}$", | |||||
| message: t("input correct phone no."), | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| }; | |||||
| case "phone2": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Phone2`), | |||||
| type: "text", | |||||
| // pattern: "^\\d{8}$", | |||||
| message: t("input correct phone no."), | |||||
| value: data[key] ?? "", | |||||
| } as Field; | |||||
| default: | |||||
| return null; | |||||
| } | |||||
| }).filter((item): item is Field => item !== null); | |||||
| ///////////////////// list 2 ///////////////////// | |||||
| const list2 = keyOrder2 | |||||
| .map((key) => { | |||||
| switch (key) { | |||||
| case "emergContactName": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Emergency Contact Name`), | |||||
| type: "text", | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| } as Field; | |||||
| case "emergContactPhone": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Emergency Contact Phone`), | |||||
| type: "text", | |||||
| // pattern: "^\\d{8}$", | |||||
| message: t("input correct phone no."), | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| } as Field; | |||||
| case "joinDate": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Join Date`), | |||||
| type: "multiDate", | |||||
| value: data[key] ?? "", | |||||
| required: true, | |||||
| } as Field; | |||||
| case "joinPosition": | |||||
| return { | |||||
| id: `${key}Id`, | |||||
| label: t(`Join Position`), | |||||
| type: "combo-Obj", | |||||
| options: positionCombo, | |||||
| value: data[key]?.id ?? "", | |||||
| required: true, | |||||
| } as Field; | |||||
| case "departDate": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Depart Date`), | |||||
| type: "multiDate", | |||||
| value: data[key] ?? "", | |||||
| } as Field; | |||||
| case "departReason": | |||||
| return { | |||||
| id: `${key}`, | |||||
| label: t(`Depart Reason`), | |||||
| type: "text", | |||||
| value: data[key] ?? "", | |||||
| } as Field; | |||||
| case "remark": | |||||
| return { | |||||
| id: `remark`, | |||||
| label: t(`Remark`), | |||||
| type: "remarks", | |||||
| value: data[key] ?? "", | |||||
| } as Field; | |||||
| default: | |||||
| return null; | |||||
| } | |||||
| }).filter((item): item is Field => item !== null); | |||||
| console.log(list2); | |||||
| console.log([list1]); | |||||
| setFieldLists([list1,list2]); | |||||
| }); | |||||
| } | |||||
| }, [companyCombo, teamCombo, departmentCombo, positionCombo, gradeCombo, skillCombo, salaryCombo, idString]); | |||||
| console.log(Staff) | |||||
| resetStaff() | |||||
| }, [Staff, combos]); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <EditStaffForm Title={title} fieldLists={fieldLists as Field[][] || [[]]} /> | |||||
| <FormProvider {...formProps}> | |||||
| <Stack | |||||
| spacing={2} | |||||
| component="form" | |||||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||||
| > | |||||
| {serverError && ( | |||||
| <Typography variant="body2" color="error" alignSelf="flex-end"> | |||||
| {serverError} | |||||
| </Typography> | |||||
| )} | |||||
| {Staff && <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> | |||||
| </> | </> | ||||
| ); | ); | ||||
| }; | }; | ||||
| export default EditStaff; | |||||
| export default EditStaff; | |||||
| @@ -1,17 +1,60 @@ | |||||
| import React from "react"; | import React from "react"; | ||||
| import EditStaff from "./EditStaff"; | |||||
| import EditStaff, { comboItem } from "./EditStaff"; | |||||
| import EditStaffLoading from "./EditStaffLoading"; | import EditStaffLoading from "./EditStaffLoading"; | ||||
| import { fetchStaff, fetchTeamLeads } from "@/app/api/staff"; | |||||
| import { StaffResult, fetchIndivStaff, fetchStaff, fetchTeamLeads, preloadStaff } from "@/app/api/staff"; | |||||
| import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
| import { fetchTeamCombo } from "@/app/api/team/actions"; | |||||
| import { fetchDepartmentCombo } from "@/app/api/departments/actions"; | |||||
| 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 { fetchCompanyCombo } from "@/app/api/companys/actions"; | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof EditStaffLoading; | Loading: typeof EditStaffLoading; | ||||
| } | } | ||||
| const EditStaffWrapper: React.FC & SubComponents = async () => { | |||||
| interface Props { | |||||
| id: number | |||||
| } | |||||
| const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| id | |||||
| }) => { | |||||
| preloadStaff() | |||||
| const [ | |||||
| Staff, | |||||
| CompanyCombo, | |||||
| TeamCombo, | |||||
| DepartmentCombo, | |||||
| PositionCombo, | |||||
| GradeCombo, | |||||
| SkillCombo, | |||||
| SalaryCombo, | |||||
| ] = await Promise.all([ | |||||
| fetchIndivStaff(id), | |||||
| fetchCompanyCombo(), | |||||
| fetchTeamCombo(), | |||||
| fetchDepartmentCombo(), | |||||
| fetchPositionCombo(), | |||||
| fetchGradeCombo(), | |||||
| fetchSkillCombo(), | |||||
| fetchSalaryCombo(), | |||||
| ]); | |||||
| const combos: comboItem = { | |||||
| company: CompanyCombo.records, | |||||
| team: TeamCombo.records, | |||||
| department: DepartmentCombo.records, | |||||
| position: PositionCombo.records, | |||||
| grade: GradeCombo.records, | |||||
| skill: SkillCombo.records, | |||||
| salary: SalaryCombo.records, | |||||
| } | |||||
| return <EditStaff/>; | |||||
| console.log(Staff.data) | |||||
| return <EditStaff Staff={Staff.data} combos={combos}/>; | |||||
| }; | }; | ||||
| EditStaffWrapper.Loading = EditStaffLoading; | EditStaffWrapper.Loading = EditStaffLoading; | ||||
| @@ -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 "./EditStaff"; | |||||
| 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 required>{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>{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 required>{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 required>{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: { | |||||
| required: true, | |||||
| error: | |||||
| joinDate === "Invalid Date" || Boolean(errors.joinDate), | |||||
| // value: errors.joinDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <FormControl fullWidth> | |||||
| <InputLabel required>{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.id} | |||||
| > | |||||
| {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 | |||||
| ? new Date(joinDate) > new Date(departDate) | |||||
| : false, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </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; | |||||