From c8bb675ba4dc53d73997f1319c87a000523756bb Mon Sep 17 00:00:00 2001 From: zephyrdark Date: Fri, 13 Feb 2026 23:54:09 +0900 Subject: [PATCH] feat: add data explorer to sidebar navigation and fix gitignore MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "데이터 탐색" menu item to sidebar with Search icon - Add "수집 데이터 조회" link button on data management page - Fix sidebar active state to correctly distinguish /admin/data from /admin/data/explorer - Add page title mapping for data explorer in header - Fix .gitignore: add negation for frontend/src/app/admin/data/ so admin data pages are tracked without needing git add -f - Fix dashboard loading state (return null → skeleton with layout) Co-Authored-By: Claude Opus 4.6 --- .gitignore | 1 + frontend/src/app/admin/data/page.tsx | 8 ++++++- frontend/src/app/page.tsx | 21 ++++++++++++++++++- frontend/src/components/layout/new-header.tsx | 13 ++++++++---- .../src/components/layout/new-sidebar.tsx | 7 +++++-- 5 files changed, 42 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index 00f87f9..da3ba3b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,7 @@ Thumbs.db *.db *.sqlite3 data/ +!frontend/src/app/admin/data/ # Test .coverage diff --git a/frontend/src/app/admin/data/page.tsx b/frontend/src/app/admin/data/page.tsx index 3b87688..c68d72c 100644 --- a/frontend/src/app/admin/data/page.tsx +++ b/frontend/src/app/admin/data/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useRef, useCallback } from 'react'; import { useRouter } from 'next/navigation'; +import Link from 'next/link'; import { DashboardLayout } from '@/components/layout/dashboard-layout'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; @@ -135,7 +136,12 @@ export default function DataManagementPage() { return ( -

데이터 수집 관리

+
+

데이터 수집 관리

+ +
{error && (
diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 8519ccc..302dfa9 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -84,7 +84,26 @@ export default function DashboardPage() { }, [router]); if (loading) { - return null; // DashboardLayout handles skeleton + return ( + +
+
+ {Array.from({ length: 4 }).map((_, i) => ( + + +
+
+ + + ))} +
+
+
+
+
+
+ + ); } const totalValue = portfolios.reduce((sum, p) => sum + (p.total_value ?? 0), 0); diff --git a/frontend/src/components/layout/new-header.tsx b/frontend/src/components/layout/new-header.tsx index f99e07f..be45981 100644 --- a/frontend/src/components/layout/new-header.tsx +++ b/frontend/src/components/layout/new-header.tsx @@ -10,7 +10,8 @@ const pageTitles: Record = { '/portfolio': '포트폴리오', '/strategy': '전략', '/backtest': '백테스트', - '/admin/data': '데이터 관리', + '/admin/data': '데이터 수집', + '/admin/data/explorer': '데이터 탐색', }; function getPageTitle(pathname: string): string { @@ -19,12 +20,16 @@ function getPageTitle(pathname: string): string { return pageTitles[pathname]; } - // Check for partial matches (for nested routes) + // Check for partial matches (for nested routes), prefer longest match + let bestMatch = ''; + let bestTitle = ''; for (const [path, title] of Object.entries(pageTitles)) { - if (path !== '/' && pathname.startsWith(path)) { - return title; + if (path !== '/' && pathname.startsWith(path) && path.length > bestMatch.length) { + bestMatch = path; + bestTitle = title; } } + if (bestTitle) return bestTitle; return '대시보드'; } diff --git a/frontend/src/components/layout/new-sidebar.tsx b/frontend/src/components/layout/new-sidebar.tsx index e023011..e8abf58 100644 --- a/frontend/src/components/layout/new-sidebar.tsx +++ b/frontend/src/components/layout/new-sidebar.tsx @@ -9,6 +9,7 @@ import { TrendingUp, FlaskConical, Database, + Search, ChevronLeft, ChevronRight, } from 'lucide-react'; @@ -27,7 +28,8 @@ const navItems = [ { href: '/portfolio', label: '포트폴리오', icon: Briefcase }, { href: '/strategy', label: '전략', icon: TrendingUp }, { href: '/backtest', label: '백테스트', icon: FlaskConical }, - { href: '/admin/data', label: '데이터 관리', icon: Database }, + { href: '/admin/data', label: '데이터 수집', icon: Database }, + { href: '/admin/data/explorer', label: '데이터 탐색', icon: Search }, ]; interface NewSidebarProps { @@ -84,7 +86,8 @@ export function NewSidebar({ collapsed = false, onCollapsedChange }: NewSidebarP {navItems.map((item) => { const isActive = pathname === item.href || - (item.href !== '/' && pathname.startsWith(item.href)); + (item.href !== '/' && pathname.startsWith(item.href) && + !navItems.some((other) => other.href !== item.href && other.href.startsWith(item.href) && pathname.startsWith(other.href))); const Icon = item.icon; const linkContent = (