Files
hanarang-dashboard/frontend/components/common/Sidebar.tsx
narang-ai 79135f24b0 feat(sprint-001): backend Nest.js + Prisma7 adapter setup, frontend Next.js UI
- Backend: NestJS + Prisma 7 (MariaDB adapter) scaffold
  - PrismaService with @prisma/adapter-mariadb driver
  - SistersService: SSH 상태 체크 with graceful fallback
  - HealthController: GET /health
  - 시드 스크립트: 자매 4명 초기 데이터
  - 테스트 5/5 pass

- Frontend: Next.js 16 + styled-components
  - styled-components SSR registry (next.config 컴파일러)
  - 다크 테마 글로벌 스타일 + 테마 토큰
  - SisterCard: glassmorphism 상태 카드 (온라인 pulse 애니메이션)
  - StatusBadge: 상태 표시 컴포넌트
  - Sidebar: 접이식 네비게이션
  - 대시보드 메인 페이지 (API 미연결 시 mock 데이터 fallback)
  - build 성공 확인

- DB credential 이랑이 대기 중
2026-04-04 11:06:44 +09:00

143 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import React, { useState } from 'react';
import styled from 'styled-components';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
const MENU_ITEMS = [
{ href: '/', icon: '⬛', label: '대시보드' },
{ href: '/projects', icon: '📁', label: '프로젝트' },
{ href: '/sisters', icon: '🦊', label: '자매' },
{ href: '/org', icon: '🏢', label: '조직도' },
{ href: '/settings', icon: '⚙️', label: '설정' },
];
const ADMIN_ITEMS = [
{ href: '/admin', icon: '🔧', label: '관리자' },
];
const Wrapper = styled.aside<{ $collapsed: boolean }>`
width: ${({ $collapsed }) => ($collapsed ? '60px' : '220px')};
height: 100vh;
background: rgba(13, 17, 23, 0.95);
backdrop-filter: blur(10px);
border-right: 1px solid rgba(240, 246, 252, 0.1);
display: flex;
flex-direction: column;
position: fixed;
left: 0;
top: 0;
z-index: 100;
transition: width 0.2s ease;
overflow: hidden;
`;
const LogoArea = styled.div`
padding: 20px 16px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid rgba(240, 246, 252, 0.08);
min-height: 64px;
`;
const LogoText = styled.span`
font-size: 15px;
font-weight: 700;
color: #E6EDF3;
white-space: nowrap;
`;
const CollapseBtn = styled.button`
margin-left: auto;
background: none;
border: none;
color: #8B949E;
cursor: pointer;
font-size: 16px;
padding: 4px;
border-radius: 4px;
flex-shrink: 0;
&:hover { color: #E6EDF3; background: rgba(240, 246, 252, 0.06); }
`;
const Nav = styled.nav`
flex: 1;
padding: 12px 8px;
display: flex;
flex-direction: column;
gap: 2px;
`;
const NavItem = styled(Link)<{ $active: boolean }>`
display: flex;
align-items: center;
gap: 10px;
padding: 10px 10px;
border-radius: 8px;
font-size: 14px;
color: ${({ $active }) => ($active ? '#E6EDF3' : '#8B949E')};
background: ${({ $active }) => ($active ? 'rgba(88, 166, 255, 0.12)' : 'transparent')};
transition: background 0.15s, color 0.15s;
white-space: nowrap;
overflow: hidden;
&:hover {
background: rgba(240, 246, 252, 0.06);
color: #E6EDF3;
}
`;
const NavIcon = styled.span`
font-size: 16px;
flex-shrink: 0;
`;
const Divider = styled.div`
height: 1px;
background: rgba(240, 246, 252, 0.08);
margin: 8px 8px;
`;
const AdminSection = styled.div`
padding: 8px;
`;
export default function Sidebar() {
const [collapsed, setCollapsed] = useState(false);
const pathname = usePathname();
return (
<Wrapper $collapsed={collapsed}>
<LogoArea>
<span style={{ fontSize: '22px', flexShrink: 0 }}>🦊</span>
{!collapsed && <LogoText></LogoText>}
<CollapseBtn onClick={() => setCollapsed(!collapsed)}>
{collapsed ? '' : ''}
</CollapseBtn>
</LogoArea>
<Nav>
{MENU_ITEMS.map((item) => (
<NavItem key={item.href} href={item.href} $active={pathname === item.href}>
<NavIcon>{item.icon}</NavIcon>
{!collapsed && item.label}
</NavItem>
))}
</Nav>
<Divider />
<AdminSection>
{ADMIN_ITEMS.map((item) => (
<NavItem key={item.href} href={item.href} $active={pathname === item.href}>
<NavIcon>{item.icon}</NavIcon>
{!collapsed && item.label}
</NavItem>
))}
</AdminSection>
</Wrapper>
);
}