From 5b4e67421433d3958c3cda7b6df8951d27e9dc64 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 26 Mar 2025 11:00:24 +0900 Subject: [PATCH 001/102] =?UTF-8?q?rename:=20base=20bottom=20form=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/{base-bottom-menu-form.tsx => base-type-form.tsx} | 6 +----- apps/cs/src/pages/menu/ui/board-menu-form.tsx | 4 ++-- apps/cs/src/pages/menu/ui/bottom-menu-form.tsx | 4 ++-- apps/cs/src/pages/menu/ui/content-menu-form.tsx | 4 ++-- apps/cs/src/pages/menu/ui/empty-menu-form.tsx | 4 ++-- apps/cs/src/pages/menu/ui/inside-menu-form.tsx | 4 ++-- apps/cs/src/pages/menu/ui/menu-form.tsx | 7 ++----- apps/cs/src/pages/menu/ui/outside-menu-form.tsx | 4 ++-- 8 files changed, 15 insertions(+), 22 deletions(-) rename apps/cs/src/pages/menu/ui/{base-bottom-menu-form.tsx => base-type-form.tsx} (97%) diff --git a/apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx b/apps/cs/src/pages/menu/ui/base-type-form.tsx similarity index 97% rename from apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx rename to apps/cs/src/pages/menu/ui/base-type-form.tsx index d8afc344d..3d0c57eb7 100644 --- a/apps/cs/src/pages/menu/ui/base-bottom-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-type-form.tsx @@ -17,11 +17,7 @@ import { } from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; -export function BaseBottomMenuForm({ - form, -}: { - form: UseFormReturn; -}) { +export function BaseTypeForm({ form }: { form: UseFormReturn }) { const handleIconClick = (icon: string) => { form.setValue("iconNm", icon); }; 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 a63fe24ff..91ebc68d3 100644 --- a/apps/cs/src/pages/menu/ui/board-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/board-menu-form.tsx @@ -16,7 +16,7 @@ import { FormMessage, } from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; -import { BaseBottomMenuForm } from "./base-bottom-menu-form"; +import { BaseTypeForm } from "./base-type-form"; export function BoardMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -97,7 +97,7 @@ export function BoardMenuForm({ form }: { form: UseFormReturn }) { - + QR코드 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 88199147b..5214f3a13 100644 --- a/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx @@ -1,7 +1,7 @@ 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"; +import { BaseTypeForm } from "./base-type-form"; export function BottomMenuForm({ form, @@ -15,7 +15,7 @@ export function 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 65e806f25..c5abc36bf 100644 --- a/apps/cs/src/pages/menu/ui/content-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/content-menu-form.tsx @@ -16,7 +16,7 @@ import { FormMessage, } from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; -import { BaseBottomMenuForm } from "./base-bottom-menu-form"; +import { BaseTypeForm } from "./base-type-form"; export function ContentMenuForm({ form, @@ -101,7 +101,7 @@ export function ContentMenuForm({ - + 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 index 1cdaaed97..d65ffd602 100644 --- a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx @@ -1,7 +1,7 @@ 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"; +import { BaseTypeForm } from "./base-type-form"; export function EmptyMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -11,7 +11,7 @@ export function EmptyMenuForm({ form }: { form: UseFormReturn }) { - + ); 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 8e63a2615..03325dc7b 100644 --- a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx @@ -1,7 +1,7 @@ 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"; +import { BaseTypeForm } from "./base-type-form"; export function InsideMenuForm({ form, @@ -15,7 +15,7 @@ export function InsideMenuForm({ - + ); diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index 894927ba0..5fce6d13d 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -67,7 +67,7 @@ export function MenuForm({
- {renderMenuTypeComponent(menuType, form)} + {renderTypeForm(menuType, form)}
@@ -88,10 +88,7 @@ export function MenuForm({ ); } -function renderMenuTypeComponent( - menuType: string, - form: UseFormReturn, -) { +function renderTypeForm(menuType: string, form: UseFormReturn) { switch (menuType) { case MENU_TYPE.EMPTY: return ; 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 b92977a83..750a00a21 100644 --- a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/outside-menu-form.tsx @@ -1,7 +1,7 @@ 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"; +import { BaseTypeForm } from "./base-type-form"; export function OutsideMenuForm({ form, @@ -15,7 +15,7 @@ export function OutsideMenuForm({ - + ); From d4f7cf140e2e446a7e47a5216c491a387700d16d Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 26 Mar 2025 15:07:53 +0900 Subject: [PATCH 002/102] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=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 }; From f03b07738e0e747e2dc5b915b082fbfac8a17509 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 26 Mar 2025 15:35:15 +0900 Subject: [PATCH 003/102] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=88=98?= =?UTF-8?q?=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/pages/menu/api/menu-service.ts | 11 ++++ .../cs/src/pages/menu/model/menu-interface.ts | 8 +++ apps/cs/src/pages/menu/ui/menu-form.tsx | 63 ++++++++++++++----- apps/cs/src/pages/menu/ui/menu-page.tsx | 19 +++++- 4 files changed, 83 insertions(+), 18 deletions(-) diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index 4825da594..6d22dcc58 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -16,6 +16,17 @@ export class MenuApiService implements MenuService { return response.data; } + public async modifyMenu( + menuCd: number, + data: Omit, + ): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + 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 dfc8de878..99c132bd6 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -59,12 +59,20 @@ export const menuFormSchema = z.object({ evlUseYn: z.string().default("Y").nullish(), qrcd: z.string().nullish(), useYn: z.string().default("Y").nullish(), + menuLnkgSnNm: z.string().nullish(), + menuPicUserUnqId: z.string().nullish(), + menuPicUserUnqIdNm: z.string().nullish(), + menuPicDsgnYmd: z.string().nullish(), }); export type MenuFormData = z.infer; export interface MenuService { createMenu: (data: MenuFormData) => Promise; + modifyMenu: ( + menuCd: number, + data: Omit, + ) => 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-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index 2db4c979a..e85204e17 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -17,36 +17,65 @@ import { PermissionsManagementDialog } from "./permissions-management-dialog"; interface MenuFormProps { form: UseFormReturn; menuCd?: number; - handleSave: (data: MenuFormData) => void; + handleCreate: (data: MenuFormData) => void; + handleModify: ({ + menuCd, + data, + }: { + menuCd: number; + data: Omit; + }) => void; handleDelete?: () => void; } -function MenuForm({ form, menuCd, handleSave, handleDelete }: MenuFormProps) { +function MenuForm({ + form, + menuCd, + handleCreate, + handleModify, + handleDelete, +}: MenuFormProps) { const isModifyMode = !!menuCd; const { confirm } = useConfirm(); const [onConfirm, setOnConfirm] = useState(false); - const onSubmit = async (data: MenuFormData) => { - const confirmMessage = isModifyMode - ? "수정하시겠습니까?" - : "저장하시겠습니까?"; - + const handleConfirm = async (fn: () => Promise) => { setOnConfirm(true); - if (await confirm(confirmMessage)) { - setOnConfirm(false); - handleSave(data); - } + await fn(); setOnConfirm(false); }; - const onDelete = async () => { - setOnConfirm(true); - if (await confirm("삭제하시겠습니까?")) { - setOnConfirm(false); - handleDelete?.(); + const onSubmit = async (data: MenuFormData) => { + if (isModifyMode) { + onModify(data); + } else { + onCreate(data); } - setOnConfirm(false); + }; + + const onCreate = (data: MenuFormData) => { + handleConfirm(async () => { + if (await confirm("저장하시겠습니까?")) { + handleCreate(data); + } + }); + }; + + const onModify = (data: MenuFormData) => { + handleConfirm(async () => { + if (await confirm("수정하시겠습니까?")) { + handleModify({ menuCd: menuCd!, data }); + } + }); + }; + + const onDelete = () => { + handleConfirm(async () => { + if (await confirm("삭제하시겠습니까?")) { + handleDelete?.(); + } + }); }; const menuType = form.watch("menuLnkgTypeCd"); diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index f159cbcf2..edd1910fd 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -69,6 +69,22 @@ function MenuPage() { }, }); + const { mutate: modifyMenu } = useMutation({ + mutationFn: ({ + menuCd, + data, + }: { + menuCd: number; + data: Omit; + }) => MenuApi.modifyMenu(menuCd, data), + onSuccess: () => { + alert("수정되었습니다."); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS], + }); + }, + }); + const form = useForm({ resolver: zodResolver(menuFormSchema), defaultValues: { @@ -138,7 +154,8 @@ function MenuPage() {
From b26e7e3d1043a92970a49573d0bb9af72b584790 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 26 Mar 2025 17:00:35 +0900 Subject: [PATCH 004/102] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=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/pages/menu/api/menu-service.ts | 14 ++++++-- apps/cs/src/pages/menu/lib/utils.ts | 35 ++++++++++--------- .../cs/src/pages/menu/model/menu-interface.ts | 32 +++++++++-------- apps/cs/src/pages/menu/ui/menu-form.tsx | 12 +++---- apps/cs/src/pages/menu/ui/menu-node.tsx | 6 ++-- apps/cs/src/pages/menu/ui/menu-page.tsx | 26 +++++++++----- apps/cs/src/pages/menu/ui/menu-tree.tsx | 12 +++---- 7 files changed, 78 insertions(+), 59 deletions(-) diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index 6d22dcc58..cdb64b521 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -5,9 +5,17 @@ import type { MenuFormData, MenuService, TreeMenu, + TreeNode, } from "../model/menu-interface"; export class MenuApiService implements MenuService { + public async getMenuInfo(menuCd: number): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + ); + return response.data; + } + public async createMenu(data: MenuFormData): Promise { const response = await client.post( `${ENDPOINT.CMS_SERVICE.MENUS}`, @@ -27,14 +35,14 @@ export class MenuApiService implements MenuService { return response.data; } - public async getTreeMenuList(siteId: string): Promise { - const response = await client.get( + public async getTreeMenuList(siteId: string): Promise { + const response = await client.get( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, ); return response.data; } - public async modifyTreeMenu(siteId: string, data: TreeMenu[]): Promise { + public async modifyTreeMenu(siteId: string, data: TreeNode[]): Promise { const response = await client.put( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, data, diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index 333ebad94..a567cf96f 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -1,9 +1,9 @@ import type { DropOptions, NodeModel } from "@minoru/react-dnd-treeview"; -import type { Menu, TreeMenu } from "../model/menu-interface"; +import type { TreeMenu, TreeNode } from "../model/menu-interface"; -export const generateTreeData = (data: Menu[]): NodeModel[] => { - return data.reduce[]>((acc, menu) => { - const node: NodeModel = { +export const generateTreeData = (data: TreeMenu[]): NodeModel[] => { + return data.reduce[]>((acc, menu) => { + const node: NodeModel = { id: menu.menuCd ?? 0, parent: menu.parentId ?? 0, text: menu.menuKornNm, @@ -22,9 +22,9 @@ export const generateTreeData = (data: Menu[]): NodeModel[] => { }; export const generateNewTreeData = ( - treeData: NodeModel[], - options: DropOptions, -): TreeMenu | null => { + treeData: NodeModel[], + options: DropOptions, +): TreeNode | null => { const { dragSourceId, dragSource, dropTargetId, dropTarget } = options; if (!dragSource?.data) return null; @@ -38,7 +38,7 @@ export const generateNewTreeData = ( (node) => node.parent === dragSourceId && node.data, ); - const baseNode = (node: NodeModel): TreeMenu => { + const baseNode = (node: NodeModel): TreeNode => { if (!node.data) return {}; return { menuCd: node.data?.menuCd, @@ -47,20 +47,20 @@ export const generateNewTreeData = ( }; }; - const treeMenu: TreeMenu = { + const treeNode: TreeNode = { ...baseNode(dragSource), parentId: dropTargetId === 0 ? null : (dropTargetId as number), sortSeq: newSortSeq, levelNo: (dropTarget?.data?.levelNo ?? 0) + 1, }; - const result: TreeMenu = { - ...treeMenu, + const result: TreeNode = { + ...treeNode, children: childNodes.map((child) => ({ ...baseNode(child), parentId: child.data?.parentId === 0 ? null : (dropTargetId as number), sortSeq: child.data?.sortSeq, - levelNo: (treeMenu?.levelNo ?? 0) + 1, + levelNo: (treeNode?.levelNo ?? 0) + 1, })), }; @@ -68,21 +68,22 @@ export const generateNewTreeData = ( }; export const handleSelectNode = - (callback: (node: NodeModel) => void) => (node: NodeModel) => { + (callback: (node: NodeModel) => void) => + (node: NodeModel) => { callback(node); }; export const handleDropNode = - (callback: (newTree: TreeMenu) => void) => - (newTree: NodeModel[], options: DropOptions) => { + (callback: (newTree: TreeNode) => void) => + (newTree: NodeModel[], options: DropOptions) => { const newTreeData = generateNewTreeData(newTree, options); if (!newTreeData) return; callback(newTreeData); }; export const handleNodeTextChange = - (treeData: NodeModel[]) => - (callback: (newTree: NodeModel[]) => void) => + (treeData: NodeModel[]) => + (callback: (newTree: NodeModel[]) => void) => (id: NodeModel["id"], value: string) => { const newTree = treeData.map((node) => { if (node.id === id) { diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 99c132bd6..31f319c82 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -1,31 +1,31 @@ import { z } from "zod"; export const MENU_TYPE = { - EMPTY: "EMPTY", - CONTENTS: "CONTENTS", - BOARD: "BOARD", - INSIDE: "INSIDE", - OUTSIDE: "OUTSIDE", - BOTTOM: "BOTTOM", + 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 TreeMenu { +export interface TreeNode { menuCd?: number; name?: string; sortSeq?: number; parentId?: number | null; levelNo?: number; icon?: string; - children?: TreeMenu[]; + children?: TreeNode[]; } -export interface Menu extends Omit { +export interface TreeMenu extends Omit { menuKornNm: string; menuEngNm: string; iconNm: string; - children: Menu[]; + children: TreeMenu[]; urlPath: string; menuLnkgTypeCd: string; menuLnkgSn: number; @@ -53,12 +53,12 @@ export const menuFormSchema = z.object({ MENU_TYPE.BOTTOM, ]), menuLnkgSn: z.number().nullish(), - otptYn: z.string().default("Y").nullish(), + otptYn: z.string().default("Y"), prntMenuCd: z.number().nullish(), sortSeq: z.number(), - evlUseYn: z.string().default("Y").nullish(), + evlUseYn: z.string().default("Y"), qrcd: z.string().nullish(), - useYn: z.string().default("Y").nullish(), + useYn: z.string().default("Y"), menuLnkgSnNm: z.string().nullish(), menuPicUserUnqId: z.string().nullish(), menuPicUserUnqIdNm: z.string().nullish(), @@ -66,14 +66,16 @@ export const menuFormSchema = z.object({ }); export type MenuFormData = z.infer; +export type Menu = MenuFormData & { menuCd: number }; export interface MenuService { + getMenuInfo: (menuCd: number) => Promise; createMenu: (data: MenuFormData) => Promise; modifyMenu: ( menuCd: number, data: Omit, ) => Promise; - getTreeMenuList: (siteId: string) => Promise; - modifyTreeMenu: (siteId: string, data: TreeMenu[]) => Promise; + getTreeMenuList: (siteId: string) => Promise; + modifyTreeMenu: (siteId: string, data: TreeNode[]) => Promise; modifyMenuName: (menuCd: number, name: string) => Promise; } diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index e85204e17..f42e3d725 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -48,22 +48,22 @@ function MenuForm({ const onSubmit = async (data: MenuFormData) => { if (isModifyMode) { - onModify(data); + await onModify(data); } else { - onCreate(data); + await onCreate(data); } }; - const onCreate = (data: MenuFormData) => { - handleConfirm(async () => { + const onCreate = async (data: MenuFormData) => { + await handleConfirm(async () => { if (await confirm("저장하시겠습니까?")) { handleCreate(data); } }); }; - const onModify = (data: MenuFormData) => { - handleConfirm(async () => { + const onModify = async (data: MenuFormData) => { + await handleConfirm(async () => { if (await confirm("수정하시겠습니까?")) { handleModify({ menuCd: menuCd!, data }); } diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx index 827e480d4..d938f0df0 100644 --- a/apps/cs/src/pages/menu/ui/menu-node.tsx +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -5,15 +5,15 @@ import { Button, Input } from "@common/components/ui"; import { cn } from "@common/components/lib"; import { ENDPOINT } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; -import type { Menu } from "../model/menu-interface"; +import type { TreeMenu } from "../model/menu-interface"; type MenuNodeProps = { - node: NodeModel; + node: NodeModel; depth: number; isOpen: boolean; isSelected: boolean; onToggle: (id: NodeModel["id"]) => void; - onSelect: (node: NodeModel) => void; + onSelect: (node: NodeModel) => void; }; function MenuNode({ diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index edd1910fd..57fdec1f1 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -17,7 +17,7 @@ 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 type { TreeMenu } from "../model/menu-interface"; import { MenuApi } from "../api/menu-service"; import { MENU_TYPE, @@ -33,7 +33,13 @@ function MenuPage() { const { setMessage: alert } = useAlertStore((state) => state); const [siteId, setSiteId] = useState(""); - const [selectedMenu, setSelectedMenu] = useState(null); + const [selectedMenu, setSelectedMenu] = useState(null); + + const { data: menuInfo } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, selectedMenu?.menuCd], + queryFn: () => MenuApi.getMenuInfo(selectedMenu?.menuCd ?? 0), + enabled: !!selectedMenu?.menuCd, + }); const { data: treeData } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], @@ -91,13 +97,15 @@ function MenuPage() { menuLnkgTypeCd: MENU_TYPE.EMPTY, }, values: { - ...selectedMenu, + ...menuInfo, siteId, - sortSeq: selectedMenu?.sortSeq ?? 1, - levelNo: selectedMenu?.levelNo ?? 1, - menuKornNm: selectedMenu?.menuKornNm ?? "", - menuLnkgTypeCd: - (selectedMenu?.menuLnkgTypeCd as MenuType) ?? MENU_TYPE.EMPTY, + sortSeq: menuInfo?.sortSeq ?? 1, + levelNo: menuInfo?.levelNo ?? 1, + menuKornNm: menuInfo?.menuKornNm ?? "", + menuLnkgTypeCd: menuInfo?.menuLnkgTypeCd ?? MENU_TYPE.EMPTY, + otptYn: menuInfo?.otptYn ?? "Y", + evlUseYn: menuInfo?.evlUseYn ?? "Y", + useYn: menuInfo?.useYn ?? "Y", }, }); @@ -106,7 +114,7 @@ function MenuPage() { form.setValue("siteId", value); }; - const handleNodeSelect = (node: NodeModel) => { + const handleNodeSelect = (node: NodeModel) => { if (!node.data) return; setSelectedMenu(node.data); }; diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index 02f7ffbba..a7d6b0fd3 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -10,15 +10,15 @@ import { } from "@minoru/react-dnd-treeview"; import { ENDPOINT } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; -import type { Menu, TreeMenu } from "../model/menu-interface"; +import type { TreeMenu, TreeNode } from "../model/menu-interface"; import { handleDropNode, handleSelectNode } from "../lib/utils"; import { MenuNode } from "./menu-node"; type MenuTreeProps = { - treeData: NodeModel[]; + treeData: NodeModel[]; siteId: string; - selectedMenu: Menu | null; - onSelectNode: (node: NodeModel) => void; + selectedMenu: TreeMenu | null; + onSelectNode: (node: NodeModel) => void; }; function MenuTree({ @@ -30,7 +30,7 @@ function MenuTree({ const queryClient = useQueryClient(); const { mutate: modifyTreeMenu } = useMutation({ - mutationFn: (newTree: TreeMenu) => + mutationFn: (newTree: TreeNode) => MenuApi.modifyTreeMenu(siteId, [newTree]), onSuccess: () => { queryClient.invalidateQueries({ @@ -44,7 +44,7 @@ function MenuTree({ , { depth, isOpen, onToggle }) => ( + render={(node: NodeModel, { depth, isOpen, onToggle }) => ( Date: Wed, 26 Mar 2025 17:46:05 +0900 Subject: [PATCH 005/102] =?UTF-8?q?refactor:=20=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=88=9C=EC=84=9C=20=EB=B3=80=EA=B2=BD=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 | 8 +++++++- apps/cs/src/pages/menu/ui/menu-page.tsx | 6 +++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index a567cf96f..304c7166a 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -31,8 +31,14 @@ export const generateNewTreeData = ( const siblings = treeData.filter((node) => node.parent === dropTargetId); + const dragSourceIndex = siblings.findIndex( + (node) => node.id === dragSourceId, + ); + + const prevSibling = siblings[dragSourceIndex - 1]; + const newSortSeq = - siblings.findIndex((sibling) => sibling.id === dragSourceId) + 1; + dragSourceIndex === 0 ? 1 : (prevSibling?.data?.sortSeq ?? 0) + 1; const childNodes = treeData.filter( (node) => node.parent === dragSourceId && node.data, diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 57fdec1f1..3219291ab 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -116,6 +116,10 @@ function MenuPage() { const handleNodeSelect = (node: NodeModel) => { if (!node.data) return; + if (node.data.menuCd === selectedMenu?.menuCd) { + setSelectedMenu(null); + return; + } setSelectedMenu(node.data); }; @@ -123,7 +127,7 @@ function MenuPage() { return ( -
+
+
+ + +
+ + ); +} + +export { EditableNode }; 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 4c97e1c66..78339d90f 100644 --- a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/empty-menu-form.tsx @@ -1,7 +1,7 @@ import { type UseFormReturn } from "react-hook-form"; import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; -import { BaseTypeForm } from "./base-type-form"; +import { BaseTypeMenuForm } from "./base-type-menu-form"; function EmptyMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -11,7 +11,7 @@ function EmptyMenuForm({ form }: { form: UseFormReturn }) { - + ); 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 4b894690c..f2fed53cf 100644 --- a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx @@ -1,7 +1,7 @@ import { type UseFormReturn } from "react-hook-form"; import { Table, TableBody } from "@common/components"; import type { MenuFormData } from "../model/menu-interface"; -import { BaseTypeForm } from "./base-type-form"; +import { BaseTypeMenuForm } from "./base-type-menu-form"; function InsideMenuForm({ form }: { form: UseFormReturn }) { return ( @@ -11,7 +11,7 @@ function InsideMenuForm({ form }: { form: UseFormReturn }) { - + ); diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index f42e3d725..13a910c23 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -95,7 +95,7 @@ function MenuForm({
- {renderTypeForm(menuType, form)} + {renderTypeMenuForm(menuType, form)}
@@ -116,7 +116,10 @@ function MenuForm({ ); } -function renderTypeForm(menuType: string, form: UseFormReturn) { +function renderTypeMenuForm( + menuType: string, + form: UseFormReturn, +) { switch (menuType) { case MENU_TYPE.EMPTY: return ; diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx index d938f0df0..add24f3af 100644 --- a/apps/cs/src/pages/menu/ui/menu-node.tsx +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -1,11 +1,12 @@ import { useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import type { NodeModel } from "@minoru/react-dnd-treeview"; -import { Button, Input } from "@common/components/ui"; +import { Button } from "@common/components/ui"; import { cn } from "@common/components/lib"; import { ENDPOINT } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; import type { TreeMenu } from "../model/menu-interface"; +import { EditableNode } from "./editable-node"; type MenuNodeProps = { node: NodeModel; @@ -52,6 +53,11 @@ function MenuNode({ setEditMode(false); }; + const handleCancel = () => { + setEditMode(false); + setNodeName(text); + }; + return (
-
- {editMode ? ( - <> - setNodeName(e.target.value)} - className="h-[2.5rem] !text-[1.3rem] text-foreground" - /> - - ) : ( - <> + {editMode ? ( + setNodeName(e.target.value)} + onSave={handleSave} + onCancel={handleCancel} + /> + ) : ( + <> +
{data?.children?.length && ( )} {text} - - )} -
- {editMode ? ( -
+
- -
- ) : ( - + )}
); diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 3219291ab..8e13fee10 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -141,7 +141,7 @@ function MenuPage() { ))} - {treeData && ( + {siteId && ( <>
- +
); } diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx index add24f3af..bce0f8ac2 100644 --- a/apps/cs/src/pages/menu/ui/menu-node.tsx +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -1,6 +1,6 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { useMutation, useQueryClient } from "@tanstack/react-query"; -import type { NodeModel } from "@minoru/react-dnd-treeview"; +import type { NodeModel, TreeMethods } from "@minoru/react-dnd-treeview"; import { Button } from "@common/components/ui"; import { cn } from "@common/components/lib"; import { ENDPOINT } from "@/shared/config"; @@ -13,6 +13,8 @@ type MenuNodeProps = { depth: number; isOpen: boolean; isSelected: boolean; + isAdditionalParent: boolean; + treeMethods: TreeMethods | null; onToggle: (id: NodeModel["id"]) => void; onSelect: (node: NodeModel) => void; }; @@ -22,6 +24,8 @@ function MenuNode({ depth, isOpen, isSelected, + isAdditionalParent, + treeMethods, onToggle, onSelect, }: MenuNodeProps) { @@ -58,6 +62,10 @@ function MenuNode({ setNodeName(text); }; + useEffect(() => { + if (isAdditionalParent) treeMethods?.open(id); + }, [isAdditionalParent]); + return (
(null); + const [additionalNode, setAdditionalNode] = + useState | null>(null); const { data: menuInfo } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS, selectedMenu?.menuCd], @@ -65,7 +67,7 @@ function MenuPage() { })), }); - const { mutate: createMenu } = useMutation({ + const { mutateAsync: createMenu } = useMutation({ mutationFn: (data: MenuFormData) => MenuApi.createMenu(data), onSuccess: () => { alert("저장되었습니다."); @@ -99,8 +101,8 @@ function MenuPage() { values: { ...menuInfo, siteId, - sortSeq: menuInfo?.sortSeq ?? 1, levelNo: menuInfo?.levelNo ?? 1, + sortSeq: menuInfo?.sortSeq ?? 1, menuKornNm: menuInfo?.menuKornNm ?? "", menuLnkgTypeCd: menuInfo?.menuLnkgTypeCd ?? MENU_TYPE.EMPTY, otptYn: menuInfo?.otptYn ?? "Y", @@ -109,21 +111,64 @@ function MenuPage() { }, }); + const additionalNodeForm = useForm({ + resolver: zodResolver(menuFormSchema), + defaultValues: { + menuLnkgTypeCd: MENU_TYPE.EMPTY, + levelNo: 1, + sortSeq: 1, + otptYn: "Y", + evlUseYn: "Y", + useYn: "Y", + }, + }); + + const handleAdditionalNodeSave = async () => { + await createMenu({ + ...additionalNodeForm.getValues(), + prntMenuCd: additionalNode?.parent as number, + }); + setAdditionalNode(null); + }; + + const handleAdditionalNodeCancel = () => { + additionalNodeForm.setValue("menuKornNm", ""); + setAdditionalNode(null); + }; + + const handleDelete = async () => {}; + const handleSiteIdChange = (value: string) => { - setSiteId(value); form.setValue("siteId", value); + additionalNodeForm.setValue("siteId", value); + + setSiteId(value); + setSelectedMenu(null); }; const handleNodeSelect = (node: NodeModel) => { if (!node.data) return; if (node.data.menuCd === selectedMenu?.menuCd) { setSelectedMenu(null); + additionalNodeForm.setValue("levelNo", 1); return; } setSelectedMenu(node.data); + additionalNodeForm.setValue("levelNo", (node.data.levelNo ?? 0) + 1); }; - const handleDelete = async () => {}; + const handleAddMenu = () => { + if (additionalNode) return; + setAdditionalNode({ + id: -1, + parent: selectedMenu?.menuCd ?? 0, + text: "", + }); + }; + + const tree = additionalNode + ? [...(treeData ?? []), ...(additionalNode ? [additionalNode] : [])] + : (treeData ?? []); return ( @@ -144,19 +189,32 @@ function MenuPage() { {siteId && ( <>
- -
diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index a7d6b0fd3..59f55b4c2 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -1,33 +1,44 @@ "use client"; +import { useRef } from "react"; import { DndProvider } from "react-dnd"; +import type { UseFormReturn } from "react-hook-form"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { Tree, MultiBackend, getBackendOptions, type NodeModel, + type TreeMethods, } from "@minoru/react-dnd-treeview"; import { ENDPOINT } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; -import type { TreeMenu, TreeNode } from "../model/menu-interface"; +import type { MenuFormData, TreeMenu, TreeNode } from "../model/menu-interface"; import { handleDropNode, handleSelectNode } from "../lib/utils"; import { MenuNode } from "./menu-node"; +import { EditableNode } from "./editable-node"; type MenuTreeProps = { - treeData: NodeModel[]; + tree: NodeModel[]; siteId: string; selectedMenu: TreeMenu | null; + additionalNodeForm: UseFormReturn; onSelectNode: (node: NodeModel) => void; + onAdditionalNodeSave: () => void; + onAdditionalNodeCancel: () => void; }; function MenuTree({ - treeData, + tree, siteId, selectedMenu, + additionalNodeForm, onSelectNode, + onAdditionalNodeSave, + onAdditionalNodeCancel, }: MenuTreeProps) { const queryClient = useQueryClient(); + const treeMethods = useRef(null); const { mutate: modifyTreeMenu } = useMutation({ mutationFn: (newTree: TreeNode) => @@ -39,21 +50,46 @@ function MenuTree({ }, }); + const additionalNode = tree.find((n) => n.id === -1); + + const isAdditionalParent = (node: NodeModel): boolean => { + if (!additionalNode) return false; + return additionalNode?.parent === node.id; + }; + return ( , { depth, isOpen, onToggle }) => ( - - )} + ref={treeMethods} + render={(node: NodeModel, { depth, isOpen, onToggle }) => { + if (node.id === -1) { + return ( + + additionalNodeForm.setValue("menuKornNm", e.target.value) + } + depth={depth} + onSave={onAdditionalNodeSave} + onCancel={onAdditionalNodeCancel} + /> + ); + } + return ( + + ); + }} onDrop={handleDropNode(modifyTreeMenu)} classes={{ root: "p-4", From 0fa026e11b0fcf87df47c7d3e22aa27d9917317f Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 14:59:16 +0900 Subject: [PATCH 008/102] =?UTF-8?q?fix:=20prntMenuCd=20=EC=97=86=EC=9D=84?= =?UTF-8?q?=20=EA=B2=BD=EC=9A=B0=20null=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/menu/ui/menu-page.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 7125710aa..18837dc26 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -124,10 +124,15 @@ function MenuPage() { }); const handleAdditionalNodeSave = async () => { + const prntMenuCd = !additionalNode?.parent + ? null + : (additionalNode?.parent as number); + await createMenu({ ...additionalNodeForm.getValues(), - prntMenuCd: additionalNode?.parent as number, + prntMenuCd, }); + additionalNodeForm.setValue("menuKornNm", ""); setAdditionalNode(null); }; @@ -167,7 +172,7 @@ function MenuPage() { }; const tree = additionalNode - ? [...(treeData ?? []), ...(additionalNode ? [additionalNode] : [])] + ? [...(additionalNode ? [additionalNode] : []), ...(treeData ?? [])] : (treeData ?? []); return ( From ae5597b1f330fcca8dcc61c310162afa2b548c40 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 15:23:01 +0900 Subject: [PATCH 009/102] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=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/api/menu-service.ts | 7 + .../cs/src/pages/menu/model/menu-interface.ts | 1 + apps/cs/src/pages/menu/ui/menu-form.tsx | 4 +- apps/cs/src/pages/menu/ui/menu-node.tsx | 5 +- apps/cs/src/pages/menu/ui/menu-page.tsx | 159 ++++++++++-------- 5 files changed, 105 insertions(+), 71 deletions(-) diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index cdb64b521..4e03f6cdf 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -35,6 +35,13 @@ export class MenuApiService implements MenuService { return response.data; } + public async deleteMenu(menuCd: number): Promise { + const response = await client.delete( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + ); + 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 91043a76c..2498dde5f 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -75,6 +75,7 @@ export interface MenuService { menuCd: number, data: Omit, ) => Promise; + deleteMenu: (menuCd: number) => Promise; getTreeMenuList: (siteId: string) => Promise; modifyTreeMenu: (siteId: string, data: TreeNode[]) => Promise; modifyMenuName: (menuCd: number, name: string) => Promise; diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index 13a910c23..922c2bb78 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -25,7 +25,7 @@ interface MenuFormProps { menuCd: number; data: Omit; }) => void; - handleDelete?: () => void; + handleDelete?: (menuCd: number) => void; } function MenuForm({ @@ -73,7 +73,7 @@ function MenuForm({ const onDelete = () => { handleConfirm(async () => { if (await confirm("삭제하시겠습니까?")) { - handleDelete?.(); + handleDelete?.(menuCd!); } }); }; diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx index bce0f8ac2..b2e595923 100644 --- a/apps/cs/src/pages/menu/ui/menu-node.tsx +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -90,7 +90,10 @@ function MenuNode({ "diveicon di-arrow-forward mr-2 text-base text-label", isOpen && "rotate-90", )} - onClick={handleToggle} + onClick={(e) => { + e.stopPropagation(); + handleToggle(); + }} /> )} {text} diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 18837dc26..12b4de492 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -4,6 +4,7 @@ import { useState } from "react"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import type { NodeModel } from "@minoru/react-dnd-treeview"; +import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Button, @@ -16,6 +17,7 @@ import { import { SiteApi } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; import { PageLayout } from "@/shared/ui/page-layout"; +import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAlertStore } from "@/shared/lib/use-alert-store"; import type { TreeMenu } from "../model/menu-interface"; import { MenuApi } from "../api/menu-service"; @@ -31,8 +33,10 @@ import { generateTreeData } from "../lib/utils"; function MenuPage() { const queryClient = useQueryClient(); const { setMessage: alert } = useAlertStore((state) => state); + const { confirm } = useConfirm(); const [siteId, setSiteId] = useState(""); + const [onConfirm, setOnConfirm] = useState(false); const [selectedMenu, setSelectedMenu] = useState(null); const [additionalNode, setAdditionalNode] = useState | null>(null); @@ -72,7 +76,7 @@ function MenuPage() { onSuccess: () => { alert("저장되었습니다."); queryClient.invalidateQueries({ - queryKey: [ENDPOINT.CMS_SERVICE.MENUS], + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], }); }, }); @@ -88,7 +92,17 @@ function MenuPage() { onSuccess: () => { alert("수정되었습니다."); queryClient.invalidateQueries({ - queryKey: [ENDPOINT.CMS_SERVICE.MENUS], + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], + }); + }, + }); + + const { mutate: deleteMenu } = useMutation({ + mutationFn: (menuCd: number) => MenuApi.deleteMenu(menuCd), + onSuccess: () => { + alert("삭제되었습니다."); + queryClient.invalidateQueries({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], }); }, }); @@ -132,17 +146,14 @@ function MenuPage() { ...additionalNodeForm.getValues(), prntMenuCd, }); + resetAdditionalNode(); + }; + + const resetAdditionalNode = () => { additionalNodeForm.setValue("menuKornNm", ""); setAdditionalNode(null); }; - const handleAdditionalNodeCancel = () => { - additionalNodeForm.setValue("menuKornNm", ""); - setAdditionalNode(null); - }; - - const handleDelete = async () => {}; - const handleSiteIdChange = (value: string) => { form.setValue("siteId", value); additionalNodeForm.setValue("siteId", value); @@ -162,6 +173,14 @@ function MenuPage() { additionalNodeForm.setValue("levelNo", (node.data.levelNo ?? 0) + 1); }; + const handleNodeDelete = async () => { + setOnConfirm(true); + if (await confirm("삭제하시겠습니까?")) { + setOnConfirm(false); + deleteMenu(selectedMenu!.menuCd!); + } + }; + const handleAddMenu = () => { if (additionalNode) return; setAdditionalNode({ @@ -176,66 +195,70 @@ function MenuPage() { : (treeData ?? []); return ( - -
-
- - {siteId && ( - <> -
- - -
-
- -
- - )} + <> + +
+
+ + {siteId && ( + <> +
+ + +
+
+ +
+ + )} +
+
+ +
-
- -
-
- + + + ); } From 8b0939ff41feec5863f9a6603acda6580446fd18 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 16:16:59 +0900 Subject: [PATCH 010/102] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EB=B3=84=20=ED=8F=BC=20=EC=9E=91=EC=97=85(=EB=82=B4?= =?UTF-8?q?=EB=B6=80=EB=A7=81=ED=81=AC,=20=EC=99=B8=EB=B6=80=EB=A7=81?= =?UTF-8?q?=ED=81=AC,=20=ED=95=98=EB=8B=A8=EB=A9=94=EB=89=B4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs/src/pages/menu/model/menu-interface.ts | 5 ++- .../src/pages/menu/ui/base-type-menu-form.tsx | 2 +- .../cs/src/pages/menu/ui/inside-menu-form.tsx | 38 ++++++++++++++++++- apps/cs/src/pages/menu/ui/menu-page.tsx | 1 + .../src/pages/menu/ui/outside-menu-form.tsx | 38 ++++++++++++++++++- 5 files changed, 80 insertions(+), 4 deletions(-) diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 2498dde5f..47df6f749 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -66,7 +66,10 @@ export const menuFormSchema = z.object({ }); export type MenuFormData = z.infer; -export type Menu = MenuFormData & { menuCd: number }; +export type Menu = Omit & { + menuCd: number; + iconName?: string; +}; export interface MenuService { getMenuInfo: (menuCd: number) => Promise; diff --git a/apps/cs/src/pages/menu/ui/base-type-menu-form.tsx b/apps/cs/src/pages/menu/ui/base-type-menu-form.tsx index 9835f1e3a..5f7c3bb45 100644 --- a/apps/cs/src/pages/menu/ui/base-type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-type-menu-form.tsx @@ -37,7 +37,7 @@ function BaseTypeMenuForm({ form }: { form: UseFormReturn }) { {...field} value={field.value ?? ""} className="!text-[1.3rem]" - placeholder="메뉴 서브명을 입력하십시오." + placeholder="사이트맵에서 다른 이름으로 사용할 경우 입력하십시오." /> 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 f2fed53cf..f671c7d26 100644 --- a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/inside-menu-form.tsx @@ -1,5 +1,18 @@ import { type UseFormReturn } from "react-hook-form"; -import { Table, TableBody } from "@common/components"; +import { + Input, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormField, + FormItem, + FormControl, + FormMessage, +} from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; @@ -11,6 +24,29 @@ function InsideMenuForm({ form }: { form: UseFormReturn }) { + + 연결 URL + + ( + + + + + + + )} + /> + + + {/* TODO: 연결 형태 추가 */} diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 12b4de492..f06f7a773 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -122,6 +122,7 @@ function MenuPage() { otptYn: menuInfo?.otptYn ?? "Y", evlUseYn: menuInfo?.evlUseYn ?? "Y", useYn: menuInfo?.useYn ?? "Y", + iconNm: menuInfo?.iconName, }, }); 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 ac72a2892..035d2dbda 100644 --- a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/outside-menu-form.tsx @@ -1,5 +1,18 @@ import { type UseFormReturn } from "react-hook-form"; -import { Table, TableBody } from "@common/components"; +import { + Input, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormField, + FormItem, + FormControl, + FormMessage, +} from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; @@ -11,6 +24,29 @@ function OutsideMenuForm({ form }: { form: UseFormReturn }) { + + 연결 URL + + ( + + + + + + + )} + /> + + + {/* TODO: 연결 형태 추가 */} From e32d0ba1447e4b8bb3de0795c0f698065b8d0858 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 16:17:42 +0900 Subject: [PATCH 011/102] =?UTF-8?q?feat:=20=EB=A9=94=EB=89=B4=20=EA=B6=8C?= =?UTF-8?q?=ED=95=9C=EA=B4=80=EB=A6=AC=20=EB=8B=A4=EC=9D=B4=EC=96=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/ui/permissions-management-dialog.tsx | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx diff --git a/apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx b/apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx new file mode 100644 index 000000000..c5f782cbb --- /dev/null +++ b/apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx @@ -0,0 +1,22 @@ +import { Button, Dialog, DialogTrigger } from "@common/components/ui"; + +type PermissionsManagementDialogProps = { + menuCd?: number; +} & React.ComponentProps; + +function PermissionsManagementDialog({ + ...props +}: PermissionsManagementDialogProps) { + return ( + + + + + + ); +} + +export { PermissionsManagementDialog }; From 89f31c4c6b7c529982cda1138b5eb1f875edba9e Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 16:29:40 +0900 Subject: [PATCH 012/102] =?UTF-8?q?chore:=20=EC=9D=B8=ED=84=B0=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EA=B7=9C?= =?UTF-8?q?=EC=B9=99=20=EB=B3=80=EA=B2=BD('I'=20prefix=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/entities/site/api/site-service.ts | 10 +++++----- apps/cs/src/entities/site/index.ts | 4 ++-- apps/cs/src/entities/site/model/site-interface.ts | 14 +++++++------- apps/cs/src/pages/site/api/domain-service.ts | 4 ++-- apps/cs/src/pages/site/api/ip-service.ts | 4 ++-- apps/cs/src/pages/site/model/domain-interface.ts | 6 +++--- apps/cs/src/pages/site/model/ip-interface.ts | 6 +++--- apps/cs/src/pages/site/model/table-columns.tsx | 4 ++-- apps/cs/src/pages/site/ui/site-page.tsx | 6 +++--- apps/cs/src/shared/model/api-interface.ts | 2 +- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/apps/cs/src/entities/site/api/site-service.ts b/apps/cs/src/entities/site/api/site-service.ts index 629bf5f01..65f6a95a0 100644 --- a/apps/cs/src/entities/site/api/site-service.ts +++ b/apps/cs/src/entities/site/api/site-service.ts @@ -4,13 +4,13 @@ import { siteFormSchema, type SiteService, type SiteList, - type ISiteDetail, - type ISiteParams, + type SiteDetail, + type SiteParams, type SiteFormData, } from "../model/site-interface"; export class SiteApiService implements SiteService { - public async getSiteList(params: ISiteParams): Promise { + public async getSiteList(params: SiteParams): Promise { const response = await client.get( `${ENDPOINT.CMS_SERVICE.SITES}`, { params }, @@ -18,8 +18,8 @@ export class SiteApiService implements SiteService { return response.data; } - public async siteDetail(siteId: string): Promise { - const response = await client.get( + public async siteDetail(siteId: string): Promise { + const response = await client.get( `${ENDPOINT.CMS_SERVICE.SITES}/${siteId}`, ); return response.data; diff --git a/apps/cs/src/entities/site/index.ts b/apps/cs/src/entities/site/index.ts index 985b9346a..c7b437a64 100644 --- a/apps/cs/src/entities/site/index.ts +++ b/apps/cs/src/entities/site/index.ts @@ -1,7 +1,7 @@ export { SiteApi } from "./api/site-service"; export { siteFormSchema, - type ISiteParams, - type ISite, + type SiteParams, + type Site, type SiteFormData, } from "./model/site-interface"; diff --git a/apps/cs/src/entities/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts index ae14297ae..7fb34670e 100644 --- a/apps/cs/src/entities/site/model/site-interface.ts +++ b/apps/cs/src/entities/site/model/site-interface.ts @@ -1,13 +1,13 @@ import { z } from "zod"; -import type { IPageableParams, PageableList } from "@/shared/model"; +import type { PageableParams, PageableList } from "@/shared/model"; -export interface ISiteParams extends IPageableParams { +export interface SiteParams extends PageableParams { siteNm?: string; siteKndCd?: string; useYn?: string; } -export interface ISite { +export interface Site { siteId: string; siteNm: string; siteKindCode: string; @@ -19,7 +19,7 @@ export interface ISite { dmnAddr: string; } -export interface ISiteDetail extends SiteFormData { +export interface SiteDetail extends SiteFormData { siteId: string; siteKindNm: string; basicSiteYn: string; @@ -49,11 +49,11 @@ export const siteFormSchema = z.object({ }); export type SiteFormData = z.infer; -export type SiteList = PageableList; +export type SiteList = PageableList; export interface SiteService { - getSiteList(params: ISiteParams): Promise; - siteDetail(siteId: string): Promise; + getSiteList(params: SiteParams): Promise; + siteDetail(siteId: string): Promise; modifySite(siteId: string, data: SiteFormData): Promise; createSite(data: SiteFormData): Promise; } diff --git a/apps/cs/src/pages/site/api/domain-service.ts b/apps/cs/src/pages/site/api/domain-service.ts index 7cc36363f..a8d2acfab 100644 --- a/apps/cs/src/pages/site/api/domain-service.ts +++ b/apps/cs/src/pages/site/api/domain-service.ts @@ -4,11 +4,11 @@ import type { Domain, DomainService, DomainList, - IDomainParams, + DomainParams, } from "../model/domain-interface"; export class DomainApiService implements DomainService { - public async getDomainList(params: IDomainParams): Promise { + public async getDomainList(params: DomainParams): Promise { const response = await client.get( `${ENDPOINT.CMS_SERVICE.DOMAINS}`, { params }, diff --git a/apps/cs/src/pages/site/api/ip-service.ts b/apps/cs/src/pages/site/api/ip-service.ts index 748676b21..903f0407e 100644 --- a/apps/cs/src/pages/site/api/ip-service.ts +++ b/apps/cs/src/pages/site/api/ip-service.ts @@ -1,9 +1,9 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import type { IIpParams, IP, IpList, IpService } from "../model/ip-interface"; +import type { IpParams, IP, IpList, IpService } from "../model/ip-interface"; export class IpApiService implements IpService { - public async getIpList(params: IIpParams): Promise { + public async getIpList(params: IpParams): Promise { const response = await client.get(`${ENDPOINT.CMS_SERVICE.IPS}`, { params, }); diff --git a/apps/cs/src/pages/site/model/domain-interface.ts b/apps/cs/src/pages/site/model/domain-interface.ts index 5f1076275..d9e3886c7 100644 --- a/apps/cs/src/pages/site/model/domain-interface.ts +++ b/apps/cs/src/pages/site/model/domain-interface.ts @@ -1,7 +1,7 @@ import { z } from "zod"; -import type { IPageableParams, PageableList } from "@/shared/model"; +import type { PageableParams, PageableList } from "@/shared/model"; -export interface IDomainParams extends IPageableParams { +export interface DomainParams extends PageableParams { keywordType?: string; keyword?: string; siteId?: string; @@ -26,6 +26,6 @@ export type DomainFormData = z.infer; export type DomainList = PageableList; export interface DomainService { - getDomainList(params: IDomainParams): Promise; + getDomainList(params: DomainParams): Promise; saveDomainList(body: Domain[]): Promise; } diff --git a/apps/cs/src/pages/site/model/ip-interface.ts b/apps/cs/src/pages/site/model/ip-interface.ts index db1d393f1..63c458b2a 100644 --- a/apps/cs/src/pages/site/model/ip-interface.ts +++ b/apps/cs/src/pages/site/model/ip-interface.ts @@ -1,7 +1,7 @@ import { z } from "zod"; -import type { IPageableParams, PageableList } from "@/shared/model"; +import type { PageableParams, PageableList } from "@/shared/model"; -export interface IIpParams extends IPageableParams { +export interface IpParams extends PageableParams { siteId: string; ipAddr?: string; prmYn?: string; @@ -24,6 +24,6 @@ export type IPFormData = z.infer; export type IpList = PageableList; export interface IpService { - getIpList(params: IIpParams): Promise; + getIpList(params: IpParams): Promise; saveIpList(body: IP[]): Promise; } diff --git a/apps/cs/src/pages/site/model/table-columns.tsx b/apps/cs/src/pages/site/model/table-columns.tsx index 2a07ce4b1..8a2d852cd 100644 --- a/apps/cs/src/pages/site/model/table-columns.tsx +++ b/apps/cs/src/pages/site/model/table-columns.tsx @@ -1,8 +1,8 @@ import dayjs from "dayjs"; import { createColumnHelper } from "@tanstack/react-table"; -import type { ISite } from "@/entities/site"; +import type { Site } from "@/entities/site"; -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const columns = [ columnHelper.accessor((_, index) => index + 1, { diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 33431f316..b5210df61 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -7,7 +7,7 @@ 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 { SiteApi, type Site, type SiteParams } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; import { PageLayout } from "@/shared/ui/page-layout"; import { columns } from "../model/table-columns"; @@ -20,7 +20,7 @@ function SitePage() { enabled: false, }); - const [siteQuery, setSiteQuery] = useState({ + const [siteQuery, setSiteQuery] = useState({ siteNm: "", }); @@ -35,7 +35,7 @@ function SitePage() { refetch(); }; - const handleRowClick = (row: ISite) => { + const handleRowClick = (row: Site) => { router.push(`/site/register/${row.siteId}`); }; diff --git a/apps/cs/src/shared/model/api-interface.ts b/apps/cs/src/shared/model/api-interface.ts index 5aa4cd09b..f1eaa6b3c 100644 --- a/apps/cs/src/shared/model/api-interface.ts +++ b/apps/cs/src/shared/model/api-interface.ts @@ -27,7 +27,7 @@ export interface PageableList { empty: boolean; } -export interface IPageableParams { +export interface PageableParams { page?: number; size?: number; sort?: string[]; From 050ce8148170172dd62524c8b7adcd51e5747a09 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 16:33:39 +0900 Subject: [PATCH 013/102] =?UTF-8?q?chore:=20PageableList=20=EC=A0=9C?= =?UTF-8?q?=EB=84=88=EB=A6=AD=20=ED=83=80=EC=9E=85=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/entities/site/model/site-interface.ts | 2 +- apps/cs/src/pages/site/model/domain-interface.ts | 2 +- apps/cs/src/pages/site/model/ip-interface.ts | 2 +- apps/cs/src/shared/model/api-interface.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/cs/src/entities/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts index 7fb34670e..ef83a53c1 100644 --- a/apps/cs/src/entities/site/model/site-interface.ts +++ b/apps/cs/src/entities/site/model/site-interface.ts @@ -49,7 +49,7 @@ export const siteFormSchema = z.object({ }); export type SiteFormData = z.infer; -export type SiteList = PageableList; +export type SiteList = PageableList; export interface SiteService { getSiteList(params: SiteParams): Promise; diff --git a/apps/cs/src/pages/site/model/domain-interface.ts b/apps/cs/src/pages/site/model/domain-interface.ts index d9e3886c7..e93f86bc1 100644 --- a/apps/cs/src/pages/site/model/domain-interface.ts +++ b/apps/cs/src/pages/site/model/domain-interface.ts @@ -23,7 +23,7 @@ export const domainFormSchema = z.object({ export type Domain = z.infer; export type DomainFormData = z.infer; -export type DomainList = PageableList; +export type DomainList = PageableList; export interface DomainService { getDomainList(params: DomainParams): Promise; diff --git a/apps/cs/src/pages/site/model/ip-interface.ts b/apps/cs/src/pages/site/model/ip-interface.ts index 63c458b2a..427fbfe57 100644 --- a/apps/cs/src/pages/site/model/ip-interface.ts +++ b/apps/cs/src/pages/site/model/ip-interface.ts @@ -21,7 +21,7 @@ export const ipFormSchema = z.object({ export type IP = z.infer; export type IPFormData = z.infer; -export type IpList = PageableList; +export type IpList = PageableList; export interface IpService { getIpList(params: IpParams): Promise; diff --git a/apps/cs/src/shared/model/api-interface.ts b/apps/cs/src/shared/model/api-interface.ts index f1eaa6b3c..133c0c401 100644 --- a/apps/cs/src/shared/model/api-interface.ts +++ b/apps/cs/src/shared/model/api-interface.ts @@ -14,7 +14,7 @@ interface PageableConfig { } export interface PageableList { - content: T; + content: T[]; pageable: PageableConfig; sort: SortConfig; totalPages: number; From c221a4537e3706c7b8335deb0e37ba1a828c3a78 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 27 Mar 2025 18:20:29 +0900 Subject: [PATCH 014/102] =?UTF-8?q?feat:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EB=93=B1=EB=A1=9D=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/api/content-service.ts | 19 +++++ .../src/pages/menu/model/content-interface.ts | 23 ++++++ .../cs/src/pages/menu/model/menu-interface.ts | 12 +-- apps/cs/src/pages/menu/model/table-colums.tsx | 32 ++++++++ apps/cs/src/pages/menu/ui/content-dialog.tsx | 73 +++++++++++++++++++ .../src/pages/menu/ui/content-menu-form.tsx | 46 ++++-------- apps/cs/src/shared/config/api.ts | 1 + .../common/components/src/ui/table/index.tsx | 11 +-- 8 files changed, 169 insertions(+), 48 deletions(-) create mode 100644 apps/cs/src/pages/menu/api/content-service.ts create mode 100644 apps/cs/src/pages/menu/model/content-interface.ts create mode 100644 apps/cs/src/pages/menu/model/table-colums.tsx create mode 100644 apps/cs/src/pages/menu/ui/content-dialog.tsx diff --git a/apps/cs/src/pages/menu/api/content-service.ts b/apps/cs/src/pages/menu/api/content-service.ts new file mode 100644 index 000000000..2f76baec8 --- /dev/null +++ b/apps/cs/src/pages/menu/api/content-service.ts @@ -0,0 +1,19 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + ContentService, + ContentParams, + ContentList, +} from "../model/content-interface"; + +export class ContentApiService implements ContentService { + public async getContentList(params: ContentParams): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONTENTS}`, + { params }, + ); + return response.data; + } +} + +export const ContentApi = new ContentApiService(); diff --git a/apps/cs/src/pages/menu/model/content-interface.ts b/apps/cs/src/pages/menu/model/content-interface.ts new file mode 100644 index 000000000..dc789d059 --- /dev/null +++ b/apps/cs/src/pages/menu/model/content-interface.ts @@ -0,0 +1,23 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface ContentParams extends PageableParams { + siteId?: string; + menuKornNm?: string; + urlPath?: string; + useYn?: string; +} + +export interface Content { + contsSn: number; + contsTtl: string; + aprvContsVer: number; + useYn: string; + rgtr: string; + regDt: string; +} + +export type ContentList = PageableList; + +export interface ContentService { + getContentList(params: ContentParams): Promise; +} diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 47df6f749..fcee4c505 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -1,12 +1,12 @@ import { z } from "zod"; export const MENU_TYPE = { - EMPTY: "empty", - CONTENTS: "contents", - BOARD: "board", - INSIDE: "inside", - OUTSIDE: "outside", - BOTTOM: "bottom", + EMPTY: "EMPTY", + CONTENTS: "CONTENTS", + BOARD: "BOARD", + INSIDE: "INSIDE", + OUTSIDE: "OUTSIDE", + BOTTOM: "BOTTOM", } as const; export type MenuType = (typeof MENU_TYPE)[keyof typeof MENU_TYPE]; diff --git a/apps/cs/src/pages/menu/model/table-colums.tsx b/apps/cs/src/pages/menu/model/table-colums.tsx new file mode 100644 index 000000000..b24b506fd --- /dev/null +++ b/apps/cs/src/pages/menu/model/table-colums.tsx @@ -0,0 +1,32 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { Checkbox } from "@common/components/ui"; +import type { Content } from "../model/content-interface"; + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor((_, index) => index + 1, { + id: "no", + header: "NO", + }), + columnHelper.accessor("contsTtl", { + id: "contsTtl", + header: "콘텐츠명", + }), + // TODO: 최종수정자, 최종수정일, 승인일시 추가 + columnHelper.display({ + id: "select", + header: "선택", + cell: ({ row }) => { + return ( + + ); + }, + }), +]; + +export { columns }; diff --git a/apps/cs/src/pages/menu/ui/content-dialog.tsx b/apps/cs/src/pages/menu/ui/content-dialog.tsx new file mode 100644 index 000000000..e6aedbb00 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/content-dialog.tsx @@ -0,0 +1,73 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + useReactTable, + type RowSelectionState, +} from "@tanstack/react-table"; +import { + Button, + DataTable, + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, +} from "@common/components/ui"; +import { ENDPOINT } from "@/shared/config"; +import { ContentApi } from "../api/content-service"; +import type { Content } from "../model/content-interface"; +import { columns } from "../model/table-colums"; + +type ContentDialogProps = { + handleContentSelect: (content: Content) => void; +} & React.ComponentProps; + +function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) { + const [rowSelection, setRowSelection] = useState({}); + + const { data: contentList } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS], + queryFn: () => + ContentApi.getContentList({ useYn: "Y", page: 0, size: 100 }), + }); + + const table = useReactTable({ + data: contentList?.content ?? [], + columns, + state: { + rowSelection, + }, + enableMultiRowSelection: false, + getCoreRowModel: getCoreRowModel(), + onRowSelectionChange: setRowSelection, + }); + + useEffect(() => { + const contentData = table.getSelectedRowModel().rows[0] + ?.original as Content; + if (!contentData) return; + handleContentSelect(contentData); + }, [table.getSelectedRowModel().rows]); + + return ( + + + + + + + 콘텐츠 선택 + +
+ +
+
+
+ ); +} + +export { ContentDialog }; 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 0905a594b..7ce40d940 100644 --- a/apps/cs/src/pages/menu/ui/content-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/content-menu-form.tsx @@ -16,9 +16,16 @@ import { FormMessage, } from "@/shared/ui/form"; import type { MenuFormData } from "../model/menu-interface"; +import type { Content } from "../model/content-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; +import { ContentDialog } from "./content-dialog"; function ContentMenuForm({ form }: { form: UseFormReturn }) { + const handleContentSelect = (content: Content) => { + form.setValue("menuLnkgSn", content.contsSn); + form.setValue("menuLnkgSnNm", content.contsTtl); + }; + return ( @@ -29,42 +36,15 @@ function ContentMenuForm({ form }: { form: UseFormReturn }) { 콘텐츠 -
- {form.watch("menuLnkgSn")} +
+
+ {form.watch("menuLnkgSnNm")} +
+
- - 연결 형태 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
+ {/* TODO: 연결 형태 추가 */} 메뉴 평가 사용 여부 diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index e07b6c9ff..b938ceaa9 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -14,6 +14,7 @@ export const ENDPOINT = { IPS: "/cms-service/api/v1/site-ips", DOMAINS: "/cms-service/api/v1/site-dmns", MENUS: "/cms-service/api/v1/menus", + CONTENTS: "/cms-service/api/v1/contents", }, }; diff --git a/packages/common/components/src/ui/table/index.tsx b/packages/common/components/src/ui/table/index.tsx index 797f48dfd..46c8db39c 100644 --- a/packages/common/components/src/ui/table/index.tsx +++ b/packages/common/components/src/ui/table/index.tsx @@ -79,11 +79,7 @@ const TableHead = React.forwardRef< HTMLTableCellElement, React.ThHTMLAttributes >(({ className, ...props }, ref) => ( -
+ )); TableHead.displayName = "TableHead"; @@ -93,10 +89,7 @@ const TableCell = React.forwardRef< >(({ className, ...props }, ref) => ( )); From cb5e90c2770832e87087f2ea30ae65fcca3b2ec3 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 09:22:07 +0900 Subject: [PATCH 015/102] =?UTF-8?q?rename:=20=ED=8F=BC=20=EB=B0=8F=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=ED=8F=B4?= =?UTF-8?q?=EB=8D=94=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/menu/ui/{ => dialog}/content-dialog.tsx | 6 +++--- .../menu/ui/{ => dialog}/permissions-management-dialog.tsx | 0 apps/cs/src/pages/menu/ui/{ => form}/base-menu-form.tsx | 2 +- .../cs/src/pages/menu/ui/{ => form}/base-type-menu-form.tsx | 2 +- apps/cs/src/pages/menu/ui/{ => form}/board-menu-form.tsx | 2 +- apps/cs/src/pages/menu/ui/{ => form}/bottom-menu-form.tsx | 2 +- apps/cs/src/pages/menu/ui/{ => form}/content-menu-form.tsx | 6 +++--- apps/cs/src/pages/menu/ui/{ => form}/empty-menu-form.tsx | 2 +- apps/cs/src/pages/menu/ui/{ => form}/inside-menu-form.tsx | 2 +- apps/cs/src/pages/menu/ui/{ => form}/menu-form.tsx | 4 ++-- apps/cs/src/pages/menu/ui/{ => form}/outside-menu-form.tsx | 2 +- 11 files changed, 15 insertions(+), 15 deletions(-) rename apps/cs/src/pages/menu/ui/{ => dialog}/content-dialog.tsx (91%) rename apps/cs/src/pages/menu/ui/{ => dialog}/permissions-management-dialog.tsx (100%) rename apps/cs/src/pages/menu/ui/{ => form}/base-menu-form.tsx (98%) rename apps/cs/src/pages/menu/ui/{ => form}/base-type-menu-form.tsx (98%) rename apps/cs/src/pages/menu/ui/{ => form}/board-menu-form.tsx (98%) rename apps/cs/src/pages/menu/ui/{ => form}/bottom-menu-form.tsx (88%) rename apps/cs/src/pages/menu/ui/{ => form}/content-menu-form.tsx (95%) rename apps/cs/src/pages/menu/ui/{ => form}/empty-menu-form.tsx (88%) rename apps/cs/src/pages/menu/ui/{ => form}/inside-menu-form.tsx (95%) rename apps/cs/src/pages/menu/ui/{ => form}/menu-form.tsx (96%) rename apps/cs/src/pages/menu/ui/{ => form}/outside-menu-form.tsx (95%) diff --git a/apps/cs/src/pages/menu/ui/content-dialog.tsx b/apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx similarity index 91% rename from apps/cs/src/pages/menu/ui/content-dialog.tsx rename to apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx index e6aedbb00..7c99637bf 100644 --- a/apps/cs/src/pages/menu/ui/content-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx @@ -15,9 +15,9 @@ import { DialogTitle, } from "@common/components/ui"; import { ENDPOINT } from "@/shared/config"; -import { ContentApi } from "../api/content-service"; -import type { Content } from "../model/content-interface"; -import { columns } from "../model/table-colums"; +import { ContentApi } from "../../api/content-service"; +import type { Content } from "../../model/content-interface"; +import { columns } from "../../model/table-colums"; type ContentDialogProps = { handleContentSelect: (content: Content) => void; diff --git a/apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx b/apps/cs/src/pages/menu/ui/dialog/permissions-management-dialog.tsx similarity index 100% rename from apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx rename to apps/cs/src/pages/menu/ui/dialog/permissions-management-dialog.tsx diff --git a/apps/cs/src/pages/menu/ui/base-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/base-menu-form.tsx similarity index 98% rename from apps/cs/src/pages/menu/ui/base-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/base-menu-form.tsx index 62a602a33..7e58a63eb 100644 --- a/apps/cs/src/pages/menu/ui/base-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/base-menu-form.tsx @@ -16,7 +16,7 @@ import { FormField, FormMessage, } from "@/shared/ui/form"; -import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; +import { MENU_TYPE, type MenuFormData } from "../../model/menu-interface"; interface BaseMenuFormProps { form: UseFormReturn; diff --git a/apps/cs/src/pages/menu/ui/base-type-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/base-type-menu-form.tsx similarity index 98% rename from apps/cs/src/pages/menu/ui/base-type-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/base-type-menu-form.tsx index 5f7c3bb45..58047ff3c 100644 --- a/apps/cs/src/pages/menu/ui/base-type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/base-type-menu-form.tsx @@ -15,7 +15,7 @@ import { FormField, FormMessage, } from "@/shared/ui/form"; -import type { MenuFormData } from "../model/menu-interface"; +import type { MenuFormData } from "../../model/menu-interface"; function BaseTypeMenuForm({ form }: { form: UseFormReturn }) { const handleIconClick = (icon: string) => { diff --git a/apps/cs/src/pages/menu/ui/board-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/board-menu-form.tsx similarity index 98% rename from apps/cs/src/pages/menu/ui/board-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/board-menu-form.tsx index df435e2e5..ec2b15378 100644 --- a/apps/cs/src/pages/menu/ui/board-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/board-menu-form.tsx @@ -15,7 +15,7 @@ import { FormField, FormMessage, } from "@/shared/ui/form"; -import type { MenuFormData } from "../model/menu-interface"; +import type { MenuFormData } from "../../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; function BoardMenuForm({ form }: { form: UseFormReturn }) { diff --git a/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx similarity index 88% rename from apps/cs/src/pages/menu/ui/bottom-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx index 88565210b..1a1b196f9 100644 --- a/apps/cs/src/pages/menu/ui/bottom-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx @@ -1,6 +1,6 @@ import { type UseFormReturn } from "react-hook-form"; import { Table, TableBody } from "@common/components"; -import type { MenuFormData } from "../model/menu-interface"; +import type { MenuFormData } from "../../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; function BottomMenuForm({ form }: { form: UseFormReturn }) { diff --git a/apps/cs/src/pages/menu/ui/content-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx similarity index 95% rename from apps/cs/src/pages/menu/ui/content-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/content-menu-form.tsx index 7ce40d940..ed16547f8 100644 --- a/apps/cs/src/pages/menu/ui/content-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx @@ -15,10 +15,10 @@ import { FormField, FormMessage, } from "@/shared/ui/form"; -import type { MenuFormData } from "../model/menu-interface"; -import type { Content } from "../model/content-interface"; +import type { MenuFormData } from "../../model/menu-interface"; +import type { Content } from "../../model/content-interface"; +import { ContentDialog } from "../dialog/content-dialog"; import { BaseTypeMenuForm } from "./base-type-menu-form"; -import { ContentDialog } from "./content-dialog"; function ContentMenuForm({ form }: { form: UseFormReturn }) { const handleContentSelect = (content: Content) => { diff --git a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx similarity index 88% rename from apps/cs/src/pages/menu/ui/empty-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx index 78339d90f..1246a22ae 100644 --- a/apps/cs/src/pages/menu/ui/empty-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx @@ -1,6 +1,6 @@ import { type UseFormReturn } from "react-hook-form"; import { Table, TableBody } from "@common/components"; -import type { MenuFormData } from "../model/menu-interface"; +import type { MenuFormData } from "../../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; function EmptyMenuForm({ form }: { form: UseFormReturn }) { diff --git a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx similarity index 95% rename from apps/cs/src/pages/menu/ui/inside-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx index f671c7d26..f0569cfe6 100644 --- a/apps/cs/src/pages/menu/ui/inside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx @@ -13,7 +13,7 @@ import { FormControl, FormMessage, } from "@/shared/ui/form"; -import type { MenuFormData } from "../model/menu-interface"; +import type { MenuFormData } from "../../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; function InsideMenuForm({ form }: { form: UseFormReturn }) { diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/form/menu-form.tsx similarity index 96% rename from apps/cs/src/pages/menu/ui/menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/menu-form.tsx index 922c2bb78..9a09b7c81 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/menu-form.tsx @@ -4,7 +4,7 @@ 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 { 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"; @@ -12,7 +12,7 @@ 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"; +import { PermissionsManagementDialog } from "../dialog/permissions-management-dialog"; interface MenuFormProps { form: UseFormReturn; diff --git a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx similarity index 95% rename from apps/cs/src/pages/menu/ui/outside-menu-form.tsx rename to apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx index 035d2dbda..4e8a402f3 100644 --- a/apps/cs/src/pages/menu/ui/outside-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx @@ -13,7 +13,7 @@ import { FormControl, FormMessage, } from "@/shared/ui/form"; -import type { MenuFormData } from "../model/menu-interface"; +import type { MenuFormData } from "../../model/menu-interface"; import { BaseTypeMenuForm } from "./base-type-menu-form"; function OutsideMenuForm({ form }: { form: UseFormReturn }) { From 6a784cecf3b7c7c557e4264412e33dc749be625a Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 10:53:16 +0900 Subject: [PATCH 016/102] =?UTF-8?q?feat:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EA=B2=80=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 --- .../src/pages/menu/model/content-interface.ts | 5 ++- .../pages/menu/ui/dialog/content-dialog.tsx | 31 ++++++++++++++++--- apps/cs/src/pages/menu/ui/menu-page.tsx | 4 +-- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/apps/cs/src/pages/menu/model/content-interface.ts b/apps/cs/src/pages/menu/model/content-interface.ts index dc789d059..8d59b1516 100644 --- a/apps/cs/src/pages/menu/model/content-interface.ts +++ b/apps/cs/src/pages/menu/model/content-interface.ts @@ -1,9 +1,8 @@ import type { PageableList, PageableParams } from "@/shared/model"; export interface ContentParams extends PageableParams { - siteId?: string; - menuKornNm?: string; - urlPath?: string; + contsTtl?: string; + aprvContsVer?: number; useYn?: string; } diff --git a/apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx b/apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx index 7c99637bf..00ad8810a 100644 --- a/apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/dialog/content-dialog.tsx @@ -13,6 +13,7 @@ import { DialogContent, DialogHeader, DialogTitle, + Input, } from "@common/components/ui"; import { ENDPOINT } from "@/shared/config"; import { ContentApi } from "../../api/content-service"; @@ -25,11 +26,13 @@ type ContentDialogProps = { function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) { const [rowSelection, setRowSelection] = useState({}); + const [contsTtl, setContsTtl] = useState(""); - const { data: contentList } = useQuery({ + const { data: contentList, refetch } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.CONTENTS], queryFn: () => - ContentApi.getContentList({ useYn: "Y", page: 0, size: 100 }), + ContentApi.getContentList({ contsTtl, useYn: "Y", page: 0, size: 100 }), + enabled: false, }); const table = useReactTable({ @@ -50,6 +53,10 @@ function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) { handleContentSelect(contentData); }, [table.getSelectedRowModel().rows]); + useEffect(() => { + refetch(); + }, []); + return ( @@ -60,10 +67,24 @@ function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) { - 콘텐츠 선택 + 콘텐츠 조회 -
- +
+
+ setContsTtl(e.target.value)} + /> + +
+
+ +
diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index f06f7a773..c1a83ab3e 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -27,7 +27,7 @@ import { type MenuFormData, } from "../model/menu-interface"; import { MenuTree } from "./menu-tree"; -import { MenuForm } from "./menu-form"; +import { MenuForm } from "./form/menu-form"; import { generateTreeData } from "../lib/utils"; function MenuPage() { @@ -119,10 +119,10 @@ function MenuPage() { sortSeq: menuInfo?.sortSeq ?? 1, menuKornNm: menuInfo?.menuKornNm ?? "", menuLnkgTypeCd: menuInfo?.menuLnkgTypeCd ?? MENU_TYPE.EMPTY, + iconNm: menuInfo?.iconName, otptYn: menuInfo?.otptYn ?? "Y", evlUseYn: menuInfo?.evlUseYn ?? "Y", useYn: menuInfo?.useYn ?? "Y", - iconNm: menuInfo?.iconName, }, }); From 572f18cc1a2e8bb0ce20dbdd5ac5b821253e7d83 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 15:22:14 +0900 Subject: [PATCH 017/102] =?UTF-8?q?feat:=20=EC=BD=98=ED=85=90=EC=B8=A0=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EB=93=B1=EB=A1=9D=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pages/menu/api/organization-service.ts | 17 ++ apps/cs/src/pages/menu/lib/utils.ts | 68 ++++++- .../menu/model/organization-interface.ts | 18 ++ .../menu/ui/dialog/organization-dialog.tsx | 182 ++++++++++++++++++ .../pages/menu/ui/form/content-menu-form.tsx | 50 ++++- apps/cs/src/pages/menu/ui/menu-page.tsx | 4 +- apps/cs/src/shared/config/api.ts | 1 + 7 files changed, 331 insertions(+), 9 deletions(-) create mode 100644 apps/cs/src/pages/menu/api/organization-service.ts create mode 100644 apps/cs/src/pages/menu/model/organization-interface.ts create mode 100644 apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx diff --git a/apps/cs/src/pages/menu/api/organization-service.ts b/apps/cs/src/pages/menu/api/organization-service.ts new file mode 100644 index 000000000..9ef3b55fa --- /dev/null +++ b/apps/cs/src/pages/menu/api/organization-service.ts @@ -0,0 +1,17 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + Organization, + OrganizationService, +} from "../model/organization-interface"; + +export class OrganizationApiService implements OrganizationService { + public async getOrganizationUserTreeList(): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.USER_OGNZ}/treeuser`, + ); + return response.data; + } +} + +export const OrganizationApi = new OrganizationApiService(); diff --git a/apps/cs/src/pages/menu/lib/utils.ts b/apps/cs/src/pages/menu/lib/utils.ts index 304c7166a..b1dbafe5f 100644 --- a/apps/cs/src/pages/menu/lib/utils.ts +++ b/apps/cs/src/pages/menu/lib/utils.ts @@ -1,7 +1,10 @@ import type { DropOptions, NodeModel } from "@minoru/react-dnd-treeview"; import type { TreeMenu, TreeNode } from "../model/menu-interface"; +import type { Organization, User } from "../model/organization-interface"; -export const generateTreeData = (data: TreeMenu[]): NodeModel[] => { +export const generateMenuTreeData = ( + data: TreeMenu[], +): NodeModel[] => { return data.reduce[]>((acc, menu) => { const node: NodeModel = { id: menu.menuCd ?? 0, @@ -14,14 +17,14 @@ export const generateTreeData = (data: TreeMenu[]): NodeModel[] => { acc.push(node); if (menu.children?.length) { - acc.push(...generateTreeData(menu.children)); + acc.push(...generateMenuTreeData(menu.children)); } return acc; }, []); }; -export const generateNewTreeData = ( +export const generateNewMenuTreeData = ( treeData: NodeModel[], options: DropOptions, ): TreeNode | null => { @@ -82,7 +85,7 @@ export const handleSelectNode = export const handleDropNode = (callback: (newTree: TreeNode) => void) => (newTree: NodeModel[], options: DropOptions) => { - const newTreeData = generateNewTreeData(newTree, options); + const newTreeData = generateNewMenuTreeData(newTree, options); if (!newTreeData) return; callback(newTreeData); }; @@ -103,3 +106,60 @@ export const handleNodeTextChange = }); callback(newTree); }; + +export const generateOrganizationTreeData = ( + organizations: Organization[], +): NodeModel[] => { + return organizations.reduce[]>((acc, org) => { + const orgNode: NodeModel = { + id: org.ognzId, + parent: org.upOgnzId ?? 0, + text: org.ognzNm, + data: org, + droppable: false, + }; + + acc.push(orgNode); + + if (org.children?.length) { + acc.push(...generateOrganizationTreeData(org.children)); + } + + if (org.users?.length) { + acc.push( + ...org.users.map((user) => ({ + id: user.userUnqId, + parent: org.ognzId, + text: user.userNm, + data: user, + droppable: false, + })), + ); + } + + return acc; + }, []); +}; + +export const filterOrganizationTreeDataByKeyword = ( + treeData: NodeModel[], + keyword: string, +): NodeModel[] => { + return Array.from( + treeData.reduce((matchedNodes, node) => { + if (!node.text.includes(keyword)) return matchedNodes; + + matchedNodes.add(node); + + let parentId = node.parent; + while (parentId) { + const parentNode = treeData.find((n) => n.id === parentId); + if (!parentNode) break; + matchedNodes.add(parentNode); + parentId = parentNode.parent; + } + + return matchedNodes; + }, new Set>()), + ); +}; diff --git a/apps/cs/src/pages/menu/model/organization-interface.ts b/apps/cs/src/pages/menu/model/organization-interface.ts new file mode 100644 index 000000000..86a8b8e31 --- /dev/null +++ b/apps/cs/src/pages/menu/model/organization-interface.ts @@ -0,0 +1,18 @@ +export interface User { + userUnqId: string; + userNm: string; + jbpsSeCd: string; + jbgdSeCd: string; +} + +export interface Organization { + ognzId: string; + upOgnzId: string; + ognzNm: string; + users: User[]; + children: Organization[]; +} + +export interface OrganizationService { + getOrganizationUserTreeList(): Promise; +} diff --git a/apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx b/apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx new file mode 100644 index 000000000..47fecb1ba --- /dev/null +++ b/apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx @@ -0,0 +1,182 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { DndProvider } from "react-dnd"; +import { + Tree, + MultiBackend, + getBackendOptions, + type NodeModel, +} from "@minoru/react-dnd-treeview"; +import { + Button, + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogTrigger, + Input, +} from "@common/components/ui"; +import { cn } from "@common/components/lib"; +import { ENDPOINT } from "@/shared/config"; +import { OrganizationApi } from "../../api/organization-service"; +import type { Organization, User } from "../../model/organization-interface"; +import { + filterOrganizationTreeDataByKeyword, + generateOrganizationTreeData, +} from "../../lib/utils"; + +type TreeModel = Organization | User; + +interface OrganizationDialogProps { + handleUserSelect: (user: User) => void; +} + +function OrganizationDialog({ handleUserSelect }: OrganizationDialogProps) { + const [keyword, setKeyword] = useState(""); + const [tree, setTree] = useState[]>([]); + const [selectedNode, setSelectedNode] = useState | null>( + null, + ); + + const { data: treeData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.USER_OGNZ], + queryFn: () => OrganizationApi.getOrganizationUserTreeList(), + select: (data) => generateOrganizationTreeData(data), + }); + + const handleSelectNode = (node: NodeModel) => { + setSelectedNode(node); + handleUserSelect(node.data as User); + }; + + useEffect(() => { + setTree(treeData ?? []); + }, [treeData]); + + return ( + + + + + + + 담당자 조회 + +
+
+ setKeyword(e.target.value)} + /> + +
+
+ + , + { depth, isOpen, onToggle }, + ) => ( + + )} + classes={{ + container: "flex flex-col", + }} + sort={false} + onDrop={() => {}} + /> + +
+
+
+
+ ); +} + +interface TreeNodeProps { + node: NodeModel; + depth: number; + isOpen: boolean; + selectedNode: NodeModel | null; + onToggle: (id: NodeModel["id"]) => void; + onSelect: (node: NodeModel) => void; +} + +const ORGANIZATION_PREFIX = "OGNZ"; + +function TreeNode({ + node, + depth, + isOpen, + selectedNode, + onToggle, + onSelect, +}: TreeNodeProps) { + const { id, text, data } = node; + const indent = depth * 15 + 5; + + const handleToggle = () => onToggle(id); + const handleSelect = () => { + if (id.toString().startsWith(ORGANIZATION_PREFIX)) return; + onSelect(node); + }; + + const isSelected = selectedNode?.id === id; + + return ( +
+
+
+ {data && ("children" in data || "users" in data) && ( + + )} + {text} +
+ {isSelected && } +
+
+ ); +} + +export { OrganizationDialog }; diff --git a/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx index ed16547f8..1e74109f3 100644 --- a/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx @@ -1,5 +1,7 @@ -import { type UseFormReturn } from "react-hook-form"; +import dayjs from "dayjs"; +import type { UseFormReturn } from "react-hook-form"; import { + DatePicker, Label, RadioGroup, RadioGroupItem, @@ -17,7 +19,9 @@ import { } from "@/shared/ui/form"; import type { MenuFormData } from "../../model/menu-interface"; import type { Content } from "../../model/content-interface"; +import type { User } from "../../model/organization-interface"; import { ContentDialog } from "../dialog/content-dialog"; +import { OrganizationDialog } from "../dialog/organization-dialog"; import { BaseTypeMenuForm } from "./base-type-menu-form"; function ContentMenuForm({ form }: { form: UseFormReturn }) { @@ -26,6 +30,16 @@ function ContentMenuForm({ form }: { form: UseFormReturn }) { form.setValue("menuLnkgSnNm", content.contsTtl); }; + const handleUserSelect = (user: User) => { + form.setValue("menuPicUserUnqId", user.userUnqId); + form.setValue("menuPicUserUnqIdNm", user.userNm); + }; + + const handleDateSelect = (date: Date | undefined) => { + if (!date) return; + form.setValue("menuPicDsgnYmd", dayjs(date).format("YYYYMMDD")); + }; + return ( @@ -113,8 +127,38 @@ function ContentMenuForm({ form }: { form: UseFormReturn }) { 담당자 -
- {form.watch("menuLnkgSn")} +
+
+
+ {form.watch("menuPicUserUnqIdNm")} +
+ +
+
+ 지정일: + ( + + + { + field.onChange(date); + handleDateSelect(date); + }} + /> + + + )} + /> +
diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index c1a83ab3e..6a9580856 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -28,7 +28,7 @@ import { } from "../model/menu-interface"; import { MenuTree } from "./menu-tree"; import { MenuForm } from "./form/menu-form"; -import { generateTreeData } from "../lib/utils"; +import { generateMenuTreeData } from "../lib/utils"; function MenuPage() { const queryClient = useQueryClient(); @@ -51,7 +51,7 @@ function MenuPage() { queryKey: [ENDPOINT.CMS_SERVICE.MENUS, siteId], queryFn: () => MenuApi.getTreeMenuList(siteId), select: (data) => { - const treeData = generateTreeData(data); + const treeData = generateMenuTreeData(data); const defaultSortSeq = treeData.filter((node) => node.data?.levelNo === 1).length + 1; form.setValue("sortSeq", defaultSortSeq); diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index b938ceaa9..f57b79adf 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -15,6 +15,7 @@ export const ENDPOINT = { DOMAINS: "/cms-service/api/v1/site-dmns", MENUS: "/cms-service/api/v1/menus", CONTENTS: "/cms-service/api/v1/contents", + USER_OGNZ: "/cms-service/api/v1/user-ognz", }, }; From f59320d2ff4258a3c5986277f66954d01723aa47 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 16:38:29 +0900 Subject: [PATCH 018/102] =?UTF-8?q?refactor:=20=EB=A9=94=EB=89=B4=20?= =?UTF-8?q?=ED=8F=BC=20=ED=8F=B4=EB=8D=94=20=EA=B5=AC=EC=A1=B0=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit rename: 다이얼로그 폴더 제거 chore: 참조 경로 수정 --- .../cs/src/pages/menu/model/menu-interface.ts | 1 + .../menu/ui/{form => }/base-menu-form.tsx | 9 +- .../menu/ui/{dialog => }/content-dialog.tsx | 6 +- .../menu/ui/form/base-type-menu-form.tsx | 128 ------- .../pages/menu/ui/form/board-menu-form.tsx | 146 -------- .../pages/menu/ui/form/bottom-menu-form.tsx | 20 -- .../pages/menu/ui/form/content-menu-form.tsx | 170 --------- .../pages/menu/ui/form/empty-menu-form.tsx | 20 -- .../pages/menu/ui/form/inside-menu-form.tsx | 56 --- .../pages/menu/ui/form/outside-menu-form.tsx | 56 --- .../pages/menu/ui/{form => }/menu-form.tsx | 39 +-- apps/cs/src/pages/menu/ui/menu-page.tsx | 2 +- .../ui/{dialog => }/organization-dialog.tsx | 6 +- .../permissions-management-dialog.tsx | 0 apps/cs/src/pages/menu/ui/type-menu-form.tsx | 331 ++++++++++++++++++ 15 files changed, 347 insertions(+), 643 deletions(-) rename apps/cs/src/pages/menu/ui/{form => }/base-menu-form.tsx (92%) rename apps/cs/src/pages/menu/ui/{dialog => }/content-dialog.tsx (93%) delete mode 100644 apps/cs/src/pages/menu/ui/form/base-type-menu-form.tsx delete mode 100644 apps/cs/src/pages/menu/ui/form/board-menu-form.tsx delete mode 100644 apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx delete mode 100644 apps/cs/src/pages/menu/ui/form/content-menu-form.tsx delete mode 100644 apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx delete mode 100644 apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx delete mode 100644 apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx rename apps/cs/src/pages/menu/ui/{form => }/menu-form.tsx (69%) rename apps/cs/src/pages/menu/ui/{dialog => }/organization-dialog.tsx (96%) rename apps/cs/src/pages/menu/ui/{dialog => }/permissions-management-dialog.tsx (100%) create mode 100644 apps/cs/src/pages/menu/ui/type-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 fcee4c505..51258fc49 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -59,6 +59,7 @@ export const menuFormSchema = z.object({ evlUseYn: z.string().default("Y"), qrcd: z.string().nullish(), useYn: z.string().default("Y"), + newWndwYn: z.string().nullish(), menuLnkgSnNm: z.string().nullish(), menuPicUserUnqId: z.string().nullish(), menuPicUserUnqIdNm: z.string().nullish(), diff --git a/apps/cs/src/pages/menu/ui/form/base-menu-form.tsx b/apps/cs/src/pages/menu/ui/base-menu-form.tsx similarity index 92% rename from apps/cs/src/pages/menu/ui/form/base-menu-form.tsx rename to apps/cs/src/pages/menu/ui/base-menu-form.tsx index 7e58a63eb..62d69bb38 100644 --- a/apps/cs/src/pages/menu/ui/form/base-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-menu-form.tsx @@ -16,7 +16,7 @@ import { FormField, FormMessage, } from "@/shared/ui/form"; -import { MENU_TYPE, type MenuFormData } from "../../model/menu-interface"; +import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; interface BaseMenuFormProps { form: UseFormReturn; @@ -104,11 +104,8 @@ function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) { key={key} className="flex items-center space-x-2" > - -
- - - - - - - 게시판 - -
- {form.watch("menuLnkgSn")} -
-
-
- - 연결 형태 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - 메뉴 평가 사용 여부 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - - - - QR코드 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - 담당자 - -
- {form.watch("menuLnkgSn")} -
-
-
-
-
- ); -} - -export { BoardMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx deleted file mode 100644 index 1a1b196f9..000000000 --- a/apps/cs/src/pages/menu/ui/form/bottom-menu-form.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type UseFormReturn } from "react-hook-form"; -import { Table, TableBody } from "@common/components"; -import type { MenuFormData } from "../../model/menu-interface"; -import { BaseTypeMenuForm } from "./base-type-menu-form"; - -function BottomMenuForm({ form }: { form: UseFormReturn }) { - return ( - - - - - - - - -
- ); -} - -export { BottomMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx deleted file mode 100644 index 1e74109f3..000000000 --- a/apps/cs/src/pages/menu/ui/form/content-menu-form.tsx +++ /dev/null @@ -1,170 +0,0 @@ -import dayjs from "dayjs"; -import type { UseFormReturn } from "react-hook-form"; -import { - DatePicker, - 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 type { Content } from "../../model/content-interface"; -import type { User } from "../../model/organization-interface"; -import { ContentDialog } from "../dialog/content-dialog"; -import { OrganizationDialog } from "../dialog/organization-dialog"; -import { BaseTypeMenuForm } from "./base-type-menu-form"; - -function ContentMenuForm({ form }: { form: UseFormReturn }) { - const handleContentSelect = (content: Content) => { - form.setValue("menuLnkgSn", content.contsSn); - form.setValue("menuLnkgSnNm", content.contsTtl); - }; - - const handleUserSelect = (user: User) => { - form.setValue("menuPicUserUnqId", user.userUnqId); - form.setValue("menuPicUserUnqIdNm", user.userNm); - }; - - const handleDateSelect = (date: Date | undefined) => { - if (!date) return; - form.setValue("menuPicDsgnYmd", dayjs(date).format("YYYYMMDD")); - }; - - return ( - - - - - - - - 콘텐츠 - -
-
- {form.watch("menuLnkgSnNm")} -
- -
-
-
- {/* TODO: 연결 형태 추가 */} - - 메뉴 평가 사용 여부 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - - - - QR코드 - - ( - - - -
-
- - -
-
- - -
-
-
-
- -
- )} - /> -
-
- - 담당자 - -
-
-
- {form.watch("menuPicUserUnqIdNm")} -
- -
-
- 지정일: - ( - - - { - field.onChange(date); - handleDateSelect(date); - }} - /> - - - )} - /> -
-
-
-
-
-
- ); -} - -export { ContentMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx deleted file mode 100644 index 1246a22ae..000000000 --- a/apps/cs/src/pages/menu/ui/form/empty-menu-form.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { type UseFormReturn } from "react-hook-form"; -import { Table, TableBody } from "@common/components"; -import type { MenuFormData } from "../../model/menu-interface"; -import { BaseTypeMenuForm } from "./base-type-menu-form"; - -function EmptyMenuForm({ form }: { form: UseFormReturn }) { - return ( - - - - - - - - -
- ); -} - -export { EmptyMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx deleted file mode 100644 index f0569cfe6..000000000 --- a/apps/cs/src/pages/menu/ui/form/inside-menu-form.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { type UseFormReturn } from "react-hook-form"; -import { - Input, - Table, - TableBody, - TableCell, - TableHead, - TableRow, -} from "@common/components"; -import { - FormField, - FormItem, - FormControl, - FormMessage, -} from "@/shared/ui/form"; -import type { MenuFormData } from "../../model/menu-interface"; -import { BaseTypeMenuForm } from "./base-type-menu-form"; - -function InsideMenuForm({ form }: { form: UseFormReturn }) { - return ( - - - - - - - - 연결 URL - - ( - - - - - - - )} - /> - - - {/* TODO: 연결 형태 추가 */} - - -
- ); -} - -export { InsideMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx b/apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx deleted file mode 100644 index 4e8a402f3..000000000 --- a/apps/cs/src/pages/menu/ui/form/outside-menu-form.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { type UseFormReturn } from "react-hook-form"; -import { - Input, - Table, - TableBody, - TableCell, - TableHead, - TableRow, -} from "@common/components"; -import { - FormField, - FormItem, - FormControl, - FormMessage, -} from "@/shared/ui/form"; -import type { MenuFormData } from "../../model/menu-interface"; -import { BaseTypeMenuForm } from "./base-type-menu-form"; - -function OutsideMenuForm({ form }: { form: UseFormReturn }) { - return ( - - - - - - - - 연결 URL - - ( - - - - - - - )} - /> - - - {/* TODO: 연결 형태 추가 */} - - -
- ); -} - -export { OutsideMenuForm }; diff --git a/apps/cs/src/pages/menu/ui/form/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx similarity index 69% rename from apps/cs/src/pages/menu/ui/form/menu-form.tsx rename to apps/cs/src/pages/menu/ui/menu-form.tsx index 9a09b7c81..48e7589be 100644 --- a/apps/cs/src/pages/menu/ui/form/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -1,18 +1,13 @@ import { useState } from "react"; +import type { UseFormReturn } from "react-hook-form"; 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 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"; -import { PermissionsManagementDialog } from "../dialog/permissions-management-dialog"; +import { TypeMenuForm } from "./type-menu-form"; +import { PermissionsManagementDialog } from "./permissions-management-dialog"; interface MenuFormProps { form: UseFormReturn; @@ -94,9 +89,7 @@ function MenuForm({
-
- {renderTypeMenuForm(menuType, form)} -
+
@@ -116,26 +109,4 @@ function MenuForm({ ); } -function renderTypeMenuForm( - menuType: string, - form: UseFormReturn, -) { - switch (menuType) { - case MENU_TYPE.EMPTY: - 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; - } -} - export { MenuForm }; diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 6a9580856..87227fedd 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -27,7 +27,7 @@ import { type MenuFormData, } from "../model/menu-interface"; import { MenuTree } from "./menu-tree"; -import { MenuForm } from "./form/menu-form"; +import { MenuForm } from "./menu-form"; import { generateMenuTreeData } from "../lib/utils"; function MenuPage() { diff --git a/apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx b/apps/cs/src/pages/menu/ui/organization-dialog.tsx similarity index 96% rename from apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx rename to apps/cs/src/pages/menu/ui/organization-dialog.tsx index 47fecb1ba..ceb4cbbcf 100644 --- a/apps/cs/src/pages/menu/ui/dialog/organization-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/organization-dialog.tsx @@ -20,12 +20,12 @@ import { } from "@common/components/ui"; import { cn } from "@common/components/lib"; import { ENDPOINT } from "@/shared/config"; -import { OrganizationApi } from "../../api/organization-service"; -import type { Organization, User } from "../../model/organization-interface"; +import { OrganizationApi } from "../api/organization-service"; +import type { Organization, User } from "../model/organization-interface"; import { filterOrganizationTreeDataByKeyword, generateOrganizationTreeData, -} from "../../lib/utils"; +} from "../lib/utils"; type TreeModel = Organization | User; diff --git a/apps/cs/src/pages/menu/ui/dialog/permissions-management-dialog.tsx b/apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx similarity index 100% rename from apps/cs/src/pages/menu/ui/dialog/permissions-management-dialog.tsx rename to apps/cs/src/pages/menu/ui/permissions-management-dialog.tsx diff --git a/apps/cs/src/pages/menu/ui/type-menu-form.tsx b/apps/cs/src/pages/menu/ui/type-menu-form.tsx new file mode 100644 index 000000000..86080c555 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/type-menu-form.tsx @@ -0,0 +1,331 @@ +import dayjs from "dayjs"; +import { type UseFormReturn } from "react-hook-form"; +import { + DatePicker, + IconFinder, + Input, + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { + FormItem, + FormControl, + FormField, + FormMessage, +} from "@/shared/ui/form"; +import type { Content } from "../model/content-interface"; +import type { User } from "../model/organization-interface"; +import type { MenuFormData, MenuType } from "../model/menu-interface"; +import { OrganizationDialog } from "./organization-dialog"; +import { ContentDialog } from "./content-dialog"; + +type TypeMenuFormProps = { + form: UseFormReturn; + menuType: MenuType; +}; + +function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { + const handleIconClick = (icon: string) => { + form.setValue("iconNm", icon); + }; + + const handleContentSelect = (content: Content) => { + form.setValue("menuLnkgSn", content.contsSn); + form.setValue("menuLnkgSnNm", content.contsTtl); + }; + + const handleUserSelect = (user: User) => { + form.setValue("menuPicUserUnqId", user.userUnqId); + form.setValue("menuPicUserUnqIdNm", user.userNm); + }; + + const handleDateSelect = (date: Date | undefined) => { + if (!date) return; + form.setValue("menuPicDsgnYmd", dayjs(date).format("YYYYMMDD")); + }; + + return ( + + + + + + + {menuType === "CONTENTS" && ( + + 콘텐츠 + +
+
+ {form.watch("menuLnkgSnNm")} +
+ +
+
+
+ )} + {menuType === "BOARD" && ( + + 게시판 + +
+ {form.watch("menuLnkgSnNm")} +
+
+
+ )} + {(menuType === "CONTENTS" || + menuType === "BOARD" || + menuType === "INSIDE" || + menuType === "OUTSIDE") && ( + + 연결 형태 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ )} + {(menuType === "CONTENTS" || menuType === "BOARD") && ( + + 메뉴 평가 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ )} + + {/* 메뉴 유형 공통 폼 S */} + + 메뉴 서브명 + + ( + + + + + + + )} + /> + + + + 메뉴 아이콘 + +
+
+ {form.watch("iconNm")} +
+ +
+
+
+ + 출력 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 사용 여부 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ {/* 메뉴 유형 공통 폼 E */} + + {(menuType === "CONTENTS" || menuType === "BOARD") && ( + <> + + QR코드 + + ( + + + +
+
+ + +
+
+ + +
+
+
+
+ +
+ )} + /> +
+
+ + 담당자 + +
+
+
+ {form.watch("menuPicUserUnqIdNm")} +
+ +
+
+ 지정일: + ( + + + { + field.onChange(date); + handleDateSelect(date); + }} + /> + + + )} + /> +
+
+
+
+ + )} +
+
+ ); +} + +export { TypeMenuForm }; From 701d042d4f8f39158cb63bb99fdb378a12469353 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 16:44:32 +0900 Subject: [PATCH 019/102] =?UTF-8?q?rename:=20=EC=BD=98=ED=85=90=EC=B8=A0?= =?UTF-8?q?=20=EB=B0=8F=20=EB=8B=B4=EB=8B=B9=EC=9E=90=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EB=A6=AC?= =?UTF-8?q?=EB=84=A4=EC=9D=B4=EB=B0=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...{content-dialog.tsx => content-search-dialog.tsx} | 9 ++++++--- ...ion-dialog.tsx => organization-search-dialog.tsx} | 8 +++++--- apps/cs/src/pages/menu/ui/type-menu-form.tsx | 12 ++++++++---- 3 files changed, 19 insertions(+), 10 deletions(-) rename apps/cs/src/pages/menu/ui/{content-dialog.tsx => content-search-dialog.tsx} (94%) rename apps/cs/src/pages/menu/ui/{organization-dialog.tsx => organization-search-dialog.tsx} (96%) diff --git a/apps/cs/src/pages/menu/ui/content-dialog.tsx b/apps/cs/src/pages/menu/ui/content-search-dialog.tsx similarity index 94% rename from apps/cs/src/pages/menu/ui/content-dialog.tsx rename to apps/cs/src/pages/menu/ui/content-search-dialog.tsx index 2bb711833..60ee794b8 100644 --- a/apps/cs/src/pages/menu/ui/content-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/content-search-dialog.tsx @@ -20,11 +20,14 @@ import { ContentApi } from "../api/content-service"; import type { Content } from "../model/content-interface"; import { columns } from "../model/table-colums"; -type ContentDialogProps = { +type ContentSearchDialogProps = { handleContentSelect: (content: Content) => void; } & React.ComponentProps; -function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) { +function ContentSearchDialog({ + handleContentSelect, + ...props +}: ContentSearchDialogProps) { const [rowSelection, setRowSelection] = useState({}); const [contsTtl, setContsTtl] = useState(""); @@ -91,4 +94,4 @@ function ContentDialog({ handleContentSelect, ...props }: ContentDialogProps) { ); } -export { ContentDialog }; +export { ContentSearchDialog }; diff --git a/apps/cs/src/pages/menu/ui/organization-dialog.tsx b/apps/cs/src/pages/menu/ui/organization-search-dialog.tsx similarity index 96% rename from apps/cs/src/pages/menu/ui/organization-dialog.tsx rename to apps/cs/src/pages/menu/ui/organization-search-dialog.tsx index ceb4cbbcf..9e0637082 100644 --- a/apps/cs/src/pages/menu/ui/organization-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/organization-search-dialog.tsx @@ -29,11 +29,13 @@ import { type TreeModel = Organization | User; -interface OrganizationDialogProps { +interface OrganizationSearchDialogProps { handleUserSelect: (user: User) => void; } -function OrganizationDialog({ handleUserSelect }: OrganizationDialogProps) { +function OrganizationSearchDialog({ + handleUserSelect, +}: OrganizationSearchDialogProps) { const [keyword, setKeyword] = useState(""); const [tree, setTree] = useState[]>([]); const [selectedNode, setSelectedNode] = useState | null>( @@ -179,4 +181,4 @@ function TreeNode({ ); } -export { OrganizationDialog }; +export { OrganizationSearchDialog }; diff --git a/apps/cs/src/pages/menu/ui/type-menu-form.tsx b/apps/cs/src/pages/menu/ui/type-menu-form.tsx index 86080c555..209cadb1a 100644 --- a/apps/cs/src/pages/menu/ui/type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/type-menu-form.tsx @@ -22,8 +22,8 @@ import { import type { Content } from "../model/content-interface"; import type { User } from "../model/organization-interface"; import type { MenuFormData, MenuType } from "../model/menu-interface"; -import { OrganizationDialog } from "./organization-dialog"; -import { ContentDialog } from "./content-dialog"; +import { OrganizationSearchDialog } from "./organization-search-dialog"; +import { ContentSearchDialog } from "./content-search-dialog"; type TypeMenuFormProps = { form: UseFormReturn; @@ -65,7 +65,9 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
{form.watch("menuLnkgSnNm")}
- +
@@ -291,7 +293,9 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
{form.watch("menuPicUserUnqIdNm")}
- +
지정일: From cd455fa343115dadeb547a3c2c059c5e2e5f039a Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 18:17:13 +0900 Subject: [PATCH 020/102] =?UTF-8?q?feat:=20=EA=B2=8C=EC=8B=9C=ED=8C=90=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EB=93=B1=EB=A1=9D=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/entities/board/api/board-service.ts | 21 ++++ apps/cs/src/entities/board/index.ts | 2 + .../entities/board/model/board-interface.ts | 42 ++++++++ .../pages/menu/model/board-table-columns.tsx | 43 +++++++++ .../src/pages/menu/ui/board-search-dialog.tsx | 95 +++++++++++++++++++ apps/cs/src/pages/menu/ui/type-menu-form.tsx | 21 +++- apps/cs/src/shared/config/api.ts | 3 + 7 files changed, 225 insertions(+), 2 deletions(-) create mode 100644 apps/cs/src/entities/board/api/board-service.ts create mode 100644 apps/cs/src/entities/board/index.ts create mode 100644 apps/cs/src/entities/board/model/board-interface.ts create mode 100644 apps/cs/src/pages/menu/model/board-table-columns.tsx create mode 100644 apps/cs/src/pages/menu/ui/board-search-dialog.tsx diff --git a/apps/cs/src/entities/board/api/board-service.ts b/apps/cs/src/entities/board/api/board-service.ts new file mode 100644 index 000000000..9cf29599b --- /dev/null +++ b/apps/cs/src/entities/board/api/board-service.ts @@ -0,0 +1,21 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + BoardPopupList, + BoardPopupParams, + BoardService, +} from "../model/board-interface"; + +export class BoardApiService implements BoardService { + public async getBoardPopupList( + params: BoardPopupParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.BOARD_SERVICE.BBS}`, + { params }, + ); + return response.data; + } +} + +export const BoardApi = new BoardApiService(); diff --git a/apps/cs/src/entities/board/index.ts b/apps/cs/src/entities/board/index.ts new file mode 100644 index 000000000..2173bb4a0 --- /dev/null +++ b/apps/cs/src/entities/board/index.ts @@ -0,0 +1,2 @@ +export { BoardApi } from "./api/board-service"; +export { type BoardPopup } from "./model/board-interface"; diff --git a/apps/cs/src/entities/board/model/board-interface.ts b/apps/cs/src/entities/board/model/board-interface.ts new file mode 100644 index 000000000..942840d74 --- /dev/null +++ b/apps/cs/src/entities/board/model/board-interface.ts @@ -0,0 +1,42 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export const BOARD_TYPE = { + BBSNORMAL: "BBSNORMAL", + BBSFAQ: "BBSFAQ", + BBSIMAGE: "BBSIMAGE", + BBSMOVIE: "BBSMOVIE", +} as const; + +export type BoardType = (typeof BOARD_TYPE)[keyof typeof BOARD_TYPE]; + +export interface BoardPopupParams extends PageableParams { + bbsNm?: string; +} + +export interface BoardPopup { + bbsId: number; + bbsTypeCd: BoardType; + bbsTypeNm: string; + bbsNm: string; + bbsExpln: string; + rfrncUseYn: string; + atflUseYn: string; + atflMaxCnt: number; + atflMaxFileSz: number; + ansUseYn: string; + cmntUseYn: string; + dgstfnEvlUseYn: string; + smsUseYn: string; + upndFixUseYn: string; + pstgPrdUseYn: string; + popupUseYn: string; + delYn: string; + regDt: string; + pstCnt: number; +} + +export type BoardPopupList = PageableList; + +export interface BoardService { + getBoardPopupList: (params: BoardPopupParams) => Promise; +} diff --git a/apps/cs/src/pages/menu/model/board-table-columns.tsx b/apps/cs/src/pages/menu/model/board-table-columns.tsx new file mode 100644 index 000000000..f144ac158 --- /dev/null +++ b/apps/cs/src/pages/menu/model/board-table-columns.tsx @@ -0,0 +1,43 @@ +import { createColumnHelper } from "@tanstack/react-table"; +import { Checkbox } from "@common/components/ui"; +import type { BoardPopup } from "@/entities/board"; + +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor((_, index) => index + 1, { + id: "no", + header: "NO", + }), + columnHelper.accessor("bbsNm", { + id: "bbsNm", + header: "게시판명", + }), + columnHelper.accessor("bbsTypeNm", { + id: "bbsTypeNm", + header: "게시판유형", + }), + columnHelper.accessor("regDt", { + id: "regDt", + header: "최초생성일", + }), + columnHelper.accessor("pstCnt", { + id: "pstCnt", + header: "게시물건수", + }), + columnHelper.display({ + id: "select", + header: "선택", + cell: ({ row }) => { + return ( + + ); + }, + }), +]; + +export { columns }; diff --git a/apps/cs/src/pages/menu/ui/board-search-dialog.tsx b/apps/cs/src/pages/menu/ui/board-search-dialog.tsx new file mode 100644 index 000000000..531e17c86 --- /dev/null +++ b/apps/cs/src/pages/menu/ui/board-search-dialog.tsx @@ -0,0 +1,95 @@ +import { useEffect, useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + useReactTable, + type RowSelectionState, +} from "@tanstack/react-table"; +import { + Button, + DataTable, + Dialog, + DialogTrigger, + DialogContent, + DialogHeader, + DialogTitle, + Input, +} from "@common/components/ui"; +import { ENDPOINT } from "@/shared/config"; +import { BoardApi, type BoardPopup } from "@/entities/board"; +import { columns } from "../model/board-table-columns"; + +type BoardSearchDialogProps = { + handleBoardSelect: (board: BoardPopup) => void; +} & React.ComponentProps; + +function BoardSearchDialog({ + handleBoardSelect, + ...props +}: BoardSearchDialogProps) { + const [rowSelection, setRowSelection] = useState({}); + const [bbsNm, setBbsNm] = useState(""); + + const { data: boardList, refetch } = useQuery({ + queryKey: [ENDPOINT.BOARD_SERVICE.BBS], + queryFn: () => BoardApi.getBoardPopupList({ bbsNm }), + enabled: false, + }); + + const table = useReactTable({ + data: boardList?.content ?? [], + columns, + state: { + rowSelection, + }, + enableMultiRowSelection: false, + getCoreRowModel: getCoreRowModel(), + onRowSelectionChange: setRowSelection, + }); + + useEffect(() => { + const boardData = table.getSelectedRowModel().rows[0] + ?.original as BoardPopup; + if (!boardData) return; + handleBoardSelect(boardData); + }, [table.getSelectedRowModel().rows]); + + useEffect(() => { + refetch(); + }, []); + + return ( + + + + + + + 게시판 조회 + +
+
+ setBbsNm(e.target.value)} + /> + +
+
+ +
+
+
+
+ ); +} + +export { BoardSearchDialog }; diff --git a/apps/cs/src/pages/menu/ui/type-menu-form.tsx b/apps/cs/src/pages/menu/ui/type-menu-form.tsx index 209cadb1a..e02d6dacc 100644 --- a/apps/cs/src/pages/menu/ui/type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/type-menu-form.tsx @@ -19,11 +19,13 @@ import { FormField, FormMessage, } from "@/shared/ui/form"; +import type { BoardPopup } from "@/entities/board"; import type { Content } from "../model/content-interface"; import type { User } from "../model/organization-interface"; import type { MenuFormData, MenuType } from "../model/menu-interface"; import { OrganizationSearchDialog } from "./organization-search-dialog"; import { ContentSearchDialog } from "./content-search-dialog"; +import { BoardSearchDialog } from "./board-search-dialog"; type TypeMenuFormProps = { form: UseFormReturn; @@ -45,6 +47,11 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { form.setValue("menuPicUserUnqIdNm", user.userNm); }; + const handleBoardSelect = (board: BoardPopup) => { + form.setValue("menuLnkgSn", board.bbsId); + form.setValue("menuLnkgSnNm", board.bbsNm); + }; + const handleDateSelect = (date: Date | undefined) => { if (!date) return; form.setValue("menuPicDsgnYmd", dayjs(date).format("YYYYMMDD")); @@ -63,6 +70,8 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
+ {!form.watch("menuLnkgSnNm") && + "메뉴에 표시될 콘텐츠를 조회 후 선택하십시오."} {form.watch("menuLnkgSnNm")}
게시판 -
- {form.watch("menuLnkgSnNm")} +
+
+ {!form.watch("menuLnkgSnNm") && + "메뉴에 표시될 게시판을 조회 후 선택하십시오."} + {form.watch("menuLnkgSnNm")} +
+
@@ -180,6 +194,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
+ {!form.watch("iconNm") && "아이콘을 조회 후 선택하십시오."} {form.watch("iconNm")}
@@ -291,6 +306,8 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
+ {!form.watch("menuPicUserUnqIdNm") && + "메뉴 담당자를 조회 후 선택하십시오."} {form.watch("menuPicUserUnqIdNm")}
Date: Fri, 28 Mar 2025 18:23:52 +0900 Subject: [PATCH 021/102] =?UTF-8?q?feat:=20=EB=82=B4=EB=B6=80/=EC=99=B8?= =?UTF-8?q?=EB=B6=80=EB=A7=81=ED=81=AC=20=EB=A9=94=EB=89=B4=20=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=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/ui/type-menu-form.tsx | 28 ++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/apps/cs/src/pages/menu/ui/type-menu-form.tsx b/apps/cs/src/pages/menu/ui/type-menu-form.tsx index e02d6dacc..5081a7e51 100644 --- a/apps/cs/src/pages/menu/ui/type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/type-menu-form.tsx @@ -96,6 +96,34 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { )} + {(menuType === "INSIDE" || menuType === "OUTSIDE") && ( + + 연결 URL + + ( + + + + + + + )} + /> + + + )} {(menuType === "CONTENTS" || menuType === "BOARD" || menuType === "INSIDE" || From 8b9d493ccd77427b75ef45d904fc1dc5e8aeb908 Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 18:32:53 +0900 Subject: [PATCH 022/102] =?UTF-8?q?feat:=20=ED=8A=B8=EB=A6=AC=20=EB=A9=94?= =?UTF-8?q?=EB=89=B4=20=EC=A0=84=EC=B2=B4=20=ED=8E=BC=EC=B9=A8/=EC=A0=91?= =?UTF-8?q?=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=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/ui/menu-tree.tsx | 131 ++++++++++++++---------- 1 file changed, 78 insertions(+), 53 deletions(-) diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index 59f55b4c2..b5782ec89 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -11,6 +11,7 @@ import { type NodeModel, type TreeMethods, } from "@minoru/react-dnd-treeview"; +import { Button } from "@common/components/ui"; import { ENDPOINT } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; import type { MenuFormData, TreeMenu, TreeNode } from "../model/menu-interface"; @@ -58,63 +59,87 @@ function MenuTree({ }; return ( - - , { depth, isOpen, onToggle }) => { - if (node.id === -1) { + <> + + , { depth, isOpen, onToggle }) => { + if (node.id === -1) { + return ( + + additionalNodeForm.setValue("menuKornNm", e.target.value) + } + depth={depth} + onSave={onAdditionalNodeSave} + onCancel={onAdditionalNodeCancel} + /> + ); + } return ( - - additionalNodeForm.setValue("menuKornNm", e.target.value) - } + ); - } - return ( - - ); - }} - onDrop={handleDropNode(modifyTreeMenu)} - classes={{ - root: "p-4", - container: "flex flex-col", - dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm", - placeholder: "relative", - }} - sort={false} - dropTargetOffset={5} - insertDroppableFirst={false} - canDrop={(_, { dragSource, dropTargetId }) => { - if (dragSource?.parent === dropTargetId) { - return true; - } - }} - placeholderRender={(_, { depth }) => { - return ( -
- ); - }} - /> - + }} + onDrop={handleDropNode(modifyTreeMenu)} + classes={{ + root: "p-4", + container: "flex flex-col", + dropTarget: "!bg-[rgba(32,148,250,0.5)] rounded-sm", + placeholder: "relative", + }} + sort={false} + dropTargetOffset={5} + insertDroppableFirst={false} + canDrop={(_, { dragSource, dropTargetId }) => { + if (dragSource?.parent === dropTargetId) { + return true; + } + }} + placeholderRender={(_, { depth }) => { + return ( +
+ ); + }} + /> + +
+ + +
+ ); } From 84519658c1f6066d25c270afdeda61b3b17da64d Mon Sep 17 00:00:00 2001 From: bicochan Date: Fri, 28 Mar 2025 18:50:18 +0900 Subject: [PATCH 023/102] =?UTF-8?q?design:=20=ED=8A=B8=EB=A6=AC=20?= =?UTF-8?q?=EB=A9=94=EB=89=B4=20=EC=B5=9C=EB=8C=80=20=EB=86=92=EC=9D=B4=20?= =?UTF-8?q?=EC=A7=80=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/menu/ui/menu-page.tsx | 2 +- apps/cs/src/pages/menu/ui/menu-tree.tsx | 111 +++++++++++++----------- 2 files changed, 59 insertions(+), 54 deletions(-) diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 87227fedd..52a63eb8a 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -198,7 +198,7 @@ function MenuPage() { return ( <> -
+
+
{table.getRowModel().rows.map((row) => ( From c3fdf92f5c6dd42b88e3638bb4833c407683d59c Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 13:14:18 +0900 Subject: [PATCH 029/102] =?UTF-8?q?feat:=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D/=EC=88=98=EC=A0=95=20=ED=83=80=EC=9D=B4?= =?UTF-8?q?=ED=8B=80=20=EB=B6=84=EA=B8=B0=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/site/ui/site-form.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/cs/src/pages/site/ui/site-form.tsx b/apps/cs/src/pages/site/ui/site-form.tsx index 463b5a6fa..bdc9f104d 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -80,7 +80,7 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) {

- 사이트 등록/수정 + {isModifyMode ? "사이트 수정" : "사이트 등록"}

From 8117ca5c2cc8c352222a71c67fe52cafb199d3c6 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 15:10:38 +0900 Subject: [PATCH 030/102] =?UTF-8?q?design:=20=ED=85=8C=EC=9D=B4=EB=B8=94?= =?UTF-8?q?=20=EC=8A=A4=ED=83=80=EC=9D=BC=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/app/styles/components.css | 2 +- .../components/src/ui/data-table/index.tsx | 2 +- .../components/src/ui/page-title/index.tsx | 2 +- .../common/components/src/ui/table/index.tsx | 2 +- .../common/components/src/ui/table/table.css | 38 ++++++++++++------- 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/apps/cs/src/app/styles/components.css b/apps/cs/src/app/styles/components.css index 17cd78834..5b9742c0f 100644 --- a/apps/cs/src/app/styles/components.css +++ b/apps/cs/src/app/styles/components.css @@ -120,8 +120,8 @@ /* 블랙 테마 */ [data-theme="black"] { .card-border { - border-color: #3f4042; background-color: rgba(255, 255, 255, 0.065); + border-color: #38383a; } .card-header { border-bottom-color: #3f4042; diff --git a/packages/common/components/src/ui/data-table/index.tsx b/packages/common/components/src/ui/data-table/index.tsx index c2c8c3591..d635e8554 100644 --- a/packages/common/components/src/ui/data-table/index.tsx +++ b/packages/common/components/src/ui/data-table/index.tsx @@ -55,7 +55,7 @@ function DataTable({ onRowClick?.(row.original)} - className={onRowClick ? "cursor-pointer hover:bg-muted" : ""} + className={onRowClick ? "cursor-pointer hover:bg-accent" : ""} > {row.getVisibleCells().map((cell) => { const { meta } = cell.column.columnDef; diff --git a/packages/common/components/src/ui/page-title/index.tsx b/packages/common/components/src/ui/page-title/index.tsx index 69796a428..6ea4a577e 100644 --- a/packages/common/components/src/ui/page-title/index.tsx +++ b/packages/common/components/src/ui/page-title/index.tsx @@ -10,7 +10,7 @@ const PageTitle = ({ return (

{title}

- {menuPaths.length && ( + {menuPaths.length > 0 && (
{menuPaths?.map((path, i) => ( diff --git a/packages/common/components/src/ui/table/index.tsx b/packages/common/components/src/ui/table/index.tsx index 46c8db39c..ba50e9d75 100644 --- a/packages/common/components/src/ui/table/index.tsx +++ b/packages/common/components/src/ui/table/index.tsx @@ -9,7 +9,7 @@ interface TableProps extends React.HTMLAttributes { } const Table = React.forwardRef( - ({ className, variant, ...props }, ref) => ( + ({ className, variant = "default", ...props }, ref) => (
Date: Mon, 31 Mar 2025 18:03:48 +0900 Subject: [PATCH 031/102] =?UTF-8?q?feat:=20menu=20wrapper=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/layout.tsx | 8 ++--- apps/cs/src/entities/auth/index.ts | 2 +- .../auth/{ui => lib}/auth-wrapper.tsx | 0 apps/cs/src/entities/menu/api/menu-service.ts | 14 +++++++++ apps/cs/src/entities/menu/index.ts | 1 + .../cs/src/entities/menu/lib/menu-wrapper.tsx | 30 +++++++++++++++++++ apps/cs/src/entities/menu/lib/utils.ts | 13 ++++++++ .../src/entities/menu/model/menu-interface.ts | 23 ++++++++++++++ apps/cs/src/pages/menu/ui/menu-page.tsx | 2 +- apps/cs/src/pages/site/ui/create-page.tsx | 2 +- apps/cs/src/pages/site/ui/modify-page.tsx | 2 +- apps/cs/src/pages/site/ui/site-page.tsx | 2 +- apps/cs/src/shared/config/api.ts | 1 + apps/cs/src/shared/lib/index.ts | 1 + apps/cs/src/shared/ui/page-layout.tsx | 13 ++++---- packages/common/components/src/api/menu.ts | 1 + packages/common/components/src/lib/index.ts | 3 +- .../common/components/src/ui/lnb/index.tsx | 10 +++---- .../components/src/ui/page-title/index.tsx | 2 +- .../components/src/ui/sidebar/index.tsx | 3 +- 20 files changed, 107 insertions(+), 26 deletions(-) rename apps/cs/src/entities/auth/{ui => lib}/auth-wrapper.tsx (100%) create mode 100644 apps/cs/src/entities/menu/api/menu-service.ts create mode 100644 apps/cs/src/entities/menu/index.ts create mode 100644 apps/cs/src/entities/menu/lib/menu-wrapper.tsx create mode 100644 apps/cs/src/entities/menu/lib/utils.ts create mode 100644 apps/cs/src/entities/menu/model/menu-interface.ts diff --git a/apps/cs/app/(with-base-layout)/layout.tsx b/apps/cs/app/(with-base-layout)/layout.tsx index e5f98a449..3e9d1fe8d 100644 --- a/apps/cs/app/(with-base-layout)/layout.tsx +++ b/apps/cs/app/(with-base-layout)/layout.tsx @@ -1,10 +1,10 @@ "use client"; import { usePathname, useRouter } from "next/navigation"; -import { BaseLayout } from "@common/components/ui"; import type { MenuItem } from "@common/components/api"; -import { useMenuStore } from "@/shared/lib/use-menu-store"; - +import { BaseLayout } from "@common/components/ui"; +import { MenuWrapper } from "@/entities/menu"; +import { useMenuStore } from "@/shared/lib"; export default function WithBaseLayout({ children, }: { @@ -21,7 +21,7 @@ export default function WithBaseLayout({ return ( - {children} + {children} ); } diff --git a/apps/cs/src/entities/auth/index.ts b/apps/cs/src/entities/auth/index.ts index dc3fb96c9..97e142225 100644 --- a/apps/cs/src/entities/auth/index.ts +++ b/apps/cs/src/entities/auth/index.ts @@ -1,2 +1,2 @@ export { LoginApi } from "./api/login-service"; -export { AuthWrapper } from "./ui/auth-wrapper"; +export { AuthWrapper } from "./lib/auth-wrapper"; diff --git a/apps/cs/src/entities/auth/ui/auth-wrapper.tsx b/apps/cs/src/entities/auth/lib/auth-wrapper.tsx similarity index 100% rename from apps/cs/src/entities/auth/ui/auth-wrapper.tsx rename to apps/cs/src/entities/auth/lib/auth-wrapper.tsx diff --git a/apps/cs/src/entities/menu/api/menu-service.ts b/apps/cs/src/entities/menu/api/menu-service.ts new file mode 100644 index 000000000..b6c1510cd --- /dev/null +++ b/apps/cs/src/entities/menu/api/menu-service.ts @@ -0,0 +1,14 @@ +import { client } from "@common/components/api"; +import { ENDPOINT } from "@/shared/config"; +import type { MenuItem, MenuService } from "../model/menu-interface"; + +export class MenuApiService implements MenuService { + async getMenuRoles(siteId: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENU_ROLES}/${siteId}`, + ); + return response.data; + } +} + +export const MenuApi = new MenuApiService(); diff --git a/apps/cs/src/entities/menu/index.ts b/apps/cs/src/entities/menu/index.ts new file mode 100644 index 000000000..1fa9dd218 --- /dev/null +++ b/apps/cs/src/entities/menu/index.ts @@ -0,0 +1 @@ +export { MenuWrapper } from "./lib/menu-wrapper"; diff --git a/apps/cs/src/entities/menu/lib/menu-wrapper.tsx b/apps/cs/src/entities/menu/lib/menu-wrapper.tsx new file mode 100644 index 000000000..0fbd4df90 --- /dev/null +++ b/apps/cs/src/entities/menu/lib/menu-wrapper.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useEffect } from "react"; +import { usePathname } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { ENDPOINT, SITE_ID } from "@/shared/config"; +import { useMenuStore } from "@/shared/lib"; +import { MenuApi } from "../api/menu-service"; +import { getCurrentMenu } from "./utils"; + +const MenuWrapper = ({ children }: { children: React.ReactNode }) => { + const pathname = usePathname(); + const { data: menuRoles } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES], + queryFn: () => MenuApi.getMenuRoles(SITE_ID), + }); + const { setCurrentMenuInfo } = useMenuStore((state) => state); + + useEffect(() => { + if (!menuRoles) return; + const currentMenu = getCurrentMenu(menuRoles, pathname ?? ""); + + if (!currentMenu) return; + setCurrentMenuInfo(currentMenu); + }, [menuRoles]); + + return <>{children}; +}; + +export { MenuWrapper }; diff --git a/apps/cs/src/entities/menu/lib/utils.ts b/apps/cs/src/entities/menu/lib/utils.ts new file mode 100644 index 000000000..ecd636a88 --- /dev/null +++ b/apps/cs/src/entities/menu/lib/utils.ts @@ -0,0 +1,13 @@ +import type { MenuItem } from "../model/menu-interface"; + +export const getCurrentMenu = ( + menus: MenuItem[], + pathname: string, +): MenuItem | null => { + return ( + menus.find((menu) => { + if (menu.children) return getCurrentMenu(menu.children, pathname); + return menu.urlPath === pathname; + }) ?? null + ); +}; diff --git a/apps/cs/src/entities/menu/model/menu-interface.ts b/apps/cs/src/entities/menu/model/menu-interface.ts new file mode 100644 index 000000000..12c7d007a --- /dev/null +++ b/apps/cs/src/entities/menu/model/menu-interface.ts @@ -0,0 +1,23 @@ +export interface MenuItem { + menuCd: number; + menuKornNm: string; + menuEngNm: string; + parentId: number | null; + sortSeq: number; + iconNm?: string; + children: MenuItem[] | null; + levelNo: number; + urlPath: string; + menuLnkgTypeCd: string; + menuLnkgSn: number; + menuPath: string; + qrcd: string; + useYn: string; + otptYn: string; + newWndwYn: string; + requiredUrlPath: boolean; +} + +export interface MenuService { + getMenuRoles(siteId: string): Promise; +} diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 52a63eb8a..80d0d7d6f 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -197,7 +197,7 @@ function MenuPage() { return ( <> - +
-
- {files?.filter(validFileType).map(combineFileName).join(", ")} -
- -
- ); -} diff --git a/packages/common/components/src/ui/file-search/file-search.css b/packages/common/components/src/ui/file-uploader/file-uploader.css similarity index 82% rename from packages/common/components/src/ui/file-search/file-search.css rename to packages/common/components/src/ui/file-uploader/file-uploader.css index c8a1e3975..16a92099c 100644 --- a/packages/common/components/src/ui/file-search/file-search.css +++ b/packages/common/components/src/ui/file-uploader/file-uploader.css @@ -1,13 +1,13 @@ @tailwind components; @layer components { - .file-search-container { + .file-uploader-container { @apply flex w-full max-w-sm items-center gap-2; } - .file-search-preview { + .file-uploader-preview { @apply h-12 w-full min-w-56 overflow-x-auto whitespace-nowrap rounded-md border border-input bg-background px-3 py-2 text-[1.2rem]; } - .file-search-label { + .file-uploader-label { @apply flex-shrink-0 cursor-pointer rounded-sm border border-input bg-muted px-4 py-2 text-[1.2rem] font-medium text-muted-foreground; } } diff --git a/packages/common/components/src/ui/file-uploader/file-uploader.stories.tsx b/packages/common/components/src/ui/file-uploader/file-uploader.stories.tsx new file mode 100644 index 000000000..a849f2b14 --- /dev/null +++ b/packages/common/components/src/ui/file-uploader/file-uploader.stories.tsx @@ -0,0 +1,14 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { FileUploader } from "."; + +const meta: Meta = { + title: "UI/FileUploader", + component: FileUploader, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => {}} />, +}; diff --git a/packages/common/components/src/ui/file-uploader/index.tsx b/packages/common/components/src/ui/file-uploader/index.tsx new file mode 100644 index 000000000..956c99ceb --- /dev/null +++ b/packages/common/components/src/ui/file-uploader/index.tsx @@ -0,0 +1,37 @@ +import "./file-uploader.css"; + +import { cn } from "../../lib/utils"; +import { Input } from "../input"; +import { combineFileName, validFileType } from "./utils"; + +type FileUploaderProps = React.InputHTMLAttributes & { + id: string; + files?: ReadonlyArray; + onUpload: (e: React.ChangeEvent) => void; +}; + +export function FileUploader({ + id, + className, + files, + onUpload, + ...props +}: FileUploaderProps) { + return ( +
+ +
+ {files?.filter(validFileType).map(combineFileName).join(", ")} +
+ +
+ ); +} diff --git a/packages/common/components/src/ui/file-search/utils.ts b/packages/common/components/src/ui/file-uploader/utils.ts similarity index 100% rename from packages/common/components/src/ui/file-search/utils.ts rename to packages/common/components/src/ui/file-uploader/utils.ts diff --git a/packages/common/components/src/ui/index.ts b/packages/common/components/src/ui/index.ts index dde5f5879..007c8ab43 100644 --- a/packages/common/components/src/ui/index.ts +++ b/packages/common/components/src/ui/index.ts @@ -7,7 +7,7 @@ export * from "./data-table"; export * from "./date-picker"; export * from "./dialog"; export * from "./dropdown-menu"; -export * from "./file-search"; +export * from "./file-uploader"; export * from "./icon-finder"; export * from "./input"; export * from "./label"; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 82f59a6cd..553c3a9e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -93,6 +93,9 @@ importers: tailwindcss-animate: specifier: ^1.0.7 version: 1.0.7(tailwindcss@3.4.17) + tus-js-client: + specifier: ^4.3.1 + version: 4.3.1 zod: specifier: ^3.24.2 version: 3.24.2 @@ -4175,7 +4178,6 @@ packages: /buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: true /bundle-require@5.1.0(esbuild@0.24.2): resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} @@ -4353,6 +4355,13 @@ packages: dev: false optional: true + /combine-errors@3.0.3: + resolution: {integrity: sha512-C8ikRNRMygCwaTx+Ek3Yr+OuZzgZjduCOfSQBjbM8V3MfgcjSTeto/GXP6PAwKvJz/v15b7GHZvx5rOlczFw/Q==} + dependencies: + custom-error-instance: 2.1.1 + lodash.uniqby: 4.5.0 + dev: false + /combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -4422,6 +4431,10 @@ packages: /csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + /custom-error-instance@2.1.1: + resolution: {integrity: sha512-p6JFxJc3M4OTD2li2qaHkDCw9SfMw82Ldr6OC9Je1aXiGfhx2W8p3GaoeaGrPJTUN9NirTM/KTxHWMUdR1rsUg==} + dev: false + /d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} engines: {node: '>=12'} @@ -5715,7 +5728,6 @@ packages: /graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - dev: true /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -5993,6 +6005,11 @@ packages: call-bound: 1.0.3 dev: true + /is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + dev: false + /is-string@1.1.1: resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} @@ -6097,6 +6114,10 @@ packages: engines: {node: '>=10'} dev: true + /js-base64@3.7.7: + resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} + dev: false + /js-cookie@3.0.5: resolution: {integrity: sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==} engines: {node: '>=14'} @@ -6219,6 +6240,37 @@ packages: resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} dev: true + /lodash._baseiteratee@4.7.0: + resolution: {integrity: sha512-nqB9M+wITz0BX/Q2xg6fQ8mLkyfF7MU7eE+MNBNjTHFKeKaZAPEzEg+E8LWxKWf1DQVflNEn9N49yAuqKh2mWQ==} + dependencies: + lodash._stringtopath: 4.8.0 + dev: false + + /lodash._basetostring@4.12.0: + resolution: {integrity: sha512-SwcRIbyxnN6CFEEK4K1y+zuApvWdpQdBHM/swxP962s8HIxPO3alBH5t3m/dl+f4CMUug6sJb7Pww8d13/9WSw==} + dev: false + + /lodash._baseuniq@4.6.0: + resolution: {integrity: sha512-Ja1YevpHZctlI5beLA7oc5KNDhGcPixFhcqSiORHNsp/1QTv7amAXzw+gu4YOvErqVlMVyIJGgtzeepCnnur0A==} + dependencies: + lodash._createset: 4.0.3 + lodash._root: 3.0.1 + dev: false + + /lodash._createset@4.0.3: + resolution: {integrity: sha512-GTkC6YMprrJZCYU3zcqZj+jkXkrXzq3IPBcF/fIPpNEAB4hZEtXU8zp/RwKOvZl43NUmwDbyRk3+ZTbeRdEBXA==} + dev: false + + /lodash._root@3.0.1: + resolution: {integrity: sha512-O0pWuFSK6x4EXhM1dhZ8gchNtG7JMqBtrHdoUFUWXD7dJnNSUze1GuyQr5sOs0aCvgGeI3o/OJW8f4ca7FDxmQ==} + dev: false + + /lodash._stringtopath@4.8.0: + resolution: {integrity: sha512-SXL66C731p0xPDC5LZg4wI5H+dJo/EO4KTqOMwLYCH3+FmmfAKJEZCm6ohGpI+T1xwsDsJCfL4OnhorllvlTPQ==} + dependencies: + lodash._basetostring: 4.12.0 + dev: false + /lodash.add@3.7.0: resolution: {integrity: sha512-/uxUZkB6vYiPoGSXADJJE+Ee4W2vU+bxZOFdvJdx+mDgv+qyAO76sWD84Q4FxsMgTjC1+gVFC9YNhdG243QRDw==} dev: false @@ -6248,6 +6300,17 @@ packages: resolution: {integrity: sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==} dev: true + /lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + dev: false + + /lodash.uniqby@4.5.0: + resolution: {integrity: sha512-IRt7cfTtHy6f1aRVA5n7kT8rgN3N1nH6MOWLcHfpWG2SH19E3JksLK38MktLxZDhlAjCP9jpIXkOnRXlu6oByQ==} + dependencies: + lodash._baseiteratee: 4.7.0 + lodash._baseuniq: 4.6.0 + dev: false + /lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -6905,6 +6968,14 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /proper-lockfile@4.1.2: + resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} + dependencies: + graceful-fs: 4.2.11 + retry: 0.12.0 + signal-exit: 3.0.7 + dev: false + /proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: false @@ -6914,6 +6985,10 @@ packages: engines: {node: '>=6'} dev: true + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -7272,6 +7347,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + dev: false + /resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -7304,6 +7383,11 @@ packages: supports-preserve-symlinks-flag: 1.0.0 dev: true + /retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + dev: false + /reusify@1.1.0: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} @@ -7517,6 +7601,10 @@ packages: side-channel-weakmap: 1.0.2 dev: true + /signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: false + /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} @@ -8089,6 +8177,19 @@ packages: turbo-windows-arm64: 2.4.2 dev: true + /tus-js-client@4.3.1: + resolution: {integrity: sha512-ZLeYmjrkaU1fUsKbIi8JML52uAocjEZtBx4DKjRrqzrZa0O4MYwT6db+oqePlspV+FxXJAyFBc/L5gwUi2OFsg==} + engines: {node: '>=18'} + dependencies: + buffer-from: 1.1.2 + combine-errors: 3.0.3 + is-stream: 2.0.1 + js-base64: 3.7.7 + lodash.throttle: 4.1.1 + proper-lockfile: 4.1.2 + url-parse: 1.5.10 + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -8221,6 +8322,13 @@ packages: punycode: 2.3.1 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: false + /use-callback-ref@1.3.3(@types/react@18.3.18)(react@18.3.1): resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} engines: {node: '>=10'} From f2954e6ebd472472be74811e5df1a5104feddef1 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 1 Apr 2025 14:25:17 +0900 Subject: [PATCH 034/102] =?UTF-8?q?refactor:=20page=20layout=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/layout.tsx | 6 +- apps/cs/src/pages/image/ui/image-page.tsx | 13 +-- apps/cs/src/pages/menu/ui/menu-page.tsx | 119 +++++++++++----------- apps/cs/src/pages/site/ui/create-page.tsx | 7 +- apps/cs/src/pages/site/ui/modify-page.tsx | 15 ++- apps/cs/src/pages/site/ui/site-page.tsx | 49 +++++---- 6 files changed, 98 insertions(+), 111 deletions(-) diff --git a/apps/cs/app/(with-base-layout)/layout.tsx b/apps/cs/app/(with-base-layout)/layout.tsx index 3e9d1fe8d..59e242cfc 100644 --- a/apps/cs/app/(with-base-layout)/layout.tsx +++ b/apps/cs/app/(with-base-layout)/layout.tsx @@ -5,6 +5,8 @@ import type { MenuItem } from "@common/components/api"; import { BaseLayout } from "@common/components/ui"; import { MenuWrapper } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; +import { PageLayout } from "@/shared/ui/page-layout"; + export default function WithBaseLayout({ children, }: { @@ -21,7 +23,9 @@ export default function WithBaseLayout({ return ( - {children} + + {children} + ); } diff --git a/apps/cs/src/pages/image/ui/image-page.tsx b/apps/cs/src/pages/image/ui/image-page.tsx index 9c82acf0c..ca9063b5c 100644 --- a/apps/cs/src/pages/image/ui/image-page.tsx +++ b/apps/cs/src/pages/image/ui/image-page.tsx @@ -2,7 +2,6 @@ import { Upload } from "tus-js-client"; import { FileUploader } from "@common/components/ui"; -import { PageLayout } from "@/shared/ui/page-layout"; import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config"; import { getCookie } from "@/shared/lib"; @@ -47,14 +46,12 @@ function ImagePage() { }; return ( - -
-
-

이미지 목록

-
- +
+
+

이미지 목록

- + +
); } diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index 80d0d7d6f..c3a186eb6 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -16,7 +16,6 @@ import { } from "@common/components/ui"; import { SiteApi } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; -import { PageLayout } from "@/shared/ui/page-layout"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import { useAlertStore } from "@/shared/lib/use-alert-store"; import type { TreeMenu } from "../model/menu-interface"; @@ -197,67 +196,65 @@ function MenuPage() { return ( <> - -
-
- - {siteId && ( - <> -
- - -
-
- -
- - )} -
-
- -
+
+
+ + {siteId && ( + <> +
+ + +
+
+ +
+ + )}
- +
+ +
+
); diff --git a/apps/cs/src/pages/site/ui/create-page.tsx b/apps/cs/src/pages/site/ui/create-page.tsx index 9e78e7991..1b9d2adaf 100644 --- a/apps/cs/src/pages/site/ui/create-page.tsx +++ b/apps/cs/src/pages/site/ui/create-page.tsx @@ -6,7 +6,6 @@ import { useMutation } from "@tanstack/react-query"; import { zodResolver } from "@hookform/resolvers/zod"; 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() { @@ -37,11 +36,7 @@ function CreatePage() { }, }); - return ( - - - - ); + return ; } export { CreatePage }; diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index 58c58a035..1be9eb38e 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -6,7 +6,6 @@ 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 { SiteForm } from "./site-form"; @@ -63,14 +62,12 @@ function ModifyPage({ siteId }: { siteId: string }) { }); return ( - - - + ); } diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 20d4e824f..5f4f9c18c 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -9,7 +9,6 @@ import { getCoreRowModel, useReactTable } from "@tanstack/react-table"; import { Button, DataTable, Input } from "@common/components"; import { SiteApi, type Site, type SiteParams } from "@/entities/site"; import { ENDPOINT } from "@/shared/config"; -import { PageLayout } from "@/shared/ui/page-layout"; import { columns } from "../model/table-columns"; function SitePage() { @@ -44,34 +43,32 @@ function SitePage() { }, []); return ( - -
-
-
- - setSiteQuery({ siteNm: e.target.value })} - /> - - +
+
+
+ + setSiteQuery({ siteNm: e.target.value })} + /> + + +
+
+
+

사이트 목록

+
-
-
-

사이트 목록

- -
-
- -
+
+
- +
); } From a912deeefb084a51d84ee93427f1a4a0ba40201b Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 1 Apr 2025 15:40:38 +0900 Subject: [PATCH 035/102] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=9E=91?= =?UTF-8?q?=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/entities/site/model/site-interface.ts | 2 +- apps/cs/src/pages/image/api/image-service.ts | 21 ++++ .../src/pages/image/model/image-interface.ts | 36 ++++++ .../src/pages/image/model/table-columns.tsx | 74 +++++++++++ apps/cs/src/pages/image/ui/image-page.tsx | 119 +++++++++++------- ...le-colums.tsx => content-table-colums.tsx} | 2 +- .../pages/menu/ui/content-search-dialog.tsx | 2 +- apps/cs/src/shared/config/api.ts | 1 + packages/common/components/src/api/site.ts | 2 +- .../components/src/ui/data-table/index.tsx | 11 +- 10 files changed, 222 insertions(+), 48 deletions(-) create mode 100644 apps/cs/src/pages/image/api/image-service.ts create mode 100644 apps/cs/src/pages/image/model/image-interface.ts create mode 100644 apps/cs/src/pages/image/model/table-columns.tsx rename apps/cs/src/pages/menu/model/{table-colums.tsx => content-table-colums.tsx} (92%) diff --git a/apps/cs/src/entities/site/model/site-interface.ts b/apps/cs/src/entities/site/model/site-interface.ts index ef83a53c1..a8fb2796f 100644 --- a/apps/cs/src/entities/site/model/site-interface.ts +++ b/apps/cs/src/entities/site/model/site-interface.ts @@ -29,7 +29,7 @@ export interface SiteDetail extends SiteFormData { mdfcnDt: string; mdFr: string; rgTrNm: string; - mdFrNm: string; + mdfrNm: string; } export const siteFormSchema = z.object({ diff --git a/apps/cs/src/pages/image/api/image-service.ts b/apps/cs/src/pages/image/api/image-service.ts new file mode 100644 index 000000000..63918784a --- /dev/null +++ b/apps/cs/src/pages/image/api/image-service.ts @@ -0,0 +1,21 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + ImageParams, + ImageList, + ImageService, +} from "../model/image-interface"; + +export class ImageApiService implements ImageService { + public async getImageList(params: ImageParams): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.IMAGES}`, + { + params, + }, + ); + return response.data; + } +} + +export const ImageApi = new ImageApiService(); diff --git a/apps/cs/src/pages/image/model/image-interface.ts b/apps/cs/src/pages/image/model/image-interface.ts new file mode 100644 index 000000000..b24c0eed2 --- /dev/null +++ b/apps/cs/src/pages/image/model/image-interface.ts @@ -0,0 +1,36 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export const IMAGE_TYPE = { + IMGSUB: "IMGSUB", + IMGMAIN: "IMGMAIN", + IMGLOGIN: "IMGLOGIN", +} as const; + +export type ImageType = (typeof IMAGE_TYPE)[keyof typeof IMAGE_TYPE]; + +export interface ImageParams extends PageableParams { + keyword: string; + imgTypeCd: string; + useYn: string; +} + +export interface Image { + imgSn: number; + imgTypeCd: string; + imgTypeNm: string; + imgTtl: string; + lnkgUrlAddr: string; + atflYn: string; + sortSeq: number; + atflCd: string; + useYn: string; + mdfcnDt: string; + mdFr: string; + mdfrNm: string; +} + +export type ImageList = PageableList; + +export interface ImageService { + getImageList: (params: ImageParams) => Promise; +} diff --git a/apps/cs/src/pages/image/model/table-columns.tsx b/apps/cs/src/pages/image/model/table-columns.tsx new file mode 100644 index 000000000..8ae770e0e --- /dev/null +++ b/apps/cs/src/pages/image/model/table-columns.tsx @@ -0,0 +1,74 @@ +"use client"; + +import dayjs from "dayjs"; +import { type ColumnDef } from "@tanstack/react-table"; +import { Checkbox } from "@common/components/ui"; +import { type Image } from "./image-interface"; +import { IMAGE_TYPE } from "./image-interface"; + +export const columns: ColumnDef[] = [ + { + id: "select", + header: ({ table }) => ( + e.stopPropagation()} + onCheckedChange={(checked) => + table.toggleAllPageRowsSelected(!!checked) + } + className="h-4 w-4 rounded border-gray-300" + /> + ), + cell: ({ row }) => ( + e.stopPropagation()} + onCheckedChange={(checked) => row.toggleSelected(!!checked)} + className="h-4 w-4 rounded border-gray-300" + /> + ), + }, + { + accessorKey: "imgSn", + header: "NO", + cell: ({ row }) => row.index + 1, + }, + { + accessorKey: "imgTypeCd", + header: "이미지구분", + cell: ({ row }) => { + const type = row.getValue("imgTypeCd") as string; + switch (type) { + case IMAGE_TYPE.IMGSUB: + return "서브이미지"; + case IMAGE_TYPE.IMGMAIN: + return "메인이미지"; + case IMAGE_TYPE.IMGLOGIN: + return "로그인이미지"; + default: + return type; + } + }, + }, + { + accessorKey: "imgTtl", + header: "이미지명", + }, + { + accessorKey: "useYn", + header: "사용여부", + cell: ({ row }) => (row.getValue("useYn") === "Y" ? "사용" : "미사용"), + }, + { + accessorKey: "mdfrNm", + header: "최종수정자", + }, + { + accessorKey: "mdfcnDt", + header: "최종수정일", + cell: ({ row }) => { + const date = row.getValue("mdfcnDt") as string; + return date ? dayjs(date).format("YYYY-MM-DD") : ""; + }, + }, +]; diff --git a/apps/cs/src/pages/image/ui/image-page.tsx b/apps/cs/src/pages/image/ui/image-page.tsx index ca9063b5c..7eb5863bd 100644 --- a/apps/cs/src/pages/image/ui/image-page.tsx +++ b/apps/cs/src/pages/image/ui/image-page.tsx @@ -1,56 +1,89 @@ "use client"; -import { Upload } from "tus-js-client"; -import { FileUploader } from "@common/components/ui"; -import { AUTH_USER_ID, CLAIM_NAME, ENDPOINT } from "@/shared/config"; -import { getCookie } from "@/shared/lib"; +import { useEffect, useState } from "react"; +import { useRouter } from "next/navigation"; +import { useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + useReactTable, + type RowSelectionState, +} from "@tanstack/react-table"; +import { Button, DataTable, Input } from "@common/components"; +import { ENDPOINT } from "@/shared/config"; +import { ImageApi } from "../api/image-service"; +import { columns } from "../model/table-columns"; +import { type Image, type ImageParams } from "../model/image-interface"; function ImagePage() { - const handleUpload = (e: React.ChangeEvent) => { - const file = e.target.files?.item(0); - if (!file) return; + const router = useRouter(); - const upload = new Upload(file, { - endpoint: `${ENDPOINT.ATTACH_SERVICE.TUS_UPLOAD}`, - headers: { - [CLAIM_NAME]: getCookie(CLAIM_NAME) ?? "", - [AUTH_USER_ID]: getCookie(AUTH_USER_ID) ?? "", - }, - retryDelays: [0, 3000, 5000, 10000, 20000], - metadata: { - filename: file.name, - filetype: file.type, - }, - onError: function (error) { - console.log("Failed because: " + error); - }, - onProgress: function (bytesUploaded, bytesTotal) { - const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2); - console.log(bytesUploaded, bytesTotal, percentage + "%"); - }, - onSuccess: function () { - console.log( - "Download %s from %s", - (upload.file as File).name, - upload.url, - ); - }, - }); + const [rowSelection, setRowSelection] = useState({}); + const [imageQuery, setImageQuery] = useState({ + keyword: "", + imgTypeCd: "", + useYn: "", + page: 0, + size: 100, + }); - upload.findPreviousUploads().then(function (previousUploads) { - if (previousUploads.length) { - upload.resumeFromPreviousUpload(previousUploads[0]!); - } - upload.start(); - }); + const { data: imageList, refetch } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.IMAGES], + queryFn: () => ImageApi.getImageList(imageQuery), + enabled: false, + }); + + const table = useReactTable({ + data: imageList?.content ?? [], + columns, + state: { + rowSelection, + }, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onRowSelectionChange: setRowSelection, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + refetch(); }; + const handleRowClick = (row: Image) => { + router.push(`/image/register/${row.imgSn}`); + }; + + useEffect(() => { + refetch(); + }, []); + return ( -
-
-

이미지 목록

+
+
+
+ + + setImageQuery({ ...imageQuery, keyword: e.target.value }) + } + /> + + +
+
+
+

이미지 목록

+ +
+
+ +
-
); } diff --git a/apps/cs/src/pages/menu/model/table-colums.tsx b/apps/cs/src/pages/menu/model/content-table-colums.tsx similarity index 92% rename from apps/cs/src/pages/menu/model/table-colums.tsx rename to apps/cs/src/pages/menu/model/content-table-colums.tsx index b24b506fd..b8bb28638 100644 --- a/apps/cs/src/pages/menu/model/table-colums.tsx +++ b/apps/cs/src/pages/menu/model/content-table-colums.tsx @@ -1,6 +1,6 @@ import { createColumnHelper } from "@tanstack/react-table"; import { Checkbox } from "@common/components/ui"; -import type { Content } from "../model/content-interface"; +import type { Content } from "./content-interface"; const columnHelper = createColumnHelper(); diff --git a/apps/cs/src/pages/menu/ui/content-search-dialog.tsx b/apps/cs/src/pages/menu/ui/content-search-dialog.tsx index 60ee794b8..66d939842 100644 --- a/apps/cs/src/pages/menu/ui/content-search-dialog.tsx +++ b/apps/cs/src/pages/menu/ui/content-search-dialog.tsx @@ -18,7 +18,7 @@ import { import { ENDPOINT } from "@/shared/config"; import { ContentApi } from "../api/content-service"; import type { Content } from "../model/content-interface"; -import { columns } from "../model/table-colums"; +import { columns } from "../model/content-table-colums"; type ContentSearchDialogProps = { handleContentSelect: (content: Content) => void; diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index a4f861c1a..4430285d8 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -20,6 +20,7 @@ export const ENDPOINT = { MENU_ROLES: "/cms-service/api/v1/menu-roles", CONTENTS: "/cms-service/api/v1/contents", USER_OGNZ: "/cms-service/api/v1/user-ognz", + IMAGES: "/cms-service/api/v1/images", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs", diff --git a/packages/common/components/src/api/site.ts b/packages/common/components/src/api/site.ts index 77ed968e4..2ca35bd26 100644 --- a/packages/common/components/src/api/site.ts +++ b/packages/common/components/src/api/site.ts @@ -20,7 +20,7 @@ export interface SiteInfo { mdfcnDt: string; mdFr: string; rgTrNm: string; - mdFrNm: string; + mdfrNm: string; } export const getSiteInfo = async (siteId: string) => { diff --git a/packages/common/components/src/ui/data-table/index.tsx b/packages/common/components/src/ui/data-table/index.tsx index d635e8554..53312fe83 100644 --- a/packages/common/components/src/ui/data-table/index.tsx +++ b/packages/common/components/src/ui/data-table/index.tsx @@ -3,6 +3,7 @@ import { type RowData, type Table as TableType, } from "@tanstack/react-table"; +import { cn } from "../../lib"; import { Table, TableCell, TableHead, TableHeader, TableRow } from "../table"; declare module "@tanstack/react-table" { @@ -55,7 +56,15 @@ function DataTable({ onRowClick?.(row.original)} - className={onRowClick ? "cursor-pointer hover:bg-accent" : ""} + className={cn( + onRowClick && "cursor-pointer hover:bg-accent", + variant === "default" && + "[data-theme='black'] hover:!bg-[rgba(84,85,86,0.7)]", + variant === "secondary" && + "[data-theme='black'] hover:!bg-[rgba(86,91,95,0.7)]", + variant === "line" && + "[data-theme='black'] hover:!bg-[rgba(255,255,255,0.1)]", + )} > {row.getVisibleCells().map((cell) => { const { meta } = cell.column.columnDef; From 60464b92b0957eb3ee1a9cb178e2a790203a2d97 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 1 Apr 2025 17:43:20 +0900 Subject: [PATCH 036/102] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=20=ED=8F=BC=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../image/register/page.tsx | 2 + apps/cs/src/pages/image/api/image-service.ts | 34 +- apps/cs/src/pages/image/index.ts | 1 + .../src/pages/image/model/image-interface.ts | 16 + apps/cs/src/pages/image/ui/create-page.tsx | 30 + apps/cs/src/pages/image/ui/image-form.tsx | 254 ++++++++ apps/cs/src/pages/menu/ui/base-menu-form.tsx | 10 +- apps/cs/src/pages/menu/ui/type-menu-form.tsx | 14 +- apps/cs/src/pages/site/ui/create-page.tsx | 9 +- .../site/ui/domain-management-dialog.tsx | 11 +- .../pages/site/ui/ip-management-dialog.tsx | 10 +- apps/cs/src/pages/site/ui/modify-page.tsx | 17 +- apps/cs/src/pages/site/ui/site-form.tsx | 599 +++++++++--------- .../components/src/ui/data-table/index.tsx | 10 +- .../common/components/src/ui/table/table.css | 2 +- 15 files changed, 648 insertions(+), 371 deletions(-) create mode 100644 apps/cs/app/(with-base-layout)/image/register/page.tsx create mode 100644 apps/cs/src/pages/image/ui/create-page.tsx create mode 100644 apps/cs/src/pages/image/ui/image-form.tsx diff --git a/apps/cs/app/(with-base-layout)/image/register/page.tsx b/apps/cs/app/(with-base-layout)/image/register/page.tsx new file mode 100644 index 000000000..3c29f2bfe --- /dev/null +++ b/apps/cs/app/(with-base-layout)/image/register/page.tsx @@ -0,0 +1,2 @@ +import { CreatePage } from "@/pages/image"; +export default CreatePage; diff --git a/apps/cs/src/pages/image/api/image-service.ts b/apps/cs/src/pages/image/api/image-service.ts index 63918784a..f16f2f322 100644 --- a/apps/cs/src/pages/image/api/image-service.ts +++ b/apps/cs/src/pages/image/api/image-service.ts @@ -1,8 +1,10 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; import type { - ImageParams, + Image, + ImageFormData, ImageList, + ImageParams, ImageService, } from "../model/image-interface"; @@ -16,6 +18,36 @@ export class ImageApiService implements ImageService { ); return response.data; } + + public async getImageInfo(imgSn: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.IMAGES}/${imgSn}`, + ); + return response.data; + } + + public async createImage(data: ImageFormData): Promise { + const response = await client.post( + ENDPOINT.CMS_SERVICE.IMAGES, + data, + ); + return response.data; + } + + public async updateImage( + imgSn: string, + data: Partial, + ): Promise { + const response = await client.put( + `${ENDPOINT.CMS_SERVICE.IMAGES}/${imgSn}`, + data, + ); + return response.data; + } + + public async deleteImage(imgSn: string): Promise { + await client.delete(`${ENDPOINT.CMS_SERVICE.IMAGES}/${imgSn}`); + } } export const ImageApi = new ImageApiService(); diff --git a/apps/cs/src/pages/image/index.ts b/apps/cs/src/pages/image/index.ts index 554a47695..6b02dedec 100644 --- a/apps/cs/src/pages/image/index.ts +++ b/apps/cs/src/pages/image/index.ts @@ -1 +1,2 @@ +export { CreatePage } from "./ui/create-page"; export { ImagePage } from "./ui/image-page"; diff --git a/apps/cs/src/pages/image/model/image-interface.ts b/apps/cs/src/pages/image/model/image-interface.ts index b24c0eed2..cdeff74a1 100644 --- a/apps/cs/src/pages/image/model/image-interface.ts +++ b/apps/cs/src/pages/image/model/image-interface.ts @@ -1,3 +1,4 @@ +import { z } from "zod"; import type { PageableList, PageableParams } from "@/shared/model"; export const IMAGE_TYPE = { @@ -29,8 +30,23 @@ export interface Image { mdfrNm: string; } +export const imageFormSchema = z.object({ + imgTtl: z.string(), + sortSeq: z.number(), + imgTypeCd: z.string(), + atflYn: z.string(), + atflCd: z.string().optional(), + lnkgUrlAddr: z.string().optional(), + useYn: z.string(), +}); + +export type ImageFormData = z.infer; export type ImageList = PageableList; export interface ImageService { getImageList: (params: ImageParams) => Promise; + getImageInfo: (imgSn: string) => Promise; + createImage: (data: ImageFormData) => Promise; + updateImage: (imgSn: string, data: ImageFormData) => Promise; + deleteImage: (imgSn: string) => Promise; } diff --git a/apps/cs/src/pages/image/ui/create-page.tsx b/apps/cs/src/pages/image/ui/create-page.tsx new file mode 100644 index 000000000..715091513 --- /dev/null +++ b/apps/cs/src/pages/image/ui/create-page.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useForm } from "react-hook-form"; +import { ImageForm } from "./image-form"; +import { imageFormSchema, type ImageFormData } from "../model/image-interface"; +import { zodResolver } from "@hookform/resolvers/zod"; + +function CreatePage() { + const form = useForm({ + resolver: zodResolver(imageFormSchema), + defaultValues: { + atflYn: "Y", + }, + }); + + const handleSave = async (data: ImageFormData) => { + console.log(data); + }; + + return ( +
+
+

이미지 등록

+
+ +
+ ); +} + +export { CreatePage }; diff --git a/apps/cs/src/pages/image/ui/image-form.tsx b/apps/cs/src/pages/image/ui/image-form.tsx new file mode 100644 index 000000000..0229bc9a8 --- /dev/null +++ b/apps/cs/src/pages/image/ui/image-form.tsx @@ -0,0 +1,254 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import type { UseFormReturn } from "react-hook-form"; +import { + Button, + FileUploader, + Input, + Label, + RadioGroup, + RadioGroupItem, + Table, + TableBody, + TableCell, + TableHead, + TableRow, +} from "@common/components"; +import { CodeSelect } from "@/entities/code"; +import type { ImageFormData } from "../model/image-interface"; +import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; + +type ImageFormProps = { + form: UseFormReturn; + imgSn?: string; + handleSave?: (data: ImageFormData) => Promise; + handleDelete?: () => Promise; +}; + +function ImageForm({ form, imgSn, handleSave, handleDelete }: ImageFormProps) { + const isModifyMode = !!imgSn; + const atflYn = form.watch("atflYn"); + const router = useRouter(); + + const onSubmit = async (data: ImageFormData) => { + await handleSave?.(data); + }; + + const onDelete = async () => { + await handleDelete?.(); + }; + + return ( +
+ +
+
+ + + + + + + 이미지명 + + ( + + + + + + )} + /> + + + + 정렬순서 + + ( + + + { + const value = e.target.value; + if (isNaN(Number(value))) return; + field.onChange(Number(value)); + }} + className="!text-[1.3rem]" + placeholder="정렬순서를 입력해주세요" + /> + + + )} + /> + + + + 이미지유형 + + ( + + + + + + )} + /> + + + + 첨부파일 + + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
+ {atflYn === "Y" && ( + + 이미지파일 + + ( + + + { + const file = e.target.files?.[0]; + if (file) { + field.onChange(file.name); + } + }} + /> + + + )} + /> + + + )} + {atflYn === "N" && ( + + 외부링크 + + ( + + + + + + )} + /> + + + )} + + 사용여부 + + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
+
+
+
+
+ + + {isModifyMode && ( + + )} +
+ + + ); +} + +export { ImageForm }; 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 62d69bb38..1af5bca3a 100644 --- a/apps/cs/src/pages/menu/ui/base-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/base-menu-form.tsx @@ -10,12 +10,7 @@ import { TableHead, TableRow, } from "@common/components"; -import { - FormItem, - FormControl, - FormField, - FormMessage, -} from "@/shared/ui/form"; +import { FormItem, FormControl, FormField } from "@/shared/ui/form"; import { MENU_TYPE, type MenuFormData } from "../model/menu-interface"; interface BaseMenuFormProps { @@ -57,7 +52,6 @@ function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) { placeholder="메뉴명을 입력하십시오." /> - )} /> @@ -79,7 +73,6 @@ function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) { placeholder="메뉴 영문명을 입력하십시오." /> - )} /> @@ -118,7 +111,6 @@ function BaseMenuForm({ form, menuCd }: BaseMenuFormProps) {
- )} /> diff --git a/apps/cs/src/pages/menu/ui/type-menu-form.tsx b/apps/cs/src/pages/menu/ui/type-menu-form.tsx index 5081a7e51..b2e6d98e2 100644 --- a/apps/cs/src/pages/menu/ui/type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/type-menu-form.tsx @@ -13,12 +13,7 @@ import { TableHead, TableRow, } from "@common/components"; -import { - FormItem, - FormControl, - FormField, - FormMessage, -} from "@/shared/ui/form"; +import { FormItem, FormControl, FormField } from "@/shared/ui/form"; import type { BoardPopup } from "@/entities/board"; import type { Content } from "../model/content-interface"; import type { User } from "../model/organization-interface"; @@ -117,7 +112,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { } /> - )} /> @@ -153,7 +147,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
- )} /> @@ -186,7 +179,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
- )} /> @@ -211,7 +203,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { placeholder="사이트맵에서 다른 이름으로 사용할 경우 입력하십시오." /> - )} /> @@ -255,7 +246,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
- )} /> @@ -287,7 +277,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
- )} /> @@ -322,7 +311,6 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) {
- )} /> diff --git a/apps/cs/src/pages/site/ui/create-page.tsx b/apps/cs/src/pages/site/ui/create-page.tsx index 1b9d2adaf..e8f8c700d 100644 --- a/apps/cs/src/pages/site/ui/create-page.tsx +++ b/apps/cs/src/pages/site/ui/create-page.tsx @@ -36,7 +36,14 @@ function CreatePage() { }, }); - return ; + return ( +
+
+

사이트 등록

+
+ +
+ ); } export { CreatePage }; diff --git a/apps/cs/src/pages/site/ui/domain-management-dialog.tsx b/apps/cs/src/pages/site/ui/domain-management-dialog.tsx index 4a7e854ee..abd3c609b 100644 --- a/apps/cs/src/pages/site/ui/domain-management-dialog.tsx +++ b/apps/cs/src/pages/site/ui/domain-management-dialog.tsx @@ -23,13 +23,7 @@ import { DialogClose, Checkbox, } from "@common/components"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/shared/ui/form"; +import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; import { ENDPOINT } from "@/shared/config"; import { DomainApi } from "../api/domain-service"; import { @@ -233,7 +227,6 @@ function DomainManagementDialog({ className="!text-[1.3rem]" /> - )} /> @@ -253,7 +246,6 @@ function DomainManagementDialog({ }} /> - )} /> @@ -288,7 +280,6 @@ function DomainManagementDialog({
- )} /> diff --git a/apps/cs/src/pages/site/ui/ip-management-dialog.tsx b/apps/cs/src/pages/site/ui/ip-management-dialog.tsx index 97721a6d6..b0d8d193d 100644 --- a/apps/cs/src/pages/site/ui/ip-management-dialog.tsx +++ b/apps/cs/src/pages/site/ui/ip-management-dialog.tsx @@ -23,13 +23,7 @@ import { DialogClose, Checkbox, } from "@common/components"; -import { - Form, - FormControl, - FormField, - FormItem, - FormMessage, -} from "@/shared/ui/form"; +import { Form, FormControl, FormField, FormItem } from "@/shared/ui/form"; import { ENDPOINT } from "@/shared/config"; import { IpApi } from "../api/ip-service"; import { ipFormSchema, type IP, type IPFormData } from "../model/ip-interface"; @@ -217,7 +211,6 @@ function IPManagementDialog({ className="!text-[1.3rem]" /> - )} /> @@ -252,7 +245,6 @@ function IPManagementDialog({
- )} /> diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index 1be9eb38e..b09276996 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -62,12 +62,17 @@ 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 bdc9f104d..d8eac7343 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -16,13 +16,7 @@ import { } from "@common/components"; import { CodeSelect } from "@/entities/code"; import type { SiteFormData } from "@/entities/site"; -import { - FormItem, - FormControl, - FormField, - Form, - FormMessage, -} from "@/shared/ui/form"; +import { FormItem, FormControl, FormField, Form } from "@/shared/ui/form"; import { ConfirmDialog } from "@/shared/ui/confirm-dialog"; import type { Domain } from "../model/domain-interface"; import { IPManagementDialog } from "./ip-management-dialog"; @@ -77,329 +71,310 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) { <>
-
-
-

- {isModifyMode ? "사이트 수정" : "사이트 등록"} -

-
-
- - - - - - - - 사이트 코드 - +
+
+ + + + + + + 사이트 코드 + + ( + + + + + + )} + /> + + + + 사이트 구분 + +
( - - - - - - - )} - /> - - - - 사이트 구분 - -
- ( - - - - - - - )} - /> - {isModifyMode && ( - - )} -
-
-
- - 사이트 디자인 - - ( - )} /> - - + {isModifyMode && ( + + )} +
+
+
+ + 사이트 디자인 + + ( + + + + + + )} + /> + + + + 사이트명 + + ( + + + + + + )} + /> + + + + 사이트 설명 + + ( + + + + + + )} + /> + + + {isModifyMode && ( - 사이트명 + 사이트 도메인 - ( - - - - - - - )} - /> - - - - 사이트 설명 - - ( - - - - - - - )} - /> - - - {isModifyMode && ( - - 사이트 도메인 - -
-
- {form.watch("dmnAddr")} -
- +
+
+ {form.watch("dmnAddr")}
- - - )} - - 사이트 하단 표기 주소 - - ( - - - - - - - )} - /> + +
- - 사이트 하단 표기 전화번호 - - ( - - - - - - - )} - /> - - - - 사이트 하단 표기 팩스번호 - - ( - - - - - - - )} - /> - - - - 사이트 하단 표기 내용 - - ( - - - - - - - )} - /> - - - - 북마크 아이콘 - - ( - - - - - - - )} - /> - - - - 기본 사이트 여부 - - ( - - - -
- - -
-
- - -
-
-
- -
- )} - /> -
-
- - 사용 여부 - - ( - - - -
- - -
-
- - -
-
-
- -
- )} - /> -
-
- -
-
+ )} + + 사이트 하단 표기 주소 + + ( + + + + + + )} + /> + + + + 사이트 하단 표기 전화번호 + + ( + + + + + + )} + /> + + + + 사이트 하단 표기 팩스번호 + + ( + + + + + + )} + /> + + + + 사이트 하단 표기 내용 + + ( + + + + + + )} + /> + + + + 북마크 아이콘 + + ( + + + + + + )} + /> + + + + 기본 사이트 여부 + + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
+ + 사용 여부 + + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
+ +
- - {isModifyMode && ( -
+
+ - )} -
- - + + {isModifyMode && ( + + )} +
+ + + + ); } diff --git a/apps/cs/src/pages/image/ui/modify-page.tsx b/apps/cs/src/pages/image/ui/modify-page.tsx new file mode 100644 index 000000000..c440c56f9 --- /dev/null +++ b/apps/cs/src/pages/image/ui/modify-page.tsx @@ -0,0 +1,56 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation, useQuery } from "@tanstack/react-query"; +import { ENDPOINT } from "@/shared/config"; +import { imageFormSchema, type ImageFormData } from "../model/image-interface"; +import { ImageApi } from "../api/image-service"; +import { ImageForm } from "./image-form"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; + +function ModifyPage({ serial }: { serial: string }) { + const router = useRouter(); + const { setMessage: alert } = useAlertStore((state) => state); + + const { data: imageDetail } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.IMAGES, serial], + queryFn: () => ImageApi.getImageInfo(serial), + }); + + const { mutateAsync: updateImage } = useMutation({ + mutationFn: (data: ImageFormData) => ImageApi.updateImage(serial, data), + onSuccess: () => { + alert("수정되었습니다."); + }, + }); + + const { mutateAsync: deleteImage } = useMutation({ + mutationFn: () => ImageApi.deleteImage(serial), + onSuccess: () => { + alert("삭제되었습니다.", router.back); + }, + }); + + const form = useForm({ + resolver: zodResolver(imageFormSchema), + values: imageDetail, + }); + + return ( +
+
+

이미지 수정

+
+ +
+ ); +} + +export { ModifyPage }; diff --git a/apps/cs/src/pages/menu/api/menu-service.ts b/apps/cs/src/pages/menu/api/menu-service.ts index 4e03f6cdf..7598c2abb 100644 --- a/apps/cs/src/pages/menu/api/menu-service.ts +++ b/apps/cs/src/pages/menu/api/menu-service.ts @@ -24,7 +24,7 @@ export class MenuApiService implements MenuService { return response.data; } - public async modifyMenu( + public async updateMenu( menuCd: number, data: Omit, ): Promise { @@ -49,7 +49,7 @@ export class MenuApiService implements MenuService { return response.data; } - public async modifyTreeMenu(siteId: string, data: TreeNode[]): Promise { + public async updateTreeMenu(siteId: string, data: TreeNode[]): Promise { const response = await client.put( `${ENDPOINT.CMS_SERVICE.MENUS}/${siteId}/tree`, data, @@ -57,7 +57,7 @@ export class MenuApiService implements MenuService { return response.data; } - public async modifyMenuName(menuCd: number, name: string): Promise { + public async updateMenuName(menuCd: number, name: string): Promise { const response = await client.put( `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}/${name}`, ); diff --git a/apps/cs/src/pages/menu/model/menu-interface.ts b/apps/cs/src/pages/menu/model/menu-interface.ts index 51258fc49..f9650e0e2 100644 --- a/apps/cs/src/pages/menu/model/menu-interface.ts +++ b/apps/cs/src/pages/menu/model/menu-interface.ts @@ -75,12 +75,12 @@ export type Menu = Omit & { export interface MenuService { getMenuInfo: (menuCd: number) => Promise; createMenu: (data: MenuFormData) => Promise; - modifyMenu: ( + updateMenu: ( menuCd: number, data: Omit, ) => Promise; deleteMenu: (menuCd: number) => Promise; getTreeMenuList: (siteId: string) => Promise; - modifyTreeMenu: (siteId: string, data: TreeNode[]) => Promise; - modifyMenuName: (menuCd: number, name: string) => Promise; + updateTreeMenu: (siteId: string, data: TreeNode[]) => Promise; + updateMenuName: (menuCd: number, name: string) => Promise; } diff --git a/apps/cs/src/pages/menu/ui/menu-form.tsx b/apps/cs/src/pages/menu/ui/menu-form.tsx index 48e7589be..a4f21128b 100644 --- a/apps/cs/src/pages/menu/ui/menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/menu-form.tsx @@ -13,7 +13,7 @@ interface MenuFormProps { form: UseFormReturn; menuCd?: number; handleCreate: (data: MenuFormData) => void; - handleModify: ({ + handleUpdate: ({ menuCd, data, }: { @@ -27,7 +27,7 @@ function MenuForm({ form, menuCd, handleCreate, - handleModify, + handleUpdate, handleDelete, }: MenuFormProps) { const isModifyMode = !!menuCd; @@ -35,15 +35,15 @@ function MenuForm({ const { confirm } = useConfirm(); const [onConfirm, setOnConfirm] = useState(false); - const handleConfirm = async (fn: () => Promise) => { + const handleConfirm = async (callback: () => Promise) => { setOnConfirm(true); - await fn(); + await callback(); setOnConfirm(false); }; const onSubmit = async (data: MenuFormData) => { if (isModifyMode) { - await onModify(data); + await onUpdate(data); } else { await onCreate(data); } @@ -57,10 +57,10 @@ function MenuForm({ }); }; - const onModify = async (data: MenuFormData) => { + const onUpdate = async (data: MenuFormData) => { await handleConfirm(async () => { if (await confirm("수정하시겠습니까?")) { - handleModify({ menuCd: menuCd!, data }); + handleUpdate({ menuCd: menuCd!, data }); } }); }; diff --git a/apps/cs/src/pages/menu/ui/menu-node.tsx b/apps/cs/src/pages/menu/ui/menu-node.tsx index b2e595923..1cde10b99 100644 --- a/apps/cs/src/pages/menu/ui/menu-node.tsx +++ b/apps/cs/src/pages/menu/ui/menu-node.tsx @@ -34,10 +34,10 @@ function MenuNode({ const queryClient = useQueryClient(); - const { mutateAsync: modifyMenuName } = useMutation({ + const { mutateAsync: updateMenuName } = useMutation({ mutationFn: (nodeName: string) => { const encodedName = encodeURIComponent(nodeName); - return MenuApi.modifyMenuName(id as number, encodedName); + return MenuApi.updateMenuName(id as number, encodedName); }, onSuccess: () => { queryClient.invalidateQueries({ @@ -53,7 +53,7 @@ function MenuNode({ const handleSelect = () => onSelect(node); const handleSave = async () => { - await modifyMenuName(nodeName); + await updateMenuName(nodeName); setEditMode(false); }; diff --git a/apps/cs/src/pages/menu/ui/menu-page.tsx b/apps/cs/src/pages/menu/ui/menu-page.tsx index c3a186eb6..71ad0042b 100644 --- a/apps/cs/src/pages/menu/ui/menu-page.tsx +++ b/apps/cs/src/pages/menu/ui/menu-page.tsx @@ -80,14 +80,14 @@ function MenuPage() { }, }); - const { mutate: modifyMenu } = useMutation({ + const { mutate: updateMenu } = useMutation({ mutationFn: ({ menuCd, data, }: { menuCd: number; data: Omit; - }) => MenuApi.modifyMenu(menuCd, data), + }) => MenuApi.updateMenu(menuCd, data), onSuccess: () => { alert("수정되었습니다."); queryClient.invalidateQueries({ @@ -250,7 +250,7 @@ function MenuPage() { form={form} menuCd={selectedMenu?.menuCd} handleCreate={createMenu} - handleModify={modifyMenu} + handleUpdate={updateMenu} handleDelete={deleteMenu} />
diff --git a/apps/cs/src/pages/menu/ui/menu-tree.tsx b/apps/cs/src/pages/menu/ui/menu-tree.tsx index 76dc02610..45a4cbb37 100644 --- a/apps/cs/src/pages/menu/ui/menu-tree.tsx +++ b/apps/cs/src/pages/menu/ui/menu-tree.tsx @@ -41,9 +41,9 @@ function MenuTree({ const queryClient = useQueryClient(); const treeMethods = useRef(null); - const { mutate: modifyTreeMenu } = useMutation({ + const { mutate: updateTreeMenu } = useMutation({ mutationFn: (newTree: TreeNode) => - MenuApi.modifyTreeMenu(siteId, [newTree]), + MenuApi.updateTreeMenu(siteId, [newTree]), onSuccess: () => { queryClient.invalidateQueries({ queryKey: [ENDPOINT.CMS_SERVICE.MENUS], @@ -96,7 +96,7 @@ function MenuTree({ /> ); }} - onDrop={handleDropNode(modifyTreeMenu)} + onDrop={handleDropNode(updateTreeMenu)} classes={{ root: "p-4", container: "flex flex-col", diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index b09276996..6b6c43c1f 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -10,8 +10,6 @@ import { useAlertStore } from "@/shared/lib/use-alert-store"; import { SiteForm } from "./site-form"; function ModifyPage({ siteId }: { siteId: string }) { - const isModifyMode = !!siteId; - const router = useRouter(); const queryClient = useQueryClient(); const { setMessage: alert } = useAlertStore((state) => state); @@ -30,11 +28,11 @@ function ModifyPage({ siteId }: { siteId: string }) { bscSiteYn: data.basicSiteYn, }; }, - enabled: isModifyMode, + enabled: !!siteId, }); - const { mutateAsync: modifySite } = useMutation({ - mutationFn: (data: SiteFormData) => SiteApi.modifySite(siteId, data), + const { mutateAsync: updateSite } = useMutation({ + mutationFn: (data: SiteFormData) => SiteApi.updateSite(siteId, data), onSuccess: () => { alert("수정되었습니다.", () => { queryClient.invalidateQueries({ @@ -69,7 +67,7 @@ function ModifyPage({ siteId }: { siteId: string }) {
diff --git a/apps/cs/src/pages/site/ui/site-form.tsx b/apps/cs/src/pages/site/ui/site-form.tsx index d8eac7343..cbf90c95b 100644 --- a/apps/cs/src/pages/site/ui/site-form.tsx +++ b/apps/cs/src/pages/site/ui/site-form.tsx @@ -38,6 +38,12 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) { const [isIpManagementOpen, setIsIpManagementOpen] = useState(false); const [isDomainManagementOpen, setIsDomainManagementOpen] = useState(false); + const handleConfirm = async (callback: () => Promise) => { + setOnConfirm(true); + await callback(); + setOnConfirm(false); + }; + const handleSaveDomain = (rows: Domain[]) => { form.setValue( "dmnAddr", @@ -50,21 +56,19 @@ function SiteForm({ form, siteId, handleSave, handleDelete }: SiteFormProps) { ? "수정하시겠습니까?" : "저장하시겠습니까?"; - setOnConfirm(true); - if (await confirm(confirmMessage)) { - setOnConfirm(false); - handleSave(data); - } - setOnConfirm(false); + await handleConfirm(async () => { + if (await confirm(confirmMessage)) { + handleSave(data); + } + }); }; const onDelete = async () => { - setOnConfirm(true); - if (await confirm("삭제하시겠습니까?")) { - setOnConfirm(false); - handleDelete?.(); - } - setOnConfirm(false); + await handleConfirm(async () => { + if (await confirm("삭제하시겠습니까?")) { + handleDelete?.(); + } + }); }; return ( From a9739bdba594f5888544bccfef91c5aedc846eaf Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 14:49:35 +0900 Subject: [PATCH 040/102] =?UTF-8?q?feat:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D=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/image/api/image-service.ts | 2 +- .../src/pages/image/model/image-interface.ts | 4 ++-- apps/cs/src/pages/image/ui/create-page.tsx | 24 +++++++++++++------ apps/cs/src/pages/image/ui/image-form.tsx | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/apps/cs/src/pages/image/api/image-service.ts b/apps/cs/src/pages/image/api/image-service.ts index c7fb56240..af5d95bbe 100644 --- a/apps/cs/src/pages/image/api/image-service.ts +++ b/apps/cs/src/pages/image/api/image-service.ts @@ -31,7 +31,7 @@ export class ImageApiService implements ImageService { return response.data; } - public async updateImage(imgSn: string, data: Partial): Promise { + public async updateImage(imgSn: string, data: ImageFormData): Promise { const response = await client.put( `${ENDPOINT.CMS_SERVICE.IMAGES}/${imgSn}`, data, diff --git a/apps/cs/src/pages/image/model/image-interface.ts b/apps/cs/src/pages/image/model/image-interface.ts index 4933bc860..7748589c7 100644 --- a/apps/cs/src/pages/image/model/image-interface.ts +++ b/apps/cs/src/pages/image/model/image-interface.ts @@ -35,8 +35,8 @@ export const imageFormSchema = z.object({ sortSeq: z.number(), imgTypeCd: z.string(), atflYn: z.string(), - atflCd: z.string().optional(), - lnkgUrlAddr: z.string().optional(), + atflCd: z.string().nullish(), + lnkgUrlAddr: z.string().nullish(), useYn: z.string(), }); diff --git a/apps/cs/src/pages/image/ui/create-page.tsx b/apps/cs/src/pages/image/ui/create-page.tsx index 715091513..5b3ea3924 100644 --- a/apps/cs/src/pages/image/ui/create-page.tsx +++ b/apps/cs/src/pages/image/ui/create-page.tsx @@ -1,11 +1,25 @@ "use client"; +import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; -import { ImageForm } from "./image-form"; -import { imageFormSchema, type ImageFormData } from "../model/image-interface"; import { zodResolver } from "@hookform/resolvers/zod"; +import { useMutation } from "@tanstack/react-query"; +import { useAlertStore } from "@/shared/lib/use-alert-store"; +import { imageFormSchema, type ImageFormData } from "../model/image-interface"; +import { ImageApi } from "../api/image-service"; +import { ImageForm } from "./image-form"; function CreatePage() { + const router = useRouter(); + const { setMessage: alert } = useAlertStore((state) => state); + + const { mutateAsync: createImage } = useMutation({ + mutationFn: (data: ImageFormData) => ImageApi.createImage(data), + onSuccess: () => { + alert("등록되었습니다.", router.back); + }, + }); + const form = useForm({ resolver: zodResolver(imageFormSchema), defaultValues: { @@ -13,16 +27,12 @@ function CreatePage() { }, }); - const handleSave = async (data: ImageFormData) => { - console.log(data); - }; - return (

이미지 등록

- +
); } diff --git a/apps/cs/src/pages/image/ui/image-form.tsx b/apps/cs/src/pages/image/ui/image-form.tsx index 19a63eea9..81ab21726 100644 --- a/apps/cs/src/pages/image/ui/image-form.tsx +++ b/apps/cs/src/pages/image/ui/image-form.tsx @@ -55,7 +55,7 @@ function ImageForm({ form, imgSn, handleSave, handleDelete }: ImageFormProps) { await handleConfirm(async () => { if (await confirm(confirmMessage)) { - handleSave(data); + await handleSave(data); } }); }; From 4e4bd820097f7ca665cff8a8ba155146147896c8 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 15:33:51 +0900 Subject: [PATCH 041/102] =?UTF-8?q?refactor:=20menu=20wrapper=EC=97=90?= =?UTF-8?q?=EC=84=9C=20useQuery=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/layout.tsx | 5 +---- apps/cs/src/app/providers/app-provider.tsx | 3 ++- .../cs/src/entities/menu/lib/menu-wrapper.tsx | 21 +++++++++---------- 3 files changed, 13 insertions(+), 16 deletions(-) diff --git a/apps/cs/app/(with-base-layout)/layout.tsx b/apps/cs/app/(with-base-layout)/layout.tsx index 59e242cfc..4cb6bc9af 100644 --- a/apps/cs/app/(with-base-layout)/layout.tsx +++ b/apps/cs/app/(with-base-layout)/layout.tsx @@ -3,7 +3,6 @@ import { usePathname, useRouter } from "next/navigation"; import type { MenuItem } from "@common/components/api"; import { BaseLayout } from "@common/components/ui"; -import { MenuWrapper } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; import { PageLayout } from "@/shared/ui/page-layout"; @@ -23,9 +22,7 @@ export default function WithBaseLayout({ return ( - - {children} - + {children} ); } diff --git a/apps/cs/src/app/providers/app-provider.tsx b/apps/cs/src/app/providers/app-provider.tsx index b14c3dc96..a7d49ac0a 100644 --- a/apps/cs/src/app/providers/app-provider.tsx +++ b/apps/cs/src/app/providers/app-provider.tsx @@ -2,6 +2,7 @@ import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; import { AuthWrapper } from "@/entities/auth"; +import { MenuWrapper } from "@/entities/menu"; import { StoreProvider } from "@/shared/providers"; import { AlertDialog } from "@/shared/ui/alert-dialog"; @@ -10,7 +11,7 @@ const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { - {children} + {children} diff --git a/apps/cs/src/entities/menu/lib/menu-wrapper.tsx b/apps/cs/src/entities/menu/lib/menu-wrapper.tsx index 0fbd4df90..d473548af 100644 --- a/apps/cs/src/entities/menu/lib/menu-wrapper.tsx +++ b/apps/cs/src/entities/menu/lib/menu-wrapper.tsx @@ -2,27 +2,26 @@ import { useEffect } from "react"; import { usePathname } from "next/navigation"; -import { useQuery } from "@tanstack/react-query"; -import { ENDPOINT, SITE_ID } from "@/shared/config"; +import { SITE_ID } from "@/shared/config"; import { useMenuStore } from "@/shared/lib"; import { MenuApi } from "../api/menu-service"; import { getCurrentMenu } from "./utils"; const MenuWrapper = ({ children }: { children: React.ReactNode }) => { const pathname = usePathname(); - const { data: menuRoles } = useQuery({ - queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES], - queryFn: () => MenuApi.getMenuRoles(SITE_ID), - }); const { setCurrentMenuInfo } = useMenuStore((state) => state); useEffect(() => { - if (!menuRoles) return; - const currentMenu = getCurrentMenu(menuRoles, pathname ?? ""); + (async () => { + const menuRoles = await MenuApi.getMenuRoles(SITE_ID); + if (!menuRoles) return; - if (!currentMenu) return; - setCurrentMenuInfo(currentMenu); - }, [menuRoles]); + const currentMenu = getCurrentMenu(menuRoles, pathname ?? ""); + if (!currentMenu) return; + + setCurrentMenuInfo(currentMenu); + })(); + }, []); return <>{children}; }; From 0a51ac3f799dc6f5612b851e7d330fcff49fff7b Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:19:16 +0900 Subject: [PATCH 042/102] =?UTF-8?q?refactor:=20QueryClientProvider=20?= =?UTF-8?q?=EC=84=A0=EC=96=B8=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/layout.tsx | 9 ++++++++- apps/cs/src/app/providers/app-provider.tsx | 20 +++++++++++-------- apps/cs/src/pages/site/ui/modify-page.tsx | 7 +------ .../src/provider/query-provider.tsx | 16 +++++++++------ .../common/components/src/ui/layout/base.tsx | 2 +- 5 files changed, 32 insertions(+), 22 deletions(-) diff --git a/apps/cs/app/(with-base-layout)/layout.tsx b/apps/cs/app/(with-base-layout)/layout.tsx index 4cb6bc9af..c3254a935 100644 --- a/apps/cs/app/(with-base-layout)/layout.tsx +++ b/apps/cs/app/(with-base-layout)/layout.tsx @@ -1,6 +1,7 @@ "use client"; import { usePathname, useRouter } from "next/navigation"; +import { useQueryClient } from "@tanstack/react-query"; import type { MenuItem } from "@common/components/api"; import { BaseLayout } from "@common/components/ui"; import { useMenuStore } from "@/shared/lib"; @@ -13,6 +14,8 @@ export default function WithBaseLayout({ }) { const router = useRouter(); const pathname = usePathname(); + const queryClient = useQueryClient(); + const { setCurrentMenuInfo } = useMenuStore((state) => state); const handleMenuClick = (item: MenuItem) => { @@ -21,7 +24,11 @@ export default function WithBaseLayout({ }; return ( - + {children} ); diff --git a/apps/cs/src/app/providers/app-provider.tsx b/apps/cs/src/app/providers/app-provider.tsx index a7d49ac0a..fb4b65f27 100644 --- a/apps/cs/src/app/providers/app-provider.tsx +++ b/apps/cs/src/app/providers/app-provider.tsx @@ -1,5 +1,6 @@ "use client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { ConfirmProvider } from "@frontend-opensource/use-react-hooks"; import { AuthWrapper } from "@/entities/auth"; import { MenuWrapper } from "@/entities/menu"; @@ -7,15 +8,18 @@ import { StoreProvider } from "@/shared/providers"; import { AlertDialog } from "@/shared/ui/alert-dialog"; const AppProvider = ({ children }: Readonly<{ children: React.ReactNode }>) => { + const queryClient = new QueryClient(); return ( - - - - {children} - - - - + + + + + {children} + + + + + ); }; diff --git a/apps/cs/src/pages/site/ui/modify-page.tsx b/apps/cs/src/pages/site/ui/modify-page.tsx index 6b6c43c1f..cf2fa2200 100644 --- a/apps/cs/src/pages/site/ui/modify-page.tsx +++ b/apps/cs/src/pages/site/ui/modify-page.tsx @@ -45,12 +45,7 @@ function ModifyPage({ siteId }: { siteId: string }) { const { mutateAsync: deleteSite } = useMutation({ mutationFn: () => SiteApi.deleteSite(siteId), onSuccess: () => { - alert("삭제되었습니다.", () => { - queryClient.invalidateQueries({ - queryKey: [ENDPOINT.CMS_SERVICE.SITES], - }); - router.back(); - }); + alert("삭제되었습니다.", router.back); }, }); diff --git a/packages/common/components/src/provider/query-provider.tsx b/packages/common/components/src/provider/query-provider.tsx index 5781919f1..e06880255 100644 --- a/packages/common/components/src/provider/query-provider.tsx +++ b/packages/common/components/src/provider/query-provider.tsx @@ -2,13 +2,17 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; export function QueryProvider({ children, - client, + queryClient, }: { children: React.ReactNode; - client?: QueryClient; + queryClient?: QueryClient; }) { - const queryClient = client ?? new QueryClient(); - return ( - {children} - ); + if (!queryClient) { + const queryClient = new QueryClient(); + return ( + {children} + ); + } + + return <>{children}; } diff --git a/packages/common/components/src/ui/layout/base.tsx b/packages/common/components/src/ui/layout/base.tsx index c6a564c37..21d2ca7c6 100644 --- a/packages/common/components/src/ui/layout/base.tsx +++ b/packages/common/components/src/ui/layout/base.tsx @@ -19,7 +19,7 @@ export function BaseLayout({ handleMenuClick?: (item: MenuItem) => void; }) { return ( - +
From 84d4635a75fb5c7fc7f6d1e7350716446019edbe Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:34:13 +0900 Subject: [PATCH 043/102] =?UTF-8?q?feat:=20=EC=83=81=ED=99=A9=ED=8C=90=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/dashboard/page.tsx | 2 ++ apps/cs/src/pages/dashboard/index.ts | 1 + apps/cs/src/pages/dashboard/ui/dashboard-page.tsx | 5 +++++ 3 files changed, 8 insertions(+) create mode 100644 apps/cs/app/(with-base-layout)/dashboard/page.tsx create mode 100644 apps/cs/src/pages/dashboard/index.ts create mode 100644 apps/cs/src/pages/dashboard/ui/dashboard-page.tsx diff --git a/apps/cs/app/(with-base-layout)/dashboard/page.tsx b/apps/cs/app/(with-base-layout)/dashboard/page.tsx new file mode 100644 index 000000000..53d564ac6 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/dashboard/page.tsx @@ -0,0 +1,2 @@ +import { DashboardPage } from "@/pages/dashboard"; +export default DashboardPage; diff --git a/apps/cs/src/pages/dashboard/index.ts b/apps/cs/src/pages/dashboard/index.ts new file mode 100644 index 000000000..5b787c2e2 --- /dev/null +++ b/apps/cs/src/pages/dashboard/index.ts @@ -0,0 +1 @@ +export { DashboardPage } from "./ui/dashboard-page"; diff --git a/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx b/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx new file mode 100644 index 000000000..4dc693361 --- /dev/null +++ b/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx @@ -0,0 +1,5 @@ +function DashboardPage() { + return <>; +} + +export { DashboardPage }; From 623b1e985e6836195b9d45ec684a7cbbed98392b Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:34:47 +0900 Subject: [PATCH 044/102] =?UTF-8?q?refactor:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8?= =?UTF-8?q?=20=EC=99=84=EB=A3=8C=20=EC=8B=9C=20=ED=95=98=EC=9C=84=20?= =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=A6=AC?= =?UTF-8?q?=EB=A0=8C=EB=8D=94=EB=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/auth/login/ui/login-page.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/apps/cs/src/pages/auth/login/ui/login-page.tsx b/apps/cs/src/pages/auth/login/ui/login-page.tsx index 579b866f3..4700a691b 100644 --- a/apps/cs/src/pages/auth/login/ui/login-page.tsx +++ b/apps/cs/src/pages/auth/login/ui/login-page.tsx @@ -1,13 +1,11 @@ "use client"; import Image from "next/image"; -import { useRouter } from "next/navigation"; import { images } from "@common/assets"; import { LoginApi } from "@/entities/auth"; import "./login.css"; function LoginPage() { - const router = useRouter(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -16,7 +14,7 @@ function LoginPage() { const password = formData.get("password") as string; await LoginApi.login({ userId, password }); - router.push("/site"); + window.location.href = "/dashboard"; }; return ( From 483e0b319a43844aef03db8bd4543dc1ec600846 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:47:14 +0900 Subject: [PATCH 045/102] =?UTF-8?q?chore:=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=95=88=EB=82=B4=20=EB=AC=B8?= =?UTF-8?q?=EA=B5=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/image/ui/image-form.tsx | 31 +++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/apps/cs/src/pages/image/ui/image-form.tsx b/apps/cs/src/pages/image/ui/image-form.tsx index 81ab21726..58ecdd1b1 100644 --- a/apps/cs/src/pages/image/ui/image-form.tsx +++ b/apps/cs/src/pages/image/ui/image-form.tsx @@ -213,18 +213,27 @@ function ImageForm({ form, imgSn, handleSave, handleDelete }: ImageFormProps) { }); }} customPreview={ -