Files
breakpilot-compliance/admin-compliance/app/sdk/use-cases/page.tsx
Benjamin Admin 215b95adfa
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
refactor: Admin-Layout komplett entfernt — SDK als einziges Layout
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>
2026-03-04 11:43:00 +01:00

261 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import React, { useState, useEffect } from 'react'
import Link from 'next/link'
import { RiskScoreGauge } from '@/components/sdk/use-case-assessment/RiskScoreGauge'
interface Assessment {
id: string
title: string
feasibility: string
risk_level: string
risk_score: number
domain: string
created_at: string
}
const FEASIBILITY_STYLES: Record<string, { bg: string; text: string; label: string }> = {
YES: { bg: 'bg-green-100', text: 'text-green-700', label: 'Machbar' },
CONDITIONAL: { bg: 'bg-yellow-100', text: 'text-yellow-700', label: 'Bedingt' },
NO: { bg: 'bg-red-100', text: 'text-red-700', label: 'Nein' },
}
export default function UseCasesPage() {
const [assessments, setAssessments] = useState<Assessment[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [filterFeasibility, setFilterFeasibility] = useState<string>('all')
const [filterRisk, setFilterRisk] = useState<string>('all')
const [searchQuery, setSearchQuery] = useState('')
const [page, setPage] = useState(0)
const [totalCount, setTotalCount] = useState(0)
const PAGE_SIZE = 20
useEffect(() => {
fetchAssessments()
}, [page, searchQuery])
async function fetchAssessments() {
try {
setLoading(true)
const params = new URLSearchParams({
limit: String(PAGE_SIZE),
offset: String(page * PAGE_SIZE),
})
if (searchQuery) params.set('search', searchQuery)
const response = await fetch(`/api/sdk/v1/ucca/assessments?${params}`)
if (!response.ok) {
throw new Error('Fehler beim Laden der Assessments')
}
const data = await response.json()
setAssessments(data.assessments || [])
setTotalCount(data.total || data.assessments?.length || 0)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unbekannter Fehler')
} finally {
setLoading(false)
}
}
const filtered = assessments.filter(a => {
if (filterFeasibility !== 'all' && a.feasibility !== filterFeasibility) return false
if (filterRisk !== 'all' && a.risk_level !== filterRisk) return false
return true
})
const stats = {
total: assessments.length,
feasible: assessments.filter(a => a.feasibility === 'YES').length,
conditional: assessments.filter(a => a.feasibility === 'CONDITIONAL').length,
rejected: assessments.filter(a => a.feasibility === 'NO').length,
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900">Use Case Assessment</h1>
<p className="mt-1 text-gray-500">
KI-Anwendungsfaelle erfassen und auf Compliance pruefen
</p>
</div>
<Link
href="/sdk/use-cases/new"
className="flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Neues Assessment
</Link>
</div>
{/* Stats */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-white rounded-xl border border-gray-200 p-6">
<div className="text-sm text-gray-500">Gesamt</div>
<div className="text-3xl font-bold text-gray-900">{stats.total}</div>
</div>
<div className="bg-white rounded-xl border border-green-200 p-6">
<div className="text-sm text-green-600">Machbar</div>
<div className="text-3xl font-bold text-green-600">{stats.feasible}</div>
</div>
<div className="bg-white rounded-xl border border-yellow-200 p-6">
<div className="text-sm text-yellow-600">Bedingt</div>
<div className="text-3xl font-bold text-yellow-600">{stats.conditional}</div>
</div>
<div className="bg-white rounded-xl border border-red-200 p-6">
<div className="text-sm text-red-600">Abgelehnt</div>
<div className="text-3xl font-bold text-red-600">{stats.rejected}</div>
</div>
</div>
{/* Search */}
<div className="relative">
<input
type="text"
placeholder="Assessments durchsuchen..."
value={searchQuery}
onChange={e => { setSearchQuery(e.target.value); setPage(0) }}
className="w-full px-4 py-2 pl-10 bg-white border border-gray-200 rounded-lg text-sm focus:ring-2 focus:ring-purple-500 focus:border-purple-500"
/>
<svg className="absolute left-3 top-2.5 w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</div>
{/* Filters */}
<div className="flex items-center gap-4 flex-wrap">
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Machbarkeit:</span>
{['all', 'YES', 'CONDITIONAL', 'NO'].map(f => (
<button
key={f}
onClick={() => setFilterFeasibility(f)}
className={`px-3 py-1 text-sm rounded-full transition-colors ${
filterFeasibility === f
? 'bg-purple-600 text-white'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
{f === 'all' ? 'Alle' : FEASIBILITY_STYLES[f]?.label || f}
</button>
))}
</div>
<div className="flex items-center gap-2">
<span className="text-sm text-gray-500">Risiko:</span>
{['all', 'MINIMAL', 'LOW', 'MEDIUM', 'HIGH', 'UNACCEPTABLE'].map(f => (
<button
key={f}
onClick={() => setFilterRisk(f)}
className={`px-3 py-1 text-sm rounded-full transition-colors ${
filterRisk === f
? 'bg-purple-600 text-white'
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
}`}
>
{f === 'all' ? 'Alle' : f}
</button>
))}
</div>
</div>
{/* Error */}
{error && (
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">
{error}
<button onClick={fetchAssessments} className="ml-3 underline">Erneut versuchen</button>
</div>
)}
{/* Loading */}
{loading && (
<div className="text-center py-12 text-gray-500">Lade Assessments...</div>
)}
{/* Assessment List */}
{!loading && filtered.length > 0 && (
<div className="space-y-4">
{filtered.map(assessment => {
const feasibility = FEASIBILITY_STYLES[assessment.feasibility] || FEASIBILITY_STYLES.YES
return (
<Link
key={assessment.id}
href={`/sdk/use-cases/${assessment.id}`}
className="block bg-white rounded-xl border border-gray-200 p-6 hover:border-purple-300 hover:shadow-md transition-all"
>
<div className="flex items-center gap-6">
<RiskScoreGauge score={assessment.risk_score} riskLevel={assessment.risk_level} size="sm" />
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-lg font-semibold text-gray-900">{assessment.title || 'Unbenanntes Assessment'}</h3>
<span className={`px-2 py-0.5 text-xs rounded-full ${feasibility.bg} ${feasibility.text}`}>
{feasibility.label}
</span>
</div>
<div className="flex items-center gap-4 text-sm text-gray-500">
<span>{assessment.domain}</span>
<span>{new Date(assessment.created_at).toLocaleDateString('de-DE')}</span>
</div>
</div>
<svg className="w-5 h-5 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>
</Link>
)
})}
</div>
)}
{/* Pagination */}
{!loading && totalCount > PAGE_SIZE && (
<div className="flex items-center justify-between">
<p className="text-sm text-gray-500">
{page * PAGE_SIZE + 1}{Math.min((page + 1) * PAGE_SIZE, totalCount)} von {totalCount}
</p>
<div className="flex gap-2">
<button
onClick={() => setPage(p => Math.max(0, p - 1))}
disabled={page === 0}
className="px-3 py-1 text-sm bg-gray-100 rounded-lg hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
Zurueck
</button>
<button
onClick={() => setPage(p => p + 1)}
disabled={(page + 1) * PAGE_SIZE >= totalCount}
className="px-3 py-1 text-sm bg-gray-100 rounded-lg hover:bg-gray-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
Weiter
</button>
</div>
</div>
)}
{/* Empty State */}
{!loading && filtered.length === 0 && !error && (
<div className="bg-white rounded-xl border border-gray-200 p-12 text-center">
<div className="w-16 h-16 mx-auto bg-purple-100 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-purple-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900">Noch keine Assessments</h3>
<p className="mt-2 text-gray-500 mb-4">
Erstellen Sie Ihr erstes Use Case Assessment, um die Compliance-Bewertung zu starten.
</p>
<Link
href="/sdk/use-cases/new"
className="inline-flex items-center gap-2 px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700"
>
Erstes Assessment erstellen
</Link>
</div>
)}
</div>
)
}