feat-update

This commit is contained in:
TuanVT
2026-08-20 18:07:25 +07:00
parent 94e7975cd0
commit a6f0a2ef9a
+11 -10
View File
@@ -10,7 +10,7 @@ import {
} from "lucide-react"; } from "lucide-react";
/* ================= TYPES ================= */ /* ================= TYPES ================= */
interface ColumnType<T> { export interface ColumnType<T> {
title: string | React.ReactNode; title: string | React.ReactNode;
render: (row: T, index: number, path?: number[]) => React.ReactNode; render: (row: T, index: number, path?: number[]) => React.ReactNode;
className?: string; className?: string;
@@ -23,7 +23,8 @@ interface ColumnType<T> {
interface PropsTable<T> { interface PropsTable<T> {
data: T[]; data: T[];
column: ColumnType<T>[]; // Cập nhật kiểu dữ liệu ở đây để chấp nhận thêm false | null | undefined
column: (ColumnType<T> | false | null | undefined)[];
fixedHeader?: boolean; fixedHeader?: boolean;
activeHeader?: boolean; activeHeader?: boolean;
handleCheckedAll?: (e: React.ChangeEvent<HTMLInputElement>) => void; handleCheckedAll?: (e: React.ChangeEvent<HTMLInputElement>) => void;
@@ -38,7 +39,7 @@ interface PropsTable<T> {
/* ================= COMPONENT ================= */ /* ================= COMPONENT ================= */
export default function Table<T>({ export default function Table<T>({
data, data,
column, column: rawColumn, // Đổi tên prop column thành rawColumn
fixedHeader, fixedHeader,
activeHeader, activeHeader,
handleCheckedAll, handleCheckedAll,
@@ -52,6 +53,12 @@ export default function Table<T>({
const tableRef = useRef<HTMLDivElement>(null); const tableRef = useRef<HTMLDivElement>(null);
const thRefs = useRef<(HTMLTableCellElement | null)[]>([]); const thRefs = useRef<(HTMLTableCellElement | null)[]>([]);
// Lọc bỏ các giá trị falsy (false, null, undefined) để thu được mảng ColumnType<T>[] hợp lệ
const column = useMemo(
() => rawColumn.filter((col): col is ColumnType<T> => Boolean(col)),
[rawColumn],
);
const [expandedRows, setExpandedRows] = useState<React.Key[]>([]); const [expandedRows, setExpandedRows] = useState<React.Key[]>([]);
const [sortConfig, setSortConfig] = useState<{ const [sortConfig, setSortConfig] = useState<{
key: string; key: string;
@@ -172,7 +179,7 @@ export default function Table<T>({
); );
}); });
/* ================= MOBILE CARDS (KHUÔN BẢNG GIỐNG ẢNH MẪU) ================= */ /* ================= MOBILE CARDS ================= */
const renderMobileCards = (rows: T[], path: number[] = []): React.ReactNode => const renderMobileCards = (rows: T[], path: number[] = []): React.ReactNode =>
rows.map((row, i) => { rows.map((row, i) => {
const currentPath = [...path, i]; const currentPath = [...path, i];
@@ -182,15 +189,12 @@ export default function Table<T>({
return ( return (
<Fragment key={String(keyPath)}> <Fragment key={String(keyPath)}>
{/* Khung Card cho mỗi bệnh nhân / dòng */}
<div className="border border-gray-500 bg-white overflow-hidden text-sm rounded-none mb-3"> <div className="border border-gray-500 bg-white overflow-hidden text-sm rounded-none mb-3">
{column.map((col, idx) => ( {column.map((col, idx) => (
<div <div
key={idx} key={idx}
/* Chia 40% - 60% giúp cột chứa button Action rộng rãi hơn */
className="grid grid-cols-[2fr_3fr] border-b border-gray-500 last:border-b-0 divide-x divide-gray-500 min-h-[44px]" className="grid grid-cols-[2fr_3fr] border-b border-gray-500 last:border-b-0 divide-x divide-gray-500 min-h-[44px]"
> >
{/* Tiêu đề bên trái (TÊN BỆNH NHÂN, MÃ PHIÊN KHÁM,...) */}
<div className="p-2 font-bold flex items-center justify-center text-center bg-white uppercase text-xs tracking-tight text-gray-900 leading-tight min-w-0 break-words"> <div className="p-2 font-bold flex items-center justify-center text-center bg-white uppercase text-xs tracking-tight text-gray-900 leading-tight min-w-0 break-words">
{col.checkBox && ( {col.checkBox && (
<input <input
@@ -203,14 +207,12 @@ export default function Table<T>({
{col.title} {col.title}
</div> </div>
{/* Giá trị / Nút bấm bên phải */}
<div className="p-2 flex flex-wrap items-center justify-center text-center gap-1.5 min-w-0 break-words overflow-hidden text-gray-800 font-normal"> <div className="p-2 flex flex-wrap items-center justify-center text-center gap-1.5 min-w-0 break-words overflow-hidden text-gray-800 font-normal">
{col.render(row, i, currentPath)} {col.render(row, i, currentPath)}
</div> </div>
</div> </div>
))} ))}
{/* Xem thêm nếu có hàng con */}
{getChildren && !!children?.length && ( {getChildren && !!children?.length && (
<div className="p-2 bg-gray-50 border-t border-gray-500 text-center"> <div className="p-2 bg-gray-50 border-t border-gray-500 text-center">
<button <button
@@ -230,7 +232,6 @@ export default function Table<T>({
)} )}
</div> </div>
{/* Render hàng con nếu có */}
{expanded && children && ( {expanded && children && (
<div className="pl-2 border-l-2 border-blue-500 space-y-1"> <div className="pl-2 border-l-2 border-blue-500 space-y-1">
{renderMobileCards(children, currentPath)} {renderMobileCards(children, currentPath)}