From 932ae3f961f8d9b70ae341a9f2ddf8f5c8a6e36e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Fri, 27 Jun 2025 20:38:45 +0900 Subject: [PATCH 1/9] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20API=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=94=EA=B0=80,=20sort=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=ED=83=80=EC=9E=85=20=EC=88=98=EC=A0=95=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/middleware.ts | 9 +- .../src/entities/apilog/api/apilog-service.ts | 20 ++ apps/cs/src/entities/apilog/index.ts | 6 + .../entities/apilog/model/apilog-interface.ts | 27 +++ .../entities/loginlog/api/loginlog-service.ts | 7 - apps/cs/src/entities/loginlog/index.ts | 3 +- .../loginlog/model/loginlog-interface.ts | 32 +-- apps/cs/src/pages/apilog/index.ts | 1 + .../src/pages/apilog/model/table-columns.tsx | 59 +++++ apps/cs/src/pages/apilog/ui/apilog-page.tsx | 218 ++++++++++++++++++ .../pages/loginlog/model/table-columns.tsx | 18 +- .../src/pages/loginlog/ui/loginlog-page.tsx | 209 +++++++++++++---- .../pages/site/ui/ip-management-dialog.tsx | 2 +- apps/cs/src/pages/users/ui/users-page.tsx | 6 +- apps/cs/src/shared/config/api.ts | 3 +- apps/cs/src/shared/model/api-interface.ts | 2 +- 16 files changed, 535 insertions(+), 87 deletions(-) create mode 100644 apps/cs/src/entities/apilog/api/apilog-service.ts create mode 100644 apps/cs/src/entities/apilog/index.ts create mode 100644 apps/cs/src/entities/apilog/model/apilog-interface.ts create mode 100644 apps/cs/src/pages/apilog/index.ts create mode 100644 apps/cs/src/pages/apilog/model/table-columns.tsx create mode 100644 apps/cs/src/pages/apilog/ui/apilog-page.tsx 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/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..e8576a26a --- /dev/null +++ b/apps/cs/src/entities/apilog/model/apilog-interface.ts @@ -0,0 +1,27 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface ApiLogParams extends PageableParams { + siteId?: string; + userId?: string; + apiHttpPmhd?: string; + dmndUrl?: string; + ipAddr?: string; + searchDateFrom?: string; + searchDateTo?: string; +} + +export interface ApiLog { + id: string; + siteId: string; + userId: 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/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/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..49ad3a12a --- /dev/null +++ b/apps/cs/src/pages/apilog/ui/apilog-page.tsx @@ -0,0 +1,218 @@ +"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< ApiLogParams>({ + userId: "", + siteId: "", + apiHttpPmhd: "", + dmndUrl: "", + ipAddr: "", + searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), + searchDateTo: 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, + 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 ( +
+
+
+ + +
+ +
+
+ + + handleLoginlogParamsChange("userId")(e.target.value) + } + /> + + + +
+
+
+
+
+

로그인로그 목록

+
+ +
+
+
+
{tableComponent}
+
+
+ ); +} + +export {ApiLogPage }; 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..f0483d425 100644 --- a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -1,77 +1,183 @@ "use client"; +import dayjs from "dayjs"; import { useState } from "react"; import { keepPreviousData, useQuery } from "@tanstack/react-query"; 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"; - +import { + Button, + DateRangePicker, + Input, + Label, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + } 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 [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 10, }); - - const { data: loginlogList, refetch } = useQuery({ - queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, , pagination], + + 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,asc" + }); + + + const query = useQuery({ + queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination], queryFn: () => LoginlogApi.getLoginlogList({ - ...loginlogQuery, - page: pagination.pageIndex, + ...loginlogPostQuery, + page: pagination.pageIndex, size: pagination.pageSize, }), - placeholderData: keepPreviousData, + placeholderData: keepPreviousData, }); - const table = useReactTable({ - data: loginlogList?.content ?? [], - columns, - rowCount: loginlogList?.totalElements ?? 0, - state: { pagination }, - manualPagination: true, - enableMultiRowSelection: false, - getCoreRowModel: getCoreRowModel(), - onPaginationChange: setPagination, + 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(); + query.refetch(); }; + 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) } /> + + @@ -79,11 +185,30 @@ function LoginlogPage({ }: LoginlogPageProps) {
-

로그인로그 목록

-
-
- -
+
+

로그인로그 목록

+
+ +
+
+
+
{tableComponent}
); 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/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() {

회원 목록

-
@@ -208,7 +213,19 @@ function LoginlogPage({ }) { -
{tableComponent}
+
+ {/* Debug: 로딩 상태 표시 */} + {(query.isLoading || query.isPending || isManualLoading) && ( +
+
+
+ +
+
+
+ )} + {tableComponent} +
); 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..9feea8a02 --- /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/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index b23268e66..ac0f18eca 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -21,6 +21,8 @@ export const ENDPOINT = { ROLES: "/user-service/api/v1/roles", LOGINLOG: "/user-service/api/v1/login-logs", APILOG: "/user-service/api/v1/api-logs", + PRIVACYLOG: "/user-service//api/v1/prvc-inq-logs", + }, CMS_SERVICE: { SITES: "/cms-service/api/v1/sites", From 533923a40aea807590631798eb6ed4f1cfa020b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Fri, 27 Jun 2025 22:08:38 +0900 Subject: [PATCH 5/9] =?UTF-8?q?Privacy=20Log=20Route=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/app/(with-base-layout)/privacylog/page.tsx | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 apps/cs/app/(with-base-layout)/privacylog/page.tsx 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; From 00329ff32b6ac920c11741d79b1365659df629b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Mon, 30 Jun 2025 14:20:38 +0900 Subject: [PATCH 6/9] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entities/apilog/model/apilog-interface.ts | 21 +++-- apps/cs/src/pages/apilog/ui/apilog-page.tsx | 77 ++++++++-------- .../src/pages/loginlog/ui/loginlog-page.tsx | 87 +++++++++---------- .../pages/privacylog/ui/privacylog-page.tsx | 2 +- 4 files changed, 89 insertions(+), 98 deletions(-) diff --git a/apps/cs/src/entities/apilog/model/apilog-interface.ts b/apps/cs/src/entities/apilog/model/apilog-interface.ts index e8576a26a..f00e583ad 100644 --- a/apps/cs/src/entities/apilog/model/apilog-interface.ts +++ b/apps/cs/src/entities/apilog/model/apilog-interface.ts @@ -3,25 +3,28 @@ import type { PageableList, PageableParams } from "@/shared/model"; export interface ApiLogParams extends PageableParams { siteId?: string; userId?: string; + userNm?: string; apiHttpPmhd?: string; dmndUrl?: string; ipAddr?: string; - searchDateFrom?: string; - searchDateTo?: string; + dmndDtStart?: string; + dmndDtEnd?: string; } export interface ApiLog { - id: string; + logId: string; siteId: string; - userId: string; - apiHttpPmhd: string; - dmndUrl: string; - ipAddr: string; - dmndDt: 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; + getApiLogList: (params: ApiLogParams) => Promise; } diff --git a/apps/cs/src/pages/apilog/ui/apilog-page.tsx b/apps/cs/src/pages/apilog/ui/apilog-page.tsx index 49ad3a12a..51cc960f3 100644 --- a/apps/cs/src/pages/apilog/ui/apilog-page.tsx +++ b/apps/cs/src/pages/apilog/ui/apilog-page.tsx @@ -3,61 +3,54 @@ 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, - type PaginationState, -} from "@tanstack/react-table"; -import { - Button, - DateRangePicker, - Input, + Button, + DateRangePicker, + Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, - } from "@common/components"; +} from "@common/components"; import { ApiLogApi } from "@/entities/apilog"; -import { type ApiLogParams} from "@/entities/apilog/model/apilog-interface"; -import { useDataTable } from "@/shared/lib"; +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({ }) { - +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< ApiLogParams>({ - userId: "", - siteId: "", - apiHttpPmhd: "", - dmndUrl: "", - ipAddr: "", - searchDateFrom: dayjs().subtract(7, "day").format("YYYY-MM-DD"), - searchDateTo: dayjs().format("YYYY-MM-DD"), - sort:"lgnDt,asc" + + 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: () => @@ -92,10 +85,10 @@ function ApiLogPage({ }) { }; const handleLoginlogParamsChange = - (key: keyof ApiLogParams) => (value: unknown) => { - setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); - }; - + (key: keyof ApiLogParams) => (value: unknown) => { + setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); + }; + const handlePresetDateRange = (value: string) => { let presetDateRange: DateRange | undefined; switch (value) { @@ -123,20 +116,18 @@ function ApiLogPage({ }) { 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"), + 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 (
@@ -162,7 +153,7 @@ function ApiLogPage({ }) { handleLoginlogParamsChange("userId")(e.target.value) } @@ -171,12 +162,12 @@ function ApiLogPage({ }) {
- +
{tableComponent}
); } -export {ApiLogPage }; +export { ApiLogPage }; diff --git a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx index 058c03db2..1672a5bd8 100644 --- a/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx +++ b/apps/cs/src/pages/loginlog/ui/loginlog-page.tsx @@ -3,14 +3,11 @@ 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, - type PaginationState, -} from "@tanstack/react-table"; -import { - Button, - DateRangePicker, - Input, + Button, + DateRangePicker, + Input, Label, Select, SelectContent, @@ -18,28 +15,25 @@ import { SelectTrigger, SelectValue, LoadingWithImage, - } from "@common/components"; +} from "@common/components"; import { LoginlogApi } from "@/entities/loginlog"; -import { type LoginlogParams} from "@/entities/loginlog/model/loginlog-interface"; -import { useDataTable } from "@/shared/lib"; +import { type LoginlogParams } from "@/entities/loginlog/model/loginlog-interface"; +import { useDataTable } from "@/shared/lib"; import { ENDPOINT } from "@/shared/config"; import { columns } from "@/pages/loginlog/model/table-columns"; - - type DateRange = { from: Date | undefined; to?: Date | undefined; }; -function LoginlogPage({ }) { - +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); @@ -47,20 +41,19 @@ function LoginlogPage({ }) { pageIndex: 0, pageSize: 10, }); - + 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" + 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 query = useQuery({ queryKey: [ENDPOINT.USER_SERVICE.LOGINLOG, pagination], queryFn: () => @@ -98,10 +91,10 @@ function LoginlogPage({ }) { }; const handleLoginlogParamsChange = - (key: keyof LoginlogParams) => (value: unknown) => { - setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); - }; - + (key: keyof LoginlogParams) => (value: unknown) => { + setLoginlogPostQuery({ ...loginlogPostQuery, [key]: value }); + }; + const handlePresetDateRange = (value: string) => { let presetDateRange: DateRange | undefined; switch (value) { @@ -129,20 +122,18 @@ function LoginlogPage({ }) { 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"), + 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 (
@@ -168,7 +159,7 @@ function LoginlogPage({ }) { handleLoginlogParamsChange("userId")(e.target.value) } @@ -183,8 +174,14 @@ function LoginlogPage({ }) { 오류 -
@@ -212,12 +209,12 @@ function LoginlogPage({ }) {
- -
+
+
{/* Debug: 로딩 상태 표시 */} {(query.isLoading || query.isPending || isManualLoading) && ( -
-
+
+
diff --git a/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx b/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx index 9feea8a02..5cb8bf13d 100644 --- a/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx +++ b/apps/cs/src/pages/privacylog/ui/privacylog-page.tsx @@ -203,4 +203,4 @@ function PrivacyLogPage({ }) { ); } -export {PrivacyLogPage }; +export { PrivacyLogPage }; From 9f0d984a94fe7f4d9da7e9a573a77c1ba7da57fa Mon Sep 17 00:00:00 2001 From: jcchoi Date: Mon, 30 Jun 2025 16:59:09 +0900 Subject: [PATCH 7/9] Update README.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가이드 추가 --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index ba078fb13..0e0043129 100644 --- a/README.md +++ b/README.md @@ -150,6 +150,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) From 419099d233853810fba4ac3843127c503dc87536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=A7=80=EC=B2=A0?= Date: Mon, 30 Jun 2025 19:33:31 +0900 Subject: [PATCH 8/9] =?UTF-8?q?=ED=8D=BC=EA=B0=80=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/widgets/post/ui/post-action.tsx | 22 + pnpm-lock.yaml | 3690 +++++++++-------- 2 files changed, 1994 insertions(+), 1718 deletions(-) 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..eb0acef5f 100644 --- a/apps/common/features/src/widgets/post/ui/post-action.tsx +++ b/apps/common/features/src/widgets/post/ui/post-action.tsx @@ -2,6 +2,7 @@ import { useRouter, usePathname } from "next/navigation"; import { useConfirm } from "@frontend-opensource/use-react-hooks"; import { Button } from "@common/components"; import { useMenuStore, useUserStore } from "@/shared/lib"; +import { useState } from 'react'; type PostActionProps = { bbsId: string; @@ -16,6 +17,7 @@ function PostAction({ children, handleDelete }: PostActionProps) { const { confirm } = useConfirm(); const bbsInfo = useMenuStore((state) => state.bbsInfo); const userInfo = useUserStore((state) => state.userInfo); + const [copied, setCopied] = useState(false); const onDelete = async () => { if (await confirm("삭제하시겠습니까?")) handleDelete(); @@ -33,11 +35,31 @@ function PostAction({ children, handleDelete }: PostActionProps) { // 비회원 return true; }; + const handleCopy = async () => { + try { + await navigator.clipboard.writeText(window.location.href); + setCopied(true); + setTimeout(() => setCopied(false), 2000); // 2초 후 상태 초기화 + alert("복사되었습니다..") + } catch (err) { + console.error('복사 실패:', err); + } + }; + return ( <> {children}
+ +