add user route
This commit is contained in:
parent
dc7ed4c71a
commit
b7b8431975
|
|
@ -13,6 +13,7 @@ export const API_ENDPOINTS = {
|
||||||
PING: `${BASE_URL}/ping`,
|
PING: `${BASE_URL}/ping`,
|
||||||
CSRF_TOKEN: `${BASE_URL}/csrf-token`,
|
CSRF_TOKEN: `${BASE_URL}/csrf-token`,
|
||||||
CREATE_ACCOUNT: `${BASE_URL}/auth/create-account`,
|
CREATE_ACCOUNT: `${BASE_URL}/auth/create-account`,
|
||||||
|
GET_USERS_LIST: `${BASE_URL}/users-info`,
|
||||||
},
|
},
|
||||||
APP_VERSION: {
|
APP_VERSION: {
|
||||||
//agent and app api
|
//agent and app api
|
||||||
|
|
|
||||||
|
|
@ -15,3 +15,5 @@ export * from "./usePermissionQueries";
|
||||||
|
|
||||||
// Role Queries
|
// Role Queries
|
||||||
export * from "./useRoleQueries";
|
export * from "./useRoleQueries";
|
||||||
|
// User Queries
|
||||||
|
export * from "./useUserQueries";
|
||||||
|
|
|
||||||
20
src/hooks/queries/useUserQueries.ts
Normal file
20
src/hooks/queries/useUserQueries.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
|
import * as userService from "@/services/user.service";
|
||||||
|
import type { UserProfile } from "@/types/user-profile";
|
||||||
|
|
||||||
|
const USER_QUERY_KEYS = {
|
||||||
|
all: ["users"] as const,
|
||||||
|
list: () => [...USER_QUERY_KEYS.all, "list"] as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook để lấy danh sách thông tin người dùng
|
||||||
|
*/
|
||||||
|
export function useGetUsersInfo(enabled = true) {
|
||||||
|
return useQuery<UserProfile[]>({
|
||||||
|
queryKey: USER_QUERY_KEYS.list(),
|
||||||
|
queryFn: () => userService.getUsersInfo(),
|
||||||
|
enabled,
|
||||||
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -21,7 +21,6 @@ import { Route as AuthBlacklistsIndexRouteImport } from './routes/_auth/blacklis
|
||||||
import { Route as AuthAppsIndexRouteImport } from './routes/_auth/apps/index'
|
import { Route as AuthAppsIndexRouteImport } from './routes/_auth/apps/index'
|
||||||
import { Route as AuthAgentIndexRouteImport } from './routes/_auth/agent/index'
|
import { Route as AuthAgentIndexRouteImport } from './routes/_auth/agent/index'
|
||||||
import { Route as authLoginIndexRouteImport } from './routes/(auth)/login/index'
|
import { Route as authLoginIndexRouteImport } from './routes/(auth)/login/index'
|
||||||
import { Route as AuthUserRoleIndexRouteImport } from './routes/_auth/user/role/index'
|
|
||||||
import { Route as AuthUserCreateIndexRouteImport } from './routes/_auth/user/create/index'
|
import { Route as AuthUserCreateIndexRouteImport } from './routes/_auth/user/create/index'
|
||||||
import { Route as AuthRoomsRoomNameIndexRouteImport } from './routes/_auth/rooms/$roomName/index'
|
import { Route as AuthRoomsRoomNameIndexRouteImport } from './routes/_auth/rooms/$roomName/index'
|
||||||
import { Route as AuthRoleCreateIndexRouteImport } from './routes/_auth/role/create/index'
|
import { Route as AuthRoleCreateIndexRouteImport } from './routes/_auth/role/create/index'
|
||||||
|
|
@ -91,11 +90,6 @@ const authLoginIndexRoute = authLoginIndexRouteImport.update({
|
||||||
path: '/login/',
|
path: '/login/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const AuthUserRoleIndexRoute = AuthUserRoleIndexRouteImport.update({
|
|
||||||
id: '/user/role/',
|
|
||||||
path: '/user/role/',
|
|
||||||
getParentRoute: () => AuthRoute,
|
|
||||||
} as any)
|
|
||||||
const AuthUserCreateIndexRoute = AuthUserCreateIndexRouteImport.update({
|
const AuthUserCreateIndexRoute = AuthUserCreateIndexRouteImport.update({
|
||||||
id: '/user/create/',
|
id: '/user/create/',
|
||||||
path: '/user/create/',
|
path: '/user/create/',
|
||||||
|
|
@ -163,7 +157,6 @@ export interface FileRoutesByFullPath {
|
||||||
'/role/create': typeof AuthRoleCreateIndexRoute
|
'/role/create': typeof AuthRoleCreateIndexRoute
|
||||||
'/rooms/$roomName': typeof AuthRoomsRoomNameIndexRoute
|
'/rooms/$roomName': typeof AuthRoomsRoomNameIndexRoute
|
||||||
'/user/create': typeof AuthUserCreateIndexRoute
|
'/user/create': typeof AuthUserCreateIndexRoute
|
||||||
'/user/role': typeof AuthUserRoleIndexRoute
|
|
||||||
'/role/$id/edit': typeof AuthRoleIdEditIndexRoute
|
'/role/$id/edit': typeof AuthRoleIdEditIndexRoute
|
||||||
'/rooms/$roomName/folder-status': typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
'/rooms/$roomName/folder-status': typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
||||||
'/user/change-password/$userName': typeof AuthUserChangePasswordUserNameIndexRoute
|
'/user/change-password/$userName': typeof AuthUserChangePasswordUserNameIndexRoute
|
||||||
|
|
@ -186,7 +179,6 @@ export interface FileRoutesByTo {
|
||||||
'/role/create': typeof AuthRoleCreateIndexRoute
|
'/role/create': typeof AuthRoleCreateIndexRoute
|
||||||
'/rooms/$roomName': typeof AuthRoomsRoomNameIndexRoute
|
'/rooms/$roomName': typeof AuthRoomsRoomNameIndexRoute
|
||||||
'/user/create': typeof AuthUserCreateIndexRoute
|
'/user/create': typeof AuthUserCreateIndexRoute
|
||||||
'/user/role': typeof AuthUserRoleIndexRoute
|
|
||||||
'/role/$id/edit': typeof AuthRoleIdEditIndexRoute
|
'/role/$id/edit': typeof AuthRoleIdEditIndexRoute
|
||||||
'/rooms/$roomName/folder-status': typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
'/rooms/$roomName/folder-status': typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
||||||
'/user/change-password/$userName': typeof AuthUserChangePasswordUserNameIndexRoute
|
'/user/change-password/$userName': typeof AuthUserChangePasswordUserNameIndexRoute
|
||||||
|
|
@ -211,7 +203,6 @@ export interface FileRoutesById {
|
||||||
'/_auth/role/create/': typeof AuthRoleCreateIndexRoute
|
'/_auth/role/create/': typeof AuthRoleCreateIndexRoute
|
||||||
'/_auth/rooms/$roomName/': typeof AuthRoomsRoomNameIndexRoute
|
'/_auth/rooms/$roomName/': typeof AuthRoomsRoomNameIndexRoute
|
||||||
'/_auth/user/create/': typeof AuthUserCreateIndexRoute
|
'/_auth/user/create/': typeof AuthUserCreateIndexRoute
|
||||||
'/_auth/user/role/': typeof AuthUserRoleIndexRoute
|
|
||||||
'/_auth/role/$id/edit/': typeof AuthRoleIdEditIndexRoute
|
'/_auth/role/$id/edit/': typeof AuthRoleIdEditIndexRoute
|
||||||
'/_auth/rooms/$roomName/folder-status/': typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
'/_auth/rooms/$roomName/folder-status/': typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
||||||
'/_auth/user/change-password/$userName/': typeof AuthUserChangePasswordUserNameIndexRoute
|
'/_auth/user/change-password/$userName/': typeof AuthUserChangePasswordUserNameIndexRoute
|
||||||
|
|
@ -236,7 +227,6 @@ export interface FileRouteTypes {
|
||||||
| '/role/create'
|
| '/role/create'
|
||||||
| '/rooms/$roomName'
|
| '/rooms/$roomName'
|
||||||
| '/user/create'
|
| '/user/create'
|
||||||
| '/user/role'
|
|
||||||
| '/role/$id/edit'
|
| '/role/$id/edit'
|
||||||
| '/rooms/$roomName/folder-status'
|
| '/rooms/$roomName/folder-status'
|
||||||
| '/user/change-password/$userName'
|
| '/user/change-password/$userName'
|
||||||
|
|
@ -259,7 +249,6 @@ export interface FileRouteTypes {
|
||||||
| '/role/create'
|
| '/role/create'
|
||||||
| '/rooms/$roomName'
|
| '/rooms/$roomName'
|
||||||
| '/user/create'
|
| '/user/create'
|
||||||
| '/user/role'
|
|
||||||
| '/role/$id/edit'
|
| '/role/$id/edit'
|
||||||
| '/rooms/$roomName/folder-status'
|
| '/rooms/$roomName/folder-status'
|
||||||
| '/user/change-password/$userName'
|
| '/user/change-password/$userName'
|
||||||
|
|
@ -283,7 +272,6 @@ export interface FileRouteTypes {
|
||||||
| '/_auth/role/create/'
|
| '/_auth/role/create/'
|
||||||
| '/_auth/rooms/$roomName/'
|
| '/_auth/rooms/$roomName/'
|
||||||
| '/_auth/user/create/'
|
| '/_auth/user/create/'
|
||||||
| '/_auth/user/role/'
|
|
||||||
| '/_auth/role/$id/edit/'
|
| '/_auth/role/$id/edit/'
|
||||||
| '/_auth/rooms/$roomName/folder-status/'
|
| '/_auth/rooms/$roomName/folder-status/'
|
||||||
| '/_auth/user/change-password/$userName/'
|
| '/_auth/user/change-password/$userName/'
|
||||||
|
|
@ -382,13 +370,6 @@ declare module '@tanstack/react-router' {
|
||||||
preLoaderRoute: typeof authLoginIndexRouteImport
|
preLoaderRoute: typeof authLoginIndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/_auth/user/role/': {
|
|
||||||
id: '/_auth/user/role/'
|
|
||||||
path: '/user/role'
|
|
||||||
fullPath: '/user/role'
|
|
||||||
preLoaderRoute: typeof AuthUserRoleIndexRouteImport
|
|
||||||
parentRoute: typeof AuthRoute
|
|
||||||
}
|
|
||||||
'/_auth/user/create/': {
|
'/_auth/user/create/': {
|
||||||
id: '/_auth/user/create/'
|
id: '/_auth/user/create/'
|
||||||
path: '/user/create'
|
path: '/user/create'
|
||||||
|
|
@ -470,7 +451,6 @@ interface AuthRouteChildren {
|
||||||
AuthRoleCreateIndexRoute: typeof AuthRoleCreateIndexRoute
|
AuthRoleCreateIndexRoute: typeof AuthRoleCreateIndexRoute
|
||||||
AuthRoomsRoomNameIndexRoute: typeof AuthRoomsRoomNameIndexRoute
|
AuthRoomsRoomNameIndexRoute: typeof AuthRoomsRoomNameIndexRoute
|
||||||
AuthUserCreateIndexRoute: typeof AuthUserCreateIndexRoute
|
AuthUserCreateIndexRoute: typeof AuthUserCreateIndexRoute
|
||||||
AuthUserRoleIndexRoute: typeof AuthUserRoleIndexRoute
|
|
||||||
AuthRoleIdEditIndexRoute: typeof AuthRoleIdEditIndexRoute
|
AuthRoleIdEditIndexRoute: typeof AuthRoleIdEditIndexRoute
|
||||||
AuthRoomsRoomNameFolderStatusIndexRoute: typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
AuthRoomsRoomNameFolderStatusIndexRoute: typeof AuthRoomsRoomNameFolderStatusIndexRoute
|
||||||
AuthUserChangePasswordUserNameIndexRoute: typeof AuthUserChangePasswordUserNameIndexRoute
|
AuthUserChangePasswordUserNameIndexRoute: typeof AuthUserChangePasswordUserNameIndexRoute
|
||||||
|
|
@ -492,7 +472,6 @@ const AuthRouteChildren: AuthRouteChildren = {
|
||||||
AuthRoleCreateIndexRoute: AuthRoleCreateIndexRoute,
|
AuthRoleCreateIndexRoute: AuthRoleCreateIndexRoute,
|
||||||
AuthRoomsRoomNameIndexRoute: AuthRoomsRoomNameIndexRoute,
|
AuthRoomsRoomNameIndexRoute: AuthRoomsRoomNameIndexRoute,
|
||||||
AuthUserCreateIndexRoute: AuthUserCreateIndexRoute,
|
AuthUserCreateIndexRoute: AuthUserCreateIndexRoute,
|
||||||
AuthUserRoleIndexRoute: AuthUserRoleIndexRoute,
|
|
||||||
AuthRoleIdEditIndexRoute: AuthRoleIdEditIndexRoute,
|
AuthRoleIdEditIndexRoute: AuthRoleIdEditIndexRoute,
|
||||||
AuthRoomsRoomNameFolderStatusIndexRoute:
|
AuthRoomsRoomNameFolderStatusIndexRoute:
|
||||||
AuthRoomsRoomNameFolderStatusIndexRoute,
|
AuthRoomsRoomNameFolderStatusIndexRoute,
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ function CreateUserComponent() {
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() => navigate({ to: "/dashboard" })}
|
onClick={() => navigate({ to: "/user" })}
|
||||||
>
|
>
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
Quay lại
|
Quay lại
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,165 @@
|
||||||
import { createFileRoute } from '@tanstack/react-router'
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import { UserListTemplate } from "@/template/user-list-template";
|
||||||
|
import { useGetUsersInfo } from "@/hooks/queries";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipTrigger,
|
||||||
|
TooltipContent,
|
||||||
|
} from "@/components/ui/tooltip";
|
||||||
|
import type { ColumnDef } from "@tanstack/react-table";
|
||||||
|
import { VersionTable } from "@/components/tables/version-table";
|
||||||
|
import { Edit2, Trash2, Shield } from "lucide-react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export const Route = createFileRoute('/_auth/user/')({
|
export const Route = createFileRoute("/_auth/user/")({
|
||||||
component: RouteComponent,
|
component: RouteComponent,
|
||||||
})
|
loader: async ({ context }) => {
|
||||||
|
context.breadcrumbs = [
|
||||||
|
{ title: "Quản lý người dùng", path: "#" },
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return <div>Hello "/_auth/user/"!</div>
|
const navigate = useNavigate();
|
||||||
|
const { data: users = [], isLoading } = useGetUsersInfo();
|
||||||
|
|
||||||
|
const [table, setTable] = useState<any>();
|
||||||
|
|
||||||
|
const columns = useMemo<ColumnDef<any>[]>(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
accessorKey: "userName",
|
||||||
|
header: () => <div className="text-center whitespace-normal max-w-xs">Tên đăng nhập</div>,
|
||||||
|
cell: ({ getValue }) => (
|
||||||
|
<div className="flex justify-center"><span className="font-medium">{getValue() as string}</span></div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "name",
|
||||||
|
header: () => <div className="text-center whitespace-normal max-w-xs">Họ và tên</div>,
|
||||||
|
cell: ({ getValue }) => (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<span className="truncate block max-w-[240px]">
|
||||||
|
{getValue() as string}
|
||||||
|
</span>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="bottom">
|
||||||
|
{getValue() as string}
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "role",
|
||||||
|
header: () => <div className="text-center whitespace-normal max-w-xs">Vai trò</div>,
|
||||||
|
cell: ({ getValue }) => <div className="flex justify-center">{getValue() as string}</div>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "accessRooms",
|
||||||
|
header: () => <div className="text-center whitespace-normal max-w-xs">Phòng</div>,
|
||||||
|
cell: ({ getValue }) => (
|
||||||
|
<div className="flex justify-center">{Array.isArray(getValue()) ? (getValue() as number[]).join(", ") : "-"}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "select",
|
||||||
|
header: () => <div className="text-center whitespace-normal max-w-xs">Chọn</div>,
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
checked={row.getIsSelected?.() ?? false}
|
||||||
|
onChange={row.getToggleSelectedHandler?.()}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
header: () => (
|
||||||
|
<div className="text-center whitespace-normal max-w-xs">Hành động</div>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="flex gap-2 justify-center items-center">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate({
|
||||||
|
to: "/user/change-password/$userName",
|
||||||
|
params: { userName: row.original.userName },
|
||||||
|
} as any);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Edit2 className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate({ to: "/user/role/$roleId", params: { roleId: String(row.original.roleId) } } as any);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Shield className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!confirm("Bạn có chắc muốn xóa người dùng này?")) return;
|
||||||
|
// Placeholder delete - implement API call as needed
|
||||||
|
toast.success("Xóa người dùng (chưa thực thi API)");
|
||||||
|
if (table) table.setRowSelection({});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Trash2 className="h-4 w-4 text-red-500" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
enableSorting: false,
|
||||||
|
enableHiding: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
const tableData = users.map((u) => ({ ...u, id: u.userName }));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserListTemplate
|
||||||
|
title="Người dùng"
|
||||||
|
description="Danh sách tài khoản hệ thống"
|
||||||
|
headerAction={
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
onClick={() => navigate({ to: "/user/create" } as any)}
|
||||||
|
className="gap-2"
|
||||||
|
>
|
||||||
|
Thêm người dùng
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="p-4">
|
||||||
|
<VersionTable
|
||||||
|
data={tableData}
|
||||||
|
isLoading={isLoading}
|
||||||
|
columns={columns as ColumnDef<any, any>[]}
|
||||||
|
scrollable={true}
|
||||||
|
maxHeight="400px"
|
||||||
|
enablePagination={false}
|
||||||
|
onTableInit={(t) => setTable(t)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</UserListTemplate>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,11 +13,24 @@ import { Shield, ArrowLeft, Check, X } from "lucide-react";
|
||||||
import type { PermissionOnRole } from "@/types/permission";
|
import type { PermissionOnRole } from "@/types/permission";
|
||||||
|
|
||||||
export const Route = createFileRoute("/_auth/user/role/$roleId/")({
|
export const Route = createFileRoute("/_auth/user/role/$roleId/")({
|
||||||
|
head: () => ({
|
||||||
|
meta: [{ title: "Quyền của người dùng | AccessControl" }]
|
||||||
|
}),
|
||||||
component: ViewRolePermissionsComponent,
|
component: ViewRolePermissionsComponent,
|
||||||
loader: async ({ context, params }) => {
|
loader: async ({ context, params }) => {
|
||||||
context.breadcrumbs = [
|
context.breadcrumbs = [
|
||||||
{ title: "Quản lý người dùng", path: "#" },
|
{
|
||||||
{ title: `Quyền của Role #${params.roleId}`, path: `/user/role/${params.roleId}` },
|
title: "Quản lý tài khoản",
|
||||||
|
path: "#"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Danh sách người dùng",
|
||||||
|
path: "/user"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Quyền của người dùng",
|
||||||
|
path: `/user/role/${params.roleId}`
|
||||||
|
}
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -60,7 +73,7 @@ function ViewRolePermissionsComponent() {
|
||||||
return (
|
return (
|
||||||
<div className="w-full px-6 space-y-4">
|
<div className="w-full px-6 space-y-4">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button variant="ghost" size="sm" onClick={() => navigate({ to: "/dashboard" })}>
|
<Button variant="ghost" size="sm" onClick={() => navigate({ to: "/user" })}>
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
Quay lại
|
Quay lại
|
||||||
</Button>
|
</Button>
|
||||||
|
|
@ -88,11 +101,11 @@ function ViewRolePermissionsComponent() {
|
||||||
<div key={parent?.permisionId} className="border rounded-lg p-4">
|
<div key={parent?.permisionId} className="border rounded-lg p-4">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="font-semibold text-lg">{parent?.permissionName || "Unknown"}</span>
|
<span className="font-semibold text-lg">{parent?.permissionName || "Allow all"}</span>
|
||||||
<Badge variant="secondary" className="text-xs">{parent?.permissionCode}</Badge>
|
<Badge variant="secondary" className="text-xs">{parent?.permissionCode}</Badge>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-1">
|
<div className="flex items-center gap-1">
|
||||||
{parent?.isChecked === 1 ? (
|
{(parent?.isChecked === 1 || parent === null) ? (
|
||||||
<Badge variant="default" className="bg-green-600">
|
<Badge variant="default" className="bg-green-600">
|
||||||
<Check className="h-3 w-3 mr-1" />Đã bật
|
<Check className="h-3 w-3 mr-1" />Đã bật
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|
|
||||||
|
|
@ -1,133 +0,0 @@
|
||||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
|
||||||
import { useGetRoleList } from "@/hooks/queries";
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableBody,
|
|
||||||
TableCell,
|
|
||||||
TableHead,
|
|
||||||
TableHeader,
|
|
||||||
TableRow,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Shield, Eye, ArrowLeft, Loader2 } from "lucide-react";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/_auth/user/role/")({
|
|
||||||
component: RoleListComponent,
|
|
||||||
loader: async ({ context }) => {
|
|
||||||
context.breadcrumbs = [
|
|
||||||
{ title: "Quản lý người dùng", path: "#" },
|
|
||||||
{ title: "Danh sách vai trò", path: "/user/role" },
|
|
||||||
];
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function RoleListComponent() {
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const { data: roles, isLoading, isError } = useGetRoleList();
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
return (
|
|
||||||
<div className="w-full px-6 flex items-center justify-center py-12">
|
|
||||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isError) {
|
|
||||||
return (
|
|
||||||
<div className="w-full px-6 space-y-4">
|
|
||||||
<Card className="max-w-2xl mx-auto">
|
|
||||||
<CardContent className="pt-6">
|
|
||||||
<div className="text-center text-destructive">
|
|
||||||
Không thể tải danh sách vai trò
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-center mt-4">
|
|
||||||
<Button variant="outline" onClick={() => navigate({ to: "/dashboard" })}>
|
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
||||||
Quay lại
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full px-6 space-y-4">
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => navigate({ to: "/dashboard" })}>
|
|
||||||
<ArrowLeft className="h-4 w-4 mr-2" />
|
|
||||||
Quay lại
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card className="max-w-4xl mx-auto">
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="flex items-center gap-2">
|
|
||||||
<Shield className="h-5 w-5" />
|
|
||||||
Danh sách vai trò
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Quản lý các vai trò và quyền hạn trong hệ thống
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{roles && roles.length > 0 ? (
|
|
||||||
<Table>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead>ID</TableHead>
|
|
||||||
<TableHead>Tên vai trò</TableHead>
|
|
||||||
<TableHead>Độ ưu tiên</TableHead>
|
|
||||||
<TableHead>Ngày tạo</TableHead>
|
|
||||||
<TableHead className="text-right">Thao tác</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{roles.map((role) => (
|
|
||||||
<TableRow key={role.id}>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant="outline">{role.id}</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="font-medium">{role.roleName}</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge variant="secondary">{role.priority}</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-muted-foreground">
|
|
||||||
{role.createdAt ? new Date(role.createdAt).toLocaleDateString("vi-VN") : "—"}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className="text-right">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
size="sm"
|
|
||||||
onClick={() =>
|
|
||||||
navigate({ to: "/user/role/$roleId", params: { roleId: String(role.id) } } as any)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Eye className="h-4 w-4 mr-1" />
|
|
||||||
Xem quyền
|
|
||||||
</Button>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
|
||||||
Không có vai trò nào trong hệ thống
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
34
src/services/user.service.ts
Normal file
34
src/services/user.service.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
import axios from "@/config/axios";
|
||||||
|
import { API_ENDPOINTS } from "@/config/api";
|
||||||
|
import type { UserProfile } from "@/types/user-profile";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lấy danh sách thông tin người dùng và chuyển sang camelCase keys
|
||||||
|
*/
|
||||||
|
export async function getUsersInfo(): Promise<UserProfile[]> {
|
||||||
|
try {
|
||||||
|
const response = await axios.get<any[]>(API_ENDPOINTS.AUTH.GET_USERS_LIST);
|
||||||
|
const list = Array.isArray(response.data) ? response.data : [];
|
||||||
|
|
||||||
|
return list.map((u: any) => ({
|
||||||
|
userName: u.userName ?? u.UserName ?? "",
|
||||||
|
name: u.name ?? u.Name ?? "",
|
||||||
|
role: u.role ?? u.Role ?? "",
|
||||||
|
roleId: u.roleId !== undefined ? Number(u.roleId) : u.RoleId !== undefined ? Number(u.RoleId) : 0,
|
||||||
|
accessRooms: Array.isArray(u.accessRooms)
|
||||||
|
? u.accessRooms.map((v: any) => Number(v))
|
||||||
|
: Array.isArray(u.AccessRooms)
|
||||||
|
? u.AccessRooms.map((v: any) => Number(v))
|
||||||
|
: [],
|
||||||
|
createdAt: u.createdAt ?? u.CreatedAt ?? null,
|
||||||
|
createdBy: u.createdBy ?? u.CreatedBy ?? null,
|
||||||
|
updatedAt: u.updatedAt ?? u.UpdatedAt ?? null,
|
||||||
|
updatedBy: u.updatedBy ?? u.UpdatedBy ?? null,
|
||||||
|
} as UserProfile));
|
||||||
|
} catch (error) {
|
||||||
|
console.error("getUsersInfo error:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default { getUsersInfo };
|
||||||
38
src/template/user-list-template.tsx
Normal file
38
src/template/user-list-template.tsx
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter} from "@/components/ui/card";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
children?: React.ReactNode;
|
||||||
|
headerAction?: React.ReactNode;
|
||||||
|
footer?: React.ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function UserListTemplate({ title, description, children, headerAction, footer }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="w-full px-6 space-y-6">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold">{title}</h1>
|
||||||
|
{description && <div className="text-muted-foreground mt-2">{description}</div>}
|
||||||
|
</div>
|
||||||
|
<div>{headerAction}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="w-full">
|
||||||
|
<CardHeader>
|
||||||
|
<div>
|
||||||
|
<CardTitle className="flex items-center gap-2">Danh sách người dùng</CardTitle>
|
||||||
|
{description && <CardDescription>{description}</CardDescription>}
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>{children}</CardContent>
|
||||||
|
{footer && <CardFooter>{footer}</CardFooter>}
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UserListTemplate;
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AppWindow, Building, CircleX, Home, ShieldCheck, Terminal, UserPlus} from "lucide-react";
|
import { AppWindow, Building, CircleX, Folder, Home, ShieldCheck, Terminal, UserPlus} from "lucide-react";
|
||||||
import { PermissionEnum } from "./permission";
|
import { PermissionEnum } from "./permission";
|
||||||
|
|
||||||
enum AppSidebarSectionCode {
|
enum AppSidebarSectionCode {
|
||||||
|
|
@ -54,7 +54,7 @@ export const appSidebarSection = {
|
||||||
{
|
{
|
||||||
title: "Thư mục Setup",
|
title: "Thư mục Setup",
|
||||||
url: "/apps",
|
url: "/apps",
|
||||||
icon: AppWindow,
|
icon: Folder,
|
||||||
permissions: [PermissionEnum.VIEW_APPS],
|
permissions: [PermissionEnum.VIEW_APPS],
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
||||||
11
src/types/user-profile.ts
Normal file
11
src/types/user-profile.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
export type UserProfile = {
|
||||||
|
userName: string;
|
||||||
|
name: string;
|
||||||
|
role: string;
|
||||||
|
roleId: number;
|
||||||
|
accessRooms: number[];
|
||||||
|
createdAt?: string | null;
|
||||||
|
createdBy?: string | null;
|
||||||
|
updatedAt?: string | null;
|
||||||
|
updatedBy?: string | null;
|
||||||
|
};
|
||||||
Loading…
Reference in New Issue
Block a user