feat: 7 Analyse-Module auf 100% — Backend-Endpoints, DB-Model, Frontend-Persistenz
Alle 7 Analyse-Module (Requirements → Report) von ~80% auf 100% gebracht: - Modul 1 (Requirements): POST/DELETE Endpoints + Frontend-Anbindung + Rollback - Modul 2 (Controls): Evidence-Linking UI mit Validity-Badge - Modul 3 (Evidence): Pagination (Frontend + Backend) - Modul 4 (Risk Matrix): Mitigation-UI, Residual Risk, Status-Workflow - Modul 5 (AI Act): AISystemDB Model, 6 CRUD-Endpoints, Backend-Persistenz - Modul 6 (Audit Checklist): PDF-Download + Session-History - Modul 7 (Audit Report): Detail-Seite mit Checklist Sign-Off + Navigation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -271,6 +271,18 @@ function LoadingSkeleton() {
|
||||
// MAIN PAGE
|
||||
// =============================================================================
|
||||
|
||||
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 default function AuditChecklistPage() {
|
||||
const { state, dispatch } = useSDK()
|
||||
const router = useRouter()
|
||||
@@ -279,6 +291,9 @@ export default function AuditChecklistPage() {
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [activeSessionId, setActiveSessionId] = useState<string | null>(null)
|
||||
const notesTimerRef = useRef<Record<string, ReturnType<typeof setTimeout>>>({})
|
||||
const [pastSessions, setPastSessions] = useState<PastSession[]>([])
|
||||
const [pdfLanguage, setPdfLanguage] = useState<'de' | 'en'>('de')
|
||||
const [generatingPdf, setGeneratingPdf] = useState(false)
|
||||
|
||||
// Fetch checklist from backend on mount
|
||||
useEffect(() => {
|
||||
@@ -354,6 +369,21 @@ export default function AuditChecklistPage() {
|
||||
}
|
||||
|
||||
fetchChecklist()
|
||||
|
||||
// Also fetch all sessions for history view
|
||||
const fetchAllSessions = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/sdk/v1/compliance/audit/sessions')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
const sessions = data.sessions || []
|
||||
setPastSessions(sessions)
|
||||
}
|
||||
} catch {
|
||||
// Silently fail
|
||||
}
|
||||
}
|
||||
fetchAllSessions()
|
||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
// Convert SDK checklist items to display items
|
||||
@@ -468,6 +498,32 @@ export default function AuditChecklistPage() {
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
|
||||
const handlePdfDownload = async () => {
|
||||
if (!activeSessionId) {
|
||||
setError('Kein aktives Audit vorhanden. Erstellen Sie zuerst eine Checkliste.')
|
||||
return
|
||||
}
|
||||
setGeneratingPdf(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch(`/api/sdk/v1/compliance/audit/sessions/${activeSessionId}/pdf?language=${pdfLanguage}`)
|
||||
if (!res.ok) throw new Error('Fehler bei der PDF-Generierung')
|
||||
const blob = await res.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = `audit-checklist-${activeSessionId}.pdf`
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
document.body.removeChild(a)
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'PDF-Download fehlgeschlagen')
|
||||
} finally {
|
||||
setGeneratingPdf(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleNewChecklist = async () => {
|
||||
try {
|
||||
setError(null)
|
||||
@@ -508,8 +564,25 @@ export default function AuditChecklistPage() {
|
||||
onClick={handleExport}
|
||||
className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
|
||||
>
|
||||
Exportieren
|
||||
Export JSON
|
||||
</button>
|
||||
<div className="flex items-center gap-1">
|
||||
<select
|
||||
value={pdfLanguage}
|
||||
onChange={(e) => setPdfLanguage(e.target.value as 'de' | 'en')}
|
||||
className="px-2 py-2 border border-gray-300 rounded-l-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-transparent"
|
||||
>
|
||||
<option value="de">DE</option>
|
||||
<option value="en">EN</option>
|
||||
</select>
|
||||
<button
|
||||
onClick={handlePdfDownload}
|
||||
disabled={generatingPdf || !activeSessionId}
|
||||
className="px-4 py-2 text-purple-600 border border-l-0 border-gray-300 hover:bg-purple-50 rounded-r-lg transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
||||
>
|
||||
{generatingPdf ? 'Generiere...' : 'PDF'}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleNewChecklist}
|
||||
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
@@ -646,6 +719,62 @@ export default function AuditChecklistPage() {
|
||||
<p className="mt-2 text-gray-500">Passen Sie den Filter an.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Session History */}
|
||||
{pastSessions.length > 0 && (
|
||||
<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 => {
|
||||
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
|
||||
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>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user