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
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>
|
|
);
|
|
}
|