refactor(web): remove unnecessary "use client" from 6 pure-render components
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>
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
"use client";
|
||||
|
||||
interface MobileCapacityCardProps {
|
||||
totalResources: number;
|
||||
activeResources: number;
|
||||
@@ -16,8 +14,7 @@ export function MobileCapacityCard({
|
||||
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";
|
||||
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">
|
||||
@@ -27,7 +24,15 @@ export function MobileCapacityCard({
|
||||
<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="#e5e7eb"
|
||||
strokeWidth="8"
|
||||
className="dark:stroke-gray-700"
|
||||
/>
|
||||
<circle
|
||||
cx="40"
|
||||
cy="40"
|
||||
@@ -40,7 +45,15 @@ export function MobileCapacityCard({
|
||||
strokeLinecap="round"
|
||||
transform="rotate(-90 40 40)"
|
||||
/>
|
||||
<text x="40" y="40" textAnchor="middle" dominantBaseline="middle" fontSize="15" fontWeight="700" fill={color}>
|
||||
<text
|
||||
x="40"
|
||||
y="40"
|
||||
textAnchor="middle"
|
||||
dominantBaseline="middle"
|
||||
fontSize="15"
|
||||
fontWeight="700"
|
||||
fill={color}
|
||||
>
|
||||
{Math.round(pct)}%
|
||||
</text>
|
||||
</svg>
|
||||
@@ -54,7 +67,9 @@ export function MobileCapacityCard({
|
||||
{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>
|
||||
<span className="font-semibold text-amber-600 dark:text-amber-400">
|
||||
{overbookedCount}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
const STATUS_BADGE: Record<string, string> = {
|
||||
ACTIVE: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300",
|
||||
DRAFT: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400",
|
||||
ON_HOLD: "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300",
|
||||
ACTIVE: "bg-emerald-100 text-emerald-800 dark:bg-emerald-900/40 dark:text-emerald-300",
|
||||
DRAFT: "bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-400",
|
||||
ON_HOLD: "bg-amber-100 text-amber-800 dark:bg-amber-900/40 dark:text-amber-300",
|
||||
COMPLETED: "bg-blue-100 text-blue-800 dark:bg-blue-900/40 dark:text-blue-300",
|
||||
CANCELLED: "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300",
|
||||
};
|
||||
@@ -18,20 +16,32 @@ interface MobileProjectCardProps {
|
||||
allocationsCount?: number;
|
||||
}
|
||||
|
||||
export function MobileProjectCard({ id, shortCode, name, status, allocationsCount }: MobileProjectCardProps) {
|
||||
export function MobileProjectCard({
|
||||
id,
|
||||
shortCode,
|
||||
name,
|
||||
status,
|
||||
allocationsCount,
|
||||
}: MobileProjectCardProps) {
|
||||
return (
|
||||
<Link
|
||||
href={`/projects/${id}`}
|
||||
className="flex items-center gap-3 rounded-xl border border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900 px-4 py-3 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
||||
>
|
||||
<div className="font-mono text-xs text-gray-500 dark:text-gray-400 w-16 shrink-0">{shortCode}</div>
|
||||
<div className="font-mono text-xs text-gray-500 dark:text-gray-400 w-16 shrink-0">
|
||||
{shortCode}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="text-sm font-medium text-gray-900 dark:text-gray-100 truncate">{name}</div>
|
||||
{allocationsCount !== undefined && (
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">{allocationsCount} allocation{allocationsCount !== 1 ? "s" : ""}</div>
|
||||
<div className="text-xs text-gray-500 dark:text-gray-400">
|
||||
{allocationsCount} allocation{allocationsCount !== 1 ? "s" : ""}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<span className={`shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium ${STATUS_BADGE[status] ?? STATUS_BADGE["DRAFT"]}`}>
|
||||
<span
|
||||
className={`shrink-0 rounded-full px-2 py-0.5 text-[11px] font-medium ${STATUS_BADGE[status] ?? STATUS_BADGE["DRAFT"]}`}
|
||||
>
|
||||
{status.charAt(0) + status.slice(1).toLowerCase().replace("_", " ")}
|
||||
</span>
|
||||
</Link>
|
||||
|
||||
Reference in New Issue
Block a user