225 lines
9.4 KiB
TypeScript
225 lines
9.4 KiB
TypeScript
import { Home, Calendar, Users, CreditCard, HelpCircle, LogOut, ChevronDown, Settings, CalendarDays, Table2 } from "lucide-react";
|
|
import { motion } from "motion/react";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
|
|
import { useState } from "react";
|
|
|
|
interface SidebarProps {
|
|
activeItem: string;
|
|
onItemSelect: (item: string) => void;
|
|
}
|
|
|
|
export default function Sidebar({ activeItem, onItemSelect }: SidebarProps) {
|
|
const [isProfileOpen, setIsProfileOpen] = useState(false);
|
|
const [isBookingExpanded, setIsBookingExpanded] = useState(activeItem === 'booking-table' || activeItem === 'booking-calendar');
|
|
|
|
const navigationItems = [
|
|
{ id: 'dashboard', label: 'Dashboard', icon: Home },
|
|
{
|
|
id: 'booking-management',
|
|
label: 'Booking Management',
|
|
icon: Calendar,
|
|
subItems: [
|
|
{ id: 'booking-calendar', label: 'Calendar View', icon: CalendarDays },
|
|
{ id: 'booking-table', label: 'Table View', icon: Table2 }
|
|
]
|
|
},
|
|
{ id: 'staff', label: 'Staff Management', icon: Users },
|
|
{ id: 'redemptions', label: 'Redemptions', icon: CreditCard },
|
|
{ id: 'support', label: 'Support', icon: HelpCircle },
|
|
];
|
|
|
|
return (
|
|
<div className="fixed left-0 top-0 w-[280px] h-full bg-white border-r border-gray-200 flex flex-col z-50">
|
|
{/* Logo Section */}
|
|
<div className="p-6 border-b border-gray-100">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-8 h-8 bg-gradient-to-br from-blue-600 to-purple-600 rounded-lg flex items-center justify-center">
|
|
<span className="text-white font-bold text-sm">CC</span>
|
|
</div>
|
|
<span className="text-xl font-bold text-gray-900">CityCards</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Navigation Items */}
|
|
<nav className="flex-1 p-4">
|
|
<div className="space-y-2">
|
|
{navigationItems.map((item) => {
|
|
const Icon = item.icon;
|
|
const hasActiveSubItem = item.subItems && item.subItems.some(sub => activeItem === sub.id);
|
|
const isDirectlyActive = activeItem === item.id;
|
|
const isExpanded = item.id === 'booking-management' ? isBookingExpanded : false;
|
|
const hasSubItems = item.subItems && item.subItems.length > 0;
|
|
|
|
return (
|
|
<div key={item.id} className={`${
|
|
hasActiveSubItem || isDirectlyActive
|
|
? 'bg-slate-800 rounded-lg shadow-sm mx-2'
|
|
: isExpanded && hasSubItems
|
|
? 'bg-gray-100 rounded-lg mx-2'
|
|
: ''
|
|
}`}>
|
|
<motion.button
|
|
onClick={() => {
|
|
if (hasSubItems) {
|
|
if (item.id === 'booking-management') {
|
|
setIsBookingExpanded(!isBookingExpanded);
|
|
// Default to table view when expanding
|
|
if (!isBookingExpanded) {
|
|
onItemSelect('booking-table');
|
|
}
|
|
}
|
|
} else {
|
|
onItemSelect(item.id);
|
|
}
|
|
}}
|
|
className={`w-full flex items-center gap-3 px-3 py-3 text-left transition-all duration-200 ${
|
|
hasActiveSubItem || isDirectlyActive
|
|
? 'text-white'
|
|
: isExpanded && hasSubItems
|
|
? 'text-gray-700'
|
|
: 'text-gray-700 hover:bg-gray-50 hover:text-gray-900 rounded-lg mx-2'
|
|
}`}
|
|
whileHover={{ x: (hasActiveSubItem || isDirectlyActive) ? 0 : 4 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<Icon className={`h-5 w-5 ${
|
|
hasActiveSubItem
|
|
? 'text-white'
|
|
: isDirectlyActive
|
|
? 'text-white'
|
|
: isExpanded && hasSubItems
|
|
? 'text-gray-600'
|
|
: 'text-gray-500'
|
|
}`} />
|
|
<span className="font-medium">{item.label}</span>
|
|
|
|
{/* Expand/Collapse arrow for sub-items */}
|
|
{hasSubItems && (
|
|
<motion.div
|
|
animate={{ rotate: isExpanded ? 90 : 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
className="ml-auto"
|
|
>
|
|
<ChevronDown className={`h-4 w-4 ${
|
|
hasActiveSubItem
|
|
? 'text-white'
|
|
: isDirectlyActive
|
|
? 'text-white'
|
|
: isExpanded && hasSubItems
|
|
? 'text-gray-600'
|
|
: 'text-gray-400'
|
|
}`} />
|
|
</motion.div>
|
|
)}
|
|
|
|
|
|
</motion.button>
|
|
|
|
{/* Sub-items */}
|
|
{hasSubItems && (
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{
|
|
height: isExpanded ? 'auto' : 0,
|
|
opacity: isExpanded ? 1 : 0
|
|
}}
|
|
transition={{ duration: 0.2 }}
|
|
className="overflow-hidden"
|
|
>
|
|
<div className="px-4 pt-1 pb-3 space-y-2">
|
|
{item.subItems.map((subItem) => {
|
|
const SubIcon = subItem.icon;
|
|
const isSubActive = activeItem === subItem.id;
|
|
|
|
return (
|
|
<motion.button
|
|
key={subItem.id}
|
|
onClick={() => onItemSelect(subItem.id)}
|
|
className={`w-full flex items-center px-3 py-2 text-left transition-all duration-200 ${
|
|
isSubActive
|
|
? 'bg-white text-gray-900 rounded-lg shadow-sm'
|
|
: hasActiveSubItem
|
|
? 'text-gray-300 hover:text-white hover:bg-slate-700/50 rounded-lg'
|
|
: 'text-gray-600 hover:text-gray-800 hover:bg-gray-200/50 rounded-lg'
|
|
}`}
|
|
whileHover={{ x: isSubActive ? 0 : 2 }}
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<div className="w-1.5 h-1.5 rounded-full flex-shrink-0 mr-3">
|
|
{isSubActive && (
|
|
<motion.div
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
className="w-full h-full bg-blue-500 rounded-full"
|
|
/>
|
|
)}
|
|
</div>
|
|
<span className="text-sm font-medium">{subItem.label}</span>
|
|
</motion.button>
|
|
);
|
|
})}
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
</nav>
|
|
|
|
{/* Profile Section */}
|
|
<div className="p-4 border-t border-gray-100">
|
|
<motion.button
|
|
onClick={() => setIsProfileOpen(!isProfileOpen)}
|
|
className="w-full flex items-center gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors"
|
|
whileTap={{ scale: 0.98 }}
|
|
>
|
|
<Avatar className="h-10 w-10">
|
|
<AvatarImage src="/api/placeholder/40/40" alt="Profile" />
|
|
<AvatarFallback className="bg-gradient-to-br from-blue-600 to-purple-600 text-white">
|
|
KA
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="flex-1 text-left">
|
|
<p className="font-medium text-gray-900">Kassandra</p>
|
|
<p className="text-sm text-gray-500">Admin</p>
|
|
</div>
|
|
<motion.div
|
|
animate={{ rotate: isProfileOpen ? 180 : 0 }}
|
|
transition={{ duration: 0.2 }}
|
|
>
|
|
<ChevronDown className="h-4 w-4 text-gray-400" />
|
|
</motion.div>
|
|
</motion.button>
|
|
|
|
{/* Profile Dropdown */}
|
|
<motion.div
|
|
initial={{ height: 0, opacity: 0 }}
|
|
animate={{
|
|
height: isProfileOpen ? 'auto' : 0,
|
|
opacity: isProfileOpen ? 1 : 0
|
|
}}
|
|
transition={{ duration: 0.2 }}
|
|
className="overflow-hidden"
|
|
>
|
|
<div className="pt-2 space-y-1">
|
|
<motion.button
|
|
whileHover={{ x: 4 }}
|
|
className="w-full flex items-center gap-3 px-3 py-2 text-left text-gray-700 hover:bg-gray-50 rounded-lg transition-colors"
|
|
>
|
|
<Settings className="h-4 w-4 text-gray-500" />
|
|
<span className="text-sm">Account Settings</span>
|
|
</motion.button>
|
|
<motion.button
|
|
whileHover={{ x: 4 }}
|
|
className="w-full flex items-center gap-3 px-3 py-2 text-left text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
|
>
|
|
<LogOut className="h-4 w-4 text-red-500" />
|
|
<span className="text-sm">Sign Out</span>
|
|
</motion.button>
|
|
</div>
|
|
</motion.div>
|
|
</div>
|
|
</div>
|
|
);
|
|
} |