MIF_E31221222/sigap-website/components/app-sidebar.tsx

204 lines
5.2 KiB
TypeScript

"use client";
import * as React from "react";
import { useEffect, useState } from "react";
import {
AudioWaveform,
Command,
Frame,
GalleryVerticalEnd,
Loader2,
SquareTerminal,
} from "lucide-react";
import { NavMain } from "@/components/nav-main";
import { NavProjects } from "@/components/nav-projects";
import { NavUser } from "@/components/nav-user";
import { TeamSwitcher } from "@/components/team-switcher";
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
SidebarRail,
} from "@/components/ui/sidebar";
import {
NavItemsGet,
NavSubItems,
} from "@/src/applications/entities/models/nav-items.model";
import { getNavItems } from "@/actions/dashboard/nav-items";
import DynamicIcon, { DynamicIconProps } from "./dynamic-icon";
// 🔧 Konstanta untuk localStorage key
const NAV_ITEMS_STORAGE_KEY = "navItemsData";
// 🎨 Data fallback
const fallbackData = {
user: {
name: "shadcn",
email: "m@example.com",
avatar: "/avatars/shadcn.jpg",
},
teams: [
{
name: "Acme Inc",
logo: GalleryVerticalEnd,
plan: "Enterprise",
},
{
name: "Acme Corp.",
logo: AudioWaveform,
plan: "Startup",
},
{
name: "Evil Corp.",
logo: Command,
plan: "Free",
},
],
navMain: [
{
title: "Playground",
url: "#",
icon: SquareTerminal,
isActive: true,
items: [
{
title: "History",
url: "#",
},
{
title: "Starred",
url: "#",
},
{
title: "Settings",
url: "#",
},
],
},
// additional items...
],
projects: [
{
name: "Design Engineering",
url: "#",
icon: Frame,
},
// additional projects...
],
};
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const [navItems, setNavItems] = useState<NavItemsGet | null>(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
// 📦 Ambil data dari localStorage
const loadNavItemsFromStorage = (): NavItemsGet | null => {
const storedData = localStorage.getItem(NAV_ITEMS_STORAGE_KEY);
return storedData ? (JSON.parse(storedData) as NavItemsGet) : null;
};
// ⚡ Fungsi untuk membandingkan dua objek secara deep
const isDataEqual = (data1: NavItemsGet, data2: NavItemsGet): boolean =>
JSON.stringify(data1) === JSON.stringify(data2);
useEffect(() => {
const fetchAndSyncNavItems = async () => {
try {
const localData = loadNavItemsFromStorage();
if (localData) {
setNavItems(localData);
setIsLoading(false);
}
const response = await getNavItems();
if (response.success && response.data) {
const fetchedData = response.data as NavItemsGet;
const currentStoredData = loadNavItemsFromStorage();
// 🔄 Jika data berbeda, update localStorage dan state
if (
currentStoredData &&
!isDataEqual(currentStoredData, fetchedData)
) {
localStorage.setItem(
NAV_ITEMS_STORAGE_KEY,
JSON.stringify(fetchedData)
);
setNavItems(fetchedData);
}
} else {
setError(response.error || "Failed to load navigation items");
}
} catch (err) {
setError("An unexpected error occurred while fetching navigation");
console.error(err);
} finally {
setIsLoading(false);
}
};
fetchAndSyncNavItems();
}, []);
// 🎛️ Transformasi data DB ke format NavMain
const formatNavItems = React.useMemo(() => {
if (!navItems) return fallbackData.navMain;
return navItems.map((item) => ({
title: item.title,
url: item.url,
icon: item.icon,
isActive: item.isActive,
items: item.subItems.map((subItem) => ({
title: subItem.title,
url: subItem.url,
icon: subItem.icon,
isActive: subItem.isActive,
})),
}));
}, [navItems]);
// 🧩 Komponen NavMain dinamis dengan ikon
const DynamicNavMain = ({ items }: { items: any[] }) => (
<NavMain
items={items.map((item) => ({
...item,
icon: () => <DynamicIcon iconName={item.icon} size={24} stroke={2} />,
items: item.items.map((subItem: NavSubItems) => ({
...subItem,
icon: () => (
<DynamicIcon iconName={subItem.icon} size={24} stroke={2} />
),
})),
}))}
/>
);
return (
<Sidebar collapsible="icon" {...props}>
<SidebarHeader>
<TeamSwitcher teams={fallbackData.teams} />
</SidebarHeader>
<SidebarContent>
{isLoading ? (
<div className="flex justify-center items-center py-8">
<Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
</div>
) : error ? (
<div className="px-4 py-2 text-sm text-destructive">{error}</div>
) : (
<DynamicNavMain items={formatNavItems} />
)}
{/* <NavProjects projects={fallbackData.projects} /> */}
</SidebarContent>
<SidebarFooter>
<NavUser user={fallbackData.user} />
</SidebarFooter>
<SidebarRail />
</Sidebar>
);
}