Yaltopia-Ticket-Admin/src/layouts/app-shell.tsx

244 lines
7.9 KiB
TypeScript

import { Outlet, Link, useLocation, useNavigate } from "react-router-dom"
import { useEffect, useState } from "react"
import {
LayoutDashboard,
Users,
FileText,
Settings,
Wrench,
Megaphone,
Shield,
BarChart3,
Activity,
Heart,
Search,
Bell,
LogOut,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import { cn } from "@/lib/utils"
import { authService } from "@/services"
import { toast } from "sonner"
interface User {
email: string
firstName?: string
lastName?: string
role: string
}
const adminNavigationItems = [
{ icon: LayoutDashboard, label: "Dashboard", path: "/admin/dashboard" },
{ icon: Users, label: "Users", path: "/admin/users" },
{ icon: FileText, label: "Logs", path: "/admin/logs" },
{ icon: Settings, label: "Settings", path: "/admin/settings" },
{ icon: Wrench, label: "Maintenance", path: "/admin/maintenance" },
{ icon: Megaphone, label: "Announcements", path: "/admin/announcements" },
{ icon: Activity, label: "Audit", path: "/admin/audit" },
{ icon: Shield, label: "Security", path: "/admin/security" },
{ icon: BarChart3, label: "Analytics", path: "/admin/analytics" },
{ icon: Heart, label: "System Health", path: "/admin/health" },
]
export function AppShell() {
const location = useLocation()
const navigate = useNavigate()
const [user, setUser] = useState<User | null>(null)
const [searchQuery, setSearchQuery] = useState("")
useEffect(() => {
const userStr = localStorage.getItem('user')
if (userStr) {
try {
setUser(JSON.parse(userStr))
} catch (error) {
console.error('Failed to parse user data:', error)
}
}
}, [])
const isActive = (path: string) => {
return location.pathname.startsWith(path)
}
const getPageTitle = () => {
const currentPath = location.pathname
const item = adminNavigationItems.find((item) =>
currentPath.startsWith(item.path)
)
return item?.label || "Admin Panel"
}
const handleLogout = async () => {
await authService.logout()
navigate('/login', { replace: true })
}
const handleSearch = (e: React.FormEvent) => {
e.preventDefault()
if (searchQuery.trim()) {
const currentPath = location.pathname
navigate(`${currentPath}?search=${encodeURIComponent(searchQuery)}`)
toast.success(`Searching for: ${searchQuery}`)
}
}
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setSearchQuery(e.target.value)
}
const handleNotificationClick = () => {
console.log('Notification button clicked')
navigate('/notifications')
}
const handleProfileClick = () => {
navigate('/admin/settings')
}
const getUserInitials = () => {
if (user?.firstName && user?.lastName) {
return `${user.firstName[0]}${user.lastName[0]}`.toUpperCase()
}
if (user?.email) {
return user.email.substring(0, 2).toUpperCase()
}
return 'AD'
}
const getUserDisplayName = () => {
if (user?.firstName && user?.lastName) {
return `${user.firstName} ${user.lastName}`
}
return user?.email || 'Admin User'
}
return (
<div className="flex h-screen bg-background">
{/* Sidebar */}
<aside className="w-64 bg-[#F8F8F8] flex flex-col border-r">
{/* Logo */}
<div className="p-6 flex items-center gap-2">
<div className="w-10 h-10 bg-primary rounded flex items-center justify-center">
<span className="text-primary-foreground font-bold text-lg">A</span>
</div>
<span className="text-foreground font-semibold text-lg">Admin Panel</span>
</div>
{/* Navigation */}
<nav className="flex-1 px-4 py-2 space-y-1 overflow-y-auto">
{adminNavigationItems.map((item) => {
const Icon = item.icon
return (
<Link
key={item.path}
to={item.path}
className={cn(
"flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-colors",
isActive(item.path)
? "bg-primary text-primary-foreground"
: "text-foreground/70 hover:bg-accent hover:text-foreground"
)}
>
<Icon className="w-5 h-5" />
{item.label}
</Link>
)
})}
</nav>
{/* User Section */}
<div className="p-4 border-t">
<div className="flex items-center gap-3 mb-3">
<Avatar>
<AvatarFallback>{getUserInitials()}</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="text-sm font-medium truncate">{getUserDisplayName()}</p>
<p className="text-xs text-muted-foreground truncate">{user?.email || 'admin@example.com'}</p>
</div>
</div>
<Button
variant="outline"
size="sm"
className="w-full"
onClick={handleLogout}
>
<LogOut className="w-4 h-4 mr-2" />
Logout
</Button>
</div>
</aside>
{/* Main Content */}
<div className="flex-1 flex flex-col overflow-hidden">
{/* Top Header */}
<header className="h-16 border-b bg-background flex items-center justify-between px-6">
<h1 className="text-2xl font-bold">{getPageTitle()}</h1>
<div className="flex items-center gap-4">
<form onSubmit={handleSearch} className="relative">
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
<Input
placeholder="Quick Search..."
className="pl-10 w-64"
value={searchQuery}
onChange={handleSearchChange}
/>
</form>
<Button variant="ghost" size="icon" className="relative" onClick={handleNotificationClick}>
<Bell className="w-5 h-5" />
<span className="absolute top-1 right-1 w-2 h-2 bg-red-500 rounded-full pointer-events-none" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="rounded-full">
<Avatar>
<AvatarFallback>{getUserInitials()}</AvatarFallback>
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-56">
<DropdownMenuLabel>
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium">{getUserDisplayName()}</p>
<p className="text-xs text-muted-foreground">{user?.email}</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleProfileClick}>
<Settings className="w-4 h-4 mr-2" />
Profile Settings
</DropdownMenuItem>
<DropdownMenuItem onClick={() => navigate('/notifications')}>
<Bell className="w-4 h-4 mr-2" />
Notifications
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout}>
<LogOut className="w-4 h-4 mr-2" />
Logout
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</header>
{/* Page Content */}
<main className="flex-1 overflow-auto bg-background p-6">
<Outlet />
</main>
</div>
</div>
)
}