add user route

This commit is contained in:
Do Manh Phuong 2026-03-19 18:38:21 +07:00
parent dc7ed4c71a
commit b7b8431975
12 changed files with 287 additions and 166 deletions

View File

@ -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

View File

@ -15,3 +15,5 @@ export * from "./usePermissionQueries";
// Role Queries // Role Queries
export * from "./useRoleQueries"; export * from "./useRoleQueries";
// User Queries
export * from "./useUserQueries";

View 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
});
}

View File

@ -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,

View File

@ -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

View File

@ -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ọ 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>
);
} }

View File

@ -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>

View File

@ -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 các vai trò 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 vai trò nào trong hệ thống
</div>
)}
</CardContent>
</Card>
</div>
);
}

View 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 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 };

View 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;

View File

@ -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
View 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;
};