Files
Hotel-Booking/Frontend/src/shared/components/SidebarStaff.tsx
Iliyan Angelov 3d634b4fce updates
2025-12-04 01:07:34 +02:00

345 lines
11 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,
Bell,
Mail,
AlertTriangle,
TrendingUp
} 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();
// 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 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/guest-requests',
icon: Bell,
label: 'Guest Requests'
},
{
path: '/staff/guest-communication',
icon: Mail,
label: 'Communication'
},
{
path: '/staff/incidents-complaints',
icon: AlertTriangle,
label: 'Incidents & Complaints'
},
{
path: '/staff/upsells',
icon: TrendingUp,
label: 'Upsell Management'
},
{
path: '/staff/advanced-rooms',
icon: Wrench,
label: 'Room Management'
},
{
path: '/staff/chats',
icon: MessageCircle,
label: 'Chat Support'
},
{
path: '/staff/reports',
icon: BarChart3,
label: 'Reports'
},
{
path: '/staff/profile',
icon: Users,
label: 'My Profile'
},
];
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;