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 성공
This commit is contained in:
2026-04-04 13:10:57 +09:00
parent 5e6c7085d3
commit 230d3847ef
35 changed files with 2479 additions and 1208 deletions

View File

@@ -20,9 +20,9 @@ interface ProjectCardProps {
const Card = styled(Link)`
display: block;
background: ${theme.colors.cardBg};
background: var(--bg-surface);
backdrop-filter: blur(12px);
border: 1px solid ${theme.colors.border};
border: 1px solid var(--border-color);
border-radius: 12px;
padding: 20px;
transition: transform 0.2s ease, box-shadow 0.2s ease;
@@ -46,12 +46,12 @@ const Header = styled.div`
const Name = styled.span`
font-size: 15px;
font-weight: 600;
color: ${theme.colors.textPrimary};
color: var(--text-primary);
`;
const Meta = styled.div`
font-size: 12px;
color: ${theme.colors.textSecondary};
color: var(--text-secondary);
margin-bottom: 12px;
white-space: nowrap;
overflow: hidden;
@@ -66,7 +66,7 @@ const Stats = styled.div`
const Stat = styled.div`
font-size: 12px;
color: ${theme.colors.textSecondary};
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 4px;
@@ -80,7 +80,7 @@ const ProgressLabel = styled.div`
const ProgressText = styled.span`
font-size: 11px;
color: ${theme.colors.textSecondary};
color: var(--text-secondary);
`;
const PRBadge = styled.span<{ $count: number }>`
@@ -88,7 +88,7 @@ const PRBadge = styled.span<{ $count: number }>`
padding: 2px 7px;
border-radius: 4px;
background: ${({ $count }) => $count > 0 ? 'rgba(88,166,255,0.12)' : 'rgba(139,148,158,0.08)'};
color: ${({ $count }) => $count > 0 ? theme.colors.accent : theme.colors.textSecondary};
color: ${({ $count }) => $count > 0 ? '#58A6FF' : 'var(--text-secondary)'};
font-weight: 600;
`;

View File

@@ -33,11 +33,11 @@ const sprintStatusIcon: Record<string, string> = {
};
const sprintStatusColor: Record<string, string> = {
pending: theme.colors.textSecondary,
in_progress: theme.colors.working,
pending: 'var(--text-secondary)',
in_progress: '#2979FF',
review: '#FF9800',
done: theme.colors.online,
failed: theme.colors.offline,
done: '#00FF00',
failed: '#FF1744',
};
const assigneeEmojis: Record<string, string> = {
@@ -51,8 +51,8 @@ const Wrapper = styled.div`
`;
const AccordionItem = styled.div<{ $open: boolean }>`
background: ${theme.colors.cardBg};
border: 1px solid ${theme.colors.border};
background: var(--bg-surface);
border: 1px solid var(--border-color);
border-radius: 10px;
overflow: hidden;
transition: border-color 0.2s;
@@ -69,7 +69,7 @@ const Header = styled.button<{ $statusColor: string }>`
border: none;
cursor: pointer;
text-align: left;
color: ${theme.colors.textPrimary};
color: var(--text-primary);
transition: background 0.15s;
&:hover {
@@ -96,14 +96,14 @@ const ProgressWrap = styled.div`
const Chevron = styled.span<{ $open: boolean }>`
font-size: 12px;
color: ${theme.colors.textSecondary};
color: var(--text-secondary);
transform: ${({ $open }) => $open ? 'rotate(90deg)' : 'none'};
transition: transform 0.2s;
`;
const Body = styled.div<{ $open: boolean }>`
display: ${({ $open }) => $open ? 'block' : 'none'};
border-top: 1px solid ${theme.colors.border};
border-top: 1px solid var(--border-color);
`;
const TaskRow = styled.div`
@@ -111,7 +111,7 @@ const TaskRow = styled.div`
align-items: center;
gap: 12px;
padding: 10px 16px;
border-bottom: 1px solid ${theme.colors.border};
border-bottom: 1px solid var(--border-color);
font-size: 13px;
&:last-child { border-bottom: none; }
@@ -120,14 +120,14 @@ const TaskRow = styled.div`
const TaskId = styled.code`
font-size: 11px;
color: ${theme.colors.accent};
color: #58A6FF;
min-width: 70px;
font-family: monospace;
`;
const TaskTitle = styled.span`
flex: 1;
color: ${theme.colors.textPrimary};
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@@ -135,13 +135,13 @@ const TaskTitle = styled.span`
const Assignee = styled.span`
font-size: 12px;
color: ${theme.colors.textSecondary};
color: var(--text-secondary);
min-width: 60px;
`;
const Iteration = styled.span<{ $count: number }>`
font-size: 11px;
color: ${({ $count }) => $count > 0 ? '#FF9800' : theme.colors.textSecondary};
color: ${({ $count }) => $count > 0 ? '#FF9800' : 'var(--text-secondary)'};
min-width: 28px;
text-align: right;
`;
@@ -163,7 +163,7 @@ export default function SprintAccordion({ sprints }: { sprints: Sprint[] }) {
<Wrapper>
{sprints.map((sprint) => {
const open = openIds.has(sprint.id);
const color = sprintStatusColor[sprint.status] ?? theme.colors.textSecondary;
const color = sprintStatusColor[sprint.status] ?? 'var(--text-secondary)';
return (
<AccordionItem key={sprint.id} $open={open}>
<Header $statusColor={color} onClick={() => toggle(sprint.id)}>
@@ -178,7 +178,7 @@ export default function SprintAccordion({ sprints }: { sprints: Sprint[] }) {
</Header>
<Body $open={open}>
{sprint.tasks.length === 0 ? (
<TaskRow><TaskTitle style={{ color: theme.colors.textSecondary }}> </TaskTitle></TaskRow>
<TaskRow><TaskTitle style={{ color: 'var(--text-secondary)' }}> </TaskTitle></TaskRow>
) : (
sprint.tasks.map((task) => (
<TaskRow key={task.id}>

View File

@@ -29,14 +29,14 @@ const Filters = styled.div`
const FilterBtn = styled.button<{ $active: boolean }>`
padding: 4px 10px;
border-radius: 6px;
border: 1px solid ${({ $active }) => $active ? theme.colors.accent : theme.colors.border};
border: 1px solid ${({ $active }) => $active ? '#58A6FF' : 'var(--border-color)'};
background: ${({ $active }) => $active ? 'rgba(88,166,255,0.12)' : 'transparent'};
color: ${({ $active }) => $active ? theme.colors.accent : theme.colors.textSecondary};
color: ${({ $active }) => $active ? '#58A6FF' : 'var(--text-secondary)'};
font-size: 12px;
cursor: pointer;
transition: all 0.15s;
&:hover { border-color: ${theme.colors.accent}; color: ${theme.colors.accent}; }
&:hover { border-color: #58A6FF; color: #58A6FF; }
`;
const Table = styled.table`
@@ -49,8 +49,8 @@ const Th = styled.th`
padding: 8px 12px;
font-size: 12px;
font-weight: 600;
color: ${theme.colors.textSecondary};
border-bottom: 1px solid ${theme.colors.border};
color: var(--text-secondary);
border-bottom: 1px solid var(--border-color);
text-transform: uppercase;
letter-spacing: 0.05em;
`;
@@ -58,8 +58,8 @@ const Th = styled.th`
const Td = styled.td`
padding: 10px 12px;
font-size: 13px;
border-bottom: 1px solid ${theme.colors.border};
color: ${theme.colors.textPrimary};
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
`;
const Tr = styled.tr`
@@ -68,19 +68,19 @@ const Tr = styled.tr`
`;
const TaskIdCell = styled.code`
color: ${theme.colors.accent};
color: #58A6FF;
font-family: monospace;
font-size: 12px;
`;
const IterationCell = styled.span<{ $count: number }>`
color: ${({ $count }) => $count > 0 ? '#FF9800' : theme.colors.textSecondary};
color: ${({ $count }) => $count > 0 ? '#FF9800' : 'var(--text-secondary)'};
`;
const Empty = styled.div`
padding: 32px;
text-align: center;
color: ${theme.colors.textSecondary};
color: var(--text-secondary);
font-size: 13px;
`;