From d4f7cf140e2e446a7e47a5216c491a387700d16d Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 26 Mar 2025 15:07:53 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=A0=80=EC=9E=A5?= =?UTF-8?q?=20=EB=B0=8F=20=EC=88=98=EC=A0=95=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/app/styles/components.css | 80 ++++++++++++++----- apps/cs/src/pages/menu/api/menu-service.ts | 15 +++- .../cs/src/pages/menu/model/menu-interface.ts | 7 +- apps/cs/src/pages/menu/ui/base-menu-form.tsx | 25 +++--- apps/cs/src/pages/menu/ui/base-type-form.tsx | 4 +- apps/cs/src/pages/menu/ui/board-menu-form.tsx | 4 +- .../cs/src/pages/menu/ui/bottom-menu-form.tsx | 8 +- .../src/pages/menu/ui/content-menu-form.tsx | 8 +- apps/cs/src/pages/menu/ui/empty-menu-form.tsx | 4 +- .../cs/src/pages/menu/ui/inside-menu-form.tsx | 8 +- apps/cs/src/pages/menu/ui/menu-form.tsx | 27 ++++--- apps/cs/src/pages/menu/ui/menu-node.tsx | 14 +++- apps/cs/src/pages/menu/ui/menu-page.tsx | 59 ++++++++++++-- apps/cs/src/pages/menu/ui/menu-tree.tsx | 18 +++-- .../src/pages/menu/ui/outside-menu-form.tsx | 8 +- 15 files changed, 202 insertions(+), 87 deletions(-) diff --git a/apps/cs/src/app/styles/components.css b/apps/cs/src/app/styles/components.css index c1eb79210..17cd78834 100644 --- a/apps/cs/src/app/styles/components.css +++ b/apps/cs/src/app/styles/components.css @@ -9,43 +9,84 @@ color: #666; } -.card_width10 {width:calc(10% - 12px);} +.card_width10 { + width: calc(10% - 12px); +} -.card_width20 {width:calc(20% - 12px);} +.card_width20 { + width: calc(20% - 12px); +} -.card_width25 {width:calc(25% - 12px);} +.card_width25 { + width: calc(25% - 12px); +} -.card_width30 {width:calc(30% - 12px);} +.card_width30 { + width: calc(30% - 12px); +} -.card_width33 {width:calc(33.333% - 12px);} +.card_width33 { + width: calc(33.333% - 12px); +} -.card_width40 {width:calc(40% - 12px);} +.card_width40 { + width: calc(40% - 12px); +} -.card_width45 {width:calc(45% - 12px);} +.card_width45 { + width: calc(45% - 12px); +} -.card_width50 {width:calc(50% - 12px);} +.card_width50 { + width: calc(50% - 12px); +} -.card_width60 {width:calc(60% - 12px);} +.card_width60 { + width: calc(60% - 12px); +} -.card_width66 {width:calc(66.666% - 12px);} +.card_width66 { + width: calc(66.666% - 12px); +} -.card_width70 {width:calc(70% - 12px);} +.card_width70 { + width: calc(70% - 12px); +} -.card_width75 {width:calc(75% - 12px);} +.card_width75 { + width: calc(75% - 12px); +} -.card_width80 {width:calc(80% - 12px);} +.card_width80 { + width: calc(80% - 12px); +} -.card_width90 {width:calc(90% - 12px);} +.card_width90 { + width: calc(90% - 12px); +} -.card_width100 {width:calc(100% - 12px);} +.card_width100 { + width: calc(100% - 12px); +} -.card_nopadding {padding:0 !important;} +.card_nopadding { + padding: 0 !important; +} -.card_nomargin {width:100%;margin:0 !important;padding:0 !important;} +.card_nomargin { + width: 100%; + margin: 0 !important; + padding: 0 !important; +} -.topline {border-top: 1px solid #ccc;} +.topline { + border-top: 1px solid #ccc; +} -.bottomline {border-bottom: 1px solid #e6eef3;background: #fff;} +.bottomline { + border-bottom: 1px solid #e6eef3; + background: #fff; +} .card-border { border: 1px solid #dbe2e6; @@ -54,6 +95,7 @@ } .card-header { + margin-top: -0.6rem; margin-left: -0.6rem; margin-right: -0.6rem; display: flex; diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index 4fe6c845a..4825da594 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -1,8 +1,21 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import type { Menu, MenuService, TreeMenu } from "../model/menu-interface"; +import type { + Menu, + MenuFormData, + MenuService, + TreeMenu, +} from "../model/menu-interface"; export class MenuApiService implements MenuService { + public async createMenu(data: MenuFormData): Promise { + const response = await client.post( + `${ENDPOINT.CMS_SERVICE.MENUS}`, + data, + ); + return response.data; + } + public async getTreeMenuList(siteId: string): Promise { const response = await client.get( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index fc22c462c..dfc8de878 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -53,17 +53,18 @@ export const menuFormSchema = z.object({ MENU_TYPE.BOTTOM, ]), menuLnkgSn: z.number().nullish(), - otptYn: z.enum(["Y", "N"]).default("Y").nullish(), + otptYn: z.string().default("Y").nullish(), prntMenuCd: z.number().nullish(), sortSeq: z.number(), - evlUseYn: z.enum(["Y", "N"]).nullish(), + evlUseYn: z.string().default("Y").nullish(), qrcd: z.string().nullish(), - useYn: z.enum(["Y", "N"]).default("Y").nullish(), + useYn: z.string().default("Y").nullish(), }); export type MenuFormData = z.infer; export interface MenuService { + createMenu: (data: MenuFormData) => Promise; getTreeMenuList: (siteId: string) => Promise; modifyTreeMenu: (siteId: string, data: TreeMenu[]) => Promise; modifyMenuName: (menuCd: number, name: string) => Promise; diff --git a/apps/cs/src/pages/menu/ui/base-menu-form.tsx b/apps/cs/src/pages/menu/ui/base-menu-form.tsx index 9af6dd6f1..62a602a33 100644 --- a/apps/cs/src/pages/menu/ui/base-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-menu-form.tsx @@ -20,10 +20,10 @@ import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; interface BaseMenuFormProps { form: UseFormReturn; - menuId?: number; + menuCd?: number; } -export function BaseMenuForm({ form, menuId }: BaseMenuFormProps) { +function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) { return ( @@ -31,14 +31,16 @@ export function BaseMenuForm({ form, menuId }: BaseMenuFormProps) { - - 메뉴ID - -
- {menuId} -
-
-
+ {menuCd && ( + + 메뉴ID + +
+ {menuCd} +
+
+
+ )} 메뉴명 @@ -50,6 +52,7 @@ export function BaseMenuForm({ form, menuId }: BaseMenuFormProps) { @@ -128,3 +131,5 @@ export function BaseMenuForm({ form, menuId }: BaseMenuFormProps) {
); } + +export { BaseMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/base-type-form.tsx b/apps/cs/src/pages/menu/ui/base-type-form.tsx index 3d0c57eb7..107e92b60 100644 --- a/apps/cs/src/pages/menu/ui/base-type-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-type-form.tsx @@ -17,7 +17,7 @@ import { } from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; -export function BaseTypeForm({ form }: { form: UseFormReturn }) { +function BaseTypeForm({ form }: { form: UseFormReturn }) { const handleIconClick = (icon: string) => { form.setValue("iconNm", icon); }; @@ -124,3 +124,5 @@ export function BaseTypeForm({ form }: { form: UseFormReturn }) { ); } + +export { BaseTypeForm }; diff --git a/apps/cs/src/pages/menu/ui/board-menu-form.tsx b/apps/cs/src/pages/menu/ui/board-menu-form.tsx index 91ebc68d3..cfd59b563 100644 --- a/apps/cs/src/pages/menu/ui/board-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/board-menu-form.tsx @@ -18,7 +18,7 @@ import { import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeForm } from "./base-type-form"; -export function BoardMenuForm({ form }: { form: UseFormReturn }) { +function BoardMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -142,3 +142,5 @@ export function BoardMenuForm({ form }: { form: UseFormReturn }) {
); } + +export { BoardMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx b/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx index 5214f3a13..63069aac5 100644 --- a/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx @@ -3,11 +3,7 @@ import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeForm } from "./base-type-form"; -export function BottomMenuForm({ - form, -}: { - form: UseFormReturn; -}) { +function BottomMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -20,3 +16,5 @@ export function BottomMenuForm({
); } + +export { BottomMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/content-menu-form.tsx b/apps/cs/src/pages/menu/ui/content-menu-form.tsx index c5abc36bf..153eede5b 100644 --- a/apps/cs/src/pages/menu/ui/content-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/content-menu-form.tsx @@ -18,11 +18,7 @@ import { import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeForm } from "./base-type-form"; -export function ContentMenuForm({ - form, -}: { - form: UseFormReturn; -}) { +function ContentMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -146,3 +142,5 @@ export function ContentMenuForm({
); } + +export { ContentMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx index d65ffd602..4c97e1c66 100644 --- a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx @@ -3,7 +3,7 @@ import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeForm } from "./base-type-form"; -export function EmptyMenuForm({ form }: { form: UseFormReturn }) { +function EmptyMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -16,3 +16,5 @@ export function EmptyMenuForm({ form }: { form: UseFormReturn }) {
); } + +export { EmptyMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx index 03325dc7b..4b894690c 100644 --- a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx @@ -3,11 +3,7 @@ import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeForm } from "./base-type-form"; -export function InsideMenuForm({ - form, -}: { - form: UseFormReturn; -}) { +function InsideMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -20,3 +16,5 @@ export function InsideMenuForm({
); } + +export { InsideMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index 5fce6d13d..2db4c979a 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -12,21 +12,18 @@ import { BoardMenuForm } from "./board-menu-form"; import { InsideMenuForm } from "./inside-menu-form"; import { OutsideMenuForm } from "./outside-menu-form"; import { BottomMenuForm } from "./bottom-menu-form"; +import { PermissionsManagementDialog } from "./permissions-management-dialog"; interface MenuFormProps { form: UseFormReturn; - menuId?: number; - handleSave: (data: MenuFormData) => Promise; - handleDelete?: () => Promise; + menuCd?: number; + handleSave: (data: MenuFormData) => void; + handleDelete?: () => void; } -export function MenuForm({ - form, - menuId, - handleSave, - handleDelete, -}: MenuFormProps) { - const isModifyMode = !!menuId; +function MenuForm({ form, menuCd, handleSave, handleDelete }: MenuFormProps) { + const isModifyMode = !!menuCd; + const { confirm } = useConfirm(); const [onConfirm, setOnConfirm] = useState(false); @@ -53,6 +50,7 @@ export function MenuForm({ }; const menuType = form.watch("menuLnkgTypeCd"); + const isSubmitDisabled = !form.watch("siteId"); return ( <> @@ -61,18 +59,19 @@ export function MenuForm({

- 메뉴 등록/수정 + {isModifyMode ? "메뉴 수정" : "메뉴 등록"}

+
- +
{renderTypeForm(menuType, form)}
- {isModifyMode && ( @@ -106,3 +105,5 @@ function renderTypeForm(menuType: string, form: UseFormReturn) { return null; } } + +export { MenuForm }; diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx index f8a150633..827e480d4 100644 --- a/apps/cs/src/pages/menu/ui/menu-node.tsx +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -1,10 +1,11 @@ import { useState } from "react"; -import { useMutation } from "@tanstack/react-query"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { NodeModel } from "@minoru/react-dnd-treeview"; -import { cn } from "@common/components/lib"; import { Button, Input } from "@common/components/ui"; -import type { Menu } from "../model/menu-interface"; +import { cn } from "@common/components/lib"; +import { ENDPOINT } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; +import type { Menu } from "../model/menu-interface"; type MenuNodeProps = { node: NodeModel; @@ -26,11 +27,18 @@ function MenuNode({ const { id, text, data } = node; const indent = depth * 10 + 5; + const queryClient = useQueryClient(); + const { mutateAsync: modifyMenuName } = useMutation({ mutationFn: (nodeName: string) => { const encodedName = encodeURIComponent(nodeName); return MenuApi.modifyMenuName(id as number, encodedName); }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS], + }); + }, }); const [editMode, setEditMode] = useState(false); diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 5cc63b627..f159cbcf2 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -2,8 +2,9 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; -import { useQuery } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; +import type { NodeModel } from "@minoru/react-dnd-treeview"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Button, Select, @@ -15,6 +16,8 @@ import { import { SiteApi } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; import { PageLayout } from "@/shared/ui/page-layout"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import type { Menu, MenuType } from "../model/menu-interface"; import { MenuApi } from "../api/menu-service"; import { MENU_TYPE, @@ -26,12 +29,23 @@ import { MenuForm } from "./menu-form"; import { generateTreeData } from "../lib/utils"; function MenuPage() { + const queryClient = useQueryClient(); + const { setMessage: alert } = useAlertStore((state) => state); + const [siteId, setSiteId] = useState(""); + const [selectedMenu, setSelectedMenu] = useState(null); const { data: treeData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], queryFn: () => MenuApi.getTreeMenuList(siteId), - select: (data) => generateTreeData(data), + select: (data) => { + const treeData = generateTreeData(data); + const defaultSortSeq = + treeData.filter((node) => node.data?.levelNo === 1).length + 1; + form.setValue("sortSeq", defaultSortSeq); + + return treeData; + }, enabled: !!siteId, }); @@ -45,15 +59,40 @@ function MenuPage() { })), }); + const { mutate: createMenu } = useMutation({ + mutationFn: (data: MenuFormData) => MenuApi.createMenu(data), + onSuccess: () => { + alert("저장되었습니다."); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS], + }); + }, + }); + const form = useForm({ resolver: zodResolver(menuFormSchema), defaultValues: { menuLnkgTypeCd: MENU_TYPE.EMPTY, }, + values: { + ...selectedMenu, + siteId, + sortSeq: selectedMenu?.sortSeq ?? 1, + levelNo: selectedMenu?.levelNo ?? 1, + menuKornNm: selectedMenu?.menuKornNm ?? "", + menuLnkgTypeCd: + (selectedMenu?.menuLnkgTypeCd as MenuType) ?? MENU_TYPE.EMPTY, + }, }); - const handleSave = async (data: MenuFormData) => { - console.log(data); + const handleSiteIdChange = (value: string) => { + setSiteId(value); + form.setValue("siteId", value); + }; + + const handleNodeSelect = (node: NodeModel) => { + if (!node.data) return; + setSelectedMenu(node.data); }; const handleDelete = async () => {}; @@ -62,7 +101,7 @@ function MenuPage() {
- @@ -85,7 +124,12 @@ function MenuPage() {
- +
)} @@ -93,7 +137,8 @@ function MenuPage() {
diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index 629dd1252..02f7ffbba 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -1,6 +1,5 @@ "use client"; -import { useState } from "react"; import { DndProvider } from "react-dnd"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { @@ -18,9 +17,16 @@ import { MenuNode } from "./menu-node"; type MenuTreeProps = { treeData: NodeModel[]; siteId: string; + selectedMenu: Menu | null; + onSelectNode: (node: NodeModel) => void; }; -function MenuTree({ treeData, siteId }: MenuTreeProps) { +function MenuTree({ + treeData, + siteId, + selectedMenu, + onSelectNode, +}: MenuTreeProps) { const queryClient = useQueryClient(); const { mutate: modifyTreeMenu } = useMutation({ @@ -33,10 +39,6 @@ function MenuTree({ treeData, siteId }: MenuTreeProps) { }, }); - const [selectedNode, setSelectedNode] = useState | null>( - null, - ); - return ( )} onDrop={handleDropNode(modifyTreeMenu)} diff --git a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx b/apps/cs/src/pages/menu/ui/outside-menu-form.tsx index 750a00a21..6649dd979 100644 --- a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/outside-menu-form.tsx @@ -3,11 +3,7 @@ import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeForm } from "./base-type-form"; -export function OutsideMenuForm({ - form, -}: { - form: UseFormReturn; -}) { +function OutsideMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -20,3 +16,5 @@ export function OutsideMenuForm({
); } + +export { OutsideMenuForm };