Merge branch 'main' into deploy

This commit is contained in:
2026-08-06 13:42:33 +09:00
9 changed files with 131 additions and 190 deletions
+2
View File
@@ -1,4 +1,5 @@
import { PageTitle } from "@common/components";
import { useMenuStore } from "../lib/use-menu-store";
function PageLayout({ children }: { children: React.ReactNode }) {
@@ -12,6 +13,7 @@ function PageLayout({ children }: { children: React.ReactNode }) {
<div className="h-auto min-h-full bg-container pb-2">
<PageTitle
title={currentMenuInfo?.menuKornNm ?? ""}
menuCd={currentMenuInfo?.menuCd}
menuPaths={menuPaths ?? []}
/>
{children}
@@ -42,6 +42,18 @@ export const getMenuList = async (siteId: string): Promise<MenuItem[]> => {
return response.json();
};
export const getMenuDetail = async (menuCd: number): Promise<MenuItem> => {
const response = await fetch(
`${BASE_URL}${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`,
{
method: "GET",
headers: getAuthHeaders(),
credentials: "include",
},
);
return response.json();
};
export const getPreviewTreeMenuList = async (
siteId: string,
): Promise<TreeMenu[]> => {
@@ -1,55 +0,0 @@
"use client";
import { createContext, useContext, useState, useCallback } from "react";
interface HelpDialogContextType {
openHelp: () => void;
closeHelp: () => void;
isOpen: boolean;
setHelpContent: (content: string) => void;
helpContent: string;
}
const HelpDialogContext = createContext<HelpDialogContextType | undefined>(
undefined,
);
export function HelpDialogProvider({
children,
}: {
children: React.ReactNode;
}) {
const [isOpen, setIsOpen] = useState(false);
const [helpContent, setHelpContent] = useState<string>("");
const openHelp = useCallback(() => {
setIsOpen(true);
}, []);
const closeHelp = useCallback(() => {
setIsOpen(false);
}, []);
return (
<HelpDialogContext.Provider
value={{
openHelp,
closeHelp,
isOpen,
setHelpContent,
helpContent,
}}
>
{children}
</HelpDialogContext.Provider>
);
}
export function useHelpDialog() {
const context = useContext(HelpDialogContext);
if (context === undefined) {
throw new Error("useHelpDialog must be used within a HelpDialogProvider");
}
return context;
}
@@ -2,7 +2,6 @@ import type { AuthService, TokenManager } from "@common/auth";
import type { QueryClient } from "@tanstack/react-query";
import { QueryProvider } from "../../provider/query-provider";
import { PopupProvider } from "../../provider/popup-provider";
import { HelpDialogProvider } from "../../provider/help-dialog-context";
import type { MenuItem } from "../../api";
import { SidebarProvider } from "../sidebar";
import { LNB } from "../lnb";
@@ -29,26 +28,24 @@ export function BaseLayout({
return (
<QueryProvider queryClient={queryClient}>
<PopupProvider>
<HelpDialogProvider>
<SidebarProvider>
<div className="flex-1">
<Header
authService={authService}
tokenManager={authService ? undefined : tokenManager}
<SidebarProvider>
<div className="flex-1">
<Header
authService={authService}
tokenManager={authService ? undefined : tokenManager}
/>
<div className="flex">
<LNB
currentPath={currentPath}
handleMenuClick={handleMenuClick}
/>
<div className="flex">
<LNB
currentPath={currentPath}
handleMenuClick={handleMenuClick}
/>
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col">
<div className="flex-1">{children}</div>
<Footer />
</div>
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col">
<div className="flex-1">{children}</div>
<Footer />
</div>
</div>
</SidebarProvider>
</HelpDialogProvider>
</div>
</SidebarProvider>
</PopupProvider>
</QueryProvider>
);
@@ -1,7 +1,6 @@
import type { QueryClient } from "@tanstack/react-query";
import { QueryProvider } from "../../provider/query-provider";
import { PopupProvider } from "../../provider/popup-provider";
import { HelpDialogProvider } from "../../provider/help-dialog-context";
import type { MenuItem } from "../../api";
import { SidebarProvider } from "../sidebar";
import { LNB } from "../lnb";
@@ -25,22 +24,20 @@ export function NoFooterLayout({
return (
<QueryProvider queryClient={queryClient}>
<PopupProvider>
<HelpDialogProvider>
<SidebarProvider>
<div className="flex-1">
<Header authService={authService} />
<div className="flex">
<LNB
currentPath={currentPath}
handleMenuClick={handleMenuClick}
/>
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
<div className="flex-1">{children}</div>
</div>
<SidebarProvider>
<div className="flex-1">
<Header authService={authService} />
<div className="flex">
<LNB
currentPath={currentPath}
handleMenuClick={handleMenuClick}
/>
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
<div className="flex-1">{children}</div>
</div>
</div>
</SidebarProvider>
</HelpDialogProvider>
</div>
</SidebarProvider>
</PopupProvider>
</QueryProvider>
);
@@ -2,7 +2,6 @@ import type { AuthService } from "@common/auth";
import type { QueryClient } from "@tanstack/react-query";
import { QueryProvider } from "../../provider/query-provider";
import { PopupProvider } from "../../provider/popup-provider";
import { HelpDialogProvider } from "../../provider/help-dialog-context";
import type { MenuItem } from "../../api";
import { SidebarProvider } from "../sidebar";
import { Header } from "../header";
@@ -23,19 +22,17 @@ export function NoLNBLayout({
return (
<QueryProvider queryClient={queryClient}>
<PopupProvider>
<HelpDialogProvider>
<SidebarProvider>
<div className="flex-1">
<Header authService={authService} />
<div className="flex">
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
<div className="flex-1">{children}</div>
<Footer />
</div>
<SidebarProvider>
<div className="flex-1">
<Header authService={authService} />
<div className="flex">
<div className="flex min-h-[calc(100vh-50px)] flex-1 flex-col overflow-auto">
<div className="flex-1">{children}</div>
<Footer />
</div>
</div>
</SidebarProvider>
</HelpDialogProvider>
</div>
</SidebarProvider>
</PopupProvider>
</QueryProvider>
);
@@ -1,5 +1,4 @@
import { QueryProvider } from "../../provider/query-provider";
import { HelpDialogProvider } from "../../provider/help-dialog-context";
import { SidebarProvider } from "../sidebar";
import { LNB } from "../lnb";
import { Header } from "../header";
@@ -17,8 +16,7 @@ export function VBaseLayout({
}) {
return (
<QueryProvider queryClient={queryClient}>
<HelpDialogProvider>
<SidebarProvider>
<SidebarProvider>
<div className="flex-1">
<Header viewOnly />
<div className="flex h-[calc(100vh-50px)]">
@@ -32,7 +30,6 @@ export function VBaseLayout({
</div>
</div>
</SidebarProvider>
</HelpDialogProvider>
</QueryProvider>
);
}
@@ -22,16 +22,8 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from "../collapsible";
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../dialog";
import { ENDPOINT } from "../../config";
import { cn, getCookie, setCookie } from "../../lib";
import { useHelpDialog } from "../../provider/help-dialog-context";
import {
getMenuList,
getPreviewTreeMenuList,
@@ -65,13 +57,6 @@ export function LNB({
const [currentMenuCode, setCurrentMenuCode] = useState<number | null>(null);
const prevPathRef = useRef(currentPath);
const { browser } = useDetectDevice();
const {
isOpen: isHelpModalOpen,
openHelp,
closeHelp,
setHelpContent,
helpContent,
} = useHelpDialog();
const appSiteId = getCookie("site-id-key") ?? "";
const siteId = propSiteId ?? appSiteId;
@@ -110,7 +95,6 @@ export function LNB({
const matchedMenuIds: number[] = [];
let matchedMenuCode: number | null = null;
let matchedMenuHelp: string = "";
const findMatchedMenus = (items: MenuItem[]) => {
items.forEach((item) => {
@@ -127,7 +111,6 @@ export function LNB({
if (matchedMenuCode) return;
if (currentPath === item.urlPath) {
matchedMenuCode = item.menuCd;
matchedMenuHelp = item.menuHlpCn || "";
}
if (item.children) findMatchedMenuCode(item.children);
});
@@ -148,7 +131,6 @@ export function LNB({
if (matchedMenuCode) {
setCurrentMenuCode(matchedMenuCode);
setHelpContent(matchedMenuHelp);
}
// 접속 로그 저장
@@ -298,46 +280,6 @@ export function LNB({
<MenuItem key={item.menuCd} item={item} />
))}
</SidebarMenu>
{/* 도움말 아이콘 */}
<div className="mt-auto pb-4">
<Dialog
open={isHelpModalOpen}
onOpenChange={(open) => (open ? openHelp() : closeHelp())}
>
<DialogTrigger asChild></DialogTrigger>
<DialogContent className="flex max-h-[90vh] flex-col sm:max-w-[85rem]">
<DialogHeader>
<DialogTitle>도움말</DialogTitle>
</DialogHeader>
<div className="min-h-0 flex-1 overflow-y-auto py-4">
{helpContent ? (
<div
style={{
fontSize: "16px",
lineHeight: "1.5",
}}
>
<div
dangerouslySetInnerHTML={{
__html: helpContent
.replace(/<body[^>]*>/gi, "<div>")
.replace(/<\/body>/gi, "</div>"),
}}
style={{
fontSize: "inherit",
}}
/>
</div>
) : (
<p className="text-sm text-muted-foreground">
현재 페이지에 대한 도움말이 없습니다.
</p>
)}
</div>
</DialogContent>
</Dialog>
</div>
</SidebarContent>
</Sidebar>
);
@@ -1,43 +1,95 @@
"use client";
import React from "react";
import { useHelpDialog } from "../../provider/help-dialog-context";
import React, { useState } from "react";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../dialog";
import { useQuery } from "@tanstack/react-query";
import { getMenuDetail } from "../../api";
import { ENDPOINT } from "../../config";
const PageTitle = ({
title,
menuCd,
menuPaths,
}: {
title: string;
menuCd?: number;
menuPaths: string[];
}) => {
const { openHelp } = useHelpDialog();
const [isHelpDialogOpen, setIsHelpDialogOpen] = useState(false);
const { data: menuDetail } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, menuCd],
queryFn: () => getMenuDetail(menuCd!),
enabled: !!menuCd,
});
return (
<div className="flex w-full items-center justify-between px-[1.8rem] pt-[2rem]">
<div className="flex items-center gap-3">
<h2 className="text-[2.4rem] font-semibold text-title">{title}</h2>
<>
<div className="flex w-full items-center justify-between px-[1.8rem] pt-[2rem]">
<div className="flex items-center gap-3">
<h2 className="text-[2.4rem] font-semibold text-title">{title}</h2>
</div>
<div className="flex items-center gap-3">
{menuPaths.length > 0 && (
<div className="flex items-center gap-3">
<i className="diveicon di-home-fill text-foreground" />
{menuPaths?.map((path, i) => (
<React.Fragment key={`${i}-${path}`}>
{i !== 0 && (
<i className="diveicon di-arrow-forward text-foreground" />
)}
<span className="text-xl text-foreground">{path}</span>
</React.Fragment>
))}
</div>
)}
<button
onClick={() => setIsHelpDialogOpen(true)}
className="flex cursor-pointer items-center gap-3 transition-opacity hover:opacity-80"
>
<img
src="/images/help.png"
alt="도움말"
className="h-[20px] w-[20px]"
/>
</button>
</div>
</div>
<div className="flex items-center gap-3">
{menuPaths.length > 0 && (
<div className="flex items-center gap-3">
<i className="diveicon di-home-fill text-foreground" />
{menuPaths?.map((path, i) => (
<React.Fragment key={`${i}-${path}`}>
{i !== 0 && (
<i className="diveicon di-arrow-forward text-foreground" />
)}
<span className="text-xl text-foreground">{path}</span>
</React.Fragment>
))}
</div>
)}
<button
onClick={openHelp}
className="flex items-center gap-3 cursor-pointer hover:opacity-80 transition-opacity"
>
<img src="/images/help.png" alt="도움말" className="w-[20px] h-[20px]" />
</button>
</div>
</div>
{/* 도움말 다이얼로그 */}
<Dialog open={isHelpDialogOpen} onOpenChange={setIsHelpDialogOpen}>
<DialogContent className="flex max-h-[90vh] flex-col sm:max-w-[85rem]">
<DialogHeader>
<DialogTitle>도움말</DialogTitle>
</DialogHeader>
<div className="min-h-0 flex-1 overflow-y-auto py-4">
{menuDetail?.menuHlpCn ? (
<div
style={{
fontSize: "16px",
lineHeight: "1.5",
}}
>
<div
dangerouslySetInnerHTML={{
__html: menuDetail.menuHlpCn
.replace(/<body[^>]*>/gi, "<div>")
.replace(/<\/body>/gi, "</div>"),
}}
style={{
fontSize: "inherit",
}}
/>
</div>
) : (
<p className="text-lg text-muted-foreground">
현재 페이지에 대한 도움말이 없습니다.
</p>
)}
</div>
</DialogContent>
</Dialog>
</>
);
};