@@ -80,8 +80,8 @@ export interface searchInput { | |||
export interface SalaryEffectiveInfo { | |||
id: number; | |||
date: string; | |||
salaryPoint: number; | |||
date: string | Date; | |||
salaryPoint: number | string; | |||
} | |||
export const preloadTeamLeads = () => { | |||
@@ -28,6 +28,7 @@ import { IndividualStaff, SalaryEffectiveInfo } from "@/app/api/staff"; | |||
import dayjs from "dayjs"; | |||
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
import { List, differenceBy } from "lodash"; | |||
// import { useGridApiContext } from '@mui/x-data-grid'; | |||
export interface comboItem { | |||
company: comboProp[]; | |||
@@ -47,6 +48,8 @@ interface formProps { | |||
const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) => { | |||
console.log(combos.salary) | |||
console.log(SalaryEffectiveInfo) | |||
const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id) | |||
const { t } = useTranslation(); | |||
const searchParams = useSearchParams() | |||
@@ -74,7 +77,12 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||
departDate: dayjs(Staff.departDate).toString() || "", | |||
departReason: Staff.departReason, | |||
remark: Staff.remark, | |||
salaryEffectiveInfo: SalaryEffectiveInfo | |||
salaryEffectiveInfo: SalaryEffectiveInfo.map(item => { | |||
return ({ | |||
id: Math.random(), | |||
salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label, | |||
date: dayjs(item.date).toDate(), | |||
})}) | |||
}}); | |||
const [serverError, setServerError] = useState(""); | |||
const router = useRouter(); | |||
@@ -147,6 +155,11 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||
const postData = { | |||
id: id, | |||
...data, | |||
salaryEffectiveInfo: SalaryEffectiveInfo.map(item => ({ | |||
id: item.id, | |||
salaryPoint: item.salaryPoint, | |||
date: dayjs(item.date).format('YYYY-MM-DD') | |||
})) | |||
} | |||
console.log(postData) | |||
await saveStaff(postData) | |||
@@ -168,37 +181,60 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||
router.back(); | |||
}; | |||
const resetStaff = useCallback(() => { | |||
console.log(dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT)) | |||
console.log(formProps.getValues("joinDate")) | |||
formProps.reset({ | |||
staffId: Staff.staffId, | |||
name: Staff.name, | |||
companyId: Staff.company.id, | |||
teamId: Staff.team?.id, | |||
departmentId: Staff.department?.id, | |||
gradeId: Staff.grade?.id, | |||
skillSetId: defaultSkillset, | |||
currentPositionId: Staff.currentPosition?.id, | |||
salaryId: Staff.salary.salaryPoint, | |||
employType: Staff.employType, | |||
email: Staff.email, | |||
phone1: Staff.phone1, | |||
phone2: Staff.phone2, | |||
emergContactName: Staff.emergContactName, | |||
emergContactPhone: Staff.emergContactPhone, | |||
joinDate: Staff.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, | |||
salaryEffectiveInfo: SalaryEffectiveInfo | |||
}); | |||
}, [Staff,formProps]); | |||
// const resetStaff = useCallback(() => { | |||
// window.location.reload() | |||
// console.log(dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT)) | |||
// console.log(formProps.getValues("joinDate")) | |||
// formProps.setValue('salaryEffectiveInfo', SalaryEffectiveInfo.map(item => { | |||
// return ({ | |||
// id: item.id, | |||
// salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label, | |||
// date: dayjs(item.date).toDate(), | |||
// })})) | |||
// formProps.reset({ | |||
// staffId: Staff.staffId, | |||
// name: Staff.name, | |||
// companyId: Staff.company.id, | |||
// teamId: Staff.team?.id, | |||
// departmentId: Staff.department?.id, | |||
// gradeId: Staff.grade?.id, | |||
// skillSetId: defaultSkillset, | |||
// currentPositionId: Staff.currentPosition?.id, | |||
// salaryId: Staff.salary.salaryPoint, | |||
// employType: Staff.employType, | |||
// email: Staff.email, | |||
// phone1: Staff.phone1, | |||
// phone2: Staff.phone2, | |||
// emergContactName: Staff.emergContactName, | |||
// emergContactPhone: Staff.emergContactPhone, | |||
// joinDate: Staff.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, | |||
// salaryEffectiveInfo: SalaryEffectiveInfo.map(item => { | |||
// return ({ | |||
// id: item.id, | |||
// salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label, | |||
// date: dayjs(item.date).toDate(), | |||
// })}) | |||
// }); | |||
// }, []); | |||
// useEffect(() => { | |||
// formProps.setValue('salaryEffectiveInfo', SalaryEffectiveInfo.map(item => { | |||
// return ({ | |||
// id: item.id, | |||
// salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label, | |||
// date: dayjs(item.date).toDate(), | |||
// })}) | |||
// ) | |||
// }, []); | |||
useEffect(() => { | |||
resetStaff() | |||
}, [Staff, formProps, combos]); | |||
// useEffect(() => { | |||
// resetStaff() | |||
// }, [Staff, formProps, combos]); | |||
return ( | |||
<> | |||
@@ -218,7 +254,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||
<Button | |||
variant="text" | |||
startIcon={<RestartAlt />} | |||
onClick={resetStaff} | |||
onClick={()=> window.location.reload()} | |||
> | |||
{t("Reset")} | |||
</Button> | |||
@@ -42,6 +42,8 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||
fetchSkillCombo(), | |||
fetchSalaryCombo(), | |||
]); | |||
console.log(SalaryCombo.records) | |||
const combos: comboItem = { | |||
company: CompanyCombo.records, | |||
team: TeamCombo.records, | |||
@@ -54,7 +56,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||
console.log(Staff.data) | |||
return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={[{id:0, salaryPoint: 1, date:"2021-05-05"}]}/>; | |||
return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={[{id:0, salaryPoint: 1, date:"2021-05-05"}, {id:1, salaryPoint: 43, date:"2024-05-05"}]}/>; | |||
}; | |||
EditStaffWrapper.Loading = EditStaffLoading; | |||
@@ -1,17 +1,25 @@ | |||
import React, { useEffect } from 'react'; | |||
import React, { useCallback, useEffect, useMemo, useState } 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 { useForm, Controller, useFormContext } 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'; | |||
import { DataGrid, GridEventListener, GridRowEditStopParams, GridRowEditStopReasons, GridRowModel, GridRowModes, GridRowModesModel, GridToolbarContainer } from '@mui/x-data-grid'; | |||
import StyledDataGrid from '../StyledDataGrid'; | |||
import AddIcon from '@mui/icons-material/Add'; | |||
import SaveIcon from '@mui/icons-material/Save'; | |||
import DeleteIcon from '@mui/icons-material/Delete'; | |||
import CancelIcon from '@mui/icons-material/Cancel'; | |||
import EditIcon from '@mui/icons-material/Edit'; | |||
import { GridActionsCellItem } from '@mui/x-data-grid'; | |||
interface SalaryEffectiveModelProps { | |||
open: boolean; | |||
onClose: () => void; | |||
modalSx?: SxProps; | |||
onSave: () => void; | |||
columns: any[] | |||
} | |||
const modalSx: SxProps = { | |||
@@ -20,17 +28,57 @@ const modalSx: SxProps = { | |||
left: "50%", | |||
transform: "translate(-50%, -50%)", | |||
width: "90%", | |||
maxWidth: "sm", | |||
maxHeight: "90%", | |||
maxWidth: "auto", | |||
maxHeight: "auto", | |||
padding: 3, | |||
display: "flex", | |||
flexDirection: "column", | |||
gap: 2, | |||
}; | |||
const SalaryEffectiveModel: React.FC<SalaryEffectiveModelProps> = ({ open, onClose, modalSx: mSx, onSave }) => { | |||
function EditToolbar(props: React.JSXElementConstructor<any> | null | undefined | any) { | |||
// const intl = useIntl(); | |||
// const addRecordBtn = intl.formatMessage({ id: 'add' }); | |||
const { count, setCount, setRows, setRowModesModel, _columns } = props; | |||
let obj: { [key: string]: string } = {}; | |||
for (let i = 0; i < _columns.length - 1; i++) { | |||
obj[_columns[i].field as string] = ''; | |||
} | |||
const handleClick = React.useCallback(() => { | |||
const id = Math.random(); | |||
setRows((oldRows: any) => [...oldRows, { id, ...obj, isNew: true }]); | |||
setRowModesModel((oldModel: any) => ({ | |||
...oldModel, | |||
[id]: { mode: GridRowModes.Edit, | |||
// fieldToFocus: 'material' | |||
} | |||
})); | |||
setCount((prev: number) => prev+1) | |||
}, [count, setCount, setRowModesModel, setRows]) | |||
return ( | |||
<GridToolbarContainer> | |||
<Button disabled={count>=1} color="primary" startIcon={<AddIcon />} onClick={handleClick}> | |||
{"addRecordBtn"} | |||
</Button> | |||
{/* <Button color="primary" startIcon={<AddIcon />} onClick={handleSave}> | |||
SAVE | |||
</Button> */} | |||
</GridToolbarContainer> | |||
); | |||
} | |||
const SalaryEffectiveModel: React.FC<SalaryEffectiveModelProps> = ({ open, onClose, modalSx: mSx, onSave, columns }) => { | |||
const { t } = useTranslation(); | |||
const { control, register, formState, trigger, watch, setValue } = useForm({}); | |||
const { control, register, formState, trigger, watch, setValue, getValues } = useFormContext(); | |||
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({}); | |||
const [count, setCount] = useState(0); | |||
const [_rows, setRows] = useState(() => { | |||
const list = getValues('salaryEffectiveInfo') | |||
console.log(list) | |||
return list && list.length > 0 ? list : [] | |||
}); | |||
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. | |||
@@ -38,14 +86,133 @@ const SalaryEffectiveModel: React.FC<SalaryEffectiveModelProps> = ({ open, onClo | |||
onClose(); | |||
}; | |||
const handleSave = async () => { | |||
const isValid = await trigger(); | |||
if (isValid) { | |||
onSave(); | |||
onClose(); | |||
// const handleSave = async () => { | |||
// const isValid = await trigger(); | |||
// // if (isValid) { | |||
// // onSave(); | |||
// // onClose(); | |||
// // } | |||
// }; | |||
const handleRowEditStop: GridEventListener<"rowEditStop"> = ( | |||
params, | |||
event, | |||
) => { | |||
if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||
event.defaultMuiPrevented = true; | |||
} | |||
}; | |||
const processRowUpdate = useCallback((newRow: GridRowModel) => { | |||
console.log(newRow) | |||
const updatedRow = { ...newRow, updated: true }; | |||
console.log(_rows) | |||
if (_rows.length != 0) { | |||
setRows(_rows?.map((row: any) => (row.id === newRow.id ? updatedRow : row))); | |||
} | |||
return updatedRow; | |||
}, [_rows, setValue, setRows]) | |||
useEffect(()=> { | |||
setValue('salaryEffectiveInfo', _rows) | |||
}, [_rows]) | |||
const handleSaveClick = useCallback( | |||
(id: any) => () => { | |||
setRowModesModel((prevRowModesModel) => ({ | |||
...prevRowModesModel, | |||
[id]: { mode: GridRowModes.View } | |||
})); | |||
}, | |||
[setRowModesModel] | |||
); | |||
const handleCancelClick = useCallback( | |||
(id: any) => () => { | |||
setRowModesModel((prevRowModesModel) => ({ | |||
...prevRowModesModel, | |||
[id]: { mode: GridRowModes.View, ignoreModifications: true } | |||
})); | |||
}, | |||
[setRowModesModel] | |||
); | |||
const handleEditClick = useCallback( | |||
(id: any) => () => { | |||
setRowModesModel((prevRowModesModel) => ({ | |||
...prevRowModesModel, | |||
[id]: { mode: GridRowModes.Edit } | |||
})); | |||
}, | |||
[setRowModesModel] | |||
); | |||
const handleDeleteClick = useCallback( | |||
(id: any) => () => { | |||
setRows((prevRows: any) => prevRows.filter((row: any) => row.id !== id)); | |||
setCount((prev: number) => prev - 1) | |||
}, | |||
[setRows, setCount] | |||
); | |||
const defaultCol = useMemo( | |||
() => ( | |||
{ | |||
field: 'actions', | |||
type: 'actions', | |||
headerName: 'edit', | |||
width: 100, | |||
cellClassName: 'actions', | |||
getActions: ({ id }: { id: number }) => { | |||
const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit; | |||
if (isInEditMode) { | |||
return [ | |||
<GridActionsCellItem | |||
icon={<SaveIcon />} | |||
label="Save" | |||
key="edit" | |||
sx={{ | |||
color: 'primary.main' | |||
}} | |||
onClick={handleSaveClick(id)} | |||
/>, | |||
<GridActionsCellItem | |||
icon={<CancelIcon />} | |||
label="Cancel" | |||
key="edit" | |||
onClick={handleCancelClick(id)} | |||
/> | |||
]; | |||
} | |||
return [ | |||
<GridActionsCellItem | |||
icon={<EditIcon />} | |||
label="Edit" | |||
className="textPrimary" | |||
onClick={handleEditClick(id)} | |||
color="inherit" | |||
key="edit" | |||
/>, | |||
<GridActionsCellItem | |||
icon={<DeleteIcon />} | |||
label="Delete" | |||
sx={{ | |||
color: 'error.main' | |||
}} | |||
onClick={handleDeleteClick(id)} color="inherit" key="edit" /> | |||
]; | |||
} | |||
} | |||
), [rowModesModel, handleSaveClick, handleCancelClick, handleEditClick, handleDeleteClick] | |||
) | |||
let _columns: any[] = [] | |||
if (columns) { | |||
_columns = [...columns, defaultCol] | |||
} | |||
useEffect(() => { | |||
console.log(formValues) | |||
}, [open]) | |||
@@ -56,30 +223,33 @@ const SalaryEffectiveModel: React.FC<SalaryEffectiveModelProps> = ({ open, onClo | |||
<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} | |||
/> | |||
<StyledDataGrid | |||
rows={_rows} | |||
columns={_columns} | |||
editMode="row" | |||
rowModesModel={rowModesModel} | |||
onRowModesModelChange={setRowModesModel} | |||
onRowEditStop={handleRowEditStop} | |||
processRowUpdate={processRowUpdate} | |||
slots={{ | |||
toolbar: EditToolbar | |||
}} | |||
slotProps={{ | |||
toolbar: {count, setCount, setRows, setRowModesModel, _columns} | |||
}} | |||
/> | |||
<Box display="flex" justifyContent="flex-end" gap={2}> | |||
<Button variant="text" onClick={handleClose}> | |||
{t('Cancel')} | |||
</Button> | |||
<Button variant="contained" onClick={handleSave}> | |||
<Button variant="contained" onClick={handleClose}> | |||
{t("Save")} | |||
</Button> | |||
</Box> | |||
</FormControl> | |||
{/* </FormControl> */} | |||
</Paper> | |||
</Modal> | |||
); | |||
}; | |||
export default SalaryEffectiveModel; | |||
export default SalaryEffectiveModel; |
@@ -9,7 +9,7 @@ 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, useState } from "react"; | |||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||
import { CreateStaffInputs } from "@/app/api/staff/actions"; | |||
import { | |||
Button, | |||
@@ -95,6 +95,28 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
if (departDate) clearErrors("departDate"); | |||
}, [joinDate, departDate]); | |||
const salaryCols = useMemo( | |||
() => [ | |||
{ | |||
field: 'salaryPoint', | |||
headerName: 'salaryPoint', | |||
flex: 1, | |||
editable: true, | |||
type: 'singleSelect', | |||
valueOptions: combos?.salary.map(item => item.label), | |||
// valueOptions: [], | |||
// width: 150 | |||
}, | |||
{ | |||
field: 'date', | |||
headerName: 'date', | |||
flex: 1, | |||
editable: true, | |||
type: 'date', | |||
// width: 150 | |||
}, | |||
], [combos]) | |||
return ( | |||
<Card sx={{ display: "block" }}> | |||
<CardContent component={Stack} spacing={4}> | |||
@@ -305,6 +327,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
{...field} | |||
error={Boolean(errors.salaryId)} | |||
style={{ flex: 1, marginRight: '8px' }} | |||
disabled | |||
> | |||
{combos.salary.map((salary, index) => ( | |||
<MenuItem | |||
@@ -544,6 +567,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
open={salaryEffectiveModelOpen} | |||
onClose={closeSalaryEffectiveModel} | |||
onSave={onSalaryEffectiveSave} | |||
columns={salaryCols} | |||
/> | |||
</Card> | |||
); | |||