chore: update member section

This commit is contained in:
Steven
2024-01-25 19:49:39 +08:00
parent a5bc2d0ed6
commit 6d5e1def76
4 changed files with 84 additions and 33 deletions

View File

@ -88,13 +88,13 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
return ( return (
<> <>
<div className="dialog-header-container"> <div className="dialog-header-container !w-96">
<p className="title-text">{"Add references"}</p> <p className="title-text">{"Add references"}</p>
<IconButton size="sm" onClick={() => destroy()}> <IconButton size="sm" onClick={() => destroy()}>
<Icon.X className="w-5 h-auto" /> <Icon.X className="w-5 h-auto" />
</IconButton> </IconButton>
</div> </div>
<div className="dialog-content-container !w-80"> <div className="dialog-content-container max-w-[24rem]">
<Autocomplete <Autocomplete
className="w-full" className="w-full"
size="md" size="md"
@ -133,7 +133,7 @@ const CreateMemoRelationDialog: React.FC<Props> = (props: Props) => {
} }
onChange={(_, value) => setSelectedMemos(value)} onChange={(_, value) => setSelectedMemos(value)}
/> />
<div className="mt-2 w-full flex flex-row justify-end items-center space-x-1"> <div className="mt-4 w-full flex flex-row justify-end items-center space-x-1">
<Button variant="plain" color="neutral" onClick={handleCloseDialog}> <Button variant="plain" color="neutral" onClick={handleCloseDialog}>
{t("common.cancel")} {t("common.cancel")}
</Button> </Button>

View File

@ -1,9 +1,10 @@
import { Button, Dropdown, Input, Menu, MenuButton } from "@mui/joy"; import { Button, Dropdown, Input, Menu, MenuButton, Radio, RadioGroup } from "@mui/joy";
import { sortBy } from "lodash-es";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { userServiceClient } from "@/grpcweb"; import { userServiceClient } from "@/grpcweb";
import useCurrentUser from "@/hooks/useCurrentUser"; import useCurrentUser from "@/hooks/useCurrentUser";
import { UserNamePrefix, useUserStore } from "@/store/v1"; import { UserNamePrefix, stringifyUserRole, useUserStore } from "@/store/v1";
import { RowStatus } from "@/types/proto/api/v2/common"; import { RowStatus } from "@/types/proto/api/v2/common";
import { User, User_Role } from "@/types/proto/api/v2/user_service"; import { User, User_Role } from "@/types/proto/api/v2/user_service";
import { useTranslate } from "@/utils/i18n"; import { useTranslate } from "@/utils/i18n";
@ -12,8 +13,7 @@ import { showCommonDialog } from "../Dialog/CommonDialog";
import Icon from "../Icon"; import Icon from "../Icon";
interface State { interface State {
createUserUsername: string; creatingUser: User;
createUserPassword: string;
} }
const MemberSection = () => { const MemberSection = () => {
@ -21,36 +21,56 @@ const MemberSection = () => {
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const userStore = useUserStore(); const userStore = useUserStore();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
createUserUsername: "", creatingUser: User.fromPartial({
createUserPassword: "", username: "",
password: "",
role: User_Role.USER,
}),
}); });
const [userList, setUserList] = useState<User[]>([]); const [users, setUsers] = useState<User[]>([]);
const sortedUsers = sortBy(users, "id");
useEffect(() => { useEffect(() => {
fetchUserList(); fetchUsers();
}, []); }, []);
const fetchUserList = async () => { const fetchUsers = async () => {
const users = await userStore.fetchUsers(); const users = await userStore.fetchUsers();
setUserList(users); setUsers(users);
}; };
const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleUsernameInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setState({ setState({
...state, ...state,
createUserUsername: event.target.value, creatingUser: {
...state.creatingUser,
username: event.target.value,
},
}); });
}; };
const handlePasswordInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handlePasswordInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setState({ setState({
...state, ...state,
createUserPassword: event.target.value, creatingUser: {
...state.creatingUser,
password: event.target.value,
},
});
};
const handleUserRoleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setState({
...state,
creatingUser: {
...state.creatingUser,
role: Number(event.target.value) as User_Role,
},
}); });
}; };
const handleCreateUserBtnClick = async () => { const handleCreateUserBtnClick = async () => {
if (state.createUserUsername === "" || state.createUserPassword === "") { if (state.creatingUser.username === "" || state.creatingUser.password === "") {
toast.error(t("message.fill-form")); toast.error(t("message.fill-form"));
return; return;
} }
@ -58,18 +78,22 @@ const MemberSection = () => {
try { try {
await userServiceClient.createUser({ await userServiceClient.createUser({
user: { user: {
name: `${UserNamePrefix}${state.createUserUsername}`, name: `${UserNamePrefix}${state.creatingUser.username}`,
password: state.createUserPassword, password: state.creatingUser.password,
role: User_Role.USER, role: state.creatingUser.role,
}, },
}); });
} catch (error: any) { } catch (error: any) {
toast.error(error.details); toast.error(error.details);
} }
await fetchUserList(); await fetchUsers();
setState({ setState({
createUserUsername: "", ...state,
createUserPassword: "", creatingUser: User.fromPartial({
username: "",
password: "",
role: User_Role.USER,
}),
}); });
}; };
@ -91,7 +115,7 @@ const MemberSection = () => {
}, },
updateMask: ["row_status"], updateMask: ["row_status"],
}); });
fetchUserList(); fetchUsers();
}, },
}); });
}; };
@ -104,7 +128,7 @@ const MemberSection = () => {
}, },
updateMask: ["row_status"], updateMask: ["row_status"],
}); });
fetchUserList(); fetchUsers();
}; };
const handleDeleteUserClick = (user: User) => { const handleDeleteUserClick = (user: User) => {
@ -115,7 +139,7 @@ const MemberSection = () => {
dialogName: "delete-user-dialog", dialogName: "delete-user-dialog",
onConfirm: async () => { onConfirm: async () => {
await userStore.deleteUser(user.name); await userStore.deleteUser(user.name);
fetchUserList(); fetchUsers();
}, },
}); });
}; };
@ -126,13 +150,25 @@ const MemberSection = () => {
<div className="w-full flex flex-col justify-start items-start gap-2"> <div className="w-full flex flex-col justify-start items-start gap-2">
<div className="flex flex-col justify-start items-start gap-1"> <div className="flex flex-col justify-start items-start gap-1">
<span className="text-sm">{t("common.username")}</span> <span className="text-sm">{t("common.username")}</span>
<Input type="text" placeholder={t("common.username")} value={state.createUserUsername} onChange={handleUsernameInputChange} /> <Input type="text" placeholder={t("common.username")} value={state.creatingUser.username} onChange={handleUsernameInputChange} />
</div> </div>
<div className="flex flex-col justify-start items-start gap-1"> <div className="flex flex-col justify-start items-start gap-1">
<span className="text-sm">{t("common.password")}</span> <span className="text-sm">{t("common.password")}</span>
<Input type="password" placeholder={t("common.password")} value={state.createUserPassword} onChange={handlePasswordInputChange} /> <Input
type="password"
placeholder={t("common.password")}
value={state.creatingUser.password}
onChange={handlePasswordInputChange}
/>
</div> </div>
<div className="btns-container"> <div className="flex flex-col justify-start items-start gap-1">
<span className="text-sm">{t("common.role")}</span>
<RadioGroup size="sm" orientation="horizontal" defaultValue={User_Role.USER} onChange={handleUserRoleInputChange}>
<Radio value={User_Role.USER} label="User" />
<Radio value={User_Role.ADMIN} label="Admin" />
</RadioGroup>
</div>
<div className="mt-2">
<Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button> <Button onClick={handleCreateUserBtnClick}>{t("common.create")}</Button>
</div> </div>
</div> </div>
@ -144,9 +180,12 @@ const MemberSection = () => {
<table className="min-w-full divide-y divide-gray-300 dark:divide-zinc-600"> <table className="min-w-full divide-y divide-gray-300 dark:divide-zinc-600">
<thead> <thead>
<tr className="text-sm font-semibold text-left text-gray-900 dark:text-gray-400"> <tr className="text-sm font-semibold text-left text-gray-900 dark:text-gray-400">
<th scope="col" className="py-2 pl-4 pr-3"> <th scope="col" className="px-3 py-2">
ID ID
</th> </th>
<th scope="col" className="px-3 py-2">
{t("common.role")}
</th>
<th scope="col" className="px-3 py-2"> <th scope="col" className="px-3 py-2">
{t("common.username")} {t("common.username")}
</th> </th>
@ -160,9 +199,10 @@ const MemberSection = () => {
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-gray-200 dark:divide-zinc-600"> <tbody className="divide-y divide-gray-200 dark:divide-zinc-600">
{userList.map((user) => ( {sortedUsers.map((user) => (
<tr key={user.id}> <tr key={user.id}>
<td className="whitespace-nowrap py-2 pl-4 pr-3 text-sm text-gray-900 dark:text-gray-400">{user.id}</td> <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-900 dark:text-gray-400">{user.id}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">{stringifyUserRole(user.role)}</td>
<td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400"> <td className="whitespace-nowrap px-3 py-2 text-sm text-gray-500 dark:text-gray-400">
{user.username} {user.username}
<span className="ml-1 italic">{user.rowStatus === RowStatus.ARCHIVED && "(Archived)"}</span> <span className="ml-1 italic">{user.rowStatus === RowStatus.ARCHIVED && "(Archived)"}</span>

View File

@ -64,7 +64,8 @@
"mark": "Mark", "mark": "Mark",
"profile": "Profile", "profile": "Profile",
"inbox": "Inbox", "inbox": "Inbox",
"search": "Search" "search": "Search",
"role": "Role"
}, },
"router": { "router": {
"go-to-home": "Go to Home", "go-to-home": "Go to Home",

View File

@ -1,7 +1,7 @@
import { create } from "zustand"; import { create } from "zustand";
import { combine } from "zustand/middleware"; import { combine } from "zustand/middleware";
import { authServiceClient, userServiceClient } from "@/grpcweb"; import { authServiceClient, userServiceClient } from "@/grpcweb";
import { User, UserSetting } from "@/types/proto/api/v2/user_service"; import { User, UserSetting, User_Role } from "@/types/proto/api/v2/user_service";
import { UserNamePrefix, extractUsernameFromName } from "./resourceName"; import { UserNamePrefix, extractUsernameFromName } from "./resourceName";
interface State { interface State {
@ -125,3 +125,13 @@ export const useUserStore = create(
}, },
})) }))
); );
export const stringifyUserRole = (role: User_Role) => {
if (role === User_Role.HOST) {
return "Host";
} else if (role === User_Role.ADMIN) {
return "Admin";
} else {
return "User";
}
};