All 4 page.tsx files reduced well below 500 LOC (235/181/158/262) by extracting components and hooks into colocated _components/ and _hooks/ subdirectories. Zero behavior changes — logic relocated verbatim. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
'use client'
|
|
|
|
import { Search, GitMerge } from 'lucide-react'
|
|
import {
|
|
LicenseRuleBadge, VerificationMethodBadge, type CanonicalControl,
|
|
} from './helpers'
|
|
|
|
interface SimilarControl {
|
|
control_id: string
|
|
title: string
|
|
severity: string
|
|
release_state: string
|
|
tags: string[]
|
|
license_rule: number | null
|
|
verification_method: string | null
|
|
category: string | null
|
|
similarity: number
|
|
}
|
|
|
|
interface V1Match {
|
|
matched_control_id: string
|
|
matched_title: string
|
|
matched_objective: string
|
|
matched_severity: string
|
|
matched_category: string
|
|
matched_source: string | null
|
|
matched_article: string | null
|
|
matched_source_citation: Record<string, string> | null
|
|
similarity_score: number
|
|
match_rank: number
|
|
match_method: string
|
|
}
|
|
|
|
interface ControlSimilarControlsProps {
|
|
ctrl: CanonicalControl
|
|
similarControls: SimilarControl[]
|
|
loadingSimilar: boolean
|
|
selectedDuplicates: Set<string>
|
|
merging: boolean
|
|
onToggleDuplicate: (id: string) => void
|
|
onMergeDuplicates: () => void
|
|
}
|
|
|
|
export function ControlSimilarControls({
|
|
ctrl, similarControls, loadingSimilar, selectedDuplicates, merging, onToggleDuplicate, onMergeDuplicates,
|
|
}: ControlSimilarControlsProps) {
|
|
return (
|
|
<section className="bg-gray-50 border border-gray-200 rounded-lg p-4">
|
|
<div className="flex items-center gap-2 mb-3">
|
|
<Search className="w-4 h-4 text-gray-600" />
|
|
<h3 className="text-sm font-semibold text-gray-800">Aehnliche Controls</h3>
|
|
{loadingSimilar && <span className="text-xs text-gray-400">Laden...</span>}
|
|
</div>
|
|
|
|
{similarControls.length > 0 ? (
|
|
<>
|
|
<div className="mb-3 p-2 bg-white border border-gray-100 rounded flex items-center gap-2">
|
|
<input type="radio" checked readOnly className="text-purple-600" />
|
|
<span className="text-sm font-medium text-purple-700">{ctrl.control_id} — {ctrl.title}</span>
|
|
<span className="text-xs text-gray-400 ml-auto">Behalten (Haupt-Control)</span>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
{similarControls.map(sim => (
|
|
<div key={sim.control_id} className="p-2 bg-white border border-gray-100 rounded flex items-center gap-2">
|
|
<input type="checkbox" checked={selectedDuplicates.has(sim.control_id)}
|
|
onChange={() => onToggleDuplicate(sim.control_id)} className="text-red-600" />
|
|
<span className="text-xs font-mono text-purple-600 bg-purple-50 px-1.5 py-0.5 rounded">{sim.control_id}</span>
|
|
<span className="text-sm text-gray-700 flex-1">{sim.title}</span>
|
|
<span className="text-xs font-medium text-amber-600 bg-amber-50 px-1.5 py-0.5 rounded">
|
|
{(sim.similarity * 100).toFixed(1)}%
|
|
</span>
|
|
<LicenseRuleBadge rule={sim.license_rule} />
|
|
<VerificationMethodBadge method={sim.verification_method} />
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{selectedDuplicates.size > 0 && (
|
|
<button onClick={onMergeDuplicates} disabled={merging}
|
|
className="mt-3 flex items-center gap-1.5 px-3 py-1.5 text-sm text-white bg-red-600 rounded-lg hover:bg-red-700 disabled:opacity-50">
|
|
<GitMerge className="w-3.5 h-3.5" />
|
|
{merging ? 'Zusammenfuehren...' : `${selectedDuplicates.size} Duplikat(e) zusammenfuehren`}
|
|
</button>
|
|
)}
|
|
</>
|
|
) : (
|
|
<p className="text-sm text-gray-500">
|
|
{loadingSimilar ? 'Suche aehnliche Controls...' : 'Keine aehnlichen Controls gefunden.'}
|
|
</p>
|
|
)}
|
|
</section>
|
|
)
|
|
}
|