feat(vendor-assessment): Pruefprotokoll + Frontend + Sidebar
Build + Deploy / build-admin-compliance (push) Successful in 2m16s
Build + Deploy / build-backend-compliance (push) Successful in 3m27s
Build + Deploy / build-ai-sdk (push) Successful in 58s
Build + Deploy / build-developer-portal (push) Successful in 1m13s
Build + Deploy / build-tts (push) Successful in 1m43s
Build + Deploy / build-document-crawler (push) Successful in 45s
Build + Deploy / build-dsms-gateway (push) Successful in 30s
Build + Deploy / build-dsms-node (push) Successful in 19s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m35s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 43s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 26s
CI / test-python-dsms-gateway (push) Successful in 21s
CI / validate-canonical-controls (push) Successful in 14s
Build + Deploy / trigger-orca (push) Successful in 3m33s
Build + Deploy / build-admin-compliance (push) Successful in 2m16s
Build + Deploy / build-backend-compliance (push) Successful in 3m27s
Build + Deploy / build-ai-sdk (push) Successful in 58s
Build + Deploy / build-developer-portal (push) Successful in 1m13s
Build + Deploy / build-tts (push) Successful in 1m43s
Build + Deploy / build-document-crawler (push) Successful in 45s
Build + Deploy / build-dsms-gateway (push) Successful in 30s
Build + Deploy / build-dsms-node (push) Successful in 19s
CI / branch-name (push) Has been skipped
CI / guardrail-integrity (push) Has been skipped
CI / loc-budget (push) Failing after 17s
CI / secret-scan (push) Has been skipped
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / nodejs-build (push) Successful in 2m35s
CI / dep-audit (push) Has been skipped
CI / sbom-scan (push) Has been skipped
CI / test-go (push) Successful in 43s
CI / test-python-backend (push) Successful in 37s
CI / test-python-document-crawler (push) Successful in 26s
CI / test-python-dsms-gateway (push) Successful in 21s
CI / validate-canonical-controls (push) Successful in 14s
Build + Deploy / trigger-orca (push) Successful in 3m33s
Phase 4-5: Professional Pruefprotokoll report builder with styled HTML output (Kopfdaten, Kategorie-Scores, L1/L2 Check-Hierarchie, Findings, Freigabe-Block). Frontend at /sdk/vendor-assessment with 3-step flow: DocumentUploader → AssessmentProgress → PruefprotokollView. Sidebar: "Use-Case Audits" → "Vertragspruefung" renamed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
'use client'
|
||||
|
||||
import React, { useState } from 'react'
|
||||
|
||||
interface DocumentEntry {
|
||||
doc_type: string
|
||||
label: string
|
||||
url: string
|
||||
}
|
||||
|
||||
interface Props {
|
||||
onStart: (vendorName: string, documents: DocumentEntry[]) => void
|
||||
}
|
||||
|
||||
const DOC_TYPES = [
|
||||
{ value: 'auto', label: 'Automatisch erkennen' },
|
||||
{ value: 'avv', label: 'AVV / Auftragsverarbeitungsvertrag' },
|
||||
{ value: 'scc', label: 'SCC / Standardvertragsklauseln' },
|
||||
{ value: 'tom_annex', label: 'TOM-Anlage (Art. 32)' },
|
||||
{ value: 'sub_processor_list', label: 'Sub-Processor-Liste' },
|
||||
{ value: 'agb', label: 'AGB / Nutzungsbedingungen' },
|
||||
]
|
||||
|
||||
export function DocumentUploader({ onStart }: Props) {
|
||||
const [vendorName, setVendorName] = useState('')
|
||||
const [entries, setEntries] = useState<DocumentEntry[]>([
|
||||
{ doc_type: 'auto', label: '', url: '' },
|
||||
])
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const updateEntry = (idx: number, field: keyof DocumentEntry, value: string) => {
|
||||
setEntries(prev => {
|
||||
const copy = [...prev]
|
||||
copy[idx] = { ...copy[idx], [field]: value }
|
||||
return copy
|
||||
})
|
||||
}
|
||||
|
||||
const addEntry = () => {
|
||||
setEntries(prev => [...prev, { doc_type: 'auto', label: '', url: '' }])
|
||||
}
|
||||
|
||||
const removeEntry = (idx: number) => {
|
||||
if (entries.length <= 1) return
|
||||
setEntries(prev => prev.filter((_, i) => i !== idx))
|
||||
}
|
||||
|
||||
const handleSubmit = (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const valid = entries.filter(d => d.url.trim())
|
||||
if (!vendorName.trim() || valid.length === 0) return
|
||||
setLoading(true)
|
||||
onStart(vendorName.trim(), valid.map(d => ({
|
||||
...d,
|
||||
label: d.label || `${DOC_TYPES.find(t => t.value === d.doc_type)?.label || d.doc_type}: ${vendorName}`,
|
||||
})))
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit} className="space-y-6">
|
||||
{/* Vendor Name */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
Auftragsverarbeiter / Provider *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={vendorName}
|
||||
onChange={e => setVendorName(e.target.value)}
|
||||
placeholder="z.B. SysEleven GmbH, Amazon Web Services, Microsoft"
|
||||
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Documents */}
|
||||
<div className="bg-white rounded-xl border border-gray-200 p-6">
|
||||
<h2 className="text-lg font-semibold text-gray-800 mb-4">Dokumente</h2>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
Fuegen Sie die URLs der Vertragsdokumente hinzu. Das System erkennt den Dokumenttyp automatisch.
|
||||
</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{entries.map((entry, idx) => (
|
||||
<div key={idx} className="flex items-start gap-3 p-3 bg-gray-50 rounded-lg">
|
||||
<div className="w-52 shrink-0">
|
||||
<select
|
||||
value={entry.doc_type}
|
||||
onChange={e => updateEntry(idx, 'doc_type', e.target.value)}
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm bg-white"
|
||||
>
|
||||
{DOC_TYPES.map(t => (
|
||||
<option key={t.value} value={t.value}>{t.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="url"
|
||||
value={entry.url}
|
||||
onChange={e => updateEntry(idx, 'url', e.target.value)}
|
||||
placeholder="https://example.com/avv.pdf"
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="w-44 shrink-0">
|
||||
<input
|
||||
type="text"
|
||||
value={entry.label}
|
||||
onChange={e => updateEntry(idx, 'label', e.target.value)}
|
||||
placeholder="Bezeichnung (optional)"
|
||||
className="w-full px-3 py-2 border border-gray-200 rounded-lg text-sm"
|
||||
/>
|
||||
</div>
|
||||
{entries.length > 1 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => removeEntry(idx)}
|
||||
className="p-2 text-gray-400 hover:text-red-500"
|
||||
title="Entfernen"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={addEntry}
|
||||
className="mt-3 w-full py-2.5 border-2 border-dashed border-gray-300 rounded-lg text-gray-500 hover:border-blue-400 hover:text-blue-600 text-sm"
|
||||
>
|
||||
+ Weiteres Dokument hinzufuegen
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Submit */}
|
||||
<div className="flex items-center gap-4">
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading || !vendorName.trim() || !entries.some(e => e.url.trim())}
|
||||
className="px-8 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 font-medium disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
>
|
||||
{loading ? 'Wird gestartet...' : 'Pruefung starten'}
|
||||
</button>
|
||||
<p className="text-xs text-gray-400">
|
||||
Dokumente werden automatisch gegen Art. 28 DSGVO, Art. 32, Art. 44-49 und weitere Anforderungen geprueft.
|
||||
</p>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user