| @@ -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 | |||||