update
This commit is contained in:
parent
ddb6933e42
commit
bf741d6d00
@ -1,10 +1,18 @@
|
|||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Sports Theme Colors */
|
||||||
|
:root {
|
||||||
|
--primary-blue: #2563eb;
|
||||||
|
--secondary-orange: #fb7a1b;
|
||||||
|
--accent-green: #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 0 0% 100%;
|
/* Light Mode - Sports Theme */
|
||||||
|
--background: 0 0% 98%;
|
||||||
--foreground: 222.2 84% 4.9%;
|
--foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
--card: 0 0% 100%;
|
--card: 0 0% 100%;
|
||||||
@ -13,56 +21,63 @@
|
|||||||
--popover: 0 0% 100%;
|
--popover: 0 0% 100%;
|
||||||
--popover-foreground: 222.2 84% 4.9%;
|
--popover-foreground: 222.2 84% 4.9%;
|
||||||
|
|
||||||
--primary: 222.2 47.4% 11.2%;
|
/* Electric Blue Primary */
|
||||||
--primary-foreground: 210 40% 98%;
|
--primary: 217 91.2% 59.8%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
|
||||||
--secondary: 210 40% 96.1%;
|
/* Vibrant Orange Secondary */
|
||||||
--secondary-foreground: 222.2 47.4% 11.2%;
|
--secondary: 24.6 97.4% 54.3%;
|
||||||
|
--secondary-foreground: 0 0% 100%;
|
||||||
|
|
||||||
|
/* Emerald Green Accent */
|
||||||
|
--accent: 142.1 70.6% 45.3%;
|
||||||
|
--accent-foreground: 0 0% 100%;
|
||||||
|
|
||||||
--muted: 210 40% 96.1%;
|
--muted: 210 40% 96.1%;
|
||||||
--muted-foreground: 215.4 16.3% 46.9%;
|
--muted-foreground: 215.4 16.3% 46.9%;
|
||||||
|
|
||||||
--accent: 210 40% 96.1%;
|
|
||||||
--accent-foreground: 222.2 47.4% 11.2%;
|
|
||||||
|
|
||||||
--destructive: 0 84.2% 60.2%;
|
--destructive: 0 84.2% 60.2%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--border: 214.3 31.8% 91.4%;
|
--border: 214.3 31.8% 91.4%;
|
||||||
--input: 214.3 31.8% 91.4%;
|
--input: 214.3 31.8% 91.4%;
|
||||||
--ring: 222.2 84% 4.9%;
|
--ring: 217 91.2% 59.8%;
|
||||||
|
|
||||||
--radius: 0.5rem;
|
--radius: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
|
/* Dark Mode - Sports Theme */
|
||||||
--background: 222.2 84% 4.9%;
|
--background: 222.2 84% 4.9%;
|
||||||
--foreground: 210 40% 98%;
|
--foreground: 210 40% 98%;
|
||||||
|
|
||||||
--card: 222.2 84% 4.9%;
|
--card: 225.7 29.5% 15.3%;
|
||||||
--card-foreground: 210 40% 98%;
|
--card-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--popover: 222.2 84% 4.9%;
|
--popover: 225.7 29.5% 15.3%;
|
||||||
--popover-foreground: 210 40% 98%;
|
--popover-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--primary: 210 40% 98%;
|
/* Electric Blue Primary */
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
--primary: 217 91.2% 59.8%;
|
||||||
|
--primary-foreground: 225.7 29.5% 15.3%;
|
||||||
|
|
||||||
--secondary: 217.2 32.6% 17.5%;
|
/* Vibrant Orange Secondary */
|
||||||
--secondary-foreground: 210 40% 98%;
|
--secondary: 24.6 97.4% 54.3%;
|
||||||
|
--secondary-foreground: 225.7 29.5% 15.3%;
|
||||||
|
|
||||||
|
/* Emerald Green Accent */
|
||||||
|
--accent: 142.1 70.6% 45.3%;
|
||||||
|
--accent-foreground: 225.7 29.5% 15.3%;
|
||||||
|
|
||||||
--muted: 217.2 32.6% 17.5%;
|
--muted: 217.2 32.6% 17.5%;
|
||||||
--muted-foreground: 215 20.2% 65.1%;
|
--muted-foreground: 215 20.2% 65.1%;
|
||||||
|
|
||||||
--accent: 217.2 32.6% 17.5%;
|
|
||||||
--accent-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--destructive: 0 62.8% 30.6%;
|
--destructive: 0 62.8% 30.6%;
|
||||||
--destructive-foreground: 210 40% 98%;
|
--destructive-foreground: 210 40% 98%;
|
||||||
|
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 217.2 32.6% 17.5%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 217.2 32.6% 17.5%;
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--ring: 217 91.2% 59.8%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,6 +86,28 @@
|
|||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground transition-colors duration-300;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
@apply font-bold text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.sports-gradient {
|
||||||
|
@apply bg-gradient-to-r from-[#2563eb] via-[#fb7a1b] to-[#22c55e];
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-modern {
|
||||||
|
@apply bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-sports {
|
||||||
|
@apply bg-gradient-to-r from-[#2563eb] to-[#fb7a1b] hover:shadow-lg transform hover:scale-105 transition-all duration-200 text-white font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-gradient {
|
||||||
|
@apply bg-gradient-to-r from-[#2563eb] to-[#fb7a1b] bg-clip-text text-transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Users, CreditCard, CalendarCheck, TrendingUp } from "lucide-react";
|
import { Users, CreditCard, CalendarCheck, TrendingUp, Brain, Calendar, User } from "lucide-react";
|
||||||
import { StatsCard } from "@/components/ui/StatsCard";
|
import { StatsCard } from "@/components/ui/StatsCard";
|
||||||
import { UserManagement } from "@/components/users/UserManagement";
|
import { UserManagement } from "@/components/users/UserManagement";
|
||||||
import { AnalyticsDashboard } from "@/components/analytics/AnalyticsDashboard";
|
import { AnalyticsDashboard } from "@/components/analytics/AnalyticsDashboard";
|
||||||
@ -14,6 +14,27 @@ interface DashboardStats {
|
|||||||
revenueGrowth: number;
|
revenueGrowth: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TabType = "overview" | "users" | "analytics" | "recommendations" | "attendance" | "profile";
|
||||||
|
|
||||||
|
interface Recommendation {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
content: string;
|
||||||
|
activityPlan: string;
|
||||||
|
dietPlan: string;
|
||||||
|
status: string;
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AttendanceRecord {
|
||||||
|
id: string;
|
||||||
|
clientId: string;
|
||||||
|
checkInTime: string;
|
||||||
|
checkOutTime?: string;
|
||||||
|
type: string;
|
||||||
|
notes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [stats, setStats] = useState<DashboardStats>({
|
const [stats, setStats] = useState<DashboardStats>({
|
||||||
totalUsers: 0,
|
totalUsers: 0,
|
||||||
@ -22,6 +43,11 @@ export default function Home() {
|
|||||||
revenueGrowth: 0,
|
revenueGrowth: 0,
|
||||||
});
|
});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [activeTab, setActiveTab] = useState<TabType>("overview");
|
||||||
|
const [recommendations, setRecommendations] = useState<Recommendation[]>([]);
|
||||||
|
const [attendance, setAttendance] = useState<AttendanceRecord[]>([]);
|
||||||
|
const [recommendationsLoading, setRecommendationsLoading] = useState(false);
|
||||||
|
const [attendanceLoading, setAttendanceLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchStats = async () => {
|
const fetchStats = async () => {
|
||||||
@ -38,6 +64,46 @@ export default function Home() {
|
|||||||
fetchStats();
|
fetchStats();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Fetch recommendations when tab changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === "recommendations") {
|
||||||
|
fetchRecommendations();
|
||||||
|
}
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
|
// Fetch attendance when tab changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (activeTab === "attendance") {
|
||||||
|
fetchAttendance();
|
||||||
|
}
|
||||||
|
}, [activeTab]);
|
||||||
|
|
||||||
|
const fetchRecommendations = async () => {
|
||||||
|
setRecommendationsLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/recommendations");
|
||||||
|
const data = await response.json();
|
||||||
|
setRecommendations(data.recommendations || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching recommendations:", error);
|
||||||
|
} finally {
|
||||||
|
setRecommendationsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchAttendance = async () => {
|
||||||
|
setAttendanceLoading(true);
|
||||||
|
try {
|
||||||
|
const response = await fetch("/api/admin/attendance");
|
||||||
|
const data = await response.json();
|
||||||
|
setAttendance(data || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error fetching attendance:", error);
|
||||||
|
} finally {
|
||||||
|
setAttendanceLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const formatCurrency = (value: number) => {
|
const formatCurrency = (value: number) => {
|
||||||
return new Intl.NumberFormat("en-US", {
|
return new Intl.NumberFormat("en-US", {
|
||||||
style: "currency",
|
style: "currency",
|
||||||
@ -45,58 +111,240 @@ export default function Home() {
|
|||||||
}).format(value);
|
}).format(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const tabs: { id: TabType; label: string; icon: React.ReactNode }[] = [
|
||||||
|
{ id: "overview", label: "Overview", icon: <TrendingUp size={18} /> },
|
||||||
|
{ id: "users", label: "Users", icon: <Users size={18} /> },
|
||||||
|
{ id: "analytics", label: "Analytics", icon: <TrendingUp size={18} /> },
|
||||||
|
{ id: "recommendations", label: "Recommendations", icon: <Brain size={18} /> },
|
||||||
|
{ id: "attendance", label: "Attendance", icon: <Calendar size={18} /> },
|
||||||
|
{ id: "profile", label: "Profile", icon: <User size={18} /> },
|
||||||
|
];
|
||||||
|
|
||||||
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 dark:from-slate-950 dark:via-slate-900 dark:to-slate-950">
|
||||||
<div>
|
<div className="w-full px-4 sm:px-6 lg:px-8 py-6 sm:py-8">
|
||||||
<h2 className="text-3xl font-bold text-slate-900">Dashboard</h2>
|
{/* Header Section */}
|
||||||
<p className="text-slate-500 mt-2">Welcome back, here's what's happening today.</p>
|
<div className="mb-8 sm:mb-10">
|
||||||
</div>
|
<div className="flex items-center gap-3 mb-3">
|
||||||
|
<div className="h-1 w-12 bg-gradient-to-r from-[#2563eb] to-[#fb7a1b] rounded-full"></div>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<h1 className="text-3xl sm:text-4xl font-bold text-slate-900 dark:text-white">💪 FitAI Elite</h1>
|
||||||
<StatsCard
|
</div>
|
||||||
title="Total Users"
|
<h2 className="text-2xl sm:text-3xl font-bold text-slate-900 dark:text-white mt-4">Admin Dashboard</h2>
|
||||||
value={loading ? "..." : stats.totalUsers}
|
<p className="text-slate-600 dark:text-slate-400 mt-2 text-sm sm:text-base">Manage your fitness platform with complete control.</p>
|
||||||
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>
|
|
||||||
|
|
||||||
<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">
|
{/* Tab Navigation */}
|
||||||
<h3 className="text-xl font-bold text-slate-900 mb-6">Quick Analytics</h3>
|
<div className="mb-8 overflow-x-auto">
|
||||||
<AnalyticsDashboard />
|
<div className="flex gap-2 pb-4 min-w-min">
|
||||||
|
{tabs.map((tab) => (
|
||||||
|
<button
|
||||||
|
key={tab.id}
|
||||||
|
onClick={() => setActiveTab(tab.id)}
|
||||||
|
className={`flex items-center gap-2 px-4 py-2 rounded-lg font-medium whitespace-nowrap transition-all ${
|
||||||
|
activeTab === tab.id
|
||||||
|
? "bg-gradient-to-r from-[#2563eb] to-[#fb7a1b] text-white shadow-lg"
|
||||||
|
: "bg-white dark:bg-slate-800 text-slate-700 dark:text-slate-300 border border-slate-200 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{tab.icon}
|
||||||
|
{tab.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Overview Tab */}
|
||||||
|
{activeTab === "overview" && (
|
||||||
|
<div className="space-y-8">
|
||||||
|
{/* Stats Cards - Responsive Grid */}
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4 sm:gap-5 lg:gap-6">
|
||||||
|
<StatsCard
|
||||||
|
title="Total Users"
|
||||||
|
value={loading ? "..." : stats.totalUsers}
|
||||||
|
change="+12%"
|
||||||
|
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%"
|
||||||
|
change="-2%"
|
||||||
|
trend="down"
|
||||||
|
icon={TrendingUp}
|
||||||
|
color="orange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Quick Stats Section */}
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-[#2563eb] to-[#fb7a1b] p-6 sm:p-8">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-white">Quick Overview</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8">
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
|
<div className="text-center p-4 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 mb-2">Total Revenue</p>
|
||||||
|
<p className="text-3xl font-bold text-[#2563eb]">{formatCurrency(stats.totalRevenue)}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 mb-2">Active Members</p>
|
||||||
|
<p className="text-3xl font-bold text-[#22c55e]">{stats.activeClients}</p>
|
||||||
|
</div>
|
||||||
|
<div className="text-center p-4 bg-slate-50 dark:bg-slate-700 rounded-lg">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 mb-2">Platform Users</p>
|
||||||
|
<p className="text-3xl font-bold text-[#fb7a1b]">{stats.totalUsers}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Users Tab */}
|
||||||
|
{activeTab === "users" && (
|
||||||
|
<div className="space-y-6 sm:space-y-8">
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-[#2563eb] to-[#fb7a1b] p-6 sm:p-8">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-white flex items-center gap-2">
|
||||||
|
<Users size={28} />
|
||||||
|
User Management
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8 w-full overflow-hidden">
|
||||||
|
<UserManagement />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Analytics Tab */}
|
||||||
|
{activeTab === "analytics" && (
|
||||||
|
<div className="space-y-6 sm:space-y-8">
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-[#fb7a1b] to-[#22c55e] p-6 sm:p-8">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-white flex items-center gap-2">
|
||||||
|
<TrendingUp size={28} />
|
||||||
|
Analytics Dashboard
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8 w-full overflow-hidden">
|
||||||
|
<AnalyticsDashboard />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Other Tabs - Coming Soon */}
|
||||||
|
{activeTab === "recommendations" && (
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-[#2563eb] via-[#fb7a1b] to-[#22c55e] p-6 sm:p-8">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-white flex items-center gap-2">
|
||||||
|
<Brain size={28} />
|
||||||
|
AI Recommendations
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8">
|
||||||
|
{recommendationsLoading ? (
|
||||||
|
<div className="text-center py-8">Loading recommendations...</div>
|
||||||
|
) : recommendations.length > 0 ? (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{recommendations.slice(0, 6).map((rec) => (
|
||||||
|
<div key={rec.id} className="p-4 border border-slate-200 dark:border-slate-600 rounded-lg hover:shadow-lg transition-all">
|
||||||
|
<div className="flex justify-between items-start mb-2">
|
||||||
|
<h4 className="font-bold text-sm line-clamp-1">User ID: {rec.userId}</h4>
|
||||||
|
<span className={`text-xs px-2 py-1 rounded ${rec.status === 'pending' ? 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' : 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'}`}>
|
||||||
|
{rec.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-sm text-slate-600 dark:text-slate-400 line-clamp-2 mb-2">{rec.content}</p>
|
||||||
|
<p className="text-xs text-slate-500">Activity: {rec.activityPlan.substring(0, 50)}...</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-8 text-slate-600 dark:text-slate-400">No recommendations yet</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Attendance Tab */}
|
||||||
|
{activeTab === "attendance" && (
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-[#fb7a1b] to-[#22c55e] p-6 sm:p-8">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-white flex items-center gap-2">
|
||||||
|
<Calendar size={28} />
|
||||||
|
Attendance Monitoring
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8 overflow-x-auto">
|
||||||
|
{attendanceLoading ? (
|
||||||
|
<div className="text-center py-8">Loading attendance data...</div>
|
||||||
|
) : attendance.length > 0 ? (
|
||||||
|
<table className="w-full text-sm">
|
||||||
|
<thead>
|
||||||
|
<tr className="border-b border-slate-200 dark:border-slate-600">
|
||||||
|
<th className="text-left py-2 px-4 font-bold">Client ID</th>
|
||||||
|
<th className="text-left py-2 px-4 font-bold">Check In</th>
|
||||||
|
<th className="text-left py-2 px-4 font-bold">Check Out</th>
|
||||||
|
<th className="text-left py-2 px-4 font-bold">Type</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{attendance.slice(0, 10).map((record) => (
|
||||||
|
<tr key={record.id} className="border-b border-slate-100 dark:border-slate-700 hover:bg-slate-50 dark:hover:bg-slate-700">
|
||||||
|
<td className="py-3 px-4 line-clamp-1">{record.clientId}</td>
|
||||||
|
<td className="py-3 px-4">{new Date(record.checkInTime).toLocaleString()}</td>
|
||||||
|
<td className="py-3 px-4">{record.checkOutTime ? new Date(record.checkOutTime).toLocaleString() : '-'}</td>
|
||||||
|
<td className="py-3 px-4">
|
||||||
|
<span className="px-2 py-1 rounded-full text-xs font-bold bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200">
|
||||||
|
{record.type}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-8 text-slate-600 dark:text-slate-400">No attendance records</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Profile Tab */}
|
||||||
|
{activeTab === "profile" && (
|
||||||
|
<div className="bg-white dark:bg-slate-800 rounded-2xl shadow-lg border border-slate-200 dark:border-slate-700 overflow-hidden">
|
||||||
|
<div className="bg-gradient-to-r from-[#22c55e] to-[#2563eb] p-6 sm:p-8">
|
||||||
|
<h3 className="text-xl sm:text-2xl font-bold text-white flex items-center gap-2">
|
||||||
|
<User size={28} />
|
||||||
|
Admin Profile
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div className="p-6 sm:p-8">
|
||||||
|
<p className="text-slate-600 dark:text-slate-400 text-lg">Profile management coming soon!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useUser } from "@clerk/nextjs";
|
import { useUser } from "@clerk/nextjs";
|
||||||
import { Button } from "@/components/ui/Button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
|
||||||
interface UserProfile {
|
interface UserProfile {
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { usePathname } from "next/navigation";
|
|||||||
import { Home, Users, BarChart3, User, Brain } from "lucide-react";
|
import { Home, Users, BarChart3, User, Brain } from "lucide-react";
|
||||||
import { SignedIn, UserButton } from "@clerk/nextjs";
|
import { SignedIn, UserButton } from "@clerk/nextjs";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { Button } from "@/components/ui/Button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
interface NavItem {
|
interface NavItem {
|
||||||
href: string;
|
href: string;
|
||||||
|
|||||||
@ -19,19 +19,19 @@ export function StatsCard({ title, value, change, trend, icon: Icon, color = "bl
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card className="overflow-hidden h-full">
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium text-muted-foreground">
|
<CardTitle className="text-sm font-medium text-muted-foreground line-clamp-2 break-words flex-1 pr-2">
|
||||||
{title}
|
{title}
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
<div className={`p-2 rounded-lg ${colorStyles[color]}`}>
|
<div className={`p-2 rounded-lg ${colorStyles[color]} flex-shrink-0`}>
|
||||||
<Icon size={16} />
|
<Icon size={16} />
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="overflow-hidden">
|
||||||
<div className="text-2xl font-bold">{value}</div>
|
<div className="text-2xl font-bold line-clamp-2 break-words">{value}</div>
|
||||||
{change && (
|
{change && (
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
<p className="text-xs text-muted-foreground mt-1 line-clamp-1 break-words">
|
||||||
<span
|
<span
|
||||||
className={`font-medium ${trend === "up"
|
className={`font-medium ${trend === "up"
|
||||||
? "text-green-600"
|
? "text-green-600"
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const Card = React.forwardRef<
|
|||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-xl border bg-card text-card-foreground shadow",
|
"rounded-xl border bg-card text-card-foreground shadow overflow-hidden",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
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 Recommendation {
|
interface Recommendation {
|
||||||
|
|||||||
@ -265,42 +265,42 @@ export function UserGrid({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="w-full overflow-hidden">
|
||||||
<div className="flex justify-between items-center mb-4">
|
<div className="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-4 mb-4">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search users..."
|
placeholder="Search users..."
|
||||||
className="border border-gray-300 rounded px-4 py-2"
|
className="border border-gray-300 dark:border-slate-600 dark:bg-slate-700 dark:text-white rounded px-4 py-2 flex-1 sm:flex-none w-full sm:w-auto"
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={(e) => setSearchQuery(e.target.value)}
|
onChange={(e) => setSearchQuery(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2 flex-wrap">
|
||||||
<button
|
<button
|
||||||
className="bg-green-500 text-white px-4 py-2 rounded disabled:opacity-50"
|
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded disabled:opacity-50 text-sm"
|
||||||
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="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded disabled:opacity-50 text-sm"
|
||||||
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="bg-orange-500 hover:bg-orange-600 text-white px-4 py-2 rounded disabled:opacity-50 text-sm"
|
||||||
onClick={handleBulkDelete}
|
onClick={handleBulkDelete}
|
||||||
disabled={selectedUsers.length === 0}
|
disabled={selectedUsers.length === 0}
|
||||||
>
|
>
|
||||||
Bulk Delete
|
Bulk
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="ag-theme-alpine"
|
className="ag-theme-alpine dark:ag-theme-alpine-dark w-full overflow-hidden rounded-lg"
|
||||||
style={{ height: "600px", width: "100%" }}
|
style={{ height: "400px", width: "100%" }}
|
||||||
>
|
>
|
||||||
<AgGridReact<User> {...gridOptions} ref={gridRef} />
|
<AgGridReact<User> {...gridOptions} ref={gridRef} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user