From 88a48237b058c80fe5e551e3dc0c50628cc9462b Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Tue, 12 May 2026 18:05:08 +0800 Subject: [PATCH] User Page Update --- public/PP Staff List v.7.xlsx | Bin 0 -> 20110 bytes src/app/(main)/settings/user/page.tsx | 21 +- src/app/api/group/actions.ts | 17 + src/app/api/user/client.ts | 32 ++ src/app/utils/fetchUtil.ts | 8 +- src/components/EditUser/EditUser.tsx | 81 +---- src/components/EditUser/EditUserWrapper.tsx | 13 +- .../UserSearch/UserExcelSheetView.tsx | 327 ++++++++++++++++++ src/i18n/en/user.json | 8 +- src/i18n/zh/user.json | 8 +- 10 files changed, 421 insertions(+), 94 deletions(-) create mode 100644 public/PP Staff List v.7.xlsx create mode 100644 src/components/UserSearch/UserExcelSheetView.tsx diff --git a/public/PP Staff List v.7.xlsx b/public/PP Staff List v.7.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..e96d5355452593f6cd2aea5d43dce80e9647a52d GIT binary patch literal 20110 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 M05w*Y`T6eu0ep{wk^lez literal 0 HcmV?d00001 diff --git a/src/app/(main)/settings/user/page.tsx b/src/app/(main)/settings/user/page.tsx index b0221c1..6876046 100644 --- a/src/app/(main)/settings/user/page.tsx +++ b/src/app/(main)/settings/user/page.tsx @@ -1,12 +1,13 @@ import { Metadata } from "next"; import { getServerI18n, I18nProvider } from "@/i18n"; import Typography from "@mui/material/Typography"; -import { Suspense } from "react"; import { Stack } from "@mui/material"; import { Button } from "@mui/material"; import Link from "next/link"; -import UserSearch from "@/components/UserSearch"; import Add from "@mui/icons-material/Add"; +import UserExcelSheetView from "../../../../components/UserSearch/UserExcelSheetView"; +import { fetchUser } from "@/app/api/user"; +import { fetchAuthBatchByUserIds } from "@/app/api/group/actions"; export const metadata: Metadata = { title: "User Management", @@ -14,6 +15,18 @@ export const metadata: Metadata = { const User: React.FC = async () => { const { t } = await getServerI18n("user"); + const users = await fetchUser(); + const authBatchMap = await fetchAuthBatchByUserIds(users.map(user => user.id)).catch( + () => ({} as Record), + ); + const usersWithDetails = users.map(user => { + const authRecords = authBatchMap[user.id] ?? []; + return { + ...user, + authIds: authRecords.filter(a => a.v === 1).map(a => a.id), + auths: authRecords, + }; + }); return ( <> { - }> - - + ); diff --git a/src/app/api/group/actions.ts b/src/app/api/group/actions.ts index 219804c..604977b 100644 --- a/src/app/api/group/actions.ts +++ b/src/app/api/group/actions.ts @@ -31,6 +31,8 @@ export interface record { records: auth[]; } +export type UserAuthBatchRecord = Record; + export const fetchAuth = cache(async (target: string, id?: number) => { return serverFetchJson( `${BASE_API_URL}/group/auth/${target}/${id ?? 0}`, @@ -40,6 +42,21 @@ export const fetchAuth = cache(async (target: string, id?: number) => { ); }); +export const fetchAuthBatchByUserIds = cache(async (userIds: number[]) => { + if (userIds.length === 0) { + return {} as UserAuthBatchRecord; + } + + return serverFetchJson( + `${BASE_API_URL}/group/auth/user-batch?${new URLSearchParams( + userIds.map(id => ["userIds", String(id)]), + ).toString()}`, + { + next: { tags: ["auth"] }, + }, + ); +}); + export const saveGroup = async (data: CreateGroupInputs) => { const newGroup = serverFetchJson(`${BASE_API_URL}/group/save`, { method: "POST", diff --git a/src/app/api/user/client.ts b/src/app/api/user/client.ts index f07f7d6..2040da7 100644 --- a/src/app/api/user/client.ts +++ b/src/app/api/user/client.ts @@ -134,4 +134,36 @@ export const searchUsers = async (searchParams: { } return response.json(); +}; + +export interface UpdateUserRequest { + username: string; + name: string; + staffNo?: string; + locked?: boolean; + addAuthIds?: number[]; + removeAuthIds?: number[]; +} + +export const updateUser = async ( + id: number, + data: UpdateUserRequest, +): Promise => { + const token = localStorage.getItem("accessToken"); + + const response = await fetch(`${NEXT_PUBLIC_API_URL}/user/${id}`, { + method: "PUT", + headers: { + "Content-Type": "application/json", + ...(token && { Authorization: `Bearer ${token}` }), + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + if (response.status === 401) { + throw new Error("Unauthorized: Please log in again"); + } + throw new Error(`Failed to update user: ${response.status} ${response.statusText}`); + } }; \ No newline at end of file diff --git a/src/app/utils/fetchUtil.ts b/src/app/utils/fetchUtil.ts index 1c1fa6e..67e9ae4 100644 --- a/src/app/utils/fetchUtil.ts +++ b/src/app/utils/fetchUtil.ts @@ -97,7 +97,9 @@ export async function serverFetchJson(...args: FetchParams) { const t0 = performance.now(); const response = await serverFetch(...args); const t1 = performance.now(); - console.log(`[serverFetchJson] ${response.status} ${(t1 - t0).toFixed(1)}ms ${url}`); + if (process.env.NEXT_PUBLIC_DEBUG_FETCH_LOG === "true") { + console.log(`[serverFetchJson] ${response.status} ${(t1 - t0).toFixed(1)}ms ${url}`); + } if (response.ok) { if (response.status === 204) { return response.status as T; @@ -124,7 +126,9 @@ export async function serverFetchString(...args: FetchParams) { const t0 = performance.now(); const response = await serverFetch(...args); const t1 = performance.now(); -console.log(`[serverFetchJson] ${response.status} ${(t1 - t0).toFixed(1)}ms ${url}`); +if (process.env.NEXT_PUBLIC_DEBUG_FETCH_LOG === "true") { + console.log(`[serverFetchJson] ${response.status} ${(t1 - t0).toFixed(1)}ms ${url}`); +} if (response.ok) { return response.text() as T; diff --git a/src/components/EditUser/EditUser.tsx b/src/components/EditUser/EditUser.tsx index 6da4b62..b55739b 100644 --- a/src/components/EditUser/EditUser.tsx +++ b/src/components/EditUser/EditUser.tsx @@ -10,106 +10,61 @@ import React, { import { useTranslation } from "react-i18next"; import { Button, - Card, - CardContent, - Grid, Stack, - Tab, - Tabs, - TabsProps, - TextField, Typography, } from "@mui/material"; import { - FieldErrors, FormProvider, SubmitErrorHandler, SubmitHandler, useForm, - useFormContext, } from "react-hook-form"; -import { Check, Close, Error, RestartAlt } from "@mui/icons-material"; +import { Check, Close, RestartAlt } from "@mui/icons-material"; import { UserInputs, adminChangePassword, editUser, - fetchUserDetails, } from "@/app/api/user/actions"; import UserDetail from "./UserDetail"; import { UserResult, passwordRule } from "@/app/api/user"; -import { auth } from "@/app/api/group/actions"; -import AuthAllocation from "./AuthAllocation"; interface Props { - user: UserResult; + user: UserResult & { authIds?: number[] }; rules: passwordRule; - auths: auth[]; } -const EditUser: React.FC = ({ user, rules, auths }) => { +const EditUser: React.FC = ({ user, rules }) => { console.log(user); const { t } = useTranslation("user"); const formProps = useForm(); const searchParams = useSearchParams(); const id = parseInt(searchParams.get("id") || "0"); - const [tabIndex, setTabIndex] = useState(0); const router = useRouter(); const [serverError, setServerError] = useState(""); - const addAuthIds = - auths && auths.length > 0 - ? auths - .filter((item) => item.v === 1) - .map((item) => item.id) - .sort((a, b) => a - b) - : []; - - const handleTabChange = useCallback>( - (_e, newValue) => { - setTabIndex(newValue); - }, - [], - ); - - const errors = formProps.formState.errors; const resetForm = React.useCallback((e?: React.MouseEvent) => { e?.preventDefault(); e?.stopPropagation(); - console.log("triggerred"); - console.log(addAuthIds); try { formProps.reset({ username: user.username, name: user.name, staffNo: user.staffNo?.toString() ?? "", - addAuthIds: addAuthIds, + addAuthIds: user.authIds ?? [], removeAuthIds: [], password: "", }); formProps.clearErrors(); - console.log(formProps.formState.defaultValues); } catch (error) { console.log(error); setServerError(t("An error has occurred. Please try again later.")); } - }, [formProps, auths, user, addAuthIds, t]); + }, [formProps, user, t]); useEffect(() => { resetForm(); }, [user.id]); - const hasErrorsInTab = ( - tabIndex: number, - errors: FieldErrors, - ) => { - switch (tabIndex) { - case 0: - return Object.keys(errors).length > 0; - default: - false; - } - }; - const handleCancel = () => { router.back(); }; @@ -195,31 +150,7 @@ const EditUser: React.FC = ({ user, rules, auths }) => { component="form" onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} > - - - - ) : undefined - } - iconPosition="end" - /> - - - - {tabIndex == 0 && } - {tabIndex === 1 && } +