va-tracker/src/ui/layout/DashboardLayout.tsx

180 lines
5.8 KiB
TypeScript

import React, { useState, useEffect } from 'react'
import { Outlet, NavLink, useNavigate, useLocation } from 'react-router-dom'
import { Bell, Menu, LogOut, User, Settings, Home, FolderKanban, X, CalendarDays } from 'lucide-react'
interface User {
email: string;
password: string;
}
interface DashboardLayoutProps {
user: User | null;
setUser: React.Dispatch<React.SetStateAction<User | null>>;
}
const DashboardLayout: React.FC<DashboardLayoutProps> = ({ setUser }) => {
const [isSidebarOpen, setSidebarOpen] = useState(true)
const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
const navigate = useNavigate()
const location = useLocation()
// ✅ Handle responsive behavior properly
useEffect(() => {
const handleResize = () => {
const mobile = window.innerWidth < 768
setIsMobile(mobile)
setSidebarOpen(!mobile) // open on desktop, closed on mobile
}
handleResize()
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
const handleLogout = () => {
localStorage.removeItem('user')
setUser(null)
navigate('/login')
}
const toggleSidebar = () => setSidebarOpen(!isSidebarOpen)
const closeSidebarOnMobile = () => {
if (isMobile) setSidebarOpen(false)
}
const menuItems = [
{ label: 'Dashboard', path: '/dashboard', icon: <Home size={18} />, exact: true },
{ label: 'Projects', path: '/dashboard/projects', icon: <FolderKanban size={18} /> },
{ label: 'Leave & Adjustments', path: '/dashboard/leave-adjustments', icon: <CalendarDays size={18} /> },
]
return (
<div className="flex h-screen bg-base-200 overflow-hidden">
{/* Sidebar */}
<aside
className={`
fixed md:relative inset-y-0 left-0 z-40 transform py-1
bg-base-100 border-r border-t border-base-300 transition-all duration-300 ease-in-out
${isMobile
? isSidebarOpen
? 'translate-x-0 w-64'
: '-translate-x-full w-64'
: isSidebarOpen
? 'w-64'
: 'w-20'}
`}
>
{/* Header */}
<div className="p-4 text-xl font-bold flex justify-between items-center border-b border-base-300">
{isSidebarOpen ? (
<span className="whitespace-nowrap">VA Tracker</span>
) : (
<span className="text-primary font-extrabold">VA</span>
)}
{isMobile && (
<button
onClick={toggleSidebar}
className="btn btn-ghost btn-sm md:hidden"
aria-label="Close sidebar"
>
<X size={20} />
</button>
)}
</div>
{/* Menu */}
<ul className="menu px-2 py-4">
{menuItems.map((item) => (
<li key={item.label}>
<NavLink
to={item.path}
end={item.exact}
className={({ isActive }) =>
`flex items-center gap-3 my-1 font-medium transition-all duration-150 ${
isActive
? 'bg-primary text-primary-content rounded-lg'
: 'hover:bg-base-300 rounded-lg'
}`
}
onClick={closeSidebarOnMobile}
>
<div className="min-w-[24px] flex justify-center">{item.icon}</div>
{isSidebarOpen && <span>{item.label}</span>}
</NavLink>
</li>
))}
</ul>
</aside>
{/* Overlay for mobile */}
{isMobile && isSidebarOpen && (
<div
className="fixed inset-0 bg-black/20 backdrop-blur-[1px] z-30 md:hidden"
onClick={closeSidebarOnMobile}
></div>
)}
{/* Main Content */}
<div
className={`flex flex-col flex-1 transition-all duration-300 ease-in-out bg-base-200 ${
!isMobile ? (isSidebarOpen ? 'md:ml-0' : 'md:ml-0') : ''
}`}
>
{/* Navbar */}
<nav className="navbar bg-base-100 border-b border-t border-base-300 p-3 sticky top-0 z-30 flex justify-between items-center">
<div className="flex items-center">
<button
onClick={toggleSidebar}
className="btn btn-ghost btn-square"
aria-label="Toggle sidebar"
>
<Menu size={20} />
</button>
<span className="text-lg font-semibold ml-2 md:hidden">
{location.pathname.includes('projects') ? 'Projects' : 'Dashboard'}
</span>
</div>
<div className="flex items-center gap-2">
<button className="btn btn-ghost btn-circle">
<Bell size={20} />
</button>
<div className="dropdown dropdown-end">
<label tabIndex={0} className="btn btn-ghost btn-circle avatar">
<div className="w-10 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
<User size={22} />
</div>
</label>
<ul
tabIndex={0}
className="mt-3 p-2 shadow menu menu-sm dropdown-content bg-base-200 rounded-box w-52"
>
<li>
<a>
<Settings size={16} /> Settings
</a>
</li>
<li>
<a onClick={handleLogout} className="text-error">
<LogOut size={16} /> Logout
</a>
</li>
</ul>
</div>
</div>
</nav>
{/* Page Content */}
<main className="flex-1 overflow-y-auto p-4 md:px-6 no-scrollbar">
<Outlet />
</main>
</div>
</div>
)
}
export default DashboardLayout