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/src/widgets/post/ui/post-action.tsx b/apps/common/features/src/widgets/post/ui/post-action.tsx index b063f7636..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,8 +1,8 @@ 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 { AuthorityButton } from "@/entities/menu-authority"; +import { useMenuStore, useUserStore } from "@/shared/lib"; type PostActionProps = { registerId?: string; 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/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..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/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/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/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/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() {

회원 목록