70 lines
1.8 KiB
TypeScript
70 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { cn } from '@/lib/utils';
|
|
import type { ProjectStatus } from '@/types/database';
|
|
|
|
interface ProgressRingProps {
|
|
progress: number;
|
|
status: ProjectStatus;
|
|
size?: number;
|
|
strokeWidth?: number;
|
|
showPercentage?: boolean;
|
|
className?: string;
|
|
}
|
|
|
|
export function ProgressRing({
|
|
progress,
|
|
status,
|
|
size = 64,
|
|
strokeWidth = 4,
|
|
showPercentage = true,
|
|
className,
|
|
}: ProgressRingProps) {
|
|
const radius = (size - strokeWidth) / 2;
|
|
const circumference = radius * 2 * Math.PI;
|
|
const normalizedProgress = Math.min(Math.max(progress, 0), 1);
|
|
const strokeDashoffset = circumference - normalizedProgress * circumference;
|
|
|
|
const strokeColor =
|
|
status === 'green' ? 'stroke-emerald-500' :
|
|
status === 'yellow' ? 'stroke-amber-500' :
|
|
'stroke-rose-500';
|
|
|
|
const textColor =
|
|
status === 'green' ? 'text-emerald-600' :
|
|
status === 'yellow' ? 'text-amber-600' :
|
|
'text-rose-600';
|
|
|
|
return (
|
|
<div className={cn('relative inline-flex items-center justify-center', className)}>
|
|
<svg width={size} height={size} className="-rotate-90">
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
stroke="currentColor"
|
|
strokeWidth={strokeWidth}
|
|
className="text-slate-200"
|
|
/>
|
|
<circle
|
|
cx={size / 2}
|
|
cy={size / 2}
|
|
r={radius}
|
|
fill="none"
|
|
strokeWidth={strokeWidth}
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={strokeDashoffset}
|
|
strokeLinecap="round"
|
|
className={cn('transition-all duration-500', strokeColor)}
|
|
/>
|
|
</svg>
|
|
{showPercentage && (
|
|
<span className={cn('absolute text-sm font-semibold', textColor)}>
|
|
{Math.round(normalizedProgress * 100)}%
|
|
</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|