README / client /src /components /StatsPanel.tsx
firoruheqo4's picture
Upload 139 files
926b211 verified
Raw
History Blame Contribute Delete
3.84 kB
/**
* StatsPanel - Enterprise SaaS Style
*
* SINGLE SOURCE OF TRUTH: Only displays stats based on 当前节点 (Current Node)
* 4 Cards: 全部项目 (Gray) | 超时项目 (Red) | 进行中 (Blue) | 已完成 (Green)
*/
import React from 'react';
import { ProjectStats } from '@/types/project';
import { AlertTriangle, Clock, CheckCircle2, FolderOpen } from 'lucide-react';
interface StatsPanelProps {
stats: ProjectStats;
onFilterChange?: (status: string) => void;
activeFilter?: string;
}
export default function StatsPanel({ stats, onFilterChange, activeFilter = 'ALL' }: StatsPanelProps) {
const statCards = [
{
label: '全部项目',
value: stats.total,
icon: FolderOpen,
bgColor: 'from-slate-500/10 to-slate-500/5',
textColor: 'text-slate-700',
borderColor: 'border-slate-200',
filterValue: 'ALL'
},
{
label: '超时项目',
value: stats.highRisk,
icon: AlertTriangle,
bgColor: 'from-red-500/10 to-red-500/5',
textColor: 'text-red-600',
borderColor: 'border-red-200',
filterValue: '已超时'
},
{
label: '进行中',
value: stats.inProgress,
icon: Clock,
bgColor: 'from-blue-500/10 to-blue-500/5',
textColor: 'text-blue-600',
borderColor: 'border-blue-200',
filterValue: '进行中'
},
{
label: '已完成',
value: stats.completed,
icon: CheckCircle2,
bgColor: 'from-emerald-500/10 to-emerald-500/5',
textColor: 'text-emerald-600',
borderColor: 'border-emerald-200',
filterValue: '已完成'
}
];
return (
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-4 gap-8 mb-12">
{statCards.map((stat, index) => {
const Icon = stat.icon;
return (
<button
key={index}
type="button"
className="group relative cursor-pointer text-left touch-manipulation select-none"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onFilterChange?.(stat.filterValue);
}}
>
{/* Gradient background */}
<div className={`absolute inset-0 bg-gradient-to-br ${stat.bgColor} rounded-xl`} />
{/* Main card */}
<div className={`relative backdrop-blur-sm bg-white/60 border-2 ${stat.borderColor} rounded-xl p-8 transition-all duration-300 ease-out hover:shadow-xl hover:-translate-y-1 hover:scale-105 ${
activeFilter === stat.filterValue ? 'ring-4 ring-offset-2 ' + (stat.filterValue === 'ALL' ? 'ring-slate-300' : stat.filterValue === '已超时' ? 'ring-red-300' : stat.filterValue === '进行中' ? 'ring-blue-300' : 'ring-emerald-300') : ''
}`}>
<div className="flex items-start justify-between">
<div className="space-y-3 min-w-0">
<p className={`text-base font-bold ${stat.textColor} tracking-wide whitespace-nowrap`}>
{stat.label}
</p>
<p className={`text-5xl font-bold ${stat.textColor} tabular-nums whitespace-nowrap`}>
{stat.value}
</p>
</div>
{/* Icon */}
<div className="relative">
<div className={`absolute inset-0 ${stat.textColor} opacity-10 rounded-full blur-xl group-hover:opacity-20 transition-opacity duration-300`} />
<div className={`relative p-4 rounded-full bg-gradient-to-br ${stat.bgColor}`}>
<Icon className={`w-8 h-8 ${stat.textColor}`} strokeWidth={2} />
</div>
</div>
</div>
</div>
</button>
);
})}
</div>
);
}