From d8d5c16d31d382e6a1b76426518fd52557e7d08c Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 21 Mar 2025 17:22:43 +0900 Subject: [PATCH 01/21] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EA=B4=80?= =?UTF-8?q?=EB=A6=AC=20>=20=EC=82=AC=EC=9D=B4=ED=8A=B8=EB=B3=84=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20api=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/menu/page.tsx | 2 + apps/cs/package.json | 2 + .../site/api/site-service.ts | 0 apps/cs/src/entities/site/index.ts | 7 + .../site/model/site-interface.ts | 0 apps/cs/src/pages/menu/api/menu-service.ts | 22 +++ apps/cs/src/pages/menu/index.ts | 1 + .../cs/src/pages/menu/model/menu-interface.ts | 44 ++++++ apps/cs/src/pages/menu/ui/menu-page.tsx | 85 +++++++++++ apps/cs/src/pages/menu/ui/menu-tree.tsx | 137 ++++++++++++++++++ .../cs/src/pages/site/model/table-columns.tsx | 2 +- apps/cs/src/pages/site/ui/create-page.tsx | 11 +- apps/cs/src/pages/site/ui/modify-page.tsx | 11 +- apps/cs/src/pages/site/ui/site-form.tsx | 2 +- apps/cs/src/pages/site/ui/site-page.tsx | 9 +- apps/cs/src/shared/config/api.ts | 1 + .../layout.tsx => shared/ui/page-layout.tsx} | 12 +- .../common/components/src/ui/layout/vbase.tsx | 2 +- pnpm-lock.yaml | 6 + 19 files changed, 333 insertions(+), 23 deletions(-) create mode 100644 apps/cs/app/(with-base-layout)/menu/page.tsx rename apps/cs/src/{pages => entities}/site/api/site-service.ts (100%) create mode 100644 apps/cs/src/entities/site/index.ts rename apps/cs/src/{pages => entities}/site/model/site-interface.ts (100%) create mode 100644 apps/cs/src/pages/menu/api/menu-service.ts create mode 100644 apps/cs/src/pages/menu/index.ts create mode 100644 apps/cs/src/pages/menu/model/menu-interface.ts create mode 100644 apps/cs/src/pages/menu/ui/menu-page.tsx create mode 100644 apps/cs/src/pages/menu/ui/menu-tree.tsx rename apps/cs/src/{pages/site/ui/layout.tsx => shared/ui/page-layout.tsx} (56%) diff --git a/apps/cs/app/(with-base-layout)/menu/page.tsx b/apps/cs/app/(with-base-layout)/menu/page.tsx new file mode 100644 index 000000000..bbac0670c --- /dev/null +++ b/apps/cs/app/(with-base-layout)/menu/page.tsx @@ -0,0 +1,2 @@ +import { MenuPage } from "@/pages/menu"; +export default MenuPage; diff --git a/apps/cs/package.json b/apps/cs/package.json index 4b400a285..6e774acba 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -13,6 +13,7 @@ "@common/components": "workspace:*", "@frontend-opensource/use-react-hooks": "^1.1.4", "@hookform/resolvers": "^4.1.3", + "@minoru/react-dnd-treeview": "^3.5.0", "@radix-ui/react-label": "^2.1.2", "@radix-ui/react-slot": "^1.1.2", "@tanstack/react-query": "^5.67.1", @@ -23,6 +24,7 @@ "js-cookie": "^3.0.5", "next": "15.1.7", "react-chartjs-2": "^5.3.0", + "react-dnd": "^16.0.1", "react-hook-form": "^7.54.2", "tailwindcss-animate": "^1.0.7", "zod": "^3.24.2", diff --git a/apps/cs/src/pages/site/api/site-service.ts b/apps/cs/src/entities/site/api/site-service.ts similarity index 100% rename from apps/cs/src/pages/site/api/site-service.ts rename to apps/cs/src/entities/site/api/site-service.ts diff --git a/apps/cs/src/entities/site/index.ts b/apps/cs/src/entities/site/index.ts new file mode 100644 index 000000000..985b9346a --- /dev/null +++ b/apps/cs/src/entities/site/index.ts @@ -0,0 +1,7 @@ +export { SiteApi } from "./api/site-service"; +export { + siteFormSchema, + type ISiteParams, + type ISite, + type SiteFormData, +} from "./model/site-interface"; diff --git a/apps/cs/src/pages/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts similarity index 100% rename from apps/cs/src/pages/site/model/site-interface.ts rename to apps/cs/src/entities/site/model/site-interface.ts diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts new file mode 100644 index 000000000..6e983a197 --- /dev/null +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -0,0 +1,22 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { Menu, MenuFormData, MenuService } from "../model/menu-interface"; + +export class MenuApiService implements MenuService { + public async getMenuList(siteId: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, + ); + return response.data; + } + + public async modifyMenu(menuCd: number, data: MenuFormData): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + data, + ); + return response.data; + } +} + +export const MenuApi = new MenuApiService(); diff --git a/apps/cs/src/pages/menu/index.ts b/apps/cs/src/pages/menu/index.ts new file mode 100644 index 000000000..fa0eda54f --- /dev/null +++ b/apps/cs/src/pages/menu/index.ts @@ -0,0 +1 @@ +export { MenuPage } from "./ui/menu-page"; diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts new file mode 100644 index 000000000..2463b511a --- /dev/null +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -0,0 +1,44 @@ +import { z } from "zod"; + +export interface Menu { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number; + sortSeq: number; + iconNm: string; + children: Menu[]; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + qrcd: string; + useYn: string; + otptYn: string; + requiredUrlPath: boolean; +} + +export const menuFormSchema = z.object({ + menuKornNm: z.string(), + menuSubKornNm: z.string().nullish(), + menuEngNm: z.string().nullish(), + menuSubEngNm: z.string().nullish(), + levelNo: z.number(), + urlPath: z.string().nullish(), + iconNm: z.string().nullish(), + menuLnkgTypeCd: z.string().nullish(), + menuLnkgSn: z.number().nullish(), + otptYn: z.string().nullish(), + prntMenuCd: z.number().nullish(), + sortSeq: z.number(), + evlUseYn: z.string().nullish(), + qrcd: z.string().nullish(), + useYn: z.string().nullish(), +}); + +export type MenuFormData = z.infer; + +export interface MenuService { + getMenuList: (siteId: string) => Promise; + modifyMenu: (menuCd: number, data: MenuFormData) => Promise; +} diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx new file mode 100644 index 000000000..f7236616c --- /dev/null +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -0,0 +1,85 @@ +"use client"; + +import { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { + Button, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components/ui"; +import { PageLayout } from "@/shared/ui/page-layout"; +import { ENDPOINT } from "@/shared/config"; +import { SiteApi } from "@/entities/site"; +import { MenuApi } from "../api/menu-service"; +import { MenuTree } from "./menu-tree"; + +function MenuPage() { + const [siteId, setSiteId] = useState(""); + + const { data: treeData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], + queryFn: () => MenuApi.getMenuList(siteId), + select: (data) => { + return data.map((menu) => ({ + id: menu.menuCd, + parent: menu.parentId ?? 0, + text: menu.menuKornNm, + droppable: true, + data: menu, + })); + }, + enabled: !!siteId, + }); + + const { data: options } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.SITES], + queryFn: () => SiteApi.getSiteList({}), + select: (data) => + data.content.map((site) => ({ + label: site.siteNm, + value: site.siteId, + })), + }); + + return ( + +
+
+ + {treeData && ( + <> +
+ + +
+
+ +
+ + )} +
+
+
+
+ ); +} + +export { MenuPage }; diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx new file mode 100644 index 000000000..e0ab4b596 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { useState } from "react"; +import { DndProvider } from "react-dnd"; +import { useMutation } from "@tanstack/react-query"; +import { + Tree, + MultiBackend, + getBackendOptions, + type NodeModel, + type DropOptions, +} from "@minoru/react-dnd-treeview"; +import { cn } from "@common/components/lib"; +import { MenuApi } from "../api/menu-service"; +import { + menuFormSchema, + type Menu, + type MenuFormData, +} from "../model/menu-interface"; + +function MenuTree({ treeData }: { treeData: NodeModel[] }) { + const { mutate } = useMutation({ + mutationFn: ({ menuCd, data }: { menuCd: number; data: MenuFormData }) => + MenuApi.modifyMenu(menuCd, data), + }); + + const [selectedNode, setSelectedNode] = useState | null>( + null, + ); + + const handleSelect = (node: NodeModel) => setSelectedNode(node); + + const handleDrop = ( + newTree: NodeModel[], + options: DropOptions, + ) => { + let saveData: MenuFormData | null = null; + const dragSourceNode = findNode(options.dragSourceId) ?? options.dragSource; + const dropTargetNode = findNode(options.dropTargetId) ?? options.dropTarget; + + if (!dropTargetNode) return; + + saveData = menuFormSchema.parse({ + ...dragSourceNode?.data, + }); + console.log(saveData); + }; + + const findNode = (id?: NodeModel["id"]) => { + return id ? treeData.find((node) => node.id === id) : null; + }; + + return ( + + , { depth, isOpen, onToggle }) => ( + + )} + onDrop={handleDrop} + classes={{ + root: "p-2", + container: "flex flex-col", + listItem: "", + dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm", + draggingSource: "", + placeholder: "", + }} + /> + + ); +} + +type MenuNodeProps = { + node: NodeModel; + depth: number; + isOpen: boolean; + isSelected: boolean; + onToggle: (id: NodeModel["id"]) => void; + onSelect: (node: NodeModel) => void; +}; + +function MenuNode({ + node, + depth, + isOpen, + isSelected, + onToggle, + onSelect, +}: MenuNodeProps) { + const { droppable, id, text } = node; + const indent = depth * 24 + 5; + + const handleToggle = () => onToggle(id); + const handleSelect = () => onSelect(node); + + return ( +
+ {droppable && ( + + )} +
+ {text} +
+
+ ); +} + +export { MenuTree }; diff --git a/apps/cs/src/pages/site/model/table-columns.tsx b/apps/cs/src/pages/site/model/table-columns.tsx index 35c084896..ce68b93ee 100644 --- a/apps/cs/src/pages/site/model/table-columns.tsx +++ b/apps/cs/src/pages/site/model/table-columns.tsx @@ -1,6 +1,6 @@ import dayjs from "dayjs"; import { createColumnHelper } from "@tanstack/react-table"; -import type { ISite } from "./site-interface"; +import type { ISite } from "@/entities/site"; const columnHelper = createColumnHelper(); diff --git a/apps/cs/src/pages/site/ui/create-page.tsx b/apps/cs/src/pages/site/ui/create-page.tsx index 9e591d225..66a9c63c4 100644 --- a/apps/cs/src/pages/site/ui/create-page.tsx +++ b/apps/cs/src/pages/site/ui/create-page.tsx @@ -4,11 +4,10 @@ import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { useMutation } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; -import { SiteApi } from "../api/site-service"; -import { siteFormSchema, type SiteFormData } from "../model/site-interface"; -import { SitePageLayout } from "./layout"; -import { SiteForm } from "./site-form"; +import { SiteApi, siteFormSchema, type SiteFormData } from "@/entities/site"; import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { PageLayout } from "@/shared/ui/page-layout"; +import { SiteForm } from "./site-form"; function CreatePage() { const router = useRouter(); @@ -39,9 +38,9 @@ function CreatePage() { }); return ( - + - + ); } diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index 6cb394eae..dd9bc2dcf 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -2,13 +2,12 @@ import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; -import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import { SiteApi, siteFormSchema, type SiteFormData } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; +import { PageLayout } from "@/shared/ui/page-layout"; import { useAlertStore } from "@/shared/lib/use-alert-store"; -import { SiteApi } from "../api/site-service"; -import { siteFormSchema, type SiteFormData } from "../model/site-interface"; -import { SitePageLayout } from "./layout"; import { SiteForm } from "./site-form"; function ModifyPage({ siteId }: { siteId: string }) { @@ -63,14 +62,14 @@ function ModifyPage({ siteId }: { siteId: string }) { }); return ( - + - + ); } diff --git a/apps/cs/src/pages/site/ui/site-form.tsx b/apps/cs/src/pages/site/ui/site-form.tsx index 65d50159f..50feb8ec6 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -15,6 +15,7 @@ import { TableRow, } from "@common/components"; import { CodeSelect } from "@/entities/code"; +import type { SiteFormData } from "@/entities/site"; import { FormItem, FormControl, @@ -23,7 +24,6 @@ import { FormMessage, } from "@/shared/ui/form"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; -import type { SiteFormData } from "../model/site-interface"; import type { Domain } from "../model/domain-interface"; import { IPManagementDialog } from "./ip-management-dialog"; import { DomainManagementDialog } from "./domain-management-dialog"; diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 8a2bd5264..6f9626761 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -7,11 +7,10 @@ import { useEffect, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { Button, DataTable, Input } from "@common/components"; +import { SiteApi, type ISite, type ISiteParams } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; -import { SiteApi } from "../api/site-service"; +import { PageLayout } from "@/shared/ui/page-layout"; import { columns } from "../model/table-columns"; -import { type ISite, type ISiteParams } from "../model/site-interface"; -import { SitePageLayout } from "./layout"; function SitePage() { const router = useRouter(); @@ -46,7 +45,7 @@ function SitePage() { }, []); return ( - +
@@ -73,7 +72,7 @@ function SitePage() {
-
+ ); } diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index b7164229a..e07b6c9ff 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -13,6 +13,7 @@ export const ENDPOINT = { CODE: "/cms-service/api/v1/code/cache", IPS: "/cms-service/api/v1/site-ips", DOMAINS: "/cms-service/api/v1/site-dmns", + MENUS: "/cms-service/api/v1/menus", }, }; diff --git a/apps/cs/src/pages/site/ui/layout.tsx b/apps/cs/src/shared/ui/page-layout.tsx similarity index 56% rename from apps/cs/src/pages/site/ui/layout.tsx rename to apps/cs/src/shared/ui/page-layout.tsx index 3fec47e17..e4f11141b 100644 --- a/apps/cs/src/pages/site/ui/layout.tsx +++ b/apps/cs/src/shared/ui/page-layout.tsx @@ -1,12 +1,18 @@ -function SitePageLayout({ children }: { children: React.ReactNode }) { +function PageLayout({ + children, + pageTitle, +}: { + children: React.ReactNode; + pageTitle: string; +}) { return (

- 사이트 관리 + {pageTitle}

{children}
); } -export { SitePageLayout }; +export { PageLayout }; diff --git a/packages/common/components/src/ui/layout/vbase.tsx b/packages/common/components/src/ui/layout/vbase.tsx index 63c4e6d27..d73f14254 100644 --- a/packages/common/components/src/ui/layout/vbase.tsx +++ b/packages/common/components/src/ui/layout/vbase.tsx @@ -7,7 +7,7 @@ import { Footer } from "./footer"; import "./common.css"; import type { QueryClient } from "@tanstack/react-query"; -export function BaseLayout({ +export function VBaseLayout({ children, queryClient, }: { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 70c256d33..82f59a6cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: '@hookform/resolvers': specifier: ^4.1.3 version: 4.1.3(react-hook-form@7.54.2) + '@minoru/react-dnd-treeview': + specifier: ^3.5.0 + version: 3.5.0(dnd-core@16.0.1)(react-dnd@16.0.1)(react-dom@18.3.1)(react@18.3.1) '@radix-ui/react-label': specifier: ^2.1.2 version: 2.1.2(@types/react-dom@18.3.5)(@types/react@18.3.18)(react-dom@18.3.1)(react@18.3.1) @@ -81,6 +84,9 @@ importers: react-chartjs-2: specifier: ^5.3.0 version: 5.3.0(chart.js@4.4.8)(react@18.3.1) + react-dnd: + specifier: ^16.0.1 + version: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1) react-hook-form: specifier: ^7.54.2 version: 7.54.2(react@18.3.1) From c4a9aabce7c0a84098396cead2d8f2a38c9cf34d Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 21 Mar 2025 18:22:34 +0900 Subject: [PATCH 02/21] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=ED=8F=BC=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs/src/pages/menu/model/menu-interface.ts | 27 ++- apps/cs/src/pages/menu/ui/base-menu-form.tsx | 121 +++++++++++ .../src/pages/menu/ui/content-menu-form.tsx | 198 ++++++++++++++++++ apps/cs/src/pages/menu/ui/empty-menu-form.tsx | 118 +++++++++++ apps/cs/src/pages/menu/ui/menu-form.tsx | 99 +++++++++ apps/cs/src/pages/menu/ui/menu-page.tsx | 35 +++- 6 files changed, 586 insertions(+), 12 deletions(-) create mode 100644 apps/cs/src/pages/menu/ui/base-menu-form.tsx create mode 100644 apps/cs/src/pages/menu/ui/content-menu-form.tsx create mode 100644 apps/cs/src/pages/menu/ui/empty-menu-form.tsx create mode 100644 apps/cs/src/pages/menu/ui/menu-form.tsx diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 2463b511a..966cc7520 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -1,5 +1,16 @@ import { z } from "zod"; +export const MENU_TYPE = { + EMPTY: "EMPTY", + CONTENTS: "CONTENTS", + BOARD: "BOARD", + INSIDE: "INSIDE", + OUTSIDE: "OUTSIDE", + BOTTOM: "BOTTOM", +} as const; + +export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE]; + export interface Menu { menuCd: number; menuKornNm: string; @@ -19,6 +30,7 @@ export interface Menu { } export const menuFormSchema = z.object({ + siteId: z.string(), menuKornNm: z.string(), menuSubKornNm: z.string().nullish(), menuEngNm: z.string().nullish(), @@ -26,14 +38,21 @@ export const menuFormSchema = z.object({ levelNo: z.number(), urlPath: z.string().nullish(), iconNm: z.string().nullish(), - menuLnkgTypeCd: z.string().nullish(), + menuLnkgTypeCd: z.enum([ + MENU_TYPE.EMPTY, + MENU_TYPE.CONTENTS, + MENU_TYPE.BOARD, + MENU_TYPE.INSIDE, + MENU_TYPE.OUTSIDE, + MENU_TYPE.BOTTOM, + ]), menuLnkgSn: z.number().nullish(), - otptYn: z.string().nullish(), + otptYn: z.enum(["Y", "N"]).default("Y"), prntMenuCd: z.number().nullish(), sortSeq: z.number(), - evlUseYn: z.string().nullish(), + evlUseYn: z.enum(["Y", "N"]).nullish(), qrcd: z.string().nullish(), - useYn: z.string().nullish(), + useYn: z.enum(["Y", "N"]).default("Y"), }); export type MenuFormData = z.infer; diff --git a/apps/cs/src/pages/menu/ui/base-menu-form.tsx b/apps/cs/src/pages/menu/ui/base-menu-form.tsx new file mode 100644 index 000000000..98dfe4e92 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/base-menu-form.tsx @@ -0,0 +1,121 @@ +import { type UseFormReturn } from "react-hook-form"; +import { + Input, + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormItem, + FormControl, + FormField, + FormMessage, +} from "@/shared/ui/form"; +import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; + +interface BaseMenuFormProps { + form: UseFormReturn; +} + +export function BaseMenuForm({ form }: BaseMenuFormProps) { + return ( + + + + + + + + 메뉴명 + + ( + + + + + + + )} + /> + + + + 메뉴 영문명 + + ( + + + + + + + )} + /> + + + + 메뉴 유형 + + ( + + + +
+ {Object.entries(MENU_TYPE).map(([key, value]) => ( +
+ + +
+ ))} +
+
+
+ +
+ )} + /> +
+
+
+
+ ); +} diff --git a/apps/cs/src/pages/menu/ui/content-menu-form.tsx b/apps/cs/src/pages/menu/ui/content-menu-form.tsx new file mode 100644 index 000000000..3bea24ca7 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/content-menu-form.tsx @@ -0,0 +1,198 @@ +import { type UseFormReturn } from "react-hook-form"; +import { + Input, + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormItem, + FormControl, + FormField, + FormMessage, +} from "@/shared/ui/form"; +import type { MenuFormData } from "../model/menu-interface"; + +export function ContentMenuForm({ + form, +}: { + form: UseFormReturn; +}) { + return ( + + + + + + + + 콘텐츠 + +
+ {form.watch("menuLnkgSn")} +
+
+
+ + 메뉴 평가 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 메뉴 서브명 + + ( + + + + + + + )} + /> + + + + 메뉴 아이콘 + +
+ {form.watch("iconNm")} +
+
+
+ + 출력 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + QR코드 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+
+
+ ); +} diff --git a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx new file mode 100644 index 000000000..92794aa10 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx @@ -0,0 +1,118 @@ +import { type UseFormReturn } from "react-hook-form"; +import { + Input, + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormItem, + FormControl, + FormField, + FormMessage, +} from "@/shared/ui/form"; +import type { MenuFormData } from "../model/menu-interface"; + +export function EmptyMenuForm({ form }: { form: UseFormReturn }) { + return ( + + + + + + + + 메뉴 서브명 + + ( + + + + + + + )} + /> + + + + 메뉴 아이콘 + +
+ {form.watch("iconNm")} +
+
+
+ + 출력 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+
+
+ ); +} diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx new file mode 100644 index 000000000..a3daef358 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -0,0 +1,99 @@ +import { useState } from "react"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; +import { type UseFormReturn } from "react-hook-form"; +import { Button } from "@common/components"; +import { Form } from "@/shared/ui/form"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; +import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; +import { BaseMenuForm } from "./base-menu-form"; +import { EmptyMenuForm } from "./empty-menu-form"; +import { ContentMenuForm } from "./content-menu-form"; + +interface MenuFormProps { + form: UseFormReturn; + menuId?: string; + handleSave: (data: MenuFormData) => Promise; + handleDelete?: () => Promise; +} + +export function MenuForm({ + form, + menuId, + handleSave, + handleDelete, +}: MenuFormProps) { + const isModifyMode = !!menuId; + const { confirm } = useConfirm(); + const [onConfirm, setOnConfirm] = useState(false); + + const menuType = form.watch("menuLnkgTypeCd"); + + const onSubmit = async (data: MenuFormData) => { + const confirmMessage = isModifyMode + ? "수정하시겠습니까?" + : "저장하시겠습니까?"; + + setOnConfirm(true); + if (await confirm(confirmMessage)) { + setOnConfirm(false); + handleSave(data); + } + setOnConfirm(false); + }; + + const onDelete = async () => { + setOnConfirm(true); + if (await confirm("삭제하시겠습니까?")) { + setOnConfirm(false); + handleDelete?.(); + } + setOnConfirm(false); + }; + + return ( + <> + + +
+
+

+ 메뉴 등록/수정 +

+
+
+ +
+ {renderMenuTypeComponent(menuType, form)} +
+
+
+
+ + {isModifyMode && ( + + )} +
+ + + + + ); +} + +function renderMenuTypeComponent( + menuType: string, + form: UseFormReturn, +) { + switch (menuType) { + case MENU_TYPE.EMPTY: + return ; + case MENU_TYPE.CONTENTS: + return ; + default: + return null; + } +} diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index f7236616c..88bf8ecee 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -1,7 +1,9 @@ "use client"; import { useState } from "react"; +import { useForm } from "react-hook-form"; import { useQuery } from "@tanstack/react-query"; +import { zodResolver } from "@hookform/resolvers/zod"; import { Button, Select, @@ -10,11 +12,13 @@ import { SelectTrigger, SelectValue, } from "@common/components/ui"; -import { PageLayout } from "@/shared/ui/page-layout"; -import { ENDPOINT } from "@/shared/config"; import { SiteApi } from "@/entities/site"; +import { ENDPOINT } from "@/shared/config"; +import { PageLayout } from "@/shared/ui/page-layout"; import { MenuApi } from "../api/menu-service"; +import { menuFormSchema, type MenuFormData } from "../model/menu-interface"; import { MenuTree } from "./menu-tree"; +import { MenuForm } from "./menu-form"; function MenuPage() { const [siteId, setSiteId] = useState(""); @@ -22,15 +26,14 @@ function MenuPage() { const { data: treeData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], queryFn: () => MenuApi.getMenuList(siteId), - select: (data) => { - return data.map((menu) => ({ + select: (data) => + data.map((menu) => ({ id: menu.menuCd, parent: menu.parentId ?? 0, text: menu.menuKornNm, droppable: true, data: menu, - })); - }, + })), enabled: !!siteId, }); @@ -44,9 +47,19 @@ function MenuPage() { })), }); + const form = useForm({ + resolver: zodResolver(menuFormSchema), + }); + + const handleSave = async (data: MenuFormData) => { + console.log(data); + }; + + const handleDelete = async () => {}; + return ( -
+
+ + + + )} + /> + + + + 메뉴 아이콘 + +
+
+ {form.watch("iconNm")} +
+ +
+
+
+ + 출력 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + ); +} diff --git a/apps/cs/src/pages/menu/ui/board-menu-form.tsx b/apps/cs/src/pages/menu/ui/board-menu-form.tsx new file mode 100644 index 000000000..a63fe24ff --- /dev/null +++ b/apps/cs/src/pages/menu/ui/board-menu-form.tsx @@ -0,0 +1,144 @@ +import { type UseFormReturn } from "react-hook-form"; +import { + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormItem, + FormControl, + FormField, + FormMessage, +} from "@/shared/ui/form"; +import type { MenuFormData } from "../model/menu-interface"; +import { BaseBottomMenuForm } from "./base-bottom-menu-form"; + +export function BoardMenuForm({ form }: { form: UseFormReturn }) { + return ( + + + + + + + + 게시판 + +
+ {form.watch("menuLnkgSn")} +
+
+
+ + 연결 형태 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 메뉴 평가 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + + + + QR코드 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 담당자 + +
+ {form.watch("menuLnkgSn")} +
+
+
+
+
+ ); +} diff --git a/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx b/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx new file mode 100644 index 000000000..88199147b --- /dev/null +++ b/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx @@ -0,0 +1,22 @@ +import { type UseFormReturn } from "react-hook-form"; +import { Table, TableBody } from "@common/components"; +import type { MenuFormData } from "../model/menu-interface"; +import { BaseBottomMenuForm } from "./base-bottom-menu-form"; + +export function BottomMenuForm({ + form, +}: { + form: UseFormReturn; +}) { + return ( + + + + + + + + +
+ ); +} 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 3bea24ca7..65e806f25 100644 --- a/apps/cs/src/pages/menu/ui/content-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/content-menu-form.tsx @@ -1,6 +1,5 @@ import { type UseFormReturn } from "react-hook-form"; import { - Input, Label, RadioGroup, RadioGroupItem, @@ -17,6 +16,7 @@ import { FormMessage, } from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; +import { BaseBottomMenuForm } from "./base-bottom-menu-form"; export function ContentMenuForm({ form, @@ -38,6 +38,37 @@ export function ContentMenuForm({
+ + 연결 형태 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
메뉴 평가 사용 여부 @@ -69,98 +100,9 @@ export function ContentMenuForm({ /> - - 메뉴 서브명 - - ( - - - - - - - )} - /> - - - - 메뉴 아이콘 - -
- {form.watch("iconNm")} -
-
-
- - 출력 여부 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - 사용 여부 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
+ + + QR코드 @@ -192,6 +134,14 @@ export function ContentMenuForm({ /> + + 담당자 + +
+ {form.watch("menuLnkgSn")} +
+
+
); 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 92794aa10..1cdaaed97 100644 --- a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx @@ -1,22 +1,7 @@ import { type UseFormReturn } from "react-hook-form"; -import { - Input, - Label, - RadioGroup, - RadioGroupItem, - Table, - TableBody, - TableCell, - TableHead, - TableRow, -} from "@common/components"; -import { - FormItem, - FormControl, - FormField, - FormMessage, -} from "@/shared/ui/form"; +import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; +import { BaseBottomMenuForm } from "./base-bottom-menu-form"; export function EmptyMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -26,92 +11,7 @@ export function EmptyMenuForm({ form }: { form: UseFormReturn }) { - - 메뉴 서브명 - - ( - - - - - - - )} - /> - - - - 메뉴 아이콘 - -
- {form.watch("iconNm")} -
-
-
- - 출력 여부 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - 사용 여부 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
+
); diff --git a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx new file mode 100644 index 000000000..8e63a2615 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx @@ -0,0 +1,22 @@ +import { type UseFormReturn } from "react-hook-form"; +import { Table, TableBody } from "@common/components"; +import type { MenuFormData } from "../model/menu-interface"; +import { BaseBottomMenuForm } from "./base-bottom-menu-form"; + +export function InsideMenuForm({ + form, +}: { + form: UseFormReturn; +}) { + return ( + + + + + + + + +
+ ); +} diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index 30677c288..894927ba0 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -8,6 +8,10 @@ import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; import { BaseMenuForm } from "./base-menu-form"; import { EmptyMenuForm } from "./empty-menu-form"; import { ContentMenuForm } from "./content-menu-form"; +import { BoardMenuForm } from "./board-menu-form"; +import { InsideMenuForm } from "./inside-menu-form"; +import { OutsideMenuForm } from "./outside-menu-form"; +import { BottomMenuForm } from "./bottom-menu-form"; interface MenuFormProps { form: UseFormReturn; @@ -93,6 +97,14 @@ function renderMenuTypeComponent( return ; case MENU_TYPE.CONTENTS: return ; + case MENU_TYPE.BOARD: + return ; + case MENU_TYPE.INSIDE: + return ; + case MENU_TYPE.OUTSIDE: + return ; + case MENU_TYPE.BOTTOM: + return ; default: return null; } diff --git a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx b/apps/cs/src/pages/menu/ui/outside-menu-form.tsx new file mode 100644 index 000000000..b92977a83 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/outside-menu-form.tsx @@ -0,0 +1,22 @@ +import { type UseFormReturn } from "react-hook-form"; +import { Table, TableBody } from "@common/components"; +import type { MenuFormData } from "../model/menu-interface"; +import { BaseBottomMenuForm } from "./base-bottom-menu-form"; + +export function OutsideMenuForm({ + form, +}: { + form: UseFormReturn; +}) { + return ( + + + + + + + + +
+ ); +} diff --git a/packages/common/components/src/ui/icon-finder/index.tsx b/packages/common/components/src/ui/icon-finder/index.tsx index 71f6f9c18..62e6e195a 100644 --- a/packages/common/components/src/ui/icon-finder/index.tsx +++ b/packages/common/components/src/ui/icon-finder/index.tsx @@ -1,18 +1,39 @@ +import { useState } from "react"; import { Button } from "../button"; import { Dialog, DialogContent, DialogTrigger } from "../dialog"; +import { cn } from "../../lib"; import iconNames from "./data"; -const IconFinder = () => { +type IconFinderProps = { + handleClick: (icon: string) => void; +}; + +const IconFinder = ({ handleClick }: IconFinderProps) => { + const [selectedIcon, setSelectedIcon] = useState(""); + + const handleIconClick = (icon: string) => { + setSelectedIcon(icon); + handleClick(icon); + }; + return ( - +
-
+
{iconNames.map((icon) => ( - + ))}
@@ -21,9 +42,29 @@ const IconFinder = () => { ); }; -const IconItem = ({ icon }: { icon: string }) => { +const IconItem = ({ + icon, + selectedIcon, + handleClick, +}: { + icon: string; + selectedIcon: string; + handleClick: (icon: string) => void; +}) => { return ( -
+
handleClick(icon)} + > + {icon}
diff --git a/packages/common/components/src/ui/index.ts b/packages/common/components/src/ui/index.ts index 438c18dbd..650eb30e1 100644 --- a/packages/common/components/src/ui/index.ts +++ b/packages/common/components/src/ui/index.ts @@ -7,8 +7,8 @@ export * from "./data-table"; export * from "./date-picker"; export * from "./dialog"; export * from "./dropdown-menu"; -export * from "./input"; export * from "./file-search"; +export * from "./icon-finder"; export * from "./input"; export * from "./label"; export * from "./layout"; From ada14e0003765df47308abca4e4e2fbf673a7af1 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 24 Mar 2025 17:52:35 +0900 Subject: [PATCH 09/21] =?UTF-8?q?feat:=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=ED=8F=BC=20=EB=8F=84=EB=A9=94=EC=9D=B8=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/entities/site/model/site-interface.ts | 2 ++ apps/cs/src/pages/site/model/table-columns.tsx | 4 ++-- apps/cs/src/pages/site/ui/site-form.tsx | 8 +++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/cs/src/entities/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts index 6edc1e69e..ae14297ae 100644 --- a/apps/cs/src/entities/site/model/site-interface.ts +++ b/apps/cs/src/entities/site/model/site-interface.ts @@ -16,6 +16,7 @@ export interface ISite { useYn: string; regDt: string; rgTrNm: string; + dmnAddr: string; } export interface ISiteDetail extends SiteFormData { @@ -44,6 +45,7 @@ export const siteFormSchema = z.object({ bkmkIcon: z.string().max(100).nullable(), useYn: z.string(), siteSkn: z.string().max(10).nullable(), + dmnAddr: z.string().nullable(), }); export type SiteFormData = z.infer; diff --git a/apps/cs/src/pages/site/model/table-columns.tsx b/apps/cs/src/pages/site/model/table-columns.tsx index ce68b93ee..2a07ce4b1 100644 --- a/apps/cs/src/pages/site/model/table-columns.tsx +++ b/apps/cs/src/pages/site/model/table-columns.tsx @@ -17,8 +17,8 @@ const columns = [ id: "siteNm", header: "사이트명", }), - columnHelper.accessor("siteAddr", { - id: "siteAddr", + columnHelper.accessor("dmnAddr", { + id: "dmnAddr", header: "대표 URL", }), columnHelper.accessor("bscSiteYn", { diff --git a/apps/cs/src/pages/site/ui/site-form.tsx b/apps/cs/src/pages/site/ui/site-form.tsx index 50feb8ec6..5ad735ec2 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -43,10 +43,12 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) { const [onConfirm, setOnConfirm] = useState(false); const [isIpManagementOpen, setIsIpManagementOpen] = useState(false); const [isDomainManagementOpen, setIsDomainManagementOpen] = useState(false); - const [domainUrl, setDomainUrl] = useState(""); const handleSaveDomain = (rows: Domain[]) => { - setDomainUrl(rows.find((row) => row.rprsDmnYn === "Y")?.dmnAddr || ""); + form.setValue( + "dmnAddr", + rows.find((row) => row.rprsDmnYn === "Y")?.dmnAddr || "", + ); }; const onSubmit = async (data: SiteFormData) => { @@ -211,7 +213,7 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) {
- {domainUrl} + {form.watch("dmnAddr")}
Date: Tue, 25 Mar 2025 09:48:14 +0900 Subject: [PATCH 10/21] =?UTF-8?q?feat:=20icon=20finder=20=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/src/ui/icon-finder/index.tsx | 42 +++++++++++++++---- .../common/components/src/ui/input/index.tsx | 3 +- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/packages/common/components/src/ui/icon-finder/index.tsx b/packages/common/components/src/ui/icon-finder/index.tsx index 62e6e195a..db8f6d1bd 100644 --- a/packages/common/components/src/ui/icon-finder/index.tsx +++ b/packages/common/components/src/ui/icon-finder/index.tsx @@ -1,19 +1,34 @@ import { useState } from "react"; -import { Button } from "../button"; -import { Dialog, DialogContent, DialogTrigger } from "../dialog"; import { cn } from "../../lib"; +import { Button } from "../button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "../dialog"; +import { Input } from "../input"; import iconNames from "./data"; type IconFinderProps = { - handleClick: (icon: string) => void; + handleClick?: (icon: string) => void; }; const IconFinder = ({ handleClick }: IconFinderProps) => { const [selectedIcon, setSelectedIcon] = useState(""); + const [inputValue, setInputValue] = useState(""); + const [filteredIconNames, setFilteredIconNames] = useState(iconNames); const handleIconClick = (icon: string) => { setSelectedIcon(icon); - handleClick(icon); + handleClick?.(icon); + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const value = e.target.value; + setInputValue(value); + setFilteredIconNames(iconNames.filter((icon) => icon.includes(value))); }; return ( @@ -24,10 +39,19 @@ const IconFinder = ({ handleClick }: IconFinderProps) => { 아이콘 찾기 - -
+ + + 아이콘 찾기 + +
+
- {iconNames.map((icon) => ( + {filteredIconNames.map((icon) => ( - - {icon} + + {icon}
); }; diff --git a/packages/common/components/src/ui/input/index.tsx b/packages/common/components/src/ui/input/index.tsx index a980f0ebd..d53c6a199 100644 --- a/packages/common/components/src/ui/input/index.tsx +++ b/packages/common/components/src/ui/input/index.tsx @@ -3,7 +3,7 @@ import * as React from "react"; import { cn } from "../../lib/utils"; const Input = React.forwardRef>( - ({ className, type, value, ...props }, ref) => { + ({ className, type, ...props }, ref) => { return ( >( "flex h-[3.2rem] w-full rounded-md border border-input bg-form px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-placeholder focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", className, )} - value={value ?? ""} ref={ref} {...props} /> From 2aee109912f762a3c5a2d9ec80578944a0b1462a Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 25 Mar 2025 10:55:15 +0900 Subject: [PATCH 11/21] =?UTF-8?q?feat:=20=ED=8A=B8=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20dnd=20=EB=A1=9C=EC=A7=81=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/pages/menu/lib/utils.ts | 7 ++++--- apps/cs/src/pages/menu/ui/menu-tree.tsx | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index 304feac48..723023d8c 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -37,7 +37,6 @@ export const generateNewTreeData = ( return { menuCd: node.data?.menuCd, name: node.data?.menuKornNm, - sortSeq: node.data?.sortSeq, icon: node.data?.iconNm, }; }; @@ -45,7 +44,8 @@ export const generateNewTreeData = ( const treeMenu: TreeMenu = { ...baseNode(dragSource), parentId: dropTargetId === 0 ? null : (dropTargetId as number), - levelNo: (dropTarget?.data?.levelNo ?? 0) + 1, + sortSeq: dropTarget?.data?.children?.length ?? 0 + 1, + levelNo: dropTarget?.data?.levelNo ?? 0 + 1, }; const result: TreeMenu = { @@ -53,7 +53,8 @@ export const generateNewTreeData = ( children: childNodes.map((child) => ({ ...baseNode(child), parentId: child.data?.parentId === 0 ? null : (dropTargetId as number), - levelNo: (treeMenu?.levelNo ?? 0) + 1, + sortSeq: child.data?.sortSeq, + levelNo: treeMenu?.levelNo ?? 0 + 1, })), }; diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index cab0bafcc..c38f98654 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -73,6 +73,7 @@ function MenuTree({ treeData, siteId }: MenuTreeProps) { draggingSource: "", placeholder: "", }} + sort={false} /> ); @@ -114,7 +115,7 @@ function MenuNode({ {data?.children?.length && ( { From 92f67ce1a3f20aea8d3ff004691b34c2fd321b9a Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 25 Mar 2025 14:23:16 +0900 Subject: [PATCH 12/21] =?UTF-8?q?feat:=20=ED=8A=B8=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=EB=AA=85=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/eslint.config.mjs | 5 + apps/cs/src/pages/menu/api/menu-service.ts | 11 +- apps/cs/src/pages/menu/lib/utils.ts | 36 +++++- .../cs/src/pages/menu/model/menu-interface.ts | 9 +- apps/cs/src/pages/menu/ui/menu-node.tsx | 116 ++++++++++++++++++ apps/cs/src/pages/menu/ui/menu-page.tsx | 6 +- apps/cs/src/pages/menu/ui/menu-tree.tsx | 79 ++---------- 7 files changed, 179 insertions(+), 83 deletions(-) create mode 100644 apps/cs/src/pages/menu/ui/menu-node.tsx diff --git a/apps/cs/eslint.config.mjs b/apps/cs/eslint.config.mjs index c85fb67c4..9109635e9 100644 --- a/apps/cs/eslint.config.mjs +++ b/apps/cs/eslint.config.mjs @@ -11,6 +11,11 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + { + rules: { + "react-hooks/exhaustive-deps": "off", + }, + }, ]; export default eslintConfig; diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index bb5dd45f4..4fe6c845a 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -3,20 +3,27 @@ import { ENDPOINT } from "@/shared/config"; import type { Menu, MenuService, TreeMenu } from "../model/menu-interface"; export class MenuApiService implements MenuService { - public async getMenuList(siteId: string): Promise { + public async getTreeMenuList(siteId: string): Promise { const response = await client.get( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, ); return response.data; } - public async modifyMenu(siteId: string, data: TreeMenu[]): Promise { + public async modifyTreeMenu(siteId: string, data: TreeMenu[]): Promise { const response = await client.put( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, data, ); return response.data; } + + public async modifyMenuName(menuCd: number, name: string): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`, + ); + return response.data; + } } export const MenuApi = new MenuApiService(); diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index 723023d8c..a7bc514a3 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -44,8 +44,8 @@ export const generateNewTreeData = ( const treeMenu: TreeMenu = { ...baseNode(dragSource), parentId: dropTargetId === 0 ? null : (dropTargetId as number), - sortSeq: dropTarget?.data?.children?.length ?? 0 + 1, - levelNo: dropTarget?.data?.levelNo ?? 0 + 1, + sortSeq: (dropTarget?.data?.children?.length ?? 0) + 1, + levelNo: (dropTarget?.data?.levelNo ?? 0) + 1, }; const result: TreeMenu = { @@ -54,9 +54,39 @@ export const generateNewTreeData = ( ...baseNode(child), parentId: child.data?.parentId === 0 ? null : (dropTargetId as number), sortSeq: child.data?.sortSeq, - levelNo: treeMenu?.levelNo ?? 0 + 1, + levelNo: (treeMenu?.levelNo ?? 0) + 1, })), }; return result; }; + +export const handleSelectNode = + (callback: (node: NodeModel) => void) => (node: NodeModel) => { + callback(node); + }; + +export const handleDropNode = + (callback: (newTree: TreeMenu) => void) => + (newTree: NodeModel[], options: DropOptions) => { + const newTreeData = generateNewTreeData(newTree, options); + if (!newTreeData) return; + callback(newTreeData); + }; + +export const handleNodeTextChange = + (treeData: NodeModel[]) => + (callback: (newTree: NodeModel[]) => void) => + (id: NodeModel["id"], value: string) => { + const newTree = treeData.map((node) => { + if (node.id === id) { + return { + ...node, + text: value, + }; + } + + return node; + }); + callback(newTree); + }; diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 09e473d07..fc22c462c 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"), + otptYn: z.enum(["Y", "N"]).default("Y").nullish(), prntMenuCd: z.number().nullish(), sortSeq: z.number(), evlUseYn: z.enum(["Y", "N"]).nullish(), qrcd: z.string().nullish(), - useYn: z.enum(["Y", "N"]).default("Y"), + useYn: z.enum(["Y", "N"]).default("Y").nullish(), }); export type MenuFormData = z.infer; export interface MenuService { - getMenuList: (siteId: string) => Promise; - modifyMenu: (siteId: string, data: TreeMenu[]) => 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/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx new file mode 100644 index 000000000..f8a150633 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -0,0 +1,116 @@ +import { useState } from "react"; +import { useMutation } 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 { MenuApi } from "../api/menu-service"; + +type MenuNodeProps = { + node: NodeModel; + depth: number; + isOpen: boolean; + isSelected: boolean; + onToggle: (id: NodeModel["id"]) => void; + onSelect: (node: NodeModel) => void; +}; + +function MenuNode({ + node, + depth, + isOpen, + isSelected, + onToggle, + onSelect, +}: MenuNodeProps) { + const { id, text, data } = node; + const indent = depth * 10 + 5; + + const { mutateAsync: modifyMenuName } = useMutation({ + mutationFn: (nodeName: string) => { + const encodedName = encodeURIComponent(nodeName); + return MenuApi.modifyMenuName(id as number, encodedName); + }, + }); + + const [editMode, setEditMode] = useState(false); + const [nodeName, setNodeName] = useState(text); + + const handleToggle = () => onToggle(id); + const handleSelect = () => onSelect(node); + + const handleSave = async () => { + await modifyMenuName(nodeName); + setEditMode(false); + }; + + return ( +
+
+ {editMode ? ( + <> + setNodeName(e.target.value)} + className="h-[2.5rem] !text-[1.3rem] text-foreground" + /> + + ) : ( + <> + {data?.children?.length && ( + + )} + {text} + + )} +
+ {editMode ? ( +
+ + +
+ ) : ( + + )} +
+ ); +} + +export { MenuNode }; diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 760f86c42..5cc63b627 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -30,7 +30,7 @@ function MenuPage() { const { data: treeData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], - queryFn: () => MenuApi.getMenuList(siteId), + queryFn: () => MenuApi.getTreeMenuList(siteId), select: (data) => generateTreeData(data), enabled: !!siteId, }); @@ -61,9 +61,9 @@ function MenuPage() { return (
-
+
-
- {filteredIconNames.map((icon) => ( - - ))} -
+
+
+ {filteredIconNames.map((icon) => ( + + ))}
From 517ab0f272829d9e9f3303aa723b803060043a3c Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 25 Mar 2025 16:59:54 +0900 Subject: [PATCH 14/21] =?UTF-8?q?chore:=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/entities/code/ui/code-select.tsx | 5 +---- apps/cs/src/pages/site/ui/modify-page.tsx | 1 + apps/cs/src/pages/site/ui/site-form.tsx | 2 ++ apps/cs/src/pages/site/ui/site-page.tsx | 1 - packages/common/components/src/ui/input/index.tsx | 3 +-- 5 files changed, 5 insertions(+), 7 deletions(-) diff --git a/apps/cs/src/entities/code/ui/code-select.tsx b/apps/cs/src/entities/code/ui/code-select.tsx index 00b3157aa..bd175a8a9 100644 --- a/apps/cs/src/entities/code/ui/code-select.tsx +++ b/apps/cs/src/entities/code/ui/code-select.tsx @@ -11,10 +11,7 @@ import { CodeApi } from "../api/code-service"; type CodeSelectProps = { upCd: string; - value?: string; - defaultValue?: string; - onValueChange?: (value: string) => void; -}; +} & React.ComponentPropsWithoutRef; function CodeSelect({ upCd, defaultValue, ...props }: CodeSelectProps) { const { data: code } = useQuery({ diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index dd9bc2dcf..1f1061f9d 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -16,6 +16,7 @@ function ModifyPage({ siteId }: { siteId: string }) { const router = useRouter(); const queryClient = useQueryClient(); const { setMessage: alert } = useAlertStore((state) => state); + const { data: siteDetail } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId], queryFn: () => SiteApi.siteDetail(siteId), diff --git a/apps/cs/src/pages/site/ui/site-form.tsx b/apps/cs/src/pages/site/ui/site-form.tsx index 5ad735ec2..463b5a6fa 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -101,6 +101,7 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) { @@ -175,6 +176,7 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) { diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 6f9626761..33431f316 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -41,7 +41,6 @@ function SitePage() { useEffect(() => { refetch(); - // eslint-disable-next-line react-hooks/exhaustive-deps }, []); return ( diff --git a/packages/common/components/src/ui/input/index.tsx b/packages/common/components/src/ui/input/index.tsx index d53c6a199..11cbca71d 100644 --- a/packages/common/components/src/ui/input/index.tsx +++ b/packages/common/components/src/ui/input/index.tsx @@ -3,10 +3,9 @@ import * as React from "react"; import { cn } from "../../lib/utils"; const Input = React.forwardRef>( - ({ className, type, ...props }, ref) => { + ({ className, ...props }, ref) => { return ( Date: Tue, 25 Mar 2025 17:09:03 +0900 Subject: [PATCH 15/21] =?UTF-8?q?refactor:=20components.css=20postcss?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/app/styles/components.css | 62 +++++++++++++------ .../pages/menu/ui/base-bottom-menu-form.tsx | 12 +++- 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/apps/cs/src/app/styles/components.css b/apps/cs/src/app/styles/components.css index b67efb8f0..b851d5a1e 100644 --- a/apps/cs/src/app/styles/components.css +++ b/apps/cs/src/app/styles/components.css @@ -1,25 +1,49 @@ -@tailwind components; +.card { + position: relative; + margin: 0.6rem; + box-sizing: border-box; + min-height: 2rem; + border-radius: 0.5rem; + padding: 0.6rem; + font-size: 1.3rem; + color: #666; +} -@layer components { - .card { - @apply relative m-[0.6rem] box-border min-h-[2rem] rounded-[0.5rem] p-[0.6rem] text-[1.3rem] text-[#666]; - } +.card-border { + border: 1px solid #dbe2e6; + background-color: rgba(255, 255, 255, 0.6); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.card-header { + margin-left: -0.6rem; + margin-right: -0.6rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #e1e7eb; + padding: 1rem; +} + +.card-footer { + margin-left: -0.6rem; + margin-right: -0.6rem; + margin-top: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + border-top: 1px solid #e1e7eb; + padding-top: 1rem; + padding-bottom: 1rem; +} + +/* 블랙 테마 */ +[data-theme="black"] { .card-border { - @apply border border-[#dbe2e6] bg-[rgba(255,255,255,0.6)] shadow-blur; + border-color: #3f4042; + background-color: rgba(255, 255, 255, 0.065); } .card-header { - @apply -mx-[0.6rem] flex items-center justify-between border-b border-b-[#e1e7eb] p-4; - } - .card-footer { - @apply -mx-[0.6rem] mt-2 flex items-center justify-center border-t border-t-[#e1e7eb] py-4; - } - - [data-theme="black"] { - .card-border { - @apply border-[#3f4042] bg-[rgba(255,255,255,0.065)]; - } - .card-header { - @apply border-b-[#3f4042]; - } + border-bottom-color: #3f4042; } } diff --git a/apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx b/apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx index 84527d2da..d8afc344d 100644 --- a/apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx @@ -70,7 +70,11 @@ export function BaseBottomMenuForm({ render={({ field }) => ( - +
@@ -98,7 +102,11 @@ export function BaseBottomMenuForm({ render={({ field }) => ( - +
From 64d336c2a4af4dc0ada8bd0c9027c8dfbd498918 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Tue, 25 Mar 2025 17:22:27 +0900 Subject: [PATCH 16/21] =?UTF-8?q?chore:=20package=20=EB=AA=85=EB=A0=B9?= =?UTF-8?q?=EC=96=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cs/package.json b/apps/cs/package.json index 6e774acba..4394eaf17 100644 --- a/apps/cs/package.json +++ b/apps/cs/package.json @@ -3,7 +3,7 @@ "version": "0.0.0", "private": true, "scripts": { - "dev": "NODE_OPTIONS='--inspect' next dev", + "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint" From 36c3bcb0b4c1f217f18485b614e3b9bf06bb1818 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 25 Mar 2025 19:00:54 +0900 Subject: [PATCH 17/21] =?UTF-8?q?feat:=20=ED=8A=B8=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20dnd=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/menu/lib/utils.ts | 10 +++++++++- apps/cs/src/pages/menu/ui/menu-tree.tsx | 19 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index a7bc514a3..53e79918d 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -26,8 +26,16 @@ export const generateNewTreeData = ( options: DropOptions, ): TreeMenu | null => { const { dragSourceId, dragSource, dropTargetId, dropTarget } = options; + if (!dragSource?.data) return null; + const siblings = treeData.filter( + (node) => node.parent === dragSource?.parent, + ); + + const newSortSeq = + siblings.findIndex((sibling) => sibling.id === dragSourceId) + 1; + const childNodes = treeData.filter( (node) => node.parent === dragSourceId && node.data, ); @@ -44,7 +52,7 @@ export const generateNewTreeData = ( const treeMenu: TreeMenu = { ...baseNode(dragSource), parentId: dropTargetId === 0 ? null : (dropTargetId as number), - sortSeq: (dropTarget?.data?.children?.length ?? 0) + 1, + sortSeq: newSortSeq, levelNo: (dropTarget?.data?.levelNo ?? 0) + 1, }; diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index d169d2149..b399747f4 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -56,12 +56,25 @@ function MenuTree({ treeData, siteId }: MenuTreeProps) { classes={{ root: "p-4", container: "flex flex-col", - listItem: "", dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm", - draggingSource: "", - placeholder: "", + placeholder: "relative", }} sort={false} + dropTargetOffset={10} + insertDroppableFirst={false} + canDrop={(_, { dragSource, dropTargetId }) => { + if (dragSource?.parent === dropTargetId) { + return true; + } + }} + placeholderRender={(_, { depth }) => { + return ( +
+ ); + }} /> ); From 752b5fa7891d4b13b432bfe15356fef26c9cc265 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Tue, 25 Mar 2025 20:20:36 +0900 Subject: [PATCH 18/21] =?UTF-8?q?=09chore:=20component.css=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/app/styles/components.css | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/apps/cs/src/app/styles/components.css b/apps/cs/src/app/styles/components.css index b851d5a1e..c1eb79210 100644 --- a/apps/cs/src/app/styles/components.css +++ b/apps/cs/src/app/styles/components.css @@ -9,6 +9,44 @@ color: #666; } +.card_width10 {width:calc(10% - 12px);} + +.card_width20 {width:calc(20% - 12px);} + +.card_width25 {width:calc(25% - 12px);} + +.card_width30 {width:calc(30% - 12px);} + +.card_width33 {width:calc(33.333% - 12px);} + +.card_width40 {width:calc(40% - 12px);} + +.card_width45 {width:calc(45% - 12px);} + +.card_width50 {width:calc(50% - 12px);} + +.card_width60 {width:calc(60% - 12px);} + +.card_width66 {width:calc(66.666% - 12px);} + +.card_width70 {width:calc(70% - 12px);} + +.card_width75 {width:calc(75% - 12px);} + +.card_width80 {width:calc(80% - 12px);} + +.card_width90 {width:calc(90% - 12px);} + +.card_width100 {width:calc(100% - 12px);} + +.card_nopadding {padding:0 !important;} + +.card_nomargin {width:100%;margin:0 !important;padding:0 !important;} + +.topline {border-top: 1px solid #ccc;} + +.bottomline {border-bottom: 1px solid #e6eef3;background: #fff;} + .card-border { border: 1px solid #dbe2e6; background-color: rgba(255, 255, 255, 0.6); From f94b8a3f8e30d866ebd23d3250e580b1a3c9c61a Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 26 Mar 2025 10:09:38 +0900 Subject: [PATCH 19/21] =?UTF-8?q?fix:=20=ED=8A=B8=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=20dnd=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/menu/lib/utils.ts | 4 +--- apps/cs/src/pages/menu/ui/menu-tree.tsx | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index 53e79918d..333ebad94 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -29,9 +29,7 @@ export const generateNewTreeData = ( if (!dragSource?.data) return null; - const siblings = treeData.filter( - (node) => node.parent === dragSource?.parent, - ); + const siblings = treeData.filter((node) => node.parent === dropTargetId); const newSortSeq = siblings.findIndex((sibling) => sibling.id === dragSourceId) + 1; diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index b399747f4..629dd1252 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -60,7 +60,7 @@ function MenuTree({ treeData, siteId }: MenuTreeProps) { placeholder: "relative", }} sort={false} - dropTargetOffset={10} + dropTargetOffset={5} insertDroppableFirst={false} canDrop={(_, { dragSource, dropTargetId }) => { if (dragSource?.parent === dropTargetId) { From 59c57d45d52cfd07e2d2e4b8b2df45333b990a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Wed, 26 Mar 2025 14:05:59 +0900 Subject: [PATCH 20/21] =?UTF-8?q?chore=20:=20=EA=B3=B5=ED=86=B5=20componen?= =?UTF-8?q?ts.css=20=EC=B6=94=EA=B0=80=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/assets/src/styles/components.css | 87 +++++++++++++++++++ packages/common/assets/src/styles/index.ts | 1 + packages/common/assets/tsup.config.ts | 1 + 3 files changed, 89 insertions(+) create mode 100644 packages/common/assets/src/styles/components.css diff --git a/packages/common/assets/src/styles/components.css b/packages/common/assets/src/styles/components.css new file mode 100644 index 000000000..c1eb79210 --- /dev/null +++ b/packages/common/assets/src/styles/components.css @@ -0,0 +1,87 @@ +.card { + position: relative; + margin: 0.6rem; + box-sizing: border-box; + min-height: 2rem; + border-radius: 0.5rem; + padding: 0.6rem; + font-size: 1.3rem; + color: #666; +} + +.card_width10 {width:calc(10% - 12px);} + +.card_width20 {width:calc(20% - 12px);} + +.card_width25 {width:calc(25% - 12px);} + +.card_width30 {width:calc(30% - 12px);} + +.card_width33 {width:calc(33.333% - 12px);} + +.card_width40 {width:calc(40% - 12px);} + +.card_width45 {width:calc(45% - 12px);} + +.card_width50 {width:calc(50% - 12px);} + +.card_width60 {width:calc(60% - 12px);} + +.card_width66 {width:calc(66.666% - 12px);} + +.card_width70 {width:calc(70% - 12px);} + +.card_width75 {width:calc(75% - 12px);} + +.card_width80 {width:calc(80% - 12px);} + +.card_width90 {width:calc(90% - 12px);} + +.card_width100 {width:calc(100% - 12px);} + +.card_nopadding {padding:0 !important;} + +.card_nomargin {width:100%;margin:0 !important;padding:0 !important;} + +.topline {border-top: 1px solid #ccc;} + +.bottomline {border-bottom: 1px solid #e6eef3;background: #fff;} + +.card-border { + border: 1px solid #dbe2e6; + background-color: rgba(255, 255, 255, 0.6); + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); +} + +.card-header { + margin-left: -0.6rem; + margin-right: -0.6rem; + display: flex; + align-items: center; + justify-content: space-between; + border-bottom: 1px solid #e1e7eb; + padding: 1rem; +} + +.card-footer { + margin-left: -0.6rem; + margin-right: -0.6rem; + margin-top: 0.5rem; + display: flex; + align-items: center; + justify-content: center; + border-top: 1px solid #e1e7eb; + padding-top: 1rem; + padding-bottom: 1rem; +} + +/* 블랙 테마 */ +[data-theme="black"] { + .card-border { + border-color: #3f4042; + background-color: rgba(255, 255, 255, 0.065); + } + .card-header { + border-bottom-color: #3f4042; + } +} diff --git a/packages/common/assets/src/styles/index.ts b/packages/common/assets/src/styles/index.ts index 807e81006..8b6116d95 100644 --- a/packages/common/assets/src/styles/index.ts +++ b/packages/common/assets/src/styles/index.ts @@ -2,6 +2,7 @@ export const styles = { base: require("./base.css"), diveicon: require("./diveicon.css"), grid: require("./grid.css"), + components: require("./components.css"), } as const; export type StyleType = keyof typeof styles; diff --git a/packages/common/assets/tsup.config.ts b/packages/common/assets/tsup.config.ts index 450a1b173..59c4afaeb 100644 --- a/packages/common/assets/tsup.config.ts +++ b/packages/common/assets/tsup.config.ts @@ -6,6 +6,7 @@ export default defineConfig({ "src/styles/base.css", "src/styles/diveicon.css", "src/styles/grid.css", + "src/styles/components.css", ], format: ["cjs", "esm"], dts: true, From 131ceec61e22d77723a6ccdc1a66fe9ab1c487da Mon Sep 17 00:00:00 2001 From: jcchoi Date: Wed, 26 Mar 2025 14:14:23 +0900 Subject: [PATCH 21/21] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit components.css 추가 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 798425682..e74e7e815 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,9 @@ export default nextConfig; ### 6. globals.css(또는 전역 css) 수정 ```css -@import "@common/assets/index.css"; +@import "@common/assets/styles/base.css"; +@import "@common/assets/styles/diveicon.css"; +@import "@common/assets/styles/components.css"; @tailwind base; @tailwind components;