315 lines
10 KiB
TypeScript
315 lines
10 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
|
import {
|
|
LayoutDashboard,
|
|
FileText,
|
|
BarChart3,
|
|
ChevronLeft,
|
|
ChevronRight,
|
|
LogIn,
|
|
LogOut,
|
|
Menu,
|
|
X,
|
|
CreditCard,
|
|
MessageCircle,
|
|
Award,
|
|
Users,
|
|
Wrench
|
|
} from 'lucide-react';
|
|
import useAuthStore from '../../store/useAuthStore';
|
|
import { useChatNotifications } from '../../contexts/ChatNotificationContext';
|
|
import { useResponsive } from '../../hooks';
|
|
|
|
interface SidebarStaffProps {
|
|
isCollapsed?: boolean;
|
|
onToggle?: () => void;
|
|
}
|
|
|
|
const SidebarStaff: React.FC<SidebarStaffProps> = ({
|
|
isCollapsed: controlledCollapsed,
|
|
onToggle
|
|
}) => {
|
|
const [internalCollapsed, setInternalCollapsed] = useState(false);
|
|
const [isMobileOpen, setIsMobileOpen] = useState(false);
|
|
const location = useLocation();
|
|
const navigate = useNavigate();
|
|
const { logout } = useAuthStore();
|
|
const { unreadCount } = useChatNotifications();
|
|
const { isMobile, isTablet, isDesktop } = useResponsive();
|
|
|
|
const handleLogout = async () => {
|
|
try {
|
|
await logout();
|
|
navigate('/');
|
|
if (isMobile) {
|
|
setIsMobileOpen(false);
|
|
}
|
|
} catch (error) {
|
|
console.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 menuItems = [
|
|
{
|
|
path: '/staff/dashboard',
|
|
icon: LayoutDashboard,
|
|
label: 'Dashboard'
|
|
},
|
|
{
|
|
path: '/staff/bookings',
|
|
icon: FileText,
|
|
label: 'Bookings'
|
|
},
|
|
{
|
|
path: '/staff/reception',
|
|
icon: LogIn,
|
|
label: 'Reception'
|
|
},
|
|
{
|
|
path: '/staff/payments',
|
|
icon: CreditCard,
|
|
label: 'Payments'
|
|
},
|
|
{
|
|
path: '/staff/loyalty',
|
|
icon: Award,
|
|
label: 'Loyalty Program'
|
|
},
|
|
{
|
|
path: '/staff/guest-profiles',
|
|
icon: Users,
|
|
label: 'Guest Profiles'
|
|
},
|
|
{
|
|
path: '/staff/advanced-rooms',
|
|
icon: Wrench,
|
|
label: 'Room Management'
|
|
},
|
|
{
|
|
path: '/staff/chats',
|
|
icon: MessageCircle,
|
|
label: 'Chat Support'
|
|
},
|
|
{
|
|
path: '/staff/reports',
|
|
icon: BarChart3,
|
|
label: 'Reports'
|
|
},
|
|
];
|
|
|
|
const isActive = (path: string) => {
|
|
|
|
if (location.pathname === path) return true;
|
|
|
|
return location.pathname.startsWith(`${path}/`);
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{}
|
|
{isMobile && (
|
|
<button
|
|
onClick={handleMobileToggle}
|
|
className="fixed top-4 left-4 z-50 lg:hidden p-3 bg-gradient-to-r from-blue-900 to-blue-800 text-white rounded-xl shadow-2xl border border-blue-700 hover:from-blue-800 hover:to-blue-700 transition-all duration-200"
|
|
aria-label="Toggle menu"
|
|
>
|
|
{isMobileOpen ? (
|
|
<X className="w-6 h-6" />
|
|
) : (
|
|
<Menu className="w-6 h-6" />
|
|
)}
|
|
</button>
|
|
)}
|
|
|
|
{}
|
|
{isMobile && isMobileOpen && (
|
|
<div
|
|
className="fixed inset-0 bg-black/60 backdrop-blur-sm z-40 lg:hidden"
|
|
onClick={handleMobileToggle}
|
|
/>
|
|
)}
|
|
|
|
{}
|
|
<aside
|
|
className={`
|
|
fixed lg:static inset-y-0 left-0 z-40
|
|
bg-gradient-to-b from-blue-900 via-blue-800 to-blue-900
|
|
text-white shadow-2xl
|
|
transition-all duration-300 ease-in-out flex flex-col
|
|
${isMobile
|
|
? (isMobileOpen ? 'translate-x-0' : '-translate-x-full')
|
|
: ''
|
|
}
|
|
${!isMobile && (isCollapsed ? 'w-20' : 'w-72')}
|
|
${isMobile ? 'w-72' : ''}
|
|
border-r border-blue-700/50
|
|
`}
|
|
>
|
|
{}
|
|
<div className="p-6 border-b border-blue-700/50 flex items-center justify-between bg-gradient-to-r from-blue-800/50 to-blue-900/50 backdrop-blur-sm">
|
|
{!isCollapsed && (
|
|
<div className="flex items-center gap-3">
|
|
<div className="h-1 w-12 bg-gradient-to-r from-blue-400 to-blue-600 rounded-full"></div>
|
|
<h2 className="text-xl font-bold bg-gradient-to-r from-blue-100 to-blue-200 bg-clip-text text-transparent">
|
|
Staff Panel
|
|
</h2>
|
|
</div>
|
|
)}
|
|
{isCollapsed && !isMobile && (
|
|
<div className="w-full flex justify-center">
|
|
<div className="h-8 w-8 bg-gradient-to-br from-blue-400 to-blue-600 rounded-xl flex items-center justify-center shadow-lg">
|
|
<span className="text-white font-bold text-sm">S</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
{!isMobile && (
|
|
<button
|
|
onClick={handleToggle}
|
|
className="p-2.5 rounded-xl bg-blue-800/50 hover:bg-blue-700/50 border border-blue-700/50 hover:border-blue-500/50 transition-all duration-200 ml-auto shadow-lg hover:shadow-xl"
|
|
aria-label="Toggle sidebar"
|
|
>
|
|
{isCollapsed ? (
|
|
<ChevronRight className="w-5 h-5 text-blue-200" />
|
|
) : (
|
|
<ChevronLeft className="w-5 h-5 text-blue-200" />
|
|
)}
|
|
</button>
|
|
)}
|
|
</div>
|
|
|
|
{}
|
|
<nav className="flex-1 overflow-y-auto py-4 px-3 custom-scrollbar">
|
|
<ul className="space-y-2">
|
|
{menuItems.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-200 group relative
|
|
${active
|
|
? 'bg-gradient-to-r from-blue-500/20 to-blue-600/20 text-blue-100 shadow-lg border border-blue-500/30'
|
|
: 'text-blue-300 hover:bg-blue-800/50 hover:text-blue-100 border border-transparent hover:border-blue-700/50'
|
|
}
|
|
${isCollapsed && !isMobile ? 'justify-center' : ''}
|
|
`}
|
|
title={isCollapsed && !isMobile ? item.label : undefined}
|
|
>
|
|
{active && (
|
|
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-8 bg-gradient-to-b from-blue-400 to-blue-600 rounded-r-full"></div>
|
|
)}
|
|
<Icon className={`
|
|
flex-shrink-0 transition-transform duration-200
|
|
${active ? 'text-blue-400' : 'text-blue-400 group-hover:text-blue-300'}
|
|
${isCollapsed && !isMobile ? 'w-6 h-6' : 'w-5 h-5'}
|
|
`} />
|
|
{(!isCollapsed || isMobile) && (
|
|
<span className={`
|
|
font-semibold transition-all duration-200
|
|
${active ? 'text-blue-100' : 'group-hover:text-blue-100'}
|
|
`}>
|
|
{item.label}
|
|
</span>
|
|
)}
|
|
{item.path === '/staff/chats' && unreadCount > 0 && (
|
|
<span className="ml-auto bg-red-500 text-white text-xs font-bold rounded-full w-5 h-5 flex items-center justify-center">
|
|
{unreadCount > 9 ? '9+' : unreadCount}
|
|
</span>
|
|
)}
|
|
{active && !isCollapsed && item.path !== '/staff/chats' && (
|
|
<div className="ml-auto w-2 h-2 bg-blue-400 rounded-full animate-pulse"></div>
|
|
)}
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</nav>
|
|
|
|
{}
|
|
<div className="p-4 border-t border-blue-700/50">
|
|
<button
|
|
onClick={handleLogout}
|
|
className={`
|
|
w-full flex items-center
|
|
space-x-3 px-4 py-3.5 rounded-xl
|
|
transition-all duration-200 group relative
|
|
text-blue-300 hover:bg-gradient-to-r hover:from-rose-600/20 hover:to-rose-700/20
|
|
hover:text-rose-100 border border-transparent hover:border-rose-500/30
|
|
${isCollapsed && !isMobile ? 'justify-center' : ''}
|
|
`}
|
|
title={isCollapsed && !isMobile ? 'Logout' : undefined}
|
|
>
|
|
<LogOut className={`
|
|
flex-shrink-0 transition-transform duration-200
|
|
text-blue-400 group-hover:text-rose-400 group-hover:rotate-12
|
|
${isCollapsed && !isMobile ? 'w-6 h-6' : 'w-5 h-5'}
|
|
`} />
|
|
{(!isCollapsed || isMobile) && (
|
|
<span className="font-semibold transition-all duration-200 group-hover:text-rose-100">
|
|
Logout
|
|
</span>
|
|
)}
|
|
</button>
|
|
</div>
|
|
|
|
{}
|
|
<div className="p-4 border-t border-blue-700/50 bg-gradient-to-r from-blue-800/50 to-blue-900/50 backdrop-blur-sm">
|
|
{(!isCollapsed || isMobile) ? (
|
|
<div className="text-xs text-blue-400 text-center space-y-1">
|
|
<p className="font-semibold text-blue-200/80">Staff Dashboard</p>
|
|
<p className="text-blue-500">
|
|
© {new Date().getFullYear()} Luxury Hotel
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div className="flex justify-center">
|
|
<div className="w-3 h-3 bg-gradient-to-br from-blue-400 to-blue-600 rounded-full shadow-lg animate-pulse"></div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</aside>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default SidebarStaff;
|
|
|