'use client'
import React, { useState, useEffect, useRef } from 'react'
import { useParams } from 'next/navigation'
interface EvidenceFile {
id: string
filename: string
file_type: string
file_size: number
description: string
uploaded_at: string
uploaded_by: string
linked_mitigation_ids: string[]
linked_mitigation_names: string[]
linked_verification_ids: string[]
linked_verification_names: string[]
}
interface Linkable {
id: string
name: string
}
function formatFileSize(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
}
function FileIcon({ type }: { type: string }) {
const isPdf = type.includes('pdf')
const isImage = type.includes('image')
const isDoc = type.includes('word') || type.includes('document')
const isSpreadsheet = type.includes('sheet') || type.includes('excel')
const color = isPdf ? 'text-red-500' : isImage ? 'text-blue-500' : isDoc ? 'text-blue-600' : isSpreadsheet ? 'text-green-600' : 'text-gray-500'
return (
)
}
function LinkBadges({ names, type }: { names: string[]; type: 'mitigation' | 'verification' }) {
if (names.length === 0) return null
const color = type === 'mitigation' ? 'bg-blue-50 text-blue-700' : 'bg-green-50 text-green-700'
return (
{names.map((name, i) => (
{name}
))}
)
}
function LinkModal({
evidence,
mitigations,
verifications,
onSave,
onClose,
}: {
evidence: EvidenceFile
mitigations: Linkable[]
verifications: Linkable[]
onSave: (evidenceId: string, mitIds: string[], verIds: string[]) => void
onClose: () => void
}) {
const [selectedMitigations, setSelectedMitigations] = useState(evidence.linked_mitigation_ids)
const [selectedVerifications, setSelectedVerifications] = useState(evidence.linked_verification_ids)
function toggle(list: string[], setList: (v: string[]) => void, id: string) {
setList(list.includes(id) ? list.filter((x) => x !== id) : [...list, id])
}
return (
Nachweis verknuepfen: {evidence.filename}
{mitigations.length > 0 && (
)}
{verifications.length > 0 && (
)}
)
}
export default function EvidencePage() {
const params = useParams()
const projectId = params.projectId as string
const [files, setFiles] = useState([])
const [mitigations, setMitigations] = useState([])
const [verifications, setVerifications] = useState([])
const [loading, setLoading] = useState(true)
const [uploading, setUploading] = useState(false)
const [dragging, setDragging] = useState(false)
const [linkingFile, setLinkingFile] = useState(null)
const fileInputRef = useRef(null)
useEffect(() => {
fetchData()
}, [projectId])
async function fetchData() {
try {
const [evRes, mitRes, verRes] = await Promise.all([
fetch(`/api/sdk/v1/iace/projects/${projectId}/evidence`),
fetch(`/api/sdk/v1/iace/projects/${projectId}/mitigations`),
fetch(`/api/sdk/v1/iace/projects/${projectId}/verifications`),
])
if (evRes.ok) {
const json = await evRes.json()
setFiles(json.evidence || json || [])
}
if (mitRes.ok) {
const json = await mitRes.json()
setMitigations((json.mitigations || json || []).map((m: { id: string; title: string }) => ({ id: m.id, name: m.title })))
}
if (verRes.ok) {
const json = await verRes.json()
setVerifications((json.verifications || json || []).map((v: { id: string; title: string }) => ({ id: v.id, name: v.title })))
}
} catch (err) {
console.error('Failed to fetch data:', err)
} finally {
setLoading(false)
}
}
async function handleUpload(fileList: FileList) {
setUploading(true)
try {
for (let i = 0; i < fileList.length; i++) {
const file = fileList[i]
const formData = new FormData()
formData.append('file', file)
formData.append('description', '')
await fetch(`/api/sdk/v1/iace/projects/${projectId}/evidence`, {
method: 'POST',
body: formData,
})
}
await fetchData()
} catch (err) {
console.error('Failed to upload:', err)
} finally {
setUploading(false)
}
}
function handleDrop(e: React.DragEvent) {
e.preventDefault()
setDragging(false)
if (e.dataTransfer.files.length > 0) {
handleUpload(e.dataTransfer.files)
}
}
function handleDragOver(e: React.DragEvent) {
e.preventDefault()
setDragging(true)
}
function handleDragLeave() {
setDragging(false)
}
async function handleLink(evidenceId: string, mitIds: string[], verIds: string[]) {
try {
await fetch(`/api/sdk/v1/iace/projects/${projectId}/evidence/${evidenceId}/link`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
linked_mitigation_ids: mitIds,
linked_verification_ids: verIds,
}),
})
setLinkingFile(null)
await fetchData()
} catch (err) {
console.error('Failed to link evidence:', err)
}
}
async function handleDelete(id: string) {
if (!confirm('Nachweis wirklich loeschen?')) return
try {
await fetch(`/api/sdk/v1/iace/projects/${projectId}/evidence/${id}`, { method: 'DELETE' })
await fetchData()
} catch (err) {
console.error('Failed to delete evidence:', err)
}
}
if (loading) {
return (
)
}
return (
{/* Header */}
Nachweise
Laden Sie Nachweisdokumente hoch und verknuepfen Sie diese mit Massnahmen und Verifikationen.
{/* Upload Area */}
fileInputRef.current?.click()}
className={`border-2 border-dashed rounded-xl p-8 text-center cursor-pointer transition-colors ${
dragging
? 'border-purple-400 bg-purple-50 dark:bg-purple-900/20'
: 'border-gray-300 hover:border-purple-300 hover:bg-gray-50 dark:hover:bg-gray-800'
}`}
>
e.target.files && handleUpload(e.target.files)}
/>
{uploading ? (
) : (
<>
Dateien auswaehlen oder hierher ziehen
PDF, Word, Excel, Bilder und andere Dokumente
>
)}
{/* Link Modal */}
{linkingFile && (
setLinkingFile(null)}
/>
)}
{/* File List */}
{files.length > 0 ? (
Hochgeladene Nachweise ({files.length})
{files.map((file) => (
{file.filename}
{formatFileSize(file.file_size)}
{file.description && (
{file.description}
)}
Hochgeladen am {new Date(file.uploaded_at).toLocaleDateString('de-DE')}
))}
) : (
Keine Nachweise vorhanden
Laden Sie Testberichte, Zertifikate, Analyseergebnisse und andere Nachweisdokumente
hoch und verknuepfen Sie diese mit den entsprechenden Massnahmen.
)}
)
}