'use client'; import React from 'react'; import styled, { css, keyframes } from 'styled-components'; import type { PipelineNode } from '@/components/dashboard/ActivePipeline'; // ─── Types ─────────────────────────────────────────────────────────────────── interface PipelinePanelProps { activeTask: string; focus: string; reviewLoopCount: number; escalationCount: number; deployState: string; nodes: PipelineNode[]; freshness: string; } // ─── Animations ────────────────────────────────────────────────────────────── const flowAnim = keyframes` 0% { transform: translateX(-100%); opacity: 0; } 40% { opacity: 0.8; } 100% { transform: translateX(100%); opacity: 0; } `; // ─── Styled Components ──────────────────────────────────────────────────────── const Panel = styled.section` border: 1px solid var(--border-color); background: var(--bg-surface); display: flex; flex-direction: column; gap: var(--space-md); padding: var(--space-lg); `; const PanelHeader = styled.div` display: flex; align-items: baseline; gap: var(--space-lg); justify-content: space-between; flex-wrap: wrap; `; const TitleGroup = styled.div` display: flex; flex-direction: column; gap: 4px; `; const Eyebrow = styled.div` font-family: var(--font-mono); font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.08em; `; const Title = styled.h3` font-size: 15px; font-weight: 600; color: var(--text-primary); letter-spacing: -0.01em; margin: 0; `; const Focus = styled.div` font-size: 11px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.04em; max-width: 500px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; @media (max-width: 767px) { max-width: 100%; white-space: normal; } `; const Stats = styled.div` display: flex; gap: var(--space-md); flex-wrap: wrap; `; const Stat = styled.div` display: flex; flex-direction: column; gap: 2px; min-width: 80px; `; const StatLabel = styled.div` font-family: var(--font-mono); font-size: 10px; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.06em; `; const StatValue = styled.div` font-size: 14px; font-weight: 600; color: var(--text-primary); `; const NodeRow = styled.div` display: flex; gap: var(--space-sm); align-items: stretch; overflow-x: auto; scrollbar-width: thin; scrollbar-color: var(--border-color) transparent; @media (max-width: 767px) { flex-direction: column; overflow-x: visible; } `; const stateColors: Record = { idle: 'var(--border-color)', active: '#6fc3ff', review: '#ff7ac6', blocked: '#ff8d7a', ready: '#8dffb2', }; const NodeCard = styled.div<{ $state: PipelineNode['state'] }>` border: 1px solid ${({ $state }) => stateColors[$state]}; padding: var(--space-sm) var(--space-md); min-width: 140px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; @media (max-width: 767px) { min-width: 0; } `; const NodeName = styled.div` font-size: 13px; font-weight: 600; color: var(--text-primary); `; const NodeRole = styled.div` font-size: 10px; color: var(--text-secondary); `; const NodeState = styled.div<{ $state: PipelineNode['state'] }>` font-family: var(--font-mono); font-size: 10px; color: ${({ $state }) => stateColors[$state]}; text-transform: uppercase; letter-spacing: 0.06em; `; const NodeDetail = styled.div` font-size: 11px; color: var(--text-secondary); line-height: 1.4; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; `; const Connector = styled.div<{ $active: boolean }>` width: 24px; flex-shrink: 0; height: 1px; background: ${({ $active }) => $active ? 'rgba(245,245,245,0.4)' : 'var(--border-color)'}; position: relative; overflow: hidden; align-self: center; &::after { content: ''; position: absolute; top: -2px; left: 0; width: 100%; height: 5px; background: ${({ $active }) => $active ? 'rgba(245,245,245,0.8)' : 'transparent'}; animation: ${({ $active }) => $active ? css`${flowAnim} 1.6s linear infinite` : 'none'}; } @media (max-width: 767px) { width: 1px; height: 16px; align-self: flex-start; margin-left: var(--space-lg); } `; const FreshnessNote = styled.div` font-family: var(--font-mono); font-size: 10px; color: var(--text-secondary); opacity: 0.6; text-transform: uppercase; letter-spacing: 0.06em; `; // ─── Component ──────────────────────────────────────────────────────────────── export default function PipelinePanel({ activeTask, focus, reviewLoopCount, escalationCount, deployState, nodes, freshness, }: PipelinePanelProps) { return ( pipeline panel · snapshot {activeTask} {focus} review loop {reviewLoopCount}x escalations {escalationCount} deploy state {deployState} {nodes.length > 0 ? ( {nodes.map((node, i) => { const active = node.state === 'active' || node.state === 'review' || node.state === 'ready'; return ( {node.label} {node.role} {node.state} {node.detail} {i < nodes.length - 1 && } ); })} ) : ( 파이프라인 데이터 없음 )} snapshot · {freshness} ); }