Download client/src/components/StatsPanel.tsx from zuocong12/README: direct link, hf CLI and curl.
- Browser
- Download file 3.84 kB
-
https://huggingface.co/spaces/zuocong12/README/resolve/main/client/src/components/StatsPanel.tsx
- Command line
-
hf download hf://spaces/zuocong12/README/client/src/components/StatsPanel.tsx
-
curl -L -o StatsPanel.tsx https://huggingface.co/spaces/zuocong12/README/resolve/main/client/src/components/StatsPanel.tsx
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> | |
| ); | |
| } | |