You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

243 lines
11 KiB
TypeScript

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { cn } from "@/lib/utils";
import {
LayoutDashboard,
Receipt,
Clock,
FileText,
UserCheck,
BookOpen,
PieChart,
BarChart3,
Settings,
LogOut,
ChevronRight,
Wallet,
Shield,
Briefcase,
Layers
} from "lucide-react";
import { Button } from "@/components/ui/button";
import { signOut } from "next-auth/react";
import { motion, AnimatePresence } from "framer-motion";
interface SidebarItem {
title: string;
href: string;
icon: any;
badge?: string | number;
}
interface SidebarGroup {
label: string;
items: SidebarItem[];
}
const workerGroups: SidebarGroup[] = [
{
label: "Main",
items: [
{ title: "Dashboard", href: "/me/dashboard", icon: LayoutDashboard },
]
},
{
label: "Activities",
items: [
{ title: "Reimbursements", href: "/me/reimbursements", icon: Receipt, badge: "3" },
{ title: "Overtime", href: "/me/overtime", icon: Clock },
]
},
{
label: "Documents",
items: [
{ title: "Payslips", href: "/me/payslips", icon: FileText },
]
}
];
const adminGroups: SidebarGroup[] = [
{
label: "Management",
items: [
{ title: "Dashboard", href: "/admin/dashboard", icon: LayoutDashboard },
{ title: "Approvals", href: "/admin/approvals", icon: UserCheck, badge: "12" },
]
},
{
label: "Accounting",
items: [
{ title: "Company Ledger", href: "/admin/ledger", icon: BookOpen },
{ title: "Budgeting", href: "/admin/budgeting", icon: PieChart },
]
},
{
label: "Analytics",
items: [
{ title: "Reports", href: "/admin/reports", icon: BarChart3 },
]
}
];
interface SidebarProps {
portal: "worker" | "admin";
user: {
name?: string | null;
email?: string | null;
role?: string;
};
}
export function Sidebar({ portal, user }: SidebarProps) {
const pathname = usePathname();
const isAdmin = portal === 'admin';
const groups = isAdmin ? adminGroups : workerGroups;
// Role-based theme configuration
const theme = {
accent: isAdmin ? "emerald" : "indigo",
gradient: isAdmin
? "from-emerald-600 to-teal-600"
: "from-indigo-600 to-violet-600",
bgActive: isAdmin
? "bg-emerald-50 text-emerald-700 dark:bg-emerald-900/20 dark:text-emerald-400"
: "bg-indigo-50 text-indigo-700 dark:bg-indigo-900/20 dark:text-indigo-400",
iconActive: isAdmin ? "text-emerald-600 dark:text-emerald-400" : "text-indigo-600 dark:text-indigo-400",
glow: isAdmin ? "shadow-emerald-200/50" : "shadow-indigo-200/50"
};
return (
<div className="w-72 h-screen flex flex-col bg-white dark:bg-neutral-900 border-r border-neutral-100 dark:border-neutral-800 shadow-sm z-50">
{/* Brand Section */}
<div className="p-8 pb-6">
<div className="flex items-center gap-4 group cursor-pointer">
<div className={cn(
"w-12 h-12 rounded-2xl bg-gradient-to-br flex items-center justify-center shadow-xl transition-all duration-500 group-hover:scale-110 group-hover:rotate-3",
theme.gradient,
theme.glow
)}>
<Wallet className="w-6 h-6 text-white" />
</div>
<div>
<h1 className="font-extrabold text-xl tracking-tight text-neutral-900 dark:text-white leading-none">
Finance<span className={isAdmin ? "text-emerald-600" : "text-indigo-600"}>TAM</span>
</h1>
<div className="flex items-center gap-1.5 mt-1.5 text-[10px] font-bold uppercase tracking-[0.15em] text-neutral-400">
{isAdmin ? <Shield className="w-3 h-3" /> : <Layers className="w-3 h-3" />}
{user.role || 'Portal'}
</div>
</div>
</div>
</div>
{/* Navigation Groups */}
<div className="flex-1 overflow-y-auto px-4 py-6 space-y-8 scrollbar-none">
{groups.map((group, groupIdx) => (
<div key={groupIdx} className="space-y-3">
<h3 className="px-4 text-[10px] font-black uppercase tracking-[0.2em] text-neutral-400/80">
{group.label}
</h3>
<div className="space-y-1">
{group.items.map((item) => {
const isActive = pathname === item.href;
return (
<Link
key={item.href}
href={item.href}
className={cn(
"relative flex items-center gap-3.5 px-4 py-3.5 rounded-2xl text-[13px] font-bold transition-all duration-300 group",
isActive
? theme.bgActive
: "text-neutral-500 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100 hover:bg-neutral-50 dark:hover:bg-neutral-800/50"
)}
>
{/* Active Background Pill */}
{isActive && (
<motion.div
layoutId="active-pill"
className={cn(
"absolute inset-0 rounded-2xl z-0",
isAdmin ? "bg-emerald-500/5" : "bg-indigo-500/5"
)}
transition={{ type: "spring", bounce: 0.2, duration: 0.6 }}
/>
)}
<item.icon className={cn(
"w-5 h-5 z-10 transition-transform duration-300 group-hover:scale-110",
isActive ? theme.iconActive : "text-neutral-400 group-hover:text-neutral-600 dark:group-hover:text-neutral-300"
)} />
<span className="z-10">{item.title}</span>
<AnimatePresence>
{item.badge && (
<motion.span
initial={{ scale: 0, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
className={cn(
"ml-auto px-2 py-0.5 rounded-lg text-[10px] font-black tracking-tight z-10",
isActive
? (isAdmin ? "bg-emerald-600 text-white" : "bg-indigo-600 text-white")
: "bg-neutral-100 text-neutral-500 dark:bg-neutral-800 dark:text-neutral-400"
)}
>
{item.badge}
</motion.span>
)}
</AnimatePresence>
{isActive && (
<div className={cn(
"absolute left-0 w-1 h-6 rounded-r-full",
isAdmin ? "bg-emerald-600 shadow-[2px_0_8px_rgba(16,185,129,0.4)]" : "bg-indigo-600 shadow-[2px_0_8px_rgba(79,70,229,0.4)]"
)} />
)}
</Link>
);
})}
</div>
</div>
))}
</div>
{/* User & Footer */}
<div className="p-4 mt-auto">
<div className="bg-neutral-50 dark:bg-neutral-800/50 rounded-3xl p-4 border border-neutral-100 dark:border-neutral-800 relative group transition-all hover:bg-white dark:hover:bg-neutral-800 hover:shadow-xl hover:shadow-black/5">
<div className="flex items-center gap-3">
<div className={cn(
"w-10 h-10 rounded-2xl bg-gradient-to-br flex items-center justify-center text-sm font-black text-white shadow-md",
theme.gradient
)}>
{user.name?.charAt(0) || user.email?.charAt(0) || 'U'}
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-black text-neutral-900 dark:text-neutral-100 truncate tracking-tight">{user.name || 'User Name'}</p>
<p className="text-[10px] font-bold text-neutral-400 truncate tracking-tight">{user.email}</p>
</div>
</div>
<div className="mt-4 pt-4 border-t border-neutral-200/50 dark:border-neutral-700/50">
<Button
variant="ghost"
className="w-full justify-start gap-3 text-neutral-400 hover:text-rose-600 hover:bg-rose-50 dark:hover:bg-rose-900/10 rounded-2xl px-3 py-2.5 h-auto transition-all duration-300 font-bold text-xs group"
onClick={() => signOut({ callbackUrl: '/login' })}
>
<div className="w-8 h-8 rounded-xl bg-neutral-100 dark:bg-neutral-800 flex items-center justify-center group-hover:bg-rose-100 dark:group-hover:bg-rose-900/30 transition-colors">
<LogOut className="w-4 h-4 transition-transform group-hover:translate-x-0.5" />
</div>
Sign out
</Button>
</div>
</div>
<p className="text-[9px] text-center mt-4 font-bold uppercase tracking-[0.25em] text-neutral-300 dark:text-neutral-600">
Vers. 2.4.0 Secured
</p>
</div>
</div>
);
}