feat:update

This commit is contained in:
TuanVT
2026-07-27 16:09:08 +07:00
parent 04ba0d3118
commit 684d7d2490
4 changed files with 157 additions and 69 deletions
+79 -4
View File
@@ -106,7 +106,7 @@ export default function Table<T>({
); );
}; };
/* ================= ROWS ================= */ /* ================= DESKTOP / TABLET ROWS ================= */
const renderRows = (rows: T[], path: number[] = []): React.ReactNode => const renderRows = (rows: T[], path: number[] = []): React.ReactNode =>
rows.map((row, i) => { rows.map((row, i) => {
const currentPath = [...path, i]; const currentPath = [...path, i];
@@ -141,6 +141,7 @@ export default function Table<T>({
"px-4 py-3 text-sm font-medium whitespace-nowrap", "px-4 py-3 text-sm font-medium whitespace-nowrap",
col.fixedLeft && "sticky left-0 bg-white z-10", col.fixedLeft && "sticky left-0 bg-white z-10",
col.fixedRight && "sticky right-0 bg-white z-10", col.fixedRight && "sticky right-0 bg-white z-10",
col.className,
)} )}
> >
<div <div
@@ -171,12 +172,84 @@ export default function Table<T>({
); );
}); });
/* ================= UI ================= */ /* ================= MOBILE CARDS (KHUÔN BẢNG GIỐNG ẢNH MẪU) ================= */
const renderMobileCards = (rows: T[], path: number[] = []): React.ReactNode =>
rows.map((row, i) => {
const currentPath = [...path, i];
const keyPath = useIndexPathAsKey ? currentPath.join(".") : rowKey(row);
const children = getChildren?.(row);
const expanded = expandedRows.includes(keyPath);
return ( return (
<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">
{column.map((col, idx) => (
<div <div
ref={tableRef} 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]"
>
{/* 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">
{col.checkBox && (
<input
type="checkbox"
className="mr-1.5 w-4 h-4 accent-blue-500 cursor-pointer shrink-0"
onChange={(e) => handleCheckedRow?.(e, row)}
checked={handleIsCheckedRow?.(row) ?? false}
/>
)}
{col.title}
</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">
{col.render(row, i, currentPath)}
</div>
</div>
))}
{/* Xem thêm nếu có hàng con */}
{getChildren && !!children?.length && (
<div className="p-2 bg-gray-50 border-t border-gray-500 text-center">
<button
onClick={() => toggleExpand(keyPath)}
className="flex items-center justify-center gap-1 w-full text-blue-600 font-medium text-xs"
>
{expanded ? "Thu gọn" : "Xem thêm chi tiết"}
<ChevronUp
size={16}
className={clsx( className={clsx(
"overflow-auto pb-2", "transition-transform",
expanded && "rotate-180",
)}
/>
</button>
</div>
)}
</div>
{/* Render hàng con nếu có */}
{expanded && children && (
<div className="pl-2 border-l-2 border-blue-500 space-y-1">
{renderMobileCards(children, currentPath)}
</div>
)}
</Fragment>
);
});
/* ================= UI MAIN ================= */
return (
<div ref={tableRef} className="w-full">
{/* 1. ĐIỆN THOẠI (< md) */}
<div className="block md:hidden">{renderMobileCards(sortedData)}</div>
{/* 2. TABLET & DESKTOP (>= md) */}
<div
className={clsx(
"hidden md:block overflow-auto pb-2",
fixedHeader && "[&>table>thead]:sticky [&>table>thead]:top-0", fixedHeader && "[&>table>thead]:sticky [&>table>thead]:top-0",
activeHeader && "[&>table>thead]:bg-[#F4F7FA]", activeHeader && "[&>table>thead]:bg-[#F4F7FA]",
)} )}
@@ -202,6 +275,7 @@ export default function Table<T>({
isActive && "text-blue-600", isActive && "text-blue-600",
col.fixedLeft && "sticky left-0 bg-white z-10", col.fixedLeft && "sticky left-0 bg-white z-10",
col.fixedRight && "sticky right-0 bg-white z-10", col.fixedRight && "sticky right-0 bg-white z-10",
col.className,
)} )}
> >
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@@ -237,5 +311,6 @@ export default function Table<T>({
<tbody>{renderRows(sortedData)}</tbody> <tbody>{renderRows(sortedData)}</tbody>
</table> </table>
</div> </div>
</div>
); );
} }
@@ -601,13 +601,19 @@ const MainPageConsultation = () => {
// ===== TAB TẤT CẢ ===== // ===== TAB TẤT CẢ =====
return ( return (
<div className="flex items-center gap-2"> /*
Chỉnh sửa tại đây:
- flex-col: xếp dọc trên màn mobile
- sm:flex-row: quay lại xếp ngang trên tablet / desktop (>= 640px)
- w-full sm:w-auto: mở rộng full chiều rộng ô khi ở màn mobile cho cân đối
*/
<div className="flex flex-col sm:flex-row items-center justify-center gap-2 w-full sm:w-auto">
<Popover.Root> <Popover.Root>
<Popover.Trigger asChild> <Popover.Trigger asChild>
<button <button
disabled={isDisabled} disabled={isDisabled}
className={clsx( className={clsx(
"flex h-8 w-8 items-center justify-center rounded-full border border-gray-200", "flex h-8 w-8 items-center justify-center rounded-full border border-gray-200 shrink-0",
isDisabled isDisabled
? "cursor-not-allowed opacity-50" ? "cursor-not-allowed opacity-50"
: "hover:bg-gray-100", : "hover:bg-gray-100",
@@ -658,6 +664,7 @@ const MainPageConsultation = () => {
variant="red" variant="red"
disabled={isDisabled} disabled={isDisabled}
onClick={() => handleOpenCancel(item)} onClick={() => handleOpenCancel(item)}
className="w-full sm:w-auto"
> >
Hủy Hủy
</CustomButton> </CustomButton>
@@ -667,6 +674,7 @@ const MainPageConsultation = () => {
variant="green" variant="green"
disabled={isDisabled} disabled={isDisabled}
onClick={() => handleOpenComplete(item)} onClick={() => handleOpenComplete(item)}
className="w-full sm:w-auto"
> >
Hoàn thành Hoàn thành
</CustomButton> </CustomButton>
@@ -300,7 +300,12 @@ const MainPagePatient = () => {
title: "ACTION", title: "ACTION",
render: (item: PatientItem) => ( render: (item: PatientItem) => (
<div className="flex items-center gap-2"> /*
- flex-col: Xếp dọc các icon trên mobile
- sm:flex-row: Xếp ngang lại trên màn lớn (>= 640px)
- items-center justify-center: Căn giữa các nút khi xếp dọc
*/
<div className="flex flex-col sm:flex-row items-center justify-center gap-2">
{/* UPDATE */} {/* UPDATE */}
<CustomButton <CustomButton
size="sm" size="sm"
@@ -185,7 +185,7 @@ const MainPrescription = () => {
title: "ACTION", title: "ACTION",
render: (item: any) => ( render: (item: any) => (
<div className="flex items-center gap-2"> <div className="flex flex-col sm:flex-row items-center justify-center gap-2">
<CustomButton <CustomButton
size="sm" size="sm"
icon={<Eye className="text-blue-400" />} icon={<Eye className="text-blue-400" />}