"use client"; import { useEffect, useState, useCallback } from "react"; import { Plus, Pencil, Trash2, Search, Eye, ToggleLeft, ToggleRight, RefreshCw } from "lucide-react"; import { Button } from "@/components/ui/button"; import { cn } from "@/lib/utils"; import { fetchSensors, updateSensor, deleteSensor, type SensorDevice, type DeviceType, type Protocol } from "@/lib/api"; const SITE_ID = "sg-01"; export const DEVICE_TYPE_LABELS: Record = { ups: "UPS", generator: "Generator", crac: "CRAC Unit", chiller: "Chiller", ats: "Transfer Switch", rack: "Rack PDU", network_switch: "Network Switch", leak: "Leak Sensor", fire_zone: "Fire / VESDA", custom: "Custom", }; export const PROTOCOL_LABELS: Record = { mqtt: "MQTT", modbus_tcp: "Modbus TCP", modbus_rtu: "Modbus RTU", snmp: "SNMP", bacnet: "BACnet", http: "HTTP Poll", }; const TYPE_COLORS: Record = { ups: "bg-blue-500/10 text-blue-400", generator: "bg-amber-500/10 text-amber-400", crac: "bg-cyan-500/10 text-cyan-400", chiller: "bg-sky-500/10 text-sky-400", ats: "bg-purple-500/10 text-purple-400", rack: "bg-green-500/10 text-green-400", network_switch: "bg-indigo-500/10 text-indigo-400", leak: "bg-teal-500/10 text-teal-400", fire_zone: "bg-red-500/10 text-red-400", custom: "bg-muted text-muted-foreground", }; const PROTOCOL_COLORS: Record = { mqtt: "bg-green-500/10 text-green-400", modbus_tcp: "bg-orange-500/10 text-orange-400", modbus_rtu: "bg-orange-500/10 text-orange-400", snmp: "bg-violet-500/10 text-violet-400", bacnet: "bg-pink-500/10 text-pink-400", http: "bg-yellow-500/10 text-yellow-400", }; interface Props { onAdd: () => void; onEdit: (s: SensorDevice) => void; onDetail: (id: number) => void; } export function SensorTable({ onAdd, onEdit, onDetail }: Props) { const [sensors, setSensors] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const [typeFilter, setTypeFilter] = useState("all"); const [confirmDel, setConfirmDel] = useState(null); const [toggling, setToggling] = useState(null); const load = useCallback(async () => { try { const data = await fetchSensors(SITE_ID); setSensors(data); } catch { /* keep stale */ } finally { setLoading(false); } }, []); useEffect(() => { load(); }, [load]); const handleToggle = async (s: SensorDevice) => { setToggling(s.id); try { const updated = await updateSensor(s.id, { enabled: !s.enabled }); setSensors(prev => prev.map(x => x.id === s.id ? updated : x)); } catch { /* ignore */ } finally { setToggling(null); } }; const handleDelete = async (id: number) => { try { await deleteSensor(id); setSensors(prev => prev.filter(x => x.id !== id)); } catch { /* ignore */ } finally { setConfirmDel(null); } }; const filtered = sensors.filter(s => { const matchType = typeFilter === "all" || s.device_type === typeFilter; const q = search.toLowerCase(); const matchSearch = !q || s.device_id.toLowerCase().includes(q) || s.name.toLowerCase().includes(q) || (s.room_id ?? "").toLowerCase().includes(q); return matchType && matchSearch; }); const typeOptions = [...new Set(sensors.map(s => s.device_type))].sort(); return (
{/* Toolbar */}
setSearch(e.target.value)} placeholder="Search by name or ID..." className="w-full pl-8 pr-3 py-1.5 text-sm bg-muted/30 border border-border rounded-md focus:outline-none focus:ring-1 focus:ring-primary" />
{filtered.length} of {sensors.length} devices
{/* Table */} {loading ? (
{Array.from({ length: 6 }).map((_, i) => (
))}
) : filtered.length === 0 ? (
No sensors found{search ? ` matching "${search}"` : ""}
) : (
{filtered.map(s => ( ))}
Device ID Name Type Room Protocol Enabled Actions
{s.device_id} {s.name} {DEVICE_TYPE_LABELS[s.device_type] ?? s.device_type} {s.room_id ?? "—"} {PROTOCOL_LABELS[s.protocol] ?? s.protocol}
{confirmDel === s.id ? (
) : ( )}
)}
); }