8f7c69056f
BenchResourceCard, MobileProjectCard, MobileCapacityCard, DynamicFieldRenderer, BudgetStatusBar, and TimelineHeader use no hooks, event handlers, or browser APIs — they can be server components, reducing client bundle size. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
80 lines
2.5 KiB
TypeScript
80 lines
2.5 KiB
TypeScript
interface MobileCapacityCardProps {
|
|
totalResources: number;
|
|
activeResources: number;
|
|
avgUtilizationPct: number;
|
|
overbookedCount?: number;
|
|
}
|
|
|
|
export function MobileCapacityCard({
|
|
totalResources,
|
|
activeResources,
|
|
avgUtilizationPct,
|
|
overbookedCount = 0,
|
|
}: MobileCapacityCardProps) {
|
|
const pct = Math.min(100, Math.max(0, avgUtilizationPct));
|
|
const circumference = 2 * Math.PI * 34; // radius = 34
|
|
const dashOffset = circumference * (1 - pct / 100);
|
|
const color = pct >= 90 ? "#d97706" : pct >= 70 ? "#059669" : "#6b7280";
|
|
|
|
return (
|
|
<div className="rounded-2xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 p-5">
|
|
<div className="text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-4">
|
|
Team Capacity
|
|
</div>
|
|
<div className="flex items-center gap-5">
|
|
{/* CSS-only donut */}
|
|
<svg width="80" height="80" viewBox="0 0 80 80" className="shrink-0">
|
|
<circle
|
|
cx="40"
|
|
cy="40"
|
|
r="34"
|
|
fill="none"
|
|
stroke="#e5e7eb"
|
|
strokeWidth="8"
|
|
className="dark:stroke-gray-700"
|
|
/>
|
|
<circle
|
|
cx="40"
|
|
cy="40"
|
|
r="34"
|
|
fill="none"
|
|
stroke={color}
|
|
strokeWidth="8"
|
|
strokeDasharray={circumference}
|
|
strokeDashoffset={dashOffset}
|
|
strokeLinecap="round"
|
|
transform="rotate(-90 40 40)"
|
|
/>
|
|
<text
|
|
x="40"
|
|
y="40"
|
|
textAnchor="middle"
|
|
dominantBaseline="middle"
|
|
fontSize="15"
|
|
fontWeight="700"
|
|
fill={color}
|
|
>
|
|
{Math.round(pct)}%
|
|
</text>
|
|
</svg>
|
|
<div className="space-y-2 flex-1">
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-gray-500 dark:text-gray-400">Resources</span>
|
|
<span className="font-semibold text-gray-900 dark:text-gray-100">
|
|
{activeResources} / {totalResources}
|
|
</span>
|
|
</div>
|
|
{overbookedCount > 0 && (
|
|
<div className="flex items-center justify-between text-sm">
|
|
<span className="text-amber-600 dark:text-amber-400">Overbooked</span>
|
|
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
|
{overbookedCount}
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|