@@ -15,6 +15,7 @@ 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 { fetchGradesLog, fetchPositionLog, fetchTeamLog } from "@/app/api/staffInfoHistory"; | |||||
// export const metadata: Metadata = { | // export const metadata: Metadata = { | ||||
// title: "staff-edit", | // title: "staff-edit", | ||||
@@ -23,18 +24,21 @@ import { fetchSalaryCombo } from "@/app/api/salarys/actions"; | |||||
const EditStaffPage: React.FC<searchParamsProps> = async ({ | const EditStaffPage: React.FC<searchParamsProps> = async ({ | ||||
searchParams, | searchParams, | ||||
}) => { | }) => { | ||||
const id = parseInt(searchParams.id as string) | |||||
// preload | // 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 ( | return ( | ||||
<> | <> | ||||
@@ -13,7 +13,26 @@ export interface CreateCustomInputs { | |||||
// Miscellaneous | // Miscellaneous | ||||
expectedProjectFee: string; | 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 { | export interface CreateStaffInputs { | ||||
id?: number | id?: number | ||||
name: string; | name: string; | ||||
@@ -37,6 +56,12 @@ export interface CreateStaffInputs { | |||||
departReason?: string; | departReason?: string; | ||||
remark?: string; | remark?: string; | ||||
salaryEffectiveInfo?: any; | salaryEffectiveInfo?: any; | ||||
teamHistory: teamHistory[]; | |||||
delTeamHistory?: number[]; | |||||
gradeHistory: gradeHistory[]; | |||||
delGradeHistory?: number[]; | |||||
positionHistory: positionHistory[]; | |||||
delPositionHistory?: number[]; | |||||
} | } | ||||
export interface records { | export interface records { | ||||
@@ -10,8 +10,8 @@ export type GradeLogInfo = { | |||||
staffName: String, | staffName: String, | ||||
staffCode: String, | staffCode: String, | ||||
grade: Grade, | grade: Grade, | ||||
from: String, | |||||
to?: String, | |||||
from: number[], | |||||
to?: number[], | |||||
} | } | ||||
export type PositionLogInfo = { | export type PositionLogInfo = { | ||||
@@ -20,8 +20,8 @@ export type PositionLogInfo = { | |||||
staffName: String, | staffName: String, | ||||
staffCode: String, | staffCode: String, | ||||
position: PositionResult, | position: PositionResult, | ||||
from: String, | |||||
to?: String, | |||||
from: number[], | |||||
to?: number[], | |||||
} | } | ||||
type team = { | type team = { | ||||
@@ -35,11 +35,11 @@ type team = { | |||||
export type TeamLogInfo = { | export type TeamLogInfo = { | ||||
id: number, | id: number, | ||||
staffId: 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) => { | export const fetchGradesLog = cache(async (staffId: number) => { | ||||
@@ -10,17 +10,18 @@ import { | |||||
SubmitHandler, | SubmitHandler, | ||||
useForm, | useForm, | ||||
} from "react-hook-form"; | } 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 { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; | ||||
// import CreateStaffForm from "../CreateStaffForm"; | // import CreateStaffForm from "../CreateStaffForm"; | ||||
import { comboProp } from "@/app/api/companys/actions"; | import { comboProp } from "@/app/api/companys/actions"; | ||||
// import StaffInfo from "./StaffInfo"; | // 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 StaffInfo from "./StaffInfo"; | ||||
import { IndividualStaff, projects, SalaryEffectiveInfo } from "@/app/api/staff"; | import { IndividualStaff, projects, SalaryEffectiveInfo } from "@/app/api/staff"; | ||||
import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
import ProjectHistory from "./ProjectHistory"; | import ProjectHistory from "./ProjectHistory"; | ||||
import { InfoHistory } from "./EditStaffWrapper"; | import { InfoHistory } from "./EditStaffWrapper"; | ||||
import { fetchIndivTeam } from "@/app/api/team"; | |||||
// import { useGridApiContext } from '@mui/x-data-grid'; | // import { useGridApiContext } from '@mui/x-data-grid'; | ||||
export interface comboItem { | export interface comboItem { | ||||
@@ -44,13 +45,15 @@ interface formProps { | |||||
const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, InvolvedProject, InfoHistory }) => { | 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 defaultSkillset = Staff.skillset.map((s: any) => s.skill.id) | ||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const searchParams = useSearchParams() | const searchParams = useSearchParams() | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const id = parseInt(searchParams.get("id") || "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: { | defaultValues: { | ||||
staffId: Staff.staffId, | staffId: Staff.staffId, | ||||
name: Staff.name, | 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, | salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label, | ||||
date: dayjs(item.date).toDate(), | 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 [serverError, setServerError] = useState(""); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
// const [tabIndex, setTabIndex] = useState(0); | |||||
const errors = formProps.formState.errors; | 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[] } >>( | const onSubmit = useCallback<SubmitHandler<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } >>( | ||||
async (data) => { | async (data) => { | ||||
try { | try { | ||||
@@ -148,15 +175,45 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||||
if (haveError) { | if (haveError) { | ||||
return | 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 = { | const postData: CreateStaffInputs = { | ||||
id: id, | id: id, | ||||
...data, | ...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) { | if (postData.joinDate) { | ||||
postData.joinDate = dayjs(postData.joinDate).format("YYYY-MM-DD") | 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) | console.log(postData) | ||||
await saveStaff(postData) | await saveStaff(postData) | ||||
return | |||||
router.replace("/settings/staff") | router.replace("/settings/staff") | ||||
} catch (e: any) { | } catch (e: any) { | ||||
console.log(e); | 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 ( | return ( | ||||
<> | <> | ||||
<FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
@@ -283,11 +286,12 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In | |||||
variant="scrollable" | variant="scrollable" | ||||
> | > | ||||
<Tab label={t("Info")}/> | <Tab label={t("Info")}/> | ||||
<Tab label={t("Involved Project History")} /> | |||||
<Tab label={t("Info History")} /> | |||||
<Tab label={t("Involved Project History")} /> | |||||
</Tabs> | </Tabs> | ||||
</Stack> | </Stack> | ||||
{tabIndex == 0 && Staff && <StaffInfo combos={combos} />} | {tabIndex == 0 && Staff && <StaffInfo combos={combos} />} | ||||
{tabIndex == 1 && <ProjectHistory InvolvedProject={InvolvedProject}/>} | |||||
{tabIndex == 2 && <ProjectHistory InvolvedProject={InvolvedProject}/>} | |||||
{tabIndex == 0 && | {tabIndex == 0 && | ||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
<Button | <Button | ||||
@@ -20,7 +20,7 @@ interface Props { | |||||
} | } | ||||
export type InfoHistory = { | export type InfoHistory = { | ||||
gradesLog?: GradeLogInfo[], | |||||
gradeLog?: GradeLogInfo[], | |||||
positionLog?: PositionLogInfo[], | positionLog?: PositionLogInfo[], | ||||
teamLog?: TeamLogInfo[], | teamLog?: TeamLogInfo[], | ||||
} | } | ||||
@@ -41,7 +41,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
SalaryCombo, | SalaryCombo, | ||||
SalaryEffectiveInfo, | SalaryEffectiveInfo, | ||||
InvolvedProject, | InvolvedProject, | ||||
GradesLog, | |||||
GradeLog, | |||||
PositionLog, | PositionLog, | ||||
TeamLog, | TeamLog, | ||||
] = await Promise.all([ | ] = await Promise.all([ | ||||
@@ -69,16 +69,15 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
skill: SkillCombo.records, | skill: SkillCombo.records, | ||||
salary: SalaryCombo.records, | salary: SalaryCombo.records, | ||||
} | } | ||||
console.log(TeamLog) | |||||
const InfoHistory: InfoHistory = { | const InfoHistory: InfoHistory = { | ||||
gradesLog: GradesLog, | |||||
gradeLog: GradeLog, | |||||
positionLog: PositionLog, | positionLog: PositionLog, | ||||
teamLog: TeamLog, | 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}/>; | 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 { 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, useMemo, useState } from "react"; | |||||
import { useCallback, useEffect, useMemo, useReducer, useState } from "react"; | |||||
import { CreateStaffInputs } from "@/app/api/staff/actions"; | import { CreateStaffInputs } from "@/app/api/staff/actions"; | ||||
import { | import { | ||||
Button, | Button, | ||||
@@ -25,16 +25,50 @@ import { | |||||
import { comboItem } from "./EditStaff"; | import { comboItem } from "./EditStaff"; | ||||
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | ||||
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | ||||
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 SalaryEffectiveModel from "./SalaryEffectiveModel"; | ||||
import { SalaryEffectiveInfo, projects } from "@/app/api/staff"; | import { SalaryEffectiveInfo, projects } from "@/app/api/staff"; | ||||
import TeamHistoryModal from "./TeamHistoryModal"; | |||||
import GradeHistoryModal from "./GradeHistoryModal"; | |||||
import PositionHistoryModal from "./PositionHistoryModal"; | |||||
interface Props { | interface Props { | ||||
combos: comboItem; | combos: comboItem; | ||||
// InvolvedProject?: projects[] | // 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 StaffInfo: React.FC<Props> = ({ combos }) => { | ||||
const { | const { | ||||
@@ -62,12 +96,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
(acc, skill) => ({ ...acc, [skill.id]: skill.label }), | (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(() => { | const resetStaff = useCallback(() => { | ||||
console.log(defaultValues); | console.log(defaultValues); | ||||
@@ -80,6 +113,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
useEffect(() => { | useEffect(() => { | ||||
resetStaff() | resetStaff() | ||||
}, [defaultValues]); | }, [defaultValues]); | ||||
useEffect(() => { | |||||
console.log(state) | |||||
}, [state]); | |||||
const joinDate = watch("joinDate"); | const joinDate = watch("joinDate"); | ||||
const departDate = watch("departDate"); | const departDate = watch("departDate"); | ||||
@@ -97,7 +134,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
flex: 1, | flex: 1, | ||||
editable: true, | editable: true, | ||||
type: 'singleSelect', | type: 'singleSelect', | ||||
valueOptions: combos?.salary.map(item => item.label), | |||||
valueOptions: combos.salary.map((item) => item.label), | |||||
// valueOptions: [], | // valueOptions: [], | ||||
// width: 150 | // width: 150 | ||||
}, | }, | ||||
@@ -111,6 +148,90 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
}, | }, | ||||
], [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 ( | return ( | ||||
<Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
<CardContent component={Stack} spacing={4}> | <CardContent component={Stack} spacing={4}> | ||||
@@ -185,17 +306,23 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
control={control} | control={control} | ||||
name="teamId" | name="teamId" | ||||
render={({ field }) => ( | 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> | </FormControl> | ||||
@@ -232,17 +359,23 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
control={control} | control={control} | ||||
name="gradeId" | name="gradeId" | ||||
render={({ field }) => ( | 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> | </FormControl> | ||||
@@ -290,8 +423,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
control={control} | control={control} | ||||
name="currentPositionId" | name="currentPositionId" | ||||
render={({ field }) => ( | render={({ field }) => ( | ||||
<Select | |||||
<Box display="flex" justifyContent="space-between" alignItems="center"> | |||||
<Select | |||||
label={t("Current Position")} | label={t("Current Position")} | ||||
style={{ flex: 1, marginRight: '8px' }} | |||||
{...field} | {...field} | ||||
error={Boolean(errors.currentPositionId)} | error={Boolean(errors.currentPositionId)} | ||||
> | > | ||||
@@ -304,6 +439,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
</MenuItem> | </MenuItem> | ||||
))} | ))} | ||||
</Select> | </Select> | ||||
<Button variant="contained" size="small" onClick={togglePositionModal}> | |||||
{t("Position History")} | |||||
</Button> | |||||
</Box> | |||||
)} | )} | ||||
/> | /> | ||||
</FormControl> | </FormControl> | ||||
@@ -332,7 +471,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
</MenuItem> | </MenuItem> | ||||
))} | ))} | ||||
</Select> | </Select> | ||||
<Button variant="contained" size="small" onClick={controlSalaryEffectiveModel}> | |||||
<Button variant="contained" size="small" onClick={toggleSeModal}> | |||||
{t("Edit Salary")} | {t("Edit Salary")} | ||||
</Button> | </Button> | ||||
</Box> | </Box> | ||||
@@ -414,38 +553,6 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
/> | /> | ||||
</Grid> | </Grid> | ||||
</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 container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}> | ||||
{/* <Grid item xs={6}> | {/* <Grid item xs={6}> | ||||
<TextField | <TextField | ||||
@@ -593,10 +700,25 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
</Box> | </Box> | ||||
</CardContent> | </CardContent> | ||||
<SalaryEffectiveModel | <SalaryEffectiveModel | ||||
open={salaryEffectiveModelOpen} | |||||
onClose={controlSalaryEffectiveModel} | |||||
open={state.seModal} | |||||
onClose={toggleSeModal} | |||||
columns={salaryCols} | 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> | </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 |