67 lines
2.9 KiB
TypeScript
67 lines
2.9 KiB
TypeScript
import { Badge } from '@/Components/ui/badge';
|
|
import { CheckCircle2, Circle, XCircle, SkipForward } from 'lucide-react';
|
|
|
|
interface Step {
|
|
id: number; ulid: string;
|
|
order: number;
|
|
status: string;
|
|
notes?: string;
|
|
acted_at?: string;
|
|
approver: { id: number; ulid: string; name: string };
|
|
}
|
|
|
|
interface Props {
|
|
steps: Step[];
|
|
}
|
|
|
|
const statusConfig: Record<string, { icon: React.ComponentType<{ className?: string }>; color: string; bgColor: string }> = {
|
|
pending: { icon: Circle, color: 'text-gray-400', bgColor: 'bg-gray-100' },
|
|
approved: { icon: CheckCircle2, color: 'text-green-600', bgColor: 'bg-green-50' },
|
|
rejected: { icon: XCircle, color: 'text-red-600', bgColor: 'bg-red-50' },
|
|
skipped: { icon: SkipForward, color: 'text-gray-400', bgColor: 'bg-gray-50' },
|
|
};
|
|
|
|
export default function ApprovalTimeline({ steps }: Props) {
|
|
return (
|
|
<div className="space-y-0">
|
|
{steps.map((step, idx) => {
|
|
const config = statusConfig[step.status] || statusConfig.pending;
|
|
const Icon = config.icon;
|
|
const isLast = idx === steps.length - 1;
|
|
|
|
return (
|
|
<div key={step.id} className="flex gap-3">
|
|
{/* Timeline line + icon */}
|
|
<div className="flex flex-col items-center">
|
|
<div className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${config.bgColor}`}>
|
|
<Icon className={`h-4 w-4 ${config.color}`} />
|
|
</div>
|
|
{!isLast && (
|
|
<div className="w-px flex-1 bg-gray-200 min-h-[24px]" />
|
|
)}
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="pb-4">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-sm font-medium">{step.approver.name}</span>
|
|
<Badge variant={step.status === 'approved' ? 'default' : step.status === 'rejected' ? 'destructive' : 'outline'} className="text-xs">
|
|
{step.status.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())}
|
|
</Badge>
|
|
</div>
|
|
{step.notes && (
|
|
<p className="mt-1 text-xs text-gray-500 italic">"{step.notes}"</p>
|
|
)}
|
|
{step.acted_at && (
|
|
<p className="mt-0.5 text-xs text-gray-400">
|
|
{new Date(step.acted_at).toLocaleString()}
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|