From 0aee493d324c6af0975a7be9768095acc737ed4d Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 13:09:05 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20page=20title=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/layout.tsx | 11 ++++++- apps/cs/src/shared/lib/use-menu-store.ts | 14 +++++++++ .../src/shared/providers/store-provider.tsx | 5 ++-- apps/cs/src/shared/store/index.ts | 2 ++ apps/cs/src/shared/store/menu-store.ts | 23 ++++++++++++++ apps/cs/src/shared/ui/page-layout.tsx | 13 ++++++-- packages/common/components/src/api/menu.ts | 1 + packages/common/components/src/ui/index.ts | 1 + .../common/components/src/ui/layout/base.tsx | 12 ++++---- .../common/components/src/ui/lnb/index.tsx | 6 ++-- .../components/src/ui/page-title/index.tsx | 30 +++++++++++++++++++ .../src/ui/page-title/page-title.stories.tsx | 17 +++++++++++ 12 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 apps/cs/src/shared/lib/use-menu-store.ts create mode 100644 apps/cs/src/shared/store/menu-store.ts create mode 100644 packages/common/components/src/ui/page-title/index.tsx create mode 100644 packages/common/components/src/ui/page-title/page-title.stories.tsx diff --git a/apps/cs/app/(with-base-layout)/layout.tsx b/apps/cs/app/(with-base-layout)/layout.tsx index dfb290abd..e5f98a449 100644 --- a/apps/cs/app/(with-base-layout)/layout.tsx +++ b/apps/cs/app/(with-base-layout)/layout.tsx @@ -2,6 +2,9 @@ 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"; + export default function WithBaseLayout({ children, }: { @@ -9,9 +12,15 @@ export default function WithBaseLayout({ }) { const router = useRouter(); const pathname = usePathname(); + const { setCurrentMenuInfo } = useMenuStore((state) => state); + + const handleMenuClick = (item: MenuItem) => { + setCurrentMenuInfo(item); + router.push(item.urlPath); + }; return ( - + {children} ); diff --git a/apps/cs/src/shared/lib/use-menu-store.ts b/apps/cs/src/shared/lib/use-menu-store.ts new file mode 100644 index 000000000..66e41c76c --- /dev/null +++ b/apps/cs/src/shared/lib/use-menu-store.ts @@ -0,0 +1,14 @@ +import { useStore } from "zustand"; +import { useContext } from "react"; +import type { MenuStore } from "../store/menu-store"; +import { StoreContext } from "../providers/store-provider"; + +export const useMenuStore = (selector: (store: MenuStore) => T): T => { + const storeContext = useContext(StoreContext); + + if (!storeContext) { + throw new Error("useMenuStore must be used within StoreProvider"); + } + + return useStore(storeContext.menuStore, selector); +}; diff --git a/apps/cs/src/shared/providers/store-provider.tsx b/apps/cs/src/shared/providers/store-provider.tsx index 4814705ab..82783bdac 100644 --- a/apps/cs/src/shared/providers/store-provider.tsx +++ b/apps/cs/src/shared/providers/store-provider.tsx @@ -1,11 +1,11 @@ "use client"; import { createContext, useRef } from "react"; -import { createAlertStore } from "../store/alert-store"; -import { createUserStore } from "../store/user-store"; +import { createAlertStore, createMenuStore, createUserStore } from "../store"; type StoreContextType = { alertStore: ReturnType; + menuStore: ReturnType; userStore: ReturnType; }; @@ -18,6 +18,7 @@ export const StoreProvider = ({ children }: { children: React.ReactNode }) => { if (storeRef.current === null) { storeRef.current = { alertStore: createAlertStore(), + menuStore: createMenuStore(), userStore: createUserStore(), }; } diff --git a/apps/cs/src/shared/store/index.ts b/apps/cs/src/shared/store/index.ts index 4dae19ced..73c608f05 100644 --- a/apps/cs/src/shared/store/index.ts +++ b/apps/cs/src/shared/store/index.ts @@ -1 +1,3 @@ +export { createAlertStore } from "./alert-store"; +export { createMenuStore } from "./menu-store"; export { createUserStore, type IUserInfo } from "./user-store"; diff --git a/apps/cs/src/shared/store/menu-store.ts b/apps/cs/src/shared/store/menu-store.ts new file mode 100644 index 000000000..f0c6340c9 --- /dev/null +++ b/apps/cs/src/shared/store/menu-store.ts @@ -0,0 +1,23 @@ +import { createStore } from "zustand/vanilla"; +import type { MenuItem } from "@common/components/api"; + +export type MenuState = { + currentMenuInfo: MenuItem | null; +}; + +export type MenuActions = { + setCurrentMenuInfo: (menu: MenuItem) => void; +}; + +export type MenuStore = MenuState & MenuActions; + +export const defaultInitState: MenuState = { + currentMenuInfo: null, +}; + +export const createMenuStore = (initState: MenuState = defaultInitState) => { + return createStore()((set) => ({ + ...initState, + setCurrentMenuInfo: (menu: MenuItem) => set({ currentMenuInfo: menu }), + })); +}; diff --git a/apps/cs/src/shared/ui/page-layout.tsx b/apps/cs/src/shared/ui/page-layout.tsx index e4f11141b..3a05ce9b8 100644 --- a/apps/cs/src/shared/ui/page-layout.tsx +++ b/apps/cs/src/shared/ui/page-layout.tsx @@ -1,3 +1,6 @@ +import { PageTitle } from "@common/components"; +import { useMenuStore } from "../lib/use-menu-store"; + function PageLayout({ children, pageTitle, @@ -5,11 +8,15 @@ function PageLayout({ children: React.ReactNode; pageTitle: string; }) { + const { currentMenuInfo } = useMenuStore((state) => state); + + const menuPaths = currentMenuInfo?.menuPath + ?.split(">") + .map((path) => path.trim()); + return (
-

- {pageTitle} -

+ {children}
); diff --git a/packages/common/components/src/api/menu.ts b/packages/common/components/src/api/menu.ts index e9899c111..d109a9812 100644 --- a/packages/common/components/src/api/menu.ts +++ b/packages/common/components/src/api/menu.ts @@ -13,6 +13,7 @@ export interface MenuItem { urlPath: string; menuLnkgTypeCd: string; menuLnkgSn: number; + menuPath: string; qrcd: string; useYn: string; otptYn: string; diff --git a/packages/common/components/src/ui/index.ts b/packages/common/components/src/ui/index.ts index 650eb30e1..dde5f5879 100644 --- a/packages/common/components/src/ui/index.ts +++ b/packages/common/components/src/ui/index.ts @@ -13,6 +13,7 @@ export * from "./input"; export * from "./label"; export * from "./layout"; export * from "./loading"; +export * from "./page-title"; export * from "./popover"; export * from "./radio-group"; export * from "./select"; diff --git a/packages/common/components/src/ui/layout/base.tsx b/packages/common/components/src/ui/layout/base.tsx index b48e2d9d9..c6a564c37 100644 --- a/packages/common/components/src/ui/layout/base.tsx +++ b/packages/common/components/src/ui/layout/base.tsx @@ -1,22 +1,22 @@ +import type { QueryClient } from "@tanstack/react-query"; +import type { MenuItem } from "../../api"; import { QueryProvider } from "../../provider/query-provider"; -import { SidebarProvider } from "../sidebar"; import { LNB } from "../lnb"; import { Header } from "../header"; import { Footer } from "./footer"; - +import { SidebarProvider } from "../sidebar"; import "./common.css"; -import type { QueryClient } from "@tanstack/react-query"; export function BaseLayout({ children, queryClient, currentPath, - routeHandler, + handleMenuClick, }: { children: React.ReactNode; queryClient?: QueryClient; currentPath?: string; - routeHandler?: (path: string) => void; + handleMenuClick?: (item: MenuItem) => void; }) { return ( @@ -24,7 +24,7 @@ export function BaseLayout({
- +
{children} diff --git a/packages/common/components/src/ui/lnb/index.tsx b/packages/common/components/src/ui/lnb/index.tsx index 197a44a9e..ef1d39a79 100644 --- a/packages/common/components/src/ui/lnb/index.tsx +++ b/packages/common/components/src/ui/lnb/index.tsx @@ -22,11 +22,11 @@ import { getParentIds } from "./lib"; export function LNB({ viewOnly, currentPath, - routeHandler, + handleMenuClick, }: { viewOnly?: boolean; currentPath?: string; - routeHandler?: (path: string) => void; + handleMenuClick?: (item: MenuItem) => void; }) { const { data: menuList } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES], @@ -45,7 +45,7 @@ export function LNB({ const handleClick = () => { setCookie("menu-code-key", item.menuCd.toString()); - routeHandler?.(item.urlPath); + handleMenuClick?.(item); }; if (item.children) { diff --git a/packages/common/components/src/ui/page-title/index.tsx b/packages/common/components/src/ui/page-title/index.tsx new file mode 100644 index 000000000..69796a428 --- /dev/null +++ b/packages/common/components/src/ui/page-title/index.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +const PageTitle = ({ + title, + menuPaths, +}: { + title: string; + menuPaths: string[]; +}) => { + return ( +
+

{title}

+ {menuPaths.length && ( +
+ + {menuPaths?.map((path, i) => ( + + {i !== 0 && ( + + )} + {path} + + ))} +
+ )} +
+ ); +}; + +export { PageTitle }; diff --git a/packages/common/components/src/ui/page-title/page-title.stories.tsx b/packages/common/components/src/ui/page-title/page-title.stories.tsx new file mode 100644 index 000000000..127e2a0fa --- /dev/null +++ b/packages/common/components/src/ui/page-title/page-title.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { PageTitle } from "."; + +const meta: Meta = { + title: "UI/PageTitle", +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +};