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,
|
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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user