qudoco-fe/components/landing-page/retracting-sidedar.tsx

463 lines
14 KiB
TypeScript

"use client";
import React, { Dispatch, SetStateAction, useState, useEffect } from "react";
import Image from "next/image";
import { Icon } from "@iconify/react";
import Link from "next/link";
import DashboardContainer from "../main/dashboard/dashboard-container";
import { usePathname } from "next/navigation";
import { useTheme } from "../layout/theme-context";
import { AnimatePresence, motion } from "framer-motion";
import Option from "./option";
interface RetractingSidebarProps {
sidebarData: boolean;
updateSidebarData: (newData: boolean) => void;
}
interface SidebarItem {
title: string;
link?: string;
children?: SidebarItem[];
}
interface SidebarSection {
title?: string;
items?: SidebarItem[];
children?: SidebarItem[];
}
const getSidebarByRole = (roleId: string | null) => {
if (roleId === "1") {
return [
{
title: "Dashboard",
items: [
{
title: "Dashboard",
icon: () => (
<Icon icon="material-symbols:dashboard" className="text-lg" />
),
link: "/admin/dashboard",
},
],
},
{
title: "System",
items: [
{
title: "Categories",
icon: () => (
<Icon icon="famicons:list-outline" className="text-lg" />
),
link: "/admin/master-category",
},
{
title: "User Management",
icon: () => <Icon icon="ph:users-three-fill" className="text-lg" />,
link: "/admin/master-user",
},
],
},
];
}
if (roleId === "2" || roleId === "3") {
return [
{
title: "Dashboard",
items: [
{
title: "Dashboard",
icon: () => (
<Icon icon="material-symbols:dashboard" className="text-lg" />
),
link: "/admin/dashboard",
},
],
},
{
items: [
{
title: "Content Website",
icon: () => <Icon icon="ri:article-line" className="text-lg" />,
link: "/admin/content-website",
},
],
},
{
title: "News & Article",
items: [
{
title: "News & Article",
icon: () => (
<Icon icon="grommet-icons:article" className="text-lg" />
),
children: [
{
title: "Text",
icon: () => <Icon icon="mdi:file-document-outline" />,
link: "/admin/news-article/text",
},
{
title: "Image",
icon: () => <Icon icon="mdi:image-outline" />,
link: "/admin/news-article/image",
},
{
title: "Video",
icon: () => <Icon icon="mdi:video-outline" />,
link: "/admin/news-article/video",
},
{
title: "Audio",
icon: () => <Icon icon="mdi:music-note-outline" />,
link: "/admin/news-article/audio",
},
],
},
],
},
{
items: [
{
title: "My Content",
icon: () => <Icon icon="guidance:folder" className="text-lg" />,
link: "/admin/my-content",
},
],
},
{
items: [
{
title: "Media Library",
icon: () => <Icon icon="wordpress:media" className="text-lg" />,
link: "/admin/media-library",
},
],
},
];
}
// fallback kalau role tidak dikenal
return [];
};
export const RetractingSidebar = ({
sidebarData,
updateSidebarData,
}: RetractingSidebarProps) => {
const pathname = usePathname();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) {
return null;
}
return (
<>
{/* DESKTOP SIDEBAR */}
<AnimatePresence mode="wait">
<motion.nav
key="desktop-sidebar"
layout
className="hidden md:flex sticky top-0 h-screen shrink-0 bg-gradient-to-b from-slate-50 to-white dark:from-slate-800 dark:to-slate-900 border-r border-slate-200/60 dark:border-slate-700/60 shadow-lg backdrop-blur-sm flex-col justify-between"
style={{
width: sidebarData ? "280px" : "80px",
}}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: 0.3 }}
>
<SidebarContent
open={sidebarData}
pathname={pathname}
updateSidebarData={updateSidebarData}
/>
</motion.nav>
</AnimatePresence>
{/* Desktop Toggle Button - appears when sidebar is collapsed */}
<AnimatePresence>
{!sidebarData && (
<motion.button
key="desktop-toggle"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.8 }}
className="hidden md:flex fixed top-4 left-20 z-40 p-3 bg-white rounded-xl shadow-lg border border-slate-200/60 hover:shadow-xl transition-all duration-200 hover:bg-slate-50"
onClick={() => updateSidebarData(true)}
>
<Icon
icon="heroicons:chevron-right"
className="w-5 h-5 text-slate-600"
/>
</motion.button>
)}
</AnimatePresence>
{/* Mobile Toggle Button */}
<AnimatePresence>
{!sidebarData && (
<motion.button
key="mobile-toggle"
initial={{ opacity: 0, scale: 0 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0 }}
className="md:hidden fixed top-4 left-4 z-50 p-3 bg-white dark:bg-slate-800 rounded-xl shadow-lg border border-slate-200/60 dark:border-slate-700/60 hover:shadow-xl transition-all duration-200"
onClick={() => updateSidebarData(true)}
>
<Icon
icon="heroicons:chevron-right"
className="w-6 h-6 text-slate-600"
/>
</motion.button>
)}
</AnimatePresence>
{/* MOBILE SIDEBAR */}
<AnimatePresence>
{sidebarData && (
<motion.div
key="mobile-sidebar"
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ type: "tween", duration: 0.3 }}
className="fixed top-0 left-0 z-50 w-[280px] h-full bg-gradient-to-b from-slate-50 to-white dark:from-slate-800 dark:to-slate-900 p-4 flex flex-col md:hidden shadow-2xl backdrop-blur-sm"
>
{/* <button onClick={() => updateSidebarData(false)} className="mb-4 self-end text-zinc-500">
</button> */}
<SidebarContent
open={true}
pathname={pathname}
updateSidebarData={updateSidebarData}
/>
</motion.div>
)}
</AnimatePresence>
</>
);
};
const SidebarContent = ({
open,
pathname,
updateSidebarData,
}: {
open: boolean;
pathname: string;
updateSidebarData: (newData: boolean) => void;
}) => {
const { theme, toggleTheme } = useTheme();
const [username, setUsername] = useState<string>("Guest");
const [roleId, setRoleId] = useState<string | null>(null);
const [openMenus, setOpenMenus] = useState<string[]>([]);
// ===============================
// GET COOKIE
// ===============================
useEffect(() => {
const getCookie = (name: string) => {
const match = document.cookie.match(
new RegExp("(^| )" + name + "=([^;]+)"),
);
return match ? decodeURIComponent(match[2]) : null;
};
const cookieUsername = getCookie("username");
const cookieRoleId = getCookie("urie");
if (cookieUsername) setUsername(cookieUsername);
if (cookieRoleId) setRoleId(cookieRoleId);
}, []);
// ===============================
// AUTO EXPAND JIKA DI NEWS ARTICLE
// ===============================
useEffect(() => {
if (pathname.startsWith("/admin/news-article")) {
setOpenMenus(["News & Article"]);
}
}, [pathname]);
const toggleMenu = (title: string) => {
setOpenMenus((prev) =>
prev.includes(title)
? prev.filter((item) => item !== title)
: [...prev, title],
);
};
const sidebarSections = getSidebarByRole(roleId);
return (
<div className="flex flex-col h-full">
{/* ========================= */}
{/* SCROLLABLE AREA */}
{/* ========================= */}
<div className="flex-1 overflow-y-auto">
<div className="flex flex-col space-y-6">
{/* HEADER */}
<div className="flex items-center justify-between px-4 py-6">
<Link href="/" className="flex items-center space-x-3">
<img
src="/image/qudo1.png"
className="w-10 h-10 rounded-lg shadow-sm"
/>
</Link>
{open && (
<button
className="p-2 rounded-lg hover:bg-slate-100"
onClick={() => updateSidebarData(false)}
>
<Icon icon="heroicons:chevron-left" className="w-5 h-5" />
</button>
)}
</div>
{/* ========================= */}
{/* MENU SECTION */}
{/* ========================= */}
<div className="space-y-3 px-3 pb-6">
{sidebarSections.map((section, sectionIndex) => (
<div key={sectionIndex} className="space-y-3">
{open && section.title && (
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wider px-3">
{section.title}
</h3>
)}
<div className="space-y-2">
{section.items?.map((item: any) => {
// =============================
// ITEM WITH CHILDREN (ACCORDION)
// =============================
if (item.children) {
const isExpanded = openMenus.includes(item.title);
return (
<div key={item.title} className="space-y-1">
<div
onClick={() => toggleMenu(item.title)}
className="cursor-pointer"
>
<div className="flex items-center justify-between">
<Option
Icon={item.icon}
title={item.title}
active={pathname.startsWith(
"/admin/news-article",
)}
open={open}
/>
{open && (
<motion.div
animate={{ rotate: isExpanded ? 180 : 0 }}
transition={{ duration: 0.2 }}
className="mr-3"
>
<Icon icon="heroicons:chevron-down" />
</motion.div>
)}
</div>
</div>
<AnimatePresence>
{open && isExpanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: "auto", opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="ml-8 space-y-1 overflow-hidden"
>
{item.children?.map((child: any) => (
<Link href={child.link!} key={child.title}>
<Option
Icon={child.icon}
title={child.title}
active={pathname === child.link}
open={open}
/>
</Link>
))}
</motion.div>
)}
</AnimatePresence>
</div>
);
}
return (
<Link href={item.link!} key={item.title}>
<Option
Icon={item.icon}
title={item.title}
active={pathname === item.link}
open={open}
/>
</Link>
);
})}
</div>
</div>
))}
</div>
</div>
</div>
{/* ========================= */}
{/* BOTTOM SECTION */}
{/* ========================= */}
<div className="border-t border-slate-200 p-3 space-y-2">
{/* THEME TOGGLE */}
<button
onClick={toggleTheme}
className="flex items-center gap-3 w-full p-3 rounded-lg hover:bg-slate-100"
>
{theme === "dark" ? (
<Icon icon="solar:sun-bold" />
) : (
<Icon icon="solar:moon-bold" />
)}
{open && <span>{theme === "dark" ? "Light Mode" : "Dark Mode"}</span>}
</button>
{/* SETTINGS */}
<Link href="/settings">
<Option
Icon={() => <Icon icon="lets-icons:setting-fill" />}
title="Settings"
active={pathname === "/settings"}
open={open}
/>
</Link>
{/* USER */}
{open && (
<div className="pt-4">
<p className="text-sm font-medium">{username}</p>
<Link href="/auth">
<p className="text-xs text-slate-500 hover:text-blue-600">
Sign out
</p>
</Link>
</div>
)}
</div>
</div>
);
};