[apps/cs] feat: 임시저장 메뉴 미리보기 기능 작업

This commit is contained in:
2025-07-09 14:19:10 +09:00
parent 49a699f533
commit eef73f36ff
5 changed files with 129 additions and 62 deletions
+83 -48
View File
@@ -1,7 +1,15 @@
import { useState } from "react";
import { useQuery } from "@tanstack/react-query";
import type { UseFormReturn } from "react-hook-form";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { Button } from "@common/components";
import {
Button,
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
LNB,
} from "@common/components";
import { MenuApi, menuFormSchema, type MenuFormData } from "@/entities/menu";
import { ManagementAuthorityDialog } from "@/entities/menu-authority";
import { Form } from "@/shared/ui/form";
@@ -33,7 +41,7 @@ function MenuForm({
handleDelete,
}: MenuFormProps) {
const isModifyMode = !!menuCd;
const [open, setOpen] = useState(false);
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
@@ -96,55 +104,82 @@ function MenuForm({
const isSubmitDisabled = !form.watch("siteId");
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
</h3>
<div className="flex items-center gap-2">
<MenuHistoryDialog menuCd={menuCd} />
<ManagementAuthorityDialog menuCd={menuCd} />
<>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
<div className="card card-border">
<div className="card-header">
<h3 className="text-2xl font-medium text-label">
{isModifyMode ? "메뉴 수정" : "메뉴 등록"}
</h3>
<div className="flex items-center gap-2">
<MenuHistoryDialog menuCd={menuCd} />
<ManagementAuthorityDialog menuCd={menuCd} />
</div>
</div>
<div className="card">
<BaseMenuForm form={form} menuCd={menuCd} />
<TypeMenuForm form={form} menuType={menuType} />
</div>
</div>
<div className="card">
<BaseMenuForm form={form} menuCd={menuCd} />
<TypeMenuForm form={form} menuType={menuType} />
<div className="flex justify-center gap-4 pb-12 pt-10">
{isModifyMode && (
<Button
size="lg"
color="white"
type="button"
onClick={handleLoadTemporaryData}
>
불러오기
</Button>
)}
{isModifyMode && (
<Button
size="lg"
color="gray"
type="button"
onClick={handleMenuTemporaryStorage}
>
임시저장
</Button>
)}
{isModifyMode && (
<Button
size="lg"
color="emerald"
type="button"
onClick={() => setOpen(true)}
>
미리보기
</Button>
)}
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
저장
</Button>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
)}
</div>
</div>
<div className="flex justify-center gap-4 pb-12 pt-10">
{isModifyMode && (
<Button
size="lg"
color="white"
type="button"
onClick={handleLoadTemporaryData}
>
불러오기
</Button>
)}
{isModifyMode && (
<Button
size="lg"
color="gray"
type="button"
onClick={handleMenuTemporaryStorage}
>
임시저장
</Button>
)}
<Button size="lg" type="submit" disabled={isSubmitDisabled}>
저장
</Button>
{isModifyMode && (
<Button size="lg" color="red" type="button" onClick={onDelete}>
삭제
</Button>
)}
</div>
</form>
</Form>
</form>
</Form>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent className="max-h-[90vh]">
<DialogHeader>
<DialogTitle>메뉴 미리보기</DialogTitle>
</DialogHeader>
<div className="flex h-full min-h-[60vh] flex-col gap-4">
<LNB
currentPath={form.watch("urlPath") ?? ""}
propSiteId={form.watch("siteId")}
previewMode
classNames={{ wrapper: "static h-full border-none p-0" }}
/>
</div>
</DialogContent>
</Dialog>
</>
);
}
@@ -21,6 +21,10 @@ export interface MenuItem {
requiredUrlPath: boolean;
}
export interface TreeMenu extends MenuItem {
menuHlpCn: string;
}
const client = getApiClient();
export const getMenuList = async (siteId: string) => {
@@ -29,3 +33,10 @@ export const getMenuList = async (siteId: string) => {
);
return response.data;
};
export const getPreviewTreeMenuList = async (siteId: string) => {
const response = await client.get<TreeMenu[]>(
`${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree/preview`,
);
return response.data;
};
@@ -1,5 +1,6 @@
export const ENDPOINT = {
CMS_SERVICE: {
MENUS: "/cms-service/api/v1/menus",
MENU_ROLES: "/cms-service/api/v1/menu-roles",
SITES: "/cms-service/api/v1/sites",
POPUPS: "/cms-service/api/v1/popups",
@@ -15,6 +15,7 @@ export * from "./icon-finder";
export * from "./input";
export * from "./label";
export * from "./layout";
export * from "./lnb";
export * from "./loading";
export * from "./page-title";
export * from "./popover";
+33 -14
View File
@@ -3,9 +3,7 @@
import { useState, useEffect, useRef } from "react";
import { useQuery } from "@tanstack/react-query";
import { ChevronRight } from "lucide-react";
import { ENDPOINT } from "../../config";
import { cn, setCookie } from "../../lib";
import { getMenuList, type MenuItem } from "../../api";
import { getParentIds } from "./lib";
import {
Sidebar,
SidebarContent,
@@ -22,29 +20,47 @@ import {
CollapsibleContent,
CollapsibleTrigger,
} from "../collapsible";
import { getParentIds } from "./lib";
import { getCookie } from "../../lib";
const siteId = getCookie("site-id-key") ?? "";
import { ENDPOINT } from "../../config";
import { cn, getCookie, setCookie } from "../../lib";
import { getMenuList, getPreviewTreeMenuList, type MenuItem } from "../../api";
export function LNB({
viewOnly,
currentPath,
propSiteId,
previewMode,
classNames,
handleMenuClick,
}: {
viewOnly?: boolean;
currentPath?: string;
propSiteId?: string;
previewMode?: boolean;
classNames?: {
wrapper?: string;
};
handleMenuClick?: (item: MenuItem) => void;
}) {
const { data: menuList } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
queryFn: () => getMenuList(siteId),
enabled: !viewOnly,
});
const [defaultOpenMenuIds, setDefaultOpenMenuIds] = useState<number[]>([]);
const prevPathRef = useRef(currentPath);
const cookieSiteId = getCookie("site-id-key") ?? "";
const siteId = propSiteId ?? cookieSiteId;
const { data: menuListData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
queryFn: () => getMenuList(siteId),
enabled: !viewOnly && !previewMode,
});
const { data: previewTreeMenuListData } = useQuery({
queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId, "preview"],
queryFn: () => getPreviewTreeMenuList(siteId),
enabled: previewMode,
});
const menuList = previewMode ? previewTreeMenuListData : menuListData;
function isPathMatched(path: string) {
if (!path) return false;
return currentPath?.startsWith(path);
@@ -191,7 +207,10 @@ export function LNB({
return (
<Sidebar
collapsible="icon"
className="border-border bg-background px-4 py-[6rem]"
className={cn(
"border-border bg-background px-4 py-[6rem]",
classNames?.wrapper,
)}
>
<SidebarContent className="bg-background">
<SidebarMenu className="gap-0">