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

@@ -5,9 +5,22 @@ import { DEPTH_LEVEL_LABELS, DEPTH_LEVEL_DESCRIPTIONS, DEPTH_LEVEL_COLORS, DOCUM
interface ScopeDecisionTabProps {
decision: ScopeDecision | null
answers?: unknown[]
onBackToWizard?: () => void
onGoToExport?: () => void
canEvaluate?: boolean
onEvaluate?: () => void
isEvaluating?: boolean
}
export function ScopeDecisionTab({ decision }: ScopeDecisionTabProps) {
export function ScopeDecisionTab({
decision,
onBackToWizard,
onGoToExport,
canEvaluate,
onEvaluate,
isEvaluating,
}: ScopeDecisionTabProps) {
const [expandedTrigger, setExpandedTrigger] = useState<number | null>(null)
const [showAuditTrail, setShowAuditTrail] = useState(false)
@@ -320,6 +333,35 @@ export function ScopeDecisionTab({ decision }: ScopeDecisionTabProps) {
</div>
)}
{/* Action Buttons */}
<div className="flex items-center gap-3">
{onBackToWizard && (
<button
onClick={onBackToWizard}
className="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors"
>
Zurueck zum Wizard
</button>
)}
{canEvaluate && onEvaluate && (
<button
onClick={onEvaluate}
disabled={isEvaluating}
className="px-4 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 transition-colors"
>
{isEvaluating ? 'Bewertung laeuft...' : 'Neu bewerten'}
</button>
)}
{onGoToExport && (
<button
onClick={onGoToExport}
className="px-4 py-2 text-sm bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
>
Zum Export
</button>
)}
</div>
{/* Audit Trail */}
{decision.auditTrail && decision.auditTrail.length > 0 && (
<div className="bg-white rounded-xl border border-gray-200 p-6">

View File

@@ -126,10 +126,63 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
})
}, [generateMarkdownSummary])
/** Simple markdown-to-HTML converter for print view */
const markdownToHtml = useCallback((md: string): string => {
let html = md
// Escape HTML entities
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
// Headers
html = html.replace(/^### (.+)$/gm, '<h3>$1</h3>')
html = html.replace(/^## (.+)$/gm, '<h2>$1</h2>')
html = html.replace(/^# (.+)$/gm, '<h1>$1</h1>')
// Bold
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
// Tables
const lines = html.split('\n')
let inTable = false
const result: string[] = []
for (const line of lines) {
if (line.trim().startsWith('|') && line.trim().endsWith('|')) {
if (line.includes('---')) continue // separator row
const cells = line.split('|').filter(c => c.trim() !== '')
if (!inTable) {
result.push('<table><thead><tr>')
cells.forEach(c => result.push(`<th>${c.trim()}</th>`))
result.push('</tr></thead><tbody>')
inTable = true
} else {
result.push('<tr>')
cells.forEach(c => result.push(`<td>${c.trim()}</td>`))
result.push('</tr>')
}
} else {
if (inTable) {
result.push('</tbody></table>')
inTable = false
}
// List items
if (line.trim().startsWith('- ')) {
result.push(`<li>${line.trim().slice(2)}</li>`)
} else if (/^\d+\.\s/.test(line.trim())) {
result.push(`<li>${line.trim().replace(/^\d+\.\s/, '')}</li>`)
} else if (line.trim() === '') {
result.push('<br/>')
} else {
result.push(`<p>${line}</p>`)
}
}
}
if (inTable) result.push('</tbody></table>')
return result.join('\n')
}, [])
const handlePrintView = useCallback(() => {
if (!decision) return
const markdown = generateMarkdownSummary()
const renderedHtml = markdownToHtml(markdown)
const htmlContent = `
<!DOCTYPE html>
<html>
@@ -152,6 +205,7 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
th { background-color: #f3f4f6; font-weight: 600; }
ul { list-style-type: disc; padding-left: 20px; }
li { margin: 8px 0; }
p { margin: 4px 0; }
@media print {
body { margin: 20px; }
h1, h2, h3 { page-break-after: avoid; }
@@ -160,7 +214,7 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
</style>
</head>
<body>
<pre style="white-space: pre-wrap; font-family: inherit;">${markdown}</pre>
${renderedHtml}
</body>
</html>
`
@@ -171,7 +225,7 @@ export function ScopeExportTab({ decision: decisionProp, answers: answersProp, s
printWindow.focus()
setTimeout(() => printWindow.print(), 250)
}
}, [decision, generateMarkdownSummary])
}, [decision, generateMarkdownSummary, markdownToHtml])
if (!decision) {
return (