update
This commit is contained in:
315
Frontend/src/shared/components/SidebarStaff.tsx
Normal file
315
Frontend/src/shared/components/SidebarStaff.tsx
Normal file
@@ -0,0 +1,315 @@
|
||||
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 '../../features/notifications/contexts/ChatNotificationContext';
|
||||
import { useResponsive } from '../../hooks';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
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, isDesktop } = useResponsive();
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
await logout();
|
||||
navigate('/');
|
||||
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 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;
|
||||
|
||||
Reference in New Issue
Block a user