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>
138 lines
4.2 KiB
TypeScript
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>
|
|
);
|
|
}
|