1- import { useState , useEffect } from 'react'
1+ import { useState , useEffect , useMemo } from 'react'
22import { useTranslation } from 'react-i18next'
33import Node from '@/components/nodes/node'
44import { useGetNodes , useModifyNode , NodeResponse , NodeConnectionType } from '@/service/api'
@@ -10,6 +10,10 @@ import { zodResolver } from '@hookform/resolvers/zod'
1010import { nodeFormSchema , NodeFormValues } from '@/components/dialogs/node-modal'
1111import { Card , CardContent } from '@/components/ui/card'
1212import { Skeleton } from '@/components/ui/skeleton'
13+ import { Input } from '@/components/ui/input'
14+ import { Search , X } from 'lucide-react'
15+ import useDirDetection from '@/hooks/use-dir-detection'
16+ import { cn } from '@/lib/utils'
1317
1418const initialDefaultValues : Partial < NodeFormValues > = {
1519 name : '' ,
@@ -25,7 +29,9 @@ export default function NodesList() {
2529 const { t } = useTranslation ( )
2630 const [ isDialogOpen , setIsDialogOpen ] = useState ( false )
2731 const [ editingNode , setEditingNode ] = useState < NodeResponse | null > ( null )
32+ const [ searchQuery , setSearchQuery ] = useState ( '' )
2833 const modifyNodeMutation = useModifyNode ( )
34+ const dir = useDirDetection ( )
2935
3036 const { data : nodesData , isLoading } = useGetNodes ( undefined , {
3137 query : {
@@ -99,9 +105,39 @@ export default function NodesList() {
99105 }
100106 }
101107
108+ const filteredNodes = useMemo ( ( ) => {
109+ if ( ! nodesData || ! searchQuery . trim ( ) ) return nodesData
110+ const query = searchQuery . toLowerCase ( ) . trim ( )
111+ return nodesData . filter (
112+ node =>
113+ node . name ?. toLowerCase ( ) . includes ( query ) ||
114+ node . address ?. toLowerCase ( ) . includes ( query ) ||
115+ node . connection_type ?. toLowerCase ( ) . includes ( query ) ,
116+ )
117+ } , [ nodesData , searchQuery ] )
118+
102119 return (
103120 < div className = "flex w-full flex-col items-start gap-2" >
104121 < div className = "w-full flex-1 space-y-4 pt-6" >
122+ { /* Search Input */ }
123+ < div className = "relative w-full md:w-[calc(100%/3-10px)]" dir = { dir } >
124+ < Search className = { cn ( 'absolute' , dir === 'rtl' ? 'right-2' : 'left-2' , 'top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground' ) } />
125+ < Input
126+ placeholder = { t ( 'search' ) }
127+ value = { searchQuery }
128+ onChange = { e => setSearchQuery ( e . target . value ) }
129+ className = { cn ( 'pl-8 pr-10' , dir === 'rtl' && 'pr-8 pl-10' ) }
130+ />
131+ { searchQuery && (
132+ < button
133+ onClick = { ( ) => setSearchQuery ( '' ) }
134+ className = { cn ( 'absolute' , dir === 'rtl' ? 'left-2' : 'right-2' , 'top-1/2 -translate-y-1/2 text-muted-foreground hover:text-foreground' ) }
135+ >
136+ < X className = "h-4 w-4" />
137+ </ button >
138+ ) }
139+ </ div >
140+
105141 < div
106142 className = "mb-12 grid transform-gpu animate-slide-up grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3"
107143 style = { { animationDuration : '500ms' , animationDelay : '100ms' , animationFillMode : 'both' } }
@@ -122,7 +158,7 @@ export default function NodesList() {
122158 </ div >
123159 </ Card >
124160 ) )
125- : nodesData ?. map ( node => < Node key = { node . id } node = { node } onEdit = { handleEdit } onToggleStatus = { handleToggleStatus } /> ) }
161+ : filteredNodes ?. map ( node => < Node key = { node . id } node = { node } onEdit = { handleEdit } onToggleStatus = { handleToggleStatus } /> ) }
126162 </ div >
127163
128164 { ! isLoading && ( ! nodesData || nodesData . length === 0 ) && (
@@ -142,6 +178,19 @@ export default function NodesList() {
142178 </ Card >
143179 ) }
144180
181+ { ! isLoading && nodesData && nodesData . length > 0 && ( ! filteredNodes || filteredNodes . length === 0 ) && (
182+ < Card className = "mb-12" >
183+ < CardContent className = "p-8 text-center" >
184+ < div className = "space-y-4" >
185+ < h3 className = "text-lg font-semibold" > { t ( 'noResults' ) } </ h3 >
186+ < p className = "mx-auto max-w-2xl text-muted-foreground" >
187+ { t ( 'nodes.noSearchResults' , { defaultValue : 'No nodes match your search criteria. Try adjusting your search terms.' } ) }
188+ </ p >
189+ </ div >
190+ </ CardContent >
191+ </ Card >
192+ ) }
193+
145194 < NodeModal
146195 isDialogOpen = { isDialogOpen }
147196 onOpenChange = { open => {
0 commit comments