import { useState, useMemo } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Building2, Monitor, ChevronDown, ChevronRight, Loader2, } from "lucide-react"; import type { Room } from "@/types/room"; import type { DeviceHealthCheck } from "@/types/device"; interface DeviceSearchDialogProps { open: boolean; onClose: () => void; rooms: Room[]; onSelect: (deviceIds: string[]) => void | Promise; fetchDevices: (roomName: string) => Promise; // API fetch } export function DeviceSearchDialog({ open, onClose, rooms, onSelect, fetchDevices, }: DeviceSearchDialogProps) { const [selected, setSelected] = useState([]); const [expandedRoom, setExpandedRoom] = useState(null); const [roomDevices, setRoomDevices] = useState< Record >({}); const [loadingRoom, setLoadingRoom] = useState(null); const [searchQuery, setSearchQuery] = useState(""); const sortedRooms = useMemo(() => { return [...rooms].sort((a, b) => { const nameA = typeof a.name === "string" ? a.name : ""; const nameB = typeof b.name === "string" ? b.name : ""; return nameA.localeCompare(nameB); }); }, [rooms]); const filteredRooms = useMemo(() => { if (!searchQuery) return sortedRooms; return sortedRooms.filter((room) => room.name.toLowerCase().includes(searchQuery.toLowerCase()) ); }, [sortedRooms, searchQuery]); const handleRoomClick = async (roomName: string) => { // Nếu đang mở thì đóng lại if (expandedRoom === roomName) { setExpandedRoom(null); return; } // Nếu chưa fetch devices của room này thì gọi API if (!roomDevices[roomName]) { setLoadingRoom(roomName); try { const devices = await fetchDevices(roomName); setRoomDevices((prev) => ({ ...prev, [roomName]: devices })); setExpandedRoom(roomName); } catch (error) { console.error("Failed to fetch devices:", error); // Có thể thêm toast notification ở đây } finally { setLoadingRoom(null); } } else { // Đã có data rồi thì chỉ toggle setExpandedRoom(roomName); } }; const toggleDevice = (deviceId: string) => { setSelected((prev) => prev.includes(deviceId) ? prev.filter((id) => id !== deviceId) : [...prev, deviceId] ); }; const toggleAllInRoom = (roomName: string) => { const devices = roomDevices[roomName] || []; const deviceIds = devices.map((d) => d.id); const allSelected = deviceIds.every((id) => selected.includes(id)); if (allSelected) { setSelected((prev) => prev.filter((id) => !deviceIds.includes(id))); } else { setSelected((prev) => [...new Set([...prev, ...deviceIds])]); } }; const handleConfirm = async () => { try { await onSelect(selected); } catch (e) { console.error("Error on select:", e); } finally { setSelected([]); setExpandedRoom(null); setRoomDevices({}); setSearchQuery(""); onClose(); } }; const handleClose = () => { setSelected([]); setExpandedRoom(null); setRoomDevices({}); setSearchQuery(""); onClose(); }; const parseDeviceId = (id: string) => { const match = /^P(.+?)M(\d+)$/i.exec(id.trim()); if (!match) return null; return { room: match[1].trim(), index: Number(match[2]), }; }; return ( Chọn thiết bị {/* Search bar */} setSearchQuery(e.target.value)} className="my-2" /> {/* Room list */}
{filteredRooms.length === 0 && (

Không tìm thấy phòng

)} {filteredRooms.map((room) => { const isExpanded = expandedRoom === room.name; const isLoading = loadingRoom === room.name; const devices = roomDevices[room.name] || []; const sortedDevices = [...devices].sort((a, b) => { const aId = String(a.id); const bId = String(b.id); const parsedA = parseDeviceId(aId); const parsedB = parseDeviceId(bId); if (parsedA && parsedB) { const roomCompare = parsedA.room.localeCompare(parsedB.room, undefined, { numeric: true, sensitivity: "base", }); if (roomCompare !== 0) return roomCompare; return parsedA.index - parsedB.index; } return aId.localeCompare(bId, undefined, { numeric: true, sensitivity: "base", }); }); const allSelected = sortedDevices.length > 0 && sortedDevices.every((d) => selected.includes(d.id)); const someSelected = sortedDevices.some((d) => selected.includes(d.id)); const selectedCount = sortedDevices.filter((d) => selected.includes(d.id) ).length; return (
{/* Room header - clickable */}
handleRoomClick(room.name)} > {/* Expand icon or loading */} {isLoading ? ( ) : isExpanded ? ( ) : ( )} {/* Select all checkbox - chỉ hiện khi đã load devices */} {devices.length > 0 && ( { toggleAllInRoom(room.name); }} onClick={(e) => e.stopPropagation()} className={ someSelected && !allSelected ? "opacity-50" : "" } /> )} {room.name}
{selectedCount > 0 && ( {selectedCount}/ )} {room.numberOfDevices} {room.numberOfOfflineDevices > 0 && ( {room.numberOfOfflineDevices} )}
{/* Device table - collapsible */} {isExpanded && sortedDevices.length > 0 && (
{sortedDevices.map((device) => ( ))}
Thiết bị IP MAC Ver Trạng thái
toggleDevice(device.id) } />
{device.id}
{device.networkInfos[0]?.ipAddress || "-"} {device.networkInfos[0]?.macAddress || "-"} {device.version ? `v${device.version}` : "-"} {device.isOffline ? ( Offline ) : ( Online )}
)}
); })}
{/* Selected count */} {selected.length > 0 && (
Đã chọn:{" "} {selected.length}
)} {/* Actions */}
); }