Merge branch 'main' into deploy
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user