From 1f77b12abe958ef817c1e7b1a66b5ce8b5ffe25a Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Thu, 25 Apr 2024 16:17:08 +0800 Subject: [PATCH] update staff edit bug and team edit --- src/app/(main)/settings/team/edit/page.tsx | 25 ++ src/app/api/staff/actions.ts | 22 +- src/app/api/staff/index.ts | 28 +++ src/components/CreateTeam/TeamInfo.tsx | 5 +- src/components/EditTeam/Allocation.tsx | 263 ++++++++++++++++++++ src/components/EditTeam/EditTeam.tsx | 204 +++++++++++++++ src/components/EditTeam/EditTeamLoading.tsx | 40 +++ src/components/EditTeam/EditTeamWrapper.tsx | 29 +++ src/components/EditTeam/TeamInfo.tsx | 74 ++++++ src/components/EditTeam/index.ts | 1 + src/components/TeamSearch/TeamSearch.tsx | 20 +- 11 files changed, 697 insertions(+), 14 deletions(-) create mode 100644 src/app/(main)/settings/team/edit/page.tsx create mode 100644 src/components/EditTeam/Allocation.tsx create mode 100644 src/components/EditTeam/EditTeam.tsx create mode 100644 src/components/EditTeam/EditTeamLoading.tsx create mode 100644 src/components/EditTeam/EditTeamWrapper.tsx create mode 100644 src/components/EditTeam/TeamInfo.tsx create mode 100644 src/components/EditTeam/index.ts diff --git a/src/app/(main)/settings/team/edit/page.tsx b/src/app/(main)/settings/team/edit/page.tsx new file mode 100644 index 0000000..94458f4 --- /dev/null +++ b/src/app/(main)/settings/team/edit/page.tsx @@ -0,0 +1,25 @@ +import { Edit } from "@mui/icons-material"; +import { useSearchParams } from "next/navigation"; +// import EditStaff from "@/components/EditStaff"; +import { Suspense } from "react"; +import { I18nProvider } from "@/i18n"; +// import EditStaffWrapper from "@/components/EditStaff/EditStaffWrapper"; +import { Metadata } from "next"; +import EditTeam from "@/components/EditTeam"; + + +const EditTeamPage: React.FC = () => { + + return ( + <> + + }> + + + + {/* */} + + ); +}; + +export default EditTeamPage; diff --git a/src/app/api/staff/actions.ts b/src/app/api/staff/actions.ts index 1098508..9416d2d 100644 --- a/src/app/api/staff/actions.ts +++ b/src/app/api/staff/actions.ts @@ -3,6 +3,7 @@ import { serverFetchJson } from "@/app/utils/fetchUtil"; import { BASE_API_URL } from "@/config/api"; import { StaffResult, data } from "."; import { cache } from "react"; +import { Team, staff } from "../team/actions"; export interface CreateCustomInputs { // Project details projectCode: string; @@ -28,11 +29,20 @@ export interface CreateStaffInputs { emergContactPhone: string; employType: string; joinDate: string | null; - departDate: string | null; - departReason: string | null; - remark: string | null; + departDate?: string | null; + departReason?: string | null; + remark?: string | null; } + export interface records { + id: number; + name: string; + // team: Team[]; + } + export interface Staff4TransferList { + records: records[]; + } + export const saveStaff = async (data: CreateStaffInputs) => { return serverFetchJson(`${BASE_API_URL}/staffs/save`, { method: "POST", @@ -68,3 +78,9 @@ export const fetchStaffEdit = cache(async (id: number) => { // fetchStaffEdit(id); // }; +export const fetchStaffCombo = cache(async () => { + return serverFetchJson(`${BASE_API_URL}/staffs/combo`, { + next: { tags: ["staffs"] }, + }); +}); + diff --git a/src/app/api/staff/index.ts b/src/app/api/staff/index.ts index afa082f..2c3f66d 100644 --- a/src/app/api/staff/index.ts +++ b/src/app/api/staff/index.ts @@ -6,6 +6,26 @@ import "server-only"; export interface data { [key: string]: any; } + +export interface StaffGroup { + id: number; + name: string; +} +export interface Staff { + id: number; + staffId: string; + name: string; + // description: string | null; + currentPosition: StaffGroup; +} +export interface StaffTeamTable { + id: number; + staffId: string; + name: string; + // description: string | null; + currentPosition: string; +} + export interface StaffResult { action: any; id: number; @@ -16,6 +36,8 @@ export interface StaffResult { joinPosition: string; currentPosition: string; data: data; + teamId: number; + staffName: string; } export interface searchInput { staffId: string; @@ -44,3 +66,9 @@ export const fetchStaff = cache(async () => { next: { tags: ["staffs"] }, }); }); + +// export const fetchStaffCombo = cache(async () => { +// return serverFetchJson(`${BASE_API_URL}/staffs/combo`, { +// next: { tags: ["staffs"] }, +// }); +// }); diff --git a/src/components/CreateTeam/TeamInfo.tsx b/src/components/CreateTeam/TeamInfo.tsx index 9dd4060..4e61f4b 100644 --- a/src/components/CreateTeam/TeamInfo.tsx +++ b/src/components/CreateTeam/TeamInfo.tsx @@ -16,9 +16,6 @@ import { useCallback } from "react"; import { CreateTeamInputs } from "@/app/api/team/actions"; const TeamInfo: React.FC = ( - { - // customerTypes, - } ) => { const { t } = useTranslation(); const { @@ -36,7 +33,7 @@ const TeamInfo: React.FC = ( resetField("description"); } }, [defaultValues]); - + return ( <> diff --git a/src/components/EditTeam/Allocation.tsx b/src/components/EditTeam/Allocation.tsx new file mode 100644 index 0000000..96b4b46 --- /dev/null +++ b/src/components/EditTeam/Allocation.tsx @@ -0,0 +1,263 @@ +"use client"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import CustomInputForm from "../CustomInputForm"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useTranslation } from "react-i18next"; +import { + FieldErrors, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, + useFormContext, +} from "react-hook-form"; +import { CreateTeamInputs } from "@/app/api/team/actions"; +import { Staff4TransferList, fetchStaffCombo } from "@/app/api/staff/actions"; +import { StaffResult, StaffTeamTable } from "@/app/api/staff"; +import SearchResults, { Column } from "../SearchResults"; +import { Clear, PersonAdd, PersonRemove, Search } from "@mui/icons-material"; +import { Card } from "reactstrap"; +import { + Box, + CardContent, + Grid, + IconButton, + InputAdornment, + Stack, + Tab, + Tabs, + TabsProps, + TextField, + Typography, +} from "@mui/material"; +import { differenceBy } from "lodash"; +import StarsIcon from "@mui/icons-material/Stars"; + +export interface Props { + allStaffs: StaffResult[]; +} + +const Allocation: React.FC = ({ allStaffs: staff }) => { + const { t } = useTranslation(); + const searchParams = useSearchParams(); + const idString = searchParams.get("id"); + const { + setValue, + getValues, + formState: { defaultValues }, + reset, + resetField, + } = useFormContext(); + + // let firstFilter: StaffResult[] = [] + + const initialStaffs = staff.map((s) => ({ ...s })); + const [filteredStaff, setFilteredStaff] = useState(initialStaffs); + const [selectedStaff, setSelectedStaff] = useState( + filteredStaff.filter((s) => getValues("addStaffIds")?.includes(s.id)) + ); + const [seletedTeamLead, setSeletedTeamLead] = useState(); + const [deletedStaffIds, setDeletedStaffIds] = useState([]); + + // Adding / Removing staff + const addStaff = useCallback((staff: StaffResult) => { + setSelectedStaff((s) => [...s, staff]); + // setDeletedStaffIds((s) => s.filter((s) => s === selectedStaff.id)) + }, []); + + const removeStaff = useCallback((staff: StaffResult) => { + setSelectedStaff((s) => s.filter((s) => s.id !== staff.id)); + setDeletedStaffIds((s) => s) + // setValue("deleteStaffIds", [...staff.id]) + }, []); + + const setTeamLead = useCallback( + (staff: StaffResult) => { + setSeletedTeamLead(staff.id); + const rearrangedList = getValues("addStaffIds").reduce( + (acc, num, index) => { + if (num === staff.id && index !== 0) { + acc.splice(index, 1); + acc.unshift(num); + } + return acc; + }, + getValues("addStaffIds") + ); + console.log(rearrangedList); + console.log(selectedStaff); + + const rearrangedStaff = rearrangedList.map((id) => { + return selectedStaff.find((staff) => staff.id === id); + }); + console.log(rearrangedStaff); + setSelectedStaff(rearrangedStaff as StaffResult[]); + + setValue("addStaffIds", rearrangedList); + }, + [addStaff, selectedStaff] + ); + + const clearSubsidiary = useCallback(() => { + if (defaultValues !== undefined) { + resetField("addStaffIds"); + setSelectedStaff( + initialStaffs.filter((s) => defaultValues.addStaffIds?.includes(s.id)) + ); + } + }, [defaultValues]); + + // Sync with form + useEffect(() => { + setValue( + "addStaffIds", + selectedStaff.map((s) => s.id) + ); + }, [selectedStaff, setValue]); + + useEffect(() => { + setValue("deleteStaffIds", deletedStaffIds) + console.log(deletedStaffIds) + }, [deletedStaffIds, setValue]); + + const StaffPoolColumns = useMemo[]>( + () => [ + { + label: t("Add"), + name: "id", + onClick: addStaff, + buttonIcon: , + }, + { label: t("Staff Id"), name: "staffId" }, + { label: t("Staff Name"), name: "name" }, + { label: t("Current Position"), name: "currentPosition" }, + ], + [addStaff, t] + ); + + const allocatedStaffColumns = useMemo[]>( + () => [ + { + label: t("Remove"), + name: "action", + onClick: removeStaff, + buttonIcon: , + }, + { label: t("Staff Id"), name: "staffId" }, + { label: t("Staff Name"), name: "name" }, + { label: t("Current Position"), name: "currentPosition" }, + { + label: t("Team Lead"), + name: "action", + onClick: setTeamLead, + buttonIcon: , + }, + ], + [removeStaff, selectedStaff, t] + ); + + const [query, setQuery] = React.useState(""); + const onQueryInputChange = React.useCallback< + React.ChangeEventHandler + >((e) => { + setQuery(e.target.value); + }, []); + const clearQueryInput = React.useCallback(() => { + setQuery(""); + }, []); + + React.useEffect(() => { + // setFilteredStaff( + // initialStaffs.filter((s) => { + // const q = query.toLowerCase(); + // // s.staffId.toLowerCase().includes(q) + // // const q = query.toLowerCase(); + // // return s.name.toLowerCase().includes(q); + // // s.code.toString().includes(q) || + // // (s.brNo != null && s.brNo.toLowerCase().includes(q)) + // }) + // ); + }, [staff, query]); + + useEffect(() => { + // console.log(getValues("addStaffIds")) + }, [initialStaffs]); + + const resetStaff = React.useCallback(() => { + clearQueryInput(); + clearSubsidiary(); + }, [clearQueryInput, clearSubsidiary]); + + const formProps = useForm({}); + + // Tab related + const [tabIndex, setTabIndex] = React.useState(0); + const handleTabChange = React.useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + return ( + <> + + + + + + {t("staff")} + + + + + + + + + + ), + }} + /> + + + + + + + + {tabIndex === 0 && ( + + )} + {tabIndex === 1 && ( + + )} + + + + + + + ); +}; + +export default Allocation; diff --git a/src/components/EditTeam/EditTeam.tsx b/src/components/EditTeam/EditTeam.tsx new file mode 100644 index 0000000..4b93754 --- /dev/null +++ b/src/components/EditTeam/EditTeam.tsx @@ -0,0 +1,204 @@ +"use client"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import SearchResults, { Column } from "../SearchResults"; +// import { TeamResult } from "@/app/api/team"; +import { useTranslation } from "react-i18next"; +import { Button, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material"; +import { CreateTeamInputs, saveTeam } from "@/app/api/team/actions"; +import { + FieldErrors, + FormProvider, + SubmitHandler, + useForm, + useFormContext, +} from "react-hook-form"; +import { Check, Close, Error } from "@mui/icons-material"; +import TeamInfo from "../EditTeam/TeamInfo"; +import Allocation from "./Allocation"; +import { StaffResult } from "@/app/api/staff"; + +interface desc { + id: number; + description: string; +} + +interface Props { + staff: StaffResult[]; + desc: desc[]; +} + +const EditTeam: React.FC = async ({ staff, desc }) => { + // console.log(desc) + const { t } = useTranslation(); + const formProps = useForm(); + const searchParams = useSearchParams(); + const idString = searchParams.get("id"); + const [filteredItems, setFilteredItems] = useState(); + const [allStaffs, setAllStaffs] = useState(); + const [filteredDesc, setFilteredDesc] = useState(); + const [tabIndex, setTabIndex] = useState(0); + const router = useRouter(); + // const [selectedStaff, setSelectedStaff] = useState( + // initialStaffs.filter((s) => getValues("addStaffIds")?.includes(s.id)) + // ); + + const errors = formProps.formState.errors; + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + + const [serverError, setServerError] = useState(""); + const cols = useMemo[]>( + () => [ + { label: t("Staff Id"), name: "staffId" }, + { label: t("Name"), name: "staffName" }, + // { label: t("Current Position"), name: "posCode" }, + ], + [t] + ); + useEffect(() => { + let idList: number[] = [] + if (idString) { + const filteredTeam = staff.filter( + (item) => item.teamId === parseInt(idString) + ); + const tempDesc = desc.filter( + (item) => item.id === parseInt(idString) + ) + // console.log(filteredTeam); + if (filteredTeam.length > 0) { + const filteredIds: number[] = filteredTeam.map((i) => ( + i.id + )) + idList = filteredIds + } + // console.log(filteredIds) + setFilteredItems(filteredTeam); + formProps.reset({description: tempDesc[0].description, addStaffIds: idList}) + setFilteredDesc(tempDesc[0].description) + } + // console.log(staff); + // const desc = staff[0]?.description + // setDesc(desc) + // const staff = staff.map((item) => { + // return { + // id: item.id, + // name: item.name, + // staffId: item.staffId, + // teamId: item.teamId, + // staffName: item.staffName, + // currentPosition: item.currentPosition + + // } as StaffResult + // }) + console.log(staff) + setAllStaffs(staff) + + }, [searchParams]); + +// useEffect(() => { +// console.log(allStaffs) +// }, [allStaffs]); + + const hasErrorsInTab = ( + tabIndex: number, + errors: FieldErrors + ) => { + switch (tabIndex) { + case 0: + return Object.keys(errors).length > 0; + default: + false; + } + }; + + const onSubmit = useCallback>( + async (data) => { + try { + console.log(data); + const tempData = { + description: data.description, + addStaffIds: data.addStaffIds, + id: parseInt(idString!!) + } + console.log(tempData) + // await saveTeam(tempData); + // router.replace("/settings/staff"); + } catch (e) { + console.log(e); + setServerError(t("An error has occurred. Please try again later.")); + } + }, + [router] + ); + + return ( + <> + {serverError && ( + + {serverError} + + )} + + + + + {t("Edit Team")} + + + + + ) : undefined + } + iconPosition="end" + /> + + + + {tabIndex === 0 && } + {tabIndex === 1 && } + + + + + + + + ); +}; +export default EditTeam; diff --git a/src/components/EditTeam/EditTeamLoading.tsx b/src/components/EditTeam/EditTeamLoading.tsx new file mode 100644 index 0000000..ed3d763 --- /dev/null +++ b/src/components/EditTeam/EditTeamLoading.tsx @@ -0,0 +1,40 @@ +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Skeleton from "@mui/material/Skeleton"; +import Stack from "@mui/material/Stack"; +import React from "react"; + +// Can make this nicer +export const EditTeamLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + EditTeam + + + + + + + + + + + ); +}; + +export default EditTeamLoading; diff --git a/src/components/EditTeam/EditTeamWrapper.tsx b/src/components/EditTeam/EditTeamWrapper.tsx new file mode 100644 index 0000000..bb58a09 --- /dev/null +++ b/src/components/EditTeam/EditTeamWrapper.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import EditTeam from "./EditTeam"; +import EditTeamLoading from "./EditTeamLoading"; +// import { fetchTeam, fetchTeamLeads } from "@/app/api/Team"; +import { useSearchParams } from "next/navigation"; +import { fetchTeam, fetchTeamDetail } from "@/app/api/team"; +import { fetchStaff } from "@/app/api/staff"; + +interface SubComponents { + Loading: typeof EditTeamLoading; +} + +const EditTeamWrapper: React.FC & SubComponents = async () => { + const staff = await fetchStaff() + const allTeams = await fetchTeam(); + const teamDesc = allTeams.map((i) => ( + { + id: i.id, + description: i.description + } + )) + console.log(staff) + console.log(teamDesc) + return ; +}; + +EditTeamWrapper.Loading = EditTeamLoading; + +export default EditTeamWrapper; diff --git a/src/components/EditTeam/TeamInfo.tsx b/src/components/EditTeam/TeamInfo.tsx new file mode 100644 index 0000000..4a54f6c --- /dev/null +++ b/src/components/EditTeam/TeamInfo.tsx @@ -0,0 +1,74 @@ +"use client"; +import Stack from "@mui/material/Stack"; +import Box from "@mui/material/Box"; +import Card from "@mui/material/Card"; +import CardContent from "@mui/material/CardContent"; +import Grid from "@mui/material/Grid"; +import TextField from "@mui/material/TextField"; +import Typography from "@mui/material/Typography"; +import { useTranslation } from "react-i18next"; +import CardActions from "@mui/material/CardActions"; +import RestartAlt from "@mui/icons-material/RestartAlt"; +import Button from "@mui/material/Button"; +import { Controller, useFormContext } from "react-hook-form"; +import { FormControl, InputLabel, MenuItem, Select } from "@mui/material"; +import { useCallback } from "react"; +import { CreateTeamInputs } from "@/app/api/team/actions"; + +interface Props { + value: string; +} + +const TeamInfo: React.FC = ( + { + value + } +) => { + const { t } = useTranslation(); + const { + register, + formState: { errors, defaultValues }, + control, + reset, + resetField, + setValue, + } = useFormContext(); + + const resetCustomer = useCallback(() => { + console.log(defaultValues); + if (defaultValues !== undefined) { + resetField("description"); + } + }, [defaultValues]); + + return ( + <> + + + + + {t("Team Info")} + + + + + + + + + + + ); +}; +export default TeamInfo; diff --git a/src/components/EditTeam/index.ts b/src/components/EditTeam/index.ts new file mode 100644 index 0000000..fa63ef7 --- /dev/null +++ b/src/components/EditTeam/index.ts @@ -0,0 +1 @@ +export { default } from "./EditTeamWrapper"; diff --git a/src/components/TeamSearch/TeamSearch.tsx b/src/components/TeamSearch/TeamSearch.tsx index 85d970d..9aae6f1 100644 --- a/src/components/TeamSearch/TeamSearch.tsx +++ b/src/components/TeamSearch/TeamSearch.tsx @@ -2,7 +2,7 @@ import { TeamResult } from "@/app/api/team"; import SearchBox, { Criterion } from "../SearchBox"; -import { useMemo, useState } from "react"; +import { useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults/index"; import EditNote from "@mui/icons-material/EditNote"; @@ -44,14 +44,20 @@ const TeamSearch: React.FC = ({ team }) => { [t], ); + const onTeamClick = useCallback((team: TeamResult) => { + console.log(team); + const id = team.id + router.push(`/settings/team/edit?id=${id}`); + }, [router, t]); + const columns = useMemo[]>( () => [ - // { - // name: "action", - // label: t("Actions"), - // onClick: onStaffClick, - // buttonIcon: , - // }, + { + name: "action", + label: t("Actions"), + onClick: onTeamClick, + buttonIcon: , + }, { name: "name", label: t("Name") }, { name: "code", label: t("Code") }, { name: "description", label: t("description") },