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>
This commit is contained in:
Sharang Parnerkar
2026-04-17 12:26:39 +02:00
parent 083792dfd7
commit e3a1822883
22 changed files with 1988 additions and 2848 deletions

View File

@@ -14,12 +14,14 @@ interface ContentTabProps {
bulkGenerating: boolean
bulkResult: { generated: number; skipped: number; errors: string[] } | null
moduleMedia: TrainingMedia[]
interactiveGenerating?: boolean
onGenerateContent: () => void
onGenerateQuiz: () => void
onBulkContent: () => void
onBulkQuiz: () => void
onPublishContent: (contentId: string) => void
onReloadMedia: () => void
onGenerateInteractiveVideo?: () => void
}
export function ContentTab({
@@ -31,12 +33,14 @@ export function ContentTab({
bulkGenerating,
bulkResult,
moduleMedia,
interactiveGenerating,
onGenerateContent,
onGenerateQuiz,
onBulkContent,
onBulkQuiz,
onPublishContent,
onReloadMedia,
onGenerateInteractiveVideo,
}: ContentTabProps) {
return (
<div className="space-y-6">
@@ -139,6 +143,35 @@ export function ContentTab({
/>
)}
{/* Interactive Video */}
{selectedModuleId && generatedContent?.is_published && onGenerateInteractiveVideo && (
<div className="bg-white border rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<div>
<h3 className="text-sm font-medium text-gray-700">Interaktives Video</h3>
<p className="text-xs text-gray-500">Video mit Narrator-Persona und Checkpoint-Quizzes</p>
</div>
{moduleMedia.some(m => m.media_type === 'interactive_video' && m.status === 'completed') ? (
<span className="px-3 py-1.5 text-xs bg-purple-100 text-purple-700 rounded-full">Interaktiv erstellt</span>
) : (
<button
onClick={onGenerateInteractiveVideo}
disabled={interactiveGenerating}
className="px-4 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50"
>
{interactiveGenerating ? 'Generiere interaktives Video...' : 'Interaktives Video generieren'}
</button>
)}
</div>
{moduleMedia.filter(m => m.media_type === 'interactive_video' && m.status === 'completed').map(m => (
<div key={m.id} className="text-xs text-gray-500 space-y-1 bg-gray-50 rounded p-3">
<p>Dauer: {Math.round(m.duration_seconds / 60)} Min | Groesse: {(m.file_size_bytes / 1024 / 1024).toFixed(1)} MB</p>
<p>Generiert: {new Date(m.created_at).toLocaleString('de-DE')}</p>
</div>
))}
</div>
)}
{/* Script Preview */}
{selectedModuleId && generatedContent?.is_published && (
<ScriptPreview moduleId={selectedModuleId} />