MOPC-App/src/components/shared/countdown-timer.tsx

106 lines
3.0 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { cn } from '@/lib/utils'
import { Clock, AlertTriangle } from 'lucide-react'
interface CountdownTimerProps {
deadline: Date
label?: string
className?: string
}
interface TimeRemaining {
days: number
hours: number
minutes: number
seconds: number
totalMs: number
}
function getTimeRemaining(deadline: Date): TimeRemaining {
const totalMs = deadline.getTime() - Date.now()
if (totalMs <= 0) {
return { days: 0, hours: 0, minutes: 0, seconds: 0, totalMs: 0 }
}
const seconds = Math.floor((totalMs / 1000) % 60)
const minutes = Math.floor((totalMs / 1000 / 60) % 60)
const hours = Math.floor((totalMs / (1000 * 60 * 60)) % 24)
const days = Math.floor(totalMs / (1000 * 60 * 60 * 24))
return { days, hours, minutes, seconds, totalMs }
}
function formatCountdown(time: TimeRemaining): string {
if (time.totalMs <= 0) return 'Deadline passed'
const { days, hours, minutes, seconds } = time
// Less than 1 hour: show minutes and seconds
if (days === 0 && hours === 0) {
return `${minutes}m ${seconds}s`
}
// Less than 24 hours: show hours and minutes
if (days === 0) {
return `${hours}h ${minutes}m ${seconds}s`
}
// More than 24 hours: show days, hours, minutes
return `${days}d ${hours}h ${minutes}m`
}
type Urgency = 'expired' | 'critical' | 'warning' | 'normal'
function getUrgency(totalMs: number): Urgency {
if (totalMs <= 0) return 'expired'
if (totalMs < 60 * 60 * 1000) return 'critical' // < 1 hour
if (totalMs < 24 * 60 * 60 * 1000) return 'warning' // < 24 hours
return 'normal'
}
const urgencyStyles: Record<Urgency, string> = {
expired: 'text-muted-foreground bg-muted',
critical: 'text-red-700 bg-red-50 border-red-200 dark:text-red-400 dark:bg-red-950/50 dark:border-red-900',
warning: 'text-amber-700 bg-amber-50 border-amber-200 dark:text-amber-400 dark:bg-amber-950/50 dark:border-amber-900',
normal: 'text-green-700 bg-green-50 border-green-200 dark:text-green-400 dark:bg-green-950/50 dark:border-green-900',
}
export function CountdownTimer({ deadline, label, className }: CountdownTimerProps) {
const [time, setTime] = useState<TimeRemaining>(() => getTimeRemaining(deadline))
useEffect(() => {
const timer = setInterval(() => {
const remaining = getTimeRemaining(deadline)
setTime(remaining)
if (remaining.totalMs <= 0) {
clearInterval(timer)
}
}, 1000)
return () => clearInterval(timer)
}, [deadline])
const urgency = getUrgency(time.totalMs)
const displayText = formatCountdown(time)
return (
<div
className={cn(
'inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-medium',
urgencyStyles[urgency],
className
)}
>
{urgency === 'critical' ? (
<AlertTriangle className="h-3 w-3 shrink-0" />
) : (
<Clock className="h-3 w-3 shrink-0" />
)}
{label && <span className="hidden sm:inline">{label}</span>}
<span className="tabular-nums">{displayText}</span>
</div>
)
}