refactor(admin): split audit-checklist, cookie-banner, escalations pages
Each page.tsx was 750-780 LOC. Extracted React components to _components/ and custom hooks to _hooks/ next to each page.tsx. All three pages are now under 215 LOC (well within the 500 LOC hard cap). Zero behavior changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { DisplayChecklistItem, DisplayStatus } from './types'
|
||||
|
||||
interface ChecklistItemCardProps {
|
||||
item: DisplayChecklistItem
|
||||
onStatusChange: (status: DisplayStatus) => void
|
||||
onNotesChange: (notes: string) => void
|
||||
onAddEvidence: () => void
|
||||
}
|
||||
|
||||
export function ChecklistItemCard({
|
||||
item,
|
||||
onStatusChange,
|
||||
onNotesChange,
|
||||
onAddEvidence,
|
||||
}: ChecklistItemCardProps) {
|
||||
const [showNotes, setShowNotes] = useState(false)
|
||||
|
||||
const statusColors = {
|
||||
compliant: 'bg-green-100 text-green-700 border-green-300',
|
||||
'non-compliant': 'bg-red-100 text-red-700 border-red-300',
|
||||
partial: 'bg-yellow-100 text-yellow-700 border-yellow-300',
|
||||
'not-reviewed': 'bg-gray-100 text-gray-500 border-gray-300',
|
||||
}
|
||||
|
||||
const priorityColors = {
|
||||
critical: 'bg-red-100 text-red-700',
|
||||
high: 'bg-orange-100 text-orange-700',
|
||||
medium: 'bg-yellow-100 text-yellow-700',
|
||||
low: 'bg-green-100 text-green-700',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`bg-white rounded-xl border-2 p-6 ${
|
||||
item.status === 'non-compliant' ? 'border-red-200' :
|
||||
item.status === 'partial' ? 'border-yellow-200' :
|
||||
item.status === 'compliant' ? 'border-green-200' : 'border-gray-200'
|
||||
}`}>
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<span className="text-xs text-gray-500">{item.category}</span>
|
||||
<span className={`px-2 py-0.5 text-xs rounded-full ${priorityColors[item.priority]}`}>
|
||||
{item.priority === 'critical' ? 'Kritisch' :
|
||||
item.priority === 'high' ? 'Hoch' :
|
||||
item.priority === 'medium' ? 'Mittel' : 'Niedrig'}
|
||||
</span>
|
||||
<span className="px-2 py-0.5 text-xs bg-blue-50 text-blue-600 rounded">
|
||||
{item.requirementId}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-900 font-medium">{item.question}</p>
|
||||
</div>
|
||||
<select
|
||||
value={item.status}
|
||||
onChange={(e) => onStatusChange(e.target.value as DisplayStatus)}
|
||||
className={`px-3 py-2 rounded-lg border text-sm font-medium ${statusColors[item.status]}`}
|
||||
>
|
||||
<option value="not-reviewed">Nicht geprueft</option>
|
||||
<option value="compliant">Konform</option>
|
||||
<option value="partial">Teilweise</option>
|
||||
<option value="non-compliant">Nicht konform</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{item.notes && (
|
||||
<div className="mt-3 p-3 bg-gray-50 rounded-lg text-sm text-gray-600">
|
||||
{item.notes}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.evidence.length > 0 && (
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500">Nachweise:</span>
|
||||
{item.evidence.map(ev => (
|
||||
<span key={ev} className="px-2 py-1 text-xs bg-blue-50 text-blue-600 rounded">
|
||||
{ev}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{item.verifiedBy && item.verifiedAt && (
|
||||
<div className="mt-3 text-sm text-gray-500">
|
||||
Geprueft von {item.verifiedBy} am {item.verifiedAt.toLocaleDateString('de-DE')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-3 flex items-center gap-2">
|
||||
<button
|
||||
onClick={() => setShowNotes(!showNotes)}
|
||||
className="text-sm text-purple-600 hover:text-purple-700"
|
||||
>
|
||||
{showNotes ? 'Notizen ausblenden' : 'Notizen bearbeiten'}
|
||||
</button>
|
||||
<button
|
||||
onClick={onAddEvidence}
|
||||
className="text-sm text-gray-500 hover:text-gray-700"
|
||||
>
|
||||
Nachweis hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showNotes && (
|
||||
<div className="mt-3">
|
||||
<textarea
|
||||
value={item.notes}
|
||||
onChange={(e) => onNotesChange(e.target.value)}
|
||||
placeholder="Notizen hinzufuegen..."
|
||||
rows={2}
|
||||
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
'use client'
|
||||
|
||||
export function LoadingSkeleton() {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{[1, 2, 3].map(i => (
|
||||
<div key={i} className="bg-white rounded-xl border border-gray-200 p-6 animate-pulse">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<div className="h-4 w-24 bg-gray-200 rounded" />
|
||||
<div className="h-4 w-16 bg-gray-200 rounded-full" />
|
||||
</div>
|
||||
<div className="h-5 w-full bg-gray-200 rounded" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { PastSession } from './types'
|
||||
|
||||
interface SessionHistoryProps {
|
||||
pastSessions: PastSession[]
|
||||
activeSessionId: string | null
|
||||
}
|
||||
|
||||
export function SessionHistory({ pastSessions, activeSessionId }: SessionHistoryProps) {
|
||||
const router = useRouter()
|
||||
|
||||
if (pastSessions.length === 0) return null
|
||||
|
||||
const statusBadge: Record<string, string> = {
|
||||
draft: 'bg-slate-100 text-slate-700',
|
||||
in_progress: 'bg-blue-100 text-blue-700',
|
||||
completed: 'bg-green-100 text-green-700',
|
||||
archived: 'bg-purple-100 text-purple-700',
|
||||
}
|
||||
const statusLabel: Record<string, string> = {
|
||||
draft: 'Entwurf',
|
||||
in_progress: 'In Bearbeitung',
|
||||
completed: 'Abgeschlossen',
|
||||
archived: 'Archiviert',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4">Vergangene Audit-Sessions</h3>
|
||||
<div className="space-y-3">
|
||||
{pastSessions
|
||||
.filter(s => s.id !== activeSessionId)
|
||||
.map(session => (
|
||||
<div
|
||||
key={session.id}
|
||||
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg hover:bg-gray-100 cursor-pointer transition-colors"
|
||||
onClick={() => router.push(`/sdk/audit-report/${session.id}`)}
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className={`px-2 py-0.5 text-xs rounded-full ${statusBadge[session.status] || ''}`}>
|
||||
{statusLabel[session.status] || session.status}
|
||||
</span>
|
||||
<span className="text-sm font-medium text-gray-900">{session.name}</span>
|
||||
<span className="text-xs text-gray-500">
|
||||
{new Date(session.created_at).toLocaleDateString('de-DE')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3 text-sm">
|
||||
<span className="text-gray-500">
|
||||
{session.completed_items}/{session.total_items} Punkte
|
||||
</span>
|
||||
<span className={`font-medium ${
|
||||
session.completion_percentage >= 80 ? 'text-green-600' :
|
||||
session.completion_percentage >= 50 ? 'text-yellow-600' : 'text-red-600'
|
||||
}`}>
|
||||
{session.completion_percentage}%
|
||||
</span>
|
||||
<svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
import { ChecklistTemplate } from './types'
|
||||
|
||||
// =============================================================================
|
||||
// FALLBACK TEMPLATES
|
||||
// =============================================================================
|
||||
|
||||
export const checklistTemplates: ChecklistTemplate[] = [
|
||||
{
|
||||
id: 'chk-vvt-001',
|
||||
requirementId: 'req-gdpr-30',
|
||||
question: 'Ist ein Verzeichnis von Verarbeitungstaetigkeiten (VVT) vorhanden und aktuell?',
|
||||
category: 'Dokumentation',
|
||||
priority: 'critical',
|
||||
},
|
||||
{
|
||||
id: 'chk-dse-001',
|
||||
requirementId: 'req-gdpr-13',
|
||||
question: 'Sind Datenschutzhinweise fuer alle Verarbeitungen verfuegbar?',
|
||||
category: 'Transparenz',
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'chk-consent-001',
|
||||
requirementId: 'req-gdpr-6',
|
||||
question: 'Werden Einwilligungen ordnungsgemaess eingeholt und dokumentiert?',
|
||||
category: 'Einwilligung',
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'chk-dsr-001',
|
||||
requirementId: 'req-gdpr-15',
|
||||
question: 'Ist ein Prozess fuer Betroffenenrechte implementiert?',
|
||||
category: 'Betroffenenrechte',
|
||||
priority: 'critical',
|
||||
},
|
||||
{
|
||||
id: 'chk-avv-001',
|
||||
requirementId: 'req-gdpr-28',
|
||||
question: 'Sind Auftragsverarbeitungsvertraege (AVV) mit allen Dienstleistern abgeschlossen?',
|
||||
category: 'Auftragsverarbeitung',
|
||||
priority: 'critical',
|
||||
},
|
||||
{
|
||||
id: 'chk-dsfa-001',
|
||||
requirementId: 'req-gdpr-35',
|
||||
question: 'Wird eine DSFA fuer Hochrisiko-Verarbeitungen durchgefuehrt?',
|
||||
category: 'Risikobewertung',
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'chk-tom-001',
|
||||
requirementId: 'req-gdpr-32',
|
||||
question: 'Sind technische und organisatorische Massnahmen dokumentiert?',
|
||||
category: 'TOMs',
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'chk-incident-001',
|
||||
requirementId: 'req-gdpr-33',
|
||||
question: 'Gibt es einen Incident-Response-Prozess fuer Datenpannen?',
|
||||
category: 'Incident Response',
|
||||
priority: 'critical',
|
||||
},
|
||||
{
|
||||
id: 'chk-ai-001',
|
||||
requirementId: 'req-ai-act-9',
|
||||
question: 'Ist das KI-System nach EU AI Act klassifiziert?',
|
||||
category: 'AI Act',
|
||||
priority: 'high',
|
||||
},
|
||||
{
|
||||
id: 'chk-ai-002',
|
||||
requirementId: 'req-ai-act-13',
|
||||
question: 'Sind Transparenzanforderungen fuer KI-Systeme erfuellt?',
|
||||
category: 'AI Act',
|
||||
priority: 'high',
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ChecklistItem as SDKChecklistItem } from '@/lib/sdk'
|
||||
import { DisplayStatus } from './types'
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
export function mapSDKStatusToDisplay(status: SDKChecklistItem['status']): DisplayStatus {
|
||||
switch (status) {
|
||||
case 'PASSED': return 'compliant'
|
||||
case 'FAILED': return 'non-compliant'
|
||||
case 'NOT_APPLICABLE': return 'partial'
|
||||
case 'PENDING':
|
||||
default: return 'not-reviewed'
|
||||
}
|
||||
}
|
||||
|
||||
export function mapDisplayStatusToSDK(status: DisplayStatus): SDKChecklistItem['status'] {
|
||||
switch (status) {
|
||||
case 'compliant': return 'PASSED'
|
||||
case 'non-compliant': return 'FAILED'
|
||||
case 'partial': return 'NOT_APPLICABLE'
|
||||
case 'not-reviewed':
|
||||
default: return 'PENDING'
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// =============================================================================
|
||||
// TYPES
|
||||
// =============================================================================
|
||||
|
||||
export type DisplayStatus = 'compliant' | 'non-compliant' | 'partial' | 'not-reviewed'
|
||||
export type DisplayPriority = 'critical' | 'high' | 'medium' | 'low'
|
||||
|
||||
export interface DisplayChecklistItem {
|
||||
id: string
|
||||
requirementId: string
|
||||
question: string
|
||||
category: string
|
||||
status: DisplayStatus
|
||||
notes: string
|
||||
evidence: string[]
|
||||
priority: DisplayPriority
|
||||
verifiedBy: string | null
|
||||
verifiedAt: Date | null
|
||||
}
|
||||
|
||||
export interface PastSession {
|
||||
id: string
|
||||
name: string
|
||||
status: string
|
||||
auditor_name: string
|
||||
created_at: string
|
||||
completed_at?: string
|
||||
completion_percentage: number
|
||||
total_items: number
|
||||
completed_items: number
|
||||
}
|
||||
|
||||
export interface ChecklistTemplate {
|
||||
id: string
|
||||
requirementId: string
|
||||
question: string
|
||||
category: string
|
||||
priority: DisplayPriority
|
||||
}
|
||||
Reference in New Issue
Block a user