Some checks failed
Build Frontend / build (push) Failing after 7s
Replaces imports from 'components/ui' with 'components/common' across the app and dashboard pages, and updates model and API imports to use new paths under 'lib'. Removes redundant authentication checks from several dashboard pages. Adds new dashboard components and utility files, and reorganizes hooks and services into the 'lib' directory for improved structure.
116 lines
3.4 KiB
TypeScript
116 lines
3.4 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipProvider,
|
|
TooltipTrigger,
|
|
} from "@/components/common/tooltip"
|
|
import { format, formatDistanceToNow, isToday, isYesterday, differenceInMinutes } from "date-fns"
|
|
|
|
interface RelativeTimeProps {
|
|
date: Date | string | null | undefined
|
|
className?: string
|
|
showTooltip?: boolean
|
|
updateInterval?: number // ms, for auto-updating recent times
|
|
}
|
|
|
|
/**
|
|
* RelativeTime - Displays time as "2 hours ago" with full date on hover
|
|
* Auto-updates for times less than 1 hour old
|
|
*/
|
|
export function RelativeTime({
|
|
date,
|
|
className = "",
|
|
showTooltip = true,
|
|
updateInterval = 60000 // Update every minute
|
|
}: RelativeTimeProps) {
|
|
const [, forceUpdate] = React.useReducer(x => x + 1, 0)
|
|
|
|
const parsedDate = React.useMemo(() => {
|
|
if (!date) return null
|
|
return typeof date === "string" ? new Date(date) : date
|
|
}, [date])
|
|
|
|
// Auto-update for recent times
|
|
React.useEffect(() => {
|
|
if (!parsedDate) return
|
|
|
|
const minutesAgo = differenceInMinutes(new Date(), parsedDate)
|
|
|
|
// Only auto-update if within the last hour
|
|
if (minutesAgo < 60) {
|
|
const interval = setInterval(forceUpdate, updateInterval)
|
|
return () => clearInterval(interval)
|
|
}
|
|
}, [parsedDate, updateInterval])
|
|
|
|
if (!parsedDate || isNaN(parsedDate.getTime())) {
|
|
return <span className={className}>-</span>
|
|
}
|
|
|
|
const formatRelative = (d: Date): string => {
|
|
const now = new Date()
|
|
const minutesAgo = differenceInMinutes(now, d)
|
|
|
|
// Just now (< 1 minute)
|
|
if (minutesAgo < 1) return "Just now"
|
|
|
|
// Minutes ago (< 60 minutes)
|
|
if (minutesAgo < 60) return `${minutesAgo}m ago`
|
|
|
|
// Hours ago (< 24 hours and today)
|
|
if (isToday(d)) {
|
|
const hoursAgo = Math.floor(minutesAgo / 60)
|
|
return `${hoursAgo}h ago`
|
|
}
|
|
|
|
// Yesterday
|
|
if (isYesterday(d)) return "Yesterday"
|
|
|
|
// Use formatDistanceToNow for older dates
|
|
return formatDistanceToNow(d, { addSuffix: true })
|
|
}
|
|
|
|
const fullDate = format(parsedDate, "dd MMM yyyy, HH:mm")
|
|
const relativeText = formatRelative(parsedDate)
|
|
|
|
if (!showTooltip) {
|
|
return <span className={className}>{relativeText}</span>
|
|
}
|
|
|
|
return (
|
|
<TooltipProvider>
|
|
<Tooltip delayDuration={300}>
|
|
<TooltipTrigger asChild>
|
|
<span className={`cursor-default ${className}`}>{relativeText}</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top" className="text-xs">
|
|
{fullDate}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Utility function to get relative time string without component
|
|
*/
|
|
export function getRelativeTimeString(date: Date | string | null | undefined): string {
|
|
if (!date) return "-"
|
|
const d = typeof date === "string" ? new Date(date) : date
|
|
if (isNaN(d.getTime())) return "-"
|
|
|
|
const now = new Date()
|
|
const minutesAgo = differenceInMinutes(now, d)
|
|
|
|
if (minutesAgo < 1) return "Just now"
|
|
if (minutesAgo < 60) return `${minutesAgo}m ago`
|
|
if (isToday(d)) return `${Math.floor(minutesAgo / 60)}h ago`
|
|
if (isYesterday(d)) return "Yesterday"
|
|
|
|
return formatDistanceToNow(d, { addSuffix: true })
|
|
}
|
|
|