feat: menu wrapper 생성

This commit is contained in:
2025-03-31 18:03:48 +09:00
parent 8117ca5c2c
commit ea319aa5ab
20 changed files with 107 additions and 26 deletions
+4 -4
View File
@@ -1,10 +1,10 @@
"use client";
import { usePathname, useRouter } from "next/navigation";
import { BaseLayout } from "@common/components/ui";
import type { MenuItem } from "@common/components/api";
import { useMenuStore } from "@/shared/lib/use-menu-store";
import { BaseLayout } from "@common/components/ui";
import { MenuWrapper } from "@/entities/menu";
import { useMenuStore } from "@/shared/lib";
export default function WithBaseLayout({
children,
}: {
@@ -21,7 +21,7 @@ export default function WithBaseLayout({
return (
<BaseLayout currentPath={pathname ?? ""} handleMenuClick={handleMenuClick}>
{children}
<MenuWrapper>{children}</MenuWrapper>
</BaseLayout>
);
}
+1 -1
View File
@@ -1,2 +1,2 @@
export { LoginApi } from "./api/login-service";
export { AuthWrapper } from "./ui/auth-wrapper";
export { AuthWrapper } from "./lib/auth-wrapper";
@@ -0,0 +1,14 @@
import { client } from "@common/components/api";
import { ENDPOINT } from "@/shared/config";
import type { MenuItem, MenuService } from "../model/menu-interface";
export class MenuApiService implements MenuService {
async getMenuRoles(siteId: string): Promise<MenuItem[]> {
const response = await client.get<MenuItem[]>(
`${ENDPOINT.CMS_SERVICE.MENU_ROLES}/${siteId}`,
);
return response.data;
}
}
export const MenuApi = new MenuApiService();
+1
View File
@@ -0,0 +1 @@
export { MenuWrapper } from "./lib/menu-wrapper";
@@ -0,0 +1,30 @@
"use client";
import { useEffect } from "react";
import { usePathname } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT, SITE_ID } from "@/shared/config";
import { useMenuStore } from "@/shared/lib";
import { MenuApi } from "../api/menu-service";
import { getCurrentMenu } from "./utils";
const MenuWrapper = ({ children }: { children: React.ReactNode }) => {
const pathname = usePathname();
const { data: menuRoles } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
queryFn: () => MenuApi.getMenuRoles(SITE_ID),
});
const { setCurrentMenuInfo } = useMenuStore((state) => state);
useEffect(() => {
if (!menuRoles) return;
const currentMenu = getCurrentMenu(menuRoles, pathname ?? "");
if (!currentMenu) return;
setCurrentMenuInfo(currentMenu);
}, [menuRoles]);
return <>{children}</>;
};
export { MenuWrapper };
+13
View File
@@ -0,0 +1,13 @@
import type { MenuItem } from "../model/menu-interface";
export const getCurrentMenu = (
menus: MenuItem[],
pathname: string,
): MenuItem | null => {
return (
menus.find((menu) => {
if (menu.children) return getCurrentMenu(menu.children, pathname);
return menu.urlPath === pathname;
}) ?? null
);
};
@@ -0,0 +1,23 @@
export interface MenuItem {
menuCd: number;
menuKornNm: string;
menuEngNm: string;
parentId: number | null;
sortSeq: number;
iconNm?: string;
children: MenuItem[] | null;
levelNo: number;
urlPath: string;
menuLnkgTypeCd: string;
menuLnkgSn: number;
menuPath: string;
qrcd: string;
useYn: string;
otptYn: string;
newWndwYn: string;
requiredUrlPath: boolean;
}
export interface MenuService {
getMenuRoles(siteId: string): Promise<MenuItem[]>;
}
+1 -1
View File
@@ -197,7 +197,7 @@ function MenuPage() {
return (
<>
<PageLayout pageTitle="메뉴 관리">
<PageLayout>
<div className="card !mb-0 flex !min-h-[calc(100vh-16.8rem)] gap-2">
<div className="card card-border w-[24rem]">
<Select value={siteId} onValueChange={handleSiteIdChange}>
+1 -1
View File
@@ -38,7 +38,7 @@ function CreatePage() {
});
return (
<PageLayout pageTitle="사이트 관리">
<PageLayout>
<SiteForm form={form} handleSave={createSite} />
</PageLayout>
);
+1 -1
View File
@@ -63,7 +63,7 @@ function ModifyPage({ siteId }: { siteId: string }) {
});
return (
<PageLayout pageTitle="사이트 관리">
<PageLayout>
<SiteForm
form={form}
siteId={siteId}
+1 -1
View File
@@ -44,7 +44,7 @@ function SitePage() {
}, []);
return (
<PageLayout pageTitle="사이트 관리">
<PageLayout>
<div className="card card-border !mt-6">
<div className="card card-border">
<form onSubmit={handleSubmit} className="flex items-center gap-2 p-2">
+1
View File
@@ -14,6 +14,7 @@ export const ENDPOINT = {
IPS: "/cms-service/api/v1/site-ips",
DOMAINS: "/cms-service/api/v1/site-dmns",
MENUS: "/cms-service/api/v1/menus",
MENU_ROLES: "/cms-service/api/v1/menu-roles",
CONTENTS: "/cms-service/api/v1/contents",
USER_OGNZ: "/cms-service/api/v1/user-ognz",
},
+1
View File
@@ -1,2 +1,3 @@
export { getCookie, setCookie, removeCookie } from "./cookie";
export { useMenuStore } from "./use-menu-store";
export { useUserStore } from "./use-user-store";
+5 -8
View File
@@ -1,13 +1,7 @@
import { PageTitle } from "@common/components";
import { useMenuStore } from "../lib/use-menu-store";
function PageLayout({
children,
pageTitle,
}: {
children: React.ReactNode;
pageTitle: string;
}) {
function PageLayout({ children }: { children: React.ReactNode }) {
const { currentMenuInfo } = useMenuStore((state) => state);
const menuPaths = currentMenuInfo?.menuPath
@@ -16,7 +10,10 @@ function PageLayout({
return (
<div className="h-auto min-h-full bg-container">
<PageTitle title={pageTitle} menuPaths={menuPaths ?? []} />
<PageTitle
title={currentMenuInfo?.menuKornNm ?? ""}
menuPaths={menuPaths ?? []}
/>
{children}
</div>
);
@@ -17,6 +17,7 @@ export interface MenuItem {
qrcd: string;
useYn: string;
otptYn: string;
newWndwYn: string;
requiredUrlPath: boolean;
}
+2 -1
View File
@@ -1,2 +1,3 @@
export * from "./utils";
export * from "./cookie";
export * from "./utils";
export * from "./use-mobile";
@@ -37,7 +37,7 @@ export function LNB({
const [openMenuIds, setOpenMenuIds] = useState<number[]>([]);
function isPathMatched(path: string) {
return currentPath === path || currentPath?.startsWith(`${path}/`);
return currentPath === path;
}
function MenuItem({ item }: { item: MenuItem }) {
@@ -57,7 +57,7 @@ export function LNB({
<SidebarMenuButton
className={cn(
"h-auto py-4 text-[1.4rem] text-label transition-colors hover:bg-accent",
isMatched && "bg-accent text-primary",
isMatched && "bg-accent !text-primary",
)}
onClick={handleClick}
>
@@ -90,15 +90,15 @@ export function LNB({
<Collapsible open={isOpen} onOpenChange={handleOpenChange}>
<SidebarMenuItem>
<CollapsibleTrigger asChild>
<div className="group flex items-center justify-between rounded-sm py-4 pr-2 hover:bg-accent data-[state=open]:text-primary">
<div className="group flex items-center justify-between rounded-sm py-4 pr-2 hover:bg-accent">
<SidebarMenuButton className="group/menu-button text-[1.4rem] group-data-[state=open]:bg-transparent">
<i
className={cn(
"diveicon ml-2 text-label group-data-[state=open]:text-primary",
"diveicon ml-2 text-label",
`di-${item.iconNm}`,
)}
/>
<span className="text-label group-hover/menu-button:text-foreground group-data-[state=open]:text-primary">
<span className="text-label group-hover/menu-button:text-foreground">
{item.menuKornNm}
</span>
</SidebarMenuButton>
@@ -14,7 +14,7 @@ const PageTitle = ({
<div className="flex items-center gap-3">
<i className="diveicon di-home-fill text-foreground" />
{menuPaths?.map((path, i) => (
<React.Fragment key={path}>
<React.Fragment key={`${i}-${path}`}>
{i !== 0 && (
<i className="diveicon di-arrow-forward text-foreground" />
)}
@@ -3,8 +3,7 @@ import { Slot } from "@radix-ui/react-slot";
import { type VariantProps, cva } from "class-variance-authority";
import { PanelLeft } from "lucide-react";
import { useIsMobile } from "../../lib/use-mobile";
import { cn } from "../../lib/utils";
import { cn, useIsMobile } from "../../lib";
import { Button } from "../button";
import { Input } from "../input";
import { Separator } from "../separator";