From e689ad108354dc5987678e44c71ab2c21ff07333 Mon Sep 17 00:00:00 2001 From: "MSI\\derek" Date: Tue, 7 May 2024 16:07:05 +0800 Subject: [PATCH] update user group page --- src/app/(main)/settings/group/edit/page.tsx | 26 +++ src/app/api/group/actions.ts | 17 +- .../CreateGroup/AuthorityAllocation.tsx | 5 +- .../EditUserGroup/AuthorityAllocation.tsx | 210 +++++++++++++++++ .../EditUserGroup/EditUserGroup.tsx | 165 +++++++++++++ .../EditUserGroup/EditUserGroupLoading.tsx | 40 ++++ .../EditUserGroup/EditUserGroupWrapper.tsx | 31 +++ src/components/EditUserGroup/GroupInfo.tsx | 81 +++++++ .../EditUserGroup/UserAllocation.tsx | 216 ++++++++++++++++++ src/components/EditUserGroup/index.ts | 1 + .../UserGroupSearch/UserGroupSearch.tsx | 13 +- 11 files changed, 790 insertions(+), 15 deletions(-) create mode 100644 src/components/EditUserGroup/AuthorityAllocation.tsx create mode 100644 src/components/EditUserGroup/EditUserGroup.tsx create mode 100644 src/components/EditUserGroup/EditUserGroupLoading.tsx create mode 100644 src/components/EditUserGroup/EditUserGroupWrapper.tsx create mode 100644 src/components/EditUserGroup/GroupInfo.tsx create mode 100644 src/components/EditUserGroup/UserAllocation.tsx create mode 100644 src/components/EditUserGroup/index.ts diff --git a/src/app/(main)/settings/group/edit/page.tsx b/src/app/(main)/settings/group/edit/page.tsx index e69de29..ae51d16 100644 --- a/src/app/(main)/settings/group/edit/page.tsx +++ b/src/app/(main)/settings/group/edit/page.tsx @@ -0,0 +1,26 @@ +import EditPosition from "@/components/EditPosition"; +import EditUserGroup from "@/components/EditUserGroup"; +import { I18nProvider, getServerI18n } from "@/i18n"; +import Typography from "@mui/material/Typography"; +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Edit User Group", +}; + +const Positions: React.FC = async () => { + const { t } = await getServerI18n("group"); + + // Preload necessary dependencies + + return ( + <> + {/* {t("Edit User Group")} */} + + + + + ); +}; + +export default Positions; \ No newline at end of file diff --git a/src/app/api/group/actions.ts b/src/app/api/group/actions.ts index c8881de..204c1d9 100644 --- a/src/app/api/group/actions.ts +++ b/src/app/api/group/actions.ts @@ -29,11 +29,11 @@ export interface record { records: auth[]; } - export const fetchAuth = cache(async () => { - return serverFetchJson(`${BASE_API_URL}/group/auth/combo`, { - next: { tags: ["auth"] }, - }); +export const fetchAuth = cache(async (id?: number) => { + return serverFetchJson(`${BASE_API_URL}/group/auth/combo/${id ?? 0}`, { + next: { tags: ["auth"] }, }); +}); export const saveGroup = async (data: CreateGroupInputs) => { return serverFetchJson(`${BASE_API_URL}/group/save`, { @@ -41,4 +41,11 @@ export const saveGroup = async (data: CreateGroupInputs) => { body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); - }; \ No newline at end of file + }; + +export const deleteGroup = async (id: number) => { + return serverFetchWithNoContent(`${BASE_API_URL}/group/${id}`, { + method: "DELETE", + headers: { "Content-Type": "application/json" }, + }); +}; \ No newline at end of file diff --git a/src/components/CreateGroup/AuthorityAllocation.tsx b/src/components/CreateGroup/AuthorityAllocation.tsx index fd9610b..bdd4ccb 100644 --- a/src/components/CreateGroup/AuthorityAllocation.tsx +++ b/src/components/CreateGroup/AuthorityAllocation.tsx @@ -50,6 +50,7 @@ const AuthorityAllocation: React.FC = ({ auth }) => { ); } ); + // Adding / Removing Auth const addAuth = useCallback((auth: auth) => { setSelectedAuths((a) => [...a, auth]); @@ -126,10 +127,6 @@ const AuthorityAllocation: React.FC = ({ auth }) => { // ); }, [auth, query]); - useEffect(() => { - // console.log(getValues("addStaffIds")) - }, [initialAuths]); - const resetAuth = React.useCallback(() => { clearQueryInput(); clearAuth(); diff --git a/src/components/EditUserGroup/AuthorityAllocation.tsx b/src/components/EditUserGroup/AuthorityAllocation.tsx new file mode 100644 index 0000000..da502da --- /dev/null +++ b/src/components/EditUserGroup/AuthorityAllocation.tsx @@ -0,0 +1,210 @@ +"use client"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + FieldErrors, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, + useFormContext, +} from "react-hook-form"; +import { + Box, + Card, + CardContent, + Grid, + IconButton, + InputAdornment, + Stack, + Tab, + Tabs, + TabsProps, + TextField, + Typography, +} from "@mui/material"; +import { differenceBy } from "lodash"; +import { CreateGroupInputs, auth } from "@/app/api/group/actions"; +import SearchResults, { Column } from "../SearchResults"; +import { Add, Clear, Remove, Search } from "@mui/icons-material"; + +export interface Props { + auth: auth[]; +} + +const AuthorityAllocation: React.FC = ({ auth }) => { + const { t } = useTranslation(); + const { + setValue, + getValues, + formState: { defaultValues }, + reset, + resetField, + } = useFormContext(); + console.log(auth) + const initialAuths = auth.map((a) => ({ ...a })).sort((a, b) => a.id - b.id); + const [filteredAuths, setFilteredAuths] = useState(initialAuths); + const [selectedAuths, setSelectedAuths] = useState( + () => initialAuths.filter((s) => getValues("addAuthIds")?.includes(s.id))) + const [removeAuthIds, setRemoveAuthIds] = useState([]); + + // Adding / Removing Auth + const addAuth = useCallback((auth: auth) => { + setSelectedAuths((a) => [...a, auth]); + }, []); + const removeAuth = useCallback((auth: auth) => { + setSelectedAuths((a) => a.filter((a) => a.id !== auth.id)); + setRemoveAuthIds((prevIds) => [...prevIds, auth.id]); +}, []); + + const clearAuth = useCallback(() => { + if (defaultValues !== undefined) { + resetField("addAuthIds"); + setSelectedAuths( + initialAuths.filter((auth) => defaultValues.addAuthIds?.includes(auth.id)) + ); + } + }, [defaultValues]); + + // Sync with form + useEffect(() => { + setValue( + "addAuthIds", + selectedAuths.map((a) => a.id) + ); + setValue( + "removeAuthIds", + removeAuthIds + ); + }, [selectedAuths, removeAuthIds, setValue]); + + const AuthPoolColumns = useMemo[]>( + () => [ + { + label: t("Add"), + name: "id", + onClick: addAuth, + buttonIcon: , + }, + { label: t("authority"), name: "authority" }, + { label: t("Auth Name"), name: "name" }, + // { label: t("Current Position"), name: "currentPosition" }, + ], + [addAuth, t] + ); + + const allocatedAuthColumns = useMemo[]>( + () => [ + { + label: t("Remove"), + name: "id", + onClick: removeAuth, + buttonIcon: , + }, + { label: t("authority"), name: "authority" }, + { label: t("Auth Name"), name: "name" }, + ], + [removeAuth, selectedAuths, 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)) + // }) + // ); + }, [auth, query]); + + const resetAuth = React.useCallback(() => { + clearQueryInput(); + clearAuth(); + }, [clearQueryInput, clearAuth]); + + const formProps = useForm({}); + + // Tab related + const [tabIndex, setTabIndex] = React.useState(0); + const handleTabChange = React.useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + + return ( + <> + + + + + + {t("Authority")} + + + + + + + + + + ), + }} + /> + + + + + + + + {tabIndex === 0 && ( + + )} + {tabIndex === 1 && ( + + )} + + + + + + + ); +}; + +export default AuthorityAllocation; diff --git a/src/components/EditUserGroup/EditUserGroup.tsx b/src/components/EditUserGroup/EditUserGroup.tsx new file mode 100644 index 0000000..fe75821 --- /dev/null +++ b/src/components/EditUserGroup/EditUserGroup.tsx @@ -0,0 +1,165 @@ +"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 { StaffResult } from "@/app/api/staff"; +import { CreateGroupInputs, auth, fetchAuth, saveGroup } from "@/app/api/group/actions"; +import { UserGroupResult } from "@/app/api/group"; +import { UserResult } from "@/app/api/user"; +import GroupInfo from "./GroupInfo"; +import AuthorityAllocation from "./AuthorityAllocation"; +import UserAllocation from "./UserAllocation"; +interface Props { + groups: UserGroupResult[]; +// auths: auth[]; + users: UserResult[]; +} + +const EditUserGroup: React.FC = ({ groups, users }) => { + // console.log(users) + const { t } = useTranslation(); + const [serverError, setServerError] = useState(""); + const formProps = useForm(); + const searchParams = useSearchParams(); + const id = parseInt(searchParams.get("id") || "0"); + const router = useRouter(); + const [tabIndex, setTabIndex] = useState(0); + const [auths, setAuths] = useState(); + + const errors = formProps.formState.errors; + + const handleTabChange = useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + + 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 = { + ...data, + removeUserIds: data.removeUserIds ?? [], + removeAuthIds: data.removeAuthIds ?? [], + id: id + } + console.log(tempData) + await saveGroup(tempData); + router.replace("/settings/group"); + } catch (e) { + console.log(e); + setServerError(t("An error has occurred. Please try again later.")); + } + }, + [router] + ); + useEffect(() => { + const thisGroup = groups.filter((item) => item.id === id)[0]; + const addUserIds = users.filter((item) => item.groupId === id).map((data) => data.id) + let addAuthIds: number[] = [] + fetchAuth(id).then((data) => { + setAuths(data.records) + addAuthIds = data.records.filter((data) => data.v === 1).map((data) => data.id).sort((a, b) => a - b); + formProps.reset({ + name: thisGroup.name, + description: thisGroup.description, + addAuthIds: addAuthIds, + addUserIds: addUserIds, + }); + }); + // console.log(auths) + }, [groups, users]); + + return ( + <> + + + + {t("Edit User Group")} + + + + + ) : undefined + } + iconPosition="end" + /> + + + + + {serverError && ( + + {serverError} + + )} + {tabIndex === 0 && } + {tabIndex === 1 && } + {tabIndex === 2 && } + + + + + + + + ); +}; + +export default EditUserGroup; diff --git a/src/components/EditUserGroup/EditUserGroupLoading.tsx b/src/components/EditUserGroup/EditUserGroupLoading.tsx new file mode 100644 index 0000000..9238474 --- /dev/null +++ b/src/components/EditUserGroup/EditUserGroupLoading.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 EditUserGroupLoading: React.FC = () => { + return ( + <> + + + + + + + + + + + EditUserGroup + + + + + + + + + + + ); +}; + +export default EditUserGroupLoading; diff --git a/src/components/EditUserGroup/EditUserGroupWrapper.tsx b/src/components/EditUserGroup/EditUserGroupWrapper.tsx new file mode 100644 index 0000000..84f7501 --- /dev/null +++ b/src/components/EditUserGroup/EditUserGroupWrapper.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import EditUserGroup from "./EditUserGroup"; +import EditUserGroupLoading from "./EditUserGroupLoading"; +import { fetchGroup } from "@/app/api/group"; +import { fetchAuth } from "@/app/api/group/actions"; +import { fetchUser } from "@/app/api/user"; +import { useSearchParams } from "next/navigation"; + +interface SubComponents { + Loading: typeof EditUserGroupLoading; +} + +const EditUserGroupWrapper: React.FC & SubComponents = async () => { + + const [ + groups, + // auths, + users, + ] = await Promise.all([ + fetchGroup(), + // fetchAuth(), + fetchUser(), + ]); + console.log(users) + + return ; +}; + +EditUserGroupWrapper.Loading = EditUserGroupLoading; + +export default EditUserGroupWrapper; diff --git a/src/components/EditUserGroup/GroupInfo.tsx b/src/components/EditUserGroup/GroupInfo.tsx new file mode 100644 index 0000000..d9141bc --- /dev/null +++ b/src/components/EditUserGroup/GroupInfo.tsx @@ -0,0 +1,81 @@ +"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 { CreateGroupInputs } from "@/app/api/group/actions"; +import { useFormContext } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { useCallback } from "react"; + +const GroupInfo: React.FC = () => { + const { t } = useTranslation(); + const { + register, + formState: { errors, defaultValues }, + control, + reset, + resetField, + setValue, + } = useFormContext(); + + + const resetGroup = useCallback(() => { + console.log(defaultValues); + if (defaultValues !== undefined) { + resetField("description"); + } + }, [defaultValues]); + + + return ( + + + + + {t("Group Info")} + + + + + + + + + + + + + ); +}; + +export default GroupInfo; diff --git a/src/components/EditUserGroup/UserAllocation.tsx b/src/components/EditUserGroup/UserAllocation.tsx new file mode 100644 index 0000000..14ed975 --- /dev/null +++ b/src/components/EditUserGroup/UserAllocation.tsx @@ -0,0 +1,216 @@ +"use client"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + FieldErrors, + FormProvider, + SubmitErrorHandler, + SubmitHandler, + useForm, + useFormContext, +} from "react-hook-form"; +import { + Box, + Card, + CardContent, + Grid, + IconButton, + InputAdornment, + Stack, + Tab, + Tabs, + TabsProps, + TextField, + Typography, +} from "@mui/material"; +import { differenceBy } from "lodash"; +import { CreateGroupInputs, auth } from "@/app/api/group/actions"; +import SearchResults, { Column } from "../SearchResults"; +import { Add, Clear, Remove, Search } from "@mui/icons-material"; +import { UserResult } from "@/app/api/user"; + +export interface Props { + users: UserResult[]; +} + +const UserAllocation: React.FC = ({ users }) => { + const { t } = useTranslation(); + const { + setValue, + getValues, + formState: { defaultValues }, + reset, + resetField, + } = useFormContext(); + const initialUsers = users.map((u) => ({ ...u })).sort((a, b) => a.id - b.id); + const [filteredUsers, setFilteredUsers] = useState(initialUsers); + const [selectedUsers, setSelectedUsers] = useState( + () => { + return filteredUsers.filter( + (s) => getValues("addUserIds")?.includes(s.id) + ); + } + ); + const [deletedUserIds, setDeletedUserIds] = useState([]); + + // Adding / Removing Auth + const addUser = useCallback((users: UserResult) => { + setSelectedUsers((a) => [...a, users]); + }, []); + + const removeUser = useCallback((users: UserResult) => { + setSelectedUsers((a) => a.filter((a) => a.id !== users.id)); + setDeletedUserIds((prevIds) => [...prevIds, users.id]); + }, []); + + const clearUser = useCallback(() => { + if (defaultValues !== undefined) { + resetField("addUserIds"); + setSelectedUsers( + initialUsers.filter((s) => defaultValues.addUserIds?.includes(s.id)) + ); + } + }, [defaultValues]); + + // Sync with form + useEffect(() => { + setValue( + "addUserIds", + selectedUsers.map((u) => u.id) + ); + setValue( + "removeUserIds", + deletedUserIds + ); + }, [selectedUsers, deletedUserIds, setValue]); + + const UserPoolColumns = useMemo[]>( + () => [ + { + label: t("Add"), + name: "id", + onClick: addUser, + buttonIcon: , + }, + { label: t("User Name"), name: "username" }, + { label: t("name"), name: "name" }, + ], + [addUser, t] + ); + + const allocatedUserColumns = useMemo[]>( + () => [ + { + label: t("Remove"), + name: "id", + onClick: removeUser, + buttonIcon: , + }, + { label: t("User Name"), name: "username" }, + { label: t("name"), name: "name" }, + ], + [removeUser, selectedUsers, 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)) + // }) + // ); + }, [users, query]); + + const resetUser = React.useCallback(() => { + clearQueryInput(); + clearUser(); + }, [clearQueryInput, clearUser]); + + const formProps = useForm({}); + + // Tab related + const [tabIndex, setTabIndex] = React.useState(0); + const handleTabChange = React.useCallback>( + (_e, newValue) => { + setTabIndex(newValue); + }, + [] + ); + + return ( + <> + + + + + + {t("User")} + + + + + + + + + + ), + }} + /> + + + + + + + + {tabIndex === 0 && ( + + )} + {tabIndex === 1 && ( + + )} + + + + + + + ); +}; + +export default UserAllocation; diff --git a/src/components/EditUserGroup/index.ts b/src/components/EditUserGroup/index.ts new file mode 100644 index 0000000..b062020 --- /dev/null +++ b/src/components/EditUserGroup/index.ts @@ -0,0 +1 @@ +export { default } from "./EditUserGroupWrapper"; diff --git a/src/components/UserGroupSearch/UserGroupSearch.tsx b/src/components/UserGroupSearch/UserGroupSearch.tsx index 0480167..fee25e4 100644 --- a/src/components/UserGroupSearch/UserGroupSearch.tsx +++ b/src/components/UserGroupSearch/UserGroupSearch.tsx @@ -10,6 +10,7 @@ import { useRouter } from "next/navigation"; import { deleteDialog, successDialog } from "../Swal/CustomAlerts"; import { UserGroupResult } from "@/app/api/group"; import { deleteUser } from "@/app/api/user/actions"; +import { deleteGroup } from "@/app/api/group/actions"; interface Props { users: UserGroupResult[]; @@ -34,20 +35,20 @@ const UserGroupSearch: React.FC = ({ users }) => { ); const onUserClick = useCallback( - (users: UserGroupResult) => { - console.log(users); - // router.push(`/settings/user/edit?id=${users.id}`) + (group: UserGroupResult) => { + console.log(group); + router.push(`/settings/group/edit?id=${group.id}`) }, [router, t] ); - const onDeleteClick = useCallback((users: UserGroupResult) => { + const onDeleteClick = useCallback((group: UserGroupResult) => { deleteDialog(async () => { - await deleteUser(users.id); + await deleteGroup(group.id); successDialog(t("Delete Success"), t); - setFilteredUser((prev) => prev.filter((obj) => obj.id !== users.id)); + setFilteredUser((prev) => prev.filter((obj) => obj.id !== group.id)); }, t); }, []);