626 lines
20 KiB
TypeScript
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;
|