From 0aee493d324c6af0975a7be9768095acc737ed4d Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 13:09:05 +0900 Subject: [PATCH 01/18] =?UTF-8?q?feat:=20page=20title=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=83=9D=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 | 11 ++++++- apps/cs/src/shared/lib/use-menu-store.ts | 14 +++++++++ .../src/shared/providers/store-provider.tsx | 5 ++-- apps/cs/src/shared/store/index.ts | 2 ++ apps/cs/src/shared/store/menu-store.ts | 23 ++++++++++++++ apps/cs/src/shared/ui/page-layout.tsx | 13 ++++++-- packages/common/components/src/api/menu.ts | 1 + packages/common/components/src/ui/index.ts | 1 + .../common/components/src/ui/layout/base.tsx | 12 ++++---- .../common/components/src/ui/lnb/index.tsx | 6 ++-- .../components/src/ui/page-title/index.tsx | 30 +++++++++++++++++++ .../src/ui/page-title/page-title.stories.tsx | 17 +++++++++++ 12 files changed, 120 insertions(+), 15 deletions(-) create mode 100644 apps/cs/src/shared/lib/use-menu-store.ts create mode 100644 apps/cs/src/shared/store/menu-store.ts create mode 100644 packages/common/components/src/ui/page-title/index.tsx create mode 100644 packages/common/components/src/ui/page-title/page-title.stories.tsx diff --git a/apps/cs/app/(with-base-layout)/layout.tsx b/apps/cs/app/(with-base-layout)/layout.tsx index dfb290abd..e5f98a449 100644 --- a/apps/cs/app/(with-base-layout)/layout.tsx +++ b/apps/cs/app/(with-base-layout)/layout.tsx @@ -2,6 +2,9 @@ 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"; + export default function WithBaseLayout({ children, }: { @@ -9,9 +12,15 @@ export default function WithBaseLayout({ }) { const router = useRouter(); const pathname = usePathname(); + const { setCurrentMenuInfo } = useMenuStore((state) => state); + + const handleMenuClick = (item: MenuItem) => { + setCurrentMenuInfo(item); + router.push(item.urlPath); + }; return ( - + {children} ); diff --git a/apps/cs/src/shared/lib/use-menu-store.ts b/apps/cs/src/shared/lib/use-menu-store.ts new file mode 100644 index 000000000..66e41c76c --- /dev/null +++ b/apps/cs/src/shared/lib/use-menu-store.ts @@ -0,0 +1,14 @@ +import { useStore } from "zustand"; +import { useContext } from "react"; +import type { MenuStore } from "../store/menu-store"; +import { StoreContext } from "../providers/store-provider"; + +export const useMenuStore = (selector: (store: MenuStore) => T): T => { + const storeContext = useContext(StoreContext); + + if (!storeContext) { + throw new Error("useMenuStore must be used within StoreProvider"); + } + + return useStore(storeContext.menuStore, selector); +}; diff --git a/apps/cs/src/shared/providers/store-provider.tsx b/apps/cs/src/shared/providers/store-provider.tsx index 4814705ab..82783bdac 100644 --- a/apps/cs/src/shared/providers/store-provider.tsx +++ b/apps/cs/src/shared/providers/store-provider.tsx @@ -1,11 +1,11 @@ "use client"; import { createContext, useRef } from "react"; -import { createAlertStore } from "../store/alert-store"; -import { createUserStore } from "../store/user-store"; +import { createAlertStore, createMenuStore, createUserStore } from "../store"; type StoreContextType = { alertStore: ReturnType; + menuStore: ReturnType; userStore: ReturnType; }; @@ -18,6 +18,7 @@ export const StoreProvider = ({ children }: { children: React.ReactNode }) => { if (storeRef.current === null) { storeRef.current = { alertStore: createAlertStore(), + menuStore: createMenuStore(), userStore: createUserStore(), }; } diff --git a/apps/cs/src/shared/store/index.ts b/apps/cs/src/shared/store/index.ts index 4dae19ced..73c608f05 100644 --- a/apps/cs/src/shared/store/index.ts +++ b/apps/cs/src/shared/store/index.ts @@ -1 +1,3 @@ +export { createAlertStore } from "./alert-store"; +export { createMenuStore } from "./menu-store"; export { createUserStore, type IUserInfo } from "./user-store"; diff --git a/apps/cs/src/shared/store/menu-store.ts b/apps/cs/src/shared/store/menu-store.ts new file mode 100644 index 000000000..f0c6340c9 --- /dev/null +++ b/apps/cs/src/shared/store/menu-store.ts @@ -0,0 +1,23 @@ +import { createStore } from "zustand/vanilla"; +import type { MenuItem } from "@common/components/api"; + +export type MenuState = { + currentMenuInfo: MenuItem | null; +}; + +export type MenuActions = { + setCurrentMenuInfo: (menu: MenuItem) => void; +}; + +export type MenuStore = MenuState & MenuActions; + +export const defaultInitState: MenuState = { + currentMenuInfo: null, +}; + +export const createMenuStore = (initState: MenuState = defaultInitState) => { + return createStore()((set) => ({ + ...initState, + setCurrentMenuInfo: (menu: MenuItem) => set({ currentMenuInfo: menu }), + })); +}; diff --git a/apps/cs/src/shared/ui/page-layout.tsx b/apps/cs/src/shared/ui/page-layout.tsx index e4f11141b..3a05ce9b8 100644 --- a/apps/cs/src/shared/ui/page-layout.tsx +++ b/apps/cs/src/shared/ui/page-layout.tsx @@ -1,3 +1,6 @@ +import { PageTitle } from "@common/components"; +import { useMenuStore } from "../lib/use-menu-store"; + function PageLayout({ children, pageTitle, @@ -5,11 +8,15 @@ function PageLayout({ children: React.ReactNode; pageTitle: string; }) { + const { currentMenuInfo } = useMenuStore((state) => state); + + const menuPaths = currentMenuInfo?.menuPath + ?.split(">") + .map((path) => path.trim()); + return (
-

- {pageTitle} -

+ {children}
); diff --git a/packages/common/components/src/api/menu.ts b/packages/common/components/src/api/menu.ts index e9899c111..d109a9812 100644 --- a/packages/common/components/src/api/menu.ts +++ b/packages/common/components/src/api/menu.ts @@ -13,6 +13,7 @@ export interface MenuItem { urlPath: string; menuLnkgTypeCd: string; menuLnkgSn: number; + menuPath: string; qrcd: string; useYn: string; otptYn: string; diff --git a/packages/common/components/src/ui/index.ts b/packages/common/components/src/ui/index.ts index 650eb30e1..dde5f5879 100644 --- a/packages/common/components/src/ui/index.ts +++ b/packages/common/components/src/ui/index.ts @@ -13,6 +13,7 @@ export * from "./input"; export * from "./label"; export * from "./layout"; export * from "./loading"; +export * from "./page-title"; export * from "./popover"; export * from "./radio-group"; export * from "./select"; diff --git a/packages/common/components/src/ui/layout/base.tsx b/packages/common/components/src/ui/layout/base.tsx index b48e2d9d9..c6a564c37 100644 --- a/packages/common/components/src/ui/layout/base.tsx +++ b/packages/common/components/src/ui/layout/base.tsx @@ -1,22 +1,22 @@ +import type { QueryClient } from "@tanstack/react-query"; +import type { MenuItem } from "../../api"; import { QueryProvider } from "../../provider/query-provider"; -import { SidebarProvider } from "../sidebar"; import { LNB } from "../lnb"; import { Header } from "../header"; import { Footer } from "./footer"; - +import { SidebarProvider } from "../sidebar"; import "./common.css"; -import type { QueryClient } from "@tanstack/react-query"; export function BaseLayout({ children, queryClient, currentPath, - routeHandler, + handleMenuClick, }: { children: React.ReactNode; queryClient?: QueryClient; currentPath?: string; - routeHandler?: (path: string) => void; + handleMenuClick?: (item: MenuItem) => void; }) { return ( @@ -24,7 +24,7 @@ export function BaseLayout({
- +
{children} diff --git a/packages/common/components/src/ui/lnb/index.tsx b/packages/common/components/src/ui/lnb/index.tsx index 197a44a9e..ef1d39a79 100644 --- a/packages/common/components/src/ui/lnb/index.tsx +++ b/packages/common/components/src/ui/lnb/index.tsx @@ -22,11 +22,11 @@ import { getParentIds } from "./lib"; export function LNB({ viewOnly, currentPath, - routeHandler, + handleMenuClick, }: { viewOnly?: boolean; currentPath?: string; - routeHandler?: (path: string) => void; + handleMenuClick?: (item: MenuItem) => void; }) { const { data: menuList } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES], @@ -45,7 +45,7 @@ export function LNB({ const handleClick = () => { setCookie("menu-code-key", item.menuCd.toString()); - routeHandler?.(item.urlPath); + handleMenuClick?.(item); }; if (item.children) { diff --git a/packages/common/components/src/ui/page-title/index.tsx b/packages/common/components/src/ui/page-title/index.tsx new file mode 100644 index 000000000..69796a428 --- /dev/null +++ b/packages/common/components/src/ui/page-title/index.tsx @@ -0,0 +1,30 @@ +import React from "react"; + +const PageTitle = ({ + title, + menuPaths, +}: { + title: string; + menuPaths: string[]; +}) => { + return ( +
+

{title}

+ {menuPaths.length && ( +
+ + {menuPaths?.map((path, i) => ( + + {i !== 0 && ( + + )} + {path} + + ))} +
+ )} +
+ ); +}; + +export { PageTitle }; diff --git a/packages/common/components/src/ui/page-title/page-title.stories.tsx b/packages/common/components/src/ui/page-title/page-title.stories.tsx new file mode 100644 index 000000000..127e2a0fa --- /dev/null +++ b/packages/common/components/src/ui/page-title/page-title.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { PageTitle } from "."; + +const meta: Meta = { + title: "UI/PageTitle", +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + render: () => ( +
+ +
+ ), +}; -- 2.47.1.windows.1 From 7216d6a74effa59f69a8d84f86dae5dfd25c6dd6 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 13:12:42 +0900 Subject: [PATCH 02/18] =?UTF-8?q?feat:=20data=20table=EC=97=90=20variant?= =?UTF-8?q?=20props=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/common/components/src/ui/data-table/index.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/common/components/src/ui/data-table/index.tsx b/packages/common/components/src/ui/data-table/index.tsx index 8dc30c701..c2c8c3591 100644 --- a/packages/common/components/src/ui/data-table/index.tsx +++ b/packages/common/components/src/ui/data-table/index.tsx @@ -38,12 +38,17 @@ function THead({ table }: { table: TableType }) { type TableProps = { table: TableType; + variant?: "default" | "secondary" | "line"; onRowClick?: (row: T) => void; }; -function DataTable({ table, onRowClick }: TableProps) { +function DataTable({ + table, + variant = "default", + onRowClick, +}: TableProps) { return ( - +
{table.getRowModel().rows.map((row) => ( -- 2.47.1.windows.1 From c3fdf92f5c6dd42b88e3638bb4833c407683d59c Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 13:14:18 +0900 Subject: [PATCH 03/18] =?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 ? "사이트 수정" : "사이트 등록"}

-- 2.47.1.windows.1 From 8117ca5c2cc8c352222a71c67fe52cafb199d3c6 Mon Sep 17 00:00:00 2001 From: bicochan Date: Mon, 31 Mar 2025 15:10:38 +0900 Subject: [PATCH 04/18] =?UTF-8?q?design:=20=ED=85=8C=EC=9D=B4=EB=B8=94=20?= =?UTF-8?q?=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 05/18] =?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'} -- 2.47.1.windows.1 From f2954e6ebd472472be74811e5df1a5104feddef1 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 1 Apr 2025 14:25:17 +0900 Subject: [PATCH 08/18] =?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 })} + /> + + +
+
+
+

사이트 목록

+
-
-
-

사이트 목록

- -
-
- -
+
+
- +
); } -- 2.47.1.windows.1 From a912deeefb084a51d84ee93427f1a4a0ba40201b Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 1 Apr 2025 15:40:38 +0900 Subject: [PATCH 09/18] =?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; -- 2.47.1.windows.1 From 60464b92b0957eb3ee1a9cb178e2a790203a2d97 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 1 Apr 2025 17:43:20 +0900 Subject: [PATCH 10/18] =?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 ( -- 2.47.1.windows.1 From a9739bdba594f5888544bccfef91c5aedc846eaf Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 14:49:35 +0900 Subject: [PATCH 14/18] =?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); } }); }; -- 2.47.1.windows.1 From 4e4bd820097f7ca665cff8a8ba155146147896c8 Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 15:33:51 +0900 Subject: [PATCH 15/18] =?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}; }; -- 2.47.1.windows.1 From 0a51ac3f799dc6f5612b851e7d330fcff49fff7b Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:19:16 +0900 Subject: [PATCH 16/18] =?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 ( - +
-- 2.47.1.windows.1 From 84d4635a75fb5c7fc7f6d1e7350716446019edbe Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:34:13 +0900 Subject: [PATCH 17/18] =?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 }; -- 2.47.1.windows.1 From 623b1e985e6836195b9d45ec684a7cbbed98392b Mon Sep 17 00:00:00 2001 From: bicochan Date: Wed, 2 Apr 2025 17:34:47 +0900 Subject: [PATCH 18/18] =?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 ( -- 2.47.1.windows.1