Files
MOPC-Portal/src/components/shared/status-tracker.tsx

187 lines
6.1 KiB
TypeScript
Raw Normal View History

'use client'
import { cn } from '@/lib/utils'
import { CheckCircle, Circle, Clock, XCircle, Trophy } from 'lucide-react'
interface TimelineItem {
status: string
label: string
date: Date | string | null
completed: boolean
isTerminal?: boolean
}
interface StatusTrackerProps {
timeline: TimelineItem[]
currentStatus: string
className?: string
}
export function StatusTracker({
timeline,
currentStatus,
className,
}: StatusTrackerProps) {
const formatDate = (date: Date | string | null) => {
if (!date) return null
const d = typeof date === 'string' ? new Date(date) : date
return d.toLocaleDateString('en-US', {
month: 'short',
day: 'numeric',
year: 'numeric',
})
}
return (
<div className={cn('relative', className)}>
<div className="space-y-0">
{timeline.map((item, index) => {
const isCompleted = item.completed
const isCurrent =
isCompleted && !timeline[index + 1]?.completed
const isPending = !isCompleted
const isRejected = item.status === 'REJECTED' && item.isTerminal
const isWinner = item.status === 'WINNER' && isCompleted
return (
<div key={item.status} className="relative flex gap-4">
{/* Vertical line */}
{index < timeline.length - 1 && (
<div
className={cn(
'absolute left-[15px] top-[32px] h-full w-0.5',
isRejected
? 'bg-destructive/30'
: isCompleted
? 'bg-primary'
: 'bg-muted'
)}
/>
)}
{/* Icon */}
<div className="relative z-10 flex h-8 w-8 shrink-0 items-center justify-center">
{isRejected ? (
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-destructive text-destructive-foreground">
<XCircle className="h-4 w-4" />
</div>
) : isWinner ? (
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-yellow-500 text-white">
<Trophy className="h-4 w-4" />
</div>
) : isCompleted ? (
<div
className={cn(
'flex h-8 w-8 items-center justify-center rounded-full',
isCurrent
? 'bg-primary text-primary-foreground'
: 'bg-primary/20 text-primary'
)}
>
{isCurrent ? (
<Clock className="h-4 w-4" />
) : (
<CheckCircle className="h-4 w-4" />
)}
</div>
) : (
<div className="flex h-8 w-8 items-center justify-center rounded-full border-2 border-muted bg-background">
<Circle className="h-4 w-4 text-muted-foreground" />
</div>
)}
</div>
{/* Content */}
<div className="flex-1 pb-8">
<div className="flex items-center gap-2">
<p
className={cn(
'font-medium',
isRejected && 'text-destructive',
isWinner && 'text-yellow-600',
isPending && !isRejected && 'text-muted-foreground'
)}
>
{item.label}
</p>
{isCurrent && !isRejected && !isWinner && (
<span className="text-xs bg-primary/10 text-primary px-2 py-0.5 rounded-full">
Current
</span>
)}
{isRejected && (
<span className="text-xs bg-destructive/10 text-destructive px-2 py-0.5 rounded-full">
Final
</span>
)}
{isWinner && (
<span className="text-xs bg-yellow-100 text-yellow-700 px-2 py-0.5 rounded-full">
Winner
</span>
)}
</div>
{item.date && (
<p className="text-sm text-muted-foreground">
{formatDate(item.date)}
</p>
)}
{isPending && !isCurrent && !isRejected && (
<p className="text-sm text-muted-foreground">Pending</p>
)}
</div>
</div>
)
})}
</div>
</div>
)
}
// Compact horizontal version
interface StatusBarProps {
status: string
statuses: { value: string; label: string }[]
className?: string
}
export function StatusBar({ status, statuses, className }: StatusBarProps) {
const currentIndex = statuses.findIndex((s) => s.value === status)
return (
<div className={cn('flex items-center gap-2', className)}>
{statuses.map((s, index) => {
const isCompleted = index <= currentIndex
const isCurrent = index === currentIndex
return (
<div key={s.value} className="flex items-center gap-2">
{index > 0 && (
<div
className={cn(
'h-0.5 w-8',
isCompleted ? 'bg-primary' : 'bg-muted'
)}
/>
)}
<div
className={cn(
'flex items-center gap-1 px-2 py-1 rounded-full text-xs font-medium',
isCurrent
? 'bg-primary text-primary-foreground'
: isCompleted
? 'bg-primary/20 text-primary'
: 'bg-muted text-muted-foreground'
)}
>
{isCompleted && !isCurrent && (
<CheckCircle className="h-3 w-3" />
)}
{s.label}
</div>
</div>
)
})}
</div>
)
}