/** * Mandu Kitchen DevTools - Errors Panel * @version 1.0.3 */ import React, { useCallback } from 'react'; import type { NormalizedError } from '../../../types'; import { colors, typography, spacing, borderRadius, animation, testIds } from '../../../design-tokens'; import { ManduCharacter } from '../mandu-character'; import { sanitizeErrorMessage } from '../../filters'; // ============================================================================ // Styles // ============================================================================ const styles = { container: { padding: spacing.md, height: '100%', display: 'flex', flexDirection: 'column' as const, gap: spacing.md, }, header: { display: 'flex', alignItems: 'center', justifyContent: 'space-between', }, clearButton: { padding: `${spacing.xs} ${spacing.sm}`, borderRadius: borderRadius.sm, backgroundColor: colors.background.light, border: 'none', color: colors.text.secondary, fontSize: typography.fontSize.xs, cursor: 'pointer', transition: `all ${animation.duration.fast}`, }, list: { flex: 1, overflow: 'auto', display: 'flex', flexDirection: 'column' as const, gap: spacing.sm, }, emptyState: { flex: 1, display: 'flex', flexDirection: 'column' as const, alignItems: 'center', justifyContent: 'center', gap: spacing.md, padding: spacing.xl, color: colors.text.muted, }, emptyMessage: { fontSize: typography.fontSize.sm, textAlign: 'center' as const, }, errorItem: { padding: spacing.md, borderRadius: borderRadius.md, backgroundColor: colors.background.medium, cursor: 'pointer', transition: `all ${animation.duration.fast}`, borderLeft: '3px solid', }, errorHeader: { display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: spacing.sm, marginBottom: spacing.xs, }, errorType: { display: 'flex', alignItems: 'center', gap: spacing.xs, }, errorBadge: { padding: `2px ${spacing.xs}`, borderRadius: borderRadius.sm, fontSize: typography.fontSize.xs, fontWeight: typography.fontWeight.medium, textTransform: 'uppercase' as const, }, errorTime: { fontSize: typography.fontSize.xs, color: colors.text.muted, whiteSpace: 'nowrap' as const, }, errorMessage: { fontSize: typography.fontSize.sm, color: colors.text.primary, lineHeight: typography.lineHeight.normal, wordBreak: 'break-word' as const, display: '-webkit-box', WebkitLineClamp: 2, WebkitBoxOrient: 'vertical' as const, overflow: 'hidden', }, errorSource: { marginTop: spacing.xs, fontSize: typography.fontSize.xs, color: colors.text.muted, fontFamily: typography.fontFamily.mono, }, actionButton: { padding: spacing.xs, borderRadius: borderRadius.sm, backgroundColor: 'transparent', border: 'none', color: colors.text.muted, fontSize: typography.fontSize.sm, cursor: 'pointer', opacity: 0, transition: `opacity ${animation.duration.fast}`, }, }; const severityColors: Record = { critical: { bg: `${colors.semantic.error}15`, border: colors.semantic.error, text: colors.semantic.error, }, error: { bg: `${colors.semantic.error}15`, border: colors.semantic.error, text: colors.semantic.error, }, warning: { bg: `${colors.semantic.warning}15`, border: colors.semantic.warning, text: colors.semantic.warning, }, info: { bg: `${colors.semantic.info}15`, border: colors.semantic.info, text: colors.semantic.info, }, }; // ============================================================================ // Props // ============================================================================ export interface ErrorsPanelProps { errors: NormalizedError[]; onErrorClick: (error: NormalizedError) => void; onErrorIgnore: (id: string) => void; onClearAll: () => void; } // ============================================================================ // Component // ============================================================================ export function ErrorsPanel({ errors, onErrorClick, onErrorIgnore: _onErrorIgnore, onClearAll, }: ErrorsPanelProps): React.ReactElement { const formatTime = useCallback((timestamp: number): string => { return new Date(timestamp).toLocaleTimeString('ko-KR', { hour: '2-digit', minute: '2-digit', second: '2-digit', }); }, []); if (errors.length === 0) { return (

활성 오류가 없습니다.
새 이슈가 생기면 여기에 표시됩니다.

); } return (
{/* Header */}
{errors.length}개의 이슈
{/* Error List */}
{errors.map((error) => { const severity = severityColors[error.severity] ?? severityColors.error; return (
onErrorClick(error)} onKeyDown={(e) => { if (e.key === 'Enter') onErrorClick(error); }} tabIndex={0} role="button" >
{error.type} {error.severity}
{formatTime(error.timestamp)}

{sanitizeErrorMessage(error.message)}

{error.source && (

{error.source} {error.line && `:${error.line}`} {error.column && `:${error.column}`}

)}
); })}
); }