init
This commit is contained in:
78
frontend/components/weather/refresh-countdown.tsx
Normal file
78
frontend/components/weather/refresh-countdown.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { RefreshCw } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface RefreshCountdownProps {
|
||||
onRefresh: () => void
|
||||
intervalMs?: number
|
||||
className?: string
|
||||
}
|
||||
|
||||
export function RefreshCountdown({
|
||||
onRefresh,
|
||||
intervalMs = 5 * 60 * 1000, // 5 minutes default
|
||||
className,
|
||||
}: RefreshCountdownProps) {
|
||||
const [secondsRemaining, setSecondsRemaining] = useState(intervalMs / 1000)
|
||||
const [isRefreshing, setIsRefreshing] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setSecondsRemaining((prev) => {
|
||||
if (prev <= 1) {
|
||||
// Auto refresh
|
||||
handleRefresh()
|
||||
return intervalMs / 1000
|
||||
}
|
||||
return prev - 1
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [intervalMs])
|
||||
|
||||
const handleRefresh = async () => {
|
||||
setIsRefreshing(true)
|
||||
await onRefresh()
|
||||
setSecondsRemaining(intervalMs / 1000)
|
||||
setIsRefreshing(false)
|
||||
}
|
||||
|
||||
const formatTime = (seconds: number): string => {
|
||||
const mins = Math.floor(seconds / 60)
|
||||
const secs = Math.floor(seconds % 60)
|
||||
return `${mins}:${secs.toString().padStart(2, '0')}`
|
||||
}
|
||||
|
||||
const progressPercentage = ((intervalMs / 1000 - secondsRemaining) / (intervalMs / 1000)) * 100
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-4', className)}>
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<span className="text-sm font-medium">Next refresh</span>
|
||||
<span className="text-sm text-muted-foreground">{formatTime(secondsRemaining)}</span>
|
||||
</div>
|
||||
<div className="h-2 w-full bg-secondary rounded-full overflow-hidden">
|
||||
<div
|
||||
className="h-full bg-primary transition-all duration-1000 ease-linear"
|
||||
style={{ width: `${progressPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleRefresh}
|
||||
disabled={isRefreshing}
|
||||
size="icon"
|
||||
variant="outline"
|
||||
className="min-h-[44px] min-w-[44px]"
|
||||
aria-label="Refresh now"
|
||||
>
|
||||
<RefreshCw className={cn('h-4 w-4', isRefreshing && 'animate-spin')} />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user