feat: TextReference component — original text, position, correction in findings

Shows for each finding:
- Original text block from DSE (or "missing" indicator)
- Position: section heading, number, parent section, paragraph index
- Correction: insert/append/replace with copy button
Falls back to plain correction view if no text reference available.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benjamin Admin
2026-04-29 11:59:55 +02:00
parent 0ba76d041a
commit 15d1e118ed
2 changed files with 131 additions and 1 deletions
@@ -1,6 +1,7 @@
'use client'
import React, { useState } from 'react'
import { TextReference } from './TextReference'
interface ServiceInfo {
name: string
@@ -14,11 +15,27 @@ interface ServiceInfo {
status: string
}
interface TextRef {
found: boolean
source_url: string
document_type: string
section_heading: string
section_number: string
parent_section: string
paragraph_index: number
original_text: string
issue: string
correction_type: string
correction_text: string
insert_after: string
}
interface ScanFinding {
code: string
severity: string
text: string
correction: string
text_reference: TextRef | null
}
interface ScanData {
@@ -157,7 +174,12 @@ export function ScanResult({ data }: { data: ScanData }) {
</span>
<p className="text-sm text-gray-800 flex-1">{f.text}</p>
</div>
{f.correction && (
{/* Text Reference (original text + position + correction) */}
{f.text_reference && (
<TextReference ref={f.text_reference} correction={f.correction} />
)}
{/* Fallback: correction without text reference */}
{!f.text_reference && f.correction && (
<div className="mt-2">
<button
onClick={() => setExpandedCorrection(isExpanded ? null : f.code)}
@@ -0,0 +1,108 @@
'use client'
import React, { useState } from 'react'
interface TextRef {
found: boolean
source_url: string
document_type: string
section_heading: string
section_number: string
parent_section: string
paragraph_index: number
original_text: string
issue: string
correction_type: string
correction_text: string
insert_after: string
}
const ISSUE_LABELS: Record<string, { label: string; color: string }> = {
missing: { label: 'Fehlt in der DSE', color: 'text-red-700 bg-red-50' },
incomplete: { label: 'Unvollstaendig', color: 'text-yellow-700 bg-yellow-50' },
incorrect: { label: 'Fehlerhaft', color: 'text-orange-700 bg-orange-50' },
}
const CORRECTION_LABELS: Record<string, string> = {
insert: 'Neuen Abschnitt einfuegen',
append: 'Am Ende des Absatzes ergaenzen',
replace: 'Absatz ersetzen',
}
export function TextReference({ ref, correction }: { ref: TextRef; correction?: string }) {
const [showCorrection, setShowCorrection] = useState(false)
const issue = ISSUE_LABELS[ref.issue] || null
const correctionText = correction || ref.correction_text
return (
<div className="mt-3 space-y-2 text-sm">
{/* Original Text Block */}
<div>
<p className="text-xs font-medium text-gray-500 mb-1 flex items-center gap-1">
<span>📄</span> Originaltextblock:
</p>
<div className={`rounded-lg p-3 border ${ref.found ? 'bg-gray-50 border-gray-200' : 'bg-red-50 border-red-200'}`}>
{ref.found ? (
<p className="text-gray-700 text-xs whitespace-pre-wrap">{ref.original_text || '(Textinhalt konnte nicht extrahiert werden)'}</p>
) : (
<p className="text-red-600 text-xs italic">Nicht vorhanden Eintrag fehlt in der {ref.document_type}.</p>
)}
</div>
</div>
{/* Position */}
<div>
<p className="text-xs font-medium text-gray-500 mb-1 flex items-center gap-1">
<span>📍</span> Position:
</p>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-2 text-xs text-blue-800">
{ref.found ? (
<>
<span className="font-semibold">{ref.section_heading || 'Abschnitt unbekannt'}</span>
{ref.section_number && <span className="text-blue-600 ml-1">(Nr. {ref.section_number})</span>}
{ref.parent_section && <span className="text-blue-500 ml-1">in: {ref.parent_section}</span>}
{ref.paragraph_index > 0 && <span className="text-blue-500 ml-1">| Absatz {ref.paragraph_index}</span>}
</>
) : ref.insert_after ? (
<span><strong>{CORRECTION_LABELS[ref.correction_type] || 'Einfuegen'}</strong> nach Abschnitt &quot;{ref.insert_after}&quot;</span>
) : (
<span>Neuen Abschnitt in der {ref.document_type} anlegen</span>
)}
{ref.source_url && (
<div className="text-blue-400 mt-1 truncate">in: {ref.source_url}</div>
)}
</div>
</div>
{/* Correction */}
{correctionText && (
<div>
<button
onClick={() => setShowCorrection(!showCorrection)}
className="text-xs text-purple-600 hover:text-purple-800 font-medium flex items-center gap-1"
>
<span>{showCorrection ? '▼' : '▶'}</span>
<span></span> Korrekturvorschlag {showCorrection ? 'ausblenden' : 'anzeigen'}
</button>
{showCorrection && (
<div className="mt-2 bg-white border border-purple-200 rounded-lg p-3 relative">
{issue && (
<span className={`text-[10px] px-2 py-0.5 rounded-full font-medium mb-2 inline-block ${issue.color}`}>
{CORRECTION_LABELS[ref.correction_type] || issue.label}
</span>
)}
<pre className="text-xs text-gray-700 whitespace-pre-wrap font-sans mt-1">{correctionText}</pre>
<button
onClick={() => navigator.clipboard.writeText(correctionText)}
className="absolute top-2 right-2 text-xs bg-gray-100 hover:bg-gray-200 px-2 py-1 rounded transition-colors"
title="In Zwischenablage kopieren"
>
📋 Kopieren
</button>
</div>
)}
</div>
)}
</div>
)
}