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