diff --git a/README.md b/README.md index ba078fb13..e90d891aa 100644 --- a/README.md +++ b/README.md @@ -110,15 +110,15 @@ const nextConfig: NextConfig = { { type: "header", key: "referer", - value: "(.*)/pst(.*)", + value: "(.*)/cms(.*)", }, ], }, ], afterFiles: [ { - source: "/pst/:path*", - destination: `${subDomain}/pst/:path*`, + source: "/cms/:path*", + destination: `${subDomain}/cms/:path*`, }, ], fallback: [], @@ -127,6 +127,7 @@ const nextConfig: NextConfig = { }; export default nextConfig; + ``` ### 6. globals.css(또는 전역 css) 수정 @@ -150,6 +151,26 @@ rm -rf node_modules pnpm-lock.yaml pnpm install ``` +### 8. 로그아웃 처리 가이드 + + * 참고 파일 + - apps/cs/app/api/auth/logout/route.ts + - packages/common/components/src/ui/header/index.tsx (55-65 line) + +각 서비스 폴더에 ‘/app/api/auth/logout/route.ts’ 생성해야 합니다(코드는 cs에서 확인). + +** 로그아웃 처리되면 ‘/auth/login’ 페이지로 이동됩니다. + +### 9. 'SITE_ID’ 환경변수를 쿠키로 대체하는 가이드(공통 모듈 사용하기위한 필수 사항) + + * 참고 파일: apps/cs/src/shared/api/client.ts (쿠키 저장 처리) + apps/cs/src/shared/lib/cookie.ts (쿠키 공통 모듈) + + `SITE_ID` 환경변수를 처음 사용하는 파일 또는 최상단 클라이언트 컴포넌트에서 “site-id-key” 쿠키에 `SITE_ID` 저장 + + + + ## 기술 스택 - React(^18) diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsfaq/[bbsId]/[pstNo]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsfaq/[bbsId]/[pstNo]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsfaq/[bbsId]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsfaq/[bbsId]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsfaq/[bbsId]/register/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsfaq/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsfaq/[bbsId]/register/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/[pstNo]/modify/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/[pstNo]/modify/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/[pstNo]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/[pstNo]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/register/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsimage/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsimage/[bbsId]/register/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/[pstNo]/modify/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/[pstNo]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/[pstNo]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/register/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsmovie/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsmovie/[bbsId]/register/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/[pstNo]/answer/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/[pstNo]/modify/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/[pstNo]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/[pstNo]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/register/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnormal/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnormal/[bbsId]/register/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/[pstNo]/modify/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/[pstNo]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/[pstNo]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/[pstNo]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx b/apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/register/page.tsx similarity index 100% rename from apps/common/features/app/(with-base-layout)/pst/bbsnotice/[bbsId]/register/page.tsx rename to apps/common/features/app/(with-base-layout)/(pst)/bbsnotice/[bbsId]/register/page.tsx diff --git a/apps/common/features/app/(with-base-layout)/(pst)/layout.tsx b/apps/common/features/app/(with-base-layout)/(pst)/layout.tsx new file mode 100644 index 000000000..f181eb64f --- /dev/null +++ b/apps/common/features/app/(with-base-layout)/(pst)/layout.tsx @@ -0,0 +1,30 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { MenuApi } from "@/entities/menu"; +import { ENDPOINT } from "@/shared/config"; +import { getCookie, useMenuStore } from "@/shared/lib"; +import { PageLayout } from "@/shared/ui/page-layout"; + +export default function PostLayout({ + children, +}: { + children: React.ReactNode; +}) { + const menuCd = getCookie("menu-code-key"); + const bbsInfo = useMenuStore((state) => state.bbsInfo); + + const { data } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.MENUS, menuCd], + queryFn: () => MenuApi.getMenuInfo(menuCd!), + enabled: !!menuCd, + }); + + const menuPaths = data?.menuPath?.split(">").map((path) => path.trim()); + + return ( + + {children} + + ); +} diff --git a/apps/common/features/app/(with-base-layout)/layout.tsx b/apps/common/features/app/(with-base-layout)/layout.tsx index 970ccce5e..5bbd7d024 100644 --- a/apps/common/features/app/(with-base-layout)/layout.tsx +++ b/apps/common/features/app/(with-base-layout)/layout.tsx @@ -3,7 +3,7 @@ import { usePathname, useRouter } from "next/navigation"; import { useQueryClient } from "@tanstack/react-query"; import { BaseLayout } from "@common/components"; -import type { MenuItem } from "@/entities/menu"; +import type { MenuRole } from "@/entities/menu"; import { useMenuStore } from "@/shared/lib"; export default function WithBaseLayout({ @@ -17,7 +17,7 @@ export default function WithBaseLayout({ const setCurrentMenuInfo = useMenuStore((state) => state.setCurrentMenuInfo); - const handleMenuClick = (item: MenuItem) => { + const handleMenuClick = (item: MenuRole) => { setCurrentMenuInfo(item); router.push(item.urlPath); }; diff --git a/apps/common/features/app/(with-base-layout)/pst/layout.tsx b/apps/common/features/app/(with-base-layout)/pst/layout.tsx deleted file mode 100644 index 3f700d076..000000000 --- a/apps/common/features/app/(with-base-layout)/pst/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import { useMenuStore } from "@/shared/lib"; -import { PageLayout } from "@/shared/ui/page-layout"; - -export default function PostLayout({ - children, -}: { - children: React.ReactNode; -}) { - const bbsInfo = useMenuStore((state) => state.bbsInfo); - - return ( - - {children} - - ); -} diff --git a/apps/common/features/app/api/auth/logout/route.ts b/apps/common/features/app/api/auth/logout/route.ts new file mode 100644 index 000000000..fdcc9df7c --- /dev/null +++ b/apps/common/features/app/api/auth/logout/route.ts @@ -0,0 +1,17 @@ +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; +import { + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + REFRESH_TOKEN, +} from "@/shared/config"; + +export async function POST() { + const cookieStore = await cookies(); + cookieStore.delete(ACCESS_TOKEN); + cookieStore.delete(REFRESH_TOKEN); + cookieStore.delete(AUTH_USER_ID); + cookieStore.delete(CLAIM_NAME); + return NextResponse.json({ message: "Logged out" }, { status: 200 }); +} diff --git a/apps/common/features/app/api/proxy/[...url]/route.ts b/apps/common/features/app/api/proxy/[...url]/route.ts index d3092c5c1..5d81c93fb 100644 --- a/apps/common/features/app/api/proxy/[...url]/route.ts +++ b/apps/common/features/app/api/proxy/[...url]/route.ts @@ -4,7 +4,6 @@ import { CLAIM_NAME, REFRESH_TOKEN, SERVER_API_URL, - SITE_ID, } from "@/shared/config"; import { cookies } from "next/headers"; import { NextResponse } from "next/server"; @@ -15,9 +14,10 @@ async function handler( method: "GET" | "POST" | "PUT" | "DELETE", ) { const cookieStore = await cookies(); + const siteId = cookieStore.get("site-id-key")?.value ?? ""; const headers: Record = { "Content-Type": "application/json", - "X-Site-Id": SITE_ID, + "X-Site-Id": siteId, }; let body = {}; const isRefreshHandler = params.url.includes("refresh"); diff --git a/apps/common/features/next.config.ts b/apps/common/features/next.config.ts index cb651cdc0..34965cf32 100644 --- a/apps/common/features/next.config.ts +++ b/apps/common/features/next.config.ts @@ -1,5 +1,16 @@ import type { NextConfig } from "next"; -const nextConfig: NextConfig = {}; +const nextConfig: NextConfig = { + async rewrites() { + return { + afterFiles: [ + { + source: "/cms/:path*", + destination: "/:path*", + }, + ], + }; + }, +}; export default nextConfig; diff --git a/apps/common/features/src/entities/menu-authority/index.ts b/apps/common/features/src/entities/menu-authority/index.ts index baf6e6085..da36accae 100644 --- a/apps/common/features/src/entities/menu-authority/index.ts +++ b/apps/common/features/src/entities/menu-authority/index.ts @@ -1,3 +1,4 @@ export { MenuAuthorityApi } from "./api/menu-authority-service"; export { type PageMenuAuthority } from "./model/menu-authority-interface"; +export { AuthorityButton } from "./ui/authority-button"; export { ManagementAuthorityDialog } from "./ui/management-authority-dialog"; diff --git a/apps/common/features/src/entities/menu-authority/ui/authority-button.tsx b/apps/common/features/src/entities/menu-authority/ui/authority-button.tsx new file mode 100644 index 000000000..257ab6e5d --- /dev/null +++ b/apps/common/features/src/entities/menu-authority/ui/authority-button.tsx @@ -0,0 +1,26 @@ +import { useMenuStore } from "@/shared/lib"; +import { Button, cn, type ButtonProps } from "@common/components"; +import type { PageMenuAuthority } from "../model/menu-authority-interface"; + +type AuthorityButtonProps = { + authority: keyof PageMenuAuthority; +} & ButtonProps; + +function AuthorityButton({ + authority, + className, + children, + ...props +}: AuthorityButtonProps) { + const pageMenuAuthority = useMenuStore((state) => state.pageMenuAuthority); + + const isAuthority = pageMenuAuthority?.[authority] === "Y"; + + return ( + + ); +} + +export { AuthorityButton }; diff --git a/apps/common/features/src/entities/menu/api/menu-service.ts b/apps/common/features/src/entities/menu/api/menu-service.ts index 4424f8d37..055bc9371 100644 --- a/apps/common/features/src/entities/menu/api/menu-service.ts +++ b/apps/common/features/src/entities/menu/api/menu-service.ts @@ -1,14 +1,21 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; -import type { MenuItem, MenuService } from "../model/menu-interface"; +import type { MenuInfo, MenuRole, MenuService } from "../model/menu-interface"; export class MenuApiService implements MenuService { - async getMenuRoles(siteId: string): Promise { - const response = await client.get( + async getMenuRoles(siteId: string): Promise { + const response = await client.get( `${ENDPOINT.CMS_SERVICE.MENU_ROLES}/${siteId}`, ); return response.data; } + + async getMenuInfo(menuCd: string): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.MENUS}/${menuCd}`, + ); + return response.data; + } } export const MenuApi = new MenuApiService(); diff --git a/apps/common/features/src/entities/menu/index.ts b/apps/common/features/src/entities/menu/index.ts index 04f63e38b..458867586 100644 --- a/apps/common/features/src/entities/menu/index.ts +++ b/apps/common/features/src/entities/menu/index.ts @@ -1,2 +1,3 @@ +export { MenuApi } from "./api/menu-service"; export { MenuWrapper } from "./lib/menu-wrapper"; -export type { MenuItem } from "./model/menu-interface"; +export type { MenuRole } from "./model/menu-interface"; diff --git a/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx b/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx index 2855e5ef0..e3007f8be 100644 --- a/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx +++ b/apps/common/features/src/entities/menu/lib/menu-wrapper.tsx @@ -4,10 +4,11 @@ import { useEffect } from "react"; import { usePathname, useRouter } from "next/navigation"; import { MenuAuthorityApi } from "@/entities/menu-authority/@x/menu"; import { getCookie, useAlertStore, useMenuStore } from "@/shared/lib"; -import { SITE_ID } from "@/shared/config"; import { MenuApi } from "../api/menu-service"; import { getCurrentMenu } from "./utils"; +const siteId = getCookie("site-id-key") ?? ""; + const MenuWrapper = ({ children }: { children: React.ReactNode }) => { const router = useRouter(); const pathname = usePathname(); @@ -17,7 +18,7 @@ const MenuWrapper = ({ children }: { children: React.ReactNode }) => { useMenuStore((state) => state); const getCurrentMenuInfo = async () => { - const menuRoles = await MenuApi.getMenuRoles(SITE_ID); + const menuRoles = await MenuApi.getMenuRoles(siteId); if (!menuRoles) return; return getCurrentMenu(menuRoles, pathname ?? ""); }; diff --git a/apps/common/features/src/entities/menu/lib/utils.ts b/apps/common/features/src/entities/menu/lib/utils.ts index 1cf4514d9..bb2fadb88 100644 --- a/apps/common/features/src/entities/menu/lib/utils.ts +++ b/apps/common/features/src/entities/menu/lib/utils.ts @@ -1,9 +1,9 @@ -import type { MenuItem } from "../model/menu-interface"; +import type { MenuRole } from "../model/menu-interface"; export const getCurrentMenu = ( - menus: MenuItem[], + menus: MenuRole[], pathname: string, -): MenuItem | null => { +): MenuRole | null => { for (const menu of menus) { if ( menu.urlPath !== "" && diff --git a/apps/common/features/src/entities/menu/model/menu-interface.ts b/apps/common/features/src/entities/menu/model/menu-interface.ts index 12c7d007a..885e02745 100644 --- a/apps/common/features/src/entities/menu/model/menu-interface.ts +++ b/apps/common/features/src/entities/menu/model/menu-interface.ts @@ -1,11 +1,8 @@ -export interface MenuItem { +interface Menu { menuCd: number; menuKornNm: string; menuEngNm: string; - parentId: number | null; sortSeq: number; - iconNm?: string; - children: MenuItem[] | null; levelNo: number; urlPath: string; menuLnkgTypeCd: string; @@ -15,9 +12,30 @@ export interface MenuItem { useYn: string; otptYn: string; newWndwYn: string; +} + +export interface MenuRole extends Menu { + parentId: number | null; + iconNm?: string; + children: MenuRole[] | null; requiredUrlPath: boolean; } -export interface MenuService { - getMenuRoles(siteId: string): Promise; +export interface MenuInfo extends Menu { + siteId: string; + menuSubKornNm: string; + menuSubEngNm: string; + iconName: string; + menuLnkgTypeCdNm: string; + prntMenuCd: number; + evlUseYn: string; + menuLnkgSnNm: string; + menuPicUserUnqId: string; + menuPicUserUnqIdNm: string; + menuPicDsgnYmd: string; +} + +export interface MenuService { + getMenuRoles(siteId: string): Promise; + getMenuInfo(menuCd: string): Promise; } diff --git a/apps/common/features/src/entities/post/api/post-service.ts b/apps/common/features/src/entities/post/api/post-service.ts index 1e5ac5146..c7bd0c3af 100644 --- a/apps/common/features/src/entities/post/api/post-service.ts +++ b/apps/common/features/src/entities/post/api/post-service.ts @@ -31,6 +31,16 @@ export class PostApiService implements PostService { return response.data; } + public async getPostEditInfo( + bbsId: number, + pstNo: number, + ): Promise { + const response = await client.get( + `${ENDPOINT.BOARD_SERVICE.POSTS_USER}/edit/${bbsId}/${pstNo}`, + ); + return response.data; + } + public async createPost(data: PostFormData): Promise { const validatedData = postFormSchema.parse(data); const response = await client.post( diff --git a/apps/common/features/src/entities/post/model/post-interface.ts b/apps/common/features/src/entities/post/model/post-interface.ts index 11778002f..de8c44891 100644 --- a/apps/common/features/src/entities/post/model/post-interface.ts +++ b/apps/common/features/src/entities/post/model/post-interface.ts @@ -9,6 +9,7 @@ export interface PostParams extends PageableParams { prdPstgYn?: string; siteId?: string; bbsTypeCd?: string; + ansUseYn?: string; } export interface Post { @@ -77,6 +78,7 @@ export type PostUpdateFormData = z.infer; export interface PostService { getPostList(bbsId: number, params: PostParams): Promise; getPostInfo(bbsId: number, pstNo: number): Promise; + getPostEditInfo(bbsId: number, pstNo: number): Promise; createPost(data: PostFormData): Promise; updatePost( bbsId: number, diff --git a/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx index 6987cb0bc..5c25d21e0 100644 --- a/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(pst)/faq/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx index d292fea42..a68d24a86 100644 --- a/apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(pst)/image/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx index f9fc11f3d..815112523 100644 --- a/apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(pst)/movie/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx index c47b1999c..9c2ef6f5f 100644 --- a/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(pst)/normal/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx b/apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx index 0926a2d9d..e6feb6952 100644 --- a/apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx +++ b/apps/common/features/src/pages/(pst)/notice/model/notice-table-columns.tsx @@ -8,6 +8,12 @@ export const columns: ColumnDef[] = [ { accessorKey: "rowNo", header: "NO", + cell: ({ row, table }) => { + const totalRows = table.getRowCount(); + const currentPage = table.getState().pagination.pageIndex; + const pageSize = table.getState().pagination.pageSize; + return totalRows - (currentPage * pageSize + row.index); + }, }, { accessorKey: "pstTtl", diff --git a/apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx b/apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx index 7a46a7d6b..0e04d616a 100644 --- a/apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx +++ b/apps/common/features/src/pages/(pst)/notice/ui/modify-page.tsx @@ -20,7 +20,7 @@ function ModifyPage({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { data: postInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pstNo], - queryFn: () => PostApi.getPostInfo(parseInt(bbsId), parseInt(pstNo)), + queryFn: () => PostApi.getPostEditInfo(parseInt(bbsId), parseInt(pstNo)), }); const { mutateAsync: updatePost } = useMutation({ diff --git a/apps/common/features/src/shared/api/client.ts b/apps/common/features/src/shared/api/client.ts index 709bb44a5..9c41892c0 100644 --- a/apps/common/features/src/shared/api/client.ts +++ b/apps/common/features/src/shared/api/client.ts @@ -1,8 +1,10 @@ import { Client } from "@common/network"; import { BASE_URL } from "../config/api"; -import { SITE_ID } from "../config/env"; +import { getCookie } from "../lib"; + +const siteId = getCookie("site-id-key") ?? ""; export const client = Client.getInstance({ baseURL: BASE_URL, - siteId: SITE_ID, + siteId, }); diff --git a/apps/common/features/src/shared/config/env.ts b/apps/common/features/src/shared/config/env.ts index e8d3161eb..a68868d6b 100644 --- a/apps/common/features/src/shared/config/env.ts +++ b/apps/common/features/src/shared/config/env.ts @@ -2,7 +2,6 @@ export const PORT = process.env.NEXT_PUBLIC_PORT || "13000"; export const PROXY_HOST = process.env.NEXT_PUBLIC_PROXY_HOST || `http://localhost:${PORT}`; export const SERVER_API_URL = process.env.NEXT_PUBLIC_SERVER_API_URL; -export const SITE_ID = process.env.NEXT_PUBLIC_SITE_ID || "CS"; export const CLAIM_NAME = process.env.NEXT_PUBLIC_CLAIM_NAME || "Authorization"; export const ACCESS_TOKEN = process.env.NEXT_PUBLIC_ACCESS_TOKEN || "access-token"; diff --git a/apps/common/features/src/shared/model/api-interface.ts b/apps/common/features/src/shared/model/api-interface.ts index 133c0c401..8218bfb0d 100644 --- a/apps/common/features/src/shared/model/api-interface.ts +++ b/apps/common/features/src/shared/model/api-interface.ts @@ -30,5 +30,5 @@ export interface PageableList { export interface PageableParams { page?: number; size?: number; - sort?: string[]; + sort?: string; } diff --git a/apps/common/features/src/widgets/post/model/post-table-columns.tsx b/apps/common/features/src/widgets/post/model/post-table-columns.tsx index 973ce9315..2e4f14d2f 100644 --- a/apps/common/features/src/widgets/post/model/post-table-columns.tsx +++ b/apps/common/features/src/widgets/post/model/post-table-columns.tsx @@ -29,7 +29,7 @@ export const columns: ColumnDef[] = [ { accessorKey: "rowNo", header: "NO", - cell: ({ row }) => { + cell: ({ row, table }) => { if (row.original.isUpndFixPost) { return ( [] = [ ); } - return row.original.rowNo; + const totalRows = table.getRowCount(); + const currentPage = table.getState().pagination.pageIndex; + const pageSize = table.getState().pagination.pageSize; + return totalRows - (currentPage * pageSize + row.index); }, }, { accessorKey: "pstTtl", header: "제목", cell: ({ row }) => { - const { ansPstn, pstTtl, inqNocs, cmntCnt } = row.original; + const { ansPstn = 0, pstTtl, inqNocs, cmntCnt } = row.original; + const indentPadding = ansPstn > 0 ? (ansPstn - 1) * 20 : 0; return (
{ansPstn > 0 && (
diff --git a/apps/common/features/src/widgets/post/ui/post-action.tsx b/apps/common/features/src/widgets/post/ui/post-action.tsx index 4eaa960cb..afc614bc1 100644 --- a/apps/common/features/src/widgets/post/ui/post-action.tsx +++ b/apps/common/features/src/widgets/post/ui/post-action.tsx @@ -1,22 +1,25 @@ import { useRouter, usePathname } from "next/navigation"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { Button } from "@common/components"; +import { AuthorityButton } from "@/entities/menu-authority"; import { useMenuStore, useUserStore } from "@/shared/lib"; type PostActionProps = { - bbsId: string; - pstNo: string; + registerId?: string; children: React.ReactNode; handleDelete: () => Promise; }; -function PostAction({ children, handleDelete }: PostActionProps) { +function PostAction({ registerId, children, handleDelete }: PostActionProps) { const router = useRouter(); const pathname = usePathname(); const { confirm } = useConfirm(); const bbsInfo = useMenuStore((state) => state.bbsInfo); const userInfo = useUserStore((state) => state.userInfo); + const actionButtonAuthrt = + userInfo?.adminRoleYn === "Y" || userInfo?.userUnqId === registerId; + const onDelete = async () => { if (await confirm("삭제하시겠습니까?")) handleDelete(); }; @@ -46,15 +49,19 @@ function PostAction({ children, handleDelete }: PostActionProps) { > 목록 - + {actionButtonAuthrt && ( + router.push(`${pathname}/modify`)} + > + 수정 + + )} {bbsInfo?.ansUseYn === "Y" && ( - + + )} + {actionButtonAuthrt && ( + + 삭제 + )} -
); diff --git a/apps/common/features/src/widgets/post/ui/post-page.tsx b/apps/common/features/src/widgets/post/ui/post-page.tsx index f53af0ee8..5a459835f 100644 --- a/apps/common/features/src/widgets/post/ui/post-page.tsx +++ b/apps/common/features/src/widgets/post/ui/post-page.tsx @@ -42,6 +42,7 @@ function PostPage({ bbsId }: { bbsId: string }) { const { bbsInfo, pageMenuAuthority } = useMenuStore((state) => state); const thumbRefs = useRef>(new Map()); + const [tableSort, setTableSort] = useState(""); const [postParams, setPostParams] = useState({ keywordType: "title", keyword: "", @@ -77,12 +78,14 @@ function PostPage({ bbsId }: { bbsId: string }) { isLoading, refetch, } = useQuery({ - queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pagination], + queryKey: [ENDPOINT.BOARD_SERVICE.POSTS_USER, bbsId, pagination, tableSort], queryFn: () => PostApi.getPostList(parseInt(bbsId), { ...postParams, + ansUseYn: bbsInfo?.ansUseYn, page: pagination.pageIndex, size: pagination.pageSize, + sort: tableSort, }), }); @@ -200,6 +203,24 @@ function PostPage({ bbsId }: { bbsId: string }) { ))} +
diff --git a/apps/common/features/src/widgets/post/ui/post.tsx b/apps/common/features/src/widgets/post/ui/post.tsx index bd8db7c21..8033938cf 100644 --- a/apps/common/features/src/widgets/post/ui/post.tsx +++ b/apps/common/features/src/widgets/post/ui/post.tsx @@ -125,7 +125,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { return ( <> - +

{postData?.pstTtl} diff --git a/apps/cs/app/(with-base-layout)/apilog/page.tsx b/apps/cs/app/(with-base-layout)/apilog/page.tsx new file mode 100644 index 000000000..374ebf4ae --- /dev/null +++ b/apps/cs/app/(with-base-layout)/apilog/page.tsx @@ -0,0 +1,2 @@ +import { ApiLogPage } from "@/pages/apilog"; +export default ApiLogPage; diff --git a/apps/cs/app/(with-base-layout)/privacylog/page.tsx b/apps/cs/app/(with-base-layout)/privacylog/page.tsx new file mode 100644 index 000000000..4b543f360 --- /dev/null +++ b/apps/cs/app/(with-base-layout)/privacylog/page.tsx @@ -0,0 +1,2 @@ +import { PrivacyLogPage } from "@/pages/privacylog"; +export default PrivacyLogPage; diff --git a/apps/cs/app/api/auth/logout/route.ts b/apps/cs/app/api/auth/logout/route.ts new file mode 100644 index 000000000..fdcc9df7c --- /dev/null +++ b/apps/cs/app/api/auth/logout/route.ts @@ -0,0 +1,17 @@ +import { cookies } from "next/headers"; +import { NextResponse } from "next/server"; +import { + ACCESS_TOKEN, + AUTH_USER_ID, + CLAIM_NAME, + REFRESH_TOKEN, +} from "@/shared/config"; + +export async function POST() { + const cookieStore = await cookies(); + cookieStore.delete(ACCESS_TOKEN); + cookieStore.delete(REFRESH_TOKEN); + cookieStore.delete(AUTH_USER_ID); + cookieStore.delete(CLAIM_NAME); + return NextResponse.json({ message: "Logged out" }, { status: 200 }); +} diff --git a/apps/cs/app/api/getClientIp/route.ts b/apps/cs/app/api/getClientIp/route.ts deleted file mode 100644 index ec9ff72d5..000000000 --- a/apps/cs/app/api/getClientIp/route.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { NextRequest, NextResponse } from "next/server"; - -export async function GET(request: NextRequest) { - const ipAddress = - request.headers.get("x-forwarded-for")?.split(",")[0] || "unknown"; - - const ipv4Regex = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/; - const match = ipAddress.match(ipv4Regex); - - const normalizedIp = match ? match[1] : ipAddress; - - return NextResponse.json({ - originalIp: ipAddress, - normalizedIp: normalizedIp, - }); -} diff --git a/apps/cs/middleware.ts b/apps/cs/middleware.ts index 6bdfd9545..61d38c071 100644 --- a/apps/cs/middleware.ts +++ b/apps/cs/middleware.ts @@ -7,11 +7,16 @@ const serverApiUrl = export function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; const searchParams = req.nextUrl.searchParams; + if (pathname.startsWith("/server")) { - const queryParams = searchParams.toString(); + + const decodedParams = Array.from(searchParams.entries()) + .map(([key, value]) => `${key}=${decodeURIComponent(value)}`) + .join("&"); + const newUrl = `${serverApiUrl}${pathname.replace("/server", "")}${ - queryParams ? `?${queryParams}` : "" + decodedParams ? `?${decodedParams}` : "" }`; return NextResponse.rewrite(new URL(newUrl)); } diff --git a/apps/cs/next.config.ts b/apps/cs/next.config.ts index 3b3038b09..ad1ec7949 100644 --- a/apps/cs/next.config.ts +++ b/apps/cs/next.config.ts @@ -15,15 +15,15 @@ const nextConfig: NextConfig = { { type: "header", key: "referer", - value: "(.*)/pst(.*)", + value: "(.*)/cms(.*)", }, ], }, ], afterFiles: [ { - source: "/pst/:path*", - destination: `${subDomain}/pst/:path*`, + source: "/cms/:path*", + destination: `${subDomain}/cms/:path*`, }, ], fallback: [], diff --git a/apps/cs/src/entities/apilog/api/apilog-service.ts b/apps/cs/src/entities/apilog/api/apilog-service.ts new file mode 100644 index 000000000..d7167950c --- /dev/null +++ b/apps/cs/src/entities/apilog/api/apilog-service.ts @@ -0,0 +1,20 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + ApiLogList, + ApiLogParams, + ApiLogService, +} from "../model/apilog-interface"; + +export class ApiLogApiService implements ApiLogService { + public async getApiLogList(params: ApiLogParams): Promise { + const response = await client.get( + `${ENDPOINT.USER_SERVICE.APILOG}`, + { params }, + ); + return response.data; + } + +} + +export const ApiLogApi = new ApiLogApiService(); diff --git a/apps/cs/src/entities/apilog/index.ts b/apps/cs/src/entities/apilog/index.ts new file mode 100644 index 000000000..38733364f --- /dev/null +++ b/apps/cs/src/entities/apilog/index.ts @@ -0,0 +1,6 @@ +export { ApiLogApi } from "./api/apilog-service"; +export type { + ApiLog, + ApiLogParams, +} from "./model/apilog-interface"; + diff --git a/apps/cs/src/entities/apilog/model/apilog-interface.ts b/apps/cs/src/entities/apilog/model/apilog-interface.ts new file mode 100644 index 000000000..f00e583ad --- /dev/null +++ b/apps/cs/src/entities/apilog/model/apilog-interface.ts @@ -0,0 +1,30 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface ApiLogParams extends PageableParams { + siteId?: string; + userId?: string; + userNm?: string; + apiHttpPmhd?: string; + dmndUrl?: string; + ipAddr?: string; + dmndDtStart?: string; + dmndDtEnd?: string; +} + +export interface ApiLog { + logId: string; + siteId: string; + siteNm: string; + userId: string; + userNm: string; + apiHttpPmhd: string; + dmndUrl: string; + ipAddr: string; + dmndDt: string; +} + +export type ApiLogList = PageableList; + +export interface ApiLogService { + getApiLogList: (params: ApiLogParams) => Promise; +} diff --git a/apps/cs/src/entities/board/model/board-interface.ts b/apps/cs/src/entities/board/model/board-interface.ts index 60ed9f743..8b7859223 100644 --- a/apps/cs/src/entities/board/model/board-interface.ts +++ b/apps/cs/src/entities/board/model/board-interface.ts @@ -56,6 +56,7 @@ export interface Board { popupUseYn: string; delYn: string; useYn: string; + prvcIndctYn: string; mdfcnDt: string; mdfrNm: string; } @@ -81,6 +82,7 @@ export const boardFormSchema = z.object({ pstgPrdUseYn: z.string(), popupUseYn: z.string(), useYn: z.string(), + prvcIndctYn: z.string(), }); export type BoardList = PageableList; diff --git a/apps/cs/src/entities/group-user/model/group-user-interface.ts b/apps/cs/src/entities/group-user/model/group-user-interface.ts index 38daf6fe0..8935bfacc 100644 --- a/apps/cs/src/entities/group-user/model/group-user-interface.ts +++ b/apps/cs/src/entities/group-user/model/group-user-interface.ts @@ -10,6 +10,7 @@ export interface GroupUserListResponseDto { mode: string; groupId: string; userUnqId: string; + userId: string; userName: string; ogdpSeCd: string; ogdpSeNm: string; diff --git a/apps/cs/src/entities/ip/index.ts b/apps/cs/src/entities/ip/index.ts index ef1c5be33..2d244138d 100644 --- a/apps/cs/src/entities/ip/index.ts +++ b/apps/cs/src/entities/ip/index.ts @@ -7,4 +7,3 @@ export { type IpList, type IP, } from "./model/ip-interface"; -export { ClientIpGuard } from "./lib/client-ip-guard"; diff --git a/apps/cs/src/entities/ip/lib/client-ip-guard.tsx b/apps/cs/src/entities/ip/lib/client-ip-guard.tsx deleted file mode 100644 index f3ae452ce..000000000 --- a/apps/cs/src/entities/ip/lib/client-ip-guard.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useEffect, useState } from "react"; -import { useQueryClient } from "@tanstack/react-query"; -import { SiteApi } from "@/entities/site/@x/ip"; -import { ENDPOINT, SITE_ID } from "@/shared/config"; -import { IpApi } from "../api/ip-service"; - -type ClientIpResponse = { - originalIp: string; - normalizedIp: string; -}; - -const ClientIpGuard = ({ children }: { children: React.ReactNode }) => { - const queryClient = useQueryClient(); - const [isAllowed, setIsAllowed] = useState(true); - - useEffect(() => { - (async () => { - const response = await fetch("/api/getClientIp"); - const { normalizedIp } = (await response.json()) as ClientIpResponse; - - if (!normalizedIp || !SITE_ID) return; - - const siteInfo = await queryClient.ensureQueryData({ - queryKey: [ENDPOINT.CMS_SERVICE.SITES, SITE_ID], - queryFn: () => SiteApi.siteDetail(SITE_ID), - }); - - if (siteInfo.siteKndCd === "O") return; - - const { content: ipList } = await IpApi.getIpList({ - siteId: SITE_ID, - prmYn: "Y", - }); - - const allowedClientIp = ipList.some((ip) => ip.ipAddr === normalizedIp); - setIsAllowed(allowedClientIp); - })(); - }, []); - - if (!isAllowed) { - alert("허용되지 않은 IP로 접근하였습니다."); - return <>; - } - - return <>{children}; -}; - -export { ClientIpGuard }; diff --git a/apps/cs/src/entities/loginlog/api/loginlog-service.ts b/apps/cs/src/entities/loginlog/api/loginlog-service.ts index c115aebcb..17cb2ea01 100644 --- a/apps/cs/src/entities/loginlog/api/loginlog-service.ts +++ b/apps/cs/src/entities/loginlog/api/loginlog-service.ts @@ -1,7 +1,6 @@ import { client } from "@/shared/api"; import { ENDPOINT } from "@/shared/config"; import type { - LoginlogInfo, LoginlogList, LoginlogParams, LoginlogService, @@ -16,12 +15,6 @@ export class LoginlogApiService implements LoginlogService { return response.data; } - public async getLoginlogInfo(lgnSn: string): Promise { - const response = await client.get( - `${ENDPOINT.USER_SERVICE.LOGINLOG}/${lgnSn}`, - ); - return response.data; - } } export const LoginlogApi = new LoginlogApiService(); diff --git a/apps/cs/src/entities/loginlog/index.ts b/apps/cs/src/entities/loginlog/index.ts index 72032d92d..d03672226 100644 --- a/apps/cs/src/entities/loginlog/index.ts +++ b/apps/cs/src/entities/loginlog/index.ts @@ -1,5 +1,6 @@ -export { LoginlogApiService } from "./api/loginlog-service"; +export { LoginlogApi } from "./api/loginlog-service"; export type { Loginlog, LoginlogParams, } from "./model/loginlog-interface"; + diff --git a/apps/cs/src/entities/loginlog/model/loginlog-interface.ts b/apps/cs/src/entities/loginlog/model/loginlog-interface.ts index 850b6ce5d..f35fe2444 100644 --- a/apps/cs/src/entities/loginlog/model/loginlog-interface.ts +++ b/apps/cs/src/entities/loginlog/model/loginlog-interface.ts @@ -1,37 +1,29 @@ import type { PageableList, PageableParams } from "@/shared/model"; export interface LoginlogParams extends PageableParams { - lgnSn?: string; - userId?: string; siteId?: string; + userId?: string; + errOcrnYn?: string; + remoteIp?: string; + lgnMthd?: string; + lgnErrCd?: string; + searchDateFrom?: string; + searchDateTo?: string; } export interface Loginlog { - lgnSn: string; - lgnDt: string; - userId: string; - lgnIp: string; - lgnMethodCd: string; - errOcrnYn: string; - lgnErrCd: string; + id: string; siteId: string; -} - -export interface LoginlogInfo { - lgnSn: string; - lgnDt: string; userId: string; - lgnIp: string; - lgnMethodCd: string; errOcrnYn: string; + remoteIp: string; + lgnMthd: string; lgnErrCd: string; - siteId: string; + lgnDt: string; } export type LoginlogList = PageableList; export interface LoginlogService { - getLoginlogList: (params: LoginlogParams) => Promise; - getLoginlogInfo: (lgnSn: string) => Promise; - + getLoginlogList: (params: LoginlogParams) => Promise; } diff --git a/apps/cs/src/entities/menu/lib/menu-wrapper.tsx b/apps/cs/src/entities/menu/lib/menu-wrapper.tsx index 7ecf2e6dd..1f1080cac 100644 --- a/apps/cs/src/entities/menu/lib/menu-wrapper.tsx +++ b/apps/cs/src/entities/menu/lib/menu-wrapper.tsx @@ -43,7 +43,7 @@ const MenuWrapper = ({ children }: { children: React.ReactNode }) => { const response = await getPageMenuAuthority(); if (response?.length) setPageMenuAuthority(response[0]!); })(); - }, []); + }, [pathname]); useEffect(() => { if (!pageMenuAuthority) return; diff --git a/apps/cs/src/entities/post/model/post-interface.ts b/apps/cs/src/entities/post/model/post-interface.ts index 86e27afd5..783053dd7 100644 --- a/apps/cs/src/entities/post/model/post-interface.ts +++ b/apps/cs/src/entities/post/model/post-interface.ts @@ -9,6 +9,7 @@ export interface PostParams extends PageableParams { prdPstgYn?: string; siteId?: string; bbsTypeCd?: string; + ansUseYn?: string; } export interface RecentPostParams extends PageableParams { diff --git a/apps/cs/src/entities/privacylog/api/privacylog-service.ts b/apps/cs/src/entities/privacylog/api/privacylog-service.ts new file mode 100644 index 000000000..4082af038 --- /dev/null +++ b/apps/cs/src/entities/privacylog/api/privacylog-service.ts @@ -0,0 +1,20 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import type { + PrivacyLogList, + PrivacyLogParams, + PrivacyLogService, +} from "../model/privacylog-interface"; + +export class PrivacyLogPrivacyService implements PrivacyLogService { + public async getPrivacyLogList(params: PrivacyLogParams): Promise { + const response = await client.get( + `${ENDPOINT.USER_SERVICE.PRIVACYLOG}`, + { params }, + ); + return response.data; + } + +} + +export const PrivacyLogPrivacy = new PrivacyLogPrivacyService(); diff --git a/apps/cs/src/entities/privacylog/index.ts b/apps/cs/src/entities/privacylog/index.ts new file mode 100644 index 000000000..8f0898922 --- /dev/null +++ b/apps/cs/src/entities/privacylog/index.ts @@ -0,0 +1,6 @@ +export { PrivacyLogPrivacy } from "./api/privacylog-service"; +export type { + PrivacyLog, + PrivacyLogParams, +} from "./model/privacylog-interface"; + diff --git a/apps/cs/src/entities/privacylog/model/privacylog-interface.ts b/apps/cs/src/entities/privacylog/model/privacylog-interface.ts new file mode 100644 index 000000000..d28388445 --- /dev/null +++ b/apps/cs/src/entities/privacylog/model/privacylog-interface.ts @@ -0,0 +1,27 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface PrivacyLogParams extends PageableParams { + siteId?: string; + rqstrIp?: string; + inqInfoKeywordType?: string; + inqInfoKeyword?: string; + inqDtStart?: string; + inqDtEnd?: string; +} + +export interface PrivacyLog { + inqSn: number; + siteId: string; + siteNm: string; + inqDt: string; + rqstrIp: string; + rqstrId: string; + rqstrNm: string; + inqInfoSummary: string; +} + +export type PrivacyLogList = PageableList; + +export interface PrivacyLogService { + getPrivacyLogList: (params: PrivacyLogParams) => Promise; +} diff --git a/apps/cs/src/pages/apilog/index.ts b/apps/cs/src/pages/apilog/index.ts new file mode 100644 index 000000000..564b8ae8f --- /dev/null +++ b/apps/cs/src/pages/apilog/index.ts @@ -0,0 +1 @@ +export { ApiLogPage } from "./ui/apilog-page"; diff --git a/apps/cs/src/pages/apilog/model/table-columns.tsx b/apps/cs/src/pages/apilog/model/table-columns.tsx new file mode 100644 index 000000000..554abb544 --- /dev/null +++ b/apps/cs/src/pages/apilog/model/table-columns.tsx @@ -0,0 +1,59 @@ +import dayjs from "dayjs"; +import type { ColumnDef } from "@tanstack/react-table"; +import type { ApiLog } from "@/entities/apilog"; + + +export const columns: ColumnDef[] = [ + { + id: "no", + header: "NO", + cell: ({ row, table }) => { + const totalRows = table.getRowCount(); + const { pageIndex, pageSize } = table.getState().pagination; + return totalRows - (pageIndex * pageSize + row.index); + }, + }, + { + accessorKey: "dmndDt", + header: "요청일시", + cell: ({ row }) => { + const date = dayjs(row.original.dmndDt); + return date.format("YYYY-MM-DD HH:mm:ss"); + }, + }, + { + accessorKey: "userId", + header: "사용자ID", + }, + { + accessorKey: "ipAddr", + header: "요청IP", + }, + { + accessorKey: "apiHttpPmhd", + header: "요청방식", + cell: ({ row }) => { + const val = row.original.apiHttpPmhd; + if (val === "POST") { + return "저장"; + } else if (val === "PUT") { + return "수정"; + } else if (val === "DELETE") { + return "삭제"; + } else if (val === "GET") { + return "조회"; + } else { + return "기타"; + } + }, + }, + { + accessorKey: "dmndUrl", + header: "요청URL", + }, + { + accessorKey: "siteId", + header: "사이트ID", + } +]; + diff --git a/apps/cs/src/pages/apilog/ui/apilog-page.tsx b/apps/cs/src/pages/apilog/ui/apilog-page.tsx new file mode 100644 index 000000000..51cc960f3 --- /dev/null +++ b/apps/cs/src/pages/apilog/ui/apilog-page.tsx @@ -0,0 +1,209 @@ +"use client"; + +import dayjs from "dayjs"; +import { useState } from "react"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; +import { + Button, + DateRangePicker, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@common/components"; +import { ApiLogApi } from "@/entities/apilog"; +import { type ApiLogParams } from "@/entities/apilog/model/apilog-interface"; +import { useDataTable } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { columns } from "@/pages/apilog/model/table-columns"; + +type DateRange = { + from: Date | undefined; + to?: Date | undefined; +}; + +function ApiLogPage({}) { + // 1주일 전부터 오늘까지 기본 날짜 설정 + const defaultDateRange: DateRange = { + from: dayjs().subtract(7, "day").toDate(), + to: dayjs().toDate(), + }; + + const [dateRange, setDateRange] = useState(defaultDateRange); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const [loginlogPostQuery, setLoginlogPostQuery] = useState({ + userId: "", + siteId: "", + apiHttpPmhd: "", + dmndUrl: "", + ipAddr: "", + dmndDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"), + dmndDtEnd: dayjs().format("YYYY-MM-DD"), + sort: "lgnDt,asc", + }); + + const query = useQuery({ + queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination], + queryFn: () => + ApiLogApi.getApiLogList({ + ...loginlogPostQuery, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + placeholderData: keepPreviousData, + }); + + const { tableComponent, tableInstance: table } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + }, + dataTableOptions: { + viewPagination: true, + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + query.refetch(); + }; + + const handleLoginlogParamsChange = + (key: keyof ApiLogParams) => (value: unknown) => { + setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); + }; + + const handlePresetDateRange = (value: string) => { + let presetDateRange: DateRange | undefined; + switch (value) { + case "1W": + presetDateRange = { + from: dayjs().subtract(7, "day").toDate(), + to: dayjs().toDate(), + }; + break; + case "1M": + presetDateRange = { + from: dayjs().subtract(30, "day").toDate(), + to: dayjs().toDate(), + }; + break; + case "3M": + presetDateRange = { + from: dayjs().subtract(90, "day").toDate(), + to: dayjs().toDate(), + }; + break; + default: + presetDateRange = undefined; + } + handleDateRangeChange(presetDateRange); + }; + + const handleDateRangeChange = (value: DateRange | undefined) => { + setDateRange(value || defaultDateRange); + setLoginlogPostQuery({ + ...loginlogPostQuery, + dmndDtStart: + dayjs(value?.from).format("YYYY-MM-DD") ?? + dayjs().subtract(7, "day").format("YYYY-MM-DD"), + dmndDtEnd: + dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"), + }); + }; + + return ( +
+
+
+ + +
+ +
+
+ + + handleLoginlogParamsChange("userId")(e.target.value) + } + /> + + + +
+
+
+
+
+

로그인로그 목록

+
+ +
+
+
+
{tableComponent}
+
+
+ ); +} + +export { ApiLogPage }; diff --git a/apps/cs/src/pages/bbs/ui/bbs-form.tsx b/apps/cs/src/pages/bbs/ui/bbs-form.tsx index f0b1d7ea2..3d7482bce 100644 --- a/apps/cs/src/pages/bbs/ui/bbs-form.tsx +++ b/apps/cs/src/pages/bbs/ui/bbs-form.tsx @@ -478,6 +478,35 @@ function BbsForm({ form, bbsId, ...props }: BbsFormProps) { /> + + 개인정보 표시 여부 + + ( + + + +
+ + +
+
+ + +
+
+
+
+ )} + /> +
+
사용 여부 diff --git a/apps/cs/src/pages/bbs/ui/create-page.tsx b/apps/cs/src/pages/bbs/ui/create-page.tsx index e5b8b2c21..50a23e57a 100644 --- a/apps/cs/src/pages/bbs/ui/create-page.tsx +++ b/apps/cs/src/pages/bbs/ui/create-page.tsx @@ -38,6 +38,7 @@ function CreatePage() { pstgPrdUseYn: "Y", popupUseYn: "Y", useYn: "Y", + prvcIndctYn: "Y", }, }); diff --git a/apps/cs/src/pages/groups/model/group-users-table-columns.tsx b/apps/cs/src/pages/groups/model/group-users-table-columns.tsx index 8d1054aac..e525ac7ab 100644 --- a/apps/cs/src/pages/groups/model/group-users-table-columns.tsx +++ b/apps/cs/src/pages/groups/model/group-users-table-columns.tsx @@ -26,7 +26,7 @@ export const columns: ColumnDef[] = [ enableHiding: false, }, { - accessorKey: "groupId", + accessorKey: "userId", header: "사용자ID", }, { diff --git a/apps/cs/src/pages/image/ui/image-page.tsx b/apps/cs/src/pages/image/ui/image-page.tsx index 0128c0462..3515cfbef 100644 --- a/apps/cs/src/pages/image/ui/image-page.tsx +++ b/apps/cs/src/pages/image/ui/image-page.tsx @@ -26,7 +26,10 @@ function ImagePage() { const [rowSelection, setRowSelection] = useState({}); const [postingTargets, setPostingTargets] = useState([]); - const [imageQuery, setImageQuery] = useState({ keyword: "" }); + const [imageQuery, setImageQuery] = useState({ + keyword: "" , + sort:"regDt,desc" + }); const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 5, diff --git a/apps/cs/src/pages/loginlog/model/table-columns.tsx b/apps/cs/src/pages/loginlog/model/table-columns.tsx index dbe19f044..4119e0600 100644 --- a/apps/cs/src/pages/loginlog/model/table-columns.tsx +++ b/apps/cs/src/pages/loginlog/model/table-columns.tsx @@ -18,7 +18,7 @@ export const columns: ColumnDef[] = [ header: "등록일", cell: ({ row }) => { const date = dayjs(row.original.lgnDt); - return date.format("YYYY-MM-DD HH24:mi:ss"); + return date.format("YYYY-MM-DD HH:mm:ss"); }, }, { @@ -26,24 +26,24 @@ export const columns: ColumnDef[] = [ header: "사용자ID", }, { - accessorKey: "로그인IP", - header: "remoteIp", + accessorKey: "remoteIp", + header: "로그인IP", }, { accessorKey: "lgnMthd", header: "접속방식", }, { - accessorKey: "오류여부", - header: "errOcrnYn", + accessorKey: "errOcrnYn", + header: "오류여부", }, { - accessorKey: "오류코드", - header: "lgnErrCd", + accessorKey: "lgnErrCd", + header: "오류코드", }, { - accessorKey: "사이트ID", - header: "siteId", + accessorKey: "siteId", + header: "사이트ID", } ]; diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx index 598eacee2..1672a5bd8 100644 --- a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -1,88 +1,227 @@ "use client"; +import dayjs from "dayjs"; import { useState } from "react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { getCoreRowModel, type PaginationState } from "@tanstack/react-table"; import { - getCoreRowModel, - useReactTable, - type PaginationState, -} from "@tanstack/react-table"; -import { Button, DataTable, Input, Label } from "@common/components"; -import { LoginlogApi} from "../../../entities/loginlog/api/loginlog-service"; -import { type LoginlogParams} from "../../../entities/loginlog/model/loginlog-interface"; - + Button, + DateRangePicker, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + LoadingWithImage, +} from "@common/components"; +import { LoginlogApi } from "@/entities/loginlog"; +import { type LoginlogParams } from "@/entities/loginlog/model/loginlog-interface"; +import { useDataTable } from "@/shared/lib"; import { ENDPOINT } from "@/shared/config"; -import { columns } from "../model/table-columns"; +import { columns } from "@/pages/loginlog/model/table-columns"; -type LoginlogPageProps = { - lgnSn?: string; +type DateRange = { + from: Date | undefined; + to?: Date | undefined; }; -function LoginlogPage({ }: LoginlogPageProps) { - const [loginlogQuery, setLoginlogQuery] = useState({ - userId: "", - siteId: "", - }); +function LoginlogPage({}) { + // 1주일 전부터 오늘까지 기본 날짜 설정 + const defaultDateRange: DateRange = { + from: dayjs().subtract(7, "day").toDate(), + to: dayjs().toDate(), + }; + + const [dateRange, setDateRange] = useState(defaultDateRange); + const [isManualLoading, setIsManualLoading] = useState(false); + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); - const { data: loginlogList, refetch } = useQuery({ - queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, , pagination], - queryFn: () => - LoginlogApi.getLoginlogList({ - ...loginlogQuery, - page: pagination.pageIndex, - size: pagination.pageSize, - }), - placeholderData: keepPreviousData, + const [loginlogPostQuery, setLoginlogPostQuery] = useState({ + userId: "", + siteId: "", + errOcrnYn: "N", + remoteIp: "", + lgnMthd: "", + lgnErrCd: "", + searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), + searchDateTo: dayjs().format("YYYY-MM-DD"), + sort: "lgnDt,desc", }); - const table = useReactTable({ - data: loginlogList?.content ?? [], - columns, - rowCount: loginlogList?.totalElements ?? 0, - state: { pagination }, - manualPagination: true, - enableMultiRowSelection: false, - getCoreRowModel: getCoreRowModel(), - onPaginationChange: setPagination, + const query = useQuery({ + queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination], + queryFn: () => + LoginlogApi.getLoginlogList({ + ...loginlogPostQuery, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + placeholderData: keepPreviousData, + }); + + const { tableComponent, tableInstance: table } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + }, + dataTableOptions: { + viewPagination: true, + }, }); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - refetch(); + setIsManualLoading(true); + query.refetch().finally(() => { + setIsManualLoading(false); + }); + }; + + const handleLoginlogParamsChange = + (key: keyof LoginlogParams) => (value: unknown) => { + setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); + }; + + const handlePresetDateRange = (value: string) => { + let presetDateRange: DateRange | undefined; + switch (value) { + case "1W": + presetDateRange = { + from: dayjs().subtract(7, "day").toDate(), + to: dayjs().toDate(), + }; + break; + case "1M": + presetDateRange = { + from: dayjs().subtract(30, "day").toDate(), + to: dayjs().toDate(), + }; + break; + case "3M": + presetDateRange = { + from: dayjs().subtract(90, "day").toDate(), + to: dayjs().toDate(), + }; + break; + default: + presetDateRange = undefined; + } + handleDateRangeChange(presetDateRange); + }; + + const handleDateRangeChange = (value: DateRange | undefined) => { + setDateRange(value || defaultDateRange); + setLoginlogPostQuery({ + ...loginlogPostQuery, + searchDateFrom: + dayjs(value?.from).format("YYYY-MM-DD") ?? + dayjs().subtract(7, "day").format("YYYY-MM-DD"), + searchDateTo: + dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"), + }); }; return (
- + + +
+ +
+
+ - setLoginlogQuery({ - ...loginlogQuery, - userId: e.target.value, - }) + handleLoginlogParamsChange("userId")(e.target.value) } /> -
-

로그인로그 목록

+
+

로그인로그 목록

+
+ +
+
-
- +
+ {/* Debug: 로딩 상태 표시 */} + {(query.isLoading || query.isPending || isManualLoading) && ( +
+
+
+ +
+
+
+ )} + {tableComponent}
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 8cbd7b8b0..ac62e2520 100644 --- a/apps/cs/src/pages/menu/ui/type-menu-form.tsx +++ b/apps/cs/src/pages/menu/ui/type-menu-form.tsx @@ -34,7 +34,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { }; const handleContentSelect = (content: Contents) => { - form.setValue("urlPath", `/cntns/${content.contsSn}`); + form.setValue("urlPath", `/cms/cntns/${content.contsSn}`); form.setValue("menuLnkgSn", content.contsSn); form.setValue("menuLnkgSnNm", content.contsTtl); }; @@ -47,7 +47,7 @@ function TypeMenuForm({ form, menuType }: TypeMenuFormProps) { const handleBoardSelect = (board: BoardPopup) => { form.setValue( "urlPath", - `/pst/${board.bbsTypeCd.toLowerCase()}/${board.bbsId}`, + `/cms/${board.bbsTypeCd.toLowerCase()}/${board.bbsId}`, ); form.setValue("menuLnkgSn", board.bbsId); form.setValue("menuLnkgSnNm", board.bbsNm); diff --git a/apps/cs/src/pages/privacylog/index.ts b/apps/cs/src/pages/privacylog/index.ts new file mode 100644 index 000000000..7bbd75042 --- /dev/null +++ b/apps/cs/src/pages/privacylog/index.ts @@ -0,0 +1 @@ +export { PrivacyLogPage } from "./ui/privacylog-page"; diff --git a/apps/cs/src/pages/privacylog/model/table-columns.tsx b/apps/cs/src/pages/privacylog/model/table-columns.tsx new file mode 100644 index 000000000..a5b52d796 --- /dev/null +++ b/apps/cs/src/pages/privacylog/model/table-columns.tsx @@ -0,0 +1,41 @@ +import dayjs from "dayjs"; +import type { ColumnDef } from "@tanstack/react-table"; +import type { PrivacyLog } from "@/entities/privacylog"; + + +export const columns: ColumnDef[] = [ + { + id: "no", + header: "NO", + cell: ({ row, table }) => { + const totalRows = table.getRowCount(); + const { pageIndex, pageSize } = table.getState().pagination; + return totalRows - (pageIndex * pageSize + row.index); + }, + }, + { + accessorKey: "inqDt", + header: "요청일시", + cell: ({ row }) => { + const date = dayjs(row.original.inqDt); + return date.format("YYYY-MM-DD HH:mm:ss"); + }, + }, + { + accessorKey: "siteNm", + header: "사이트명", + }, + { + accessorKey: "rqstrNm", + header: "조회자명", + }, + { + accessorKey: "rqstrIp", + header: "조회IP", + }, + { + accessorKey: "inqInfoSummary", + header: "조회정보", + } +]; + diff --git a/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx b/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx new file mode 100644 index 000000000..5cb8bf13d --- /dev/null +++ b/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx @@ -0,0 +1,206 @@ +"use client"; + +import dayjs from "dayjs"; +import { useState } from "react"; +import { keepPreviousData, useQuery } from "@tanstack/react-query"; +import { + getCoreRowModel, + type PaginationState, +} from "@tanstack/react-table"; +import { + Button, + DateRangePicker, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + } from "@common/components"; +import { PrivacyLogPrivacy } from "@/entities/privacylog"; +import { type PrivacyLogParams} from "@/entities/privacylog/model/privacylog-interface"; +import { useDataTable } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { columns } from "@/pages/privacylog/model/table-columns"; + + + +type DateRange = { + from: Date | undefined; + to?: Date | undefined; +}; + +function PrivacyLogPage({ }) { + + // 1주일 전부터 오늘까지 기본 날짜 설정 + const defaultDateRange: DateRange = { + from: dayjs().subtract(7, "day").toDate(), + to: dayjs().toDate(), + }; + + const [dateRange, setDateRange] = useState(defaultDateRange); + + const [pagination, setPagination] = useState({ + pageIndex: 0, + pageSize: 10, + }); + + const [privacylogPostQuery, setprivacylogPostQuery] = useState< PrivacyLogParams>({ + siteId: "", + inqInfoKeywordType: "userName", + inqInfoKeyword: "", + rqstrIp: "", + inqDtStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"), + inqDtEnd: dayjs().format("YYYY-MM-DD"), + sort:"inqDt,asc" + }); + + + const query = useQuery({ + queryKey: [ENDPOINT.USER_SERVICE.PRIVACYLOG, pagination], + queryFn: () => + PrivacyLogPrivacy.getPrivacyLogList({ + ...privacylogPostQuery, + page: pagination.pageIndex, + size: pagination.pageSize, + }), + placeholderData: keepPreviousData, + }); + + const { tableComponent, tableInstance: table } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + }, + manualPagination: true, + enableMultiRowSelection: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + }, + dataTableOptions: { + viewPagination: true, + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + query.refetch(); + }; + + const handleprivacylogParamsChange = + (key: keyof PrivacyLogParams) => (value: unknown) => { + setprivacylogPostQuery({ ...privacylogPostQuery, [key]: value }); + }; + + const handlePresetDateRange = (value: string) => { + let presetDateRange: DateRange | undefined; + switch (value) { + case "1W": + presetDateRange = { + from: dayjs().subtract(7, "day").toDate(), + to: dayjs().toDate(), + }; + break; + case "1M": + presetDateRange = { + from: dayjs().subtract(30, "day").toDate(), + to: dayjs().toDate(), + }; + break; + case "3M": + presetDateRange = { + from: dayjs().subtract(90, "day").toDate(), + to: dayjs().toDate(), + }; + break; + default: + presetDateRange = undefined; + } + handleDateRangeChange(presetDateRange); + }; + + + + const handleDateRangeChange = (value: DateRange | undefined) => { + setDateRange(value || defaultDateRange); + setprivacylogPostQuery({ + ...privacylogPostQuery, + inqDtStart: dayjs(value?.from).format("YYYY-MM-DD") ?? dayjs().subtract(7, "day").format("YYYY-MM-DD"), + inqDtEnd: dayjs(value?.to).format("YYYY-MM-DD") ?? dayjs().format("YYYY-MM-DD"), + }); + }; + + + + + return ( +
+
+
+ + +
+ +
+
+ + + handleprivacylogParamsChange("inqInfoKeyword")(e.target.value) + } + /> + + +
+
+
+
+
+

로그인로그 목록

+
+ +
+
+
+
{tableComponent}
+
+
+ ); +} + +export { PrivacyLogPage }; diff --git a/apps/cs/src/pages/recent/ui/recent-page.tsx b/apps/cs/src/pages/recent/ui/recent-page.tsx index 681c25e34..ec9fec78a 100644 --- a/apps/cs/src/pages/recent/ui/recent-page.tsx +++ b/apps/cs/src/pages/recent/ui/recent-page.tsx @@ -62,6 +62,7 @@ function RecentPage() { delYn: "N", frDateYmd: "", toDateYmd: "", + sort:"regDt,desc" }); const [isDeletePostData, setIsDeletePostData] = useState(false); 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 c2a2c2702..43e2c2155 100644 --- a/apps/cs/src/pages/site/ui/ip-management-dialog.tsx +++ b/apps/cs/src/pages/site/ui/ip-management-dialog.tsx @@ -46,7 +46,7 @@ function IPManagementDialog({ const { data: ipList } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.IPS, props.open], - queryFn: () => IpApi.getIpList({ siteId, sort: [] }), + queryFn: () => IpApi.getIpList({ siteId, sort: "" }), select: (data) => { const ips = data.content.map((item) => ({ ...item, diff --git a/apps/cs/src/pages/site/ui/site-page.tsx b/apps/cs/src/pages/site/ui/site-page.tsx index 6d0041a65..734ba7658 100644 --- a/apps/cs/src/pages/site/ui/site-page.tsx +++ b/apps/cs/src/pages/site/ui/site-page.tsx @@ -17,6 +17,7 @@ function SitePage() { const [siteQuery, setSiteQuery] = useState({ siteNm: "", + sort:"regDt,desc" }); const [pagination, setPagination] = useState({ pageIndex: 0, diff --git a/apps/cs/src/pages/template/ui/template-page.tsx b/apps/cs/src/pages/template/ui/template-page.tsx index dc90b69ff..59e8c8b01 100644 --- a/apps/cs/src/pages/template/ui/template-page.tsx +++ b/apps/cs/src/pages/template/ui/template-page.tsx @@ -18,6 +18,7 @@ function TemplatePage() { const [templateQuery, setTemplateQuery] = useState({ tmpltSeCd: "", keyword: "", + sort:"regDt,desc" }); const [pagination, setPagination] = useState({ pageIndex: 0, diff --git a/apps/cs/src/pages/terms/ui/terms-page.tsx b/apps/cs/src/pages/terms/ui/terms-page.tsx index 8cb6a911e..617d4eff6 100644 --- a/apps/cs/src/pages/terms/ui/terms-page.tsx +++ b/apps/cs/src/pages/terms/ui/terms-page.tsx @@ -17,7 +17,8 @@ function TermsPage() { const [termsQuery, setTermsQuery] = useState({ trmsSeCd: "", - keyword: "", + keyword: "", + sort:"regDt,desc" }); const [pagination, setPagination] = useState({ pageIndex: 0, diff --git a/apps/cs/src/pages/users/ui/users-page.tsx b/apps/cs/src/pages/users/ui/users-page.tsx index d6f77deb5..2ffe05eb4 100644 --- a/apps/cs/src/pages/users/ui/users-page.tsx +++ b/apps/cs/src/pages/users/ui/users-page.tsx @@ -51,7 +51,7 @@ function UsersPage() { grpId: "", frDateYmd: "", toDateYmd: "", - sort: ["aprvYmd,desc"], + sort: "regDt,desc" }, }); @@ -288,9 +288,9 @@ function UsersPage() {

회원 목록

+

diff --git a/apps/cs/src/widgets/post/ui/post.tsx b/apps/cs/src/widgets/post/ui/post.tsx index a7f3cb43a..040262218 100644 --- a/apps/cs/src/widgets/post/ui/post.tsx +++ b/apps/cs/src/widgets/post/ui/post.tsx @@ -5,14 +5,21 @@ import Image from "next/image"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { useQuery } from "@tanstack/react-query"; -import { Button, Skeleton } from "@common/components"; +import { + Button, + Label, + RadioGroup, + RadioGroupItem, + Skeleton, +} from "@common/components"; import { AttachmentApi } from "@/entities/attachment"; import { PostApi } from "@/entities/post"; -import { useAlertStore, useMutationWrapper, useMenuStore } from "@/shared/lib"; +import { BoardApi } from "@/entities/board"; import { ENDPOINT } from "@/shared/config"; +import { useAlertStore, useMutationWrapper, useMenuStore } from "@/shared/lib"; import { PostAction } from "./post-action"; import { PostComment } from "./post-comment"; -import { BoardApi } from "@/entities/board"; +import { PostEvaluationApi } from "../api/post-evaluation-service"; function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const router = useRouter(); @@ -20,6 +27,7 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { const { bbsInfo, setBbsInfo } = useMenuStore((state) => state); const [filename, setFilename] = useState(""); + const [evlScr, setEvlScr] = useState(5); const { data: boardInfo } = useQuery({ queryKey: [ENDPOINT.BOARD_SERVICE.BBS, bbsId], @@ -72,6 +80,17 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { }, }); + const { mutate: postEvaluation } = useMutationWrapper({ + mutationFn: (evlScr: number) => + PostEvaluationApi.postEvaluation({ + pstNo: parseInt(pstNo), + evlScr, + }), + onSuccess: () => { + alert("게시물 평가가 완료되었습니다."); + }, + }); + const handleDownload = async () => { if (!downloadResponse) return; try { @@ -187,6 +206,53 @@ function Post({ bbsId, pstNo }: { bbsId: string; pstNo: string }) { ) )} + {bbsInfo?.dgstfnEvlUseYn === "Y" && ( + <> +
+
+

+ 이 페이지에서 제공하는 +
+ 정보에 대하여 만족하시나요? +

+
+ setEvlScr(parseInt(value))} + className="flex items-center space-x-4" + > +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ + )}
diff --git a/packages/common/components/src/api/client.ts b/packages/common/components/src/api/client.ts index 0226a4f9a..46d5291fd 100644 --- a/packages/common/components/src/api/client.ts +++ b/packages/common/components/src/api/client.ts @@ -1,14 +1,17 @@ import { Client } from "@common/network"; import { getConfig } from "@common/config"; +import { getCookie } from "../lib"; + +const siteId = getCookie("site-id-key") ?? ""; const getClientInstance = () => { const config = getConfig(); - const { PROXY_HOST, SITE_ID } = config; + const { PROXY_HOST } = config; const BASE_URL = `${PROXY_HOST}/server`; return Client.getInstance({ baseURL: BASE_URL, - siteId: SITE_ID ?? "", + siteId, }); }; diff --git a/packages/common/components/src/lib/client-ip-guard.tsx b/packages/common/components/src/lib/client-ip-guard.tsx index e6b30fe81..d1ae1735c 100644 --- a/packages/common/components/src/lib/client-ip-guard.tsx +++ b/packages/common/components/src/lib/client-ip-guard.tsx @@ -1,11 +1,11 @@ import { useEffect, useState } from "react"; import { useQueryClient } from "@tanstack/react-query"; -import { getConfig } from "@common/config"; import { getSiteInfo } from "../api/site"; import { getIpList } from "../api/ip"; import { ENDPOINT } from "../config"; +import { getCookie } from "../lib"; -const { SITE_ID } = getConfig(); +const siteId = getCookie("site-id-key") ?? ""; const ClientIpGuard = ({ currentPath, @@ -29,17 +29,17 @@ const ClientIpGuard = ({ useEffect(() => { (async () => { - if (!SITE_ID || !clientIp) return; + if (!siteId || !clientIp) return; const siteInfo = await queryClient.ensureQueryData({ - queryKey: [ENDPOINT.CMS_SERVICE.SITES, SITE_ID], - queryFn: () => getSiteInfo(SITE_ID), + queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId], + queryFn: () => getSiteInfo(siteId), }); if (siteInfo.siteKndCd === "O") return; const { content: ipList } = await getIpList({ - siteId: SITE_ID, + siteId, prmYn: "Y", }); diff --git a/packages/common/components/src/ui/header/index.tsx b/packages/common/components/src/ui/header/index.tsx index 7d4caad94..433435009 100644 --- a/packages/common/components/src/ui/header/index.tsx +++ b/packages/common/components/src/ui/header/index.tsx @@ -1,9 +1,8 @@ import "./header.css"; -import { useEffect, useCallback } from "react"; +import { useEffect, useCallback, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { images } from "@common/assets"; -import { getConfig } from "@common/config"; import { ENDPOINT } from "../../config"; import { setCookie, getCookie } from "../../lib"; import { @@ -21,10 +20,13 @@ import { } from "../dropdown-menu"; import { Button } from "../button"; import { SidebarTrigger } from "../sidebar"; +import { Skeleton } from "../skeleton"; -const { SITE_ID } = getConfig(); +const siteId = getCookie("site-id-key") ?? ""; export function Header({ viewOnly }: { viewOnly?: boolean }) { + const [mounted, setMounted] = useState(false); + const { data: userInfo } = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.USERS], queryFn: () => getUserInfo(), @@ -32,8 +34,8 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) { }); const { data: siteInfo } = useQuery({ - queryKey: [ENDPOINT.CMS_SERVICE.SITES, SITE_ID], - queryFn: () => getSiteInfo(SITE_ID ?? ""), + queryKey: [ENDPOINT.CMS_SERVICE.SITES, siteId], + queryFn: () => getSiteInfo(siteId), select: (data) => { if (data.siteSkn) { const siteSkin = data.siteSkn.toLowerCase(); @@ -50,6 +52,18 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) { queryFn: () => getUserPopupList(), }); + const logout = async () => { + try { + const response = await fetch("/api/auth/logout", { + method: "POST", + }); + if (!response.ok) throw new Error("Failed to logout"); + window.location.href = "/auth/login"; + } catch (error) { + console.error(error); + } + }; + const openPopup = useCallback(async (popup: Popup) => { try { const cookieName = `cs-popup-hide-for-day-${popup.popupSn}`; @@ -97,12 +111,20 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) { return () => clearTimeout(timer); }, [userPopupList, openPopup]); + useEffect(() => { + setMounted(true); + }, []); + return (

- logo + {mounted ? ( + logo + ) : ( + + )} {siteInfo?.siteNm} @@ -129,7 +151,7 @@ export function Header({ viewOnly }: { viewOnly?: boolean }) {

접속 IP

- 로그아웃 + 로그아웃
diff --git a/packages/common/components/src/ui/lnb/index.tsx b/packages/common/components/src/ui/lnb/index.tsx index 80d9ad87d..552a7d67a 100644 --- a/packages/common/components/src/ui/lnb/index.tsx +++ b/packages/common/components/src/ui/lnb/index.tsx @@ -3,7 +3,6 @@ import { useState, useEffect, useRef } from "react"; import { useQuery } from "@tanstack/react-query"; import { ChevronRight } from "lucide-react"; -import { getConfig } from "@common/config"; import { ENDPOINT } from "../../config"; import { cn, setCookie } from "../../lib"; import { getMenuList, type MenuItem } from "../../api"; @@ -24,8 +23,9 @@ import { CollapsibleTrigger, } from "../collapsible"; import { getParentIds } from "./lib"; +import { getCookie } from "../../lib"; -const { SITE_ID } = getConfig(); +const siteId = getCookie("site-id-key") ?? ""; export function LNB({ viewOnly, @@ -38,7 +38,7 @@ export function LNB({ }) { const { data: menuList } = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES], - queryFn: () => getMenuList(SITE_ID ?? ""), + queryFn: () => getMenuList(siteId), enabled: !viewOnly, }); @@ -134,17 +134,18 @@ export function LNB({ function MenuGroup({ item }: { item: MenuItem }) { const { open: isSidebarOpen } = useSidebar(); + const [isOpen, setIsOpen] = useState( + defaultOpenMenuIds.includes(item.menuCd), + ); return ( - + diff --git a/packages/common/config/src/initialize-config.ts b/packages/common/config/src/initialize-config.ts index f1d3178f5..2184db348 100644 --- a/packages/common/config/src/initialize-config.ts +++ b/packages/common/config/src/initialize-config.ts @@ -64,8 +64,5 @@ export function getViteConfig(): EnvConfig { } export function getConfig(): EnvConfig { - if (typeof import.meta?.env !== "undefined") { - return getViteConfig(); - } return getNextConfig(); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 359575837..c75c08abc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2390,7 +2390,7 @@ packages: magic-string: 0.27.0 react-docgen-typescript: 2.2.2(typescript@5.6.3) typescript: 5.6.3 - vite: 6.2.0(@types/node@22.13.5) + vite: 6.2.0(@types/node@22.13.9) dev: true /@jridgewell/gen-mapping@0.3.8: @@ -2482,7 +2482,7 @@ packages: dnd-multi-backend: 8.1.2(dnd-core@16.0.1) framer-motion: 11.18.2(react-dom@18.3.1)(react@18.3.1) react: 18.3.1 - react-dnd: 16.0.1(@types/node@22.13.5)(@types/react@18.3.18)(react@18.3.1) + react-dnd: 16.0.1(@types/node@22.13.9)(@types/react@18.3.18)(react@18.3.1) react-dnd-html5-backend: 16.0.1 react-dnd-touch-backend: 16.0.1 react-dom: 18.3.1(react@18.3.1) @@ -3983,7 +3983,7 @@ packages: browser-assert: 1.2.1 storybook: 8.6.0(prettier@3.5.2) ts-dedent: 2.2.0 - vite: 6.2.0(@types/node@22.13.5) + vite: 6.2.0(@types/node@22.13.9) dev: true /@storybook/components@8.6.0(storybook@8.6.0): @@ -4115,7 +4115,7 @@ packages: resolve: 1.22.10 storybook: 8.6.0(prettier@3.5.2) tsconfig-paths: 4.2.0 - vite: 6.2.0(@types/node@22.13.5) + vite: 6.2.0(@types/node@22.13.9) transitivePeerDependencies: - rollup - supports-color @@ -4590,7 +4590,7 @@ packages: '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.9) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 6.2.0(@types/node@22.13.5) + vite: 6.2.0(@types/node@22.13.9) transitivePeerDependencies: - supports-color dev: true @@ -9878,7 +9878,7 @@ packages: debug: 4.4.0 globrex: 0.1.2 tsconfck: 3.1.5(typescript@5.6.3) - vite: 6.2.0(@types/node@22.13.5) + vite: 6.2.0(@types/node@22.13.9) transitivePeerDependencies: - supports-color - typescript