TTMT.ManageWebGUI/src/components/device-table.tsx

226 lines
7.1 KiB
TypeScript
Raw Normal View History

2025-10-20 16:46:17 +07:00
import {
flexRender,
getCoreRowModel,
getPaginationRowModel,
useReactTable,
type ColumnDef,
} from "@tanstack/react-table";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Wifi, WifiOff, Clock, MapPin, Monitor, ChevronLeft, ChevronRight } from "lucide-react";
import { useMachineNumber } from "../hooks/useMachineNumber";
interface DeviceTableProps {
devices: any[];
}
/**
* Component hiển thị danh sách thiết bị dạng bảng
*/
export function DeviceTable({ devices }: DeviceTableProps) {
const getMachineNumber = useMachineNumber();
const columns: ColumnDef<any>[] = [
{
header: "STT",
cell: ({ row }) => {
const machineNumber = getMachineNumber(row.original.id || "");
return (
<div className="flex items-center gap-3">
<Avatar className="h-8 w-8">
<AvatarFallback className="bg-primary/10 text-primary">
<Monitor className="h-4 w-4" />
</AvatarFallback>
</Avatar>
<span className="font-medium text-sm">{machineNumber}</span>
</div>
);
},
},
{
header: () => (
<div className="flex items-center gap-2">
<Clock className="h-4 w-4" />
Thời gian thiết bị
</div>
),
accessorKey: "deviceTime",
cell: ({ getValue }) => {
const date = new Date(getValue() as string);
return (
<div className="text-sm">
<div className="font-medium">{date.toLocaleDateString("vi-VN")}</div>
<div className="text-muted-foreground">{date.toLocaleTimeString("vi-VN")}</div>
</div>
);
},
},
{
header: "Phiên bản",
accessorKey: "version",
cell: ({ getValue }) => (
<Badge variant="secondary" className="font-mono">
v{getValue() as string}
</Badge>
),
},
{
header: () => (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4" />
Phòng
</div>
),
accessorKey: "room",
cell: ({ getValue }) => <span className="text-sm font-medium">{getValue() as string}</span>,
},
{
header: () => (
<div className="flex items-center gap-2">
<Wifi className="h-4 w-4" />
Thông tin mạng
</div>
),
accessorKey: "networkInfos",
cell: ({ getValue }) => {
const infos = getValue() as { macAddress?: string; ipAddress?: string }[];
if (!infos || infos.length === 0) {
return <span className="text-muted-foreground text-sm">Không dữ liệu</span>;
}
return (
<div className="flex flex-col gap-1">
{infos.map((info, idx) => (
<div
key={idx}
className="flex items-center gap-2 text-sm font-mono px-2 py-1 rounded bg-muted/30"
>
<span className="text-primary"></span>
<code className="bg-background px-2 py-0.5 rounded">
{info.macAddress ?? "-"}
</code>
<span className="text-muted-foreground"></span>
<code className="bg-background px-2 py-0.5 rounded">
{info.ipAddress ?? "-"}
</code>
</div>
))}
</div>
);
},
},
{
header: "Trạng thái",
accessorKey: "isOffline",
cell: ({ getValue }) => {
const isOffline = getValue() as boolean;
return (
<Badge
variant={isOffline ? "destructive" : "default"}
className={`flex items-center gap-1 w-fit ${
isOffline
? "bg-red-100 text-red-700 hover:bg-red-100"
: "bg-green-100 text-green-700 hover:bg-green-100"
}`}
>
{isOffline ? <WifiOff className="h-3 w-3" /> : <Wifi className="h-3 w-3" />}
{isOffline ? "Offline" : "Online"}
</Badge>
);
},
},
];
const table = useReactTable({
data: devices,
columns,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: { pagination: { pageSize: 16 } },
});
return (
<div className="max-h-[600px] overflow-y-auto">
<Table>
<TableHeader className="sticky top-0 bg-background z-10">
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id} className="hover:bg-transparent border-b">
{headerGroup.headers.map((header) => (
<TableHead key={header.id} className="font-semibold text-foreground bg-muted/30">
{flexRender(header.column.columnDef.header, header.getContext())}
</TableHead>
))}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows.map((row) => (
<TableRow key={row.id} className="hover:bg-muted/50 transition-colors">
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id} className="py-4">
{flexRender(cell.column.columnDef.cell, cell.getContext())}
</TableCell>
))}
</TableRow>
))}
</TableBody>
</Table>
{/* Pagination */}
<div className="flex items-center justify-between p-4 border-t bg-muted/20">
<div className="flex items-center gap-2 text-sm text-muted-foreground">
<span>
Hiển thị{" "}
{table.getState().pagination.pageIndex * table.getState().pagination.pageSize + 1} -{" "}
{Math.min(
(table.getState().pagination.pageIndex + 1) * table.getState().pagination.pageSize,
devices.length
)}{" "}
trong tổng số {devices.length} thiết bị
</span>
</div>
<div className="flex items-center gap-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
className="flex items-center gap-1"
>
<ChevronLeft className="h-4 w-4" />
Trước
</Button>
<div className="flex items-center gap-1 text-sm font-medium">
<span>Trang</span>
<span className="bg-primary text-primary-foreground px-2 py-1 rounded">
{table.getState().pagination.pageIndex + 1}
</span>
<span>của {table.getPageCount()}</span>
</div>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
className="flex items-center gap-1"
>
Sau
<ChevronRight className="h-4 w-4" />
</Button>
</div>
</div>
</div>
);
}