diff --git a/backend/src/controllers/fileDownload.controller.ts b/backend/src/controllers/fileDownload.controller.ts index c5f31975..fed0e70c 100644 --- a/backend/src/controllers/fileDownload.controller.ts +++ b/backend/src/controllers/fileDownload.controller.ts @@ -84,15 +84,51 @@ export async function downloadFile(req: AuthRequest, res: Response): Promise = { INTERIM: 'Zwischenrechnung', @@ -121,7 +121,7 @@ export default function InvoicesSection({ {invoice.documentPath && (
= { ELECTRICITY: 'Strom', @@ -2118,7 +2118,7 @@ export default function ContractDetail() { {c.cancellationLetterPath ? (
window.open(fileUrl(`/uploads/${request.proofDocument}`), '_blank')} + onClick={() => window.open(viewUrl(`/uploads/${request.proofDocument}`), '_blank')} title="Löschnachweis anzeigen" > diff --git a/frontend/src/pages/settings/PdfTemplates.tsx b/frontend/src/pages/settings/PdfTemplates.tsx index b3341db4..5d785a32 100644 --- a/frontend/src/pages/settings/PdfTemplates.tsx +++ b/frontend/src/pages/settings/PdfTemplates.tsx @@ -9,7 +9,7 @@ import Input from '../../components/ui/Input'; import Badge from '../../components/ui/Badge'; import Modal from '../../components/ui/Modal'; import { ArrowLeft, Plus, Edit, Trash2, FileText, Upload, Link2, Eye, Play } from 'lucide-react'; -import { fileUrl } from '../../utils/fileUrl'; +import { viewUrl } from '../../utils/fileUrl'; export default function PdfTemplates() { const navigate = useNavigate(); @@ -96,7 +96,7 @@ export default function PdfTemplates() { - + diff --git a/frontend/src/utils/fileUrl.ts b/frontend/src/utils/fileUrl.ts index 742735ac..c205f1d2 100644 --- a/frontend/src/utils/fileUrl.ts +++ b/frontend/src/utils/fileUrl.ts @@ -15,11 +15,25 @@ */ import { getAccessToken } from '../services/api'; -export function fileUrl(path: string | null | undefined): string { +/** + * Kurzform für Inline-Vorschau: identisch zu `fileUrl(path, { inline: true })`. + * Verwenden für „Anzeigen"-Links / target="_blank"-Vorschauen. Default- + * `fileUrl(path)` bleibt für Downloads (Content-Disposition: attachment). + */ +export function viewUrl(path: string | null | undefined): string { + return fileUrl(path, { inline: true }); +} + +export function fileUrl(path: string | null | undefined, opts?: { inline?: boolean }): string { if (!path) return ''; const token = getAccessToken(); const normalizedPath = path.startsWith('/') ? path : '/' + path; - const base = `/api/files/download?path=${encodeURIComponent(normalizedPath)}`; + // `?disposition=inline` schaltet die Anzeige im Browser-Tab ein, + // der Backend-Controller bleibt aber nur dann inline, wenn die + // Datei tatsächlich ein safe Type (PDF/PNG/JPEG/GIF/WebP) ist – + // sonst fällt's auf attachment zurück. Default attachment. + const dispParam = opts?.inline ? '&disposition=inline' : ''; + const base = `/api/files/download?path=${encodeURIComponent(normalizedPath)}${dispParam}`; if (!token) return base; return `${base}&token=${encodeURIComponent(token)}`; }