Files
Hotel-Booking/Frontend/src/shared/components/SidebarAdmin.tsx
Iliyan Angelov b818d645a9 updates
2025-12-07 20:36:17 +02:00

626 lines
20 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import {
LayoutDashboard,
Users,
Settings,
FileText,
BarChart3,
Globe,
ChevronLeft,
ChevronRight,
LogIn,
LogOut,
Menu,
X,
Award,
User,
Workflow,
CheckSquare,
Bell,
UserCheck,
Hotel,
Tag,
Package,
Shield,
Mail,
TrendingUp,
Building2,
Crown,
Star,
AlertCircle,
FileCheck,
ClipboardCheck,
CheckCircle2,
Download,
Webhook,
Key,
HardDrive,
Activity,
Calendar,
Boxes,
Monitor,
CreditCard,
MessageSquare
} from 'lucide-react';
import useAuthStore from '../../store/useAuthStore';
import { useResponsive } from '../../hooks';
import { logger } from '../utils/logger';
interface SidebarAdminProps {
isCollapsed?: boolean;
onToggle?: () => void;
}
interface MenuGroup {
title: string;
icon?: React.ComponentType<{ className?: string }>;
items: Array<{
path: string;
icon: React.ComponentType<{ className?: string }>;
label: string;
}>;
}
const SidebarAdmin: React.FC<SidebarAdminProps> = ({
isCollapsed: controlledCollapsed,
onToggle
}) => {
const [internalCollapsed, setInternalCollapsed] = useState(false);
const [isMobileOpen, setIsMobileOpen] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const { logout } = useAuthStore();
const { isMobile, isDesktop } = useResponsive();
const handleLogout = async () => {
try {
await logout();
// Logout function will redirect to homepage
if (isMobile) {
setIsMobileOpen(false);
}
} catch (error) {
logger.error('Logout error', error);
}
};
// Close mobile menu when screen becomes desktop
useEffect(() => {
if (isDesktop) {
setIsMobileOpen(false);
}
}, [isDesktop]);
const isCollapsed =
controlledCollapsed !== undefined
? controlledCollapsed
: internalCollapsed;
const handleToggle = () => {
if (onToggle) {
onToggle();
} else {
setInternalCollapsed(!internalCollapsed);
}
};
const handleMobileToggle = () => {
setIsMobileOpen(!isMobileOpen);
};
const handleLinkClick = () => {
if (isMobile) {
setIsMobileOpen(false);
}
};
const menuGroups: MenuGroup[] = [
{
title: 'Overview',
icon: LayoutDashboard,
items: [
{
path: '/admin/dashboard',
icon: LayoutDashboard,
label: 'Dashboard'
},
]
},
{
title: 'Operations',
icon: Building2,
items: [
{
path: '/admin/reception',
icon: LogIn,
label: 'Reception'
},
{
path: '/admin/advanced-rooms',
icon: Hotel,
label: 'Rooms & Housekeeping'
},
{
path: '/admin/services',
icon: Activity,
label: 'Services'
},
{
path: '/admin/inventory',
icon: Boxes,
label: 'Inventory'
},
{
path: '/admin/shifts',
icon: Calendar,
label: 'Staff Shifts'
},
]
},
{
title: 'Bookings & Finance',
icon: TrendingUp,
items: [
{
path: '/admin/bookings',
icon: Calendar,
label: 'All Bookings'
},
{
path: '/admin/payments',
icon: CreditCard,
label: 'Payments'
},
{
path: '/admin/invoices',
icon: FileText,
label: 'Invoices'
},
{
path: '/admin/promotions',
icon: Tag,
label: 'Promotions'
},
{
path: '/admin/financial-audit',
icon: FileCheck,
label: 'Financial Audit'
},
]
},
{
title: 'Analytics',
icon: BarChart3,
items: [
{
path: '/admin/analytics',
icon: BarChart3,
label: 'Reports & Analytics'
},
]
},
{
title: 'Users & Guests',
icon: Users,
items: [
{
path: '/admin/users',
icon: Users,
label: 'Users'
},
{
path: '/admin/guest-profiles',
icon: User,
label: 'Guest Profiles'
},
{
path: '/admin/group-bookings',
icon: UserCheck,
label: 'Group Bookings'
},
{
path: '/admin/loyalty',
icon: Award,
label: 'Loyalty Program'
},
{
path: '/admin/complaints',
icon: AlertCircle,
label: 'Complaints'
},
]
},
{
title: 'Products & Pricing',
icon: Tag,
items: [
{
path: '/admin/rate-plans',
icon: Tag,
label: 'Rate Plans'
},
{
path: '/admin/packages',
icon: Package,
label: 'Packages'
},
]
},
{
title: 'Marketing',
icon: Mail,
items: [
{
path: '/admin/email-campaigns',
icon: Mail,
label: 'Email Campaigns'
},
]
},
{
title: 'Communication',
icon: MessageSquare,
items: [
{
path: '/admin/team-chat',
icon: MessageSquare,
label: 'Team Chat'
},
]
},
{
title: 'Content Management',
icon: Globe,
items: [
{
path: '/admin/page-content',
icon: Globe,
label: 'Page Content'
},
{
path: '/admin/blog',
icon: FileText,
label: 'Blog Management'
},
{
path: '/admin/reviews',
icon: Star,
label: 'Reviews'
},
]
},
{
title: 'System',
icon: Settings,
items: [
{
path: '/admin/security',
icon: Shield,
label: 'Security'
},
{
path: '/admin/tasks',
icon: CheckSquare,
label: 'Tasks'
},
{
path: '/admin/workflows',
icon: Workflow,
label: 'Workflows'
},
{
path: '/admin/notifications',
icon: Bell,
label: 'Notifications'
},
{
path: '/admin/settings',
icon: Settings,
label: 'Settings'
},
{
path: '/admin/approvals',
icon: CheckCircle2,
label: 'Approvals'
},
{
path: '/admin/gdpr',
icon: Download,
label: 'GDPR & Compliance'
},
{
path: '/admin/webhooks',
icon: Webhook,
label: 'Webhooks'
},
{
path: '/admin/api-keys',
icon: Key,
label: 'API Keys'
},
{
path: '/admin/backups',
icon: HardDrive,
label: 'Backups'
},
]
},
{
title: 'Account',
icon: User,
items: [
{
path: '/admin/profile',
icon: User,
label: 'My Profile'
},
{
path: '/admin/sessions',
icon: Monitor,
label: 'Sessions'
},
]
},
];
const isActive = (path: string) => {
if (location.pathname === path) return true;
if (path === '/admin/settings' || path === '/admin/analytics' || path === '/admin/reception' || path === '/admin/advanced-rooms' || path === '/admin/page-content' || path === '/admin/loyalty') {
return location.pathname === path;
}
if (path === '/admin/reception' || path === '/admin/advanced-rooms') {
return location.pathname === path || location.pathname.startsWith(`${path}/`);
}
return location.pathname.startsWith(`${path}/`);
};
return (
<>
{/* Mobile Menu Button - Always visible on mobile screens */}
<button
onClick={handleMobileToggle}
className="fixed top-2 left-2 sm:top-3 sm:left-3 z-50 lg:hidden p-2.5 sm:p-3 bg-gradient-to-br from-amber-500 via-amber-600 to-amber-700 text-white rounded-xl sm:rounded-2xl shadow-2xl border border-amber-400/30 hover:from-amber-600 hover:via-amber-700 hover:to-amber-800 transition-all duration-300 backdrop-blur-sm hover:scale-110"
aria-label="Toggle menu"
>
{isMobileOpen ? (
<X className="w-6 h-6" />
) : (
<Menu className="w-6 h-6" />
)}
</button>
{/* Mobile Overlay */}
{isMobileOpen && (
<div
className="fixed inset-0 bg-black/80 backdrop-blur-md z-30 lg:hidden"
onClick={handleMobileToggle}
/>
)}
<aside
className={`
bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950
text-white shadow-2xl
transition-all duration-300 ease-in-out flex flex-col
border-r border-amber-500/20
overflow-hidden
${isMobile
? `fixed inset-y-0 left-0 z-40 w-80 ${isMobileOpen ? 'translate-x-0' : '-translate-x-full'}`
: `relative ${isCollapsed ? 'w-20' : 'w-80'} translate-x-0`
}
`}
>
{/* Luxury background pattern */}
<div className="absolute inset-0 opacity-[0.03] pointer-events-none">
<div className="absolute inset-0" style={{
backgroundImage: `radial-gradient(circle at 2px 2px, rgba(251, 191, 36, 0.4) 1px, transparent 0)`,
backgroundSize: '50px 50px'
}}></div>
</div>
{/* Animated gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-br from-amber-900/5 via-transparent to-amber-800/5 pointer-events-none"></div>
{/* Header */}
<div className="relative p-6 border-b border-amber-500/20 flex items-center justify-between bg-gradient-to-r from-amber-900/30 via-amber-800/20 to-transparent backdrop-blur-sm">
{!isCollapsed && (
<div className="flex items-center gap-4">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-amber-400 to-amber-600 rounded-2xl blur-lg opacity-60 animate-pulse"></div>
<div className="relative h-12 w-12 bg-gradient-to-br from-amber-500 via-amber-600 to-amber-700 rounded-2xl flex items-center justify-center shadow-2xl border border-amber-400/40">
<Crown className="w-6 h-6 text-white" />
<div className="absolute -top-1 -right-1 w-3 h-3 bg-gradient-to-br from-amber-300 to-amber-500 rounded-full shadow-lg animate-ping"></div>
</div>
</div>
<div>
<h2 className="text-lg font-bold bg-gradient-to-r from-amber-200 via-amber-100 to-amber-200 bg-clip-text text-transparent tracking-wide">
Admin Panel
</h2>
<p className="text-xs text-amber-300/60 font-medium mt-0.5">Luxury Hotel</p>
</div>
</div>
)}
{isCollapsed && !isMobile && (
<div className="w-full flex justify-center">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-amber-400 to-amber-600 rounded-2xl blur-lg opacity-60 animate-pulse"></div>
<div className="relative h-12 w-12 bg-gradient-to-br from-amber-500 via-amber-600 to-amber-700 rounded-2xl flex items-center justify-center shadow-2xl border border-amber-400/40">
<Crown className="w-6 h-6 text-white" />
</div>
</div>
</div>
)}
{!isMobile && (
<button
onClick={handleToggle}
className="p-2.5 rounded-xl bg-slate-800/70 hover:bg-amber-500/20 border border-slate-700/50 hover:border-amber-500/50 transition-all duration-300 ml-auto shadow-lg hover:shadow-xl backdrop-blur-sm hover:scale-110"
aria-label="Toggle sidebar"
>
{isCollapsed ? (
<ChevronRight className="w-5 h-5 text-amber-300" />
) : (
<ChevronLeft className="w-5 h-5 text-amber-300" />
)}
</button>
)}
</div>
{/* Navigation */}
<nav className="flex-1 overflow-y-auto py-6 px-4 custom-scrollbar relative">
<ul className="space-y-6">
{menuGroups.map((group, groupIndex) => {
return (
<li key={groupIndex} className="space-y-2">
{!isCollapsed && (
<div className="px-4 mb-3">
<div className="flex items-center gap-2.5">
{group.icon && (
<group.icon className="w-3.5 h-3.5 text-amber-400/70" />
)}
<span className="text-[10px] font-bold uppercase tracking-widest text-amber-300/40 letter-spacing-wider">
{group.title}
</span>
</div>
<div className="h-px bg-gradient-to-r from-amber-500/30 via-amber-500/15 to-transparent mt-2.5"></div>
</div>
)}
<ul className="space-y-1.5">
{group.items.map((item) => {
const Icon = item.icon;
const active = isActive(item.path);
return (
<li key={item.path}>
<Link
to={item.path}
onClick={handleLinkClick}
className={`
flex items-center
space-x-3 px-4 py-3.5 rounded-xl
transition-all duration-300 group relative overflow-hidden
${active
? 'bg-gradient-to-r from-amber-500/30 via-amber-600/25 to-amber-500/30 text-amber-50 shadow-xl shadow-amber-500/25 border border-amber-500/50'
: 'text-slate-300 hover:bg-gradient-to-r hover:from-slate-800/70 hover:via-slate-800/50 hover:to-slate-800/70 hover:text-amber-100 border border-transparent hover:border-amber-500/20'
}
${isCollapsed && !isMobile ? 'justify-center px-3' : ''}
`}
title={isCollapsed && !isMobile ? item.label : undefined}
>
{active && (
<>
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-12 bg-gradient-to-b from-amber-400 via-amber-500 to-amber-600 rounded-r-full shadow-lg shadow-amber-500/60"></div>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-amber-500/10 to-transparent"></div>
</>
)}
<div className={`
relative flex items-center justify-center z-10
${active
? 'bg-gradient-to-br from-amber-500/40 to-amber-600/30 p-2.5 rounded-xl shadow-lg'
: 'p-2.5 rounded-xl group-hover:bg-amber-500/15 transition-all duration-300'
}
`}>
<Icon className={`
flex-shrink-0 transition-all duration-300
${active
? 'text-amber-200 scale-110 drop-shadow-lg'
: 'text-slate-400 group-hover:text-amber-400 group-hover:scale-110'
}
${isCollapsed && !isMobile ? 'w-5 h-5' : 'w-4 h-4'}
`} />
</div>
{(!isCollapsed || isMobile) && (
<span className={`
font-medium text-sm transition-all duration-300 relative z-10
${active
? 'text-amber-50 font-semibold tracking-wide'
: 'group-hover:text-amber-100'
}
`}>
{item.label}
</span>
)}
{active && !isCollapsed && (
<div className="ml-auto relative z-10">
<div className="w-2 h-2 bg-amber-400 rounded-full animate-pulse shadow-lg shadow-amber-400/60"></div>
</div>
)}
</Link>
</li>
);
})}
</ul>
</li>
);
})}
</ul>
</nav>
{/* Footer */}
<div className="relative border-t border-amber-500/20 bg-gradient-to-r from-slate-900/60 to-slate-950/60 backdrop-blur-sm">
<div className="p-4">
<button
onClick={handleLogout}
className={`
w-full flex items-center
space-x-3 px-4 py-3.5 rounded-xl
transition-all duration-300 group relative overflow-hidden
text-slate-300 hover:bg-gradient-to-r hover:from-rose-600/25 hover:via-rose-700/20 hover:to-rose-600/25
hover:text-rose-100 border border-transparent hover:border-rose-500/40
${isCollapsed && !isMobile ? 'justify-center px-3' : ''}
`}
title={isCollapsed && !isMobile ? 'Logout' : undefined}
>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-rose-500/10 to-transparent opacity-0 group-hover:opacity-100 transition-opacity"></div>
<div className={`
relative flex items-center justify-center z-10
p-2.5 rounded-xl group-hover:bg-rose-500/15 transition-all duration-300
`}>
<LogOut className={`
flex-shrink-0 transition-all duration-300
text-slate-400 group-hover:text-rose-400 group-hover:rotate-12
${isCollapsed && !isMobile ? 'w-5 h-5' : 'w-4 h-4'}
`} />
</div>
{(!isCollapsed || isMobile) && (
<span className="font-medium text-sm transition-all duration-300 relative z-10 group-hover:text-rose-100">
Logout
</span>
)}
</button>
</div>
<div className="px-4 pb-4">
{(!isCollapsed || isMobile) ? (
<div className="text-xs text-center space-y-2.5 p-3.5 rounded-xl bg-gradient-to-r from-amber-900/30 to-amber-800/20 border border-amber-500/15 backdrop-blur-sm">
<div className="flex items-center justify-center gap-2">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-full blur-md opacity-50 animate-pulse"></div>
<div className="relative w-2 h-2 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-full shadow-lg"></div>
</div>
<p className="font-semibold text-amber-200/90 tracking-wide">System Active</p>
</div>
<p className="text-amber-300/50 text-[10px] font-medium tracking-wider">
© {new Date().getFullYear()} Luxury Hotel Management
</p>
</div>
) : (
<div className="flex justify-center">
<div className="relative">
<div className="absolute inset-0 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-full blur-md opacity-50 animate-pulse"></div>
<div className="relative w-3 h-3 bg-gradient-to-br from-emerald-400 to-emerald-600 rounded-full shadow-lg"></div>
</div>
</div>
)}
</div>
</div>
</aside>
</>
);
};
export default SidebarAdmin;