From 8fedc8657e6b9c5b4834174d7a9836ff45f478da Mon Sep 17 00:00:00 2001 From: wypark Date: Sun, 7 Jun 2026 11:43:52 +0900 Subject: [PATCH] . --- LOG.md | 5 ++ src/app/admin/page.tsx | 56 ++++++++++++++++++- .../dashboard/AdminDashboardTrafficChart.tsx | 52 +++++++++++------ 3 files changed, 93 insertions(+), 20 deletions(-) diff --git a/LOG.md b/LOG.md index f605ac1..c1f0553 100644 --- a/LOG.md +++ b/LOG.md @@ -2,6 +2,11 @@ ## 2026-06-07 +- Fixed the admin dashboard traffic chart visibility by giving bar columns a real height context, so percentage-based bar heights render instead of collapsing. +- Added fallback traffic points from recent/popular post data when the dashboard API or `traffic` array is empty, keeping the graph visible while the backend traffic aggregation is unavailable. +- Validation: `npm run lint` passed and `npm run build` passed. Browser navigation to `/admin` was attempted on `http://localhost:3112/`, but the current browser session was unauthenticated and rendered the login screen, so the admin chart itself was validated by code path and build checks rather than a live authenticated screenshot. +- Recommended next task: verify `/admin` with an authenticated admin session against the production API to confirm the real `traffic` payload replaces the fallback graph. + - Simplified the macOS-inspired layout by removing duplicated navigation surfaces: the desktop shortcut icon layer, the home page's inner Finder sidebar, and app navigation links from the system menu bar. - Reworked the global sidebar into a cleaner library panel focused on profile, search, and categories, with a quieter compact profile row and no duplicated app shortcuts. - Kept app-level movement in the Dock, category/search movement in the sidebar, and current-context/status information in the menu bar so each shell area has a clearer role. diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index d026b24..73be554 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -29,10 +29,17 @@ import { Category, DashboardPostStat, DashboardRange, + DashboardTrafficPoint, PageMeta, Post, } from '@/types'; +const dashboardRangeDays: Record = { + '7d': 7, + '30d': 30, + '90d': 90, +}; + const countCategories = (categories: Category[] = []): number => { return categories.reduce((count, category) => { return count + 1 + countCategories(category.children || []); @@ -89,6 +96,46 @@ const toPostStat = (post: Post): DashboardPostStat => ({ createdAt: post.createdAt, }); +const formatKstDateKey = (date: Date) => { + return new Intl.DateTimeFormat('sv-SE', { + timeZone: 'Asia/Seoul', + year: 'numeric', + month: '2-digit', + day: '2-digit', + }).format(date); +}; + +const getFallbackTraffic = ( + posts: Post[], + range: DashboardRange, +): DashboardTrafficPoint[] => { + const days = dashboardRangeDays[range]; + const today = new Date(); + const points = Array.from({ length: days }, (_, index) => { + const date = new Date(today); + date.setDate(today.getDate() - (days - 1 - index)); + + return { + date: formatKstDateKey(date), + views: 0, + }; + }); + const pointMap = new Map(points.map((point) => [point.date, point])); + + posts.forEach((post) => { + const createdAt = new Date(post.createdAt); + if (Number.isNaN(createdAt.getTime())) return; + + const dateKey = formatKstDateKey(createdAt); + const point = pointMap.get(dateKey); + if (!point) return; + + point.views += post.viewCount; + }); + + return points; +}; + function RecentPostRow({ post }: { post: Post }) { return ( dashboard?.topPosts ?? (popularData?.content ?? []).map(toPostStat), [dashboard?.topPosts, popularData?.content], ); + const fallbackTraffic = useMemo( + () => getFallbackTraffic([...(latestData?.content ?? []), ...(popularData?.content ?? [])], range), + [latestData?.content, popularData?.content, range], + ); + const trafficPoints = dashboard?.traffic?.length ? dashboard.traffic : fallbackTraffic; return (
@@ -301,10 +353,10 @@ export default function AdminPage() { />
diff --git a/src/components/admin/dashboard/AdminDashboardTrafficChart.tsx b/src/components/admin/dashboard/AdminDashboardTrafficChart.tsx index e6ce7d4..241c159 100644 --- a/src/components/admin/dashboard/AdminDashboardTrafficChart.tsx +++ b/src/components/admin/dashboard/AdminDashboardTrafficChart.tsx @@ -34,6 +34,7 @@ export default function AdminDashboardTrafficChart({ isFallback, }: AdminDashboardTrafficChartProps) { const maxViews = Math.max(...points.map((point) => point.views), 1); + const hasTraffic = points.some((point) => point.views > 0); return ( @@ -44,7 +45,9 @@ export default function AdminDashboardTrafficChart({

트래픽 흐름

- 최근 기간별 조회수 흐름을 확인합니다. + {isFallback + ? '대시보드 집계 전에는 게시글 누적 조회수로 임시 흐름을 표시합니다.' + : '최근 기간별 조회수 흐름을 확인합니다.'}

{points.length > 0 ? ( -
- {points.map((point) => { - const height = Math.max((point.views / maxViews) * 100, 4); + <> +
+
+ {points.map((point) => { + const height = Math.max((point.views / maxViews) * 100, 4); - return ( -
-
- - {formatDay(point.date)} - -
- ); - })} -
+ return ( +
+
+
+
+ + {formatDay(point.date)} + +
+ ); + })} +
+
+ {isFallback && ( +

+ {hasTraffic + ? '임시 그래프는 글 발행일과 누적 조회수를 기준으로 계산됩니다.' + : '집계 API가 연결되면 실제 일별 조회수로 대체됩니다.'} +

+ )} + ) : (