import React, { useState } from 'react'; import { API_BASE } from '../api'; interface StageCompleteData { stage_name: string; summary: string; artifacts: Array<{ name: string; path: string; description: string }>; decisions: string[]; warnings: string[]; next_stage_name: string; next_stage_preview: string; } interface Props { data: StageCompleteData; designName: string; jobId: string; onApprove: () => void; onReject: (feedback: string) => void; isSubmitting: boolean; } const STAGE_ICONS: Record = { INIT: '⚙', SPEC: '◈', SPEC_VALIDATE: '⊘', HIERARCHY_EXPAND: '⊞', FEASIBILITY_CHECK: '⚖', CDC_ANALYZE: '↔', VERIFICATION_PLAN: '☑', RTL_GEN: '⌨', RTL_FIX: '◪', VERIFICATION: '◉', FORMAL_VERIFY: '◈', COVERAGE_CHECK: '◎', REGRESSION: '↺', SDC_GEN: '⧗', FLOORPLAN: '▣', HARDENING: '⬡', CONVERGENCE_REVIEW: '◎', ECO_PATCH: '⟴', SIGNOFF: '✓', SUCCESS: '✦', FAIL: '✗', }; const ARTIFACT_TYPE_LABELS: Record = { rtl: 'RTL', waveform: 'Waveform', layout: 'Layout', constraints: 'SDC', config: 'Config', script: 'Script', formal: 'Formal', log: 'Log', report: 'Report', other: 'File', }; function fmtStage(name: string): string { return name.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase()); } function guessType(name: string): string { const ext = name.split('.').pop()?.toLowerCase() || ''; const map: Record = { v: 'rtl', sv: 'rtl', vcd: 'waveform', gds: 'layout', def: 'layout', sdc: 'constraints', json: 'config', tcl: 'script', sby: 'formal', log: 'log', csv: 'report', }; return map[ext] || 'other'; } export const ApprovalCard: React.FC = ({ data, designName, jobId, onApprove, onReject, isSubmitting }) => { const [showFeedback, setShowFeedback] = useState(false); const [feedback, setFeedback] = useState(''); const [artifactsExpanded, setArtifactsExpanded] = useState(false); const hasWarnings = data.warnings && data.warnings.length > 0; const hasErrors = data.decisions?.some((d: string) => /error|fail/i.test(d)); const artifactCount = data.artifacts?.length || 0; const hasNext = data.next_stage_name && data.next_stage_name !== 'DONE'; const icon = STAGE_ICONS[data.stage_name] || '◆'; const handleReject = () => { onReject(feedback); setShowFeedback(false); setFeedback(''); }; return (
{/* Header — stage identity */}
{icon} {fmtStage(data.stage_name)} {hasErrors && Issue detected} {!hasErrors && hasWarnings && Warning}
{artifactCount > 0 && ( )}
{/* Summary — primary content */}

{data.summary || `${fmtStage(data.stage_name)} completed successfully.`}

{/* Artifact file list */} {artifactsExpanded && data.artifacts && data.artifacts.length > 0 && (
{data.artifacts.map((a, i) => { const aType = guessType(a.name); return (
{ARTIFACT_TYPE_LABELS[aType] || aType} {a.name} {a.description && ( {a.description} )}
); })}
)} {/* Next stage preview */} {hasNext && data.next_stage_preview && (
{data.next_stage_preview}
)} {/* Action footer */}
{/* Stage report downloads */} {jobId && ( )} {!showFeedback ? ( <> ) : (
setFeedback(e.target.value)} autoFocus onKeyDown={e => { if (e.key === 'Enter') handleReject(); if (e.key === 'Escape') { setShowFeedback(false); setFeedback(''); } }} />
)}
); };