From 4ff36d172818455659f73f67bef2c8a89a0b2088 Mon Sep 17 00:00:00 2001 From: seaznCode Date: Tue, 7 Apr 2026 17:04:05 +0200 Subject: [PATCH] feat: add PDF viewing functionality for invoices in finance management --- src/app/admin/finance-management/page.tsx | 36 ++++++++++++++++++- .../profile/components/financeInvoices.tsx | 21 ++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/src/app/admin/finance-management/page.tsx b/src/app/admin/finance-management/page.tsx index 1d4db21..9b559ba 100644 --- a/src/app/admin/finance-management/page.tsx +++ b/src/app/admin/finance-management/page.tsx @@ -120,6 +120,33 @@ export default function FinanceManagementPage() { URL.revokeObjectURL(url) } + const [pdfLoading, setPdfLoading] = useState(null) + + const viewInvoicePdf = async (inv: AdminInvoice) => { + setPdfLoading(inv.id) + try { + const base = process.env.NEXT_PUBLIC_API_BASE_URL || '' + const res = await fetch(`${base}/api/invoices/${inv.id}/pdf`, { + method: 'GET', + credentials: 'include', + headers: { + ...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}), + }, + }) + if (!res.ok) { + const body = await res.json().catch(() => ({})) + throw new Error(body?.message || `Failed to load PDF (${res.status})`) + } + const blob = await res.blob() + const blobUrl = URL.createObjectURL(blob) + window.open(blobUrl, '_blank', 'noopener,noreferrer') + } catch (e: any) { + setReportMsg({ type: 'error', text: e?.message || 'Failed to load invoice PDF.' }) + } finally { + setPdfLoading(null) + } + } + const sendEmailReport = async () => { if (!reportEmail.trim()) return setReportMsg(null) @@ -385,11 +412,18 @@ export default function FinanceManagementPage() { + diff --git a/src/app/profile/components/financeInvoices.tsx b/src/app/profile/components/financeInvoices.tsx index f055ba7..22ca016 100644 --- a/src/app/profile/components/financeInvoices.tsx +++ b/src/app/profile/components/financeInvoices.tsx @@ -28,8 +28,10 @@ const isAbsUrl = (url: string) => /^https?:\/\//i.test(url) const resolveInvoiceUrl = (invoice: AboInvoice) => { const raw = invoice.pdfUrl || invoice.downloadUrl || invoice.htmlUrl || invoice.fileUrl - if (!raw) return null - return isAbsUrl(raw) ? raw : `${BASE_URL}${raw.startsWith('/') ? '' : '/'}${raw}` + if (raw) return isAbsUrl(raw) ? raw : `${BASE_URL}${raw.startsWith('/') ? '' : '/'}${raw}` + // Fallback: use the backend PDF proxy endpoint if an id is available + if (invoice.id) return `${BASE_URL}/api/invoices/${invoice.id}/pdf` + return null } type UiLifecycleStatus = 'issued' | 'ongoing' | 'finished' | 'pause' | 'cancelled' @@ -80,14 +82,25 @@ export default function FinanceInvoices({ abonementId }: Props) { const [busyId, setBusyId] = React.useState(null) const [actionError, setActionError] = React.useState(null) - const onView = (invoice: AboInvoice) => { + const onView = async (invoice: AboInvoice) => { setActionError(null) const url = resolveInvoiceUrl(invoice) if (!url) { setActionError('No view URL is available for this invoice.') return } - window.open(url, '_blank', 'noopener,noreferrer') + setBusyId(invoice.id) + try { + const res = await authFetch(url, { method: 'GET' }) + if (!res.ok) throw new Error(`Failed to load PDF: ${res.status}`) + const blob = await res.blob() + const blobUrl = URL.createObjectURL(blob) + window.open(blobUrl, '_blank', 'noopener,noreferrer') + } catch (e: any) { + setActionError(e?.message || 'Failed to load invoice PDF.') + } finally { + setBusyId(null) + } } const onDownload = async (invoice: AboInvoice) => {