'use client'; import React, { useRef, useEffect, useState } from 'react'; import styled from 'styled-components'; const Wrapper = styled.div` background: #0a0e14; border: 1px solid #1e2430; border-radius: 8px; overflow: hidden; `; const Toolbar = styled.div` background: #0d1117; border-bottom: 1px solid #1e2430; padding: 8px 12px; display: flex; align-items: center; gap: 12px; `; const SearchInput = styled.input` background: rgba(255,255,255,0.05); border: 1px solid #1e2430; border-radius: 5px; color: #8B949E; font-size: 12px; padding: 4px 10px; outline: none; width: 200px; &:focus { border-color: #58A6FF; color: #E6EDF3; } `; const FilterBtn = styled.button<{ $active: boolean }>` padding: 3px 10px; border-radius: 4px; font-size: 11px; cursor: pointer; border: 1px solid ${({ $active }) => $active ? 'rgba(255,23,68,0.5)' : '#1e2430'}; background: ${({ $active }) => $active ? 'rgba(255,23,68,0.1)' : 'transparent'}; color: ${({ $active }) => $active ? '#FF1744' : '#8B949E'}; transition: all 0.15s; `; const Terminal = styled.div` height: 420px; overflow-y: auto; padding: 12px; font-family: 'Fira Code', 'Cascadia Code', monospace; font-size: 12px; line-height: 1.7; &::-webkit-scrollbar { width: 4px; } &::-webkit-scrollbar-thumb { background: #1e2430; border-radius: 2px; } `; const LogLine = styled.div<{ $isError: boolean }>` color: ${({ $isError }) => $isError ? '#FF1744' : '#a8c4e0'}; white-space: pre-wrap; word-break: break-all; &:hover { background: rgba(255,255,255,0.03); } `; const LineNum = styled.span` color: #3d4f5a; margin-right: 12px; user-select: none; min-width: 32px; display: inline-block; text-align: right; `; interface LogTerminalProps { lines: string[]; } export default function LogTerminal({ lines }: LogTerminalProps) { const [search, setSearch] = useState(''); const [errorsOnly, setErrorsOnly] = useState(false); const bottomRef = useRef(null); useEffect(() => { bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [lines]); const filtered = lines.filter((line) => { if (errorsOnly && !/error|fail|exception|warn/i.test(line)) return false; if (search && !line.toLowerCase().includes(search.toLowerCase())) return false; return true; }); return ( setSearch(e.target.value)} /> setErrorsOnly(!errorsOnly)}> 🔴 에러만 {filtered.length}/{lines.length} lines {filtered.map((line, i) => ( {i + 1} {line} ))}
); }