@@ -2,19 +2,23 @@ import { Edit } from "@mui/icons-material"; | |||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
// import EditStaff from "@/components/EditStaff"; | // import EditStaff from "@/components/EditStaff"; | ||||
import { Suspense } from "react"; | import { Suspense } from "react"; | ||||
import { I18nProvider } from "@/i18n"; | |||||
import { I18nProvider, getServerI18n } from "@/i18n"; | |||||
// import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; | // import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; | ||||
import { Metadata } from "next"; | import { Metadata } from "next"; | ||||
import EditTeam from "@/components/EditTeam"; | import EditTeam from "@/components/EditTeam"; | ||||
import { searchParamsProps } from "@/app/utils/fetchUtil"; | |||||
const EditTeamPage: React.FC = () => { | |||||
const EditTeamPage: React.FC<searchParamsProps> = async ({ | |||||
searchParams, | |||||
}) => { | |||||
const { t } = await getServerI18n("team"); | |||||
return ( | return ( | ||||
<> | <> | ||||
<I18nProvider namespaces={["team", "common"]}> | <I18nProvider namespaces={["team", "common"]}> | ||||
<Suspense fallback={<EditTeam.Loading />}> | <Suspense fallback={<EditTeam.Loading />}> | ||||
<EditTeam /> | |||||
<EditTeam id={parseInt(searchParams.id as string)}/> | |||||
</Suspense> | </Suspense> | ||||
</I18nProvider> | </I18nProvider> | ||||
{/* <EditStaff /> */} | {/* <EditStaff /> */} | ||||
@@ -8,7 +8,8 @@ import { revalidateTag } from "next/cache"; | |||||
export interface CreateTeamInputs { | export interface CreateTeamInputs { | ||||
id: number | null; | id: number | null; | ||||
// name: string; | |||||
name: string; | |||||
code: string; | |||||
// team: string; | // team: string; | ||||
// staffId: string; | // staffId: string; | ||||
// grade: string; | // grade: string; | ||||
@@ -15,7 +15,19 @@ export interface TeamResult { | |||||
posLabel: string; | posLabel: string; | ||||
posCode: string; | posCode: string; | ||||
teamLead: number; | teamLead: number; | ||||
} | |||||
export type IndivTeam = { | |||||
team: IndividualTeam | |||||
staffIds: number[] | |||||
} | |||||
export type IndividualTeam = { | |||||
id: number; | |||||
description: string; | |||||
name: string; | |||||
code: string; | |||||
teamLead: number; | |||||
} | } | ||||
export interface comboProp { | export interface comboProp { | ||||
@@ -33,6 +45,12 @@ export const fetchTeam = cache(async () => { | |||||
}); | }); | ||||
}); | }); | ||||
export const fetchIndivTeam = cache(async (id: number) => { | |||||
return serverFetchJson<IndivTeam>(`${BASE_API_URL}/team/${id}`, { | |||||
next: { tags: ["team"] }, | |||||
}); | |||||
}); | |||||
export const preloadTeamDetail = () => { | export const preloadTeamDetail = () => { | ||||
fetchTeamDetail(); | fetchTeamDetail(); | ||||
}; | }; | ||||
@@ -43,6 +43,28 @@ const TeamInfo: React.FC = ( | |||||
{t("Team Info")} | {t("Team Info")} | ||||
</Typography> | </Typography> | ||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | <Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | ||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("name")} | |||||
fullWidth | |||||
{...register("name", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.name)} | |||||
helperText={Boolean(errors.name) && (errors.name?.message ? t(errors.name.message) : t("Please input correct name"))} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("code")} | |||||
fullWidth | |||||
{...register("code", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.code)} | |||||
helperText={Boolean(errors.code) && (errors.code?.message ? t(errors.code.message) : t("Please input correct code"))} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={12}> | <Grid item xs={12}> | ||||
<TextField | <TextField | ||||
label={t("Team Description")} | label={t("Team Description")} | ||||
@@ -62,6 +62,10 @@ const Allocation: React.FC<Props> = ({ allStaffs: staff, teamLead }) => { | |||||
); | ); | ||||
const [deletedStaffIds, setDeletedStaffIds] = useState<number[]>([]); | const [deletedStaffIds, setDeletedStaffIds] = useState<number[]>([]); | ||||
console.log(getValues("addStaffIds")); | |||||
console.log(filteredStaff); | |||||
console.log(selectedStaff) | |||||
// Adding / Removing staff | // Adding / Removing staff | ||||
const addStaff = useCallback((staff: StaffResult) => { | const addStaff = useCallback((staff: StaffResult) => { | ||||
setSelectedStaff((s) => [...s, staff]); | setSelectedStaff((s) => [...s, staff]); | ||||
@@ -1,6 +1,6 @@ | |||||
"use client"; | "use client"; | ||||
import { useRouter, useSearchParams } from "next/navigation"; | import { useRouter, useSearchParams } from "next/navigation"; | ||||
import { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import React, { useCallback, useEffect, useMemo, useState } from "react"; | |||||
import SearchResults, { Column } from "../SearchResults"; | import SearchResults, { Column } from "../SearchResults"; | ||||
// import { TeamResult } from "@/app/api/team"; | // import { TeamResult } from "@/app/api/team"; | ||||
import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
@@ -13,40 +13,27 @@ import { | |||||
useForm, | useForm, | ||||
useFormContext, | useFormContext, | ||||
} from "react-hook-form"; | } from "react-hook-form"; | ||||
import { Check, Close, Error } from "@mui/icons-material"; | |||||
import { Check, Close, Error, RestartAlt } from "@mui/icons-material"; | |||||
import TeamInfo from "../EditTeam/TeamInfo"; | import TeamInfo from "../EditTeam/TeamInfo"; | ||||
import Allocation from "./Allocation"; | import Allocation from "./Allocation"; | ||||
import { StaffResult } from "@/app/api/staff"; | import { StaffResult } from "@/app/api/staff"; | ||||
interface desc { | |||||
id: number; | |||||
name: string; | |||||
description: string; | |||||
teamLead: number; | |||||
} | |||||
import { IndivTeam } from "@/app/api/team"; | |||||
interface Props { | interface Props { | ||||
staff: StaffResult[]; | staff: StaffResult[]; | ||||
desc: desc[]; | |||||
// teamLead: StaffResult[] | |||||
teamInfo: IndivTeam; | |||||
} | } | ||||
const EditTeam: React.FC<Props> = async ({ staff, desc }) => { | |||||
// console.log(desc) | |||||
const EditTeam: React.FC<Props> = async ({ staff, teamInfo }) => { | |||||
const { team, staffIds } = teamInfo; | |||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const formProps = useForm<CreateTeamInputs>(); | const formProps = useForm<CreateTeamInputs>(); | ||||
const searchParams = useSearchParams(); | const searchParams = useSearchParams(); | ||||
const idString = searchParams.get("id"); | const idString = searchParams.get("id"); | ||||
const [filteredItems, setFilteredItems] = useState<StaffResult[]>(); | |||||
const [allStaffs, setAllStaffs] = useState<StaffResult[]>(); | const [allStaffs, setAllStaffs] = useState<StaffResult[]>(); | ||||
const [filteredDesc, setFilteredDesc] = useState<string>(); | |||||
const [filteredName, setFilteredName] = useState<string>(); | |||||
const [teamLead, setTeamLead] = useState<number>(); | const [teamLead, setTeamLead] = useState<number>(); | ||||
const [tabIndex, setTabIndex] = useState(0); | const [tabIndex, setTabIndex] = useState(0); | ||||
const router = useRouter(); | const router = useRouter(); | ||||
// const [selectedStaff, setSelectedStaff] = useState<typeof filteredItems>( | |||||
// initialStaffs.filter((s) => getValues("addStaffIds")?.includes(s.id)) | |||||
// ); | |||||
const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
@@ -58,64 +45,36 @@ const EditTeam: React.FC<Props> = async ({ staff, desc }) => { | |||||
); | ); | ||||
const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
const cols = useMemo<Column<StaffResult>[]>( | |||||
() => [ | |||||
{ label: t("Staff Id"), name: "staffId" }, | |||||
{ label: t("Name"), name: "staffName" }, | |||||
// { label: t("Current Position"), name: "posCode" }, | |||||
], | |||||
[t] | |||||
); | |||||
useEffect(() => { | useEffect(() => { | ||||
let idList: number[] = [] | |||||
let idList: number[] = []; | |||||
// console.log(desc) | // console.log(desc) | ||||
if (idString) { | if (idString) { | ||||
const filteredTeam = staff.filter( | |||||
(item) => { | |||||
return (item.teamId === parseInt(idString))} | |||||
); | |||||
// console.log(filteredTeam) | |||||
const tempDesc = desc.filter( | |||||
(item) => item.id === parseInt(idString) | |||||
) | |||||
// const leader = teamLead.filter( | |||||
// (staff) => staff.teamId === parseInt(idString) | |||||
// ) | |||||
// console.log(leader) | |||||
console.log(tempDesc[0].teamLead) | |||||
setTeamLead(tempDesc[0].teamLead) | |||||
const filteredTeam = staff.filter((item) => { | |||||
return item.teamId === parseInt(idString); | |||||
}); | |||||
console.log(team.teamLead); | |||||
setTeamLead(team.teamLead); | |||||
if (filteredTeam.length > 0) { | if (filteredTeam.length > 0) { | ||||
const filteredIds: number[] = filteredTeam.map((i) => ( | |||||
i.id | |||||
)) | |||||
// const teamLead = tempDesc[0].teamLead | |||||
// const index = filteredIds.indexOf(teamLead); | |||||
// if (index !== -1) { | |||||
// filteredIds.splice(index, 1); | |||||
// filteredIds.unshift(teamLead); | |||||
// } | |||||
const filteredIds: number[] = filteredTeam.map((i) => i.id); | |||||
// const teamLead = tempDesc[0].teamLead | |||||
// const index = filteredIds.indexOf(teamLead); | |||||
// if (index !== -1) { | |||||
// filteredIds.splice(index, 1); | |||||
// filteredIds.unshift(teamLead); | |||||
// } | |||||
idList = filteredIds | |||||
// console.log(filteredIds) | |||||
idList = filteredIds; | |||||
} | } | ||||
// console.log(idList) | |||||
setFilteredItems(filteredTeam); | |||||
formProps.reset({description: tempDesc[0].description, addStaffIds: idList}) | |||||
setFilteredDesc(tempDesc[0].description) | |||||
setFilteredName(tempDesc[0].name) | |||||
formProps.reset({ description: team.description, addStaffIds: idList }); | |||||
} | } | ||||
// console.log(staff) | // console.log(staff) | ||||
setAllStaffs(staff) | |||||
setAllStaffs(staff); | |||||
}, [searchParams]); | }, [searchParams]); | ||||
// useEffect(() => { | |||||
// console.log(allStaffs) | |||||
// }, [allStaffs]); | |||||
const hasErrorsInTab = ( | const hasErrorsInTab = ( | ||||
tabIndex: number, | tabIndex: number, | ||||
errors: FieldErrors<CreateTeamInputs> | errors: FieldErrors<CreateTeamInputs> | ||||
@@ -133,14 +92,14 @@ const EditTeam: React.FC<Props> = async ({ staff, desc }) => { | |||||
try { | try { | ||||
// console.log(data); | // console.log(data); | ||||
const tempData = { | const tempData = { | ||||
description: data.description, | |||||
addStaffIds: data.addStaffIds, | |||||
deleteStaffIds: data.deleteStaffIds, | |||||
id: parseInt(idString!!) | |||||
} | |||||
console.log(tempData) | |||||
await saveTeam(tempData); | |||||
router.replace("/settings/team"); | |||||
description: data.description, | |||||
addStaffIds: data.addStaffIds, | |||||
deleteStaffIds: data.deleteStaffIds, | |||||
id: parseInt(idString!!), | |||||
}; | |||||
console.log(tempData); | |||||
// await saveTeam(tempData); | |||||
// router.replace("/settings/team"); | |||||
} catch (e) { | } catch (e) { | ||||
console.log(e); | console.log(e); | ||||
setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
@@ -149,6 +108,20 @@ const EditTeam: React.FC<Props> = async ({ staff, desc }) => { | |||||
[router] | [router] | ||||
); | ); | ||||
const resetTeam = React.useCallback(() => { | |||||
formProps.reset({ | |||||
name: team.name, | |||||
code: team.code, | |||||
description: team.description, | |||||
addStaffIds: staffIds, | |||||
deleteStaffIds: [] | |||||
}); | |||||
}, []); | |||||
useEffect(() => { | |||||
resetTeam(); | |||||
}, [team]); | |||||
return ( | return ( | ||||
<> | <> | ||||
{serverError && ( | {serverError && ( | ||||
@@ -157,41 +130,50 @@ const EditTeam: React.FC<Props> = async ({ staff, desc }) => { | |||||
</Typography> | </Typography> | ||||
)} | )} | ||||
<FormProvider {...formProps}> | <FormProvider {...formProps}> | ||||
<Stack | |||||
<Stack | |||||
spacing={2} | spacing={2} | ||||
component="form" | component="form" | ||||
onSubmit={formProps.handleSubmit(onSubmit)} | onSubmit={formProps.handleSubmit(onSubmit)} | ||||
> | > | ||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Edit Team")} - {filteredName} | |||||
</Typography> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent="space-between" | |||||
flexWrap="wrap" | |||||
rowGap={2} | |||||
> | |||||
<Tabs | |||||
value={tabIndex} | |||||
onChange={handleTabChange} | |||||
variant="scrollable" | |||||
<Typography variant="h4" marginInlineEnd={2}> | |||||
{t("Edit Team")} | |||||
{/* - {team.name} */} | |||||
</Typography> | |||||
<Stack | |||||
direction="row" | |||||
justifyContent="space-between" | |||||
flexWrap="wrap" | |||||
rowGap={2} | |||||
> | > | ||||
<Tab | |||||
label={t("Team Info")} | |||||
icon={ | |||||
hasErrorsInTab(0, errors) ? ( | |||||
<Error sx={{ marginInlineEnd: 1 }} color="error" /> | |||||
) : undefined | |||||
} | |||||
iconPosition="end" | |||||
/> | |||||
<Tab label={t("Staff Allocation")} iconPosition="end" /> | |||||
</Tabs> | |||||
</Stack> | |||||
{tabIndex === 0 && <TeamInfo value={filteredDesc!!} />} | |||||
{tabIndex === 1 && <Allocation allStaffs={allStaffs!!} teamLead={teamLead!!}/>} | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Tabs | |||||
value={tabIndex} | |||||
onChange={handleTabChange} | |||||
variant="scrollable" | |||||
> | |||||
<Tab | |||||
label={t("Team Info")} | |||||
icon={ | |||||
hasErrorsInTab(0, errors) ? ( | |||||
<Error sx={{ marginInlineEnd: 1 }} color="error" /> | |||||
) : undefined | |||||
} | |||||
iconPosition="end" | |||||
/> | |||||
<Tab label={t("Staff Allocation")} iconPosition="end" /> | |||||
</Tabs> | |||||
</Stack> | |||||
{tabIndex === 0 && <TeamInfo />} | |||||
{tabIndex === 1 && ( | |||||
<Allocation allStaffs={allStaffs!!} teamLead={teamLead!!} /> | |||||
)} | |||||
<Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
<Button | |||||
variant="text" | |||||
startIcon={<RestartAlt />} | |||||
onClick={resetTeam} | |||||
> | |||||
{t("Reset")} | |||||
</Button> | |||||
<Button | <Button | ||||
variant="outlined" | variant="outlined" | ||||
startIcon={<Close />} | startIcon={<Close />} | ||||
@@ -208,7 +190,7 @@ const EditTeam: React.FC<Props> = async ({ staff, desc }) => { | |||||
{t("Confirm")} | {t("Confirm")} | ||||
</Button> | </Button> | ||||
</Stack> | </Stack> | ||||
</Stack> | |||||
</Stack> | |||||
</FormProvider> | </FormProvider> | ||||
</> | </> | ||||
); | ); | ||||
@@ -3,37 +3,28 @@ import EditTeam from "./EditTeam"; | |||||
import EditTeamLoading from "./EditTeamLoading"; | import EditTeamLoading from "./EditTeamLoading"; | ||||
// import { fetchTeam, fetchTeamLeads } from "@/app/api/Team"; | // import { fetchTeam, fetchTeamLeads } from "@/app/api/Team"; | ||||
import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; | |||||
import { fetchIndivTeam, fetchTeam, fetchTeamDetail } from "@/app/api/team"; | |||||
import { fetchStaff, fetchStaffWithoutTeam, fetchTeamLeads } from "@/app/api/staff"; | import { fetchStaff, fetchStaffWithoutTeam, fetchTeamLeads } from "@/app/api/staff"; | ||||
interface SubComponents { | interface SubComponents { | ||||
Loading: typeof EditTeamLoading; | Loading: typeof EditTeamLoading; | ||||
} | } | ||||
const EditTeamWrapper: React.FC & SubComponents = async () => { | |||||
interface Props { | |||||
id: number | |||||
} | |||||
const EditTeamWrapper: React.FC<Props> & SubComponents = async ({ id }) => { | |||||
const [ | const [ | ||||
staff, | staff, | ||||
allTeams, | |||||
// teamLead, | |||||
team, | |||||
] = await Promise.all([ | ] = await Promise.all([ | ||||
fetchStaff(), | fetchStaff(), | ||||
fetchTeam(), | |||||
// fetchTeamLeads(), | |||||
fetchIndivTeam(id), | |||||
]); | ]); | ||||
console.log(allTeams) | |||||
const teamDesc = allTeams.map((i) => { | |||||
return ( | |||||
{ | |||||
id: i.id, | |||||
name: i.name, | |||||
description: i.description, | |||||
teamLead: i.teamLead | |||||
} | |||||
)}) | |||||
return <EditTeam staff={staff} desc={teamDesc} />; | |||||
return <EditTeam staff={staff} teamInfo={team} />; | |||||
}; | }; | ||||
EditTeamWrapper.Loading = EditTeamLoading; | EditTeamWrapper.Loading = EditTeamLoading; | ||||
@@ -16,14 +16,10 @@ import { useCallback } from "react"; | |||||
import { CreateTeamInputs } from "@/app/api/team/actions"; | import { CreateTeamInputs } from "@/app/api/team/actions"; | ||||
interface Props { | interface Props { | ||||
value: string; | |||||
} | } | ||||
const TeamInfo: React.FC<Props> = ( | |||||
{ | |||||
value | |||||
} | |||||
) => { | |||||
const TeamInfo: React.FC<Props> = () => { | |||||
const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
const { | const { | ||||
register, | register, | ||||
@@ -34,13 +30,6 @@ const TeamInfo: React.FC<Props> = ( | |||||
setValue, | setValue, | ||||
} = useFormContext<CreateTeamInputs>(); | } = useFormContext<CreateTeamInputs>(); | ||||
const resetCustomer = useCallback(() => { | |||||
console.log(defaultValues); | |||||
if (defaultValues !== undefined) { | |||||
resetField("description"); | |||||
} | |||||
}, [defaultValues]); | |||||
return ( | return ( | ||||
<> | <> | ||||
<Card sx={{ display: "block" }}> | <Card sx={{ display: "block" }}> | ||||
@@ -49,22 +38,58 @@ const TeamInfo: React.FC<Props> = ( | |||||
<Typography variant="overline" display="block" marginBlockEnd={1}> | <Typography variant="overline" display="block" marginBlockEnd={1}> | ||||
{t("Team Info")} | {t("Team Info")} | ||||
</Typography> | </Typography> | ||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={12}> | |||||
<TextField | |||||
label={t("Team Description")} | |||||
fullWidth | |||||
multiline | |||||
rows={4} | |||||
{...register("description", { | |||||
required: true, | |||||
})} | |||||
defaultValue={value} | |||||
error={Boolean(errors.description)} | |||||
helperText={Boolean(errors.description) && (errors.description?.message ? t(errors.description.message) : t("Please input correct description"))} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("name")} | |||||
fullWidth | |||||
{...register("name", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.name)} | |||||
helperText={ | |||||
Boolean(errors.name) && | |||||
(errors.name?.message | |||||
? t(errors.name.message) | |||||
: t("Please input correct name")) | |||||
} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={6}> | |||||
<TextField | |||||
label={t("code")} | |||||
fullWidth | |||||
{...register("code", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.code)} | |||||
helperText={ | |||||
Boolean(errors.code) && | |||||
(errors.code?.message | |||||
? t(errors.code.message) | |||||
: t("Please input correct code")) | |||||
} | |||||
/> | |||||
</Grid> | |||||
<Grid item xs={12}> | |||||
<TextField | |||||
label={t("Team Description")} | |||||
fullWidth | |||||
multiline | |||||
rows={4} | |||||
{...register("description", { | |||||
required: true, | |||||
})} | |||||
error={Boolean(errors.description)} | |||||
helperText={ | |||||
Boolean(errors.description) && | |||||
(errors.description?.message | |||||
? t(errors.description.message) | |||||
: t("Please input correct description")) | |||||
} | |||||
/> | |||||
</Grid> | |||||
</Grid> | |||||
</Box> | </Box> | ||||
</CardContent> | </CardContent> | ||||
</Card> | </Card> | ||||