contract-extractor / frontend /src /components /ContractCompare.jsx
myke69's picture
Add files using upload-large-folder tool
3752e5d verified
Raw
History Blame Contribute Delete
6.24 kB
import React, { useState } from 'react'
import { api } from '../api.js'
const LEVEL_COLOR = { HIGH: '#c92a2a', MEDIUM: '#d9750b', LOW: '#5b6478' }
function RiskBadge({ risk }) {
return (
<div className="cmp-risk" style={{ color: LEVEL_COLOR[risk.level] }}>
<span className="cmp-risk-dot" style={{ background: LEVEL_COLOR[risk.level] }} />
{risk.title} <span className="cmp-sev">({risk.severity})</span>
</div>
)
}
function Side({ side }) {
if (!side.clauses.length && !side.risks.length) {
return <span className="cmp-absent">Not present</span>
}
return (
<div className="cmp-side">
{side.clauses.map((c, i) => (
<div key={i} className="cmp-clause-name">
{c.heading || `Clause ${c.number || c.id}`}
</div>
))}
{side.risks.map((r, i) => <RiskBadge key={i} risk={r} />)}
</div>
)
}
export default function ContractCompare({ currentId, currentFilename, existing, onClose }) {
const [otherId, setOtherId] = useState('')
const [result, setResult] = useState(null)
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
const others = existing.filter((c) => c.contract_id !== currentId)
async function runCompare() {
if (!otherId) return
setLoading(true)
setError(null)
setResult(null)
try {
const res = await api(`/api/compare?a=${currentId}&b=${otherId}`)
if (!res.ok) {
const body = await res.json().catch(() => ({}))
throw new Error(body.detail || `Compare failed (${res.status})`)
}
setResult(await res.json())
} catch (e) {
setError(e.message)
} finally {
setLoading(false)
}
}
return (
<div className="compare-overlay">
<div className="compare-header">
<h2 className="compare-title">Contract Comparison</h2>
<button className="compare-close" onClick={onClose}>✕ Close</button>
</div>
<div className="compare-controls">
<div className="compare-contract-a">
<span className="compare-label">A</span>
<span className="compare-filename">{currentFilename}</span>
</div>
<span className="compare-vs">vs</span>
<div className="compare-contract-b">
<span className="compare-label">B</span>
<select
className="compare-select"
value={otherId}
onChange={(e) => setOtherId(e.target.value)}
>
<option value="">Select contract…</option>
{others.map((c) => (
<option key={c.contract_id} value={c.contract_id}>
{c.filename}
</option>
))}
</select>
</div>
<button
className="compare-run-btn"
onClick={runCompare}
disabled={!otherId || loading}
>
{loading ? 'Comparing…' : 'Compare →'}
</button>
</div>
{others.length === 0 && (
<p className="muted pad">Upload at least one other contract to compare against.</p>
)}
{error && <div className="banner error">⚠ {error}</div>}
{result && (
<div className="compare-results">
<div className="compare-summary-bar">
<span className="csb-item">{result.summary.both} shared categories</span>
<span className="csb-item csb-only-a">{result.summary.only_a} only in A</span>
<span className="csb-item csb-only-b">{result.summary.only_b} only in B</span>
{result.summary.risk_increased > 0 && (
<span className="csb-item csb-worse">↑ {result.summary.risk_increased} higher risk in B</span>
)}
{result.summary.risk_decreased > 0 && (
<span className="csb-item csb-better">↓ {result.summary.risk_decreased} lower risk in B</span>
)}
</div>
<div className="compare-table-wrap">
<table className="compare-table">
<thead>
<tr>
<th className="ct-cat-hdr">Category</th>
<th className="ct-side-hdr">A — {result.a.filename}</th>
<th className="ct-side-hdr">B — {result.b.filename}</th>
<th className="ct-delta-hdr">Δ Risk</th>
</tr>
</thead>
<tbody>
{result.rows.map((row, i) => (
<tr
key={i}
className={
'ct-row' +
(row.status === 'only_a' ? ' ct-only-a' : '') +
(row.status === 'only_b' ? ' ct-only-b' : '')
}
>
<td className="ct-cat">
<b>{row.label}</b>
{row.status !== 'both' && (
<span className="ct-only-badge">
{row.status === 'only_a' ? 'A only' : 'B only'}
</span>
)}
</td>
<td className="ct-side-cell"><Side side={row.a} /></td>
<td className="ct-side-cell"><Side side={row.b} /></td>
<td className="ct-delta">
{row.risk_delta != null ? (
<span
className={
row.risk_delta > 0
? 'delta-worse'
: row.risk_delta < 0
? 'delta-better'
: 'delta-same'
}
>
{row.risk_delta > 0 ? `+${row.risk_delta}` : row.risk_delta}
</span>
) : (
<span className="ct-na"></span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
)}
</div>
)
}