From 3e733ec1b8ac3718968d0f09d88b242cfcafdfab Mon Sep 17 00:00:00 2001 From: zephyrdark Date: Thu, 5 Feb 2026 22:46:54 +0900 Subject: [PATCH] feat(frontend): add new layout components - Collapsible Sidebar with navigation - Header with page titles and logout - DashboardLayout with responsive design - Updated dashboard page Co-Authored-By: Claude Opus 4.5 --- frontend/src/app/page.tsx | 145 +++++++++++++----- .../components/layout/dashboard-layout.tsx | 83 ++++++++++ frontend/src/components/layout/new-header.tsx | 84 ++++++++++ .../src/components/layout/new-sidebar.tsx | 134 ++++++++++++++++ 4 files changed, 405 insertions(+), 41 deletions(-) create mode 100644 frontend/src/components/layout/dashboard-layout.tsx create mode 100644 frontend/src/components/layout/new-header.tsx create mode 100644 frontend/src/components/layout/new-sidebar.tsx diff --git a/frontend/src/app/page.tsx b/frontend/src/app/page.tsx index 9b97cea..d8c4ac8 100644 --- a/frontend/src/app/page.tsx +++ b/frontend/src/app/page.tsx @@ -1,49 +1,112 @@ -import { Button } from "@/components/ui/button"; -import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; -import { ThemeToggle } from "@/components/ui/theme-toggle"; -import { Home, Settings, User } from "lucide-react"; +'use client'; -export default function TestPage() { +import { DashboardLayout } from '@/components/layout/dashboard-layout'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Wallet, TrendingUp, Briefcase, RefreshCw } from 'lucide-react'; + +const summaryCards = [ + { + title: '총 자산', + value: '---', + description: '전체 포트폴리오 가치', + icon: Wallet, + }, + { + title: '총 수익률', + value: '---%', + description: '전체 수익률', + icon: TrendingUp, + }, + { + title: '포트폴리오', + value: '-', + description: '활성 포트폴리오 수', + icon: Briefcase, + }, + { + title: '리밸런싱', + value: '-', + description: '예정된 리밸런싱', + icon: RefreshCw, + }, +]; + +export default function DashboardPage() { return ( -
-
-
-

Phase 1 테스트

- + +
+ {/* Summary Cards */} +
+ {summaryCards.map((card) => { + const Icon = card.icon; + return ( + + + + {card.title} + + + + +
{card.value}
+

+ {card.description} +

+
+
+ ); + })}
- - - shadcn/ui 컴포넌트 테스트 - - -
- - - - - -
-
- - - - Lucide Icons -
-
-
+ {/* Chart Placeholders */} +
+ + + 포트폴리오 성과 + + +
+

차트 영역

+
+
+
- - - 테마 테스트 - - -

- 위의 테마 토글 버튼으로 라이트/다크 모드를 전환해보세요. -

-
-
+ + + 자산 배분 + + +
+

차트 영역

+
+
+
+
+ +
+ + + 최근 거래 + + +
+

거래 내역 영역

+
+
+
+ + + + 알림 + + +
+

알림 영역

+
+
+
+
-
+ ); } diff --git a/frontend/src/components/layout/dashboard-layout.tsx b/frontend/src/components/layout/dashboard-layout.tsx new file mode 100644 index 0000000..b50ef1a --- /dev/null +++ b/frontend/src/components/layout/dashboard-layout.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { api } from '@/lib/api'; +import { NewSidebar } from './new-sidebar'; +import { NewHeader } from './new-header'; +import { + Sheet, + SheetContent, + SheetTitle, +} from '@/components/ui/sheet'; + +interface User { + username: string; +} + +interface DashboardLayoutProps { + children: React.ReactNode; +} + +export function DashboardLayout({ children }: DashboardLayoutProps) { + const router = useRouter(); + const [isLoading, setIsLoading] = useState(true); + const [user, setUser] = useState(null); + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + + useEffect(() => { + const checkAuth = async () => { + try { + const userData = await api.getCurrentUser() as User; + setUser(userData); + } catch { + router.push('/login'); + return; + } finally { + setIsLoading(false); + } + }; + + checkAuth(); + }, [router]); + + if (isLoading) { + return ( +
+
+
+

로딩 중...

+
+
+ ); + } + + return ( +
+ {/* Desktop Sidebar */} +
+ +
+ + {/* Mobile Sidebar (Sheet) */} + + + 내비게이션 메뉴 + + + + + {/* Main Content Area */} +
+ setIsMobileMenuOpen(true)} + /> +
+ {children} +
+
+
+ ); +} diff --git a/frontend/src/components/layout/new-header.tsx b/frontend/src/components/layout/new-header.tsx new file mode 100644 index 0000000..f99e07f --- /dev/null +++ b/frontend/src/components/layout/new-header.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { usePathname, useRouter } from 'next/navigation'; +import { Menu, LogOut, User } from 'lucide-react'; +import { api } from '@/lib/api'; +import { Button } from '@/components/ui/button'; + +const pageTitles: Record = { + '/': '대시보드', + '/portfolio': '포트폴리오', + '/strategy': '전략', + '/backtest': '백테스트', + '/admin/data': '데이터 관리', +}; + +function getPageTitle(pathname: string): string { + // Check exact match first + if (pageTitles[pathname]) { + return pageTitles[pathname]; + } + + // Check for partial matches (for nested routes) + for (const [path, title] of Object.entries(pageTitles)) { + if (path !== '/' && pathname.startsWith(path)) { + return title; + } + } + + return '대시보드'; +} + +interface NewHeaderProps { + username?: string; + onMenuClick?: () => void; + showMenuButton?: boolean; +} + +export function NewHeader({ username, onMenuClick, showMenuButton = false }: NewHeaderProps) { + const pathname = usePathname(); + const router = useRouter(); + const pageTitle = getPageTitle(pathname); + + const handleLogout = () => { + api.logout(); + router.push('/login'); + }; + + return ( +
+
+ {showMenuButton && ( + + )} +

{pageTitle}

+
+ +
+ {username && ( +
+ + {username} +
+ )} + +
+
+ ); +} diff --git a/frontend/src/components/layout/new-sidebar.tsx b/frontend/src/components/layout/new-sidebar.tsx new file mode 100644 index 0000000..a297f8e --- /dev/null +++ b/frontend/src/components/layout/new-sidebar.tsx @@ -0,0 +1,134 @@ +'use client'; + +import Link from 'next/link'; +import { usePathname } from 'next/navigation'; +import { useState } from 'react'; +import { + LayoutDashboard, + Briefcase, + TrendingUp, + FlaskConical, + Database, + 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 }, +]; + +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 ( + + + + ); +}