| @@ -15,6 +15,7 @@ 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 { fetchGradesLog, fetchPositionLog, fetchTeamLog } from "@/app/api/staffInfoHistory"; | |||
| // export const metadata: Metadata = { | |||
| // title: "staff-edit", | |||
| @@ -23,18 +24,21 @@ import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | |||
| const EditStaffPage: React.FC<searchParamsProps> = async ({ | |||
| searchParams, | |||
| }) => { | |||
| const id = parseInt(searchParams.id as string) | |||
| // preload | |||
| fetchIndivStaff(parseInt(searchParams.id as string)), | |||
| fetchCompanyCombo(), | |||
| fetchTeamCombo(), | |||
| fetchDepartmentCombo(), | |||
| fetchPositionCombo(), | |||
| fetchGradeCombo(), | |||
| fetchSkillCombo(), | |||
| fetchSalaryCombo(), | |||
| fetchStaffSalaryEffectiveInfo(parseInt(searchParams.id as string)), | |||
| fetchStaffInvolvedProjects(parseInt(searchParams.id as string)) | |||
| fetchIndivStaff(id) | |||
| fetchCompanyCombo() | |||
| fetchTeamCombo() | |||
| fetchDepartmentCombo() | |||
| fetchPositionCombo() | |||
| fetchGradeCombo() | |||
| fetchSkillCombo() | |||
| fetchSalaryCombo() | |||
| fetchStaffSalaryEffectiveInfo(id) | |||
| fetchStaffInvolvedProjects(id) | |||
| fetchGradesLog(id) | |||
| fetchPositionLog(id) | |||
| fetchTeamLog(id) | |||
| return ( | |||
| <> | |||
| @@ -13,7 +13,26 @@ export interface CreateCustomInputs { | |||
| // Miscellaneous | |||
| expectedProjectFee: string; | |||
| } | |||
| export type teamHistory = { | |||
| id: number, | |||
| team: string | number, | |||
| from: Date | string, | |||
| to?: Date | string | |||
| } | |||
| export type gradeHistory = { | |||
| id: number, | |||
| grade: string | number, | |||
| from: Date | string, | |||
| to?: Date | string | |||
| } | |||
| export type positionHistory = { | |||
| id: number, | |||
| position: string | number, | |||
| from: Date | string, | |||
| to?: Date | string | |||
| } | |||
| export interface CreateStaffInputs { | |||
| id?: number | |||
| name: string; | |||
| @@ -37,6 +56,12 @@ export interface CreateStaffInputs { | |||
| departReason?: string; | |||
| remark?: string; | |||
| salaryEffectiveInfo?: any; | |||
| teamHistory: teamHistory[]; | |||
| delTeamHistory?: number[]; | |||
| gradeHistory: gradeHistory[]; | |||
| delGradeHistory?: number[]; | |||
| positionHistory: positionHistory[]; | |||
| delPositionHistory?: number[]; | |||
| } | |||
| export interface records { | |||
| @@ -10,8 +10,8 @@ export type GradeLogInfo = { | |||
| staffName: String, | |||
| staffCode: String, | |||
| grade: Grade, | |||
| from: String, | |||
| to?: String, | |||
| from: number[], | |||
| to?: number[], | |||
| } | |||
| export type PositionLogInfo = { | |||
| @@ -20,8 +20,8 @@ export type PositionLogInfo = { | |||
| staffName: String, | |||
| staffCode: String, | |||
| position: PositionResult, | |||
| from: String, | |||
| to?: String, | |||
| from: number[], | |||
| to?: number[], | |||
| } | |||
| type team = { | |||
| @@ -35,11 +35,11 @@ type team = { | |||
| export type TeamLogInfo = { | |||
| id: number, | |||
| staffId: number, | |||
| staffName: String, | |||
| staffCode: String, | |||
| Team: team, | |||
| from: String, | |||
| to?: String, | |||
| staffName: string, | |||
| staffCode: string, | |||
| team: team, | |||
| from: number[], | |||
| to?: number[], | |||
| } | |||
| export const fetchGradesLog = cache(async (staffId: number) => { | |||
| @@ -10,17 +10,18 @@ import { | |||
| SubmitHandler, | |||
| useForm, | |||
| } from "react-hook-form"; | |||
| import { CreateStaffInputs, saveStaff } from "@/app/api/staff/actions"; | |||
| import { CreateStaffInputs, saveStaff, teamHistory } from "@/app/api/staff/actions"; | |||
| import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | |||
| // import CreateStaffForm from "../CreateStaffForm"; | |||
| import { comboProp } from "@/app/api/companys/actions"; | |||
| // import StaffInfo from "./StaffInfo"; | |||
| import { Check, Close, RestartAlt } from "@mui/icons-material"; | |||
| import { Check, Close, ConstructionOutlined, RestartAlt } from "@mui/icons-material"; | |||
| import StaffInfo from "./StaffInfo"; | |||
| import { IndividualStaff, projects, SalaryEffectiveInfo } from "@/app/api/staff"; | |||
| import dayjs from "dayjs"; | |||
| import ProjectHistory from "./ProjectHistory"; | |||
| import { InfoHistory } from "./EditStaffWrapper"; | |||
| import { fetchIndivTeam } from "@/app/api/team"; | |||
| // import { useGridApiContext } from '@mui/x-data-grid'; | |||
| export interface comboItem { | |||
| @@ -44,13 +45,15 @@ interface formProps { | |||
| const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, InvolvedProject, InfoHistory }) => { | |||
| console.log(InfoHistory) | |||
| console.log(combos.position) | |||
| const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id) | |||
| const { t } = useTranslation(); | |||
| const searchParams = useSearchParams() | |||
| const [tabIndex, setTabIndex] = useState(0); | |||
| const id = parseInt(searchParams.get("id") || "0"); | |||
| const formProps = useForm<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } & { delSalaryEffectiveInfo: number[] }>({ | |||
| const formProps = useForm<CreateStaffInputs | |||
| & { salaryEffectiveInfo: SalaryEffectiveInfo[] } | |||
| & { delSalaryEffectiveInfo: number[] }>({ | |||
| defaultValues: { | |||
| staffId: Staff.staffId, | |||
| name: Staff.name, | |||
| @@ -79,18 +82,42 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||
| salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label, | |||
| date: dayjs(item.date).toDate(), | |||
| })}), | |||
| delSalaryEffectiveInfo: [] | |||
| delSalaryEffectiveInfo: [], | |||
| teamHistory: InfoHistory.teamLog ? InfoHistory.teamLog.map(item => { | |||
| return ({ | |||
| id: item.id, | |||
| team: item.team.name, | |||
| from: dayjs(item.from.join()).toDate(), | |||
| to: item.to ? dayjs(item.to.join()).toDate() : "", | |||
| }) | |||
| }) : [], | |||
| delTeamHistory: [], | |||
| gradeHistory: InfoHistory.gradeLog ? InfoHistory.gradeLog.map(item => { | |||
| return ({ | |||
| id: item.id, | |||
| grade: item.grade.name, | |||
| from: dayjs(item.from.join()).toDate(), | |||
| to: item.to ? dayjs(item.to.join()).toDate() : "", | |||
| }) | |||
| }) : [], | |||
| delGradeHistory: [], | |||
| positionHistory: InfoHistory.positionLog ? InfoHistory.positionLog.map(item => { | |||
| return ({ | |||
| id: item.id, | |||
| position: item.position.name, | |||
| from: dayjs(item.from.join()).toDate(), | |||
| to: item.to ? dayjs(item.to.join()).toDate() : "", | |||
| }) | |||
| }) : [], | |||
| delPositionHistory: [], | |||
| }}); | |||
| const [serverError, setServerError] = useState(""); | |||
| const router = useRouter(); | |||
| // const [tabIndex, setTabIndex] = useState(0); | |||
| const errors = formProps.formState.errors; | |||
| const checkDuplicates = (str1: string, str2: string, str3: string) => { | |||
| return str1 === str2 || str1 === str3 || str2 === str3; | |||
| } | |||
| const onSubmit = useCallback<SubmitHandler<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } >>( | |||
| async (data) => { | |||
| try { | |||
| @@ -148,15 +175,45 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||
| if (haveError) { | |||
| return | |||
| } | |||
| console.log("passed") | |||
| const teamHistory = data.teamHistory.map((item) => ({ | |||
| id: item.id, | |||
| team: combos.team.filter(team => team.label === item.team)[0].id, | |||
| from: dayjs(item.from).format('YYYY-MM-DD'), | |||
| to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined, | |||
| })) | |||
| const gradeHistory = data.gradeHistory.map((item) => ({ | |||
| id: item.id, | |||
| grade: combos.grade.filter(grade => grade.label === item.grade)[0].id, | |||
| from: dayjs(item.from).format('YYYY-MM-DD'), | |||
| to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined, | |||
| })) | |||
| const positionHistory = data.positionHistory.map((item) => ({ | |||
| id: item.id, | |||
| position: combos.position.filter(position => position.label === item.position)[0].id, | |||
| from: dayjs(item.from).format('YYYY-MM-DD'), | |||
| to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined, | |||
| })) | |||
| console.log(teamHistory) | |||
| console.log(gradeHistory) | |||
| console.log(positionHistory) | |||
| const salaryEffectiveInfo = data.salaryEffectiveInfo.map((item: SalaryEffectiveInfo) => ({ | |||
| id: item.id, | |||
| salaryPoint: chopSalaryPoints(item.salaryPoint), | |||
| date: dayjs(item.date).format('YYYY-MM-DD').toString() | |||
| })) | |||
| const postData: CreateStaffInputs = { | |||
| id: id, | |||
| ...data, | |||
| salaryEffectiveInfo: data.salaryEffectiveInfo.map((item: SalaryEffectiveInfo) => ({ | |||
| id: item.id, | |||
| salaryPoint: chopSalaryPoints(item.salaryPoint), | |||
| date: dayjs(item.date).format('YYYY-MM-DD').toString() | |||
| })) | |||
| salaryEffectiveInfo: salaryEffectiveInfo, | |||
| teamHistory: teamHistory, | |||
| gradeHistory: gradeHistory, | |||
| positionHistory: positionHistory, | |||
| delTeamHistory: data.delTeamHistory ? data.delTeamHistory : [], | |||
| delGradeHistory: data.delGradeHistory ? data.delGradeHistory : [], | |||
| delPositionHistory: data.delPositionHistory ? data.delPositionHistory : [], | |||
| } | |||
| if (postData.joinDate) { | |||
| postData.joinDate = dayjs(postData.joinDate).format("YYYY-MM-DD") | |||
| @@ -166,6 +223,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||
| } | |||
| console.log(postData) | |||
| await saveStaff(postData) | |||
| return | |||
| router.replace("/settings/staff") | |||
| } catch (e: any) { | |||
| console.log(e); | |||
| @@ -203,61 +261,6 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||
| [] | |||
| ); | |||
| // 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(), | |||
| // })}) | |||
| // ) | |||
| // }, [formProps]); | |||
| // useEffect(() => { | |||
| // resetStaff() | |||
| // }, [Staff, formProps, combos]); | |||
| return ( | |||
| <> | |||
| <FormProvider {...formProps}> | |||
| @@ -283,11 +286,12 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||
| variant="scrollable" | |||
| > | |||
| <Tab label={t("Info")}/> | |||
| <Tab label={t("Involved Project History")} /> | |||
| <Tab label={t("Info History")} /> | |||
| <Tab label={t("Involved Project History")} /> | |||
| </Tabs> | |||
| </Stack> | |||
| {tabIndex == 0 && Staff && <StaffInfo combos={combos} />} | |||
| {tabIndex == 1 && <ProjectHistory InvolvedProject={InvolvedProject}/>} | |||
| {tabIndex == 2 && <ProjectHistory InvolvedProject={InvolvedProject}/>} | |||
| {tabIndex == 0 && | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| @@ -20,7 +20,7 @@ interface Props { | |||
| } | |||
| export type InfoHistory = { | |||
| gradesLog?: GradeLogInfo[], | |||
| gradeLog?: GradeLogInfo[], | |||
| positionLog?: PositionLogInfo[], | |||
| teamLog?: TeamLogInfo[], | |||
| } | |||
| @@ -41,7 +41,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||
| SalaryCombo, | |||
| SalaryEffectiveInfo, | |||
| InvolvedProject, | |||
| GradesLog, | |||
| GradeLog, | |||
| PositionLog, | |||
| TeamLog, | |||
| ] = await Promise.all([ | |||
| @@ -69,16 +69,15 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||
| skill: SkillCombo.records, | |||
| salary: SalaryCombo.records, | |||
| } | |||
| console.log(TeamLog) | |||
| const InfoHistory: InfoHistory = { | |||
| gradesLog: GradesLog, | |||
| gradeLog: GradeLog, | |||
| positionLog: PositionLog, | |||
| teamLog: TeamLog, | |||
| } | |||
| console.log(InfoHistory) | |||
| Staff.data.joinDate = Staff.data.joinDate && dateArrayToString(Staff.data.joinDate) as string | |||
| Staff.data.departDate = Staff.data.departDate && dateArrayToString(Staff.data.departDate) as string | |||
| Staff.data.joinDate = Staff.data.joinDate && dateArrayToString(Staff.data.joinDate) as string | |||
| Staff.data.departDate = Staff.data.departDate && dateArrayToString(Staff.data.departDate) as string | |||
| return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={SalaryEffectiveInfo} InvolvedProject={InvolvedProject} InfoHistory={InfoHistory}/>; | |||
| }; | |||
| @@ -0,0 +1,207 @@ | |||
| import { Box, Button, Modal, Paper, SxProps, Typography } from "@mui/material" | |||
| import StyledDataGrid from "../StyledDataGrid" | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { GridActionsCellItem, GridEventListener, GridRowEditStopReasons, GridRowModel, GridRowModes, GridRowModesModel } from "@mui/x-data-grid"; | |||
| 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'; | |||
| interface Props { | |||
| open: boolean; | |||
| onClose: () => void; | |||
| columns: any[] | |||
| } | |||
| const modalSx: SxProps = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| width: "90%", | |||
| maxWidth: "auto", | |||
| maxHeight: "auto", | |||
| padding: 3, | |||
| display: "flex", | |||
| flexDirection: "column", | |||
| gap: 2, | |||
| }; | |||
| const GradeHistoryModal: React.FC<Props> = async ({ open, onClose, columns }) => { | |||
| const { | |||
| t, | |||
| // i18n: { language }, | |||
| } = useTranslation(); | |||
| 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('gradeHistory') | |||
| return list && list.length > 0 ? list : [] | |||
| }); | |||
| const [_delRows, setDelRows] = useState<number[]>([]); | |||
| const formValues = watch(); | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| const handleRowEditStop: GridEventListener<"rowEditStop"> = ( | |||
| params, | |||
| event, | |||
| ) => { | |||
| if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||
| event.defaultMuiPrevented = true; | |||
| } | |||
| }; | |||
| // handle row update here | |||
| const processRowUpdate = | |||
| // useCallback( | |||
| (newRow: GridRowModel) => { | |||
| console.log(newRow) | |||
| const updatedRow = { ...newRow, updated: true }; | |||
| console.log(_rows) | |||
| if (_rows.length != 0) { | |||
| setRows((prev: any[]) => prev?.map((row: any) => (row.id === newRow.id ? updatedRow : row))); | |||
| } | |||
| return updatedRow; | |||
| } | |||
| // , [_rows, setValue, setRows]) | |||
| 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); | |||
| setDelRows((prevRowsId: number[]) => [...prevRowsId, id]) | |||
| }, | |||
| [setRows, setCount, setDelRows] | |||
| ); | |||
| useEffect(()=> { | |||
| console.log(_rows) | |||
| setValue('gradeHistory', _rows) | |||
| setValue('delGradeHistory', _delRows) | |||
| }, [_rows, _delRows]) | |||
| 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] | |||
| } | |||
| return ( | |||
| <Modal open={open} onClose={handleClose}> | |||
| <Paper sx={{ ...modalSx }}> | |||
| <Typography variant="h6" component="h2"> | |||
| {t('GradeHistoryModal')} | |||
| </Typography> | |||
| <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={handleClose}> | |||
| {t("Save")} | |||
| </Button> | |||
| </Box> | |||
| {/* </FormControl> */} | |||
| </Paper> | |||
| </Modal> | |||
| ) | |||
| } | |||
| export default GradeHistoryModal | |||
| @@ -1,11 +0,0 @@ | |||
| interface Props { | |||
| gradeLog?: any[] | |||
| } | |||
| const PositionGradeHistory: React.FC<Props> = async ({ gradeLog }) => { | |||
| return null | |||
| } | |||
| @@ -0,0 +1,207 @@ | |||
| import { Box, Button, Modal, Paper, SxProps, Typography } from "@mui/material" | |||
| import StyledDataGrid from "../StyledDataGrid" | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { GridActionsCellItem, GridEventListener, GridRowEditStopReasons, GridRowModel, GridRowModes, GridRowModesModel } from "@mui/x-data-grid"; | |||
| 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'; | |||
| interface Props { | |||
| open: boolean; | |||
| onClose: () => void; | |||
| columns: any[] | |||
| } | |||
| const modalSx: SxProps = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| width: "90%", | |||
| maxWidth: "auto", | |||
| maxHeight: "auto", | |||
| padding: 3, | |||
| display: "flex", | |||
| flexDirection: "column", | |||
| gap: 2, | |||
| }; | |||
| const PositionHistoryModal: React.FC<Props> = async ({ open, onClose, columns }) => { | |||
| const { | |||
| t, | |||
| // i18n: { language }, | |||
| } = useTranslation(); | |||
| 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('positionHistory') | |||
| return list && list.length > 0 ? list : [] | |||
| }); | |||
| const [_delRows, setDelRows] = useState<number[]>([]); | |||
| const formValues = watch(); | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| const handleRowEditStop: GridEventListener<"rowEditStop"> = ( | |||
| params, | |||
| event, | |||
| ) => { | |||
| if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||
| event.defaultMuiPrevented = true; | |||
| } | |||
| }; | |||
| // handle row update here | |||
| const processRowUpdate = | |||
| // useCallback( | |||
| (newRow: GridRowModel) => { | |||
| console.log(newRow) | |||
| const updatedRow = { ...newRow, updated: true }; | |||
| console.log(_rows) | |||
| if (_rows.length != 0) { | |||
| setRows((prev: any[]) => prev?.map((row: any) => (row.id === newRow.id ? updatedRow : row))); | |||
| } | |||
| return updatedRow; | |||
| } | |||
| // , [_rows, setValue, setRows]) | |||
| 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); | |||
| setDelRows((prevRowsId: number[]) => [...prevRowsId, id]) | |||
| }, | |||
| [setRows, setCount, setDelRows] | |||
| ); | |||
| useEffect(()=> { | |||
| console.log(_rows) | |||
| setValue('positionHistory', _rows) | |||
| setValue('delPositionHistory', _delRows) | |||
| }, [_rows, _delRows]) | |||
| 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] | |||
| } | |||
| return ( | |||
| <Modal open={open} onClose={handleClose}> | |||
| <Paper sx={{ ...modalSx }}> | |||
| <Typography variant="h6" component="h2"> | |||
| {t('PositionHistoryModal')} | |||
| </Typography> | |||
| <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={handleClose}> | |||
| {t("Save")} | |||
| </Button> | |||
| </Box> | |||
| {/* </FormControl> */} | |||
| </Paper> | |||
| </Modal> | |||
| ) | |||
| } | |||
| export default PositionHistoryModal | |||
| @@ -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, useMemo, useState } from "react"; | |||
| import { useCallback, useEffect, useMemo, useReducer, useState } from "react"; | |||
| import { CreateStaffInputs } from "@/app/api/staff/actions"; | |||
| import { | |||
| Button, | |||
| @@ -25,16 +25,50 @@ import { | |||
| 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"; | |||
| import SalaryEffectiveModel from "./SalaryEffectiveModel"; | |||
| import { SalaryEffectiveInfo, projects } from "@/app/api/staff"; | |||
| import TeamHistoryModal from "./TeamHistoryModal"; | |||
| import GradeHistoryModal from "./GradeHistoryModal"; | |||
| import PositionHistoryModal from "./PositionHistoryModal"; | |||
| interface Props { | |||
| combos: comboItem; | |||
| // InvolvedProject?: projects[] | |||
| } | |||
| // se = salary effective | |||
| const initState = { | |||
| teamModal: false, | |||
| seModal: false, | |||
| gradeModal: false, | |||
| positionModal: false, | |||
| } | |||
| const enum REDUCER_ACTION_TYPE { | |||
| TOGGLE_TEAM_MODAL, | |||
| TOGGLE_SALARY_EFFECTIVE_MODAL, | |||
| TOGGLE_GRADE_MODAL, | |||
| TOGGLE_POSITION_MODAL, | |||
| } | |||
| type ReducerAction = { | |||
| type: REDUCER_ACTION_TYPE | |||
| } | |||
| const reducer = (state: typeof initState, action: ReducerAction): typeof initState => { | |||
| switch (action.type) { | |||
| case REDUCER_ACTION_TYPE.TOGGLE_TEAM_MODAL: | |||
| return { ...state, teamModal: !state.teamModal }; | |||
| case REDUCER_ACTION_TYPE.TOGGLE_SALARY_EFFECTIVE_MODAL: | |||
| return { ...state, seModal: !state.seModal }; | |||
| case REDUCER_ACTION_TYPE.TOGGLE_GRADE_MODAL: | |||
| return { ...state, gradeModal: !state.gradeModal }; | |||
| case REDUCER_ACTION_TYPE.TOGGLE_POSITION_MODAL: | |||
| return { ...state, positionModal: !state.positionModal }; | |||
| default: | |||
| return state; | |||
| } | |||
| } | |||
| const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| const { | |||
| @@ -62,12 +96,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| (acc, skill) => ({ ...acc, [skill.id]: skill.label }), | |||
| {} | |||
| ); | |||
| // Salary Effiective History edit modal related | |||
| const [salaryEffectiveModelOpen, setSalaaryEffectiveModelOpen] = useState(false); | |||
| const controlSalaryEffectiveModel = useCallback(() => { | |||
| setSalaaryEffectiveModelOpen((prev: Boolean) => !prev); | |||
| }, []); | |||
| const [state, dispatch] = useReducer(reducer, initState) | |||
| const toggleSeModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_SALARY_EFFECTIVE_MODAL}) | |||
| const toggleTeamModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_TEAM_MODAL}) | |||
| const toggleGradeModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_GRADE_MODAL}) | |||
| const togglePositionModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_POSITION_MODAL}) | |||
| const resetStaff = useCallback(() => { | |||
| console.log(defaultValues); | |||
| @@ -80,6 +113,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| useEffect(() => { | |||
| resetStaff() | |||
| }, [defaultValues]); | |||
| useEffect(() => { | |||
| console.log(state) | |||
| }, [state]); | |||
| const joinDate = watch("joinDate"); | |||
| const departDate = watch("departDate"); | |||
| @@ -97,7 +134,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'singleSelect', | |||
| valueOptions: combos?.salary.map(item => item.label), | |||
| valueOptions: combos.salary.map((item) => item.label), | |||
| // valueOptions: [], | |||
| // width: 150 | |||
| }, | |||
| @@ -111,6 +148,90 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| }, | |||
| ], [combos]) | |||
| const teamHistoryCols = useMemo( | |||
| () => [ | |||
| { | |||
| field: 'team', | |||
| headerName: 'team', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'singleSelect', | |||
| valueOptions: combos.team.map(item => item.label), | |||
| // valueOptions: [], | |||
| // width: 150 | |||
| }, | |||
| { | |||
| field: 'from', | |||
| headerName: 'from', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'date', | |||
| }, | |||
| { | |||
| field: 'to', | |||
| headerName: 'to', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'date', | |||
| }, | |||
| ], [combos]) | |||
| const gradeHistoryCols = useMemo( | |||
| () => [ | |||
| { | |||
| field: 'grade', | |||
| headerName: 'grade', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'singleSelect', | |||
| valueOptions: combos.grade.map(item => item.label), | |||
| // valueOptions: [], | |||
| // width: 150 | |||
| }, | |||
| { | |||
| field: 'from', | |||
| headerName: 'from', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'date', | |||
| }, | |||
| { | |||
| field: 'to', | |||
| headerName: 'to', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'date', | |||
| }, | |||
| ], [combos]) | |||
| const positionHistoryCols = useMemo( | |||
| () => [ | |||
| { | |||
| field: 'position', | |||
| headerName: 'position', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'singleSelect', | |||
| valueOptions: combos.position.map(item => item.label), | |||
| // valueOptions: [], | |||
| // width: 150 | |||
| }, | |||
| { | |||
| field: 'from', | |||
| headerName: 'from', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'date', | |||
| }, | |||
| { | |||
| field: 'to', | |||
| headerName: 'to', | |||
| flex: 1, | |||
| editable: true, | |||
| type: 'date', | |||
| }, | |||
| ], [combos]) | |||
| return ( | |||
| <Card sx={{ display: "block" }}> | |||
| <CardContent component={Stack} spacing={4}> | |||
| @@ -185,17 +306,23 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| 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> | |||
| <Box display="flex" justifyContent="space-between" alignItems="center"> | |||
| <Select | |||
| label={t("Team")} | |||
| style={{ flex: 1, marginRight: '8px' }} | |||
| {...field} | |||
| // error={Boolean(errors.teamId)} | |||
| > | |||
| {combos.team.map((team, index) => ( | |||
| <MenuItem key={`${team.id}-${index}`} value={team.id}> | |||
| {t(team.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| <Button variant="contained" size="small" onClick={toggleTeamModal}> | |||
| {t("Team History")} | |||
| </Button> | |||
| </Box> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| @@ -232,17 +359,23 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| 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> | |||
| <Box display="flex" justifyContent="space-between" alignItems="center"> | |||
| <Select | |||
| label={t("Grade")} | |||
| style={{ flex: 1, marginRight: '8px' }} | |||
| {...field} | |||
| error={Boolean(errors.gradeId)} | |||
| > | |||
| {combos.grade.map((grade, index) => ( | |||
| <MenuItem key={`${grade.id}-${index}`} value={grade.id}> | |||
| {t(grade.label)} | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| <Button variant="contained" size="small" onClick={toggleGradeModal}> | |||
| {t("Grade History")} | |||
| </Button> | |||
| </Box> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| @@ -290,8 +423,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| control={control} | |||
| name="currentPositionId" | |||
| render={({ field }) => ( | |||
| <Select | |||
| <Box display="flex" justifyContent="space-between" alignItems="center"> | |||
| <Select | |||
| label={t("Current Position")} | |||
| style={{ flex: 1, marginRight: '8px' }} | |||
| {...field} | |||
| error={Boolean(errors.currentPositionId)} | |||
| > | |||
| @@ -304,6 +439,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| <Button variant="contained" size="small" onClick={togglePositionModal}> | |||
| {t("Position History")} | |||
| </Button> | |||
| </Box> | |||
| )} | |||
| /> | |||
| </FormControl> | |||
| @@ -332,7 +471,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| </MenuItem> | |||
| ))} | |||
| </Select> | |||
| <Button variant="contained" size="small" onClick={controlSalaryEffectiveModel}> | |||
| <Button variant="contained" size="small" onClick={toggleSeModal}> | |||
| {t("Edit Salary")} | |||
| </Button> | |||
| </Box> | |||
| @@ -414,38 +553,6 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| {/* <Grid container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}> */} | |||
| {/* <Grid item xs={6} md={3}> | |||
| <Typography sx={{ ml: 1 }} variant="h6" component="div"> | |||
| {t("on-going")} | |||
| </Typography> | |||
| <List> | |||
| {InvolvedProject.filter((item: projects) => item.status === "On-going") | |||
| .map((item: projects) => ( | |||
| <ListItem key={item.code}> | |||
| <ListItemText | |||
| primary={item.name} | |||
| secondary={item.code} | |||
| /> | |||
| </ListItem> | |||
| )) | |||
| } | |||
| </List> | |||
| </Grid> | |||
| <Grid item xs={6} md={3}> | |||
| <Typography sx={{ ml: 1 }} variant="h6" component="div"> | |||
| {t("completed")} | |||
| </Typography> | |||
| <List> | |||
| <ListItem> | |||
| <ListItemText | |||
| primary="Single-line item" | |||
| secondary={'Secondary text'} | |||
| /> | |||
| </ListItem> | |||
| </List> | |||
| </Grid> | |||
| </Grid> */} | |||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}> | |||
| {/* <Grid item xs={6}> | |||
| <TextField | |||
| @@ -593,10 +700,25 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||
| </Box> | |||
| </CardContent> | |||
| <SalaryEffectiveModel | |||
| open={salaryEffectiveModelOpen} | |||
| onClose={controlSalaryEffectiveModel} | |||
| open={state.seModal} | |||
| onClose={toggleSeModal} | |||
| columns={salaryCols} | |||
| /> | |||
| <TeamHistoryModal | |||
| open={state.teamModal} | |||
| onClose={toggleTeamModal} | |||
| columns={teamHistoryCols} | |||
| /> | |||
| <GradeHistoryModal | |||
| open={state.gradeModal} | |||
| onClose={toggleGradeModal} | |||
| columns={gradeHistoryCols} | |||
| /> | |||
| <PositionHistoryModal | |||
| open={state.positionModal} | |||
| onClose={togglePositionModal} | |||
| columns={positionHistoryCols} | |||
| /> | |||
| </Card> | |||
| ); | |||
| }; | |||
| @@ -0,0 +1,208 @@ | |||
| import { Box, Button, Modal, Paper, SxProps, Typography } from "@mui/material"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { GridActionsCellItem, GridEventListener, GridRowEditStopReasons, GridRowModel, GridRowModes, GridRowModesModel, GridToolbarContainer } from "@mui/x-data-grid"; | |||
| 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 { useFormContext } from "react-hook-form"; | |||
| interface Props { | |||
| open: boolean; | |||
| onClose: () => void; | |||
| columns: any[] | |||
| } | |||
| const modalSx: SxProps = { | |||
| position: "absolute", | |||
| top: "50%", | |||
| left: "50%", | |||
| transform: "translate(-50%, -50%)", | |||
| width: "90%", | |||
| maxWidth: "auto", | |||
| maxHeight: "auto", | |||
| padding: 3, | |||
| display: "flex", | |||
| flexDirection: "column", | |||
| gap: 2, | |||
| }; | |||
| const TeamHistoryModal: React.FC<Props> = async ({ open, onClose, columns }) => { | |||
| const { | |||
| t, | |||
| // i18n: { language }, | |||
| } = useTranslation(); | |||
| 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('teamHistory') | |||
| return list && list.length > 0 ? list : [] | |||
| }); | |||
| const [_delRows, setDelRows] = useState<number[]>([]); | |||
| const formValues = watch(); | |||
| const handleClose = () => { | |||
| onClose(); | |||
| }; | |||
| const handleRowEditStop: GridEventListener<"rowEditStop"> = ( | |||
| params, | |||
| event, | |||
| ) => { | |||
| if (params.reason === GridRowEditStopReasons.rowFocusOut) { | |||
| event.defaultMuiPrevented = true; | |||
| } | |||
| }; | |||
| // handle row update here | |||
| const processRowUpdate = | |||
| // useCallback( | |||
| (newRow: GridRowModel) => { | |||
| console.log(newRow) | |||
| const updatedRow = { ...newRow, updated: true }; | |||
| console.log(_rows) | |||
| if (_rows.length != 0) { | |||
| setRows((prev: any[]) => prev?.map((row: any) => (row.id === newRow.id ? updatedRow : row))); | |||
| } | |||
| return updatedRow; | |||
| } | |||
| // , [_rows, setValue, setRows]) | |||
| 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); | |||
| setDelRows((prevRowsId: number[]) => [...prevRowsId, id]) | |||
| }, | |||
| [setRows, setCount, setDelRows] | |||
| ); | |||
| useEffect(()=> { | |||
| console.log(_rows) | |||
| setValue('teamHistory', _rows) | |||
| setValue('delTeamHistory', _delRows) | |||
| }, [_rows, _delRows]) | |||
| 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] | |||
| } | |||
| return ( | |||
| <Modal open={open} onClose={handleClose}> | |||
| <Paper sx={{ ...modalSx }}> | |||
| <Typography variant="h6" component="h2"> | |||
| {t('TeamHistoryModal')} | |||
| </Typography> | |||
| <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={handleClose}> | |||
| {t("Save")} | |||
| </Button> | |||
| </Box> | |||
| {/* </FormControl> */} | |||
| </Paper> | |||
| </Modal> | |||
| ) | |||
| } | |||
| export default TeamHistoryModal | |||