zephyrdark c8bb675ba4
All checks were successful
Deploy to Production / deploy (push) Successful in 1m30s
feat: add data explorer to sidebar navigation and fix gitignore
- 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>
2026-02-13 23:54:09 +09:00

138 lines
4.2 KiB
TypeScript

'use client';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import { useState } from 'react';
import {
LayoutDashboard,
Briefcase,
TrendingUp,
FlaskConical,
Database,
Search,
ChevronLeft,
ChevronRight,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { ThemeToggle } from '@/components/ui/theme-toggle';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
const navItems = [
{ href: '/', label: '대시보드', icon: LayoutDashboard },
{ href: '/portfolio', label: '포트폴리오', icon: Briefcase },
{ href: '/strategy', label: '전략', icon: TrendingUp },
{ href: '/backtest', label: '백테스트', icon: FlaskConical },
{ href: '/admin/data', label: '데이터 수집', icon: Database },
{ href: '/admin/data/explorer', label: '데이터 탐색', icon: Search },
];
interface NewSidebarProps {
collapsed?: boolean;
onCollapsedChange?: (collapsed: boolean) => void;
}
export function NewSidebar({ collapsed = false, onCollapsedChange }: NewSidebarProps) {
const pathname = usePathname();
const [isCollapsed, setIsCollapsed] = useState(collapsed);
const handleCollapse = () => {
const newCollapsed = !isCollapsed;
setIsCollapsed(newCollapsed);
onCollapsedChange?.(newCollapsed);
};
return (
<TooltipProvider delayDuration={0}>
<aside
className={cn(
'flex flex-col h-full bg-card border-r transition-all duration-300',
isCollapsed ? 'w-16' : 'w-64'
)}
>
{/* Header */}
<div className={cn(
'flex items-center h-16 border-b px-4',
isCollapsed ? 'justify-center' : 'justify-between'
)}>
{!isCollapsed && (
<div>
<h1 className="text-lg font-bold">Galaxis-Po</h1>
<p className="text-xs text-muted-foreground">Quant Portfolio</p>
</div>
)}
<Button
variant="ghost"
size="icon"
onClick={handleCollapse}
className="h-8 w-8"
>
{isCollapsed ? (
<ChevronRight className="h-4 w-4" />
) : (
<ChevronLeft className="h-4 w-4" />
)}
</Button>
</div>
{/* Navigation */}
<nav className="flex-1 py-4">
<ul className="space-y-1 px-2">
{navItems.map((item) => {
const isActive =
pathname === 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 = (
<Link
href={item.href}
className={cn(
'flex items-center gap-3 rounded-lg px-3 py-2 transition-colors',
isActive
? 'bg-primary text-primary-foreground'
: 'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
isCollapsed && 'justify-center px-2'
)}
>
<Icon className="h-5 w-5 shrink-0" />
{!isCollapsed && <span>{item.label}</span>}
</Link>
);
if (isCollapsed) {
return (
<li key={item.href}>
<Tooltip>
<TooltipTrigger asChild>{linkContent}</TooltipTrigger>
<TooltipContent side="right">
<p>{item.label}</p>
</TooltipContent>
</Tooltip>
</li>
);
}
return <li key={item.href}>{linkContent}</li>;
})}
</ul>
</nav>
{/* Footer */}
<div className={cn(
'border-t p-4',
isCollapsed ? 'flex justify-center' : ''
)}>
<ThemeToggle />
</div>
</aside>
</TooltipProvider>
);
}