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:
@@ -7,6 +7,7 @@ import {
|
||||
getThread,
|
||||
downloadMailAttachment,
|
||||
downloadMailRaw,
|
||||
downloadMailOCRText,
|
||||
exportMailPDF,
|
||||
type MailDetail,
|
||||
type MailAttachment,
|
||||
@@ -20,6 +21,8 @@ import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||
import { OcrBadge } from "@/components/ocr-badge";
|
||||
import { FileText } from "lucide-react";
|
||||
|
||||
// ── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -87,7 +90,7 @@ function MailHeaderGrid({ mail }: { mail: MailDetail }) {
|
||||
<span>{formatBytes(mail.size)}</span>
|
||||
{/* Verification status */}
|
||||
<span className="font-medium text-muted-foreground">Integrität:</span>
|
||||
<span>
|
||||
<span className="flex flex-wrap items-center gap-2">
|
||||
{mail.verify_ok === true ? (
|
||||
<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">
|
||||
@@ -110,6 +113,7 @@ function MailHeaderGrid({ mail }: { mail: MailDetail }) {
|
||||
Noch nicht geprüft
|
||||
</span>
|
||||
)}
|
||||
<OcrBadge status={mail.ocr_status} />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -257,6 +261,8 @@ export default function MailViewPage({
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [downloading, setDownloading] = 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 [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 (
|
||||
<div className="min-h-screen">
|
||||
<Navbar username={user?.username ?? ""} role={user?.role ?? ""} />
|
||||
@@ -341,9 +366,26 @@ export default function MailViewPage({
|
||||
>
|
||||
{pdfLoading ? "..." : "Als PDF exportieren"}
|
||||
</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>
|
||||
{ocrInfo && (
|
||||
<Alert>
|
||||
<AlertDescription>{ocrInfo}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Loading */}
|
||||
{loading && (
|
||||
|
||||
Reference in New Issue
Block a user