2025-08-14 12:16:32 +07:00
|
|
|
import { createFileRoute, useParams } from "@tanstack/react-router";
|
2025-10-20 16:46:17 +07:00
|
|
|
import { useState } from "react";
|
|
|
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
2025-12-03 18:26:36 +07:00
|
|
|
import { LayoutGrid, TableIcon, Monitor, FolderCheck, Loader2 } from "lucide-react";
|
2025-10-20 16:46:17 +07:00
|
|
|
import { Button } from "@/components/ui/button";
|
2025-12-22 14:53:19 +07:00
|
|
|
import { useGetDeviceFromRoom } from "@/hooks/queries";
|
2025-12-03 18:26:36 +07:00
|
|
|
import { useDeviceEvents } from "@/hooks/useDeviceEvents";
|
|
|
|
|
import { useClientFolderStatus } from "@/hooks/useClientFolderStatus";
|
2025-11-19 14:55:14 +07:00
|
|
|
import { DeviceGrid } from "@/components/grids/device-grid";
|
|
|
|
|
import { DeviceTable } from "@/components/tables/device-table";
|
|
|
|
|
import { useMachineNumber } from "@/hooks/useMachineNumber";
|
2025-12-03 18:26:36 +07:00
|
|
|
import { toast } from "sonner";
|
2026-01-18 22:52:19 +07:00
|
|
|
import { CommandActionButtons } from "@/components/buttons/command-action-buttons";
|
2025-08-11 23:21:36 +07:00
|
|
|
|
2025-12-22 14:53:19 +07:00
|
|
|
export const Route = createFileRoute("/_auth/room/$roomName/")({
|
2025-08-14 12:16:32 +07:00
|
|
|
head: ({ params }) => ({
|
|
|
|
|
meta: [{ title: `Danh sách thiết bị phòng ${params.roomName}` }],
|
|
|
|
|
}),
|
2025-10-20 16:46:17 +07:00
|
|
|
component: RoomDetailPage,
|
2025-08-14 12:16:32 +07:00
|
|
|
});
|
2025-08-11 23:21:36 +07:00
|
|
|
|
2025-10-20 16:46:17 +07:00
|
|
|
function RoomDetailPage() {
|
2025-12-22 14:53:19 +07:00
|
|
|
const { roomName } = useParams({ from: "/_auth/room/$roomName/" });
|
2025-10-31 16:52:56 +07:00
|
|
|
const [viewMode, setViewMode] = useState<"grid" | "table">("grid");
|
2025-12-03 18:26:36 +07:00
|
|
|
const [isCheckingFolder, setIsCheckingFolder] = useState(false);
|
|
|
|
|
|
|
|
|
|
// SSE real-time updates
|
|
|
|
|
useDeviceEvents(roomName);
|
|
|
|
|
|
|
|
|
|
// Folder status from SSE
|
|
|
|
|
const folderStatuses = useClientFolderStatus(roomName);
|
|
|
|
|
|
2025-12-22 14:53:19 +07:00
|
|
|
const { data: devices = [] } = useGetDeviceFromRoom(roomName);
|
2025-08-14 12:16:32 +07:00
|
|
|
|
2025-11-19 14:55:14 +07:00
|
|
|
const parseMachineNumber = useMachineNumber();
|
|
|
|
|
|
2025-12-03 18:26:36 +07:00
|
|
|
const handleCheckFolderStatus = async () => {
|
|
|
|
|
try {
|
|
|
|
|
setIsCheckingFolder(true);
|
2025-12-22 14:53:19 +07:00
|
|
|
// Trigger folder status check via the service
|
2025-12-03 18:26:36 +07:00
|
|
|
const response = await fetch(
|
2025-12-22 14:53:19 +07:00
|
|
|
`/api/device-comm/request-get-client-folder-status?roomName=${encodeURIComponent(roomName)}`,
|
2025-12-03 18:26:36 +07:00
|
|
|
{
|
|
|
|
|
method: "POST",
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error("Failed to request folder status");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toast.success("Đang kiểm tra thư mục Setup...");
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error("Check folder error:", error);
|
|
|
|
|
toast.error("Lỗi khi kiểm tra thư mục!");
|
2025-12-22 14:53:19 +07:00
|
|
|
} finally {
|
2025-12-03 18:26:36 +07:00
|
|
|
setIsCheckingFolder(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2025-11-19 14:55:14 +07:00
|
|
|
const sortedDevices = [...devices].sort((a, b) => {
|
|
|
|
|
return parseMachineNumber(a.id) - parseMachineNumber(b.id);
|
|
|
|
|
});
|
|
|
|
|
|
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">
|
|
|
|
|
<Card className="shadow-sm">
|
2026-01-18 22:52:19 +07:00
|
|
|
<CardHeader className="bg-muted/50 space-y-4">
|
|
|
|
|
{/* Hàng 1: Thông tin phòng và controls */}
|
|
|
|
|
<div className="flex items-center justify-between w-full gap-4">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<Monitor className="h-5 w-5" />
|
|
|
|
|
<CardTitle>Danh sách thiết bị phòng {roomName}</CardTitle>
|
|
|
|
|
</div>
|
2025-12-03 18:26:36 +07:00
|
|
|
|
2026-01-18 22:52:19 +07:00
|
|
|
<div className="flex items-center gap-2 bg-background rounded-lg p-1 border shrink-0">
|
2025-12-03 18:26:36 +07:00
|
|
|
<Button
|
|
|
|
|
variant={viewMode === "grid" ? "default" : "ghost"}
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => setViewMode("grid")}
|
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<LayoutGrid className="h-4 w-4" />
|
|
|
|
|
Sơ đồ
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
variant={viewMode === "table" ? "default" : "ghost"}
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={() => setViewMode("table")}
|
|
|
|
|
className="flex items-center gap-2"
|
|
|
|
|
>
|
|
|
|
|
<TableIcon className="h-4 w-4" />
|
|
|
|
|
Bảng
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
2026-01-18 22:52:19 +07:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Hàng 2: Thực thi lệnh */}
|
|
|
|
|
<div className="flex items-center justify-between w-full gap-4">
|
|
|
|
|
<div className="flex items-center gap-2 text-sm font-semibold">
|
|
|
|
|
Thực thi lệnh
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex items-center gap-3 flex-wrap justify-end">
|
|
|
|
|
{/* Command Action Buttons */}
|
|
|
|
|
{devices.length > 0 && (
|
|
|
|
|
<>
|
|
|
|
|
<CommandActionButtons roomName={roomName} />
|
|
|
|
|
|
|
|
|
|
<div className="h-8 w-px bg-border" />
|
|
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
onClick={handleCheckFolderStatus}
|
|
|
|
|
disabled={isCheckingFolder}
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
className="flex items-center gap-2 shrink-0"
|
|
|
|
|
>
|
|
|
|
|
{isCheckingFolder ? (
|
|
|
|
|
<Loader2 className="h-4 w-4 animate-spin" />
|
|
|
|
|
) : (
|
|
|
|
|
<FolderCheck className="h-4 w-4" />
|
|
|
|
|
)}
|
|
|
|
|
{isCheckingFolder ? "Đang kiểm tra..." : "Kiểm tra thư mục Setup"}
|
|
|
|
|
</Button>
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2025-10-20 16:46:17 +07:00
|
|
|
</div>
|
2025-08-14 12:16:32 +07:00
|
|
|
</CardHeader>
|
2025-10-20 16:46:17 +07:00
|
|
|
|
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 có thiết bị</h3>
|
|
|
|
|
<p className="text-muted-foreground text-center max-w-sm">
|
|
|
|
|
Phòng này chưa có thiết bị nào được kết nối.
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
2025-10-20 16:46:17 +07:00
|
|
|
) : viewMode === "grid" ? (
|
2025-12-03 18:26:36 +07:00
|
|
|
<DeviceGrid
|
|
|
|
|
devices={sortedDevices}
|
|
|
|
|
folderStatuses={folderStatuses}
|
|
|
|
|
isCheckingFolder={isCheckingFolder}
|
|
|
|
|
/>
|
2025-09-26 17:56:55 +07:00
|
|
|
) : (
|
2025-12-03 18:26:36 +07:00
|
|
|
<DeviceTable
|
|
|
|
|
devices={sortedDevices}
|
|
|
|
|
folderStatuses={folderStatuses}
|
|
|
|
|
isCheckingFolder={isCheckingFolder}
|
|
|
|
|
/>
|
2025-09-26 17:56:55 +07:00
|
|
|
)}
|
2025-08-14 12:16:32 +07:00
|
|
|
</CardContent>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2025-08-11 23:21:36 +07:00
|
|
|
}
|