| @@ -43,6 +43,12 @@ export interface CreateStaffInputs { | |||||
| // team: Team[]; | // team: Team[]; | ||||
| } | } | ||||
| export interface salaryEffectiveInfo { | |||||
| id: number; | |||||
| date: string; | |||||
| salaryPoint: number; | |||||
| } | |||||
| export const saveStaff = async (data: CreateStaffInputs) => { | export const saveStaff = async (data: CreateStaffInputs) => { | ||||
| // try { | // try { | ||||
| const newStaffList = await serverFetchJson(`${BASE_API_URL}/staffs/save`, { | const newStaffList = await serverFetchJson(`${BASE_API_URL}/staffs/save`, { | ||||
| @@ -78,6 +78,12 @@ export interface searchInput { | |||||
| currentPosition: string; | currentPosition: string; | ||||
| } | } | ||||
| export interface SalaryEffectiveInfo { | |||||
| id: number; | |||||
| date: string; | |||||
| salaryPoint: number; | |||||
| } | |||||
| export const preloadTeamLeads = () => { | export const preloadTeamLeads = () => { | ||||
| fetchTeamLeads(); | fetchTeamLeads(); | ||||
| }; | }; | ||||
| @@ -24,7 +24,7 @@ import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | |||||
| import { Check, Close, RestartAlt } from "@mui/icons-material"; | import { Check, Close, RestartAlt } from "@mui/icons-material"; | ||||
| import { ServerFetchError } from "@/app/utils/fetchUtil"; | import { ServerFetchError } from "@/app/utils/fetchUtil"; | ||||
| import StaffInfo from "./StaffInfo"; | import StaffInfo from "./StaffInfo"; | ||||
| import { IndividualStaff } from "@/app/api/staff"; | |||||
| import { IndividualStaff, SalaryEffectiveInfo } from "@/app/api/staff"; | |||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| import { List, differenceBy } from "lodash"; | import { List, differenceBy } from "lodash"; | ||||
| @@ -42,15 +42,16 @@ export interface comboItem { | |||||
| interface formProps { | interface formProps { | ||||
| Staff: IndividualStaff | Staff: IndividualStaff | ||||
| combos: comboItem; | combos: comboItem; | ||||
| SalaryEffectiveInfo: SalaryEffectiveInfo[]; | |||||
| } | } | ||||
| const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) => { | |||||
| const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id) | const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id) | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const searchParams = useSearchParams() | const searchParams = useSearchParams() | ||||
| const id = parseInt(searchParams.get("id") || "0"); | const id = parseInt(searchParams.get("id") || "0"); | ||||
| const formProps = useForm<CreateStaffInputs>({ | |||||
| const formProps = useForm<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] }>({ | |||||
| defaultValues: { | defaultValues: { | ||||
| staffId: Staff.staffId, | staffId: Staff.staffId, | ||||
| name: Staff.name, | name: Staff.name, | ||||
| @@ -73,6 +74,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| departDate: dayjs(Staff.departDate).toString() || "", | departDate: dayjs(Staff.departDate).toString() || "", | ||||
| departReason: Staff.departReason, | departReason: Staff.departReason, | ||||
| remark: Staff.remark, | remark: Staff.remark, | ||||
| salaryEffectiveInfo: SalaryEffectiveInfo | |||||
| }}); | }}); | ||||
| const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| @@ -84,7 +86,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| return str1 === str2 || str1 === str3 || str2 === str3; | return str1 === str2 || str1 === str3 || str2 === str3; | ||||
| } | } | ||||
| const onSubmit = useCallback<SubmitHandler<CreateStaffInputs>>( | |||||
| const onSubmit = useCallback<SubmitHandler<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } >>( | |||||
| async (data) => { | async (data) => { | ||||
| try { | try { | ||||
| console.log(data); | console.log(data); | ||||
| @@ -190,6 +192,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos }) => { | |||||
| departDate: !Staff.departDate ? "" : dayjs(Staff.departDate).format(INPUT_DATE_FORMAT), | departDate: !Staff.departDate ? "" : dayjs(Staff.departDate).format(INPUT_DATE_FORMAT), | ||||
| departReason: Staff.departReason, | departReason: Staff.departReason, | ||||
| remark: Staff.remark, | remark: Staff.remark, | ||||
| salaryEffectiveInfo: SalaryEffectiveInfo | |||||
| }); | }); | ||||
| }, [Staff,formProps]); | }, [Staff,formProps]); | ||||
| @@ -54,7 +54,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| console.log(Staff.data) | console.log(Staff.data) | ||||
| return <EditStaff Staff={Staff.data} combos={combos}/>; | |||||
| return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={[{id:0, salaryPoint: 1, date:"2021-05-05"}]}/>; | |||||
| }; | }; | ||||
| EditStaffWrapper.Loading = EditStaffLoading; | EditStaffWrapper.Loading = EditStaffLoading; | ||||
| @@ -0,0 +1,85 @@ | |||||
| import React, { useEffect } from 'react'; | |||||
| import { Modal, Box, Typography, Button, TextField, FormControl, InputLabel, Select, MenuItem, Paper, SxProps } from '@mui/material'; | |||||
| import { useForm, Controller } from 'react-hook-form'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import { INPUT_DATE_FORMAT, OUTPUT_DATE_FORMAT } from '@/app/utils/formatUtil'; | |||||
| import dayjs from 'dayjs'; | |||||
| import { DatePicker } from '@mui/x-date-pickers'; | |||||
| interface SalaryEffectiveModelProps { | |||||
| open: boolean; | |||||
| onClose: () => void; | |||||
| modalSx?: SxProps; | |||||
| onSave: () => void; | |||||
| } | |||||
| const modalSx: SxProps = { | |||||
| position: "absolute", | |||||
| top: "50%", | |||||
| left: "50%", | |||||
| transform: "translate(-50%, -50%)", | |||||
| width: "90%", | |||||
| maxWidth: "sm", | |||||
| maxHeight: "90%", | |||||
| padding: 3, | |||||
| display: "flex", | |||||
| flexDirection: "column", | |||||
| gap: 2, | |||||
| }; | |||||
| const SalaryEffectiveModel: React.FC<SalaryEffectiveModelProps> = ({ open, onClose, modalSx: mSx, onSave }) => { | |||||
| const { t } = useTranslation(); | |||||
| const { control, register, formState, trigger, watch, setValue } = useForm({}); | |||||
| const formValues = watch(); // This line of code is using the watch function from react-hook-form to get the current values of the form fields. | |||||
| const handleClose = () => { | |||||
| onClose(); | |||||
| }; | |||||
| const handleSave = async () => { | |||||
| const isValid = await trigger(); | |||||
| if (isValid) { | |||||
| onSave(); | |||||
| onClose(); | |||||
| } | |||||
| }; | |||||
| useEffect(() => { | |||||
| console.log(formValues) | |||||
| }, [open]) | |||||
| return ( | |||||
| <Modal open={open} onClose={handleClose}> | |||||
| <Paper sx={{ ...modalSx, ...mSx }}> | |||||
| <Typography variant="h6" component="h2"> | |||||
| {t('Salary Effective Date Change')} | |||||
| </Typography> | |||||
| <FormControl> | |||||
| <TextField | |||||
| label={t('Salary')} | |||||
| type="number" | |||||
| fullWidth | |||||
| {...register('salary', { | |||||
| valueAsNumber: true, | |||||
| required: t('Salary is required'), | |||||
| })} | |||||
| error={Boolean(formState.errors.salary)} | |||||
| // helperText={formState.errors.salary?.message} | |||||
| /> | |||||
| <Box display="flex" justifyContent="flex-end" gap={2}> | |||||
| <Button variant="text" onClick={handleClose}> | |||||
| {t('Cancel')} | |||||
| </Button> | |||||
| <Button variant="contained" onClick={handleSave}> | |||||
| {t("Save")} | |||||
| </Button> | |||||
| </Box> | |||||
| </FormControl> | |||||
| </Paper> | |||||
| </Modal> | |||||
| ); | |||||
| }; | |||||
| export default SalaryEffectiveModel; | |||||
| @@ -9,9 +9,10 @@ import Typography from "@mui/material/Typography"; | |||||
| import { CreateGroupInputs } from "@/app/api/group/actions"; | import { CreateGroupInputs } from "@/app/api/group/actions"; | ||||
| import { Controller, useFormContext } from "react-hook-form"; | import { Controller, useFormContext } from "react-hook-form"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { useCallback, useEffect } from "react"; | |||||
| import { useCallback, useEffect, useState } from "react"; | |||||
| import { CreateStaffInputs } from "@/app/api/staff/actions"; | import { CreateStaffInputs } from "@/app/api/staff/actions"; | ||||
| import { | import { | ||||
| Button, | |||||
| Checkbox, | Checkbox, | ||||
| FormControl, | FormControl, | ||||
| InputLabel, | InputLabel, | ||||
| @@ -25,6 +26,8 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
| import { DemoItem } from "@mui/x-date-pickers/internals/demo"; | import { DemoItem } from "@mui/x-date-pickers/internals/demo"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| import SalaryEffectiveModel from "./SalaryEffectiveModel"; | |||||
| import { SalaryEffectiveInfo } from "@/app/api/staff"; | |||||
| interface Props { | interface Props { | ||||
| combos: comboItem; | combos: comboItem; | ||||
| @@ -45,7 +48,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| getValues, | getValues, | ||||
| watch, | watch, | ||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<CreateStaffInputs>(); | |||||
| } = useFormContext<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] }>(); | |||||
| const employType = [ | const employType = [ | ||||
| { id: 1, label: "FT" }, | { id: 1, label: "FT" }, | ||||
| @@ -57,6 +60,21 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| {} | {} | ||||
| ); | ); | ||||
| // Salary Effiective History edit modal related | |||||
| const [salaryEffectiveModelOpen, setSalaaryEffectiveModelOpen] = useState(false); | |||||
| const closeSalaryEffectiveModel = useCallback(() => { | |||||
| setSalaaryEffectiveModelOpen(false); | |||||
| }, []); | |||||
| const openSalaryEffectiveModel = useCallback(() => { | |||||
| setSalaaryEffectiveModelOpen(true); | |||||
| }, []); | |||||
| const onSalaryEffectiveSave = useCallback(async () => { | |||||
| console.log(getValues()) | |||||
| setSalaaryEffectiveModelOpen(false); | |||||
| }, []); | |||||
| const resetStaff = useCallback(() => { | const resetStaff = useCallback(() => { | ||||
| console.log(defaultValues); | console.log(defaultValues); | ||||
| if (defaultValues !== undefined) { | if (defaultValues !== undefined) { | ||||
| @@ -275,29 +293,35 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <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} | |||||
| <FormControl fullWidth> | |||||
| <InputLabel required>{t("Salary Point")}</InputLabel> | |||||
| <Controller | |||||
| control={control} | |||||
| name="salaryId" | |||||
| render={({ field }) => ( | |||||
| <Box display="flex" justifyContent="space-between" alignItems="center"> | |||||
| <Select | |||||
| label={t("Salary Point")} | |||||
| {...field} | |||||
| error={Boolean(errors.salaryId)} | |||||
| style={{ flex: 1, marginRight: '8px' }} | |||||
| > | > | ||||
| {t(salary.label)} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| )} | |||||
| /> | |||||
| </FormControl> | |||||
| {combos.salary.map((salary, index) => ( | |||||
| <MenuItem | |||||
| key={`${salary.id}-${index}`} | |||||
| value={salary.id} | |||||
| > | |||||
| {t(salary.label)} | |||||
| </MenuItem> | |||||
| ))} | |||||
| </Select> | |||||
| <Button variant="contained" size="small" onClick={openSalaryEffectiveModel}> | |||||
| {t("Edit")} | |||||
| </Button> | |||||
| </Box> | |||||
| )} | |||||
| /> | |||||
| </FormControl> | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| @@ -516,6 +540,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| </Grid> | </Grid> | ||||
| </Box> | </Box> | ||||
| </CardContent> | </CardContent> | ||||
| <SalaryEffectiveModel | |||||
| open={salaryEffectiveModelOpen} | |||||
| onClose={closeSalaryEffectiveModel} | |||||
| onSave={onSalaryEffectiveSave} | |||||
| /> | |||||
| </Card> | </Card> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -27,5 +27,6 @@ | |||||
| "Remark": "備註", | "Remark": "備註", | ||||
| "Reset": "重設", | "Reset": "重設", | ||||
| "Confirm": "確認", | "Confirm": "確認", | ||||
| "Cancel": "取消" | |||||
| "Cancel": "取消", | |||||
| "Save": "儲存" | |||||
| } | } | ||||