5da20af4fd
ResultSummary: Titel (Firma aus extracted_profile) + check_id + 4 Kacheln (Dokumente, Konform, Offene Pflichtangaben, Zu pruefen), gerechnet aus result.results. Co-Pilot-Ton: gruen/gelb/rot nur bei echten Werten. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
161 lines
7.5 KiB
TypeScript
161 lines
7.5 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* ComplianceResultTabs — standardisierte Ergebnis-Darstellung des
|
|
* Compliance-Checks: Kopf-Boxen (erkanntes Profil + Banner) ÜBER einer
|
|
* Tab-Leiste. Ein Tab je Themen-Agent (result.agent_outputs, P1: Impressum)
|
|
* via AgentResultTab + ein "Alle Checks (roh)"-Tab mit der bisherigen
|
|
* ChecklistView — so geht nichts verloren, während die Themen-Tabs wachsen.
|
|
*/
|
|
|
|
import React, { useState } from 'react'
|
|
|
|
import { ChecklistView, DOC_TYPE_LABELS, type DocResult } from './ChecklistView'
|
|
import { DocResultView } from './DocResultView'
|
|
import { MigrationPanel } from './MigrationPanel'
|
|
import { ResultSummary } from './ResultSummary'
|
|
|
|
export function ComplianceResultTabs({ results }: { results: any }) {
|
|
// Themen-Tabs aus der HAUPT-Engine (result.results) — nicht aus dem
|
|
// v3-Agent. Jedes Dokument = ein Tab mit der genauen Pflichtangaben-Tabelle.
|
|
const docs: DocResult[] = results.results || []
|
|
const tabs = docs.map((_: DocResult, i: number) => String(i)).concat('raw')
|
|
const [active, setActive] = useState<string>(tabs[0] ?? 'raw')
|
|
|
|
return (
|
|
<div className="bg-white border border-gray-200 rounded-xl p-6 shadow-sm space-y-4">
|
|
{/* Audit-Kopf: Titel + check_id + 4 KPI-Kacheln */}
|
|
<ResultSummary results={results} />
|
|
|
|
{/* Kopf-Boxen über den Tabs */}
|
|
{results.business_profile && (
|
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded-lg text-xs">
|
|
<div className="font-semibold text-blue-900 mb-1">Erkanntes Geschaeftsmodell</div>
|
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-blue-700">
|
|
<span>Typ: <strong>{results.business_profile.business_type?.toUpperCase()}</strong></span>
|
|
<span>Branche: {results.business_profile.industry}</span>
|
|
{results.business_profile.has_online_shop && <span className="text-amber-700">Online-Shop</span>}
|
|
{results.business_profile.is_regulated_profession && <span className="text-amber-700">Regulierter Beruf ({results.business_profile.regulated_profession_type})</span>}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{results.extracted_profile?.company_profile && Object.keys(results.extracted_profile.company_profile).length > 0 && (
|
|
<div className="p-3 bg-emerald-50 border border-emerald-200 rounded-lg text-xs">
|
|
<div className="flex items-center justify-between mb-1">
|
|
<span className="font-semibold text-emerald-900">Aus Dokumenten extrahiert</span>
|
|
<button className="text-emerald-700 hover:text-emerald-900 text-xs font-medium underline"
|
|
onClick={() => { /* TODO: navigate to company profile with pre-fill */ }}>
|
|
In Company Profile uebernehmen
|
|
</button>
|
|
</div>
|
|
<div className="flex flex-wrap gap-x-4 gap-y-1 text-emerald-700">
|
|
{results.extracted_profile.company_profile.companyName && (
|
|
<span>Firma: <strong>{results.extracted_profile.company_profile.companyName}</strong></span>
|
|
)}
|
|
{results.extracted_profile.company_profile.legalForm && (
|
|
<span>Rechtsform: {results.extracted_profile.company_profile.legalForm.toUpperCase()}</span>
|
|
)}
|
|
{results.extracted_profile.company_profile.headquartersCity && (
|
|
<span>Sitz: {results.extracted_profile.company_profile.headquartersZip} {results.extracted_profile.company_profile.headquartersCity}</span>
|
|
)}
|
|
{results.extracted_profile.company_profile.dpoEmail && (
|
|
<span>DSB: {results.extracted_profile.company_profile.dpoEmail}</span>
|
|
)}
|
|
{results.extracted_profile.company_profile.ustIdNr && (
|
|
<span>USt-IdNr: {results.extracted_profile.company_profile.ustIdNr}</span>
|
|
)}
|
|
</div>
|
|
{results.extracted_profile.compliance_scope_hints?.length > 0 && (
|
|
<div className="mt-2 pt-2 border-t border-emerald-200 text-emerald-600">
|
|
<span className="font-medium">Scope-Hinweise: </span>
|
|
{results.extracted_profile.compliance_scope_hints.map((h: any, i: number) => (
|
|
<span key={i} className="inline-block bg-emerald-100 rounded px-1.5 py-0.5 mr-1 mb-1">
|
|
{h.source}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{results.banner_result && (
|
|
<div className={`p-3 rounded-lg border text-xs ${
|
|
results.banner_result.violations > 0
|
|
? 'bg-amber-50 border-amber-200'
|
|
: results.banner_result.detected
|
|
? 'bg-green-50 border-green-200'
|
|
: 'bg-gray-50 border-gray-200'
|
|
}`}>
|
|
<div className="flex items-center gap-2">
|
|
<span className={`w-2 h-2 rounded-full ${
|
|
results.banner_result.violations > 0 ? 'bg-amber-500'
|
|
: results.banner_result.detected ? 'bg-green-500' : 'bg-gray-400'
|
|
}`} />
|
|
<span className="font-semibold text-gray-900">
|
|
Cookie-Banner-Check (automatisch)
|
|
</span>
|
|
</div>
|
|
<div className="mt-1 text-gray-600 ml-4">
|
|
{results.banner_result.detected ? (
|
|
<>
|
|
Banner erkannt{results.banner_result.provider ? ` (${results.banner_result.provider})` : ''}.
|
|
{results.banner_result.violations > 0
|
|
? ` ${results.banner_result.violations} Auffaelligkeit${results.banner_result.violations !== 1 ? 'en' : ''} gefunden.`
|
|
: ' Keine Auffaelligkeiten.'}
|
|
</>
|
|
) : (
|
|
'Kein Cookie-Banner erkannt oder Banner-Check nicht moeglich.'
|
|
)}
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{/* Tab-Leiste — ein Tab je Dokument (Haupt-Engine) + Übersicht */}
|
|
<div className="flex gap-1 border-b border-gray-200 flex-wrap">
|
|
{tabs.map(t => {
|
|
const tabClass = `px-3 py-1.5 text-sm font-medium border-b-2 -mb-px transition-colors flex items-center gap-1.5 ${
|
|
active === t
|
|
? 'border-purple-500 text-purple-700'
|
|
: 'border-transparent text-gray-500 hover:text-gray-700'
|
|
}`
|
|
if (t === 'raw') {
|
|
return (
|
|
<button key={t} onClick={() => setActive(t)} className={tabClass}>
|
|
Alle Checks
|
|
</button>
|
|
)
|
|
}
|
|
const doc = docs[Number(t)]
|
|
const dot = doc.error ? 'bg-gray-300'
|
|
: doc.scenario === 'import' ? 'bg-green-500'
|
|
: doc.scenario === 'fix' ? 'bg-amber-500'
|
|
: doc.scenario === 'regenerate' ? 'bg-red-500' : 'bg-gray-400'
|
|
return (
|
|
<button key={t} onClick={() => setActive(t)} className={tabClass}>
|
|
<span className={`w-2 h-2 rounded-full ${dot}`} />
|
|
{DOC_TYPE_LABELS[doc.doc_type] || doc.doc_type}
|
|
</button>
|
|
)
|
|
})}
|
|
</div>
|
|
|
|
{/* Tab-Inhalt */}
|
|
{active === 'raw' ? (
|
|
<ChecklistView results={results.results} />
|
|
) : docs[Number(active)] ? (
|
|
<DocResultView doc={docs[Number(active)]} />
|
|
) : null}
|
|
|
|
{/* Check-Footer (themenübergreifend) */}
|
|
{results.email_status && (
|
|
<div className="text-xs text-gray-500 flex items-center gap-2 border-t border-gray-100 pt-3">
|
|
<span className={`w-2 h-2 rounded-full ${results.email_status === 'sent' ? 'bg-green-400' : 'bg-gray-300'}`} />
|
|
E-Mail: {results.email_status === 'sent' ? 'Gesendet' : results.email_status}
|
|
</div>
|
|
)}
|
|
{results.check_id && <MigrationPanel checkId={results.check_id} />}
|
|
</div>
|
|
)
|
|
}
|