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] =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20API=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80,=20sort=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=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() {

회원 목록