- 디자인 시스템: #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 성공
132 lines
2.9 KiB
TypeScript
132 lines
2.9 KiB
TypeScript
'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>
|
||
);
|
||
}
|