feat: 7 Vorbereitungs-Module auf 100% — Frontend, Proxy-Routen, Backend-Fixes
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-ai-compliance (push) Successful in 35s
CI / test-python-backend-compliance (push) Successful in 30s
CI / test-python-document-crawler (push) Successful in 22s
CI / test-python-dsms-gateway (push) Successful in 19s

Profil: machineBuilder-Felder im POST-Body, PATCH-Handler
Scope: API-Route (GET/POST), ScopeDecisionTab Props + Buttons, Export-Druckansicht HTML
Anwendung: PUT-Handler, Bearbeiten-Button, Pagination/Search
Import: Verlauf laden, DELETE-Route, Offline-Badge, ObjectURL Memory-Leak fix
Screening: Security-Backlog Button verdrahtet, Scan-Verlauf
Module: Detail-Seite, GET-Proxy, Konfigurieren-Button, Modul-erstellen-Modal, Error-Toast
Quellen: 10 Proxy-Routen, Tab-Komponenten umgestellt, Dashboard-Tab, blocked_today Bug fix, Datum-Filter

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-03-02 15:08:13 +01:00
parent fc83ebfd82
commit d079886819
32 changed files with 1734 additions and 76 deletions

View File

@@ -1,6 +1,6 @@
'use client'
import { useState, useCallback } from 'react'
import { useState, useCallback, useEffect } from 'react'
import { useRouter } from 'next/navigation'
import { useSDK } from '@/lib/sdk'
import type { ImportedDocument, ImportedDocumentType, GapAnalysis, GapItem } from '@/lib/sdk/types'
@@ -216,7 +216,15 @@ function FileItem({
<span className="text-sm">Analysiere...</span>
</div>
)}
{file.status === 'complete' && (
{file.status === 'complete' && file.error === 'offline' && (
<div className="flex items-center gap-1 text-amber-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
<span className="text-sm">Offline nicht analysiert</span>
</div>
)}
{file.status === 'complete' && file.error !== 'offline' && (
<div className="flex items-center gap-1 text-green-600">
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
@@ -334,6 +342,42 @@ export default function ImportPage() {
const [files, setFiles] = useState<UploadedFile[]>([])
const [isAnalyzing, setIsAnalyzing] = useState(false)
const [analysisResult, setAnalysisResult] = useState<GapAnalysis | null>(null)
const [importHistory, setImportHistory] = useState<any[]>([])
const [historyLoading, setHistoryLoading] = useState(false)
const [objectUrls, setObjectUrls] = useState<string[]>([])
// 4.1: Load import history
useEffect(() => {
const loadHistory = async () => {
setHistoryLoading(true)
try {
const response = await fetch('/api/sdk/v1/import?tenant_id=default')
if (response.ok) {
const data = await response.json()
setImportHistory(Array.isArray(data) ? data : data.items || [])
}
} catch (err) {
console.error('Failed to load import history:', err)
} finally {
setHistoryLoading(false)
}
}
loadHistory()
}, [analysisResult])
// 4.4: Cleanup ObjectURLs on unmount
useEffect(() => {
return () => {
objectUrls.forEach(url => URL.revokeObjectURL(url))
}
}, [objectUrls])
// Helper to create and track ObjectURLs
const createTrackedObjectURL = useCallback((file: File) => {
const url = URL.createObjectURL(file)
setObjectUrls(prev => [...prev, url])
return url
}, [])
const handleFilesAdded = useCallback((newFiles: File[]) => {
const uploadedFiles: UploadedFile[] = newFiles.map(file => ({
@@ -391,7 +435,7 @@ export default function ImportPage() {
id: result.document_id || file.id,
name: file.file.name,
type: result.detected_type || file.type,
fileUrl: URL.createObjectURL(file.file),
fileUrl: createTrackedObjectURL(file.file),
uploadedAt: new Date(),
analyzedAt: new Date(),
analysisResult: {
@@ -430,7 +474,7 @@ export default function ImportPage() {
id: file.id,
name: file.file.name,
type: file.type,
fileUrl: URL.createObjectURL(file.file),
fileUrl: createTrackedObjectURL(file.file),
uploadedAt: new Date(),
analyzedAt: new Date(),
analysisResult: {
@@ -442,7 +486,7 @@ export default function ImportPage() {
},
}
addImportedDocument(doc)
setFiles(prev => prev.map(f => (f.id === file.id ? { ...f, progress: 100, status: 'complete' as const } : f)))
setFiles(prev => prev.map(f => (f.id === file.id ? { ...f, progress: 100, status: 'complete' as const, error: 'offline' } : f)))
}
}
@@ -565,6 +609,56 @@ export default function ImportPage() {
</button>
</div>
)}
{/* Import-Verlauf (4.1) */}
{importHistory.length > 0 && (
<div className="bg-white rounded-xl border border-gray-200 overflow-hidden">
<div className="px-6 py-4 border-b border-gray-200 bg-gray-50">
<h3 className="font-semibold text-gray-900">Import-Verlauf</h3>
<p className="text-sm text-gray-500">{importHistory.length} fruehere Imports</p>
</div>
<div className="divide-y divide-gray-100">
{importHistory.map((item: any, idx: number) => (
<div key={item.id || idx} className="px-6 py-4 flex items-center justify-between hover:bg-gray-50">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-gray-100 rounded-lg flex items-center justify-center">
<svg className="w-5 h-5 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
</div>
<div>
<p className="text-sm font-medium text-gray-900">{item.name || item.filename || `Import #${idx + 1}`}</p>
<p className="text-xs text-gray-500">
{item.document_type || item.type || 'Unbekannt'} {item.uploaded_at ? new Date(item.uploaded_at).toLocaleString('de-DE') : 'Unbekannt'}
</p>
</div>
</div>
<button
onClick={async () => {
try {
const res = await fetch(`/api/sdk/v1/import/${item.id}`, { method: 'DELETE' })
if (res.ok) {
setImportHistory(prev => prev.filter(h => h.id !== item.id))
}
} catch (err) {
console.error('Failed to delete import:', err)
}
}}
className="p-2 text-gray-400 hover:text-red-500 transition-colors"
title="Import loeschen"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
))}
</div>
</div>
)}
{historyLoading && (
<div className="text-center py-4 text-sm text-gray-500">Import-Verlauf wird geladen...</div>
)}
</div>
)
}