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

203 lines
6.2 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,
SettingsIcon,
TimerIcon,
HelpCircleIcon,
} 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();
useEffect(() => {
const handleResize = () => {
const mobile = window.innerWidth < 768;
setIsMobile(mobile);
setSidebarOpen(!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: "PTO", path: "/dashboard/leave-adjustments", icon: <CalendarDays size={18} /> },
{ label: "Timesheet", path: "/dashboard/timesheet", icon: <TimerIcon size={18} /> },
{ label: "Settings", path: "/dashboard/settings", icon: <SettingsIcon size={18} /> },
{ label: "Help & Support", path: "/dashboard/help-support", icon: <HelpCircleIcon 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
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-50"
: "w-20"
}
`}
>
{/* Sidebar Header */}
<div className="h-14 text-xl font-bold flex justify-between items-center border-b border-base-300">
{isSidebarOpen ? (
<span className="whitespace-nowrap mx-auto">VA Tracker</span>
) : (
<span className="text-primary font-extrabold mx-auto">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>
{/* Mobile overlay */}
{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 bg-base-200 transition-all duration-300 ease-in-out">
{/* Navbar */}
<nav className="h-14 bg-base-100 border-b border-t border-base-300 px-4 flex justify-between items-center sticky top-0 z-30">
{/* Left */}
<div className="flex items-center gap-2">
<button
onClick={toggleSidebar}
className="btn btn-ghost btn-square"
aria-label="Toggle sidebar"
>
<Menu size={20} />
</button>
<span className="text-lg font-semibold md:hidden">
{location.pathname.includes("projects")
? "Projects"
: location.pathname.includes("leave-adjustments")
? "Leave & Adjustments"
: "Dashboard"}
</span>
</div>
{/* Right */}
<div className="flex items-center gap-1">
<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-8 h-8 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2 flex justify-center items-center">
<User size={18} className="text-base-content" />
</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;