import React from 'react' import { authFetch } from '../../utils/authFetch' import { AboInvoice, useAboInvoices } from '../hooks/getAboInvoices' type Props = { abonementId?: string | number | null } const BASE_URL = (process.env.NEXT_PUBLIC_API_BASE_URL || '').replace(/\/+$/, '') const formatDate = (value?: string | null) => { if (!value) return '—' const d = new Date(value) return Number.isNaN(d.getTime()) ? '—' : d.toLocaleDateString('de-DE') } const formatMoney = (value?: string | number | null, currency?: string | null) => { if (value == null || value === '') return '—' const n = typeof value === 'string' ? Number(value) : value if (!Number.isFinite(Number(n))) return String(value) return new Intl.NumberFormat('de-DE', { style: 'currency', currency: currency || 'EUR', }).format(Number(n)) } 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}` } type UiLifecycleStatus = 'issued' | 'ongoing' | 'finished' | 'pause' | 'cancelled' const normalizeInvoiceStatus = (rawStatus?: string | null): UiLifecycleStatus => { const status = (rawStatus || '').toLowerCase() if (!status) return 'issued' if (status === 'cancelled' || status === 'canceled' || status === 'void') return 'cancelled' if (status === 'pause' || status === 'paused') return 'pause' if (status === 'finished' || status === 'paid' || status === 'closed' || status === 'settled') return 'finished' if (status === 'ongoing' || status === 'active' || status === 'processing') return 'ongoing' if (status === 'issued' || status === 'draft' || status === 'pending') return 'issued' return 'issued' } const statusBadgeClass = (status: UiLifecycleStatus) => { if (status === 'ongoing') return 'bg-green-100 text-green-800' if (status === 'pause') return 'bg-amber-100 text-amber-800' if (status === 'cancelled') return 'bg-red-100 text-red-700' if (status === 'finished') return 'bg-gray-200 text-gray-700' return 'bg-blue-100 text-blue-800' } const displayStatus = (status: UiLifecycleStatus) => status === 'pause' ? 'Pause' : status === 'cancelled' ? 'Cancelled' : status === 'ongoing' ? 'Ongoing' : status === 'finished' ? 'Finished' : 'Issued' function downloadBlob(content: Blob, fileName: string) { const url = URL.createObjectURL(content) const a = document.createElement('a') a.href = url a.download = fileName document.body.appendChild(a) a.click() a.remove() URL.revokeObjectURL(url) } export default function FinanceInvoices({ abonementId }: Props) { const { data: invoices, loading, error } = useAboInvoices(abonementId) const [busyId, setBusyId] = React.useState(null) const [actionError, setActionError] = React.useState(null) const onView = (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') } const onDownload = async (invoice: AboInvoice) => { setActionError(null) setBusyId(invoice.id) try { const url = resolveInvoiceUrl(invoice) if (url) { const res = await authFetch(url, { method: 'GET' }) if (!res.ok) throw new Error(`Download failed: ${res.status}`) const blob = await res.blob() const invoiceNo = invoice.invoiceNumber || String(invoice.id) const ext = invoice.pdfUrl ? 'pdf' : 'html' downloadBlob(blob, `invoice-${invoiceNo}.${ext}`) } else { const blob = new Blob([JSON.stringify(invoice.raw, null, 2)], { type: 'application/json' }) downloadBlob(blob, `invoice-${invoice.invoiceNumber || invoice.id}.json`) } } catch (e: any) { setActionError(e?.message || 'Failed to download invoice.') } finally { setBusyId(null) } } const onExportAll = () => { setActionError(null) if (!invoices.length) { setActionError('No invoices available to export.') return } const exportPayload = { exportedAt: new Date().toISOString(), abonementId: abonementId ?? null, count: invoices.length, invoices: invoices.map((inv) => ({ id: inv.id, invoiceNumber: inv.invoiceNumber, issuedAt: inv.issuedAt, createdAt: inv.createdAt, totalNet: inv.totalNet, totalTax: inv.totalTax, totalGross: inv.totalGross, currency: inv.currency, status: inv.status, })), } const blob = new Blob([JSON.stringify(exportPayload, null, 2)], { type: 'application/json' }) downloadBlob(blob, `invoices-export-${new Date().toISOString().slice(0, 10)}.json`) } return (

Finance & Invoices

{!abonementId ? (
No subscription selected. Invoices will appear once you have an active subscription.
) : loading ? (
Loading invoices…
) : error ? (
{error}
) : invoices.length === 0 ? (
No invoices found for this subscription.
) : (
{invoices.map((invoice) => ( ))}
Date Invoice # Status Total Actions
{formatDate(invoice.issuedAt || invoice.createdAt)} {invoice.invoiceNumber || `#${invoice.id}`} {displayStatus(normalizeInvoiceStatus(invoice.status))} {formatMoney(invoice.totalGross, invoice.currency)}
)} {actionError && (
{actionError}
)}
) }