feat: add data explorer to sidebar navigation and fix gitignore
All checks were successful
Deploy to Production / deploy (push) Successful in 1m30s
All checks were successful
Deploy to Production / deploy (push) Successful in 1m30s
- 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 <noreply@anthropic.com>
This commit is contained in:
parent
2858c87b1b
commit
c8bb675ba4
1
.gitignore
vendored
1
.gitignore
vendored
@ -55,6 +55,7 @@ Thumbs.db
|
||||
*.db
|
||||
*.sqlite3
|
||||
data/
|
||||
!frontend/src/app/admin/data/
|
||||
|
||||
# Test
|
||||
.coverage
|
||||
|
||||
@ -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 (
|
||||
<DashboardLayout>
|
||||
<h1 className="text-2xl font-bold text-foreground mb-6">데이터 수집 관리</h1>
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h1 className="text-2xl font-bold text-foreground">데이터 수집 관리</h1>
|
||||
<Button asChild variant="outline">
|
||||
<Link href="/admin/data/explorer">수집 데이터 조회</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div className="bg-destructive/10 border border-destructive text-destructive px-4 py-3 rounded mb-4">
|
||||
|
||||
@ -84,7 +84,26 @@ export default function DashboardPage() {
|
||||
}, [router]);
|
||||
|
||||
if (loading) {
|
||||
return null; // DashboardLayout handles skeleton
|
||||
return (
|
||||
<DashboardLayout>
|
||||
<div className="space-y-6">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, i) => (
|
||||
<Card key={i}>
|
||||
<CardContent className="pt-6">
|
||||
<div className="h-4 w-20 bg-muted animate-pulse rounded mb-2" />
|
||||
<div className="h-8 w-32 bg-muted animate-pulse rounded" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card><CardContent className="pt-6"><div className="h-64 bg-muted animate-pulse rounded" /></CardContent></Card>
|
||||
<Card><CardContent className="pt-6"><div className="h-64 bg-muted animate-pulse rounded" /></CardContent></Card>
|
||||
</div>
|
||||
</div>
|
||||
</DashboardLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const totalValue = portfolios.reduce((sum, p) => sum + (p.total_value ?? 0), 0);
|
||||
|
||||
@ -10,7 +10,8 @@ const pageTitles: Record<string, string> = {
|
||||
'/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 '대시보드';
|
||||
}
|
||||
|
||||
@ -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 = (
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user