| @@ -30,6 +30,17 @@ export type IndivStaff = { | |||||
| data: IndividualStaff | data: IndividualStaff | ||||
| } | } | ||||
| export type projects = { | |||||
| id: number, | |||||
| code: string, | |||||
| name: string, | |||||
| status: string | |||||
| } | |||||
| // export type InvolvedProject = { | |||||
| // records: projects[] | |||||
| // } | |||||
| export type IndividualStaff = { | export type IndividualStaff = { | ||||
| id: number | id: number | ||||
| staffId: string | staffId: string | ||||
| @@ -110,6 +121,12 @@ export const fetchIndivStaff = cache(async (id: number) => { | |||||
| }); | }); | ||||
| }); | }); | ||||
| export const fetchStaffInvolvedProjects = cache(async (id: number) => { | |||||
| return serverFetchJson<projects[]>(`${BASE_API_URL}/staffs/staff-projects/${id}`, { | |||||
| next: { tags: ["staffs"] }, | |||||
| }); | |||||
| }); | |||||
| export const fetchStaffWithoutTeam = cache(async () => { | export const fetchStaffWithoutTeam = cache(async () => { | ||||
| return serverFetchJson<StaffResult[]>(`${BASE_API_URL}/staffs/noteam`, { | return serverFetchJson<StaffResult[]>(`${BASE_API_URL}/staffs/noteam`, { | ||||
| next: { tags: ["staffs"] }, | next: { tags: ["staffs"] }, | ||||
| @@ -11,14 +11,15 @@ import { | |||||
| useForm, | useForm, | ||||
| } from "react-hook-form"; | } from "react-hook-form"; | ||||
| import { CreateStaffInputs, saveStaff } from "@/app/api/staff/actions"; | import { CreateStaffInputs, saveStaff } from "@/app/api/staff/actions"; | ||||
| import { Button, Stack, 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, RestartAlt } from "@mui/icons-material"; | ||||
| import StaffInfo from "./StaffInfo"; | import StaffInfo from "./StaffInfo"; | ||||
| import { IndividualStaff, 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 { useGridApiContext } from '@mui/x-data-grid'; | // import { useGridApiContext } from '@mui/x-data-grid'; | ||||
| export interface comboItem { | export interface comboItem { | ||||
| @@ -35,14 +36,16 @@ interface formProps { | |||||
| Staff: IndividualStaff | Staff: IndividualStaff | ||||
| combos: comboItem; | combos: comboItem; | ||||
| SalaryEffectiveInfo: SalaryEffectiveInfo[]; | SalaryEffectiveInfo: SalaryEffectiveInfo[]; | ||||
| InvolvedProject?: projects[] | |||||
| } | } | ||||
| const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) => { | |||||
| // console.log(Staff.joinDate) | |||||
| const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo, InvolvedProject }) => { | |||||
| console.log(InvolvedProject) | |||||
| 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 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: { | ||||
| @@ -190,6 +193,13 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||||
| return null; | return null; | ||||
| } | } | ||||
| const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>( | |||||
| (_e, newValue) => { | |||||
| setTabIndex(newValue); | |||||
| }, | |||||
| [] | |||||
| ); | |||||
| // const resetStaff = useCallback(() => { | // const resetStaff = useCallback(() => { | ||||
| // window.location.reload() | // window.location.reload() | ||||
| // console.log(dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT)) | // console.log(dayjs(Staff.joinDate).format(INPUT_DATE_FORMAT)) | ||||
| @@ -258,7 +268,24 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||||
| {serverError} | {serverError} | ||||
| </Typography> | </Typography> | ||||
| )} | )} | ||||
| {Staff && <StaffInfo combos={combos}/>} | |||||
| <Stack | |||||
| direction="row" | |||||
| justifyContent="space-between" | |||||
| flexWrap="wrap" | |||||
| rowGap={2} | |||||
| > | |||||
| <Tabs | |||||
| value={tabIndex} | |||||
| onChange={handleTabChange} | |||||
| variant="scrollable" | |||||
| > | |||||
| <Tab label={t("Info")}/> | |||||
| <Tab label={t("Involved Project History")} /> | |||||
| </Tabs> | |||||
| </Stack> | |||||
| {tabIndex == 0 && Staff && <StaffInfo combos={combos} />} | |||||
| {tabIndex == 1 && <ProjectHistory InvolvedProject={InvolvedProject}/>} | |||||
| {tabIndex == 0 && | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | <Stack direction="row" justifyContent="flex-end" gap={1}> | ||||
| <Button | <Button | ||||
| variant="text" | variant="text" | ||||
| @@ -283,6 +310,7 @@ const EditStaff: React.FC<formProps> = ({ Staff, combos, SalaryEffectiveInfo }) | |||||
| {t("Confirm")} | {t("Confirm")} | ||||
| </Button> | </Button> | ||||
| </Stack> | </Stack> | ||||
| } | |||||
| </Stack> | </Stack> | ||||
| </FormProvider> | </FormProvider> | ||||
| </> | </> | ||||
| @@ -1,8 +1,7 @@ | |||||
| import React from "react"; | import React from "react"; | ||||
| import EditStaff, { comboItem } from "./EditStaff"; | import EditStaff, { comboItem } from "./EditStaff"; | ||||
| import EditStaffLoading from "./EditStaffLoading"; | import EditStaffLoading from "./EditStaffLoading"; | ||||
| import { StaffResult, fetchIndivStaff, fetchStaff, fetchStaffSalaryEffectiveInfo, fetchTeamLeads, preloadStaff } from "@/app/api/staff"; | |||||
| import { useSearchParams } from "next/navigation"; | |||||
| import { fetchIndivStaff, fetchStaffInvolvedProjects, fetchStaffSalaryEffectiveInfo, preloadStaff } from "@/app/api/staff"; | |||||
| import { fetchTeamCombo } from "@/app/api/team/actions"; | import { fetchTeamCombo } from "@/app/api/team/actions"; | ||||
| import { fetchDepartmentCombo } from "@/app/api/departments/actions"; | import { fetchDepartmentCombo } from "@/app/api/departments/actions"; | ||||
| import { fetchPositionCombo } from "@/app/api/positions/actions"; | import { fetchPositionCombo } from "@/app/api/positions/actions"; | ||||
| @@ -23,6 +22,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| id | id | ||||
| }) => { | }) => { | ||||
| preloadStaff() | preloadStaff() | ||||
| const [ | const [ | ||||
| Staff, | Staff, | ||||
| CompanyCombo, | CompanyCombo, | ||||
| @@ -33,6 +33,7 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| SkillCombo, | SkillCombo, | ||||
| SalaryCombo, | SalaryCombo, | ||||
| SalaryEffectiveInfo, | SalaryEffectiveInfo, | ||||
| InvolvedProject | |||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchIndivStaff(id), | fetchIndivStaff(id), | ||||
| fetchCompanyCombo(), | fetchCompanyCombo(), | ||||
| @@ -43,8 +44,10 @@ const EditStaffWrapper: React.FC<Props> & SubComponents = async ({ | |||||
| fetchSkillCombo(), | fetchSkillCombo(), | ||||
| fetchSalaryCombo(), | fetchSalaryCombo(), | ||||
| fetchStaffSalaryEffectiveInfo(id), | fetchStaffSalaryEffectiveInfo(id), | ||||
| fetchStaffInvolvedProjects(id) | |||||
| ]); | ]); | ||||
| console.log(InvolvedProject) | |||||
| console.log(SalaryCombo.records) | console.log(SalaryCombo.records) | ||||
| const combos: comboItem = { | const combos: comboItem = { | ||||
| company: CompanyCombo.records, | company: CompanyCombo.records, | ||||
| @@ -61,7 +64,7 @@ Staff.data.departDate = Staff.data.departDate && dateArrayToString(Staff.data.de | |||||
| // [{id:0, salaryPoint: 1, date:"2021-05-05"}, {id:1, salaryPoint: 43, date:"2024-05-05"}] | // [{id:0, salaryPoint: 1, date:"2021-05-05"}, {id:1, salaryPoint: 43, date:"2024-05-05"}] | ||||
| console.log(Staff.data) | console.log(Staff.data) | ||||
| return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={SalaryEffectiveInfo}/>; | |||||
| return <EditStaff Staff={Staff.data} combos={combos} SalaryEffectiveInfo={SalaryEffectiveInfo} InvolvedProject={InvolvedProject}/>; | |||||
| }; | }; | ||||
| EditStaffWrapper.Loading = EditStaffLoading; | EditStaffWrapper.Loading = EditStaffLoading; | ||||
| @@ -0,0 +1,53 @@ | |||||
| import { projects } from "@/app/api/staff"; | |||||
| import { Box, Card, CardContent, Grid, Stack } from "@mui/material"; | |||||
| import StyledDataGrid from "../StyledDataGrid"; | |||||
| import { useMemo } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | |||||
| interface Props { | |||||
| InvolvedProject?: projects[] | |||||
| } | |||||
| const ProjectHistory: React.FC<Props> = async ({ InvolvedProject }) => { | |||||
| const { t } = useTranslation(); | |||||
| const projectCols = useMemo( | |||||
| () => [ | |||||
| { | |||||
| field: 'code', | |||||
| headerName: t("Code"), | |||||
| flex: .4, | |||||
| }, | |||||
| { | |||||
| field: 'name', | |||||
| headerName: t("Name"), | |||||
| flex: 1, | |||||
| }, | |||||
| ], [InvolvedProject]) | |||||
| return ( | |||||
| <Card sx={{ display: "block" }}> | |||||
| <CardContent component={Stack} spacing={4}> | |||||
| <Box> | |||||
| <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
| <Grid item xs={6}> | |||||
| <StyledDataGrid | |||||
| rows={InvolvedProject?.filter(item => item.status === "On-going") ?? []} | |||||
| columns={projectCols} | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={6}> | |||||
| <StyledDataGrid | |||||
| rows={InvolvedProject?.filter(item => item.status === "Completed") ?? []} | |||||
| columns={projectCols} | |||||
| /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ) | |||||
| } | |||||
| export default ProjectHistory; | |||||
| @@ -16,6 +16,8 @@ import { | |||||
| Checkbox, | Checkbox, | ||||
| FormControl, | FormControl, | ||||
| InputLabel, | InputLabel, | ||||
| List, | |||||
| ListItem, | |||||
| ListItemText, | ListItemText, | ||||
| MenuItem, | MenuItem, | ||||
| Select, | Select, | ||||
| @@ -27,10 +29,11 @@ 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 } from "@/app/api/staff"; | |||||
| import { SalaryEffectiveInfo, projects } from "@/app/api/staff"; | |||||
| interface Props { | interface Props { | ||||
| combos: comboItem; | combos: comboItem; | ||||
| // InvolvedProject?: projects[] | |||||
| } | } | ||||
| const StaffInfo: React.FC<Props> = ({ combos }) => { | const StaffInfo: React.FC<Props> = ({ combos }) => { | ||||
| @@ -411,8 +414,40 @@ 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 | ||||
| label={t("Emergency Contact Name")} | label={t("Emergency Contact Name")} | ||||
| fullWidth | fullWidth | ||||
| @@ -447,7 +482,7 @@ const StaffInfo: React.FC<Props> = ({ combos }) => { | |||||
| : t("Please input correct Emergency Contact Phone")) | : t("Please input correct Emergency Contact Phone")) | ||||
| } | } | ||||
| /> | /> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| <Grid item xs={6}> | <Grid item xs={6}> | ||||
| <LocalizationProvider | <LocalizationProvider | ||||
| dateAdapter={AdapterDayjs} | dateAdapter={AdapterDayjs} | ||||
| @@ -65,8 +65,8 @@ const TimesheetMailDetails: React.FC<Props> = ({ isActive }) => { | |||||
| label={t("Required Params")} | label={t("Required Params")} | ||||
| fullWidth | fullWidth | ||||
| value={"${date}"} | value={"${date}"} | ||||
| disabled | |||||
| error={Boolean(errors.template?.template)} | |||||
| // disabled | |||||
| // error={Boolean(errors.template?.template)} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||