Feature/UI: updated layout, page, cards, sidebar, stats, user management

This commit is contained in:
Aleksandar 2025-12-10 14:00:43 +01:00
parent 0b9902dc5c
commit ddcc9f85a8
6 changed files with 473 additions and 278 deletions

View File

@ -26,7 +26,7 @@ export default function RootLayout({
<ClerkProvider> <ClerkProvider>
<html lang="en"> <html lang="en">
<body className={inter.className}> <body className={inter.className}>
<div className="flex min-h-screen bg-slate-50"> <div className="flex min-h-screen bg-gradient-to-br from-white via-blue-50 to-slate-50">
<Sidebar /> <Sidebar />
<main className="flex-1 ml-64 p-8"> <main className="flex-1 ml-64 p-8">
{children} {children}

View File

@ -1,10 +1,10 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Users, CreditCard, CalendarCheck, TrendingUp } from "lucide-react"; import { Users, Activity, Zap, TrendingUp, Trophy, Target } from "lucide-react";
import { StatsCard } from "@/components/ui/StatsCard"; import { StatsCard } from "@/components/ui/StatsCard";
import { UserManagement } from "@/components/users/UserManagement";
import { AnalyticsDashboard } from "@/components/analytics/AnalyticsDashboard"; import { AnalyticsDashboard } from "@/components/analytics/AnalyticsDashboard";
import { UserManagement } from "@/components/users/UserManagement";
import axios from "axios"; import axios from "axios";
interface DashboardStats { interface DashboardStats {
@ -46,56 +46,130 @@ export default function Home() {
}; };
return ( return (
<div className="space-y-8"> <div className="min-h-screen bg-gradient-to-br from-white via-slate-50 to-blue-50">
<div> {/* Premium Header */}
<h2 className="text-3xl font-bold text-slate-900">Dashboard</h2> <div className="bg-gradient-to-r from-slate-900 via-blue-900 to-slate-900 shadow-2xl">
<p className="text-slate-500 mt-2">Welcome back, here's what's happening today.</p> <div className="max-w-7xl mx-auto px-8 py-12">
<div className="flex items-end justify-between">
<div className="flex items-end gap-6">
<div className="w-16 h-16 bg-gradient-to-br from-blue-400 to-cyan-400 rounded-3xl flex items-center justify-center shadow-lg">
<Trophy className="w-9 h-9 text-white" />
</div>
<div>
<h1 className="text-5xl font-black text-white tracking-tight">FitAI</h1>
<p className="text-blue-200 text-sm font-semibold mt-2">Premium Sports Management Platform</p>
</div>
</div>
<div className="text-right">
<p className="text-blue-100 text-sm">Welcome</p>
<p className="text-2xl font-bold text-white">Admin Control</p>
</div>
</div>
</div>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> {/* Main Content with Proper Spacing */}
<StatsCard <div className="max-w-7xl mx-auto px-8 py-16">
title="Total Users"
value={loading ? "..." : stats.totalUsers} {/* Section: Performance Metrics */}
change="+12%" // Placeholder for now as we don't track historical growth yet <div className="mb-20">
trend="up" <div className="mb-10">
icon={Users} <h2 className="text-3xl font-black text-slate-900">Performance Metrics</h2>
color="blue" <p className="text-slate-600 mt-2 font-medium">Real-time insights into your platform</p>
/> </div>
<StatsCard
title="Active Clients" <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
value={loading ? "..." : stats.activeClients} <StatsCard
change="+5%" title="Total Members"
trend="up" value={loading ? "..." : stats.totalUsers}
icon={CalendarCheck} change="+12% this month"
color="green" trend="up"
/> icon={Users}
<StatsCard color="blue"
title="Revenue" />
value={loading ? "..." : formatCurrency(stats.totalRevenue)} <StatsCard
change={`${stats.revenueGrowth > 0 ? "+" : ""}${stats.revenueGrowth}%`} title="Active Athletes"
trend={stats.revenueGrowth >= 0 ? "up" : "down"} value={loading ? "..." : stats.activeClients}
icon={CreditCard} change="+8% this week"
color="purple" trend="up"
/> icon={Activity}
<StatsCard color="green"
title="Growth" />
value="24%" // Placeholder <StatsCard
change="-2%" title="Revenue"
trend="down" value={loading ? "..." : formatCurrency(stats.totalRevenue)}
icon={TrendingUp} change={`${stats.revenueGrowth > 0 ? "+" : ""}${stats.revenueGrowth}% growth`}
color="orange" trend={stats.revenueGrowth >= 0 ? "up" : "down"}
/> icon={Zap}
</div> color="amber"
/>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8"> <StatsCard
<div className="lg:col-span-2 bg-white rounded-xl shadow-sm border border-slate-100 p-6"> title="Growth"
<h3 className="text-xl font-bold text-slate-900 mb-6">Recent Activity</h3> value="24%"
<UserManagement /> change="+3% vs last month"
trend="up"
icon={TrendingUp}
color="cyan"
/>
</div>
</div> </div>
<div className="bg-white rounded-xl shadow-sm border border-slate-100 p-6"> {/* Section: Member Management */}
<h3 className="text-xl font-bold text-slate-900 mb-6">Quick Analytics</h3> <div className="mb-20">
<AnalyticsDashboard /> <div className="mb-8">
<h2 className="text-3xl font-black text-slate-900">Members Directory</h2>
<p className="text-slate-600 mt-2 font-medium">View detailed member information and analytics</p>
</div>
<div className="bg-white rounded-3xl shadow-lg border border-slate-100 p-10">
<UserManagement />
</div>
</div>
{/* Section: Key Insights */}
<div className="mb-20">
<div className="mb-8">
<h2 className="text-3xl font-black text-slate-900">Key Insights</h2>
<p className="text-slate-600 mt-2 font-medium">Quick stats overview</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="bg-white rounded-3xl shadow-lg border border-slate-100 p-8">
<div className="p-6 bg-gradient-to-br from-blue-50 via-blue-100 to-blue-50 rounded-2xl border border-blue-200 hover:shadow-md transition-shadow">
<p className="text-xs text-blue-700 uppercase font-black tracking-wider">Members</p>
<p className="text-3xl font-black text-blue-900 mt-4 truncate">{loading ? "..." : stats.totalUsers}</p>
<p className="text-sm text-blue-700 mt-2 font-semibold">Active subscriptions</p>
</div>
</div>
<div className="bg-white rounded-3xl shadow-lg border border-slate-100 p-8">
<div className="p-6 bg-gradient-to-br from-emerald-50 via-emerald-100 to-emerald-50 rounded-2xl border border-emerald-200 hover:shadow-md transition-shadow">
<p className="text-xs text-emerald-700 uppercase font-black tracking-wider">Training Now</p>
<p className="text-3xl font-black text-emerald-900 mt-4 truncate">{loading ? "..." : stats.activeClients}</p>
<p className="text-sm text-emerald-700 mt-2 font-semibold">Currently active</p>
</div>
</div>
<div className="bg-white rounded-3xl shadow-lg border border-slate-100 p-8">
<div className="p-6 bg-gradient-to-br from-amber-50 via-amber-100 to-amber-50 rounded-2xl border border-amber-200 hover:shadow-md transition-shadow">
<p className="text-xs text-amber-700 uppercase font-black tracking-wider">Revenue</p>
<p className="text-2xl font-black text-amber-900 mt-4 truncate">{loading ? "..." : formatCurrency(stats.totalRevenue)}</p>
<p className="text-sm text-amber-700 mt-2 font-semibold">This period</p>
</div>
</div>
</div>
</div>
{/* Section: Detailed Report - Full Width */}
<div className="mb-20">
<div className="mb-8">
<h2 className="text-3xl font-black text-slate-900">Detailed Report</h2>
<p className="text-slate-600 mt-2 font-medium">Analytics Dashboard</p>
</div>
<div className="bg-white rounded-3xl shadow-lg border border-slate-100 p-10">
<div className="bg-slate-50 rounded-2xl p-8">
<AnalyticsDashboard />
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -65,14 +65,15 @@ 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-white to-slate-50 h-screen fixed left-0 top-0 flex flex-col border-r border-slate-200/50">
<div className="p-6 border-b border-slate-800"> <div className="p-6 border-b border-slate-200">
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-400 to-indigo-400 bg-clip-text text-transparent"> <h1 className="text-2xl font-black bg-gradient-to-r from-blue-600 to-cyan-600 bg-clip-text text-transparent">
FitAI Admin FitAI
</h1> </h1>
<p className="text-xs text-slate-600 font-medium mt-1">Sports Admin</p>
</div> </div>
<nav className="flex-1 p-4 space-y-2"> <nav className="flex-1 p-4 space-y-1">
{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,25 +82,25 @@ 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-2xl transition-all duration-200 group font-medium ${isActive
? "bg-blue-600 text-white shadow-lg shadow-blue-900/20" ? "bg-gradient-to-r from-blue-500 to-cyan-500 text-white shadow-md shadow-blue-500/20"
: "text-slate-400 hover:bg-slate-800 hover:text-white"}`} : "text-slate-700 hover:bg-slate-100 hover:text-blue-600"}`}
> >
<Icon size={20} className={isActive ? "text-white" : "text-slate-500 group-hover:text-white"} /> <Icon size={20} className={isActive ? "text-white" : "text-slate-500 group-hover:text-blue-600"} />
<span className="font-medium">{label}</span> <span className="text-sm">{label}</span>
</Link> </Link>
); );
})} })}
</nav> </nav>
<div className="p-4 border-t border-slate-800"> <div className="p-4 border-t border-slate-200">
<div className="flex items-center gap-3 px-4 py-3 rounded-lg bg-slate-800/50"> <div className="flex items-center gap-3 px-4 py-3 rounded-2xl bg-slate-100">
<UserButton afterSignOutUrl="/" /> <UserButton afterSignOutUrl="/" />
<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-slate-900 truncate">
{user?.fullName || "Admin User"} {user?.fullName || "Admin User"}
</p> </p>
<p className="text-xs text-slate-400 truncate"> <p className="text-xs text-slate-600 truncate">
{user?.primaryEmailAddress?.emailAddress} {user?.primaryEmailAddress?.emailAddress}
</p> </p>
</div> </div>

View File

@ -1,5 +1,4 @@
import { LucideIcon } from "lucide-react"; import { LucideIcon } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
interface StatsCardProps { interface StatsCardProps {
title: string; title: string;
@ -7,45 +6,67 @@ interface StatsCardProps {
change?: string; change?: string;
trend?: "up" | "down" | "neutral"; trend?: "up" | "down" | "neutral";
icon: LucideIcon; icon: LucideIcon;
color?: "blue" | "green" | "purple" | "orange"; color?: "blue" | "green" | "amber" | "cyan";
} }
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: "from-blue-50 via-blue-100 to-blue-50",
purple: "bg-purple-50 text-purple-600", text: "text-blue-950",
orange: "bg-orange-50 text-orange-600", icon: "bg-gradient-to-br from-blue-500 to-blue-600",
border: "border-blue-200",
label: "text-blue-700"
},
green: {
bg: "from-emerald-50 via-emerald-100 to-emerald-50",
text: "text-emerald-950",
icon: "bg-gradient-to-br from-emerald-500 to-emerald-600",
border: "border-emerald-200",
label: "text-emerald-700"
},
amber: {
bg: "from-amber-50 via-amber-100 to-amber-50",
text: "text-amber-950",
icon: "bg-gradient-to-br from-amber-500 to-amber-600",
border: "border-amber-200",
label: "text-amber-700"
},
cyan: {
bg: "from-cyan-50 via-cyan-100 to-cyan-50",
text: "text-cyan-950",
icon: "bg-gradient-to-br from-cyan-500 to-cyan-600",
border: "border-cyan-200",
label: "text-cyan-700"
},
}; };
const style = colorStyles[color];
return ( return (
<Card> <div className={`bg-gradient-to-br ${style.bg} rounded-3xl border ${style.border} p-6 hover:shadow-xl transition-all duration-300 cursor-default group`}>
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2"> <div className="flex items-start justify-between mb-4 gap-3">
<CardTitle className="text-sm font-medium text-muted-foreground"> <div className="flex-1 min-w-0">
{title} <p className={`text-xs ${style.label} uppercase font-black tracking-widest truncate`}>
</CardTitle> {title}
<div className={`p-2 rounded-lg ${colorStyles[color]}`}>
<Icon size={16} />
</div>
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{value}</div>
{change && (
<p className="text-xs text-muted-foreground mt-1">
<span
className={`font-medium ${trend === "up"
? "text-green-600"
: trend === "down"
? "text-red-600"
: "text-slate-600"
}`}
>
{change}
</span>{" "}
vs last month
</p> </p>
)} </div>
</CardContent> <div className={`p-3 ${style.icon} rounded-2xl flex items-center justify-center shadow-lg group-hover:scale-110 transition-transform flex-shrink-0`}>
</Card> <Icon size={20} className="text-white" />
</div>
</div>
<div className={`text-4xl font-black ${style.text} mb-3 truncate`}>
{value}
</div>
{change && (
<div className="flex items-center gap-1 truncate">
<span className={`text-xs font-black uppercase tracking-wide ${style.label} truncate`}>
{trend === "up" ? "↑" : trend === "down" ? "↓" : "→"} {change}
</span>
</div>
)}
</div>
); );
} }

View File

@ -27,4 +27,12 @@ export function CardContent({ children, className = '' }: CardProps) {
{children} {children}
</div> </div>
) )
}
export function CardTitle({ children, className = '' }: CardProps) {
return (
<div className={`text-sm font-medium ${className}`}>
{children}
</div>
)
} }

View File

@ -2,7 +2,7 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { UserGrid } from "@/components/users/UserGrid"; import { UserGrid } from "@/components/users/UserGrid";
import { Button } from "@/components/ui/Button"; import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardContent } from "@/components/ui/card"; import { Card, CardHeader, CardContent } from "@/components/ui/card";
interface User { interface User {
@ -34,6 +34,7 @@ export function UserManagement() {
const [selectedUser, setSelectedUser] = useState<User | null>(null); const [selectedUser, setSelectedUser] = useState<User | null>(null);
const [isEditing, setIsEditing] = useState(false); const [isEditing, setIsEditing] = useState(false);
const [isDeleting, setIsDeleting] = useState(false); const [isDeleting, setIsDeleting] = useState(false);
const [activeTab, setActiveTab] = useState<"users" | "details">("users");
const [editForm, setEditForm] = useState<{ const [editForm, setEditForm] = useState<{
firstName: string; firstName: string;
lastName: string; lastName: string;
@ -205,104 +206,228 @@ export function UserManagement() {
}; };
return ( return (
<div className="space-y-6"> <div className="space-y-4">
<div className="flex justify-between items-center"> {/* Header with Count */}
<h2 className="text-2xl font-bold">User Management</h2> <div className="flex items-center justify-between">
<div className="flex gap-2"> <h3 className="text-lg font-black text-slate-900">
<Button {users.length} {users.length === 1 ? 'user' : 'users'}
variant={filter === "all" ? "primary" : "secondary"} </h3>
onClick={() => setFilter("all")} {selectedUser && (
> <p className="text-sm text-blue-600 font-semibold">
All Users Selected: {selectedUser.firstName} {selectedUser.lastName}
</Button> </p>
<Button )}
variant="secondary" </div>
onClick={() => selectedUser && handleEditUser(selectedUser)}
disabled={!selectedUser} {/* Compact Controls - Always Visible but Organized */}
> <div className="grid grid-cols-1 md:grid-cols-2 gap-3 p-4 bg-gradient-to-r from-slate-50 to-blue-50 rounded-lg border border-slate-200">
Edit User {/* Filter Row */}
</Button> <div>
<Button <p className="text-xs font-black uppercase tracking-wider text-slate-600 mb-2">Roles</p>
variant="secondary" <div className="flex flex-wrap gap-2">
onClick={() => { <button
setEditForm({ onClick={() => setFilter("all")}
firstName: "", className={`px-2 py-1 text-xs font-semibold rounded-lg transition-all ${
lastName: "", filter === "all"
email: "", ? "bg-blue-500 text-white shadow-md"
role: "client", : "bg-white text-slate-700 hover:bg-slate-100 border border-slate-200"
phone: "", }`}
}); >
setSelectedUser(null); All
setIsEditing(true); </button>
}} <button
> onClick={() => setFilter("client")}
Invite User className={`px-2 py-1 text-xs font-semibold rounded-lg transition-all ${
</Button> filter === "client"
<Button ? "bg-blue-500 text-white shadow-md"
variant="secondary" : "bg-white text-slate-700 hover:bg-slate-100 border border-slate-200"
onClick={() => selectedUser && handleDeleteUser(selectedUser)} }`}
disabled={!selectedUser} >
> Clients
Delete User </button>
</Button> <button
<Button onClick={() => setFilter("trainer")}
variant={filter === "client" ? "primary" : "secondary"} className={`px-2 py-1 text-xs font-semibold rounded-lg transition-all ${
onClick={() => setFilter("client")} filter === "trainer"
> ? "bg-blue-500 text-white shadow-md"
Clients : "bg-white text-slate-700 hover:bg-slate-100 border border-slate-200"
</Button> }`}
<Button >
variant={filter === "trainer" ? "primary" : "secondary"} Trainers
onClick={() => setFilter("trainer")} </button>
> <button
Trainers onClick={() => setFilter("admin")}
</Button> className={`px-2 py-1 text-xs font-semibold rounded-lg transition-all ${
<Button filter === "admin"
variant={filter === "admin" ? "primary" : "secondary"} ? "bg-blue-500 text-white shadow-md"
onClick={() => setFilter("admin")} : "bg-white text-slate-700 hover:bg-slate-100 border border-slate-200"
> }`}
Admins >
</Button> Admins
<Button </button>
variant={filter === "superAdmin" ? "primary" : "secondary"} <button
onClick={() => setFilter("superAdmin")} onClick={() => setFilter("superAdmin")}
> className={`px-2 py-1 text-xs font-semibold rounded-lg transition-all ${
Super Admins filter === "superAdmin"
</Button> ? "bg-blue-500 text-white shadow-md"
: "bg-white text-slate-700 hover:bg-slate-100 border border-slate-200"
}`}
>
Super Admins
</button>
</div>
</div>
{/* Action Buttons Row */}
<div>
<p className="text-xs font-black uppercase tracking-wider text-slate-600 mb-2">Quick Actions</p>
<div className="flex flex-wrap gap-2">
<button
onClick={() => {
setEditForm({
firstName: "",
lastName: "",
email: "",
role: "client",
phone: "",
});
setSelectedUser(null);
setIsEditing(true);
}}
className="px-2 py-1 text-xs font-semibold rounded-lg bg-blue-500 text-white hover:bg-blue-600"
>
+ Invite
</button>
<button
onClick={() => selectedUser && handleEditUser(selectedUser)}
disabled={!selectedUser}
className="px-2 py-1 text-xs font-semibold rounded-lg bg-white text-slate-700 hover:bg-slate-100 border border-slate-200 disabled:opacity-50"
>
Edit
</button>
<button
onClick={() => selectedUser && handleDeleteUser(selectedUser)}
disabled={!selectedUser}
className="px-2 py-1 text-xs font-semibold rounded-lg bg-white text-slate-700 hover:bg-slate-100 border border-slate-200 disabled:opacity-50"
>
Delete
</button>
<button onClick={handleRefresh} className="px-2 py-1 text-xs font-semibold rounded-lg bg-white text-slate-700 hover:bg-slate-100 border border-slate-200">
Refresh
</button>
<button onClick={handleExport} className="px-2 py-1 text-xs font-semibold rounded-lg bg-white text-slate-700 hover:bg-slate-100 border border-slate-200">
Export
</button>
</div>
</div> </div>
</div> </div>
<div className="flex justify-between items-center"> {/* Tab Navigation */}
<div className="text-sm text-gray-600"> <div className="flex gap-2 border-b border-slate-200">
Showing {users.length} users <button
{selectedUser && ( onClick={() => setActiveTab("users")}
<span className="ml-4 text-blue-600"> className={`px-4 py-3 text-sm font-semibold border-b-2 transition-all ${
Selected: {selectedUser.firstName} {selectedUser.lastName} activeTab === "users"
</span> ? "border-blue-500 text-blue-600"
)} : "border-transparent text-slate-600 hover:text-slate-900"
</div> }`}
<div className="flex gap-2"> >
<Button variant="secondary" onClick={handleRefresh}> User List ({users.length})
Refresh </button>
</Button> <button
<Button variant="secondary" onClick={handleExport}> onClick={() => setActiveTab("details")}
Export CSV disabled={!selectedUser}
</Button> className={`px-4 py-3 text-sm font-semibold border-b-2 transition-all ${
</div> activeTab === "details"
? "border-blue-500 text-blue-600"
: "border-transparent text-slate-600 hover:text-slate-900"
} ${!selectedUser ? "opacity-50 cursor-not-allowed" : ""}`}
>
User Details
</button>
</div> </div>
<Card> {/* Tab Content */}
<CardContent className="p-0"> {activeTab === "users" && (
<UserGrid <Card className="border border-slate-200">
users={users} <CardContent className="p-0">
onUserSelect={(user) => handleUserSelect(user)} <UserGrid
onEditUser={handleEditUser} users={users}
onDeleteUser={handleDeleteUser} onUserSelect={(user) => {
onBulkDelete={handleBulkDelete} handleUserSelect(user);
loading={loading} setActiveTab("details");
/> }}
</CardContent> onEditUser={handleEditUser}
</Card> onDeleteUser={handleDeleteUser}
onBulkDelete={handleBulkDelete}
loading={loading}
/>
</CardContent>
</Card>
)}
{activeTab === "details" && selectedUser && (
<div className="p-6 bg-white rounded-xl border border-slate-200">
<div className="mb-6">
<h3 className="text-xl font-black text-slate-900">
{selectedUser.firstName} {selectedUser.lastName}
</h3>
<p className="text-sm text-slate-600 mt-1">{selectedUser.email}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="space-y-2">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Role</p>
<p className="text-base font-semibold text-slate-900">{selectedUser.role}</p>
</div>
<div className="space-y-2">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Joined</p>
<p className="text-base font-semibold text-slate-900">{new Date(selectedUser.createdAt).toLocaleDateString()}</p>
</div>
{selectedUser.client && (
<>
<div className="space-y-2">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Membership</p>
<p className="text-base font-semibold text-slate-900">{selectedUser.client.membershipType}</p>
</div>
<div className="space-y-2">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Status</p>
<p className="text-base font-semibold text-slate-900">{selectedUser.client.membershipStatus}</p>
</div>
</>
)}
<div className="space-y-2">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Check-ins This Month</p>
<p className="text-2xl font-black text-blue-600">{selectedUser.checkInsThisMonth || 0}</p>
</div>
</div>
<div className="flex gap-3 mt-8 pt-6 border-t border-slate-200">
<button
onClick={() => selectedUser && handleEditUser(selectedUser)}
className="px-5 py-2 bg-blue-50 text-blue-600 rounded-lg font-semibold text-sm hover:bg-blue-100 transition-all"
>
Edit User
</button>
<button
onClick={() => selectedUser && handleDeleteUser(selectedUser)}
className="px-5 py-2 bg-red-50 text-red-600 rounded-lg font-semibold text-sm hover:bg-red-100 transition-all"
>
Delete User
</button>
<a
href={`/users/${selectedUser.id}`}
className="px-5 py-2 bg-gradient-to-r from-blue-500 to-cyan-500 text-white rounded-lg font-semibold text-sm hover:shadow-lg transition-all"
>
Full Profile
</a>
</div>
</div>
)}
{isEditing && editForm && ( {isEditing && editForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
@ -438,98 +563,64 @@ export function UserManagement() {
)} )}
{selectedUser && ( {selectedUser && (
<Card> <div className="mt-8 pt-8 border-t border-slate-200">
<CardHeader className="flex flex-row items-center justify-between"> <div className="mb-6">
<h3 className="text-lg font-semibold">User Details</h3> <h3 className="text-lg font-black text-slate-900">
{selectedUser.firstName} {selectedUser.lastName}
</h3>
<p className="text-sm text-slate-600 mt-1">{selectedUser.email}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div className="space-y-3">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Role</p>
<p className="text-base font-semibold text-slate-900">{selectedUser.role}</p>
<p className="text-xs font-black uppercase tracking-wider text-slate-600 mt-4">Joined</p>
<p className="text-base font-semibold text-slate-900">{new Date(selectedUser.createdAt).toLocaleDateString()}</p>
</div>
{selectedUser.client && (
<div className="space-y-3">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Membership</p>
<p className="text-base font-semibold text-slate-900">{selectedUser.client.membershipType}</p>
<p className="text-xs font-black uppercase tracking-wider text-slate-600 mt-4">Status</p>
<p className="text-base font-semibold text-slate-900">{selectedUser.client.membershipStatus}</p>
</div>
)}
<div className="space-y-3">
<p className="text-xs font-black uppercase tracking-wider text-slate-600">Check-ins This Month</p>
<p className="text-2xl font-black text-blue-600">{selectedUser.checkInsThisMonth || 0}</p>
<p className="text-xs font-black uppercase tracking-wider text-slate-600 mt-4">Last Check-in</p>
<p className="text-sm text-slate-900">
{selectedUser.lastCheckInTime
? new Date(selectedUser.lastCheckInTime).toLocaleDateString()
: "Never"}
</p>
</div>
</div>
<div className="flex gap-3 mt-6">
<button
onClick={() => selectedUser && handleEditUser(selectedUser)}
className="px-5 py-2 bg-blue-50 text-blue-600 rounded-lg font-semibold text-sm hover:bg-blue-100 transition-all"
>
Edit
</button>
<button
onClick={() => selectedUser && handleDeleteUser(selectedUser)}
className="px-5 py-2 bg-red-50 text-red-600 rounded-lg font-semibold text-sm hover:bg-red-100 transition-all"
>
Delete
</button>
<a <a
href={`/users/${selectedUser.id}`} href={`/users/${selectedUser.id}`}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 text-sm font-medium" className="px-5 py-2 bg-gradient-to-r from-blue-500 to-cyan-500 text-white rounded-lg font-semibold text-sm hover:shadow-lg transition-all"
> >
View Full Profile & Recommendations Full Profile
</a> </a>
</CardHeader> </div>
<CardContent> </div>
<div className="grid grid-cols-3 gap-4">
<div>
<h4 className="font-medium mb-2">Basic Information</h4>
<div className="space-y-1 text-sm">
<p>
<span className="font-medium">Name:</span>{" "}
{selectedUser.firstName} {selectedUser.lastName}
</p>
<p>
<span className="font-medium">Email:</span>{" "}
{selectedUser.email}
</p>
<p>
<span className="font-medium">Phone:</span>{" "}
{selectedUser.phone || "N/A"}
</p>
<p>
<span className="font-medium">Role:</span>{" "}
{selectedUser.role}
</p>
<p>
<span className="font-medium">Joined:</span>{" "}
{new Date(selectedUser.createdAt).toLocaleDateString()}
</p>
</div>
</div>
{selectedUser.client && (
<div>
<h4 className="font-medium mb-2">Client Information</h4>
<div className="space-y-1 text-sm">
<p>
<span className="font-medium">Membership:</span>{" "}
{selectedUser.client.membershipType}
</p>
<p>
<span className="font-medium">Status:</span>{" "}
{selectedUser.client.membershipStatus}
</p>
<p>
<span className="font-medium">Member Since:</span>{" "}
{new Date(
selectedUser.client.joinDate,
).toLocaleDateString()}
</p>
<p>
<span className="font-medium">Last Visit:</span>{" "}
{selectedUser.client.lastVisit
? new Date(
selectedUser.client.lastVisit,
).toLocaleDateString()
: "Never"}
</p>
</div>
</div>
)}
<div>
<h4 className="font-medium mb-2">Check-In Statistics</h4>
<div className="space-y-1 text-sm">
<p>
<span className="font-medium">Last Check-In:</span>{" "}
{selectedUser.lastCheckInTime
? new Date(
selectedUser.lastCheckInTime,
).toLocaleString()
: "Never"}
</p>
<p>
<span className="font-medium">This Week:</span>{" "}
{selectedUser.checkInsThisWeek || 0} check-ins
</p>
<p>
<span className="font-medium">This Month:</span>{" "}
{selectedUser.checkInsThisMonth || 0} check-ins
</p>
</div>
</div>
</div>
</CardContent>
</Card>
)} )}
</div> </div>
); );