Procházet zdrojové kódy

update writing grade/position/team to logs when edit

tags/Baseline_180220205_Frontend
MSI\derek před 11 měsíci
rodič
revize
3c798ea98f
10 změnil soubory, kde provedl 944 přidání a 179 odebrání
  1. +15
    -11
      src/app/(main)/settings/staff/edit/page.tsx
  2. +25
    -0
      src/app/api/staff/actions.ts
  3. +9
    -9
      src/app/api/staffInfoHistory/index.ts
  4. +78
    -74
      src/components/EditStaff/EditStaff.tsx
  5. +6
    -7
      src/components/EditStaff/EditStaffWrapper.tsx
  6. +207
    -0
      src/components/EditStaff/GradeHistoryModal.tsx
  7. +0
    -11
      src/components/EditStaff/PositionGradeHistory.tsx
  8. +207
    -0
      src/components/EditStaff/PositionHistoryModal.tsx
  9. +189
    -67
      src/components/EditStaff/StaffInfo.tsx
  10. +208
    -0
      src/components/EditStaff/TeamHistoryModal.tsx

+ 15
- 11
src/app/(main)/settings/staff/edit/page.tsx Zobrazit soubor

@@ -15,6 +15,7 @@ import { fetchPositionCombo } from "@/app/api/positions/actions";
import { fetchGradeCombo } from "@/app/api/grades/actions";
import { fetchSkillCombo } from "@/app/api/skill/actions";
import { fetchSalaryCombo } from "@/app/api/salarys/actions";
import { fetchGradesLog, fetchPositionLog, fetchTeamLog } from "@/app/api/staffInfoHistory";

// export const metadata: Metadata = {
// title: "staff-edit",
@@ -23,18 +24,21 @@ import { fetchSalaryCombo } from "@/app/api/salarys/actions";
const EditStaffPage: React.FC<searchParamsProps> = async ({
searchParams,
}) => {
const id = parseInt(searchParams.id as string)
// preload
fetchIndivStaff(parseInt(searchParams.id as string)),
fetchCompanyCombo(),
fetchTeamCombo(),
fetchDepartmentCombo(),
fetchPositionCombo(),
fetchGradeCombo(),
fetchSkillCombo(),
fetchSalaryCombo(),
fetchStaffSalaryEffectiveInfo(parseInt(searchParams.id as string)),
fetchStaffInvolvedProjects(parseInt(searchParams.id as string))
fetchIndivStaff(id)
fetchCompanyCombo()
fetchTeamCombo()
fetchDepartmentCombo()
fetchPositionCombo()
fetchGradeCombo()
fetchSkillCombo()
fetchSalaryCombo()
fetchStaffSalaryEffectiveInfo(id)
fetchStaffInvolvedProjects(id)
fetchGradesLog(id)
fetchPositionLog(id)
fetchTeamLog(id)

return (
<>


+ 25
- 0
src/app/api/staff/actions.ts Zobrazit soubor

@@ -13,7 +13,26 @@ export interface CreateCustomInputs {
// Miscellaneous
expectedProjectFee: string;
}
export type teamHistory = {
id: number,
team: string | number,
from: Date | string,
to?: Date | string
}

export type gradeHistory = {
id: number,
grade: string | number,
from: Date | string,
to?: Date | string
}

export type positionHistory = {
id: number,
position: string | number,
from: Date | string,
to?: Date | string
}
export interface CreateStaffInputs {
id?: number
name: string;
@@ -37,6 +56,12 @@ export interface CreateStaffInputs {
departReason?: string;
remark?: string;
salaryEffectiveInfo?: any;
teamHistory: teamHistory[];
delTeamHistory?: number[];
gradeHistory: gradeHistory[];
delGradeHistory?: number[];
positionHistory: positionHistory[];
delPositionHistory?: number[];
}
export interface records {


+ 9
- 9
src/app/api/staffInfoHistory/index.ts Zobrazit soubor

@@ -10,8 +10,8 @@ export type GradeLogInfo = {
staffName: String,
staffCode: String,
grade: Grade,
from: String,
to?: String,
from: number[],
to?: number[],
}

export type PositionLogInfo = {
@@ -20,8 +20,8 @@ export type PositionLogInfo = {
staffName: String,
staffCode: String,
position: PositionResult,
from: String,
to?: String,
from: number[],
to?: number[],
}

type team = {
@@ -35,11 +35,11 @@ type team = {
export type TeamLogInfo = {
id: number,
staffId: number,
staffName: String,
staffCode: String,
Team: team,
from: String,
to?: String,
staffName: string,
staffCode: string,
team: team,
from: number[],
to?: number[],
}

export const fetchGradesLog = cache(async (staffId: number) => {


+ 78
- 74
src/components/EditStaff/EditStaff.tsx Zobrazit soubor

@@ -10,17 +10,18 @@ import {
SubmitHandler,
useForm,
} from "react-hook-form";
import { CreateStaffInputs, saveStaff } from "@/app/api/staff/actions";
import { CreateStaffInputs, saveStaff, teamHistory } from "@/app/api/staff/actions";
import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
// import CreateStaffForm from "../CreateStaffForm";
import { comboProp } from "@/app/api/companys/actions";
// import StaffInfo from "./StaffInfo";
import { Check, Close, RestartAlt } from "@mui/icons-material";
import { Check, Close, ConstructionOutlined, RestartAlt } from "@mui/icons-material";
import StaffInfo from "./StaffInfo";
import { IndividualStaff, projects, SalaryEffectiveInfo } from "@/app/api/staff";
import dayjs from "dayjs";
import ProjectHistory from "./ProjectHistory";
import { InfoHistory } from "./EditStaffWrapper";
import { fetchIndivTeam } from "@/app/api/team";
// import { useGridApiContext } from '@mui/x-data-grid';

export interface comboItem {
@@ -44,13 +45,15 @@ interface formProps {


const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, InvolvedProject, InfoHistory }) => {
console.log(InfoHistory)
console.log(combos.position)
const defaultSkillset = Staff.skillset.map((s: any) => s.skill.id)
const { t } = useTranslation();
const searchParams = useSearchParams()
const [tabIndex, setTabIndex] = useState(0);
const id = parseInt(searchParams.get("id") || "0");
const formProps = useForm<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } & { delSalaryEffectiveInfo: number[] }>({
const formProps = useForm<CreateStaffInputs
& { salaryEffectiveInfo: SalaryEffectiveInfo[] }
& { delSalaryEffectiveInfo: number[] }>({
defaultValues: {
staffId: Staff.staffId,
name: Staff.name,
@@ -79,18 +82,42 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In
salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label,
date: dayjs(item.date).toDate(),
})}),
delSalaryEffectiveInfo: []
delSalaryEffectiveInfo: [],
teamHistory: InfoHistory.teamLog ? InfoHistory.teamLog.map(item => {
return ({
id: item.id,
team: item.team.name,
from: dayjs(item.from.join()).toDate(),
to: item.to ? dayjs(item.to.join()).toDate() : "",
})
}) : [],
delTeamHistory: [],
gradeHistory: InfoHistory.gradeLog ? InfoHistory.gradeLog.map(item => {
return ({
id: item.id,
grade: item.grade.name,
from: dayjs(item.from.join()).toDate(),
to: item.to ? dayjs(item.to.join()).toDate() : "",
})
}) : [],
delGradeHistory: [],
positionHistory: InfoHistory.positionLog ? InfoHistory.positionLog.map(item => {
return ({
id: item.id,
position: item.position.name,
from: dayjs(item.from.join()).toDate(),
to: item.to ? dayjs(item.to.join()).toDate() : "",
})
}) : [],
delPositionHistory: [],
}});

const [serverError, setServerError] = useState("");
const router = useRouter();
// const [tabIndex, setTabIndex] = useState(0);

const errors = formProps.formState.errors;

const checkDuplicates = (str1: string, str2: string, str3: string) => {
return str1 === str2 || str1 === str3 || str2 === str3;
}

const onSubmit = useCallback<SubmitHandler<CreateStaffInputs & { salaryEffectiveInfo: SalaryEffectiveInfo[] } >>(
async (data) => {
try {
@@ -148,15 +175,45 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In
if (haveError) {
return
}
console.log("passed")
const teamHistory = data.teamHistory.map((item) => ({
id: item.id,
team: combos.team.filter(team => team.label === item.team)[0].id,
from: dayjs(item.from).format('YYYY-MM-DD'),
to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined,
}))
const gradeHistory = data.gradeHistory.map((item) => ({
id: item.id,
grade: combos.grade.filter(grade => grade.label === item.grade)[0].id,
from: dayjs(item.from).format('YYYY-MM-DD'),
to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined,
}))
const positionHistory = data.positionHistory.map((item) => ({
id: item.id,
position: combos.position.filter(position => position.label === item.position)[0].id,
from: dayjs(item.from).format('YYYY-MM-DD'),
to: (item.to as string).length != 0 ? dayjs(item.to).format('YYYY-MM-DD') : undefined,
}))
console.log(teamHistory)
console.log(gradeHistory)
console.log(positionHistory)
const salaryEffectiveInfo = data.salaryEffectiveInfo.map((item: SalaryEffectiveInfo) => ({
id: item.id,
salaryPoint: chopSalaryPoints(item.salaryPoint),
date: dayjs(item.date).format('YYYY-MM-DD').toString()
}))

const postData: CreateStaffInputs = {
id: id,
...data,
salaryEffectiveInfo: data.salaryEffectiveInfo.map((item: SalaryEffectiveInfo) => ({
id: item.id,
salaryPoint: chopSalaryPoints(item.salaryPoint),
date: dayjs(item.date).format('YYYY-MM-DD').toString()
}))
salaryEffectiveInfo: salaryEffectiveInfo,
teamHistory: teamHistory,
gradeHistory: gradeHistory,
positionHistory: positionHistory,
delTeamHistory: data.delTeamHistory ? data.delTeamHistory : [],
delGradeHistory: data.delGradeHistory ? data.delGradeHistory : [],
delPositionHistory: data.delPositionHistory ? data.delPositionHistory : [],
}
if (postData.joinDate) {
postData.joinDate = dayjs(postData.joinDate).format("YYYY-MM-DD")
@@ -166,6 +223,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In
}
console.log(postData)
await saveStaff(postData)
return
router.replace("/settings/staff")
} catch (e: any) {
console.log(e);
@@ -203,61 +261,6 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In
[]
);

// const resetStaff = useCallback(() => {
// window.location.reload()
// console.log(dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT))
// console.log(formProps.getValues("joinDate"))
// formProps.setValue('salaryEffectiveInfo', SalaryEffectiveInfo.map(item => {
// return ({
// id: item.id,
// salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label,
// date: dayjs(item.date).toDate(),
// })}))

// formProps.reset({
// staffId: Staff.staffId,
// name: Staff.name,
// companyId: Staff.company.id,
// teamId: Staff.team?.id,
// departmentId: Staff.department?.id,
// gradeId: Staff.grade?.id,
// skillSetId: defaultSkillset,
// currentPositionId: Staff.currentPosition?.id,
// salaryId: Staff.salary.salaryPoint,
// employType: Staff.employType,
// email: Staff.email,
// phone1: Staff.phone1,
// phone2: Staff.phone2,
// emergContactName: Staff.emergContactName,
// emergContactPhone: Staff.emergContactPhone,
// joinDate: Staff.joinDate ? dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT) : "",
// joinPositionId: Staff.joinPosition?.id,
// departDate: !Staff.departDate ? "" : dayjs(Staff.departDate).format(INPUT_DATE_FORMAT),
// departReason: Staff.departReason,
// remark: Staff.remark,
// salaryEffectiveInfo: SalaryEffectiveInfo.map(item => {
// return ({
// id: item.id,
// salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label,
// date: dayjs(item.date).toDate(),
// })})
// });
// }, []);
// useEffect(() => {
// formProps.setValue('salaryEffectiveInfo', SalaryEffectiveInfo.map(item => {
// return ({
// id: item.id,
// salaryPoint: combos.salary.filter(sal => sal.id === item.salaryPoint)[0].label,
// date: dayjs(item.date).toDate(),
// })})
// )
// }, [formProps]);
// useEffect(() => {
// resetStaff()
// }, [Staff, formProps, combos]);

return (
<>
<FormProvider {...formProps}>
@@ -283,11 +286,12 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, In
variant="scrollable"
>
<Tab label={t("Info")}/>
<Tab label={t("Involved Project History")} />
<Tab label={t("Info History")} />
<Tab label={t("Involved Project History")} />
</Tabs>
</Stack>
{tabIndex == 0 && Staff && <StaffInfo combos={combos} />}
{tabIndex == 1 && <ProjectHistory InvolvedProject={InvolvedProject}/>}
{tabIndex == 2 && <ProjectHistory InvolvedProject={InvolvedProject}/>}
{tabIndex == 0 &&
<Stack direction="row" justifyContent="flex-end" gap={1}>
<Button


+ 6
- 7
src/components/EditStaff/EditStaffWrapper.tsx Zobrazit soubor

@@ -20,7 +20,7 @@ interface Props {
}

export type InfoHistory = {
gradesLog?: GradeLogInfo[],
gradeLog?: GradeLogInfo[],
positionLog?: PositionLogInfo[],
teamLog?: TeamLogInfo[],
}
@@ -41,7 +41,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({
SalaryCombo,
SalaryEffectiveInfo,
InvolvedProject,
GradesLog,
GradeLog,
PositionLog,
TeamLog,
] = await Promise.all([
@@ -69,16 +69,15 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({
skill: SkillCombo.records,
salary: SalaryCombo.records,
}
console.log(TeamLog)
const InfoHistory: InfoHistory = {
gradesLog: GradesLog,
gradeLog: GradeLog,
positionLog: PositionLog,
teamLog: TeamLog,
}
console.log(InfoHistory)

Staff.data.joinDate = Staff.data.joinDate && dateArrayToString(Staff.data.joinDate) as string
Staff.data.departDate = Staff.data.departDate && dateArrayToString(Staff.data.departDate) as string
Staff.data.joinDate = Staff.data.joinDate && dateArrayToString(Staff.data.joinDate) as string
Staff.data.departDate = Staff.data.departDate && dateArrayToString(Staff.data.departDate) as string

return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={SalaryEffectiveInfo} InvolvedProject={InvolvedProject} InfoHistory={InfoHistory}/>;
};


+ 207
- 0
src/components/EditStaff/GradeHistoryModal.tsx Zobrazit soubor

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

+ 0
- 11
src/components/EditStaff/PositionGradeHistory.tsx Zobrazit soubor

@@ -1,11 +0,0 @@

interface Props {
gradeLog?: any[]
}


const PositionGradeHistory: React.FC<Props> = async ({ gradeLog }) => {


return null
}

+ 207
- 0
src/components/EditStaff/PositionHistoryModal.tsx Zobrazit soubor

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

+ 189
- 67
src/components/EditStaff/StaffInfo.tsx Zobrazit soubor

@@ -9,7 +9,7 @@ import Typography from "@mui/material/Typography";
import { CreateGroupInputs } from "@/app/api/group/actions";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useReducer, useState } from "react";
import { CreateStaffInputs } from "@/app/api/staff/actions";
import {
Button,
@@ -25,16 +25,50 @@ import {
import { comboItem } from "./EditStaff";
import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import { DemoItem } from "@mui/x-date-pickers/internals/demo";
import dayjs from "dayjs";
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import SalaryEffectiveModel from "./SalaryEffectiveModel";
import { SalaryEffectiveInfo, projects } from "@/app/api/staff";
import TeamHistoryModal from "./TeamHistoryModal";
import GradeHistoryModal from "./GradeHistoryModal";
import PositionHistoryModal from "./PositionHistoryModal";

interface Props {
combos: comboItem;
// InvolvedProject?: projects[]
}
// se = salary effective
const initState = {
teamModal: false,
seModal: false,
gradeModal: false,
positionModal: false,
}
const enum REDUCER_ACTION_TYPE {
TOGGLE_TEAM_MODAL,
TOGGLE_SALARY_EFFECTIVE_MODAL,
TOGGLE_GRADE_MODAL,
TOGGLE_POSITION_MODAL,
}

type ReducerAction = {
type: REDUCER_ACTION_TYPE
}

const reducer = (state: typeof initState, action: ReducerAction): typeof initState => {
switch (action.type) {
case REDUCER_ACTION_TYPE.TOGGLE_TEAM_MODAL:
return { ...state, teamModal: !state.teamModal };
case REDUCER_ACTION_TYPE.TOGGLE_SALARY_EFFECTIVE_MODAL:
return { ...state, seModal: !state.seModal };
case REDUCER_ACTION_TYPE.TOGGLE_GRADE_MODAL:
return { ...state, gradeModal: !state.gradeModal };
case REDUCER_ACTION_TYPE.TOGGLE_POSITION_MODAL:
return { ...state, positionModal: !state.positionModal };
default:
return state;
}
}

const StaffInfo: React.FC<Props> = ({ combos }) => {
const {
@@ -62,12 +96,11 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
(acc, skill) => ({ ...acc, [skill.id]: skill.label }),
{}
);

// Salary Effiective History edit modal related
const [salaryEffectiveModelOpen, setSalaaryEffectiveModelOpen] = useState(false);
const controlSalaryEffectiveModel = useCallback(() => {
setSalaaryEffectiveModelOpen((prev: Boolean) => !prev);
}, []);
const [state, dispatch] = useReducer(reducer, initState)
const toggleSeModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_SALARY_EFFECTIVE_MODAL})
const toggleTeamModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_TEAM_MODAL})
const toggleGradeModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_GRADE_MODAL})
const togglePositionModal = () => dispatch({ type: REDUCER_ACTION_TYPE.TOGGLE_POSITION_MODAL})

const resetStaff = useCallback(() => {
console.log(defaultValues);
@@ -80,6 +113,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
useEffect(() => {
resetStaff()
}, [defaultValues]);
useEffect(() => {
console.log(state)
}, [state]);

const joinDate = watch("joinDate");
const departDate = watch("departDate");
@@ -97,7 +134,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
flex: 1,
editable: true,
type: 'singleSelect',
valueOptions: combos?.salary.map(item => item.label),
valueOptions: combos.salary.map((item) => item.label),
// valueOptions: [],
// width: 150
},
@@ -111,6 +148,90 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
},
], [combos])

const teamHistoryCols = useMemo(
() => [
{
field: 'team',
headerName: 'team',
flex: 1,
editable: true,
type: 'singleSelect',
valueOptions: combos.team.map(item => item.label),
// valueOptions: [],
// width: 150
},
{
field: 'from',
headerName: 'from',
flex: 1,
editable: true,
type: 'date',
},
{
field: 'to',
headerName: 'to',
flex: 1,
editable: true,
type: 'date',
},
], [combos])

const gradeHistoryCols = useMemo(
() => [
{
field: 'grade',
headerName: 'grade',
flex: 1,
editable: true,
type: 'singleSelect',
valueOptions: combos.grade.map(item => item.label),
// valueOptions: [],
// width: 150
},
{
field: 'from',
headerName: 'from',
flex: 1,
editable: true,
type: 'date',
},
{
field: 'to',
headerName: 'to',
flex: 1,
editable: true,
type: 'date',
},
], [combos])

const positionHistoryCols = useMemo(
() => [
{
field: 'position',
headerName: 'position',
flex: 1,
editable: true,
type: 'singleSelect',
valueOptions: combos.position.map(item => item.label),
// valueOptions: [],
// width: 150
},
{
field: 'from',
headerName: 'from',
flex: 1,
editable: true,
type: 'date',
},
{
field: 'to',
headerName: 'to',
flex: 1,
editable: true,
type: 'date',
},
], [combos])

return (
<Card sx={{ display: "block" }}>
<CardContent component={Stack} spacing={4}>
@@ -185,17 +306,23 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
control={control}
name="teamId"
render={({ field }) => (
<Select
label={t("Team")}
{...field}
// error={Boolean(errors.teamId)}
>
{combos.team.map((team, index) => (
<MenuItem key={`${team.id}-${index}`} value={team.id}>
{t(team.label)}
</MenuItem>
))}
</Select>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Select
label={t("Team")}
style={{ flex: 1, marginRight: '8px' }}
{...field}
// error={Boolean(errors.teamId)}
>
{combos.team.map((team, index) => (
<MenuItem key={`${team.id}-${index}`} value={team.id}>
{t(team.label)}
</MenuItem>
))}
</Select>
<Button variant="contained" size="small" onClick={toggleTeamModal}>
{t("Team History")}
</Button>
</Box>
)}
/>
</FormControl>
@@ -232,17 +359,23 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
control={control}
name="gradeId"
render={({ field }) => (
<Select
label={t("Grade")}
{...field}
error={Boolean(errors.gradeId)}
>
{combos.grade.map((grade, index) => (
<MenuItem key={`${grade.id}-${index}`} value={grade.id}>
{t(grade.label)}
</MenuItem>
))}
</Select>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Select
label={t("Grade")}
style={{ flex: 1, marginRight: '8px' }}
{...field}
error={Boolean(errors.gradeId)}
>
{combos.grade.map((grade, index) => (
<MenuItem key={`${grade.id}-${index}`} value={grade.id}>
{t(grade.label)}
</MenuItem>
))}
</Select>
<Button variant="contained" size="small" onClick={toggleGradeModal}>
{t("Grade History")}
</Button>
</Box>
)}
/>
</FormControl>
@@ -290,8 +423,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
control={control}
name="currentPositionId"
render={({ field }) => (
<Select
<Box display="flex" justifyContent="space-between" alignItems="center">
<Select
label={t("Current Position")}
style={{ flex: 1, marginRight: '8px' }}
{...field}
error={Boolean(errors.currentPositionId)}
>
@@ -304,6 +439,10 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
</MenuItem>
))}
</Select>
<Button variant="contained" size="small" onClick={togglePositionModal}>
{t("Position History")}
</Button>
</Box>
)}
/>
</FormControl>
@@ -332,7 +471,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
</MenuItem>
))}
</Select>
<Button variant="contained" size="small" onClick={controlSalaryEffectiveModel}>
<Button variant="contained" size="small" onClick={toggleSeModal}>
{t("Edit Salary")}
</Button>
</Box>
@@ -414,38 +553,6 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
/>
</Grid>
</Grid>
{/* <Grid container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}> */}
{/* <Grid item xs={6} md={3}>
<Typography sx={{ ml: 1 }} variant="h6" component="div">
{t("on-going")}
</Typography>
<List>
{InvolvedProject.filter((item: projects) => item.status === "On-going")
.map((item: projects) => (
<ListItem key={item.code}>
<ListItemText
primary={item.name}
secondary={item.code}
/>
</ListItem>
))
}
</List>
</Grid>
<Grid item xs={6} md={3}>
<Typography sx={{ ml: 1 }} variant="h6" component="div">
{t("completed")}
</Typography>
<List>
<ListItem>
<ListItemText
primary="Single-line item"
secondary={'Secondary text'}
/>
</ListItem>
</List>
</Grid>
</Grid> */}
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }} marginTop={3}>
{/* <Grid item xs={6}>
<TextField
@@ -593,10 +700,25 @@ const StaffInfo: React.FC<Props> = ({ combos }) => {
</Box>
</CardContent>
<SalaryEffectiveModel
open={salaryEffectiveModelOpen}
onClose={controlSalaryEffectiveModel}
open={state.seModal}
onClose={toggleSeModal}
columns={salaryCols}
/>
<TeamHistoryModal
open={state.teamModal}
onClose={toggleTeamModal}
columns={teamHistoryCols}
/>
<GradeHistoryModal
open={state.gradeModal}
onClose={toggleGradeModal}
columns={gradeHistoryCols}
/>
<PositionHistoryModal
open={state.positionModal}
onClose={togglePositionModal}
columns={positionHistoryCols}
/>
</Card>
);
};


+ 208
- 0
src/components/EditStaff/TeamHistoryModal.tsx Zobrazit soubor

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

Načítá se…
Zrušit
Uložit