import { useState, useEffect, useRef } from 'react' import { useReactFlow } from '@xyflow/react' import { Search, X } from 'lucide-react' import { useCanvasStore } from '@/stores/canvasStore' import { scanApi } from '@/api/client' import { NODE_TYPE_LABELS } from '@/types' import type { PendingDevice } from '@/components/modals/PendingDeviceModal' interface SearchBarProps { onOpenPending?: (deviceId: string) => void } export function SearchBar({ onOpenPending }: SearchBarProps) { const [open, setOpen] = useState(false) const [query, setQuery] = useState('') const [pendingDevices, setPendingDevices] = useState([]) const inputRef = useRef(null) const { nodes, setSelectedNode } = useCanvasStore() const { setCenter } = useReactFlow() useEffect(() => { if (!open) return scanApi.pending().then((res) => setPendingDevices(res.data)).catch(() => {}) }, [open]) useEffect(() => { const handler = (e: KeyboardEvent) => { if ((e.ctrlKey || e.metaKey) && e.key === 'f') { e.preventDefault() setOpen(true) } if (e.key === 'Escape') { setOpen(false) setQuery('') } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, []) useEffect(() => { if (open) inputRef.current?.focus() }, [open]) const q = query.toLowerCase().trim() const nodeResults = q ? nodes.filter((n) => { if (n.data.type === 'groupRect') return false return ( n.data.label?.toLowerCase().includes(q) || n.data.ip?.toLowerCase().includes(q) || n.data.hostname?.toLowerCase().includes(q) || (n.data.services ?? []).some((s) => s.service_name?.toLowerCase().includes(q)) ) }) : [] const pendingResults = q ? pendingDevices.filter((d) => d.ip.toLowerCase().includes(q) || d.hostname?.toLowerCase().includes(q) || d.services.some((s) => s.service_name?.toLowerCase().includes(q) || s.category?.toLowerCase().includes(q) ) ).slice(0, 4) : [] const totalResults = nodeResults.length + pendingResults.length const goToNode = (id: string) => { const node = nodes.find((n) => n.id === id) if (!node) return setSelectedNode(id) // For grouped nodes, add parent's absolute position let absX = node.position.x let absY = node.position.y if (node.parentId) { const parent = nodes.find((n) => n.id === node.parentId) if (parent) { absX += parent.position.x; absY += parent.position.y } } const w = node.measured?.width ?? node.width ?? 200 const h = node.measured?.height ?? node.height ?? 80 setCenter(absX + w / 2, absY + h / 2, { zoom: 1.5, duration: 500 }) setOpen(false) setQuery('') } if (!open) return null return (
setQuery(e.target.value)} placeholder="Search by name, IP, hostname or service…" style={{ flex: 1, background: 'transparent', border: 'none', outline: 'none', color: '#e6edf3', fontSize: 13, }} /> {query && ( {totalResults} result{totalResults !== 1 ? 's' : ''} )}
{totalResults > 0 && (
{nodeResults.map((n) => ( ))} {pendingResults.length > 0 && nodeResults.length > 0 && (
)} {pendingResults.map((d) => { const serviceName = d.services.find((s) => s.service_name)?.service_name return ( ) })}
)} {q && totalResults === 0 && (
No results for “{query}”
)}
) }