Add modular dashboard widgets and layout editor
Some checks failed
Build Frontend / build (push) Failing after 7s
Some checks failed
Build Frontend / build (push) Failing after 7s
Introduces a modular dashboard system with draggable, configurable widgets including revenue, low stock, recent customers, and pending chats. Adds a dashboard editor for layout customization, widget visibility, and settings. Refactors dashboard content to use the new widget system and improves UI consistency and interactivity.
This commit is contained in:
114
components/ui/relative-time.tsx
Normal file
114
components/ui/relative-time.tsx
Normal file
@@ -0,0 +1,114 @@
|
||||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "@/components/ui/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 })
|
||||
}
|
||||
Reference in New Issue
Block a user