Files
breakpilot-compliance/admin-compliance/app/sdk/control-library/components/ControlRegulatorySection.tsx
Sharang Parnerkar e3a1822883 refactor(admin): split training, control-provenance, iace/verification, training/learner, ControlDetail
All 5 files reduced below 500 LOC (hard cap) by extracting sub-components:

- training/page.tsx: 780→278 LOC — imports existing _components/, adds BlocksSection
- control-provenance/page.tsx: 739→145 LOC — extracts provenance-data.ts, ProvenanceHelpers, LicenseMatrix, SourceRegistry
- iace/[projectId]/verification/page.tsx: 673→164 LOC — extracts VerificationForm, CompleteModal, SuggestEvidenceModal, VerificationTable
- training/learner/page.tsx: 560→216 LOC — extracts AssignmentsList, ContentView, QuizView, CertificatesView
- ControlDetail.tsx: 878→311 LOC — adds ControlSourceCitation, ControlTraceability, ControlRegulatorySection, ControlSimilarControls, ControlReviewActions siblings

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 12:26:39 +02:00

86 lines
3.7 KiB
TypeScript

'use client'
import { Scale } from 'lucide-react'
import type { CanonicalControl } from './helpers'
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 ControlRegulatorySectionProps {
ctrl: CanonicalControl
v1Matches: V1Match[]
loadingV1: boolean
onNavigateToControl?: (controlId: string) => void
onCompare?: (ctrl: CanonicalControl, matches: V1Match[]) => void
}
export function ControlRegulatorySection({
ctrl, v1Matches, loadingV1, onNavigateToControl, onCompare,
}: ControlRegulatorySectionProps) {
return (
<section className="bg-orange-50 border border-orange-200 rounded-lg p-4">
<div className="flex items-center gap-2 mb-3">
<Scale className="w-4 h-4 text-orange-600" />
<h3 className="text-sm font-semibold text-orange-900">Regulatorische Abdeckung</h3>
{loadingV1 && <span className="text-xs text-orange-400">Laden...</span>}
</div>
{v1Matches.length > 0 ? (
<div className="space-y-2">
{v1Matches.map((match, i) => (
<div key={i} className="bg-white/60 border border-orange-100 rounded-lg p-3">
<div className="flex items-start justify-between gap-2">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap mb-1">
{match.matched_source && (
<span className="text-xs font-semibold text-blue-800 bg-blue-100 px-1.5 py-0.5 rounded">{match.matched_source}</span>
)}
{match.matched_article && (
<span className="text-xs text-blue-700 bg-blue-50 px-1.5 py-0.5 rounded">{match.matched_article}</span>
)}
<span className={`text-xs font-medium px-1.5 py-0.5 rounded ${
match.similarity_score >= 0.85 ? 'bg-green-100 text-green-700' :
match.similarity_score >= 0.80 ? 'bg-yellow-100 text-yellow-700' : 'bg-gray-100 text-gray-600'
}`}>
{(match.similarity_score * 100).toFixed(0)}%
</span>
</div>
<p className="text-sm text-gray-800">
{onNavigateToControl ? (
<button onClick={() => onNavigateToControl(match.matched_control_id)}
className="font-mono text-xs text-purple-600 bg-purple-50 px-1.5 py-0.5 rounded hover:bg-purple-100 hover:underline mr-1.5">
{match.matched_control_id}
</button>
) : (
<span className="font-mono text-xs text-purple-600 bg-purple-50 px-1.5 py-0.5 rounded mr-1.5">{match.matched_control_id}</span>
)}
{match.matched_title}
</p>
</div>
{onCompare && (
<button onClick={() => onCompare(ctrl, v1Matches)}
className="text-xs text-orange-600 border border-orange-300 rounded px-2 py-1 hover:bg-orange-100 whitespace-nowrap flex-shrink-0">
Vergleichen
</button>
)}
</div>
</div>
))}
</div>
) : !loadingV1 ? (
<p className="text-sm text-orange-600">Keine regulatorische Abdeckung gefunden. Dieses Control ist eine reine Eigenentwicklung.</p>
) : null}
</section>
)
}