feat(PROJ-44): OCR-Badge + OCR-Text-Download auf Mail-Detail-Seite

- OcrBadge neben dem Verifikations-Status im Mail-Header
- "OCR-Text"-Button (lucide FileText) in der Action-Leiste,
  sichtbar nur bei ocr_status=done und ocr_chars>0
- Tooltip via title-Attribut zeigt erkannte Zeichenzahl
- Pending-/Not-Available-Antworten werden als Alert angezeigt
  ("OCR laeuft noch, bitte gleich nochmal versuchen")

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-05-10 22:18:49 +02:00
parent 83039dcf8d
commit f4403c8e6c
+43 -1
View File
@@ -7,6 +7,7 @@ import {
getThread, getThread,
downloadMailAttachment, downloadMailAttachment,
downloadMailRaw, downloadMailRaw,
downloadMailOCRText,
exportMailPDF, exportMailPDF,
type MailDetail, type MailDetail,
type MailAttachment, type MailAttachment,
@@ -20,6 +21,8 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { Skeleton } from "@/components/ui/skeleton"; import { Skeleton } from "@/components/ui/skeleton";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { OcrBadge } from "@/components/ocr-badge";
import { FileText } from "lucide-react";
// ── Helpers ──────────────────────────────────────────────────────────────── // ── Helpers ────────────────────────────────────────────────────────────────
@@ -87,7 +90,7 @@ function MailHeaderGrid({ mail }: { mail: MailDetail }) {
<span>{formatBytes(mail.size)}</span> <span>{formatBytes(mail.size)}</span>
{/* Verification status */} {/* Verification status */}
<span className="font-medium text-muted-foreground">Integrität:</span> <span className="font-medium text-muted-foreground">Integrität:</span>
<span> <span className="flex flex-wrap items-center gap-2">
{mail.verify_ok === true ? ( {mail.verify_ok === true ? (
<span className="inline-flex items-center gap-1 text-green-600 text-sm font-medium"> <span className="inline-flex items-center gap-1 text-green-600 text-sm font-medium">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@@ -110,6 +113,7 @@ function MailHeaderGrid({ mail }: { mail: MailDetail }) {
Noch nicht geprüft Noch nicht geprüft
</span> </span>
)} )}
<OcrBadge status={mail.ocr_status} />
</span> </span>
</div> </div>
@@ -257,6 +261,8 @@ export default function MailViewPage({
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [downloading, setDownloading] = useState(false); const [downloading, setDownloading] = useState(false);
const [pdfLoading, setPdfLoading] = useState(false); const [pdfLoading, setPdfLoading] = useState(false);
const [ocrLoading, setOcrLoading] = useState(false);
const [ocrInfo, setOcrInfo] = useState<string | null>(null);
const [thread, setThread] = useState<ThreadMail[] | null>(null); const [thread, setThread] = useState<ThreadMail[] | null>(null);
const [threadOpen, setThreadOpen] = useState(false); const [threadOpen, setThreadOpen] = useState(false);
@@ -302,6 +308,25 @@ export default function MailViewPage({
} }
} }
async function handleOCRDownload() {
setOcrLoading(true);
setOcrInfo(null);
try {
const result = await downloadMailOCRText(id);
if (result.kind === "ok") {
triggerDownload(result.blob, result.filename);
} else if (result.kind === "pending") {
setOcrInfo("OCR läuft noch, bitte gleich nochmal versuchen.");
} else {
setOcrInfo("Kein OCR-Text verfügbar.");
}
} catch (e) {
alert(`OCR-Download fehlgeschlagen: ${e instanceof Error ? e.message : e}`);
} finally {
setOcrLoading(false);
}
}
return ( return (
<div className="min-h-screen"> <div className="min-h-screen">
<Navbar username={user?.username ?? ""} role={user?.role ?? ""} /> <Navbar username={user?.username ?? ""} role={user?.role ?? ""} />
@@ -341,9 +366,26 @@ export default function MailViewPage({
> >
{pdfLoading ? "..." : "Als PDF exportieren"} {pdfLoading ? "..." : "Als PDF exportieren"}
</Button> </Button>
{mail.ocr_status === "done" && (mail.ocr_chars ?? 0) > 0 && (
<Button
variant="outline"
size="sm"
onClick={handleOCRDownload}
disabled={ocrLoading}
title={`${(mail.ocr_chars ?? 0).toLocaleString("de-DE")} Zeichen erkannt`}
>
<FileText className="mr-1.5 h-4 w-4" />
{ocrLoading ? "..." : "OCR-Text"}
</Button>
)}
</div> </div>
)} )}
</div> </div>
{ocrInfo && (
<Alert>
<AlertDescription>{ocrInfo}</AlertDescription>
</Alert>
)}
{/* Loading */} {/* Loading */}
{loading && ( {loading && (