179 lines
5.8 KiB
TypeScript
179 lines
5.8 KiB
TypeScript
"use client";
|
|
|
|
import Link from "next/link";
|
|
import { usePathname } from "next/navigation";
|
|
import { useEffect, useMemo, useState } from "react";
|
|
import {
|
|
fetchAnonymousMenu,
|
|
findActiveTopMenu,
|
|
hasAnyChild,
|
|
isMenuActive,
|
|
type MenuItem,
|
|
} from "@/app/store/common";
|
|
|
|
export type LnbItem = {
|
|
label: string;
|
|
href?: string;
|
|
children?: LnbItem[];
|
|
};
|
|
|
|
function findOpenMenuCode(items: MenuItem[], pathname: string): number | null {
|
|
const matched = items.find((item) => {
|
|
if (isMenuActive(pathname, item)) return true;
|
|
return (item.children ?? []).some((child) => isMenuActive(pathname, child));
|
|
});
|
|
|
|
return matched?.menuCd ?? null;
|
|
}
|
|
|
|
export default function Lnb() {
|
|
const pathname = usePathname();
|
|
const [menus, setMenus] = useState<MenuItem[]>([]);
|
|
const [openMenuCode, setOpenMenuCode] = useState<number | null>(null);
|
|
|
|
useEffect(() => {
|
|
let isMounted = true;
|
|
|
|
(async () => {
|
|
try {
|
|
const loaded = await fetchAnonymousMenu();
|
|
if (!isMounted) return;
|
|
console.log("Fetched LNB menus:", loaded);
|
|
setMenus(loaded);
|
|
} catch (error) {
|
|
console.error("Failed to load DN LNB menu", error);
|
|
if (!isMounted) return;
|
|
setMenus([]);
|
|
}
|
|
})();
|
|
|
|
return () => {
|
|
isMounted = false;
|
|
};
|
|
}, []);
|
|
|
|
const currentTopMenu = useMemo(
|
|
() => findActiveTopMenu(menus, pathname),
|
|
[menus, pathname],
|
|
);
|
|
|
|
const secondLevelMenus = useMemo(
|
|
() => currentTopMenu?.children ?? [],
|
|
[currentTopMenu],
|
|
);
|
|
// console.log("LNB render", { pathname, currentTopMenu, secondLevelMenus, menus });
|
|
useEffect(() => {
|
|
const defaultOpen = findOpenMenuCode(secondLevelMenus, pathname);
|
|
setOpenMenuCode(defaultOpen);
|
|
}, [pathname, secondLevelMenus]);
|
|
|
|
return (
|
|
<aside
|
|
className={[
|
|
"fixed left-0 top-[83px]", // ✅ 화면 고정 + TopNav 아래로
|
|
"w-[260px] min-w-[260px] shrink-0",
|
|
"h-[calc(100vh-83px)]", // ✅ 남은 높이만큼
|
|
"overflow-y-auto", // ✅ LNB 내부 스크롤
|
|
"bg-white z-50", // ✅ 겹침 방지
|
|
].join(" ")}
|
|
>
|
|
{/* 타이틀 */}
|
|
<div className="flex h-[120px] items-center justify-center bg-gradient-to-r from-cyan-500 to-green-500 text-[40px] font-bold text-white">
|
|
{currentTopMenu?.menuKornNm?.trim().split(/\s+/).pop() ?? ""}
|
|
</div>
|
|
|
|
{/* 메뉴 */}
|
|
<ul>
|
|
{secondLevelMenus.map((item) => {
|
|
const hasChildren = hasAnyChild(item);
|
|
const active = isMenuActive(pathname, item);
|
|
|
|
if (!hasChildren) {
|
|
return (
|
|
<li key={item.menuCd}>
|
|
<Link
|
|
href={item.urlPath || "#"}
|
|
className={[
|
|
"block border-b border-slate-300 px-5 py-4 text-center text-base font-semibold transition-colors",
|
|
active
|
|
? "cursor-default bg-white text-sky-500"
|
|
: "bg-[#F0F0F0] text-[#767676] hover:bg-white hover:text-sky-500",
|
|
].join(" ")}
|
|
>
|
|
{item.menuKornNm}
|
|
</Link>
|
|
</li>
|
|
);
|
|
}
|
|
|
|
const isOpen = openMenuCode === item.menuCd;
|
|
const childMenus = item.children ?? [];
|
|
|
|
return (
|
|
<li key={item.menuCd}>
|
|
<button
|
|
type="button"
|
|
onClick={() =>
|
|
setOpenMenuCode((prev) => (prev === item.menuCd ? null : item.menuCd))
|
|
}
|
|
className={[
|
|
"flex w-full items-center justify-between border-b border-slate-300 px-5 py-6 text-base font-semibold",
|
|
active ? "bg-slate-100 text-slate-900" : "bg-slate-100 text-slate-500",
|
|
].join(" ")}
|
|
aria-expanded={isOpen}
|
|
>
|
|
<span className="w-full text-center">{item.menuKornNm}</span>
|
|
<span
|
|
aria-hidden
|
|
className={[
|
|
"ml-3 shrink-0 transition-transform duration-200",
|
|
isOpen ? "rotate-180" : "rotate-0",
|
|
].join(" ")}
|
|
>
|
|
<img
|
|
src="/images/landing/drop_arrow.svg"
|
|
alt="menu toggle icon"
|
|
className="h-7 w-7"
|
|
/>
|
|
</span>
|
|
</button>
|
|
|
|
<div
|
|
className={[
|
|
"grid transition-[grid-template-rows] duration-200 ease-out",
|
|
isOpen ? "grid-rows-[1fr]" : "grid-rows-[0fr]",
|
|
].join(" ")}
|
|
>
|
|
<div className="overflow-hidden bg-white">
|
|
<ul>
|
|
{childMenus.map((child) => {
|
|
const childActive = isMenuActive(pathname, child);
|
|
|
|
return (
|
|
<li key={child.menuCd}>
|
|
<Link
|
|
href={child.urlPath || "#"}
|
|
className={[
|
|
"block border-b border-slate-200 bg-slate-50 px-5 py-4 pl-[60px] text-[18px] font-medium transition-colors",
|
|
childActive
|
|
? "cursor-default bg-white text-sky-500"
|
|
: "text-slate-500 hover:bg-white hover:text-sky-500",
|
|
].join(" ")}
|
|
>
|
|
<span className="mr-2 text-current">-</span>
|
|
{child.menuKornNm}
|
|
</Link>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</aside>
|
|
);
|
|
}
|