Sprint 002 non-blocking:
- API_URL/POLL_INTERVAL lib/config.ts 공통 추출
- 폴링 간격 NEXT_PUBLIC_POLL_INTERVAL_MS env화
Sprint 003 본문:
- TASK-008: SisterDetailService (config/sessions/subagents/activity 조회)
- TASK-008: GET /api/sisters/:name/{config,sessions,subagents,activity}
- TASK-008: GET /api/org → 조직도 + 파이프라인 데이터
- TASK-009: /sisters 목록 페이지 (카드 클릭 → 상세 이동)
- TASK-009: /sisters/[name] 상세 페이지 (탭: 개요/설정/세션/서브에이전트)
- TASK-009: SisterCard → /sisters/:name 링크 연결
- TASK-010: /org 조직도 페이지 (OrgTree 컴포넌트, 호버 팝업)
- TASK-011: /settings 설정 뷰어 페이지 (자매 선택 드롭다운)
- 테스트 16/16 pass, FE build 성공 (7 routes)
104 lines
2.5 KiB
TypeScript
104 lines
2.5 KiB
TypeScript
'use client';
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import styled from 'styled-components';
|
|
import OrgTree from '@/components/org/OrgTree';
|
|
import { theme } from '@/styles/theme';
|
|
import { API_URL } from '@/lib/config';
|
|
|
|
const Page = styled.div`
|
|
padding: 32px;
|
|
min-height: 100vh;
|
|
background: ${theme.colors.bg};
|
|
`;
|
|
|
|
const PageTitle = styled.h1`
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
color: ${theme.colors.textPrimary};
|
|
margin-bottom: 6px;
|
|
`;
|
|
|
|
const PageSubtitle = styled.p`
|
|
font-size: 14px;
|
|
color: ${theme.colors.textSecondary};
|
|
margin-bottom: 28px;
|
|
`;
|
|
|
|
const Card = styled.div`
|
|
background: ${theme.colors.cardBg};
|
|
border: 1px solid ${theme.colors.border};
|
|
border-radius: 16px;
|
|
padding: 32px;
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
`;
|
|
|
|
const Legend = styled.div`
|
|
display: flex;
|
|
gap: 20px;
|
|
justify-content: center;
|
|
margin-top: 24px;
|
|
padding-top: 20px;
|
|
border-top: 1px solid ${theme.colors.border};
|
|
flex-wrap: wrap;
|
|
`;
|
|
|
|
const LegendItem = styled.div`
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 12px;
|
|
color: ${theme.colors.textSecondary};
|
|
`;
|
|
|
|
const Dot = styled.div<{ $color: string }>`
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background: ${({ $color }) => $color};
|
|
`;
|
|
|
|
export default function OrgPage() {
|
|
const [orgData, setOrgData] = useState<any>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
const load = async () => {
|
|
try {
|
|
const res = await fetch(`${API_URL}/api/org`);
|
|
if (res.ok) setOrgData(await res.json());
|
|
} catch {
|
|
// silent
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
load();
|
|
}, []);
|
|
|
|
return (
|
|
<Page>
|
|
<PageTitle>조직도</PageTitle>
|
|
<PageSubtitle>하나랑 4자매 멀티에이전트 파이프라인 구조</PageSubtitle>
|
|
|
|
<Card>
|
|
{loading ? (
|
|
<div style={{ textAlign: 'center', color: theme.colors.textSecondary, padding: '40px' }}>
|
|
로딩 중...
|
|
</div>
|
|
) : (
|
|
<OrgTree sisters={orgData?.sisters ?? []} />
|
|
)}
|
|
|
|
<Legend>
|
|
<LegendItem><Dot $color={theme.colors.online} /> 온라인</LegendItem>
|
|
<LegendItem><Dot $color={theme.colors.working} /> 작업 중</LegendItem>
|
|
<LegendItem><Dot $color={theme.colors.offline} /> 오프라인</LegendItem>
|
|
<LegendItem><Dot $color={theme.colors.textSecondary} /> 알 수 없음</LegendItem>
|
|
</Legend>
|
|
</Card>
|
|
</Page>
|
|
);
|
|
}
|