From b4a667e764b4236156cef2ac3ee83aa4c6b884e7 Mon Sep 17 00:00:00 2001 From: bokhonglo Date: Thu, 9 Apr 2026 15:36:16 +0700 Subject: [PATCH] update table --- .../(tables)/user-table/page.tsx | 74 +++++----- src/components/tables/BasicTableOne.tsx | 130 ++++++++++++------ src/components/ui/table/index.tsx | 56 ++++---- src/service/adminService.ts | 1 + 4 files changed, 159 insertions(+), 102 deletions(-) diff --git a/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx b/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx index 1fe7a7c..fee8a9d 100644 --- a/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx +++ b/src/app/(admin)/(others-pages)/(tables)/user-table/page.tsx @@ -8,6 +8,7 @@ import { Modal } from "@/components/ui/modal"; import { responseUserTable, getUserDto, fullDataUser } from "@/interface/admin"; import { apiDeleteUser, + apiGetAllRole, apiGetListUser, apiRestoreUser, } from "@/service/adminService"; @@ -17,19 +18,18 @@ import { toast } from "sonner"; export type SortColumn = "created_at" | "updated_at" | "display_name" | "email"; export default function UserTable() { - const [limit, setLimit] = useState(5); - const [limitInput, setLimitInput] = useState("5"); const [page, setPage] = useState(1); + const [limitInput, setLimitInput] = useState("5"); + + const [selectedRole, setSelectedRole] = useState(""); + const [roles, setRoles] = useState<{ id: string; name: string }[]>([]); const [searchTerm, setSearchTerm] = useState(""); const [authProvider, setAuthProvider] = useState(""); - const [createdFrom, setCreatedFrom] = useState(""); - const [createdTo, setCreatedTo] = useState(""); const [isDeleted, setIsDeleted] = useState(undefined); const [selectedUser, setSelectedUser] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); - const [roleUser, setRoleUser] = useState(null); const [isRoleModalOpen, setIsRoleModalOpen] = useState(false); @@ -44,6 +44,21 @@ export default function UserTable() { const [sortBy, setSortBy] = useState(undefined); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("asc"); + useEffect(() => { + const fetchRoles = async () => { + try { + const res = await apiGetAllRole(); + console.log("Danh sách role:", res); + if (res?.status) { + setRoles(res.data); + } + } catch (err) { + console.error("Lỗi lấy danh sách role:", err); + } + }; + fetchRoles(); + }, []); + console.log("Roles đã fetch:", roles); useEffect(() => { const handler = setTimeout(() => { setDebouncedParams({ @@ -64,15 +79,13 @@ export default function UserTable() { limit: debouncedParams.limit, search: debouncedParams.search || undefined, auth_provider: debouncedParams.authProvider || undefined, - created_from: createdFrom || undefined, - created_to: createdTo || undefined, is_deleted: isDeleted, sort: sortBy, order: sortOrder, + role_ids: selectedRole ? [selectedRole] : undefined, }; const response = await apiGetListUser(payload); - if (response?.status) { setTableData(response); } @@ -82,15 +95,7 @@ export default function UserTable() { } finally { setLoading(false); } - }, [ - page, - debouncedParams, - createdFrom, - createdTo, - isDeleted, - sortBy, - sortOrder, - ]); + }, [page, debouncedParams, isDeleted, sortBy, sortOrder, selectedRole]); useEffect(() => { fetchUsers(); @@ -112,34 +117,39 @@ export default function UserTable() { setSelectedUser(user); setIsModalOpen(true); }; + const handleOpenRoleModal = (user: fullDataUser) => { setRoleUser(user); setIsRoleModalOpen(true); }; const handleDelete = async (user: fullDataUser) => { - const confirmMessage = `Bạn có chắc chắn muốn khóa/xóa người dùng ${user.profile?.display_name || user.email}?`; - if (window.confirm(confirmMessage)) { + if ( + window.confirm( + `Khóa người dùng ${user.profile?.display_name || user.email}?`, + ) + ) { try { await apiDeleteUser(user.id); toast.success("Đã khóa người dùng thành công!"); fetchUsers(); } catch (err) { - console.error(err); toast.error("Đã xảy ra lỗi khi xóa!"); } } }; const handleRestore = async (user: fullDataUser) => { - const confirmMessage = `Bạn có chắc chắn muốn khôi phục người dùng ${user.profile?.display_name || user.email}?`; - if (window.confirm(confirmMessage)) { + if ( + window.confirm( + `Khôi phục người dùng ${user.profile?.display_name || user.email}?`, + ) + ) { try { await apiRestoreUser(user.id); toast.success("Khôi phục người dùng thành công!"); - fetchUsers(); + fetchUsers(); } catch (err) { - console.error(err); toast.error("Đã xảy ra lỗi khi khôi phục!"); } } @@ -149,9 +159,7 @@ export default function UserTable() {
- {/* Grid Layout cho các Filter giống Swagger */}
- {/* Search */}
Auth Provider - setAuthProvider(e.target.value)} - className="w-full px-3 py-2 border rounded-lg dark:bg-gray-800 dark:border-gray-700" - /> + className="w-full px-3 py-2 border rounded-lg dark:bg-gray-800 dark:border-gray-700 cursor-pointer bg-white" + > + + +
@@ -226,6 +235,9 @@ export default function UserTable() { sortBy={sortBy} sortOrder={sortOrder} onViewDetail={handleOpenDetail} + roles={roles} + selectedRole={selectedRole} + onFilterRole={(role) => setSelectedRole(role)} />
diff --git a/src/components/tables/BasicTableOne.tsx b/src/components/tables/BasicTableOne.tsx index 410d66d..dee794e 100644 --- a/src/components/tables/BasicTableOne.tsx +++ b/src/components/tables/BasicTableOne.tsx @@ -13,22 +13,32 @@ import { fullDataUser } from "@/interface/admin"; type SortColumn = "created_at" | "updated_at" | "display_name" | "email"; +interface Role { + id: string; + name: string; +} + interface BasicTableOneProps { data: fullDataUser[]; onSort: (column: SortColumn) => void; - onViewDetail: (user: fullDataUser) => void; + onViewDetail: (user: fullDataUser) => void; sortBy?: SortColumn; sortOrder?: "asc" | "desc"; + onFilterRole?: (role: string) => void; + selectedRole?: string; + roles?: Role[]; } export default function BasicTableOne({ - data, + data, onSort, onViewDetail, sortBy, sortOrder, + onFilterRole, + selectedRole, + roles = [], }: BasicTableOneProps) { - const formatDate = (dateString: string) => { if (!dateString) return "-"; const date = new Date(dateString); @@ -48,7 +58,6 @@ export default function BasicTableOne({ fill="none" stroke="currentColor" viewBox="0 0 24 24" - xmlns="http://www.w3.org/2000/svg" >
onSort("display_name")} > - Người dùng - + Người dùng
onSort("email")} > - Email - + Email +
+
+ + +
+ + + +
@@ -112,11 +151,6 @@ export default function BasicTableOne({ isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400" > - Vai trò - - - - Trạng thái @@ -128,8 +162,7 @@ export default function BasicTableOne({ className="flex items-center cursor-pointer select-none" onClick={() => onSort("created_at")} > - Ngày tham gia - + Ngày tham gia
@@ -141,11 +174,14 @@ export default function BasicTableOne({ className="flex items-center cursor-pointer select-none" onClick={() => onSort("updated_at")} > - Cập nhật - + Cập nhật
- + + Thao tác @@ -166,7 +202,7 @@ export default function BasicTableOne({ width={40} height={40} src={user.profile.avatar_url} - alt={user.profile.display_name || "Avatar"} + alt="Avatar" className="object-cover w-full h-full" /> ) : ( @@ -192,17 +228,17 @@ export default function BasicTableOne({
- {user.roles && user.roles.length > 0 ? ( - user.roles.map((role) => ( - - {role.name} - - )) - ) : ( - No Role + {user.roles?.map((role) => ( + + {role.name} + + )) || ( + + No Role + )}
@@ -220,27 +256,35 @@ export default function BasicTableOne({ {formatDate(user.created_at)} - {formatDate(user.updated_at)} - - + +
)) ) : ( - + +
+

+ Không tìm thấy dữ liệu người dùng +

+
+
)} - + diff --git a/src/components/ui/table/index.tsx b/src/components/ui/table/index.tsx index 2a5b2ee..9d6b152 100644 --- a/src/components/ui/table/index.tsx +++ b/src/components/ui/table/index.tsx @@ -1,54 +1,49 @@ -import React, { ReactNode } from "react"; +import React, { ReactNode, TdHTMLAttributes, HTMLAttributes } from "react"; // Props for Table -interface TableProps { - children: ReactNode; // Table content (thead, tbody, etc.) - className?: string; // Optional className for styling +interface TableProps extends HTMLAttributes { + children: ReactNode; } // Props for TableHeader -interface TableHeaderProps { - children: ReactNode; // Header row(s) - className?: string; // Optional className for styling +interface TableHeaderProps extends HTMLAttributes { + children: ReactNode; } // Props for TableBody -interface TableBodyProps { - children: ReactNode; // Body row(s) - className?: string; // Optional className for styling +interface TableBodyProps extends HTMLAttributes { + children: ReactNode; } // Props for TableRow -interface TableRowProps { - children: ReactNode; // Cells (th or td) - className?: string; // Optional className for styling +interface TableRowProps extends HTMLAttributes { + children: ReactNode; } -// Props for TableCell -interface TableCellProps { - children: ReactNode; // Cell content - isHeader?: boolean; // If true, renders as , otherwise - className?: string; // Optional className for styling +// Props for TableCell - Hỗ trợ colSpan, rowSpan... +interface TableCellProps extends TdHTMLAttributes { + children: ReactNode; + isHeader?: boolean; } // Table Component -const Table: React.FC = ({ children, className }) => { - return {children}
; +const Table: React.FC = ({ children, className, ...props }) => { + return {children}
; }; // TableHeader Component -const TableHeader: React.FC = ({ children, className }) => { - return {children}; +const TableHeader: React.FC = ({ children, className, ...props }) => { + return {children}; }; // TableBody Component -const TableBody: React.FC = ({ children, className }) => { - return {children}; +const TableBody: React.FC = ({ children, className, ...props }) => { + return {children}; }; // TableRow Component -const TableRow: React.FC = ({ children, className }) => { - return {children}; +const TableRow: React.FC = ({ children, className, ...props }) => { + return {children}; }; // TableCell Component @@ -56,9 +51,14 @@ const TableCell: React.FC = ({ children, isHeader = false, className, + ...props }) => { const CellTag = isHeader ? "th" : "td"; - return {children}; + return ( + + {children} + + ); }; -export { Table, TableHeader, TableBody, TableRow, TableCell }; +export { Table, TableHeader, TableBody, TableRow, TableCell }; \ No newline at end of file diff --git a/src/service/adminService.ts b/src/service/adminService.ts index b4e8d17..40516d0 100644 --- a/src/service/adminService.ts +++ b/src/service/adminService.ts @@ -22,6 +22,7 @@ export const apiRestoreUser = async (id: string) => { const response = await api.patch(API.Admin.RESTORE_USER(id)); return response?.data; }; + export const apiGetAllRole = async () => { const response = await api.get(API.Admin.GET_ALL_ROLE); return response?.data;