262 lines
7.2 KiB
TypeScript
262 lines
7.2 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect, useRef } from "react";
|
|
import { useQuery } from "@tanstack/react-query";
|
|
import { ChevronRight } from "lucide-react";
|
|
import { getConfig } from "@common/config";
|
|
import { getParentIds } from "./lib";
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarMenuSub,
|
|
SidebarMenuSubButton,
|
|
SidebarMenuSubItem,
|
|
useSidebar,
|
|
} from "../sidebar";
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "../collapsible";
|
|
import { ENDPOINT } from "../../config";
|
|
import { cn, getCookie, setCookie } from "../../lib";
|
|
import {
|
|
getAnonymouseMenuList,
|
|
getMenuList,
|
|
getPreviewTreeMenuList,
|
|
type MenuItem,
|
|
} from "../../api";
|
|
|
|
const appConfig = getConfig();
|
|
const appSiteId = appConfig.SITE_ID ?? "";
|
|
const userId = getCookie(appConfig.AUTH_USER_ID ?? "");
|
|
const isLoggedIn = !!userId;
|
|
|
|
export function LNB({
|
|
viewOnly,
|
|
currentPath,
|
|
propSiteId,
|
|
previewMode,
|
|
classNames,
|
|
handleMenuClick,
|
|
}: {
|
|
viewOnly?: boolean;
|
|
currentPath?: string;
|
|
propSiteId?: string;
|
|
previewMode?: boolean;
|
|
classNames?: {
|
|
wrapper?: string;
|
|
};
|
|
handleMenuClick?: (item: MenuItem) => void;
|
|
}) {
|
|
const [defaultOpenMenuIds, setDefaultOpenMenuIds] = useState<number[]>([]);
|
|
const [currentMenuCode, setCurrentMenuCode] = useState<number | null>(null);
|
|
const prevPathRef = useRef(currentPath);
|
|
|
|
const siteId = propSiteId ?? appSiteId;
|
|
|
|
const { data: menuListData } = useQuery({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
|
queryFn: () => getMenuList(siteId),
|
|
enabled: isLoggedIn && !viewOnly && !previewMode,
|
|
});
|
|
|
|
const { data: previewTreeMenuListData } = useQuery({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId, "preview"],
|
|
queryFn: () => getPreviewTreeMenuList(siteId),
|
|
enabled: isLoggedIn && previewMode,
|
|
});
|
|
|
|
const { data: anonymousMenuListData } = useQuery({
|
|
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ANONYMOUS, siteId],
|
|
queryFn: () => getAnonymouseMenuList(siteId),
|
|
enabled: !isLoggedIn,
|
|
});
|
|
|
|
const menuList = isLoggedIn
|
|
? previewMode
|
|
? previewTreeMenuListData
|
|
: menuListData
|
|
: anonymousMenuListData;
|
|
|
|
function isPathMatched(path: string) {
|
|
if (!path) return false;
|
|
return currentPath?.startsWith(path);
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (!menuList || !currentPath) return;
|
|
|
|
const matchedMenuIds: number[] = [];
|
|
let matchedMenuCode: number | null = null;
|
|
|
|
const findMatchedMenus = (items: MenuItem[]) => {
|
|
items.forEach((item) => {
|
|
if (isPathMatched(item.urlPath)) {
|
|
const parentIds = getParentIds(item.menuCd)(menuList);
|
|
matchedMenuIds.push(...parentIds, item.menuCd);
|
|
}
|
|
if (item.children) findMatchedMenus(item.children);
|
|
});
|
|
};
|
|
|
|
const findMatchedMenuCode = (items: MenuItem[]) => {
|
|
items.forEach((item) => {
|
|
if (matchedMenuCode) return;
|
|
if (currentPath === item.urlPath) matchedMenuCode = item.menuCd;
|
|
if (item.children) findMatchedMenuCode(item.children);
|
|
});
|
|
};
|
|
|
|
findMatchedMenus(menuList);
|
|
findMatchedMenuCode(menuList);
|
|
|
|
if (matchedMenuIds.length > 0)
|
|
setDefaultOpenMenuIds((prev) => [
|
|
...new Set([...prev, ...matchedMenuIds]),
|
|
]);
|
|
|
|
if (prevPathRef.current !== currentPath) {
|
|
setDefaultOpenMenuIds(matchedMenuIds);
|
|
prevPathRef.current = currentPath;
|
|
}
|
|
|
|
if (matchedMenuCode) setCurrentMenuCode(matchedMenuCode);
|
|
}, [menuList, currentPath]);
|
|
|
|
function MenuItem({
|
|
item,
|
|
isSubMenu,
|
|
}: {
|
|
item: MenuItem;
|
|
isSubMenu?: boolean;
|
|
}) {
|
|
const { open: isSidebarOpen, setOpen } = useSidebar();
|
|
const isMatched =
|
|
isPathMatched(item.urlPath) && currentMenuCode === item.menuCd;
|
|
|
|
const handleClick = () => {
|
|
if (!isSidebarOpen) setOpen(true);
|
|
setCookie("menu-code-key", item.menuCd.toString());
|
|
handleMenuClick?.(item);
|
|
};
|
|
|
|
if (item.children) {
|
|
return <MenuGroup item={item} />;
|
|
}
|
|
|
|
const MenuItem = isSubMenu ? SidebarMenuSubItem : SidebarMenuItem;
|
|
const MenuButton = isSubMenu ? SidebarMenuSubButton : SidebarMenuButton;
|
|
|
|
return (
|
|
<MenuItem>
|
|
<MenuButton
|
|
className={cn(
|
|
"h-auto cursor-pointer py-4 text-[1.4rem] text-label transition-colors hover:bg-accent",
|
|
isMatched && "bg-accent !text-primary",
|
|
!isSidebarOpen && "justify-center",
|
|
)}
|
|
data-matched={isMatched}
|
|
onClick={handleClick}
|
|
tooltip={item.menuKornNm}
|
|
>
|
|
<i
|
|
className={cn(
|
|
"diveicon",
|
|
`di-${item.iconNm}`,
|
|
isSidebarOpen && "ml-2",
|
|
)}
|
|
/>
|
|
<span className={cn("whitespace-nowrap", !isSidebarOpen && "hidden")}>
|
|
{item.menuKornNm}
|
|
</span>
|
|
</MenuButton>
|
|
</MenuItem>
|
|
);
|
|
}
|
|
|
|
function MenuGroup({ item }: { item: MenuItem }) {
|
|
const { open: isSidebarOpen, setOpen: setSidebarOpen } = useSidebar();
|
|
const [isOpen, setIsOpen] = useState(
|
|
defaultOpenMenuIds.includes(item.menuCd),
|
|
);
|
|
|
|
const handleOpenChange = (open: boolean) => {
|
|
setIsOpen(open);
|
|
if (!isSidebarOpen) setSidebarOpen(true);
|
|
};
|
|
|
|
return (
|
|
<Collapsible
|
|
open={isOpen}
|
|
onOpenChange={handleOpenChange}
|
|
className="w-full"
|
|
>
|
|
<SidebarMenuItem>
|
|
<CollapsibleTrigger asChild>
|
|
<SidebarMenuButton
|
|
className={cn(
|
|
"flex h-auto items-center py-4 pr-2 text-[1.4rem]",
|
|
isOpen && "bg-transparent",
|
|
!isSidebarOpen && "justify-center",
|
|
)}
|
|
tooltip={item.menuKornNm}
|
|
>
|
|
<div className="flex items-center gap-2">
|
|
<i
|
|
className={cn(
|
|
"diveicon text-label",
|
|
`di-${item.iconNm}`,
|
|
isSidebarOpen && "ml-2",
|
|
)}
|
|
/>
|
|
<span
|
|
className={cn(
|
|
"whitespace-nowrap text-label group-hover/menu-button:text-foreground",
|
|
!isSidebarOpen && "hidden",
|
|
)}
|
|
>
|
|
{item.menuKornNm}
|
|
</span>
|
|
</div>
|
|
<ChevronRight
|
|
className={cn(
|
|
"ml-auto !size-6 text-label transition-transform",
|
|
isOpen && "rotate-90",
|
|
!isSidebarOpen && "hidden",
|
|
)}
|
|
/>
|
|
</SidebarMenuButton>
|
|
</CollapsibleTrigger>
|
|
<CollapsibleContent>
|
|
<SidebarMenuSub>
|
|
{item.children?.map((child) => (
|
|
<MenuItem key={child.menuCd} item={child} isSubMenu />
|
|
))}
|
|
</SidebarMenuSub>
|
|
</CollapsibleContent>
|
|
</SidebarMenuItem>
|
|
</Collapsible>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Sidebar
|
|
collapsible="icon"
|
|
className={cn(
|
|
"border-border bg-background px-4 py-[6rem]",
|
|
classNames?.wrapper,
|
|
)}
|
|
>
|
|
<SidebarContent className="bg-background">
|
|
<SidebarMenu className="gap-0">
|
|
{menuList?.map((item) => <MenuItem key={item.menuCd} item={item} />)}
|
|
</SidebarMenu>
|
|
</SidebarContent>
|
|
</Sidebar>
|
|
);
|
|
}
|