Files
breakpilot-compliance/admin-compliance/app/sdk/iace/[projectId]/verification/page.tsx
Benjamin Admin 9c1355c05f
All checks were successful
CI/CD / go-lint (push) Has been skipped
CI/CD / python-lint (push) Has been skipped
CI/CD / nodejs-lint (push) Has been skipped
CI/CD / test-go-ai-compliance (push) Successful in 34s
CI/CD / test-python-backend-compliance (push) Successful in 33s
CI/CD / test-python-document-crawler (push) Successful in 23s
CI/CD / test-python-dsms-gateway (push) Successful in 19s
CI/CD / validate-canonical-controls (push) Successful in 13s
CI/CD / Deploy (push) Successful in 2s
feat(iace): Phase 5+6 — frontend integration, RAG library search, comprehensive tests
Phase 5 — Frontend Integration:
- components/page.tsx: ComponentLibraryModal with 120 components + 20 energy sources
- hazards/page.tsx: AutoSuggestPanel with 3-column pattern matching review
- mitigations/page.tsx: SuggestMeasuresModal per hazard with 3-level grouping
- verification/page.tsx: SuggestEvidenceModal per mitigation with evidence types

Phase 6 — RAG Library Search:
- Added bp_iace_libraries to AllowedCollections whitelist in rag_handlers.go
- SearchLibrary endpoint: POST /iace/library-search (semantic search across libraries)
- EnrichTechFileSection endpoint: POST /projects/:id/tech-file/:section/enrich
- Created ingest-iace-libraries.sh ingestion script for Qdrant collection

Tests (123 passing):
- tag_taxonomy_test.go: 8 tests for taxonomy entries, domains, essential tags
- controls_library_test.go: 7 tests for measures, reduction types, subtypes
- integration_test.go: 7 integration tests for full match flow and library consistency
- Extended tag_resolver_test.go: 9 new tests for FindByTags and cross-category resolution

Documentation:
- Updated iace.md with Hazard-Matching-Engine, RAG enrichment, and new DB tables

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 10:22:49 +01:00

674 lines
29 KiB
TypeScript

'use client'
import React, { useState, useEffect } from 'react'
import { useParams } from 'next/navigation'
interface VerificationItem {
id: string
title: string
description: string
method: string
status: 'pending' | 'in_progress' | 'completed' | 'failed'
result: string | null
linked_hazard_id: string | null
linked_hazard_name: string | null
linked_mitigation_id: string | null
linked_mitigation_name: string | null
completed_at: string | null
completed_by: string | null
created_at: string
}
interface SuggestedEvidence {
id: string
name: string
description: string
method: string
tags?: string[]
}
const VERIFICATION_METHODS = [
{ value: 'design_review', label: 'Design-Review', description: 'Systematische Pruefung der Konstruktionsunterlagen' },
{ value: 'calculation', label: 'Berechnung', description: 'Rechnerischer Nachweis (FEM, Festigkeit, Thermik)' },
{ value: 'test_report', label: 'Pruefbericht', description: 'Dokumentierter Test mit Messprotokoll' },
{ value: 'validation', label: 'Validierung', description: 'Nachweis der Eignung unter realen Betriebsbedingungen' },
{ value: 'electrical_test', label: 'Elektrische Pruefung', description: 'Isolationsmessung, Schutzleiter, Spannungsfestigkeit' },
{ value: 'software_test', label: 'Software-Test', description: 'Unit-, Integrations- oder Systemtest der Steuerungssoftware' },
{ value: 'penetration_test', label: 'Penetrationstest', description: 'Security-Test der Netzwerk- und Steuerungskomponenten' },
{ value: 'acceptance_protocol', label: 'Abnahmeprotokoll', description: 'Formelle Abnahme mit Checkliste und Unterschrift' },
{ value: 'user_test', label: 'Anwendertest', description: 'Pruefung durch Bediener unter realen Einsatzbedingungen' },
{ value: 'documentation_release', label: 'Dokumentenfreigabe', description: 'Formelle Freigabe der technischen Dokumentation' },
]
const STATUS_CONFIG: Record<string, { label: string; color: string }> = {
pending: { label: 'Ausstehend', color: 'bg-gray-100 text-gray-700' },
in_progress: { label: 'In Bearbeitung', color: 'bg-blue-100 text-blue-700' },
completed: { label: 'Abgeschlossen', color: 'bg-green-100 text-green-700' },
failed: { label: 'Fehlgeschlagen', color: 'bg-red-100 text-red-700' },
}
function StatusBadge({ status }: { status: string }) {
const config = STATUS_CONFIG[status] || STATUS_CONFIG.pending
return (
<span className={`inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium ${config.color}`}>
{config.label}
</span>
)
}
interface VerificationFormData {
title: string
description: string
method: string
linked_hazard_id: string
linked_mitigation_id: string
}
function VerificationForm({
onSubmit,
onCancel,
hazards,
mitigations,
}: {
onSubmit: (data: VerificationFormData) => void
onCancel: () => void
hazards: { id: string; name: string }[]
mitigations: { id: string; title: string }[]
}) {
const [formData, setFormData] = useState<VerificationFormData>({
title: '',
description: '',
method: 'test',
linked_hazard_id: '',
linked_mitigation_id: '',
})
return (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">Neues Verifikationselement</h3>
<div className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Titel *</label>
<input
type="text"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
placeholder="z.B. Funktionstest Lichtvorhang"
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Methode</label>
<select
value={formData.method}
onChange={(e) => setFormData({ ...formData, method: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
>
{VERIFICATION_METHODS.map((m) => (
<option key={m.value} value={m.value}>{m.label}</option>
))}
</select>
</div>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Beschreibung</label>
<textarea
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={2}
placeholder="Beschreiben Sie den Verifikationsschritt..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Verknuepfte Gefaehrdung</label>
<select
value={formData.linked_hazard_id}
onChange={(e) => setFormData({ ...formData, linked_hazard_id: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
>
<option value="">-- Keine --</option>
{hazards.map((h) => (
<option key={h.id} value={h.id}>{h.name}</option>
))}
</select>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Verknuepfte Massnahme</label>
<select
value={formData.linked_mitigation_id}
onChange={(e) => setFormData({ ...formData, linked_mitigation_id: e.target.value })}
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
>
<option value="">-- Keine --</option>
{mitigations.map((m) => (
<option key={m.id} value={m.id}>{m.title}</option>
))}
</select>
</div>
</div>
</div>
<div className="mt-4 flex items-center gap-3">
<button
onClick={() => onSubmit(formData)}
disabled={!formData.title}
className={`px-6 py-2 rounded-lg font-medium transition-colors ${
formData.title
? 'bg-purple-600 text-white hover:bg-purple-700'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}`}
>
Hinzufuegen
</button>
<button onClick={onCancel} className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
Abbrechen
</button>
</div>
</div>
)
}
function CompleteModal({
item,
onSubmit,
onClose,
}: {
item: VerificationItem
onSubmit: (id: string, result: string, passed: boolean) => void
onClose: () => void
}) {
const [result, setResult] = useState('')
const [passed, setPassed] = useState(true)
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-xl w-full max-w-lg p-6">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-4">
Verifikation abschliessen: {item.title}
</h3>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Ergebnis</label>
<textarea
value={result}
onChange={(e) => setResult(e.target.value)}
rows={3}
placeholder="Beschreiben Sie das Ergebnis der Verifikation..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent dark:bg-gray-700 dark:border-gray-600 dark:text-white"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">Bewertung</label>
<div className="flex gap-3">
<button
onClick={() => setPassed(true)}
className={`flex-1 py-2 rounded-lg border text-sm font-medium transition-colors ${
passed
? 'border-green-400 bg-green-50 text-green-700'
: 'border-gray-200 text-gray-500 hover:bg-gray-50'
}`}
>
Bestanden
</button>
<button
onClick={() => setPassed(false)}
className={`flex-1 py-2 rounded-lg border text-sm font-medium transition-colors ${
!passed
? 'border-red-400 bg-red-50 text-red-700'
: 'border-gray-200 text-gray-500 hover:bg-gray-50'
}`}
>
Nicht bestanden
</button>
</div>
</div>
</div>
<div className="mt-6 flex items-center gap-3">
<button
onClick={() => onSubmit(item.id, result, passed)}
disabled={!result}
className={`px-6 py-2 rounded-lg font-medium transition-colors ${
result
? 'bg-purple-600 text-white hover:bg-purple-700'
: 'bg-gray-200 text-gray-400 cursor-not-allowed'
}`}
>
Abschliessen
</button>
<button onClick={onClose} className="px-4 py-2 text-gray-600 hover:bg-gray-100 rounded-lg transition-colors">
Abbrechen
</button>
</div>
</div>
</div>
)
}
// ============================================================================
// Suggest Evidence Modal (Phase 5)
// ============================================================================
function SuggestEvidenceModal({
mitigations,
projectId,
onAddEvidence,
onClose,
}: {
mitigations: { id: string; title: string }[]
projectId: string
onAddEvidence: (title: string, description: string, method: string, mitigationId: string) => void
onClose: () => void
}) {
const [selectedMitigation, setSelectedMitigation] = useState<string>('')
const [suggested, setSuggested] = useState<SuggestedEvidence[]>([])
const [loadingSuggestions, setLoadingSuggestions] = useState(false)
async function handleSelectMitigation(mitigationId: string) {
setSelectedMitigation(mitigationId)
setSuggested([])
if (!mitigationId) return
setLoadingSuggestions(true)
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations/${mitigationId}/suggest-evidence`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
})
if (res.ok) {
const json = await res.json()
setSuggested(json.suggested_evidence || [])
}
} catch (err) {
console.error('Failed to suggest evidence:', err)
} finally {
setLoadingSuggestions(false)
}
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50 p-4">
<div className="bg-white dark:bg-gray-800 rounded-xl w-full max-w-3xl max-h-[85vh] flex flex-col">
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Nachweise vorschlagen</h3>
<button onClick={onClose} className="p-1 text-gray-400 hover:text-gray-600 rounded">
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<p className="text-sm text-gray-500 mb-3">
Waehlen Sie eine Massnahme, um passende Nachweismethoden vorgeschlagen zu bekommen.
</p>
<div className="flex flex-wrap gap-2">
{mitigations.map(m => (
<button
key={m.id}
onClick={() => handleSelectMitigation(m.id)}
className={`px-3 py-1.5 text-xs rounded-lg border transition-colors ${
selectedMitigation === m.id
? 'border-purple-400 bg-purple-50 text-purple-700 font-medium'
: 'border-gray-200 bg-white text-gray-700 hover:border-purple-300'
}`}
>
{m.title}
</button>
))}
</div>
</div>
<div className="flex-1 overflow-auto p-6">
{loadingSuggestions ? (
<div className="flex items-center justify-center py-12">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
</div>
) : suggested.length > 0 ? (
<div className="space-y-3">
{suggested.map(ev => (
<div key={ev.id} className="border border-gray-200 rounded-lg p-4 hover:bg-gray-50 transition-colors">
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<span className="text-xs font-mono text-gray-400">{ev.id}</span>
{ev.method && (
<span className="text-xs px-1.5 py-0.5 rounded bg-blue-50 text-blue-600">
{VERIFICATION_METHODS.find(m => m.value === ev.method)?.label || ev.method}
</span>
)}
</div>
<div className="text-sm font-medium text-gray-900 dark:text-white">{ev.name}</div>
<div className="text-xs text-gray-500 mt-0.5">{ev.description}</div>
</div>
<button
onClick={() => onAddEvidence(ev.name, ev.description, ev.method || 'test_report', selectedMitigation)}
className="ml-3 px-3 py-1.5 text-xs bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors flex-shrink-0"
>
Uebernehmen
</button>
</div>
</div>
))}
</div>
) : selectedMitigation ? (
<div className="text-center py-12 text-gray-500">
Keine Vorschlaege fuer diese Massnahme gefunden.
</div>
) : (
<div className="text-center py-12 text-gray-500">
Waehlen Sie eine Massnahme aus, um Nachweise vorgeschlagen zu bekommen.
</div>
)}
</div>
</div>
</div>
)
}
// ============================================================================
// Main Page
// ============================================================================
export default function VerificationPage() {
const params = useParams()
const projectId = params.projectId as string
const [items, setItems] = useState<VerificationItem[]>([])
const [hazards, setHazards] = useState<{ id: string; name: string }[]>([])
const [mitigations, setMitigations] = useState<{ id: string; title: string }[]>([])
const [loading, setLoading] = useState(true)
const [showForm, setShowForm] = useState(false)
const [completingItem, setCompletingItem] = useState<VerificationItem | null>(null)
// Phase 5: Suggest evidence
const [showSuggest, setShowSuggest] = useState(false)
useEffect(() => {
fetchData()
}, [projectId])
async function fetchData() {
try {
const [verRes, hazRes, mitRes] = await Promise.all([
fetch(`/api/sdk/v1/iace/projects/${projectId}/verifications`),
fetch(`/api/sdk/v1/iace/projects/${projectId}/hazards`),
fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations`),
])
if (verRes.ok) {
const json = await verRes.json()
setItems(json.verifications || json || [])
}
if (hazRes.ok) {
const json = await hazRes.json()
setHazards((json.hazards || json || []).map((h: { id: string; name: string }) => ({ id: h.id, name: h.name })))
}
if (mitRes.ok) {
const json = await mitRes.json()
setMitigations((json.mitigations || json || []).map((m: { id: string; title: string }) => ({ id: m.id, title: m.title })))
}
} catch (err) {
console.error('Failed to fetch data:', err)
} finally {
setLoading(false)
}
}
async function handleSubmit(data: VerificationFormData) {
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/verifications`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
if (res.ok) {
setShowForm(false)
await fetchData()
}
} catch (err) {
console.error('Failed to add verification:', err)
}
}
async function handleAddSuggestedEvidence(title: string, description: string, method: string, mitigationId: string) {
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/verifications`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title,
description,
method,
linked_mitigation_id: mitigationId,
}),
})
if (res.ok) {
await fetchData()
}
} catch (err) {
console.error('Failed to add suggested evidence:', err)
}
}
async function handleComplete(id: string, result: string, passed: boolean) {
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/verifications/${id}/complete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ result, passed }),
})
if (res.ok) {
setCompletingItem(null)
await fetchData()
}
} catch (err) {
console.error('Failed to complete verification:', err)
}
}
async function handleDelete(id: string) {
if (!confirm('Verifikation wirklich loeschen?')) return
try {
const res = await fetch(`/api/sdk/v1/iace/projects/${projectId}/verifications/${id}`, { method: 'DELETE' })
if (res.ok) {
await fetchData()
}
} catch (err) {
console.error('Failed to delete verification:', err)
}
}
const completed = items.filter((i) => i.status === 'completed').length
const failed = items.filter((i) => i.status === 'failed').length
const pending = items.filter((i) => i.status === 'pending' || i.status === 'in_progress').length
if (loading) {
return (
<div className="flex items-center justify-center h-64">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-purple-600" />
</div>
)
}
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Verifikationsplan</h1>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Nachweisfuehrung fuer alle Schutzmassnahmen und Sicherheitsanforderungen.
</p>
</div>
<div className="flex items-center gap-2">
{mitigations.length > 0 && (
<button
onClick={() => setShowSuggest(true)}
className="flex items-center gap-2 px-3 py-2 border border-green-300 text-green-700 rounded-lg hover:bg-green-50 transition-colors text-sm"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
</svg>
Nachweise vorschlagen
</button>
)}
<button
onClick={() => setShowForm(true)}
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>
Verifikation hinzufuegen
</button>
</div>
</div>
{/* Stats */}
{items.length > 0 && (
<div className="grid grid-cols-4 gap-3">
<div className="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 text-center">
<div className="text-2xl font-bold text-gray-900 dark:text-white">{items.length}</div>
<div className="text-xs text-gray-500">Gesamt</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-green-200 p-4 text-center">
<div className="text-2xl font-bold text-green-600">{completed}</div>
<div className="text-xs text-green-600">Abgeschlossen</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-red-200 p-4 text-center">
<div className="text-2xl font-bold text-red-600">{failed}</div>
<div className="text-xs text-red-600">Fehlgeschlagen</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg border border-yellow-200 p-4 text-center">
<div className="text-2xl font-bold text-yellow-600">{pending}</div>
<div className="text-xs text-yellow-600">Ausstehend</div>
</div>
</div>
)}
{/* Form */}
{showForm && (
<VerificationForm
onSubmit={handleSubmit}
onCancel={() => setShowForm(false)}
hazards={hazards}
mitigations={mitigations}
/>
)}
{/* Complete Modal */}
{completingItem && (
<CompleteModal
item={completingItem}
onSubmit={handleComplete}
onClose={() => setCompletingItem(null)}
/>
)}
{/* Suggest Evidence Modal (Phase 5) */}
{showSuggest && (
<SuggestEvidenceModal
mitigations={mitigations}
projectId={projectId}
onAddEvidence={handleAddSuggestedEvidence}
onClose={() => setShowSuggest(false)}
/>
)}
{/* Table */}
{items.length > 0 ? (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 overflow-hidden">
<div className="overflow-x-auto">
<table className="w-full">
<thead>
<tr className="bg-gray-50 dark:bg-gray-750 border-b border-gray-200 dark:border-gray-700">
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Titel</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Methode</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Gefaehrdung</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Massnahme</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Status</th>
<th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Ergebnis</th>
<th className="px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">Aktionen</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-700">
{items.map((item) => (
<tr key={item.id} className="hover:bg-gray-50 dark:hover:bg-gray-750 transition-colors">
<td className="px-4 py-3">
<div className="text-sm font-medium text-gray-900 dark:text-white">{item.title}</div>
{item.description && (
<div className="text-xs text-gray-500 truncate max-w-[200px]">{item.description}</div>
)}
</td>
<td className="px-4 py-3">
<span className="text-xs px-2 py-0.5 rounded bg-gray-100 text-gray-700 dark:bg-gray-700 dark:text-gray-300">
{VERIFICATION_METHODS.find((m) => m.value === item.method)?.label || item.method}
</span>
</td>
<td className="px-4 py-3 text-sm text-gray-600">{item.linked_hazard_name || '--'}</td>
<td className="px-4 py-3 text-sm text-gray-600">{item.linked_mitigation_name || '--'}</td>
<td className="px-4 py-3"><StatusBadge status={item.status} /></td>
<td className="px-4 py-3 text-sm text-gray-600 max-w-[150px] truncate">{item.result || '--'}</td>
<td className="px-4 py-3 text-right">
<div className="flex items-center justify-end gap-1">
{item.status !== 'completed' && item.status !== 'failed' && (
<button
onClick={() => setCompletingItem(item)}
className="text-xs px-2.5 py-1 bg-green-50 text-green-700 border border-green-200 rounded-lg hover:bg-green-100 transition-colors"
>
Abschliessen
</button>
)}
<button
onClick={() => handleDelete(item.id)}
className="p-1 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<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>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
) : (
!showForm && (
<div className="bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-12 text-center">
<div className="w-16 h-16 mx-auto bg-purple-100 dark:bg-purple-900/30 rounded-full flex items-center justify-center mb-4">
<svg className="w-8 h-8 text-purple-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">Kein Verifikationsplan vorhanden</h3>
<p className="mt-2 text-gray-500 max-w-md mx-auto">
Definieren Sie Verifikationsschritte fuer Ihre Schutzmassnahmen.
Jede Massnahme sollte durch mindestens eine Verifikation abgedeckt sein.
</p>
<div className="mt-6 flex items-center justify-center gap-3">
{mitigations.length > 0 && (
<button
onClick={() => setShowSuggest(true)}
className="px-6 py-3 border border-green-300 text-green-700 rounded-lg hover:bg-green-50 transition-colors"
>
Nachweise vorschlagen
</button>
)}
<button
onClick={() => setShowForm(true)}
className="px-6 py-3 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors"
>
Erste Verifikation anlegen
</button>
</div>
</div>
)
)}
</div>
)
}