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>
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import Link from "next/link";
|
|
|
|
interface BenchResourceCardProps {
|
|
id: string;
|
|
name: string;
|
|
eid: string;
|
|
role: string | null;
|
|
chapter: string | null;
|
|
availableHours: number;
|
|
availableHoursPerDay: number;
|
|
workingDays: number;
|
|
}
|
|
|
|
export function BenchResourceCard({
|
|
id,
|
|
name,
|
|
eid,
|
|
role,
|
|
chapter,
|
|
availableHours,
|
|
availableHoursPerDay,
|
|
}: BenchResourceCardProps) {
|
|
const initials = name
|
|
.split(" ")
|
|
.slice(0, 2)
|
|
.map((w) => w[0]?.toUpperCase() ?? "")
|
|
.join("");
|
|
|
|
const availabilityLevel =
|
|
availableHoursPerDay >= 6 ? "high" : availableHoursPerDay >= 3 ? "medium" : "low";
|
|
|
|
const levelClass =
|
|
availabilityLevel === "high"
|
|
? "border-emerald-300 dark:border-emerald-700 bg-emerald-50 dark:bg-emerald-950/20"
|
|
: availabilityLevel === "medium"
|
|
? "border-amber-300 dark:border-amber-700 bg-amber-50 dark:bg-amber-950/20"
|
|
: "border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900/20";
|
|
|
|
const barColor =
|
|
availabilityLevel === "high"
|
|
? "bg-emerald-500"
|
|
: availabilityLevel === "medium"
|
|
? "bg-amber-500"
|
|
: "bg-gray-400";
|
|
|
|
const barWidth = Math.min(100, Math.round((availableHoursPerDay / 8) * 100));
|
|
|
|
return (
|
|
<div className={`rounded-xl border p-4 space-y-3 ${levelClass}`}>
|
|
<div className="flex items-start gap-3">
|
|
<div className="h-10 w-10 shrink-0 rounded-full bg-brand-100 dark:bg-brand-900/40 flex items-center justify-center">
|
|
<span className="text-sm font-semibold text-brand-700 dark:text-brand-300">
|
|
{initials}
|
|
</span>
|
|
</div>
|
|
<div className="min-w-0 flex-1">
|
|
<div className="font-medium text-sm text-gray-900 dark:text-gray-100 truncate">
|
|
{name}
|
|
</div>
|
|
<div className="text-xs text-gray-500 dark:text-gray-400">{eid}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{(role ?? chapter) && (
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{role && (
|
|
<span className="rounded-full bg-brand-100 dark:bg-brand-900/40 px-2 py-0.5 text-[11px] font-medium text-brand-700 dark:text-brand-300">
|
|
{role}
|
|
</span>
|
|
)}
|
|
{chapter && (
|
|
<span className="rounded-full bg-gray-100 dark:bg-gray-800 px-2 py-0.5 text-[11px] text-gray-600 dark:text-gray-400">
|
|
{chapter}
|
|
</span>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
<div>
|
|
<div className="flex items-center justify-between text-xs mb-1">
|
|
<span className="text-gray-500 dark:text-gray-400">Available capacity</span>
|
|
<span className="font-semibold text-gray-800 dark:text-gray-200">
|
|
{availableHoursPerDay.toFixed(1)}h/day · {availableHours.toFixed(0)}h total
|
|
</span>
|
|
</div>
|
|
<div className="h-1.5 rounded-full bg-gray-200 dark:bg-gray-700 overflow-hidden">
|
|
<div className={`h-full rounded-full ${barColor}`} style={{ width: `${barWidth}%` }} />
|
|
</div>
|
|
</div>
|
|
|
|
<Link
|
|
href={`/resources/${id}`}
|
|
className="block w-full rounded-lg border border-gray-200 dark:border-gray-700 py-1.5 text-center text-xs font-medium text-gray-600 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors"
|
|
>
|
|
View Profile
|
|
</Link>
|
|
</div>
|
|
);
|
|
}
|