@@ -11,7 +11,8 @@ import * as z from 'zod'
1111import HostModal from '../dialogs/host-modal'
1212import SortableHost from './sortable-host'
1313import { Input } from '@/components/ui/input'
14- import { Search , X } from 'lucide-react'
14+ import { Button } from '@/components/ui/button'
15+ import { RefreshCw , Search , X } from 'lucide-react'
1516import useDirDetection from '@/hooks/use-dir-detection'
1617import { cn } from '@/lib/utils'
1718
@@ -439,12 +440,15 @@ export interface HostsListProps {
439440 onSubmit : ( data : HostFormValues ) => Promise < { status : number } >
440441 editingHost : BaseHost | null
441442 setEditingHost : ( host : BaseHost | null ) => void
443+ onRefresh ?: ( ) => Promise < unknown >
444+ isRefreshing ?: boolean
442445}
443446
444- export default function HostsList ( { data, onAddHost, isDialogOpen, onSubmit, editingHost, setEditingHost } : HostsListProps ) {
447+ export default function HostsList ( { data, onAddHost, isDialogOpen, onSubmit, editingHost, setEditingHost, onRefresh , isRefreshing : isRefreshingProp } : HostsListProps ) {
445448 const [ hosts , setHosts ] = useState < BaseHost [ ] | undefined > ( )
446449 const [ isUpdatingPriorities , setIsUpdatingPriorities ] = useState ( false )
447450 const [ searchQuery , setSearchQuery ] = useState ( '' )
451+ const [ isManualRefreshing , setIsManualRefreshing ] = useState ( false )
448452 const { t } = useTranslation ( )
449453 const dir = useDirDetection ( )
450454
@@ -460,13 +464,28 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
460464
461465 const refreshHostsData = ( ) => {
462466 // Just invalidate the main query key used in the dashboard
463- queryClient . invalidateQueries ( {
467+ return queryClient . invalidateQueries ( {
464468 queryKey : [ 'getGetHostsQueryKey' ] ,
465469 exact : true , // Only invalidate this exact query
466470 refetchType : 'active' , // Only refetch if the query is currently being rendered
467471 } )
468472 }
469473
474+ const handleRefreshClick = async ( ) => {
475+ if ( onRefresh ) {
476+ await onRefresh ( )
477+ return
478+ }
479+ setIsManualRefreshing ( true )
480+ try {
481+ await refreshHostsData ( )
482+ } finally {
483+ setIsManualRefreshing ( false )
484+ }
485+ }
486+
487+ const isRefreshing = isRefreshingProp ?? isManualRefreshing
488+
470489 const handleEdit = ( host : BaseHost ) => {
471490 const formData : HostFormValues = {
472491 remark : host . remark || '' ,
@@ -814,7 +833,7 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
814833 return (
815834 < div >
816835 { /* Search Input */ }
817- < div className = "mb-4" >
836+ < div className = "mb-4 flex items-center gap-2 md:gap-3 " >
818837 < div className = "relative w-full md:w-[calc(100%/3-10px)]" dir = { dir } >
819838 < Search className = { cn ( 'absolute' , dir === 'rtl' ? 'right-2' : 'left-2' , 'top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground' ) } />
820839 < Input placeholder = { t ( 'search' ) } value = { searchQuery } onChange = { e => setSearchQuery ( e . target . value ) } className = { cn ( 'pl-8 pr-10' , dir === 'rtl' && 'pl-10 pr-8' ) } />
@@ -824,6 +843,16 @@ export default function HostsList({ data, onAddHost, isDialogOpen, onSubmit, edi
824843 </ button >
825844 ) }
826845 </ div >
846+ < Button
847+ size = "icon-md"
848+ variant = "ghost"
849+ onClick = { handleRefreshClick }
850+ className = { cn ( 'border' , isRefreshing && 'opacity-70' ) }
851+ aria-label = { t ( 'autoRefresh.refreshNow' ) }
852+ title = { t ( 'autoRefresh.refreshNow' ) }
853+ >
854+ < RefreshCw className = { cn ( 'h-4 w-4' , isRefreshing && 'animate-spin' ) } />
855+ </ Button >
827856 </ div >
828857 < div >
829858 < DndContext sensors = { isUpdatingPriorities ? [ ] : sensors } collisionDetection = { closestCenter } onDragEnd = { handleDragEnd } >
0 commit comments