Files
Nexus/apps/web/src/components/mobile/MobileCapacityCard.tsx
T
Hartmut f3cb75bfc7 feat(mobile): add mobile summary view for 320-428px viewports (Sprint 4c)
Read-only capacity snapshot with utilization donut, top 5 active projects,
open demand alert banner, and quick-link grid — single-column card layout
optimised for PWA standalone mode.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 13:37:08 +02:00

65 lines
2.3 KiB
TypeScript

"use client";
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>
);
}