From 1fcd8244b1b82e3a31d9bae6e05034d1ff600e37 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:12:15 +0200 Subject: [PATCH] refactor(admin): split evidence, process-tasks, iace/hazards pages Extract components and hooks into _components/ and _hooks/ subdirectories to reduce each page.tsx to under 500 LOC (was 1545/1383/1316). Final line counts: evidence=213, process-tasks=304, hazards=157. Co-Authored-By: Claude Sonnet 4.6 --- .../evidence/_components/AuditTrailPanel.tsx | 95 + .../sdk/evidence/_components/ChecksTab.tsx | 115 ++ .../sdk/evidence/_components/EvidenceCard.tsx | 73 +- .../sdk/evidence/_components/EvidenceTypes.ts | 77 + .../sdk/evidence/_components/MappingTab.tsx | 73 + .../sdk/evidence/_components/RejectModal.tsx | 76 + .../sdk/evidence/_components/ReportTab.tsx | 88 + .../sdk/evidence/_components/ReviewModal.tsx | 125 ++ .../app/sdk/evidence/_hooks/useEvidence.ts | 195 +- admin-compliance/app/sdk/evidence/page.tsx | 1634 ++--------------- .../hazards/_components/AutoSuggestPanel.tsx | 153 ++ .../hazards/_components/HazardForm.tsx | 171 +- .../hazards/_components/HazardTable.tsx | 39 +- .../hazards/_components/LibraryModal.tsx | 83 +- .../hazards/_components/RiskBadge.tsx | 17 +- .../[projectId]/hazards/_components/types.ts | 130 +- .../[projectId]/hazards/_hooks/useHazards.ts | 173 ++ .../app/sdk/iace/[projectId]/hazards/page.tsx | 1265 +------------ .../_components/CalendarView.tsx | 81 + .../_components/CompleteModal.tsx | 70 + .../process-tasks/_components/SkipModal.tsx | 55 + .../_components/TaskDetailModal.tsx | 165 ++ .../_components/TaskFormModal.tsx | 146 ++ .../sdk/process-tasks/_components/Toast.tsx | 16 + .../sdk/process-tasks/_components/types.ts | 179 ++ .../process-tasks/_hooks/useProcessTasks.ts | 149 ++ .../app/sdk/process-tasks/page.tsx | 1261 +------------ 27 files changed, 2621 insertions(+), 4083 deletions(-) create mode 100644 admin-compliance/app/sdk/evidence/_components/AuditTrailPanel.tsx create mode 100644 admin-compliance/app/sdk/evidence/_components/ChecksTab.tsx create mode 100644 admin-compliance/app/sdk/evidence/_components/MappingTab.tsx create mode 100644 admin-compliance/app/sdk/evidence/_components/RejectModal.tsx create mode 100644 admin-compliance/app/sdk/evidence/_components/ReportTab.tsx create mode 100644 admin-compliance/app/sdk/evidence/_components/ReviewModal.tsx create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_components/AutoSuggestPanel.tsx create mode 100644 admin-compliance/app/sdk/iace/[projectId]/hazards/_hooks/useHazards.ts create mode 100644 admin-compliance/app/sdk/process-tasks/_components/CalendarView.tsx create mode 100644 admin-compliance/app/sdk/process-tasks/_components/CompleteModal.tsx create mode 100644 admin-compliance/app/sdk/process-tasks/_components/SkipModal.tsx create mode 100644 admin-compliance/app/sdk/process-tasks/_components/TaskDetailModal.tsx create mode 100644 admin-compliance/app/sdk/process-tasks/_components/TaskFormModal.tsx create mode 100644 admin-compliance/app/sdk/process-tasks/_components/Toast.tsx create mode 100644 admin-compliance/app/sdk/process-tasks/_components/types.ts create mode 100644 admin-compliance/app/sdk/process-tasks/_hooks/useProcessTasks.ts diff --git a/admin-compliance/app/sdk/evidence/_components/AuditTrailPanel.tsx b/admin-compliance/app/sdk/evidence/_components/AuditTrailPanel.tsx new file mode 100644 index 0000000..0543c39 --- /dev/null +++ b/admin-compliance/app/sdk/evidence/_components/AuditTrailPanel.tsx @@ -0,0 +1,95 @@ +'use client' + +import { useState, useEffect } from 'react' + +export function AuditTrailPanel({ evidenceId, onClose }: { evidenceId: string; onClose: () => void }) { + const [entries, setEntries] = useState<{ id: string; action: string; actor: string; timestamp: string; details: Record | null }[]>([]) + const [loading, setLoading] = useState(true) + + useEffect(() => { + fetch(`/api/sdk/v1/compliance/audit-trail?entity_type=evidence&entity_id=${evidenceId}`) + .then(res => res.json()) + .then(data => { + const mapped = (data.entries || []).map((e: Record) => ({ + id: e.id as string, + action: e.action as string, + actor: (e.performed_by || 'System') as string, + timestamp: (e.performed_at || '') as string, + details: { + ...(e.field_changed ? { field: e.field_changed } : {}), + ...(e.old_value ? { old: e.old_value } : {}), + ...(e.new_value ? { new: e.new_value } : {}), + ...(e.change_summary ? { summary: e.change_summary } : {}), + } as Record, + })) + setEntries(mapped) + }) + .catch(() => {}) + .finally(() => setLoading(false)) + }, [evidenceId]) + + const actionLabels: Record = { + created: { label: 'Erstellt', color: 'bg-blue-100 text-blue-700' }, + uploaded: { label: 'Hochgeladen', color: 'bg-purple-100 text-purple-700' }, + reviewed: { label: 'Reviewed', color: 'bg-green-100 text-green-700' }, + rejected: { label: 'Abgelehnt', color: 'bg-red-100 text-red-700' }, + updated: { label: 'Aktualisiert', color: 'bg-yellow-100 text-yellow-700' }, + deleted: { label: 'Geloescht', color: 'bg-gray-100 text-gray-700' }, + approved: { label: 'Genehmigt', color: 'bg-emerald-100 text-emerald-700' }, + four_eyes_first: { label: '1. Review (4-Augen)', color: 'bg-blue-100 text-blue-700' }, + four_eyes_final: { label: 'Finale Freigabe (4-Augen)', color: 'bg-emerald-100 text-emerald-700' }, + } + + return ( +
+
e.stopPropagation()}> +
+

Audit-Trail

+ +
+ + {loading ? ( +
+
+
+ ) : entries.length === 0 ? ( +
+

Keine Audit-Trail-Eintraege vorhanden.

+
+ ) : ( +
+
+
+ {entries.map((entry, idx) => { + const meta = actionLabels[entry.action] || { label: entry.action, color: 'bg-gray-100 text-gray-700' } + return ( +
+
+
+
+ {meta.label} + + {entry.timestamp ? new Date(entry.timestamp).toLocaleString('de-DE') : '—'} + +
+
+ {entry.actor || 'System'} +
+ {entry.details && Object.keys(entry.details).length > 0 && ( +
+ {Object.entries(entry.details).map(([k, v]) => ( +
{k}: {String(v)}
+ ))} +
+ )} +
+
+ ) + })} +
+
+ )} +
+
+ ) +} diff --git a/admin-compliance/app/sdk/evidence/_components/ChecksTab.tsx b/admin-compliance/app/sdk/evidence/_components/ChecksTab.tsx new file mode 100644 index 0000000..5bc3206 --- /dev/null +++ b/admin-compliance/app/sdk/evidence/_components/ChecksTab.tsx @@ -0,0 +1,115 @@ +'use client' + +import type { EvidenceCheck, CheckResult, CHECK_TYPE_LABELS, RUN_STATUS_LABELS } from './EvidenceTypes' +import { CHECK_TYPE_LABELS as checkTypeLabels, RUN_STATUS_LABELS as runStatusLabels } from './EvidenceTypes' + +export function ChecksTab({ + checks, + checksLoading, + checkResults, + runningCheckId, + seedingChecks, + onRun, + onLoadResults, + onSeed, +}: { + checks: EvidenceCheck[] + checksLoading: boolean + checkResults: Record + runningCheckId: string | null + seedingChecks: boolean + onRun: (id: string) => void + onLoadResults: (id: string) => void + onSeed: () => void +}) { + return ( + <> + {!checksLoading && checks.length === 0 && ( +
+
+

Keine automatischen Checks vorhanden

+

Laden Sie ca. 15 Standard-Checks (TLS, Header, Zertifikate, etc.).

+
+ +
+ )} + + {checksLoading ? ( +
+
+
+ ) : ( +
+ {checks.map(check => { + const typeMeta = checkTypeLabels[check.check_type] || { label: check.check_type, color: 'bg-gray-100 text-gray-700' } + const results = checkResults[check.id] || [] + const lastResult = results[0] + const isRunning = runningCheckId === check.id + + return ( +
+
+
+
+

{check.title}

+ {typeMeta.label} + {!check.is_active && ( + Deaktiviert + )} +
+ {check.description &&

{check.description}

} +
+ Code: {check.check_code} + {check.target_url && Ziel: {check.target_url}} + Frequenz: {check.frequency} + {check.last_run_at && Letzter Lauf: {new Date(check.last_run_at).toLocaleDateString('de-DE')}} +
+
+
+ {lastResult && ( + + {runStatusLabels[lastResult.run_status]?.label || lastResult.run_status} + + )} + + +
+
+ + {results.length > 0 && ( +
+

Letzte Ergebnisse

+
+ {results.slice(0, 3).map(r => ( +
+ + {runStatusLabels[r.run_status]?.label || r.run_status} + + {new Date(r.run_at).toLocaleString('de-DE')} + {r.duration_ms}ms + {r.findings_count > 0 && ( + {r.findings_count} Findings ({r.critical_findings} krit.) + )} + {r.summary && {r.summary}} +
+ ))} +
+
+ )} +
+ ) + })} +
+ )} + + ) +} diff --git a/admin-compliance/app/sdk/evidence/_components/EvidenceCard.tsx b/admin-compliance/app/sdk/evidence/_components/EvidenceCard.tsx index a94bd48..a0f4957 100644 --- a/admin-compliance/app/sdk/evidence/_components/EvidenceCard.tsx +++ b/admin-compliance/app/sdk/evidence/_components/EvidenceCard.tsx @@ -1,5 +1,12 @@ 'use client' +import React from 'react' +import { + ConfidenceLevelBadge, + TruthStatusBadge, + GenerationModeBadge, + ApprovalStatusBadge, +} from '../components/anti-fake-badges' import type { DisplayEvidence, DisplayEvidenceType } from './EvidenceTypes' const typeIcons: Record = { @@ -50,16 +57,14 @@ const typeIconBg: Record = { document: 'bg-gray-100 text-gray-600', } -export function EvidenceCard({ - evidence, - onDelete, - onView, - onDownload, -}: { +export function EvidenceCard({ evidence, onDelete, onView, onDownload, onReview, onReject, onShowHistory }: { evidence: DisplayEvidence onDelete: () => void onView: () => void onDownload: () => void + onReview: () => void + onReject: () => void + onShowHistory: () => void }) { return (

{evidence.name}

- - {statusLabels[evidence.status]} - +
+ + {statusLabels[evidence.status]} + + + + + +

{evidence.description}

@@ -91,14 +102,10 @@ export function EvidenceCard({
{evidence.linkedRequirements.map(req => ( - - {req} - + {req} ))} {evidence.linkedControls.map(ctrl => ( - - {ctrl} - + {ctrl} ))}
@@ -107,26 +114,34 @@ export function EvidenceCard({
Hochgeladen von: {evidence.uploadedBy}
- - - + {(evidence.approvalStatus === 'none' || evidence.approvalStatus === 'pending_first' || evidence.approvalStatus === 'first_approved' || !evidence.approvalStatus) && evidence.approvalStatus !== 'approved' && evidence.approvalStatus !== 'rejected' && ( + + )} + {evidence.requiresFourEyes && evidence.approvalStatus !== 'rejected' && evidence.approvalStatus !== 'approved' && ( + + )} +
diff --git a/admin-compliance/app/sdk/evidence/_components/EvidenceTypes.ts b/admin-compliance/app/sdk/evidence/_components/EvidenceTypes.ts index ce3ad6a..ab37925 100644 --- a/admin-compliance/app/sdk/evidence/_components/EvidenceTypes.ts +++ b/admin-compliance/app/sdk/evidence/_components/EvidenceTypes.ts @@ -20,6 +20,12 @@ export interface DisplayEvidence { status: DisplayStatus fileSize: string fileUrl: string | null + // Anti-Fake-Evidence Phase 2 + confidenceLevel: string | null + truthStatus: string | null + generationMode: string | null + approvalStatus: string | null + requiresFourEyes: boolean } export interface EvidenceTemplate { @@ -37,6 +43,77 @@ export interface EvidenceTemplate { fileSize: string } +export interface EvidenceCheck { + id: string + check_code: string + title: string + description: string | null + check_type: string + target_url: string | null + frequency: string + is_active: boolean + last_run_at: string | null + next_run_at: string | null +} + +export interface CheckResult { + id: string + check_id: string + run_status: string + summary: string | null + findings_count: number + critical_findings: number + duration_ms: number + run_at: string +} + +export interface EvidenceMapping { + id: string + evidence_id: string + control_code: string + mapping_type: string + verified_at: string | null + verified_by: string | null + notes: string | null +} + +export interface CoverageReport { + total_controls: number + controls_with_evidence: number + controls_without_evidence: number + coverage_percent: number +} + +export type EvidenceTabKey = 'evidence' | 'checks' | 'mapping' | 'report' + +export const CHECK_TYPE_LABELS: Record = { + tls_scan: { label: 'TLS-Scan', color: 'bg-blue-100 text-blue-700' }, + header_check: { label: 'Header-Check', color: 'bg-green-100 text-green-700' }, + certificate_check: { label: 'Zertifikat', color: 'bg-yellow-100 text-yellow-700' }, + dns_check: { label: 'DNS-Check', color: 'bg-purple-100 text-purple-700' }, + api_scan: { label: 'API-Scan', color: 'bg-indigo-100 text-indigo-700' }, + config_scan: { label: 'Config-Scan', color: 'bg-orange-100 text-orange-700' }, + port_scan: { label: 'Port-Scan', color: 'bg-red-100 text-red-700' }, +} + +export const RUN_STATUS_LABELS: Record = { + running: { label: 'Laeuft...', color: 'bg-blue-100 text-blue-700' }, + passed: { label: 'Bestanden', color: 'bg-green-100 text-green-700' }, + failed: { label: 'Fehlgeschlagen', color: 'bg-red-100 text-red-700' }, + warning: { label: 'Warnung', color: 'bg-yellow-100 text-yellow-700' }, + error: { label: 'Fehler', color: 'bg-red-100 text-red-700' }, +} + +export const confidenceFilterColors: Record = { + E0: 'bg-red-200 text-red-800', + E1: 'bg-yellow-200 text-yellow-800', + E2: 'bg-blue-200 text-blue-800', + E3: 'bg-green-200 text-green-800', + E4: 'bg-emerald-200 text-emerald-800', +} + +export const CHECK_API = '/api/sdk/v1/compliance/evidence-checks' + export function mapEvidenceTypeToDisplay(type: EvidenceType): DisplayEvidenceType { switch (type) { case 'DOCUMENT': return 'document' diff --git a/admin-compliance/app/sdk/evidence/_components/MappingTab.tsx b/admin-compliance/app/sdk/evidence/_components/MappingTab.tsx new file mode 100644 index 0000000..8a9771a --- /dev/null +++ b/admin-compliance/app/sdk/evidence/_components/MappingTab.tsx @@ -0,0 +1,73 @@ +'use client' + +import type { EvidenceMapping, CoverageReport } from './EvidenceTypes' + +export function MappingTab({ + mappings, + coverageReport, +}: { + mappings: EvidenceMapping[] + coverageReport: CoverageReport | null +}) { + return ( + <> + {coverageReport && ( +
+
+

Gesamt Controls

+

{coverageReport.total_controls}

+
+
+

Mit Nachweis

+

{coverageReport.controls_with_evidence}

+
+
+

Ohne Nachweis

+

{coverageReport.controls_without_evidence}

+
+
+

Abdeckung

+

{coverageReport.coverage_percent.toFixed(0)}%

+
+
+ )} + +
+
+

Evidence-Control-Verknuepfungen ({mappings.length})

+
+ {mappings.length === 0 ? ( +
+

Noch keine Verknuepfungen erstellt.

+

Fuehren Sie automatische Checks aus, um Nachweise automatisch mit Controls zu verknuepfen.

+
+ ) : ( + + + + + + + + + + + {mappings.map(m => ( + + + + + + + ))} + +
ControlEvidenceTypVerifiziert
{m.control_code}{m.evidence_id.slice(0, 8)}... + {m.mapping_type} + + {m.verified_at ? `${new Date(m.verified_at).toLocaleDateString('de-DE')} von ${m.verified_by || '—'}` : 'Ausstehend'} +
+ )} +
+ + ) +} diff --git a/admin-compliance/app/sdk/evidence/_components/RejectModal.tsx b/admin-compliance/app/sdk/evidence/_components/RejectModal.tsx new file mode 100644 index 0000000..349a24d --- /dev/null +++ b/admin-compliance/app/sdk/evidence/_components/RejectModal.tsx @@ -0,0 +1,76 @@ +'use client' + +import { useState } from 'react' +import type { DisplayEvidence } from './EvidenceTypes' + +export function RejectModal({ evidence, onClose, onSuccess }: { + evidence: DisplayEvidence + onClose: () => void + onSuccess: () => void +}) { + const [reviewedBy, setReviewedBy] = useState('') + const [rejectionReason, setRejectionReason] = useState('') + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + + const handleSubmit = async () => { + if (!reviewedBy.trim()) { setError('Bitte E-Mail-Adresse angeben'); return } + if (!rejectionReason.trim()) { setError('Bitte Ablehnungsgrund angeben'); return } + setSubmitting(true) + setError(null) + try { + const res = await fetch(`/api/sdk/v1/compliance/evidence/${evidence.id}/reject`, { + method: 'PATCH', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ reviewed_by: reviewedBy, rejection_reason: rejectionReason }), + }) + if (!res.ok) { + const err = await res.json().catch(() => ({ detail: 'Ablehnung fehlgeschlagen' })) + throw new Error(typeof err.detail === 'string' ? err.detail : JSON.stringify(err.detail)) + } + onSuccess() + } catch (err) { + setError(err instanceof Error ? err.message : 'Unbekannter Fehler') + } finally { + setSubmitting(false) + } + } + + return ( +
+
e.stopPropagation()}> +

Evidence Ablehnen

+

{evidence.name}

+ +
+ + setReviewedBy(e.target.value)} + placeholder="reviewer@unternehmen.de" + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-red-500 focus:border-transparent" /> +
+ +
+ +