import { useState, useCallback, useEffect } from 'react' import { useReactFlow } from '@xyflow/react' import { Search } from 'lucide-react' import { useCanvasStore } from '@/stores/canvasStore' import { scanApi } from '@/api/client' import type { PendingDevice } from '@/components/modals/PendingDeviceModal' interface SearchModalProps { open: boolean onClose: () => void onOpenPending: (deviceId: string) => void } export function SearchModal({ open, onClose, onOpenPending }: SearchModalProps) { const [query, setQuery] = useState('') const [pendingDevices, setPendingDevices] = useState([]) const nodes = useCanvasStore((s) => s.nodes) const setSelectedNode = useCanvasStore((s) => s.setSelectedNode) const { fitView } = useReactFlow() useEffect(() => { if (!open) return scanApi.pending().then((res) => setPendingDevices(res.data)).catch(() => {}) }, [open]) const searchable = nodes.filter((n) => n.data.type !== 'groupRect') const q = query.toLowerCase() const nodeResults = q.length === 0 ? [] : searchable.filter((n) => n.data.label?.toLowerCase().includes(q) || n.data.ip?.toLowerCase().includes(q) || n.data.hostname?.toLowerCase().includes(q) ).slice(0, 6) const pendingResults = q.length === 0 ? [] : pendingDevices.filter((d) => d.ip?.toLowerCase().includes(q) || d.hostname?.toLowerCase().includes(q) || d.friendly_name?.toLowerCase().includes(q) || d.ieee_address?.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 handleSelectNode = useCallback((nodeId: string) => { setSelectedNode(nodeId) fitView({ nodes: [{ id: nodeId }], duration: 600, padding: 0.4, maxZoom: 1.5 }) onClose() setQuery('') }, [fitView, setSelectedNode, onClose]) const handleSelectPending = useCallback((deviceId: string) => { onOpenPending(deviceId) onClose() setQuery('') }, [onOpenPending, onClose]) if (!open) return null return (
e.stopPropagation()} >
setQuery(e.target.value)} placeholder="Search nodes, pending devices by IP or service…" className="flex-1 bg-transparent text-sm text-foreground placeholder:text-muted-foreground outline-none" onKeyDown={(e) => { if (e.key === 'Escape') { onClose(); setQuery('') } if (e.key === 'Enter' && nodeResults.length > 0) handleSelectNode(nodeResults[0].id) if (e.key === 'Enter' && nodeResults.length === 0 && pendingResults.length > 0) handleSelectPending(pendingResults[0].id) }} /> ESC
{totalResults > 0 && (
    {nodeResults.map((node) => (
  • handleSelectNode(node.id)} > {node.data.type} {node.data.label} {node.data.ip && ( {node.data.ip} )}
  • ))} {pendingResults.length > 0 && nodeResults.length > 0 && (
  • )} {pendingResults.map((device) => { const serviceName = device.services.find((s) => s.service_name)?.service_name return (
  • handleSelectPending(device.id)} > pending {device.hostname ?? device.ip} {serviceName ?? device.ip}
  • ) })}
)} {q.length > 0 && totalResults === 0 && (

No results match "{query}"

)} {q.length === 0 && (

Type to search nodes and pending devices…

)}
) }