refactor: Admin-Layout komplett entfernt — SDK als einziges Layout
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 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
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 32s
CI / test-python-backend-compliance (push) Successful in 31s
CI / test-python-document-crawler (push) Successful in 21s
CI / test-python-dsms-gateway (push) Successful in 19s
Kaputtes (admin) Layout geloescht (Role-Selection, 404-Sidebar, localhost-Dashboard). SDK-Flow nach /sdk/sdk-flow verschoben. Route-Gruppe (sdk) aufgeloest. Root-Seite redirected auf /sdk. ~25 ungenutzte Dateien/Verzeichnisse entfernt. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
289
admin-compliance/app/sdk/use-cases/[id]/page.tsx
Normal file
289
admin-compliance/app/sdk/use-cases/[id]/page.tsx
Normal file
@@ -0,0 +1,289 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import Link from 'next/link'
|
||||
import { AssessmentResultCard } from '@/components/sdk/use-case-assessment/AssessmentResultCard'
|
||||
|
||||
interface TriggeredRule {
|
||||
code: string
|
||||
category: string
|
||||
title: string
|
||||
description: string
|
||||
severity: string
|
||||
score_delta: number
|
||||
gdpr_ref: string
|
||||
}
|
||||
|
||||
interface RequiredControl {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
severity: string
|
||||
category: string
|
||||
gdpr_ref: string
|
||||
}
|
||||
|
||||
interface PatternRecommendation {
|
||||
pattern_id: string
|
||||
title: string
|
||||
description: string
|
||||
rationale: string
|
||||
priority: number
|
||||
}
|
||||
|
||||
interface ForbiddenPattern {
|
||||
pattern_id: string
|
||||
title: string
|
||||
description: string
|
||||
reason: string
|
||||
}
|
||||
|
||||
interface FullAssessment {
|
||||
id: string
|
||||
title: string
|
||||
tenant_id: string
|
||||
domain: string
|
||||
created_at: string
|
||||
intake?: {
|
||||
use_case_text?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
// Flat result fields
|
||||
feasibility: string
|
||||
risk_level: string
|
||||
risk_score: number
|
||||
complexity: string
|
||||
dsfa_recommended: boolean
|
||||
art22_risk: boolean
|
||||
training_allowed: string
|
||||
triggered_rules?: TriggeredRule[]
|
||||
required_controls?: RequiredControl[]
|
||||
recommended_architecture?: PatternRecommendation[]
|
||||
forbidden_patterns?: ForbiddenPattern[]
|
||||
explanation_text?: string
|
||||
explanation_model?: string
|
||||
explanation_generated_at?: string
|
||||
policy_version: string
|
||||
}
|
||||
|
||||
export default function AssessmentDetailPage() {
|
||||
const params = useParams()
|
||||
const router = useRouter()
|
||||
const assessmentId = params.id as string
|
||||
|
||||
const [assessment, setAssessment] = useState<FullAssessment | null>(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [explaining, setExplaining] = useState(false)
|
||||
const [explanationError, setExplanationError] = useState<string | null>(null)
|
||||
|
||||
async function loadAssessment() {
|
||||
const response = await fetch(`/api/sdk/v1/ucca/assessments/${assessmentId}`)
|
||||
if (!response.ok) throw new Error('Assessment nicht gefunden')
|
||||
return response.json()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!assessmentId) return
|
||||
loadAssessment()
|
||||
.then(data => setAssessment(data))
|
||||
.catch(() => {
|
||||
// Fallback: fetch from list
|
||||
fetch('/api/sdk/v1/ucca/assessments')
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const found = (data.assessments || []).find((a: FullAssessment) => a.id === assessmentId)
|
||||
if (found) {
|
||||
setAssessment(found)
|
||||
setError(null)
|
||||
} else {
|
||||
setError('Assessment nicht gefunden')
|
||||
}
|
||||
})
|
||||
.catch(() => setError('Fehler beim Laden'))
|
||||
})
|
||||
.finally(() => setLoading(false))
|
||||
}, [assessmentId])
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!confirm('Assessment wirklich loeschen?')) return
|
||||
try {
|
||||
await fetch(`/api/sdk/v1/ucca/assessments/${assessmentId}`, { method: 'DELETE' })
|
||||
router.push('/sdk/use-cases')
|
||||
} catch {
|
||||
// Ignore delete errors
|
||||
}
|
||||
}
|
||||
|
||||
const handleExplain = async () => {
|
||||
setExplaining(true)
|
||||
setExplanationError(null)
|
||||
try {
|
||||
const res = await fetch(`/api/sdk/v1/ucca/assessments/${assessmentId}/explain`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ language: 'de' }),
|
||||
})
|
||||
if (!res.ok) throw new Error('Fehler bei der Erklärung')
|
||||
// Reload assessment to get updated explanation_text
|
||||
const updated = await fetch(`/api/sdk/v1/ucca/assessments/${assessmentId}`)
|
||||
setAssessment(await updated.json())
|
||||
} catch (err) {
|
||||
setExplanationError(err instanceof Error ? err.message : 'Fehler')
|
||||
} finally {
|
||||
setExplaining(false)
|
||||
}
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-64">
|
||||
<div className="text-gray-500">Lade Assessment...</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error || !assessment) {
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-6 text-center">
|
||||
<h3 className="text-lg font-semibold text-red-800">Fehler</h3>
|
||||
<p className="text-red-600 mt-1">{error || 'Assessment nicht gefunden'}</p>
|
||||
</div>
|
||||
<Link href="/sdk/use-cases" className="text-purple-600 hover:text-purple-700">
|
||||
Zurueck zur Uebersicht
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Build result object for AssessmentResultCard from flat assessment fields
|
||||
const resultForCard = {
|
||||
feasibility: assessment.feasibility,
|
||||
risk_level: assessment.risk_level,
|
||||
risk_score: assessment.risk_score,
|
||||
complexity: assessment.complexity,
|
||||
dsfa_recommended: assessment.dsfa_recommended,
|
||||
art22_risk: assessment.art22_risk,
|
||||
training_allowed: assessment.training_allowed,
|
||||
// AssessmentResultCard expects rule_code; backend stores code — map here
|
||||
triggered_rules: assessment.triggered_rules?.map(r => ({
|
||||
rule_code: r.code,
|
||||
title: r.title,
|
||||
severity: r.severity,
|
||||
gdpr_ref: r.gdpr_ref,
|
||||
})),
|
||||
required_controls: assessment.required_controls?.map(c => ({
|
||||
id: c.id,
|
||||
title: c.title,
|
||||
description: c.description,
|
||||
effort: c.category,
|
||||
})),
|
||||
recommended_architecture: assessment.recommended_architecture?.map(p => ({
|
||||
id: p.pattern_id,
|
||||
title: p.title,
|
||||
description: p.description,
|
||||
benefit: p.rationale,
|
||||
})),
|
||||
summary: '',
|
||||
recommendation: '',
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Breadcrumb */}
|
||||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||||
<Link href="/sdk/use-cases" className="hover:text-purple-600">Use Cases</Link>
|
||||
<span>/</span>
|
||||
<span className="text-gray-900">{assessment.title || assessmentId.slice(0, 8)}</span>
|
||||
</div>
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex items-start justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-gray-900">{assessment.title || 'Assessment Detail'}</h1>
|
||||
<div className="flex items-center gap-4 mt-2 text-sm text-gray-500">
|
||||
<span>Domain: {assessment.domain}</span>
|
||||
<span>Erstellt: {new Date(assessment.created_at).toLocaleDateString('de-DE')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-wrap justify-end">
|
||||
<button
|
||||
onClick={handleExplain}
|
||||
disabled={explaining}
|
||||
className="px-4 py-2 text-sm bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors disabled:opacity-50"
|
||||
>
|
||||
{explaining ? 'Generiere...' : '✨ KI-Erklärung'}
|
||||
</button>
|
||||
<a
|
||||
href={`/api/sdk/v1/ucca/export/${assessmentId}?format=md`}
|
||||
download
|
||||
className="px-4 py-2 text-sm bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
↓ Markdown
|
||||
</a>
|
||||
<a
|
||||
href={`/api/sdk/v1/ucca/export/${assessmentId}?format=json`}
|
||||
download
|
||||
className="px-4 py-2 text-sm bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
↓ JSON
|
||||
</a>
|
||||
<Link
|
||||
href={`/sdk/use-cases/new?edit=${assessmentId}`}
|
||||
className="px-4 py-2 text-sm bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
|
||||
>
|
||||
Bearbeiten
|
||||
</Link>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="px-4 py-2 text-sm text-red-600 hover:bg-red-50 rounded-lg transition-colors"
|
||||
>
|
||||
Loeschen
|
||||
</button>
|
||||
<Link
|
||||
href="/sdk/use-cases"
|
||||
className="px-4 py-2 text-sm bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors"
|
||||
>
|
||||
Zurueck
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Explanation error */}
|
||||
{explanationError && (
|
||||
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700 text-sm">
|
||||
{explanationError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Use Case Text */}
|
||||
{assessment.intake?.use_case_text && (
|
||||
<div className="bg-gray-50 rounded-xl border border-gray-200 p-6">
|
||||
<h3 className="text-sm font-medium text-gray-500 mb-2">Beschreibung des Anwendungsfalls</h3>
|
||||
<p className="text-gray-800 whitespace-pre-wrap">{assessment.intake.use_case_text as string}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Result */}
|
||||
<AssessmentResultCard result={resultForCard as Parameters<typeof AssessmentResultCard>[0]['result']} />
|
||||
|
||||
{/* KI-Erklärung */}
|
||||
{assessment.explanation_text && (
|
||||
<div className="bg-purple-50 border border-purple-200 rounded-xl p-6">
|
||||
<div className="flex items-center gap-2 mb-3">
|
||||
<span className="text-lg">✨</span>
|
||||
<h3 className="text-sm font-semibold text-purple-800">KI-Erklärung</h3>
|
||||
{assessment.explanation_model && (
|
||||
<span className="text-xs text-purple-500 ml-auto">via {assessment.explanation_model}</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-purple-900 whitespace-pre-wrap text-sm leading-relaxed">
|
||||
{assessment.explanation_text}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user