CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (push) Canceled after 0s
CI / Web-Barrierefreiheitspruefung (axe, Tastatur, Reflow, Textabstaende) (pull_request) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (push) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (push) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (push) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (push) Canceled after 0s
CI / Web (dms-browser) (push) Canceled after 0s
CI / Web (pdf-viewer) (push) Canceled after 0s
CI / Web (shl) (push) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (push) Canceled after 0s
CI / Core-Modul (Build/Vet/Test) (pull_request) Canceled after 0s
CI / DMS Security-Test-Gate (AC5, Merge-Gate) (pull_request) Canceled after 0s
CI / Connect Security-Test-Gate (QA-01 AC4, Merge-Gate) (pull_request) Canceled after 0s
CI / DMS-Modul Supply-Chain-Scan (govulncheck) (pull_request) Canceled after 0s
CI / Web (dms-browser) (pull_request) Canceled after 0s
CI / Web (pdf-viewer) (pull_request) Canceled after 0s
CI / Web (shl) (pull_request) Canceled after 0s
Core-Schnittstellen-Vertragstests / contract-tests (pull_request) Canceled after 0s
Supply-Chain-Scan (Go) / govulncheck (pull_request) Canceled after 0s
Mail-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
Supply-Chain-Scan (npm) / npm-audit (pull_request) Canceled after 0s
Core-Pflichttest-Gate / pflichttest-gate (pull_request) Canceled after 0s
RBAC-Rollenhierarchie-Pflichtgate / rbac-policy-invariant (pull_request) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (push) Canceled after 0s
CI / DMS-Modul (Lint/Build/Test/Coverage/Artefakt) (pull_request) Canceled after 0s
Frontend (web/mail-search): Anhang-Vorschau fuer PDF (Canvas-Rendering via pdfjs-dist, gleiches Muster wie DOC-05s web/pdf-viewer/PdfViewer.tsx -- kein aequivalentes PdfCanvasPreview im Repo gefunden, daher PdfViewer als Vorlage uebernommen statt neu erfunden) und Bilder (<img> auf einem authentifiziert geladenen Blob). Die Vorschauart haengt ausschliesslich vom serverseitig per Magic-Bytes bestimmten contentType ab, nie von einem Dateinamen (den es an der Schnittstelle gar nicht gibt); nicht unterstuetzte Typen werden nur als Hinweistext angezeigt, nie eingebettet. Kein iframe/<object>/dangerouslySetInnerHTML -- durch automatisierten Sicherheitsreview-Test abgesichert. Neue lib/http.ts (authentifizierter Zugriff ueber @nexarch/shl, identisch zu web/pdf-viewer/lib/http.ts) und lib/attachments.ts (Anhangsliste/ -inhalt der Mail-API). Pruefprotokoll in mail/docs/UX-03-PRUEFPROTOKOLL.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { use } from "react";
|
|
import { useSearchParams } from "next/navigation";
|
|
import { splitHighlighted } from "../../../lib/highlight";
|
|
import { HIGHLIGHT_BG_LIGHT, HIGHLIGHT_FG_LIGHT } from "../../../lib/highlightColors";
|
|
import CaseContextPanel from "./CaseContextPanel";
|
|
import { AttachmentList } from "./AttachmentList";
|
|
|
|
// SRC-04 Akzeptanzkriterium 2: Klick auf einen Suchtreffer öffnet die Mail
|
|
// direkt an der Fundstelle. Der vollständige Mail-Inhaltsabruf (Betreff/
|
|
// Text laden anhand messageId) ist NICHT Bestandteil dieser Kachel — dafür
|
|
// gibt es noch keine HTTP-API (folgt mit INT-01). Bis dahin trägt der
|
|
// Suchtreffer-Link Betreff-/Text-Snippet als Kontext mit, damit die
|
|
// Fundstelle bereits jetzt real anspring- und hervorhebbar ist; die
|
|
// vollständige Mail-Ansicht wird von einer späteren Kachel ergänzt.
|
|
function Highlighted({ text }: { text: string }) {
|
|
return (
|
|
<>
|
|
{splitHighlighted(text).map((segment, idx) =>
|
|
segment.matched ? (
|
|
<mark
|
|
key={idx}
|
|
id={idx === 0 ? "fundstelle" : undefined}
|
|
style={{ background: HIGHLIGHT_BG_LIGHT, color: HIGHLIGHT_FG_LIGHT, padding: "0 2px" }}
|
|
>
|
|
{segment.text}
|
|
</mark>
|
|
) : (
|
|
<span key={idx}>{segment.text}</span>
|
|
)
|
|
)}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default function MailDetailPage({ params }: { params: Promise<{ messageId: string }> }) {
|
|
const { messageId } = use(params);
|
|
const searchParams = useSearchParams();
|
|
const subject = searchParams.get("subject") ?? "";
|
|
const snippet = searchParams.get("snippet") ?? "";
|
|
const tenant = searchParams.get("tenant") ?? "";
|
|
|
|
return (
|
|
<main style={{ maxWidth: 720, margin: "40px auto", padding: "0 16px" }}>
|
|
<p>
|
|
<a href="/">← Zurück zur Suche</a>
|
|
</p>
|
|
<h1>
|
|
{subject ? <Highlighted text={subject} /> : messageId}
|
|
</h1>
|
|
{snippet && (
|
|
<p>
|
|
<Highlighted text={snippet} />
|
|
</p>
|
|
)}
|
|
<CaseContextPanel messageId={messageId} tenant={tenant} />
|
|
<AttachmentList messageId={messageId} />
|
|
</main>
|
|
);
|
|
}
|