diff --git a/package-lock.json b/package-lock.json index fc46afc..a6257f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@tanstack/react-query": "^5.83.0", "@tanstack/react-router": "^1.121.2", "@tanstack/react-router-devtools": "^1.121.2", + "@tanstack/react-table": "^8.21.3", "@tanstack/router-plugin": "^1.121.2", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", @@ -2496,6 +2497,25 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz", + "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==", + "dependencies": { + "@tanstack/table-core": "8.21.3" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/@tanstack/router-core": { "version": "1.129.8", "resolved": "https://registry.npmjs.org/@tanstack/router-core/-/router-core-1.129.8.tgz", @@ -2648,6 +2668,18 @@ "url": "https://github.com/sponsors/tannerlinsley" } }, + "node_modules/@tanstack/table-core": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz", + "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tanstack/virtual-file-routes": { "version": "1.129.7", "resolved": "https://registry.npmjs.org/@tanstack/virtual-file-routes/-/virtual-file-routes-1.129.7.tgz", diff --git a/package.json b/package.json index 3b9c9fa..3515800 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@tanstack/react-query": "^5.83.0", "@tanstack/react-router": "^1.121.2", "@tanstack/react-router-devtools": "^1.121.2", + "@tanstack/react-table": "^8.21.3", "@tanstack/router-plugin": "^1.121.2", "axios": "^1.11.0", "class-variance-authority": "^0.7.1", diff --git a/src/config/api.ts b/src/config/api.ts index 4b96e77..a3a9ba5 100644 --- a/src/config/api.ts +++ b/src/config/api.ts @@ -28,4 +28,8 @@ export const API_ENDPOINTS = { GET_ROOM_LIST: `/DeviceComm/rooms`, GET_DEVICE_FROM_ROOM: (roomName: string) => `/DeviceComm/room/${roomName}`, }, + SSE_EVENTS: { + DEVICE_ONLINE: `/Sse/events/onlineDevices`, + DEVICE_OFFLINE: `/Sse/events/offlineDevices`, + }, }; diff --git a/src/hooks/useDeviceEvents.ts b/src/hooks/useDeviceEvents.ts new file mode 100644 index 0000000..a0cdcfd --- /dev/null +++ b/src/hooks/useDeviceEvents.ts @@ -0,0 +1,55 @@ +import { useEffect } from "react"; +import { BASE_URL, API_ENDPOINTS } from "@/config/api"; +interface DeviceEventData { + Message: string; + DeviceId: string; + Room: string; + Timestamp?: string; +} + +interface UseDeviceEventsOptions { + onRoomDeviceOnline?: (room: string, deviceId: string) => void; + onRoomDeviceOffline?: (room: string, deviceId: string) => void; + onDeviceOnlineInRoom?: (deviceId: string, room: string) => void; + onDeviceOfflineInRoom?: (deviceId: string, room: string) => void; +} + +export function useDeviceEvents(options: UseDeviceEventsOptions) { + useEffect(() => { + const onlineES = new EventSource(`${BASE_URL}${API_ENDPOINTS.SSE_EVENTS.DEVICE_ONLINE}`); + const offlineES = new EventSource(`${BASE_URL}${API_ENDPOINTS.SSE_EVENTS.DEVICE_OFFLINE}`); + + onlineES.addEventListener("online", (event) => { + try { + const data: DeviceEventData = JSON.parse(event.data); + options.onRoomDeviceOnline?.(data.Room, data.DeviceId); + options.onDeviceOnlineInRoom?.(data.DeviceId, data.Room); + } catch (err) { + console.error("Error parsing online event:", err); + } + }); + + offlineES.addEventListener("offline", (event) => { + try { + const data: DeviceEventData = JSON.parse(event.data); + options.onRoomDeviceOffline?.(data.Room, data.DeviceId); + options.onDeviceOfflineInRoom?.(data.DeviceId, data.Room); + } catch (err) { + console.error("Error parsing offline event:", err); + } + }); + + onlineES.onerror = (err) => { + console.error("Online SSE connection error:", err); + }; + + offlineES.onerror = (err) => { + console.error("Offline SSE connection error:", err); + }; + + return () => { + onlineES.close(); + offlineES.close(); + }; + }, [options]); +} diff --git a/src/routes/_authenticated/room/$roomName/index.tsx b/src/routes/_authenticated/room/$roomName/index.tsx index 653334b..8a7361d 100644 --- a/src/routes/_authenticated/room/$roomName/index.tsx +++ b/src/routes/_authenticated/room/$roomName/index.tsx @@ -1,9 +1,158 @@ -import { createFileRoute } from '@tanstack/react-router' +import { createFileRoute, useParams } from "@tanstack/react-router"; +import { useQueryData } from "@/hooks/useQueryData"; +import { API_ENDPOINTS, BASE_URL } from "@/config/api"; +import { + flexRender, + getCoreRowModel, + 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 { useQueryClient } from "@tanstack/react-query"; +import { useDeviceEvents } from "@/hooks/useDeviceEvents"; -export const Route = createFileRoute('/_authenticated/room/$roomName/')({ - component: RouteComponent, -}) +export const Route = createFileRoute("/_authenticated/room/$roomName/")({ + head: ({ params }) => ({ + meta: [{ title: `Danh sách thiết bị phòng ${params.roomName}` }], + }), + component: RoomDetailComponent, +}); -function RouteComponent() { - return
+ Danh sách thiết bị trong phòng +
++ Danh sách các phòng hiện có trong hệ thống +
+