import React, { useState, useMemo } from 'react' import { useQuery } from '@tanstack/react-query' import { Wifi } from 'lucide-react' import { wirelessApi, type LinkResponse } from '@/lib/api' import { cn } from '@/lib/utils' import { DeviceLink } from '@/components/ui/device-link' import { TableSkeleton } from '@/components/ui/page-skeleton' import { EmptyState } from '@/components/ui/empty-state' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { signalColor } from './signal-color' import { SignalHistoryChart } from './SignalHistoryChart' function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime() const mins = Math.floor(diff / 60000) if (mins < 1) return 'just now' if (mins < 60) return `${mins}m ago` const hours = Math.floor(mins / 60) if (hours < 24) return `${hours}h ago` const days = Math.floor(hours / 24) return `${days}d ago` } const STATE_STYLES: Record = { active: 'bg-success/20 text-success border-success/40', degraded: 'bg-warning/20 text-warning border-warning/40', down: 'bg-error/20 text-error border-error/40', stale: 'bg-elevated text-text-muted border-border', discovered: 'bg-info/20 text-info border-info/40', } function StateBadge({ state }: { state: string }) { return ( {state} ) } interface WirelessLinksTableProps { tenantId: string siteId?: string stateFilter?: string showUnknownClients?: boolean } export function WirelessLinksTable({ tenantId, siteId }: WirelessLinksTableProps) { const [filter, setFilter] = useState('all') const { data, isLoading } = useQuery({ queryKey: ['wireless-links', tenantId, siteId, filter], queryFn: () => { if (siteId) { return wirelessApi.getSiteLinks(tenantId, siteId) } const params = filter !== 'all' ? { state: filter } : undefined return wirelessApi.getLinks(tenantId, params) }, }) // Group links by AP device const grouped = useMemo(() => { if (!data?.items) return new Map() const map = new Map() for (const link of data.items) { const key = link.ap_device_id if (!map.has(key)) { map.set(key, { apHostname: link.ap_hostname ?? link.ap_device_id, apDeviceId: link.ap_device_id, links: [], }) } map.get(key)!.links.push(link) } return map }, [data]) if (isLoading) { return } if (!data || data.items.length === 0) { return ( ) } return (
{/* Filter */}
State: {data.items.length} link{data.items.length !== 1 ? 's' : ''}
{/* Links grouped by AP */}
{[...grouped.values()].map((group) => ( ))}
CPE Signal CCQ TX Rate RX Rate State Last Seen
) } function APGroup({ tenantId, apHostname, apDeviceId, links, }: { tenantId: string apHostname: string apDeviceId: string links: LinkResponse[] }) { const [expandedLinkId, setExpandedLinkId] = useState(null) return ( <> {/* AP header row */}
{apHostname} ({links.length} client{links.length !== 1 ? 's' : ''})
{/* CPE rows */} {links.map((link) => ( setExpandedLinkId(expandedLinkId === link.id ? null : link.id)} > {link.cpe_hostname ?? link.client_mac} {link.signal_strength != null ? `${link.signal_strength} dBm` : '--'} {link.tx_ccq != null ? `${link.tx_ccq}%` : '--'} {link.tx_rate ?? '--'} {link.rx_rate ?? '--'} {timeAgo(link.last_seen)} {expandedLinkId === link.id && ( )} ))} ) }