update table

This commit is contained in:
2026-04-09 15:36:16 +07:00
parent 430063e913
commit b4a667e764
4 changed files with 159 additions and 102 deletions

View File

@@ -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"
>
<path
strokeLinecap="round"
@@ -62,7 +71,6 @@ export default function BasicTableOne({
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path
strokeLinecap="round"
@@ -84,27 +92,58 @@ export default function BasicTableOne({
<TableRow>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[265px] max-w-[265px]"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("display_name")}
>
Người dùng
<SortIcon column="display_name" />
Người dùng <SortIcon column="display_name" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400"
className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400 min-w-[337px] max-w-[337px]"
>
<div
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("email")}
>
Email
<SortIcon column="email" />
Email <SortIcon column="email" />
</div>
</TableCell>
<TableCell
isHeader
className="px-5 py-3 font-medium text-start text-theme-xs min-w-[188px] max-w-[188px]"
>
<div className="relative inline-flex items-center group">
<select
value={selectedRole}
onChange={(e) => onFilterRole?.(e.target.value)}
className="bg-transparent border-none outline-none cursor-pointer appearance-none text-gray-500 dark:text-gray-400 pr-5 hover:text-brand-500 transition-colors font-medium"
>
<option value="">Vai trò (Tất cả)</option>
{roles.map((role) => (
<option key={role.id} value={role.id}>
{role.name}
</option>
))}
</select>
<svg
className="w-3 h-3 absolute right-0 text-gray-400 pointer-events-none group-hover:text-brand-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={3}
d="M19 9l-7 7-7-7"
/>
</svg>
</div>
</TableCell>
@@ -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ò
</TableCell>
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-start text-theme-xs dark:text-gray-400">
Trạng thái
</TableCell>
@@ -128,8 +162,7 @@ export default function BasicTableOne({
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("created_at")}
>
Ngày tham gia
<SortIcon column="created_at" />
Ngày tham gia <SortIcon column="created_at" />
</div>
</TableCell>
@@ -141,11 +174,14 @@ export default function BasicTableOne({
className="flex items-center cursor-pointer select-none"
onClick={() => onSort("updated_at")}
>
Cập nhật
<SortIcon column="updated_at" />
Cập nhật <SortIcon column="updated_at" />
</div>
</TableCell>
<TableCell isHeader className="px-5 py-3 font-medium text-gray-500 text-center text-theme-xs dark:text-gray-400">
<TableCell
isHeader
className="px-5 py-3 font-medium text-gray-500 text-center text-theme-xs dark:text-gray-400"
>
Thao tác
</TableCell>
</TableRow>
@@ -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({
<TableCell className="px-5 py-4 text-start text-theme-sm">
<div className="flex flex-wrap gap-1">
{user.roles && user.roles.length > 0 ? (
user.roles.map((role) => (
<span
key={role.id}
className="px-2 py-0.5 rounded-md bg-brand-50 text-brand-600 dark:bg-brand-500/10 dark:text-brand-400 text-[10px] font-normal uppercase tracking-wider"
>
{role.name}
</span>
))
) : (
<span className="text-gray-400 italic">No Role</span>
{user.roles?.map((role) => (
<span
key={role.id}
className="px-2 py-0.5 rounded-md bg-brand-50 text-brand-600 dark:bg-brand-500/10 dark:text-brand-400 text-[10px] uppercase"
>
{role.name}
</span>
)) || (
<span className="text-gray-400 italic text-[10px]">
No Role
</span>
)}
</div>
</TableCell>
@@ -220,27 +256,35 @@ export default function BasicTableOne({
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{formatDate(user.created_at)}
</TableCell>
<TableCell className="px-5 py-4 text-gray-600 text-theme-sm dark:text-gray-400">
{formatDate(user.updated_at)}
</TableCell>
<TableCell className="px-5 py-4 text-center">
<button
onClick={() => onViewDetail(user)}
className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm transition-colors"
>
Chi tiết
</button>
</TableCell>
<button
onClick={() => onViewDetail(user)}
className="text-brand-500 hover:text-brand-600 font-medium text-theme-sm"
>
Chi tiết
</button>
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell className="px-5 py-4 text-center text-gray-500 italic"> </TableCell>
<TableCell
colSpan={7}
className="px-5 py-24 text-center text-gray-500 italic"
>
<div className="flex flex-col items-center justify-center gap-2">
<p className="text-theme-sm">
Không tìm thấy dữ liệu người dùng
</p>
</div>
</TableCell>
</TableRow>
)}
</TableBody>
</TableBody>
</Table>
</div>
</div>

View File

@@ -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<HTMLTableElement> {
children: ReactNode;
}
// Props for TableHeader
interface TableHeaderProps {
children: ReactNode; // Header row(s)
className?: string; // Optional className for styling
interface TableHeaderProps extends HTMLAttributes<HTMLTableSectionElement> {
children: ReactNode;
}
// Props for TableBody
interface TableBodyProps {
children: ReactNode; // Body row(s)
className?: string; // Optional className for styling
interface TableBodyProps extends HTMLAttributes<HTMLTableSectionElement> {
children: ReactNode;
}
// Props for TableRow
interface TableRowProps {
children: ReactNode; // Cells (th or td)
className?: string; // Optional className for styling
interface TableRowProps extends HTMLAttributes<HTMLTableRowElement> {
children: ReactNode;
}
// Props for TableCell
interface TableCellProps {
children: ReactNode; // Cell content
isHeader?: boolean; // If true, renders as <th>, otherwise <td>
className?: string; // Optional className for styling
// Props for TableCell - Hỗ trợ colSpan, rowSpan...
interface TableCellProps extends TdHTMLAttributes<HTMLTableCellElement> {
children: ReactNode;
isHeader?: boolean;
}
// Table Component
const Table: React.FC<TableProps> = ({ children, className }) => {
return <table className={`min-w-full ${className}`}>{children}</table>;
const Table: React.FC<TableProps> = ({ children, className, ...props }) => {
return <table className={`min-w-full ${className}`} {...props}>{children}</table>;
};
// TableHeader Component
const TableHeader: React.FC<TableHeaderProps> = ({ children, className }) => {
return <thead className={className}>{children}</thead>;
const TableHeader: React.FC<TableHeaderProps> = ({ children, className, ...props }) => {
return <thead className={className} {...props}>{children}</thead>;
};
// TableBody Component
const TableBody: React.FC<TableBodyProps> = ({ children, className }) => {
return <tbody className={className}>{children}</tbody>;
const TableBody: React.FC<TableBodyProps> = ({ children, className, ...props }) => {
return <tbody className={className} {...props}>{children}</tbody>;
};
// TableRow Component
const TableRow: React.FC<TableRowProps> = ({ children, className }) => {
return <tr className={className}>{children}</tr>;
const TableRow: React.FC<TableRowProps> = ({ children, className, ...props }) => {
return <tr className={className} {...props}>{children}</tr>;
};
// TableCell Component
@@ -56,9 +51,14 @@ const TableCell: React.FC<TableCellProps> = ({
children,
isHeader = false,
className,
...props
}) => {
const CellTag = isHeader ? "th" : "td";
return <CellTag className={` ${className}`}>{children}</CellTag>;
return (
<CellTag className={className} {...(props as any)}>
{children}
</CellTag>
);
};
export { Table, TableHeader, TableBody, TableRow, TableCell };
export { Table, TableHeader, TableBody, TableRow, TableCell };