"use client"; /** * 플로우 검증 결과 패널 * * 모든 검증 결과를 사이드바에 표시 */ import { memo, useMemo } from "react"; import { AlertTriangle, AlertCircle, Info, ChevronDown, ChevronUp, X } from "lucide-react"; import type { FlowValidation } from "@/lib/utils/flowValidation"; import { summarizeValidations } from "@/lib/utils/flowValidation"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; import { cn } from "@/lib/utils"; import { useState } from "react"; interface ValidationPanelProps { validations: FlowValidation[]; onNodeClick?: (nodeId: string) => void; onClose?: () => void; } export const ValidationPanel = memo( ({ validations, onNodeClick, onClose }: ValidationPanelProps) => { const [expandedTypes, setExpandedTypes] = useState>(new Set()); const summary = useMemo( () => summarizeValidations(validations), [validations] ); // 타입별로 그룹화 const groupedValidations = useMemo(() => { const groups = new Map(); for (const validation of validations) { if (!groups.has(validation.type)) { groups.set(validation.type, []); } groups.get(validation.type)!.push(validation); } return Array.from(groups.entries()).sort((a, b) => { // 심각도 순으로 정렬 const severityOrder = { error: 0, warning: 1, info: 2 }; const aSeverity = Math.min( ...a[1].map((v) => severityOrder[v.severity]) ); const bSeverity = Math.min( ...b[1].map((v) => severityOrder[v.severity]) ); return aSeverity - bSeverity; }); }, [validations]); const toggleExpanded = (type: string) => { setExpandedTypes((prev) => { const next = new Set(prev); if (next.has(type)) { next.delete(type); } else { next.add(type); } return next; }); }; const getTypeLabel = (type: string): string => { const labels: Record = { "parallel-conflict": "병렬 실행 충돌", "missing-where": "WHERE 조건 누락", "circular-reference": "순환 참조", "data-source-mismatch": "데이터 소스 불일치", "parallel-table-access": "병렬 테이블 접근", }; return labels[type] || type; }; if (validations.length === 0) { return (

검증 결과

{onClose && ( )}

문제 없음

플로우에 문제가 발견되지 않았습니다

); } return (
{/* 헤더 */}

검증 결과

{onClose && ( )}
{/* 요약 */}
{summary.errorCount > 0 && ( 오류 {summary.errorCount} )} {summary.warningCount > 0 && ( 경고 {summary.warningCount} )} {summary.infoCount > 0 && ( 정보 {summary.infoCount} )}
{summary.hasBlockingIssues && (

⛔ 오류를 해결해야 저장할 수 있습니다

)}
{/* 검증 결과 목록 */}
{groupedValidations.map(([type, typeValidations]) => { const isExpanded = expandedTypes.has(type); const firstValidation = typeValidations[0]; const Icon = firstValidation.severity === "error" ? AlertCircle : firstValidation.severity === "warning" ? AlertTriangle : Info; return (
{/* 그룹 헤더 */} {/* 상세 내용 */} {isExpanded && (
{typeValidations.map((validation, index) => (
onNodeClick?.(validation.nodeId)} >
{validation.message}
{validation.affectedNodes && validation.affectedNodes.length > 1 && (
영향받는 노드: {validation.affectedNodes.length}개
)}
클릭하여 노드 보기
))}
)}
); })}
); } ); ValidationPanel.displayName = "ValidationPanel";