feat: modernize admin dashboard with premium sports design
This commit is contained in:
parent
fda66a7703
commit
e84d0f5f8f
@ -32,7 +32,7 @@
|
|||||||
--input: 214.3 31.8% 91.4%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 222.2 84% 4.9%;
|
--ring: 222.2 84% 4.9%;
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
|||||||
@ -25,11 +25,34 @@ export default function RootLayout({
|
|||||||
return (
|
return (
|
||||||
<ClerkProvider>
|
<ClerkProvider>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body className={inter.className}>
|
<body className={`${inter.className} bg-gradient-to-br from-slate-50 via-blue-50 to-slate-100 min-h-screen`}>
|
||||||
<div className="flex min-h-screen bg-slate-50">
|
<div className="flex min-h-screen">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
<main className="flex-1 ml-64 p-8">
|
<main className="flex-1 ml-64">
|
||||||
{children}
|
<div className="sticky top-0 z-40 backdrop-blur-xl bg-white/75 border-b border-slate-200/50 shadow-sm">
|
||||||
|
<div className="px-8 py-4 flex items-center justify-between max-w-7xl mx-auto">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-sm font-semibold text-gray-600 uppercase tracking-wide">FitAI Pro</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<SignedIn>
|
||||||
|
<UserButton
|
||||||
|
appearance={{
|
||||||
|
elements: {
|
||||||
|
avatarBox: "w-10 h-10 rounded-full ring-2 ring-blue-200"
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</SignedIn>
|
||||||
|
<SignedOut>
|
||||||
|
<SignInButton mode="modal" />
|
||||||
|
</SignedOut>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="p-8">
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@ -46,56 +46,78 @@ export default function Home() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-slate-100">
|
||||||
<div>
|
<div className="space-y-8 max-w-7xl">
|
||||||
<h2 className="text-3xl font-bold text-slate-900">Dashboard</h2>
|
{/* Hero Section */}
|
||||||
<p className="text-slate-500 mt-2">Welcome back, here's what's happening today.</p>
|
<div className="pt-8 pb-4">
|
||||||
</div>
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="h-12 w-1 bg-gradient-to-b from-blue-600 to-cyan-600 rounded-full"></div>
|
||||||
<StatsCard
|
<h1 className="text-5xl font-black bg-gradient-to-r from-blue-600 via-blue-700 to-cyan-600 bg-clip-text text-transparent">
|
||||||
title="Total Users"
|
FitAI Dashboard
|
||||||
value={loading ? "..." : stats.totalUsers}
|
</h1>
|
||||||
change="+12%" // Placeholder for now as we don't track historical growth yet
|
</div>
|
||||||
trend="up"
|
<p className="text-lg text-gray-600 ml-4">Performance metrics & athlete insights</p>
|
||||||
icon={Users}
|
</div>
|
||||||
color="blue"
|
|
||||||
/>
|
|
||||||
<StatsCard
|
|
||||||
title="Active Clients"
|
|
||||||
value={loading ? "..." : stats.activeClients}
|
|
||||||
change="+5%"
|
|
||||||
trend="up"
|
|
||||||
icon={CalendarCheck}
|
|
||||||
color="green"
|
|
||||||
/>
|
|
||||||
<StatsCard
|
|
||||||
title="Revenue"
|
|
||||||
value={loading ? "..." : formatCurrency(stats.totalRevenue)}
|
|
||||||
change={`${stats.revenueGrowth > 0 ? "+" : ""}${stats.revenueGrowth}%`}
|
|
||||||
trend={stats.revenueGrowth >= 0 ? "up" : "down"}
|
|
||||||
icon={CreditCard}
|
|
||||||
color="purple"
|
|
||||||
/>
|
|
||||||
<StatsCard
|
|
||||||
title="Growth"
|
|
||||||
value="24%" // Placeholder
|
|
||||||
change="-2%"
|
|
||||||
trend="down"
|
|
||||||
icon={TrendingUp}
|
|
||||||
color="orange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
||||||
<div className="lg:col-span-2 bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
|
||||||
<h3 className="text-xl font-bold text-slate-900 mb-6">Recent Activity</h3>
|
|
||||||
<UserManagement />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-6">
|
{/* Stats Grid */}
|
||||||
<h3 className="text-xl font-bold text-slate-900 mb-6">Quick Analytics</h3>
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-5">
|
||||||
<AnalyticsDashboard />
|
<StatsCard
|
||||||
|
title="Total Users"
|
||||||
|
value={loading ? "..." : stats.totalUsers}
|
||||||
|
change="+12%" // Placeholder for now as we don't track historical growth yet
|
||||||
|
trend="up"
|
||||||
|
icon={Users}
|
||||||
|
color="blue"
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Active Clients"
|
||||||
|
value={loading ? "..." : stats.activeClients}
|
||||||
|
change="+5%"
|
||||||
|
trend="up"
|
||||||
|
icon={CalendarCheck}
|
||||||
|
color="green"
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Revenue"
|
||||||
|
value={loading ? "..." : formatCurrency(stats.totalRevenue)}
|
||||||
|
change={`${stats.revenueGrowth > 0 ? "+" : ""}${stats.revenueGrowth}%`}
|
||||||
|
trend={stats.revenueGrowth >= 0 ? "up" : "down"}
|
||||||
|
icon={CreditCard}
|
||||||
|
color="purple"
|
||||||
|
/>
|
||||||
|
<StatsCard
|
||||||
|
title="Growth"
|
||||||
|
value="24%" // Placeholder
|
||||||
|
change="-2%"
|
||||||
|
trend="down"
|
||||||
|
icon={TrendingUp}
|
||||||
|
color="orange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Main Content Grid */}
|
||||||
|
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6 pb-12">
|
||||||
|
<div className="lg:col-span-2 bg-white rounded-2xl shadow-lg border border-slate-200/80 p-7 hover:shadow-xl transition-shadow">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900">Active Athletes</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">Manage and monitor your fitness clients</p>
|
||||||
|
</div>
|
||||||
|
<UserManagement />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="bg-white rounded-2xl shadow-lg border border-slate-200/80 p-7 hover:shadow-xl transition-shadow">
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-2xl font-bold text-slate-900">Performance</h2>
|
||||||
|
<p className="text-sm text-gray-500 mt-1">Quick insights & analytics</p>
|
||||||
|
</div>
|
||||||
|
<AnalyticsDashboard />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -67,74 +67,71 @@ export function AnalyticsDashboard() {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-center items-center h-64">
|
<div className="flex justify-center items-center h-64">
|
||||||
<div className="text-lg">Loading analytics...</div>
|
<div className="text-center">
|
||||||
|
<div className="inline-block animate-spin">⚡</div>
|
||||||
|
<p className="text-gray-600 mt-2 text-sm">Loading analytics...</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<h2 className="text-2xl font-bold">Analytics Dashboard</h2>
|
{/* Key Metrics Cards */}
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div className="bg-gradient-to-br from-blue-50 to-cyan-50 rounded-xl p-5 border border-blue-200/30 hover:border-blue-200 transition-colors">
|
||||||
|
<p className="text-xs uppercase tracking-wide font-semibold text-gray-600 mb-2">Total Athletes</p>
|
||||||
|
<div className="flex items-baseline gap-2">
|
||||||
|
<div className="text-3xl font-black bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">{totalUsers}</div>
|
||||||
|
<span className="text-sm text-gray-500">active</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Key Metrics */}
|
<div className="bg-gradient-to-br from-emerald-50 to-teal-50 rounded-xl p-5 border border-emerald-200/30 hover:border-emerald-200 transition-colors">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<p className="text-xs uppercase tracking-wide font-semibold text-gray-600 mb-2">Total Revenue</p>
|
||||||
<Card>
|
<div className="flex items-baseline gap-2">
|
||||||
<CardContent>
|
<div className="text-3xl font-black bg-gradient-to-r from-emerald-600 to-teal-600 bg-clip-text text-transparent">${totalRevenue.toLocaleString()}</div>
|
||||||
<div className="text-center">
|
<span className="text-sm text-gray-500">ytd</span>
|
||||||
<div className="text-3xl font-bold text-blue-600">{totalUsers}</div>
|
</div>
|
||||||
<div className="text-gray-600">Total Users</div>
|
</div>
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div className="bg-gradient-to-br from-purple-50 to-pink-50 rounded-xl p-5 border border-purple-200/30 hover:border-purple-200 transition-colors">
|
||||||
<CardContent>
|
<p className="text-xs uppercase tracking-wide font-semibold text-gray-600 mb-2">Active Members</p>
|
||||||
<div className="text-center">
|
<div className="flex items-baseline gap-2">
|
||||||
<div className="text-3xl font-bold text-green-600">${totalRevenue.toLocaleString()}</div>
|
<div className="text-3xl font-black bg-gradient-to-r from-purple-600 to-pink-600 bg-clip-text text-transparent">{activeMembers}</div>
|
||||||
<div className="text-gray-600">Total Revenue</div>
|
<span className="text-sm text-gray-500">members</span>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
|
||||||
<CardContent>
|
|
||||||
<div className="text-center">
|
|
||||||
<div className="text-3xl font-bold text-purple-600">{activeMembers}</div>
|
|
||||||
<div className="text-gray-600">Active Members</div>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Charts */}
|
{/* Charts Grid */}
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 gap-5">
|
||||||
<Card>
|
<div className="bg-white rounded-2xl shadow-lg border border-slate-200 p-5 hover:shadow-xl transition-shadow">
|
||||||
<CardHeader>
|
<div className="mb-4">
|
||||||
<h3 className="text-lg font-semibold">User Growth</h3>
|
<h3 className="text-lg font-bold text-gray-900">User Growth Trend</h3>
|
||||||
</CardHeader>
|
<p className="text-sm text-gray-500">Last 6 months performance</p>
|
||||||
<CardContent>
|
</div>
|
||||||
<UserGrowthChart data={userGrowthData} />
|
<UserGrowthChart data={userGrowthData} />
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card>
|
<div className="grid grid-cols-2 gap-5">
|
||||||
<CardHeader>
|
<div className="bg-white rounded-2xl shadow-lg border border-slate-200 p-5 hover:shadow-xl transition-shadow">
|
||||||
<h3 className="text-lg font-semibold">Membership Distribution</h3>
|
<div className="mb-4">
|
||||||
</CardHeader>
|
<h3 className="text-lg font-bold text-gray-900">Membership Mix</h3>
|
||||||
<CardContent>
|
<p className="text-sm text-gray-500">Distribution breakdown</p>
|
||||||
|
</div>
|
||||||
<MembershipDistributionChart data={membershipData} />
|
<MembershipDistributionChart data={membershipData} />
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Card>
|
<div className="bg-white rounded-2xl shadow-lg border border-slate-200 p-5 hover:shadow-xl transition-shadow">
|
||||||
<CardHeader>
|
<div className="mb-4">
|
||||||
<h3 className="text-lg font-semibold">Monthly Revenue</h3>
|
<h3 className="text-lg font-bold text-gray-900">Revenue Stream</h3>
|
||||||
</CardHeader>
|
<p className="text-sm text-gray-500">Monthly earnings</p>
|
||||||
<CardContent>
|
</div>
|
||||||
<RevenueChart data={revenueData} />
|
<RevenueChart data={revenueData} />
|
||||||
</CardContent>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -65,14 +65,24 @@ export function Sidebar() {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<aside className="w-64 bg-slate-900 text-white h-screen fixed left-0 top-0 flex flex-col border-r border-slate-800">
|
<aside className="w-64 bg-gradient-to-b from-slate-900 via-slate-900 to-slate-950 text-white h-screen fixed left-0 top-0 flex flex-col border-r border-slate-800/50 shadow-2xl">
|
||||||
<div className="p-6 border-b border-slate-800">
|
{/* Logo Section */}
|
||||||
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-indigo-400 bg-clip-text text-transparent">
|
<div className="p-6 border-b border-slate-800/50">
|
||||||
FitAI Admin
|
<div className="flex items-center gap-3">
|
||||||
</h1>
|
<div className="h-10 w-10 rounded-xl bg-gradient-to-br from-blue-500 to-cyan-500 flex items-center justify-center font-bold text-white shadow-lg">
|
||||||
|
⚡
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-lg font-black bg-gradient-to-r from-blue-400 via-cyan-400 to-blue-300 bg-clip-text text-transparent">
|
||||||
|
FitAI
|
||||||
|
</h1>
|
||||||
|
<p className="text-xs text-slate-400 font-semibold tracking-wide">ADMIN PRO</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav className="flex-1 p-4 space-y-2">
|
{/* Navigation */}
|
||||||
|
<nav className="flex-1 p-4 space-y-1 overflow-y-auto">
|
||||||
{menuItems.map((item) => {
|
{menuItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
const isActive = pathname === item.href;
|
const isActive = pathname === item.href;
|
||||||
@ -81,23 +91,36 @@ export function Sidebar() {
|
|||||||
<Link
|
<Link
|
||||||
key={item.href}
|
key={item.href}
|
||||||
href={item.href}
|
href={item.href}
|
||||||
className={`flex items-center gap-3 px-4 py-3 rounded-lg transition-all duration-200 group ${isActive
|
className={`flex items-center gap-3 px-4 py-3 rounded-xl transition-all duration-200 group relative overflow-hidden ${isActive
|
||||||
? "bg-blue-600 text-white shadow-lg shadow-blue-900/20"
|
? "bg-gradient-to-r from-blue-600 to-cyan-600 text-white shadow-lg shadow-blue-900/30"
|
||||||
: "text-slate-400 hover:bg-slate-800 hover:text-white"}`}
|
: "text-slate-400 hover:bg-slate-800/40 hover:text-white"}`}
|
||||||
>
|
>
|
||||||
<Icon size={20} className={isActive ? "text-white" : "text-slate-500 group-hover:text-white"} />
|
{isActive && <div className="absolute inset-0 bg-white/10 blur-xl"></div>}
|
||||||
<span className="font-medium">{label}</span>
|
<Icon size={20} className={`${isActive ? "text-white" : "text-slate-500 group-hover:text-white"} relative z-10`} />
|
||||||
|
<span className="font-semibold relative z-10">{label}</span>
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div className="p-4 border-t border-slate-800">
|
{/* User Section */}
|
||||||
<div className="flex items-center gap-3 px-4 py-3 rounded-lg bg-slate-800/50">
|
<div className="p-4 border-t border-slate-800/50 bg-slate-800/20">
|
||||||
<UserButton afterSignOutUrl="/" />
|
<div className="flex items-center gap-3 px-3 py-3 rounded-xl bg-slate-800/50 hover:bg-slate-800/70 transition-colors">
|
||||||
|
<div className="h-10 w-10 rounded-full bg-gradient-to-br from-blue-500 to-cyan-500 flex-shrink-0 overflow-hidden">
|
||||||
|
<UserButton afterSignOutUrl="/" />
|
||||||
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<p className="text-sm font-medium text-white truncate">
|
<p className="text-sm font-semibold text-white truncate">
|
||||||
{user?.fullName || "Admin User"}
|
{user?.fullName || "Admin"}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-slate-400 truncate">
|
||||||
|
{user?.emailAddresses[0]?.emailAddress || "admin@fitai.com"}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
);
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-slate-400 truncate">
|
<p className="text-xs text-slate-400 truncate">
|
||||||
{user?.primaryEmailAddress?.emailAddress}
|
{user?.primaryEmailAddress?.emailAddress}
|
||||||
|
|||||||
@ -12,38 +12,62 @@ interface StatsCardProps {
|
|||||||
|
|
||||||
export function StatsCard({ title, value, change, trend, icon: Icon, color = "blue" }: StatsCardProps) {
|
export function StatsCard({ title, value, change, trend, icon: Icon, color = "blue" }: StatsCardProps) {
|
||||||
const colorStyles = {
|
const colorStyles = {
|
||||||
blue: "bg-blue-50 text-blue-600",
|
blue: {
|
||||||
green: "bg-green-50 text-green-600",
|
bg: "bg-gradient-to-br from-blue-600 to-blue-700",
|
||||||
purple: "bg-purple-50 text-purple-600",
|
iconBg: "bg-blue-500/20 text-blue-300",
|
||||||
orange: "bg-orange-50 text-orange-600",
|
light: "from-blue-50 to-blue-100/50",
|
||||||
|
},
|
||||||
|
green: {
|
||||||
|
bg: "bg-gradient-to-br from-emerald-600 to-emerald-700",
|
||||||
|
iconBg: "bg-emerald-500/20 text-emerald-300",
|
||||||
|
light: "from-emerald-50 to-emerald-100/50",
|
||||||
|
},
|
||||||
|
purple: {
|
||||||
|
bg: "bg-gradient-to-br from-purple-600 to-purple-700",
|
||||||
|
iconBg: "bg-purple-500/20 text-purple-300",
|
||||||
|
light: "from-purple-50 to-purple-100/50",
|
||||||
|
},
|
||||||
|
orange: {
|
||||||
|
bg: "bg-gradient-to-br from-orange-600 to-orange-700",
|
||||||
|
iconBg: "bg-orange-500/20 text-orange-300",
|
||||||
|
light: "from-orange-50 to-orange-100/50",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const styles = colorStyles[color];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card className={`bg-gradient-to-br ${styles.light} border-0 shadow-lg hover:shadow-xl transition-all duration-300 overflow-hidden group`}>
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<div className={`absolute inset-0 opacity-0 group-hover:opacity-5 ${styles.bg} transition-opacity`}></div>
|
||||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-3 relative z-10">
|
||||||
|
<CardTitle className="text-sm font-semibold text-gray-700 uppercase tracking-wide">
|
||||||
{title}
|
{title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className={`p-2 rounded-lg ${colorStyles[color]}`}>
|
<div className={`p-3 rounded-2xl ${styles.iconBg} backdrop-blur-sm`}>
|
||||||
<Icon size={16} />
|
<Icon size={20} />
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="relative z-10">
|
||||||
<div className="text-2xl font-bold">{value}</div>
|
<div className={`text-4xl font-black bg-gradient-to-r ${styles.bg.replace('bg-gradient-to-br', '')} bg-clip-text text-transparent`}>
|
||||||
|
{value}
|
||||||
|
</div>
|
||||||
{change && (
|
{change && (
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<div className="flex items-center gap-2 mt-3">
|
||||||
<span
|
<p className="text-xs text-gray-600 font-medium">
|
||||||
className={`font-medium ${trend === "up"
|
<span
|
||||||
? "text-green-600"
|
className={`font-bold px-2 py-1 rounded-full text-xs ${
|
||||||
: trend === "down"
|
trend === "up"
|
||||||
? "text-red-600"
|
? "bg-emerald-100 text-emerald-700"
|
||||||
: "text-slate-600"
|
: trend === "down"
|
||||||
|
? "bg-red-100 text-red-700"
|
||||||
|
: "bg-gray-100 text-gray-700"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{change}
|
{trend === "up" ? "↑" : trend === "down" ? "↓" : "→"} {change}
|
||||||
</span>{" "}
|
</span>
|
||||||
vs last month
|
</p>
|
||||||
</p>
|
<span className="text-xs text-gray-500">vs last month</span>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -265,45 +265,72 @@ export function UserGrid({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-4">
|
||||||
<div className="flex justify-between items-center mb-4">
|
{/* Search and Actions Bar */}
|
||||||
<input
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 bg-gradient-to-r from-slate-50 to-blue-50 p-4 rounded-xl border border-slate-200">
|
||||||
type="text"
|
<div className="w-full sm:w-auto flex-1">
|
||||||
placeholder="Search users..."
|
<div className="relative">
|
||||||
className="border border-gray-300 rounded px-4 py-2"
|
<svg className="absolute left-3 top-3 w-5 h-5 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
value={searchQuery}
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
</svg>
|
||||||
/>
|
<input
|
||||||
<div className="flex gap-2">
|
type="text"
|
||||||
|
placeholder="Search athletes..."
|
||||||
|
className="w-full pl-10 pr-4 py-2.5 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent bg-white shadow-sm"
|
||||||
|
value={searchQuery}
|
||||||
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-2 w-full sm:w-auto">
|
||||||
<button
|
<button
|
||||||
className="bg-green-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
className="flex-1 sm:flex-none bg-gradient-to-r from-blue-500 to-blue-600 hover:from-blue-600 hover:to-blue-700 text-white px-4 py-2.5 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed font-semibold transition-all shadow-md hover:shadow-lg"
|
||||||
onClick={handleEdit}
|
onClick={handleEdit}
|
||||||
disabled={selectedUsers.length !== 1}
|
disabled={selectedUsers.length !== 1}
|
||||||
>
|
>
|
||||||
Edit User
|
✏️ Edit
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="bg-red-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
className="flex-1 sm:flex-none bg-gradient-to-r from-red-500 to-red-600 hover:from-red-600 hover:to-red-700 text-white px-4 py-2.5 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed font-semibold transition-all shadow-md hover:shadow-lg"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
disabled={selectedUsers.length !== 1}
|
disabled={selectedUsers.length !== 1}
|
||||||
>
|
>
|
||||||
Delete User
|
🗑️ Delete
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="bg-yellow-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
className="flex-1 sm:flex-none bg-gradient-to-r from-orange-500 to-orange-600 hover:from-orange-600 hover:to-orange-700 text-white px-4 py-2.5 rounded-lg disabled:opacity-50 disabled:cursor-not-allowed font-semibold transition-all shadow-md hover:shadow-lg"
|
||||||
onClick={handleBulkDelete}
|
onClick={handleBulkDelete}
|
||||||
disabled={selectedUsers.length === 0}
|
disabled={selectedUsers.length === 0}
|
||||||
>
|
>
|
||||||
Bulk Delete
|
⚡ Bulk
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
className="ag-theme-alpine"
|
{/* Table */}
|
||||||
style={{ height: "600px", width: "100%" }}
|
<div className="bg-white rounded-2xl shadow-lg border border-slate-200 overflow-hidden">
|
||||||
>
|
<div
|
||||||
<AgGridReact<User> {...gridOptions} ref={gridRef} />
|
className="ag-theme-alpine"
|
||||||
|
style={{ height: "600px", width: "100%" }}
|
||||||
|
>
|
||||||
|
<AgGridReact<User> {...gridOptions} ref={gridRef} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Selected Count */}
|
||||||
|
{selectedUsers.length > 0 && (
|
||||||
|
<div className="flex items-center justify-between bg-gradient-to-r from-blue-500/10 to-cyan-500/10 border border-blue-200/50 px-4 py-3 rounded-lg">
|
||||||
|
<p className="text-sm font-semibold text-blue-700">
|
||||||
|
{selectedUsers.length} athlete{selectedUsers.length !== 1 ? 's' : ''} selected
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={() => setSelectedUsers([])}
|
||||||
|
className="text-sm text-blue-600 hover:text-blue-700 font-medium"
|
||||||
|
>
|
||||||
|
Clear selection
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user