'use client' import React, { useState, useEffect } from 'react' interface PaymentControl { control_id: string domain: string title: string objective: string check_target: string evidence: string[] automation: string } interface PaymentDomain { id: string name: string description: string } interface Assessment { id: string project_name: string tender_reference: string customer_name: string system_type: string total_controls: number controls_passed: number controls_failed: number controls_partial: number controls_not_applicable: number controls_not_checked: number compliance_score: number status: string created_at: string } interface TenderAnalysis { id: string file_name: string file_size: number project_name: string customer_name: string status: string total_requirements: number matched_count: number unmatched_count: number partial_count: number requirements?: Array<{ req_id: string; text: string; obligation_level: string; technical_domain: string; confidence: number }> match_results?: Array<{ req_id: string; req_text: string; verdict: string; matched_controls: Array<{ control_id: string; title: string; relevance: number }>; gap_description?: string }> created_at: string } const AUTOMATION_STYLES: Record = { high: { bg: 'bg-green-100', text: 'text-green-700' }, medium: { bg: 'bg-yellow-100', text: 'text-yellow-700' }, partial: { bg: 'bg-orange-100', text: 'text-orange-700' }, low: { bg: 'bg-red-100', text: 'text-red-700' }, } const TARGET_ICONS: Record = { code: '๐Ÿ’ป', system: '๐Ÿ–ฅ๏ธ', config: 'โš™๏ธ', process: '๐Ÿ“‹', repository: '๐Ÿ“ฆ', certificate: '๐Ÿ“œ', } export default function PaymentCompliancePage() { const [controls, setControls] = useState([]) const [domains, setDomains] = useState([]) const [assessments, setAssessments] = useState([]) const [tenderAnalyses, setTenderAnalyses] = useState([]) const [selectedTender, setSelectedTender] = useState(null) const [selectedDomain, setSelectedDomain] = useState('all') const [loading, setLoading] = useState(true) const [tab, setTab] = useState<'controls' | 'assessments' | 'tender'>('controls') const [uploading, setUploading] = useState(false) const [processing, setProcessing] = useState(false) const [showNewAssessment, setShowNewAssessment] = useState(false) const [newProject, setNewProject] = useState({ project_name: '', tender_reference: '', customer_name: '', system_type: 'full_stack' }) useEffect(() => { loadData() }, []) async function loadData() { try { setLoading(true) const [ctrlResp, assessResp, tenderResp] = await Promise.all([ fetch('/api/sdk/v1/payment-compliance?endpoint=controls'), fetch('/api/sdk/v1/payment-compliance?endpoint=assessments'), fetch('/api/sdk/v1/payment-compliance/tender'), ]) if (ctrlResp.ok) { const data = await ctrlResp.json() setControls(data.controls || []) setDomains(data.domains || []) } if (assessResp.ok) { const data = await assessResp.json() setAssessments(data.assessments || []) } if (tenderResp.ok) { const data = await tenderResp.json() setTenderAnalyses(data.analyses || []) } } catch {} finally { setLoading(false) } } async function handleTenderUpload(e: React.ChangeEvent) { const file = e.target.files?.[0] if (!file) return setUploading(true) try { const formData = new FormData() formData.append('file', file) formData.append('project_name', file.name.replace(/\.[^.]+$/, '')) const resp = await fetch('/api/sdk/v1/payment-compliance/tender', { method: 'POST', body: formData }) if (resp.ok) { const data = await resp.json() // Auto-start extraction + matching setProcessing(true) const extractResp = await fetch(`/api/sdk/v1/payment-compliance/tender/${data.id}?action=extract`, { method: 'POST' }) if (extractResp.ok) { await fetch(`/api/sdk/v1/payment-compliance/tender/${data.id}?action=match`, { method: 'POST' }) } // Reload and show result const detailResp = await fetch(`/api/sdk/v1/payment-compliance/tender/${data.id}`) if (detailResp.ok) { const detail = await detailResp.json() setSelectedTender(detail) } loadData() } } catch {} finally { setUploading(false) setProcessing(false) } } async function handleViewTender(id: string) { const resp = await fetch(`/api/sdk/v1/payment-compliance/tender/${id}`) if (resp.ok) { setSelectedTender(await resp.json()) } } async function handleCreateAssessment() { const resp = await fetch('/api/sdk/v1/payment-compliance', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(newProject), }) if (resp.ok) { setShowNewAssessment(false) setNewProject({ project_name: '', tender_reference: '', customer_name: '', system_type: 'full_stack' }) loadData() } } const filteredControls = selectedDomain === 'all' ? controls : controls.filter(c => c.domain === selectedDomain) const domainStats = domains.map(d => ({ ...d, count: controls.filter(c => c.domain === d.id).length, })) return (
{/* Header */}

Payment Terminal Compliance

Technische Pruefbibliothek fuer Zahlungssysteme โ€” {controls.length} Controls in {domains.length} Domaenen

{loading ? (
Lade...
) : tab === 'controls' ? ( <> {/* Domain Filter */}
{domainStats.map(d => ( ))}
{/* Domain Description */} {selectedDomain !== 'all' && (
{domains.find(d => d.id === selectedDomain)?.name}:{' '} {domains.find(d => d.id === selectedDomain)?.description}
)} {/* Controls List */}
{filteredControls.map(ctrl => { const autoStyle = AUTOMATION_STYLES[ctrl.automation] || AUTOMATION_STYLES.low return (
{ctrl.control_id} {TARGET_ICONS[ctrl.check_target] || '๐Ÿ”'} {ctrl.check_target} {ctrl.automation}

{ctrl.title}

{ctrl.objective}

{ctrl.evidence.map(ev => ( {ev} ))}
) })}
) : ( <> {/* Assessments Tab */}
{showNewAssessment && (

Neues Payment Compliance Assessment

setNewProject(p => ({ ...p, project_name: e.target.value }))} className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500" placeholder="z.B. Ausschreibung Muenchen 2026" />
setNewProject(p => ({ ...p, tender_reference: e.target.value }))} className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500" placeholder="z.B. 2026-PAY-001" />
setNewProject(p => ({ ...p, customer_name: e.target.value }))} className="w-full px-3 py-2 border rounded-lg focus:ring-2 focus:ring-purple-500" placeholder="z.B. Stadt Muenchen" />
)} {assessments.length === 0 ? (

Noch keine Assessments

Erstelle ein neues Assessment fuer eine Ausschreibung.

) : (
{assessments.map(a => (

{a.project_name}

{a.customer_name && {a.customer_name} ยท } {a.tender_reference && Ref: {a.tender_reference} ยท } {new Date(a.created_at).toLocaleDateString('de-DE')}
{a.status}
{a.total_controls}
Total
{a.controls_passed}
Passed
{a.controls_failed}
Failed
{a.controls_partial}
Partial
{a.controls_not_applicable}
N/A
{a.controls_not_checked}
Offen
))}
)} ) : tab === 'tender' ? ( <> {/* Tender Analysis Tab */}

Ausschreibung analysieren

Laden Sie ein Ausschreibungsdokument hoch. Die KI extrahiert automatisch alle Anforderungen und matcht sie gegen die Control-Bibliothek.

PDF, TXT oder Word. Max 50 MB. Dokument wird nur fuer diese Analyse verwendet.

{/* Selected Tender Detail */} {selectedTender && (

{selectedTender.project_name}

{selectedTender.file_name} โ€” {selectedTender.status}

{/* Stats */}
{selectedTender.total_requirements}
Anforderungen
{selectedTender.matched_count}
Abgedeckt
{selectedTender.partial_count}
Teilweise
{selectedTender.unmatched_count}
Luecken
{/* Match Results */} {selectedTender.match_results && selectedTender.match_results.length > 0 && (

Requirement โ†’ Control Matching

{selectedTender.match_results.map((mr, idx) => (
{mr.req_id} {mr.verdict === 'matched' ? 'Abgedeckt' : mr.verdict === 'partial' ? 'Teilweise' : 'Luecke'}

{mr.req_text}

{mr.matched_controls && mr.matched_controls.length > 0 && (
{mr.matched_controls.map(mc => ( {mc.control_id} ({Math.round(mc.relevance * 100)}%) ))}
)} {mr.gap_description && (

{mr.gap_description}

)}
))}
)}
)} {/* Previous Analyses */} {tenderAnalyses.length > 0 && (

Bisherige Analysen

{tenderAnalyses.map(ta => ( ))}
)} ) : null}
) }