361 lines
14 KiB
TypeScript
361 lines
14 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useState } from "react"
|
|
import { useNavigate } from "react-router-dom"
|
|
import { motion } from "framer-motion"
|
|
import {
|
|
Users,
|
|
Database,
|
|
FileText,
|
|
Settings,
|
|
BarChart2,
|
|
Calendar,
|
|
Clock,
|
|
PlusCircle,
|
|
Search,
|
|
User,
|
|
Bell,
|
|
Home,
|
|
} from "lucide-react"
|
|
import { Link } from "react-router-dom"
|
|
|
|
|
|
export default function AdminDashboard() {
|
|
const [isFirstLoad, setIsFirstLoad] = useState(true)
|
|
const [isProfileDropdownOpen, setIsProfileDropdownOpen] = useState(false)
|
|
const navigate = useNavigate()
|
|
|
|
useEffect(() => {
|
|
// Check if user is authenticated
|
|
const token = localStorage.getItem("adminToken")
|
|
if (!token) {
|
|
navigate("/admin/login")
|
|
return
|
|
}
|
|
|
|
// Check if we're navigating from another admin page
|
|
const hasVisitedAdmin = localStorage.getItem("adminNavigation")
|
|
|
|
// Skip loading if we're navigating from another admin page
|
|
if (hasVisitedAdmin) {
|
|
setIsFirstLoad(false)
|
|
} else {
|
|
// Set flag to indicate we've visited an admin page
|
|
localStorage.setItem("adminNavigation", "true")
|
|
|
|
// Only show loading state on first load
|
|
if (isFirstLoad) {
|
|
// Simulate loading dashboard data
|
|
const timer = setTimeout(() => {
|
|
setIsFirstLoad(false)
|
|
|
|
// Show welcome toast
|
|
|
|
}, 500) // Reduced loading time for better UX
|
|
|
|
return () => clearTimeout(timer)
|
|
}
|
|
}
|
|
}, [isFirstLoad, navigate])
|
|
|
|
const handleLogout = () => {
|
|
localStorage.removeItem("adminToken")
|
|
localStorage.removeItem("adminNavigation") // Clear navigation flag on logout
|
|
|
|
navigate("/admin/login")
|
|
}
|
|
|
|
|
|
// If it's the first load, show a loading state
|
|
if (isFirstLoad) {
|
|
return (
|
|
<div className="min-h-screen flex items-center justify-center bg-[#E8DCCA]/10">
|
|
<div className="text-center">
|
|
<div className="w-16 h-16 border-4 border-[#9B2335] border-t-transparent rounded-full animate-spin mx-auto"></div>
|
|
<p className="mt-4 text-gray-600">Loading dashboard...</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen flex bg-[#E8DCCA]/10">
|
|
{/* Sidebar */}
|
|
<div className="w-64 bg-[#1A2A57] text-white">
|
|
<div className="p-4 border-b border-[#1A2A57]/30">
|
|
<h2 className="text-xl font-serif font-bold">Italian Migrants</h2>
|
|
<p className="text-sm text-white/70">Northern Territory DB</p>
|
|
</div>
|
|
|
|
<nav className="mt-6 px-4">
|
|
<div className="space-y-1">
|
|
<Link to="/admin" className="flex items-center px-4 py-3 text-white bg-[#1A2A57]/40 rounded-md">
|
|
<Home className="h-5 w-5 mr-3" />
|
|
Dashboard
|
|
</Link>
|
|
<Link
|
|
to="/admin/migrants"
|
|
className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md"
|
|
>
|
|
<User className="h-5 w-5 mr-3" />
|
|
Migrants
|
|
</Link>
|
|
<Link to="#" className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md">
|
|
<FileText className="h-5 w-5 mr-3" />
|
|
Reports
|
|
</Link>
|
|
<Link to="#" className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md">
|
|
<Database className="h-5 w-5 mr-3" />
|
|
Database
|
|
</Link>
|
|
<Link
|
|
to="/admin/settings/profile"
|
|
className="flex items-center px-4 py-3 text-white/80 hover:bg-[#1A2A57]/40 rounded-md"
|
|
>
|
|
<Settings className="h-5 w-5 mr-3" />
|
|
Settings
|
|
</Link>
|
|
</div>
|
|
</nav>
|
|
|
|
<div className="absolute bottom-0 w-64 p-4 border-t border-[#1A2A57]/30">
|
|
<div className="flex items-center">
|
|
<div className="h-8 w-8 rounded-full bg-white text-[#1A2A57] flex items-center justify-center">
|
|
<User className="h-5 w-5" />
|
|
</div>
|
|
<span className="ml-2 text-sm">Admin User</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Main content */}
|
|
<div className="flex-1 overflow-auto">
|
|
{/* Header */}
|
|
<div className="bg-white shadow-sm border-b border-gray-200">
|
|
<div className="flex justify-between items-center px-6 py-4">
|
|
<h1 className="text-xl font-medium text-[#1A2A57]">Admin Portal</h1>
|
|
<div className="flex items-center space-x-4">
|
|
<div className="relative">
|
|
<input
|
|
type="text"
|
|
placeholder="Search records..."
|
|
className="pl-10 pr-4 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-[#01796F] focus:border-[#01796F]"
|
|
/>
|
|
<Search className="absolute left-3 top-2.5 h-5 w-5 text-gray-400" />
|
|
</div>
|
|
|
|
{/* Notifications */}
|
|
<button
|
|
className="relative p-2 text-gray-500 hover:text-gray-700 focus:outline-none"
|
|
>
|
|
<Bell className="h-5 w-5" />
|
|
<span className="absolute top-0 right-0 h-2 w-2 rounded-full bg-[#9B2335]"></span>
|
|
</button>
|
|
|
|
{/* Profile dropdown */}
|
|
<div className="relative">
|
|
<button
|
|
className="h-8 w-8 rounded-full bg-[#9B2335] text-white flex items-center justify-center focus:outline-none"
|
|
onClick={() => setIsProfileDropdownOpen(!isProfileDropdownOpen)}
|
|
>
|
|
<User className="h-4 w-4" />
|
|
</button>
|
|
|
|
{isProfileDropdownOpen && (
|
|
<motion.div
|
|
className="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-10"
|
|
initial={{ opacity: 0, y: -10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<Link
|
|
to="/admin/settings/profile"
|
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={() => setIsProfileDropdownOpen(false)}
|
|
>
|
|
Your Profile
|
|
</Link>
|
|
<Link
|
|
to="#"
|
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
|
|
>
|
|
Settings
|
|
</Link>
|
|
<button
|
|
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={handleLogout}
|
|
>
|
|
Sign out
|
|
</button>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Dashboard content */}
|
|
<div className="p-6">
|
|
{/* Stats cards */}
|
|
<motion.div
|
|
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-8"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.2 }}
|
|
>
|
|
<div className="bg-white rounded-lg shadow p-6 border-t-4 border-[#9B2335]">
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Total Migrants</p>
|
|
<h3 className="text-3xl font-bold mt-1">256</h3>
|
|
</div>
|
|
<div className="p-2 bg-[#9B2335]/10 rounded-md">
|
|
<Users className="h-6 w-6 text-[#9B2335]" />
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 text-sm text-green-600">+12 this month</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow p-6 border-t-4 border-[#01796F]">
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Total Records</p>
|
|
<h3 className="text-3xl font-bold mt-1">1,248</h3>
|
|
</div>
|
|
<div className="p-2 bg-[#01796F]/10 rounded-md">
|
|
<Database className="h-6 w-6 text-[#01796F]" />
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 text-sm text-green-600">+86 this month</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow p-6 border-t-4 border-[#FFC857]">
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Recent Updates</p>
|
|
<h3 className="text-3xl font-bold mt-1">24</h3>
|
|
</div>
|
|
<div className="p-2 bg-[#FFC857]/10 rounded-md">
|
|
<Clock className="h-6 w-6 text-[#FFC857]" />
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 text-sm text-blue-600">Last update: Today</div>
|
|
</div>
|
|
|
|
<div className="bg-white rounded-lg shadow p-6 border-t-4 border-[#1A2A57]">
|
|
<div className="flex justify-between items-start">
|
|
<div>
|
|
<p className="text-gray-500 text-sm">Pending Tasks</p>
|
|
<h3 className="text-3xl font-bold mt-1">8</h3>
|
|
</div>
|
|
<div className="p-2 bg-[#1A2A57]/10 rounded-md">
|
|
<Calendar className="h-6 w-6 text-[#1A2A57]" />
|
|
</div>
|
|
</div>
|
|
<div className="mt-4 text-sm text-orange-500">3 require attention</div>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Quick actions */}
|
|
<motion.div
|
|
className="bg-white rounded-lg shadow mb-8"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.3 }}
|
|
>
|
|
<div className="px-6 py-4 border-b border-gray-200">
|
|
<h2 className="text-lg font-medium">Quick Actions</h2>
|
|
</div>
|
|
<div className="p-6 grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<Link
|
|
to="/admin/migrants"
|
|
className="flex items-center justify-center px-4 py-3 bg-[#9B2335] text-white rounded-md hover:bg-[#9B2335]/90"
|
|
>
|
|
<PlusCircle className="h-5 w-5 mr-2" />
|
|
Add New Migrant
|
|
</Link>
|
|
<button
|
|
className="flex items-center justify-center px-4 py-3 bg-[#01796F] text-white rounded-md hover:bg-[#01796F]/90"
|
|
>
|
|
<FileText className="h-5 w-5 mr-2" />
|
|
Generate Report
|
|
</button>
|
|
<button
|
|
className="flex items-center justify-center px-4 py-3 bg-[#1A2A57] text-white rounded-md hover:bg-[#1A2A57]/90"
|
|
|
|
>
|
|
<Database className="h-5 w-5 mr-2" />
|
|
Import Records
|
|
</button>
|
|
</div>
|
|
</motion.div>
|
|
|
|
{/* Recent activity */}
|
|
<motion.div
|
|
className="bg-white rounded-lg shadow"
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ duration: 0.5, delay: 0.4 }}
|
|
>
|
|
<div className="px-6 py-4 border-b border-gray-200">
|
|
<h2 className="text-lg font-medium">Recent Activity</h2>
|
|
</div>
|
|
<div className="p-6">
|
|
<div className="space-y-6">
|
|
{[
|
|
{ action: "Added new migrant", user: "Admin", time: "10 minutes ago", type: "add" },
|
|
{ action: "Updated record #1248", user: "Admin", time: "2 hours ago", type: "update" },
|
|
{ action: "Generated monthly report", user: "System", time: "Yesterday", type: "report" },
|
|
{ action: "Deleted duplicate record", user: "Admin", time: "2 days ago", type: "delete" },
|
|
{ action: "Imported 15 new records", user: "Admin", time: "1 week ago", type: "import" },
|
|
].map((activity, index) => (
|
|
<motion.div
|
|
key={index}
|
|
className="flex items-start"
|
|
initial={{ opacity: 0, x: -20 }}
|
|
animate={{ opacity: 1, x: 0 }}
|
|
transition={{ duration: 0.3, delay: 0.5 + index * 0.1 }}
|
|
>
|
|
<div
|
|
className={`p-2 rounded-full mr-4 ${
|
|
activity.type === "add"
|
|
? "bg-green-100 text-green-600"
|
|
: activity.type === "update"
|
|
? "bg-blue-100 text-blue-600"
|
|
: activity.type === "delete"
|
|
? "bg-red-100 text-red-600"
|
|
: activity.type === "report"
|
|
? "bg-purple-100 text-purple-600"
|
|
: "bg-yellow-100 text-yellow-600"
|
|
}`}
|
|
>
|
|
{activity.type === "add" ? (
|
|
<PlusCircle className="h-5 w-5" />
|
|
) : activity.type === "update" ? (
|
|
<FileText className="h-5 w-5" />
|
|
) : activity.type === "delete" ? (
|
|
<Users className="h-5 w-5" />
|
|
) : activity.type === "report" ? (
|
|
<BarChart2 className="h-5 w-5" />
|
|
) : (
|
|
<Database className="h-5 w-5" />
|
|
)}
|
|
</div>
|
|
<div>
|
|
<p className="font-medium">{activity.action}</p>
|
|
<p className="text-sm text-gray-500">
|
|
By {activity.user} • {activity.time}
|
|
</p>
|
|
</div>
|
|
</motion.div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|