From 4e04676a57fc9c16b531aca35831304d404d2441 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 29 Jul 2025 11:28:01 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[apps/cs]=20chore:=20=EB=8C=80=EC=8B=9C?= =?UTF-8?q?=EB=B3=B4=EB=93=9C=20=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=ED=91=9C=EA=B8=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/pages/dashboard/ui/dashboard-page.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx b/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx index 4bae35f7c..b217531dd 100644 --- a/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx +++ b/apps/cs/src/pages/dashboard/ui/dashboard-page.tsx @@ -118,7 +118,7 @@ function DashboardPage() { 금일 신규 가입자 {data?.memberTodayCnt ? data.memberTodayCnt.toLocaleString() - : "-"} + : "0"} 명

@@ -141,7 +141,7 @@ function DashboardPage() {

금일 + - {data?.pstTodayCnt ? data.pstTodayCnt.toLocaleString() : "-"}{" "} + {data?.pstTodayCnt ? data.pstTodayCnt.toLocaleString() : "0"} 등록

@@ -168,7 +168,7 @@ function DashboardPage() { 전일 대비 + {data?.accessBefDayPer ? data.accessBefDayPer.toLocaleString() - : "-"} + : "0"} %

From 387362d42bd510b4f63404a692949b27a44136cf Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 29 Jul 2025 11:28:48 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[apps/cs]=20feat:=20=EA=B2=8C=EC=8B=9C?= =?UTF-8?q?=EA=B8=80=20=EB=82=B4=20=EB=AA=A9=EB=A1=9D=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/cs/src/widgets/post/ui/post-action.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/cs/src/widgets/post/ui/post-action.tsx b/apps/cs/src/widgets/post/ui/post-action.tsx index 421cb4542..860d8dad8 100644 --- a/apps/cs/src/widgets/post/ui/post-action.tsx +++ b/apps/cs/src/widgets/post/ui/post-action.tsx @@ -25,6 +25,7 @@ function PostAction({ children, handleDelete }: PostActionProps) { const { prompt } = useSelectPrompt(selectData); const bbsInfo = useMenuStore((state) => state.bbsInfo); const userInfo = useUserStore((state) => state.userInfo); + const postPageLink = `/bbs/${bbsInfo?.bbsTypeCd.toLowerCase()}/${bbsInfo?.bbsId}`; const onDelete = async () => { const { resolved, value } = await prompt("삭제 사유를 입력해주세요."); @@ -64,7 +65,7 @@ function PostAction({ children, handleDelete }: PostActionProps) { size="lg" color="white" type="button" - onClick={() => router.back()} + onClick={() => router.push(postPageLink)} > 목록 From 12f7fe53ad759a518b44bfaaa479732ff8340c51 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 29 Jul 2025 13:23:48 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[apps/cs]=20feat:=20=EC=A0=91=EC=86=8D?= =?UTF-8?q?=EC=9E=90=ED=86=B5=EA=B3=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=B0=94=EC=9D=B8=EB=94=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../cs/src/pages/user-statistics/api/index.ts | 1 - .../api/user-statistics-service.ts | 147 ++--------- .../pages/user-statistics/model/chart-data.ts | 245 +++++++----------- .../model/user-statistics-interface.ts | 84 +++--- .../ui/user-statistics-page.tsx | 163 +++++------- apps/cs/src/shared/config/api.ts | 1 + 6 files changed, 220 insertions(+), 421 deletions(-) delete mode 100644 apps/cs/src/pages/user-statistics/api/index.ts diff --git a/apps/cs/src/pages/user-statistics/api/index.ts b/apps/cs/src/pages/user-statistics/api/index.ts deleted file mode 100644 index 802b91e30..000000000 --- a/apps/cs/src/pages/user-statistics/api/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { UserStatisticsApi } from "./user-statistics-service"; diff --git a/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts b/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts index 10c90c024..d0522267b 100644 --- a/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts +++ b/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts @@ -1,138 +1,21 @@ +import { client } from "@/shared/api"; import { - type AccessLog, - type AccessLogParams, - type AccessLogService, - type PageableList, + type UserStatisticsService, + type ConnectionStatisticsParams, + type ConnectionStatistics, } from "../model/user-statistics-interface"; +import { ENDPOINT } from "@/shared/config"; -// 목 데이터 -const mockAccessLogs: AccessLog[] = [ - { - no: 1, - accessTime: "2024-01-15 14:30:25", - accessIp: "192.168.1.100", - accessRegion: "서울특별시", - urlPath: "/index", - browser: "Chrome", - site: "SITE1", - accessType: "메인", - }, - { - no: 2, - accessTime: "2024-01-15 14:28:15", - accessIp: "203.241.50.123", - accessRegion: "부산광역시", - urlPath: "/board/list", - browser: "Edge", - site: "SITE2", - accessType: "게시판", - }, - { - no: 3, - accessTime: "2024-01-15 14:25:42", - accessIp: "175.223.45.67", - accessRegion: "대구광역시", - urlPath: "/statistics", - browser: "Safari", - site: "SITE1", - accessType: "통계", - }, - { - no: 4, - accessTime: "2024-01-15 14:22:18", - accessIp: "211.252.87.45", - accessRegion: "인천광역시", - urlPath: "/contents/view", - browser: "Firefox", - site: "SITE3", - accessType: "컨텐츠", - }, - { - no: 5, - accessTime: "2024-01-15 14:20:33", - accessIp: "121.254.12.89", - accessRegion: "광주광역시", - urlPath: "/search", - browser: "Chrome", - site: "SITE1", - accessType: "검색", - }, -]; - -// 목 API 서비스 -class UserStatisticsApiImpl implements AccessLogService { - async getAccessLogList( - params: AccessLogParams, - ): Promise> { - // 실제 API 호출 대신 목 데이터 반환 - const { page = 0, size = 10 } = params; - - // 필터링 로직 (실제로는 서버에서 처리) - let filteredLogs = [...mockAccessLogs]; - - if (params.urlPath) { - filteredLogs = filteredLogs.filter((log) => - log.urlPath.toLowerCase().includes(params.urlPath!.toLowerCase()), - ); - } - - if (params.accessIp) { - filteredLogs = filteredLogs.filter((log) => - log.accessIp.includes(params.accessIp!), - ); - } - - if (params.accessRegion) { - filteredLogs = filteredLogs.filter((log) => - log.accessRegion.includes(params.accessRegion!), - ); - } - - if (params.browser) { - filteredLogs = filteredLogs.filter( - (log) => log.browser.toLowerCase() === params.browser!.toLowerCase(), - ); - } - - if (params.siteId) { - filteredLogs = filteredLogs.filter((log) => log.site === params.siteId); - } - - // 페이지네이션 - const start = page * size; - const end = start + size; - const paginatedLogs = filteredLogs.slice(start, end); - - // 목 응답 생성 - return { - content: paginatedLogs, - pageable: { - pageNumber: page, - pageSize: size, - sort: { - empty: true, - sorted: false, - unsorted: true, - }, - offset: page * size, - paged: true, - unpaged: false, - }, - last: (page + 1) * size >= filteredLogs.length, - totalPages: Math.ceil(filteredLogs.length / size), - totalElements: filteredLogs.length, - size, - number: page, - sort: { - empty: true, - sorted: false, - unsorted: true, - }, - first: page === 0, - numberOfElements: paginatedLogs.length, - empty: paginatedLogs.length === 0, - }; +class UserStatisticsApiService implements UserStatisticsService { + async getConnectionStatistics( + params: ConnectionStatisticsParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONNECTION_LOG}/stat`, + { params }, + ); + return response.data; } } -export const UserStatisticsApi = new UserStatisticsApiImpl(); +export const UserStatisticsApi = new UserStatisticsApiService(); diff --git a/apps/cs/src/pages/user-statistics/model/chart-data.ts b/apps/cs/src/pages/user-statistics/model/chart-data.ts index c836a4e5d..8c4241cfd 100644 --- a/apps/cs/src/pages/user-statistics/model/chart-data.ts +++ b/apps/cs/src/pages/user-statistics/model/chart-data.ts @@ -1,148 +1,103 @@ -import { type ChartData, type ChartOptions } from "chart.js"; - -// 일별 접속 현황 차트 데이터 -export const monthlyStatisticsData = { - labels: [ - "1일", - "2일", - "3일", - "4일", - "5일", - "6일", - "7일", - "8일", - "9일", - "10일", - "11일", - "12일", - "13일", - "14일", - "15일", - ], - datasets: [ - { - type: "bar" as const, - label: "페이지뷰", - data: [ - 850, 1000, 750, 750, 700, 750, 780, 800, 850, 900, 1200, 1700, 2300, - 2100, 2600, - ], - backgroundColor: "rgba(54, 162, 235, 0.8)", - order: 2, - }, - { - type: "line" as const, - label: "신규 접속자", - data: [ - 700, 600, 500, 550, 500, 550, 500, 550, 600, 850, 1200, 1500, 1700, - 2300, 2200, - ], - borderColor: "rgba(255, 99, 132, 0.8)", - borderWidth: 2, - tension: 0.4, - pointRadius: 3, - order: 0, - }, - { - type: "line" as const, - label: "총 접속자", - data: [ - 200, 300, 400, 500, 600, 500, 550, 500, 400, 500, 350, 800, 900, 1200, - 1300, - ], - borderColor: "rgba(75, 192, 192, 0.8)", - borderWidth: 2, - tension: 0.4, - pointRadius: 3, - order: 1, - }, - ], -}; - -export const monthlyStatisticsOptions: ChartOptions<"bar"> = { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: "top" as const, - }, - tooltip: { - mode: "index" as const, - intersect: false, - }, - }, -}; - -// 사이트별 접속자 차트 데이터 -export const siteVisitorsData: ChartData<"bar"> = { - labels: ["SITE1", "SITE2", "SITE3", "SITE4", "SITE5"], - datasets: [ - { - label: "사이트별 접속자", - data: [2500, 2000, 1500, 1200, 800], - backgroundColor: [ - "rgba(54, 162, 235, 0.8)", - "rgba(153, 102, 255, 0.8)", - "rgba(255, 99, 132, 0.8)", - "rgba(255, 159, 64, 0.8)", - "rgba(255, 205, 86, 0.8)", - ], - borderWidth: 1, - }, - ], -}; - -export const siteVisitorsOptions: ChartOptions<"bar"> = { - responsive: true, - maintainAspectRatio: false, - indexAxis: "y" as const, - plugins: { - legend: { - display: false, - }, - }, -}; - -// 브라우저별 접속자 차트 데이터 -export const browserStatisticsData: ChartData<"doughnut"> = { - labels: ["Chrome", "Edge", "Safari", "Firefox", "Internet Explorer"], - datasets: [ - { - data: [45, 25, 15, 10, 5], - backgroundColor: [ - "rgba(54, 162, 235, 0.8)", - "rgba(153, 102, 255, 0.8)", - "rgba(255, 99, 132, 0.8)", - "rgba(255, 159, 64, 0.8)", - "rgba(255, 205, 86, 0.8)", - ], - borderColor: [ - "rgba(54, 162, 235, 1)", - "rgba(153, 102, 255, 1)", - "rgba(255, 99, 132, 1)", - "rgba(255, 159, 64, 1)", - "rgba(255, 205, 86, 1)", - ], - borderWidth: 1, - }, - ], -}; - -export const browserStatisticsOptions: ChartOptions<"doughnut"> = { - responsive: true, - maintainAspectRatio: false, - plugins: { - legend: { - position: "bottom" as const, - }, - }, -}; +import type { ChartData, ChartOptions } from "chart.js"; +import { getCssVariableValue } from "@common/components"; +import type { ConnectionStatistics } from "./user-statistics-interface"; export const chartData = { - monthlyStatisticsData, - monthlyStatisticsOptions, - siteVisitorsData, - siteVisitorsOptions, - browserStatisticsData, - browserStatisticsOptions, + day: ( + data: ConnectionStatistics, + ): { + data: ChartData<"line">; + options: ChartOptions<"line">; + } => { + return { + data: { + labels: data.dayList.map((item) => item.dayYmd), + datasets: [ + { + type: "line", + label: "접속자 수", + data: data.dayList.map((item) => item.accessCnt), + borderColor: getCssVariableValue("--chart-1"), + }, + ], + }, + options: { + maintainAspectRatio: false, + scales: { + y: { + ticks: { + precision: 0, + }, + }, + }, + }, + }; + }, + site: ( + data: ConnectionStatistics, + ): { + data: ChartData<"bar">; + options: ChartOptions<"bar">; + } => { + return { + data: { + labels: data.siteList.map((item) => item.siteNm), + datasets: [ + { + type: "bar", + label: "사이트별 접속자", + data: data.siteList.map((item) => item.accessCnt), + backgroundColor: data.siteList.map((_, index) => + getCssVariableValue(`--chart-${(index % 5) + 1}`), + ), + }, + ], + }, + options: { + maintainAspectRatio: false, + indexAxis: "y", + scales: { + x: { + ticks: { + precision: 0, + }, + }, + }, + plugins: { + legend: { + display: false, + }, + }, + }, + }; + }, + browser: ( + data: ConnectionStatistics, + ): { + data: ChartData<"doughnut">; + options: ChartOptions<"doughnut">; + } => { + return { + data: { + labels: data.browserList.map((item) => item.wbsrKndNm), + datasets: [ + { + type: "doughnut", + data: data.browserList.map((item) => item.accessCnt), + backgroundColor: data.browserList.map((_, index) => + getCssVariableValue(`--chart-${(index % 5) + 1}`), + ), + }, + ], + }, + options: { + maintainAspectRatio: false, + plugins: { + legend: { + position: "bottom", + }, + }, + }, + }; + }, }; diff --git a/apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts b/apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts index 124d91044..42d247185 100644 --- a/apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts +++ b/apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts @@ -1,56 +1,40 @@ -export interface AccessLog { - no: number; - accessTime: string; - accessIp: string; - accessRegion: string; - urlPath: string; - browser: string; - site: string; - accessType: string; -} - -export interface AccessLogParams { - page?: number; - size?: number; - urlPath?: string; - accessIp?: string; - accessRegion?: string; - browser?: string; - accessDateStart?: string; - accessDateEnd?: string; +export interface ConnectionStatisticsParams { + keywordType?: string; + keyword?: string; siteId?: string; - sort?: string; + cntnIp?: string; + cntnRgn?: string; + wbsrKnd?: string; + urlPath?: string; + cntnDtStart?: string; + cntnDtEnd?: string; } -export interface AccessLogService { - getAccessLogList(params: AccessLogParams): Promise>; +interface DayConnectionStatistics { + dayYmd: string; + accessCnt: number; } -export interface PageableList { - content: T[]; - pageable: { - pageNumber: number; - pageSize: number; - sort: { - empty: boolean; - sorted: boolean; - unsorted: boolean; - }; - offset: number; - paged: boolean; - unpaged: boolean; - }; - last: boolean; - totalPages: number; - totalElements: number; - size: number; - number: number; - sort: { - empty: boolean; - sorted: boolean; - unsorted: boolean; - }; - first: boolean; - numberOfElements: number; - empty: boolean; +interface SiteConnectionStatistics { + siteId: string; + siteNm: string; + accessCnt: number; +} + +interface BrowserConnectionStatistics { + wbsrKnd: string; + wbsrKndNm: string; + accessCnt: number; +} + +export interface ConnectionStatistics { + dayList: DayConnectionStatistics[]; + siteList: SiteConnectionStatistics[]; + browserList: BrowserConnectionStatistics[]; +} + +export interface UserStatisticsService { + getConnectionStatistics( + params: ConnectionStatisticsParams, + ): Promise; } diff --git a/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx b/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx index 9fedb47f4..4fe1d3fc0 100644 --- a/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx +++ b/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx @@ -15,9 +15,8 @@ import { Filler, Legend, Tooltip, - type ChartData, } from "chart.js"; -import { Bar, Doughnut } from "react-chartjs-2"; +import { Bar, Doughnut, Line } from "react-chartjs-2"; import { Button, Input, @@ -30,11 +29,13 @@ import { } from "@common/components"; import { SiteSelect } from "@/entities/site"; import { useDataTable } from "@/shared/lib"; +import { ENDPOINT } from "@/shared/config"; +import { NoData } from "@/shared/ui/no-data"; import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker"; -import { UserStatisticsApi } from "../api"; +import { UserStatisticsApi } from "../api/user-statistics-service"; import { chartData } from "../model/chart-data"; import { columns } from "../model/table-columns"; -import { type AccessLogParams } from "../model/user-statistics-interface"; +import { type ConnectionStatisticsParams } from "../model/user-statistics-interface"; ChartJS.register( CategoryScale, @@ -65,44 +66,41 @@ function UserStatisticsPage() { }); const [keywordType, setKeywordType] = useState(""); const [keyword, setKeyword] = useState(""); + const [siteId, setSiteId] = useState(""); const [presetDateRange, setPresetDateRange] = useState("1W"); const [dateRange, setDateRange] = useState(defaultDateRange); - const [searchParams, setSearchParams] = useState({ - urlPath: "", - accessIp: "", - accessRegion: "", - browser: "", - accessDateStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"), - accessDateEnd: dayjs().format("YYYY-MM-DD"), - sort: "accessTime,desc", + const [searchParams, setSearchParams] = useState({ + keywordType: "", + keyword: "", + siteId: "", + cntnDtStart: dayjs(defaultDateRange.from).format("YYYY-MM-DD"), + cntnDtEnd: dayjs(defaultDateRange.to).format("YYYY-MM-DD"), }); const query = useQuery({ - queryKey: ["user-statistics", searchParams, pagination], + queryKey: [ENDPOINT.CMS_SERVICE.CONNECTION_LOG, searchParams], queryFn: () => - UserStatisticsApi.getAccessLogList({ + UserStatisticsApi.getConnectionStatistics({ ...searchParams, - page: pagination.pageIndex, - size: pagination.pageSize, }), placeholderData: keepPreviousData, }); - const { tableComponent, tableInstance: table } = useDataTable({ - query, - tableOptions: { - columns, - state: { - pagination, - }, - manualPagination: true, - getCoreRowModel: getCoreRowModel(), - onPaginationChange: setPagination, - }, - dataTableOptions: { - viewPagination: true, - }, - }); + // const { tableComponent, tableInstance: table } = useDataTable({ + // query, + // tableOptions: { + // columns, + // state: { + // pagination, + // }, + // manualPagination: true, + // getCoreRowModel: getCoreRowModel(), + // onPaginationChange: setPagination, + // }, + // dataTableOptions: { + // viewPagination: true, + // }, + // }); const handlePresetDateRange = (value: string) => { let presetDateRange: DateRange; @@ -139,54 +137,30 @@ function UserStatisticsPage() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - setPagination({ - ...pagination, - pageIndex: 0, - }); - - // 검색 조건에 따라 검색 파라미터 설정 - const params: AccessLogParams = { - sort: "accessTime,desc", - accessDateStart: dateRange.from + const params: ConnectionStatisticsParams = { + keywordType, + keyword, + siteId, + cntnDtStart: dateRange.from ? dayjs(dateRange.from).format("YYYY-MM-DD") : "", - accessDateEnd: dateRange.to - ? dayjs(dateRange.to).format("YYYY-MM-DD") - : "", + cntnDtEnd: dateRange.to ? dayjs(dateRange.to).format("YYYY-MM-DD") : "", }; - - // 검색어 타입에 따라 파라미터 설정 - switch (keywordType) { - case "urlPath": - params.urlPath = keyword; - break; - case "ip": - params.accessIp = keyword; - break; - case "region": - params.accessRegion = keyword; - break; - case "browser": - params.browser = keyword; - break; - } - setSearchParams(params); }; const handleReset = () => { setKeywordType(""); setKeyword(""); + setSiteId(""); setDateRange(defaultDateRange); setPresetDateRange("1W"); setSearchParams({ - urlPath: "", - accessIp: "", - accessRegion: "", - browser: "", - accessDateStart: dayjs().subtract(7, "day").format("YYYY-MM-DD"), - accessDateEnd: dayjs().format("YYYY-MM-DD"), - sort: "accessTime,desc", + keywordType: "", + keyword: "", + siteId: "", + cntnDtStart: dayjs(defaultDateRange.from).format("YYYY-MM-DD"), + cntnDtEnd: dayjs(defaultDateRange.to).format("YYYY-MM-DD"), }); }; @@ -210,7 +184,11 @@ function UserStatisticsPage() { setDateRange={handleDateRangeChange} /> - + setSiteId(value)} + /> - {/* 차트 영역 */}
- {/* 일별 접속 현황 */}
-

일별 접속 현황

+

일별 접속 현황

- } - options={chartData.monthlyStatisticsOptions} - /> + {query.data?.dayList.length ? ( + + ) : ( + + )}
- {/* 사이트별 접속자 */}
-

사이트별 접속자

+

사이트별 접속자

- + {query.data?.siteList.length ? ( + + ) : ( + + )}
- {/* 브라우저별 접속자 */}
-

브라우저별 접속자

+

브라우저별 접속자

- + {query.data?.browserList.length ? ( + + ) : ( + + )}
@@ -295,7 +272,7 @@ function UserStatisticsPage() {

접속 로그 상세

- + */}
-
{tableComponent}
+ {/*
{tableComponent}
*/} ); diff --git a/apps/cs/src/shared/config/api.ts b/apps/cs/src/shared/config/api.ts index 6db62f1a0..ce6f61381 100644 --- a/apps/cs/src/shared/config/api.ts +++ b/apps/cs/src/shared/config/api.ts @@ -55,6 +55,7 @@ export const ENDPOINT = { MANAGER_IP: "/cms-service/api/v1/mgrip", APILOG: "/cms-service/api/v1/api-logs", SITE_AUTHORITY: "/cms-service/api/v1/site-authrt", + CONNECTION_LOG: "/cms-service/api/v1/cntnlog", }, BOARD_SERVICE: { BBS: "/board-service/api/v1/bbs", From c222e809b68e0eabc386ed573199ba5e4313c959 Mon Sep 17 00:00:00 2001 From: bicochan Date: Tue, 29 Jul 2025 15:44:47 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[apps/cs]=20feat:=20=EC=A0=91=EC=86=8D?= =?UTF-8?q?=EC=9E=90=20=ED=86=B5=EA=B3=84=20>=20=EC=A0=91=EC=86=8D=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=20=EC=83=81=EC=84=B8=20=ED=85=8C=EC=9D=B4?= =?UTF-8?q?=EB=B8=94=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/connection-log-service.ts | 33 ++++++++++ .../api/user-statistics-service.ts | 21 ------ .../pages/user-statistics/model/chart-data.ts | 2 +- ...terface.ts => connection-log-interface.ts} | 34 +++++++++- .../user-statistics/model/table-columns.ts | 65 ++++++++++--------- .../ui/user-statistics-page.tsx | 63 ++++++++++-------- 6 files changed, 139 insertions(+), 79 deletions(-) create mode 100644 apps/cs/src/pages/user-statistics/api/connection-log-service.ts delete mode 100644 apps/cs/src/pages/user-statistics/api/user-statistics-service.ts rename apps/cs/src/pages/user-statistics/model/{user-statistics-interface.ts => connection-log-interface.ts} (50%) diff --git a/apps/cs/src/pages/user-statistics/api/connection-log-service.ts b/apps/cs/src/pages/user-statistics/api/connection-log-service.ts new file mode 100644 index 000000000..0e2daeb5b --- /dev/null +++ b/apps/cs/src/pages/user-statistics/api/connection-log-service.ts @@ -0,0 +1,33 @@ +import { client } from "@/shared/api"; +import { ENDPOINT } from "@/shared/config"; +import { + type ConnectionLogParams, + type ConnectionLogList, + type ConnectionStatisticsParams, + type ConnectionStatistics, + type ConnectionLogService, +} from "../model/connection-log-interface"; + +class ConnectionLogApiService implements ConnectionLogService { + async getConnectionLog( + params: ConnectionLogParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONNECTION_LOG}`, + { params }, + ); + return response.data; + } + + async getConnectionStatistics( + params: ConnectionStatisticsParams, + ): Promise { + const response = await client.get( + `${ENDPOINT.CMS_SERVICE.CONNECTION_LOG}/stat`, + { params }, + ); + return response.data; + } +} + +export const ConnectionLogApi = new ConnectionLogApiService(); diff --git a/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts b/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts deleted file mode 100644 index d0522267b..000000000 --- a/apps/cs/src/pages/user-statistics/api/user-statistics-service.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { client } from "@/shared/api"; -import { - type UserStatisticsService, - type ConnectionStatisticsParams, - type ConnectionStatistics, -} from "../model/user-statistics-interface"; -import { ENDPOINT } from "@/shared/config"; - -class UserStatisticsApiService implements UserStatisticsService { - async getConnectionStatistics( - params: ConnectionStatisticsParams, - ): Promise { - const response = await client.get( - `${ENDPOINT.CMS_SERVICE.CONNECTION_LOG}/stat`, - { params }, - ); - return response.data; - } -} - -export const UserStatisticsApi = new UserStatisticsApiService(); diff --git a/apps/cs/src/pages/user-statistics/model/chart-data.ts b/apps/cs/src/pages/user-statistics/model/chart-data.ts index 8c4241cfd..455dcac56 100644 --- a/apps/cs/src/pages/user-statistics/model/chart-data.ts +++ b/apps/cs/src/pages/user-statistics/model/chart-data.ts @@ -1,6 +1,6 @@ import type { ChartData, ChartOptions } from "chart.js"; import { getCssVariableValue } from "@common/components"; -import type { ConnectionStatistics } from "./user-statistics-interface"; +import type { ConnectionStatistics } from "./connection-log-interface"; export const chartData = { day: ( diff --git a/apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts b/apps/cs/src/pages/user-statistics/model/connection-log-interface.ts similarity index 50% rename from apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts rename to apps/cs/src/pages/user-statistics/model/connection-log-interface.ts index 42d247185..6fbf4dc15 100644 --- a/apps/cs/src/pages/user-statistics/model/user-statistics-interface.ts +++ b/apps/cs/src/pages/user-statistics/model/connection-log-interface.ts @@ -1,3 +1,32 @@ +import type { PageableList, PageableParams } from "@/shared/model"; + +export interface ConnectionLogParams extends PageableParams { + keywordType?: string; + keyword?: string; + siteId?: string; + cntnIp?: string; + cntnRgn?: string; + wbsrKnd?: string; + urlPath?: string; + cntnDtStart?: string; + cntnDtEnd?: string; +} + +export interface ConnectionLog { + cntnSn: number; + cntnDt: string; + cntnId: string; + cntnIp: string; + cntnRgn: string; + wbsrKnd: string; + menuCd: number; + siteId: string; + urlPath: string; + siteNm: string; + menuNm: string; + wbsrKndNm: string; +} + export interface ConnectionStatisticsParams { keywordType?: string; keyword?: string; @@ -33,7 +62,10 @@ export interface ConnectionStatistics { browserList: BrowserConnectionStatistics[]; } -export interface UserStatisticsService { +export type ConnectionLogList = PageableList; + +export interface ConnectionLogService { + getConnectionLog(params: ConnectionLogParams): Promise; getConnectionStatistics( params: ConnectionStatisticsParams, ): Promise; diff --git a/apps/cs/src/pages/user-statistics/model/table-columns.ts b/apps/cs/src/pages/user-statistics/model/table-columns.ts index c0d53380e..09fe58adf 100644 --- a/apps/cs/src/pages/user-statistics/model/table-columns.ts +++ b/apps/cs/src/pages/user-statistics/model/table-columns.ts @@ -1,39 +1,46 @@ -import { createColumnHelper } from "@tanstack/react-table"; -import { type AccessLog } from "./user-statistics-interface"; +import type { ColumnDef } from "@tanstack/react-table"; +import type { ConnectionLog } from "./connection-log-interface"; +import dayjs from "dayjs"; -const columnHelper = createColumnHelper(); - -export const columns = [ - columnHelper.accessor("no", { +export const columns: ColumnDef[] = [ + { header: "No", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("accessTime", { + 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: "cntnDt", header: "접속일시", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("accessIp", { + cell: ({ row }) => { + return dayjs(row.original.cntnDt).format("YYYY-MM-DD HH:mm:ss"); + }, + }, + { + accessorKey: "cntnIp", header: "접속IP", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("accessRegion", { + }, + { + accessorKey: "cntnRgn", header: "접속지역", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("urlPath", { + }, + { + accessorKey: "urlPath", header: "URL경로", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("browser", { + }, + { + accessorKey: "wbsrKndNm", header: "브라우저", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("site", { + }, + { + accessorKey: "siteNm", header: "사이트", - cell: (info) => info.getValue(), - }), - columnHelper.accessor("accessType", { + }, + { + accessorKey: "menuNm", header: "메뉴명", - cell: (info) => info.getValue(), - }), + }, ]; diff --git a/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx b/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx index 4fe1d3fc0..801ca5eb0 100644 --- a/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx +++ b/apps/cs/src/pages/user-statistics/ui/user-statistics-page.tsx @@ -32,10 +32,10 @@ import { useDataTable } from "@/shared/lib"; import { ENDPOINT } from "@/shared/config"; import { NoData } from "@/shared/ui/no-data"; import { InputDateRangePicker } from "@/shared/ui/input-date-range-picker"; -import { UserStatisticsApi } from "../api/user-statistics-service"; +import { ConnectionLogApi } from "../api/connection-log-service"; import { chartData } from "../model/chart-data"; import { columns } from "../model/table-columns"; -import { type ConnectionStatisticsParams } from "../model/user-statistics-interface"; +import type { ConnectionStatisticsParams } from "../model/connection-log-interface"; ChartJS.register( CategoryScale, @@ -80,27 +80,36 @@ function UserStatisticsPage() { const query = useQuery({ queryKey: [ENDPOINT.CMS_SERVICE.CONNECTION_LOG, searchParams], queryFn: () => - UserStatisticsApi.getConnectionStatistics({ + ConnectionLogApi.getConnectionLog({ ...searchParams, }), placeholderData: keepPreviousData, }); - // const { tableComponent, tableInstance: table } = useDataTable({ - // query, - // tableOptions: { - // columns, - // state: { - // pagination, - // }, - // manualPagination: true, - // getCoreRowModel: getCoreRowModel(), - // onPaginationChange: setPagination, - // }, - // dataTableOptions: { - // viewPagination: true, - // }, - // }); + const { data: connectionStatisticsData } = useQuery({ + queryKey: [ENDPOINT.CMS_SERVICE.CONNECTION_LOG, searchParams], + queryFn: () => + ConnectionLogApi.getConnectionStatistics({ + ...searchParams, + }), + placeholderData: keepPreviousData, + }); + + const { tableComponent, tableInstance: table } = useDataTable({ + query, + tableOptions: { + columns, + state: { + pagination, + }, + manualPagination: true, + getCoreRowModel: getCoreRowModel(), + onPaginationChange: setPagination, + }, + dataTableOptions: { + viewPagination: true, + }, + }); const handlePresetDateRange = (value: string) => { let presetDateRange: DateRange; @@ -229,8 +238,8 @@ function UserStatisticsPage() {

일별 접속 현황

- {query.data?.dayList.length ? ( - + {connectionStatisticsData?.dayList?.length ? ( + ) : ( )} @@ -243,8 +252,8 @@ function UserStatisticsPage() {

사이트별 접속자

- {query.data?.siteList.length ? ( - + {connectionStatisticsData?.siteList?.length ? ( + ) : ( )} @@ -256,8 +265,8 @@ function UserStatisticsPage() {

브라우저별 접속자

- {query.data?.browserList.length ? ( - + {connectionStatisticsData?.browserList?.length ? ( + ) : ( )} @@ -272,7 +281,7 @@ function UserStatisticsPage() {

접속 로그 상세

- {/* */} +
- {/*
{tableComponent}
*/} +
{tableComponent}
);