Files
hanarang-dashboard/frontend/components/sisters/SisterHeader.tsx
narang-ai 230d3847ef feat(redesign-v2): 미니멀 터미널 UI 전체 리디자인
- 디자인 시스템: #151515 배경, 1px 보더 카드, CSS vars, mono 포인트
- GlobalStyle/theme.ts v2 (DESIGN-SYSTEM.md 기반)
- 공통 컴포넌트 (ui/base.tsx): LabelMeta, BracketValue, Card, TechBar, Timeline 등
- Sidebar: [대시] 브라켓 네비 + 모바일 하단 탭바 (767px)
- LayoutShell: SidebarContext 제거, 단순화
- 대시보드 (/): SYS 상태 카드 4개 + ONGOING PROJECTS + ACTIVITY FEED
- 활동 로그 (/activities): 로그 테이블 + 필터 + 검색 + 페이지네이션 (신규)
- 설정 (/settings): 4섹션 Toggle/Bracket Input 그리드
- 자매 노드 관리 (/sisters): 3열 수평 레이아웃 (INFO/GRAPH/SYNC)
- 자매 상세 (/sisters/[name]): 간소화 + 터미널 스타일
- 조직도 (/org): 터미널 카드 트리 구조 + 레벨 색상
- 프로젝트 목록 (/projects): 새 라우트
- 프로젝트 상세 (/projects/[id]): Phase Timeline + Audit Log + Checklist
- FE 14 routes build 성공
2026-04-04 13:10:57 +09:00

132 lines
2.9 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import React from 'react';
import styled from 'styled-components';
import StatusBadge from '../common/StatusBadge';
import { theme } from '@/styles/theme';
type Status = 'online' | 'offline' | 'working' | 'unknown';
interface SisterHeaderProps {
name: string;
role: string;
description: string;
status: Status;
lastSeen: string | null;
lxcId: number;
}
const sisterEmojis: Record<string, string> = {
harang: '🦊', narang: '🦊', darang: '🐱', erang: '🐺',
};
const sisterDisplayNames: Record<string, string> = {
harang: '하랑이', narang: '나랑이', darang: '다랑이', erang: '이랑이',
};
const Wrapper = styled.div`
margin-bottom: 28px;
`;
const Top = styled.div`
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 12px;
`;
const Avatar = styled.div<{ $status: Status }>`
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--bg-surface);
border: 2px solid ${({ $status }) =>
$status === 'online' ? '#00FF00' :
$status === 'working' ? '#2979FF' :
$status === 'offline' ? '#FF1744' :
'var(--border-color)'};
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
flex-shrink: 0;
`;
const Info = styled.div`
flex: 1;
`;
const Name = styled.h1`
font-size: 20px;
font-weight: 700;
color: var(--text-primary);
margin-bottom: 4px;
display: flex;
align-items: center;
gap: 10px;
`;
const Role = styled.span`
font-size: 13px;
color: var(--text-secondary);
display: block;
margin-bottom: 6px;
`;
const Description = styled.p`
font-size: 13px;
color: var(--text-secondary);
line-height: 1.6;
max-width: 600px;
`;
const MetaRow = styled.div`
display: flex;
gap: 16px;
margin-top: 10px;
flex-wrap: wrap;
`;
const MetaItem = styled.span`
font-size: 12px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 4px;
`;
function formatLastSeen(lastSeen: string | null): string {
if (!lastSeen) return '기록 없음';
const diff = Date.now() - new Date(lastSeen).getTime();
const min = Math.floor(diff / 60000);
if (min < 1) return '방금 전';
if (min < 60) return `${min}분 전`;
const hr = Math.floor(min / 60);
if (hr < 24) return `${hr}시간 전`;
return `${Math.floor(hr / 24)}일 전`;
}
export default function SisterHeader({ name, role, description, status, lastSeen, lxcId }: SisterHeaderProps) {
return (
<Wrapper>
<Top>
<Avatar $status={status}>
{sisterEmojis[name] ?? '🤖'}
</Avatar>
<Info>
<Name>
{sisterDisplayNames[name] ?? name}
<StatusBadge status={status} />
</Name>
<Role>{role}</Role>
</Info>
</Top>
<Description>{description}</Description>
<MetaRow>
<MetaItem>🖥 LXC {lxcId}</MetaItem>
<MetaItem>🕐 : {formatLastSeen(lastSeen)}</MetaItem>
</MetaRow>
</Wrapper>
);
}