Spaces:
Running
Running
File size: 4,253 Bytes
7b4f5dd | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | /* βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
FindingCard β Expandable finding card with severity + code
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */
import { useState } from 'react';
import SeverityBadge from './SeverityBadge';
import './FindingCard.css';
const AGENT_ICONS = {
security: 'π',
performance: 'β‘',
fix: 'π§',
};
export default function FindingCard({ finding, index, fix }) {
const [isExpanded, setIsExpanded] = useState(false);
return (
<div
className={`finding-card glass-card-static animate-slide-in`}
style={{ animationDelay: `${index * 0.05}s` }}
>
<div
className="finding-card-header"
onClick={() => setIsExpanded(!isExpanded)}
>
<div className="finding-header-left">
<SeverityBadge severity={finding.severity} />
<span className="finding-agent-icon" title={`${finding.agent} agent`}>
{AGENT_ICONS[finding.agent] || 'π'}
</span>
</div>
<div className="finding-header-right">
{finding.cwe && <span className="tag">{finding.cwe}</span>}
<button className="expand-btn" aria-label="Toggle details">
{isExpanded ? 'βΎ' : 'βΈ'}
</button>
</div>
</div>
<div className="finding-title-row">
<h4 className="finding-title">{finding.title}</h4>
{finding.id && <span className="finding-id mono">{finding.id}</span>}
</div>
<p className="finding-description">{finding.description}</p>
{finding.file && (
<div className="finding-location">
<span className="location-icon">π</span>
<span className="location-path mono">{finding.file}</span>
{finding.line && <span className="location-line mono">:{finding.line}</span>}
</div>
)}
{/* Expanded Details */}
{isExpanded && (
<div className="finding-details animate-fade-in">
{/* Code Snippet */}
{finding.code && (
<div className="finding-code-section">
<div className="code-section-label">Vulnerable Code</div>
<div className="code-block">
<pre><code>{finding.code}</code></pre>
</div>
</div>
)}
{/* Suggestion */}
{finding.suggestion && (
<div className="finding-suggestion">
<div className="code-section-label">π‘ Recommendation</div>
<p>{finding.suggestion}</p>
</div>
)}
{/* Fix Preview */}
{fix && (
<div className="finding-fix-preview">
<div className="code-section-label">π§ AI-Generated Fix</div>
<div className="diff-container">
<div className="diff-panel diff-before">
<div className="diff-header">Before</div>
<div className="code-block">
<pre><code>{fix.before}</code></pre>
</div>
</div>
<div className="diff-arrow">β</div>
<div className="diff-panel diff-after">
<div className="diff-header">After</div>
<div className="code-block">
<pre><code>{fix.after}</code></pre>
</div>
</div>
</div>
{fix.explanation && (
<p className="fix-explanation">{fix.explanation}</p>
)}
</div>
)}
</div>
)}
{/* Quick action bar */}
<div className="finding-actions">
{finding.fixAvailable && !fix && (
<span className="fix-available-tag">
<span>π§</span> Fix available
</span>
)}
{fix && (
<span className="fix-ready-tag">
<span>β
</span> Fix generated
</span>
)}
</div>
</div>
);
}
|