TTMT.ManageWebGUI/src/routes/_authenticated/room/$roomName/index.tsx

352 lines
11 KiB
TypeScript
Raw Normal View History

2025-08-14 12:16:32 +07:00
import { createFileRoute, useParams } from "@tanstack/react-router";
import { useQueryData } from "@/hooks/useQueryData";
import { API_ENDPOINTS, BASE_URL } from "@/config/api";
import {
flexRender,
getCoreRowModel,
2025-09-26 17:56:55 +07:00
getPaginationRowModel,
2025-08-14 12:16:32 +07:00
useReactTable,
type ColumnDef,
} from "@tanstack/react-table";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { useDeviceEvents } from "@/hooks/useDeviceEvents";
2025-09-26 17:56:55 +07:00
import {
ChevronLeft,
ChevronRight,
Clock,
Loader2,
MapPin,
Monitor,
Wifi,
WifiOff,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
2025-08-11 23:21:36 +07:00
2025-08-14 12:16:32 +07:00
export const Route = createFileRoute("/_authenticated/room/$roomName/")({
head: ({ params }) => ({
meta: [{ title: `Danh sách thiết bị phòng ${params.roomName}` }],
}),
component: RoomDetailComponent,
});
2025-08-11 23:21:36 +07:00
2025-08-14 12:16:32 +07:00
function RoomDetailComponent() {
const { roomName } = useParams({ from: "/_authenticated/room/$roomName/" });
const { data: devices = [], isLoading } = useQueryData({
queryKey: ["devices", roomName],
url: BASE_URL + API_ENDPOINTS.DEVICE_COMM.GET_DEVICE_FROM_ROOM(roomName),
});
// Lắng nghe SSE và update state
useDeviceEvents(roomName);
2025-08-14 12:16:32 +07:00
const columns: ColumnDef<any>[] = [
{
header: "STT",
2025-09-26 17:56:55 +07:00
cell: ({ row }) => (
<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">{row.index + 1}</span>
</div>
),
2025-08-14 12:16:32 +07:00
},
{
2025-09-26 17:56:55 +07:00
header: () => (
<div className="flex items-center gap-2">
<Clock className="h-4 w-4" />
Thời gian thiết bị
</div>
),
2025-08-14 12:16:32 +07:00
accessorKey: "deviceTime",
cell: ({ getValue }) => {
const date = new Date(getValue() as string);
2025-09-26 17:56:55 +07:00
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>
);
2025-08-14 12:16:32 +07:00
},
},
{
header: "Phiên bản",
accessorKey: "version",
2025-09-26 17:56:55 +07:00
cell: ({ getValue }) => (
<Badge variant="secondary" className="font-mono">
v{getValue() as string}
</Badge>
),
2025-08-14 12:16:32 +07:00
},
{
2025-09-26 17:56:55 +07:00
header: () => (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4" />
2025-10-06 15:52:48 +07:00
Phòng
2025-09-26 17:56:55 +07:00
</div>
),
2025-10-06 15:52:48 +07:00
accessorKey: "room",
2025-09-26 17:56:55 +07:00
cell: ({ getValue }) => (
2025-10-06 15:52:48 +07:00
<span className="text-sm font-medium">{getValue() as string}</span>
2025-09-26 17:56:55 +07:00
),
2025-08-14 12:16:32 +07:00
},
2025-10-06 15:52:48 +07:00
{
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 networkInfos = getValue() as {
macAddress?: string;
ipAddress?: string;
}[];
if (!networkInfos || networkInfos.length === 0) {
return (
<span className="text-muted-foreground text-sm">
Không dữ liệu
</span>
);
}
return (
<div className="flex flex-col gap-1">
{networkInfos.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>
);
},
},
2025-08-14 12:16:32 +07:00
{
header: "Trạng thái",
accessorKey: "isOffline",
2025-09-26 17:56:55 +07:00
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>
);
},
2025-08-14 12:16:32 +07:00
},
];
const table = useReactTable({
data: devices,
columns,
getCoreRowModel: getCoreRowModel(),
2025-09-26 17:56:55 +07:00
getPaginationRowModel: getPaginationRowModel(),
initialState: { pagination: { pageSize: 16 } },
2025-08-14 12:16:32 +07:00
});
2025-09-26 17:56:55 +07:00
if (isLoading) {
return (
<div className="w-full px-6 py-8">
<div className="flex items-center justify-center min-h-[400px]">
<div className="flex flex-col items-center gap-4">
<Loader2 className="h-8 w-8 animate-spin text-primary" />
<p className="text-muted-foreground">Đang tải danh sách phòng...</p>
</div>
</div>
</div>
);
}
const onlineDevices = devices.filter(
(device: any) => !device.isOffline
).length;
const offlineDevices = devices.length - onlineDevices;
2025-08-14 12:16:32 +07:00
return (
2025-09-26 17:56:55 +07:00
<div className="w-full px-6 space-y-6">
2025-08-14 12:16:32 +07:00
<div className="flex items-center justify-between">
2025-09-26 17:56:55 +07:00
<div className="space-y-1">
<h1 className="text-3xl font-bold tracking-tight">
Phòng: {roomName}
</h1>
<p className="text-muted-foreground">
Quản theo dõi thiết bị trong phòng
2025-08-14 12:16:32 +07:00
</p>
</div>
2025-09-26 17:56:55 +07:00
<div className="flex gap-4">
<div className="text-center">
<div className="text-2xl font-bold text-green-600">
{onlineDevices}
</div>
<div className="text-sm text-muted-foreground">Online</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold text-red-600">
{offlineDevices}
</div>
<div className="text-sm text-muted-foreground">Offline</div>
</div>
<div className="text-center">
<div className="text-2xl font-bold">{devices.length}</div>
<div className="text-sm text-muted-foreground">Tổng cộng</div>
</div>
</div>
2025-08-14 12:16:32 +07:00
</div>
2025-09-26 17:56:55 +07:00
<Card className="shadow-sm">
<CardHeader className="bg-muted/50">
<CardTitle className="flex items-center gap-2">
<Monitor className="h-5 w-5" />
Danh sách thiết bị
</CardTitle>
2025-08-14 12:16:32 +07:00
</CardHeader>
2025-09-26 17:56:55 +07:00
<CardContent className="p-0">
{devices.length === 0 ? (
<div className="flex flex-col items-center justify-center py-12">
<Monitor className="h-12 w-12 text-muted-foreground mb-4" />
<h3 className="text-lg font-semibold mb-2">Không thiết bị</h3>
<p className="text-muted-foreground text-center max-w-sm">
Phòng này chưa thiết bị nào đưc kết nối.
</p>
</div>
) : (
<>
<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>
</div>
<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>
</>
)}
2025-08-14 12:16:32 +07:00
</CardContent>
</Card>
</div>
);
2025-08-11 23:21:36 +07:00
}