feat: add email report functionality to finance management page

This commit is contained in:
seaznCode 2026-04-07 16:49:56 +02:00
parent ba28c54ae8
commit 7a8801274f

View File

@ -18,6 +18,10 @@ export default function FinanceManagementPage() {
const [diagData, setDiagData] = useState<any | null>(null) const [diagData, setDiagData] = useState<any | null>(null)
const [selectedInvoice, setSelectedInvoice] = useState<AdminInvoice | null>(null) const [selectedInvoice, setSelectedInvoice] = useState<AdminInvoice | null>(null)
const [detailModalOpen, setDetailModalOpen] = useState(false) const [detailModalOpen, setDetailModalOpen] = useState(false)
const [emailDialogOpen, setEmailDialogOpen] = useState(false)
const [reportEmail, setReportEmail] = useState('')
const [sendingReport, setSendingReport] = useState(false)
const [reportMsg, setReportMsg] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
// NEW: fetch invoices from backend // NEW: fetch invoices from backend
const { const {
@ -116,6 +120,39 @@ export default function FinanceManagementPage() {
URL.revokeObjectURL(url) URL.revokeObjectURL(url)
} }
const sendEmailReport = async () => {
if (!reportEmail.trim()) return
setReportMsg(null)
setSendingReport(true)
try {
const base = process.env.NEXT_PUBLIC_API_BASE_URL || ''
const res = await fetch(`${base}/api/admin/invoices/email-report`, {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...(accessToken ? { Authorization: `Bearer ${accessToken}` } : {}),
},
body: JSON.stringify({
email: reportEmail.trim(),
from: billFilter.from || undefined,
to: billFilter.to || undefined,
}),
})
const body = await res.json().catch(() => ({}))
if (!res.ok || body?.success === false) {
throw new Error(body?.message || `Request failed (${res.status})`)
}
setReportMsg({ type: 'success', text: `Report sent to ${reportEmail.trim()} (${body.data?.sentCount ?? 0} paid invoice(s)).` })
setEmailDialogOpen(false)
setReportEmail('')
} catch (e: any) {
setReportMsg({ type: 'error', text: e?.message || 'Failed to send email report.' })
} finally {
setSendingReport(false)
}
}
return ( return (
<PageLayout> <PageLayout>
<div className="bg-gradient-to-tr from-blue-50 via-white to-blue-100 min-h-screen"> <div className="bg-gradient-to-tr from-blue-50 via-white to-blue-100 min-h-screen">
@ -182,6 +219,7 @@ export default function FinanceManagementPage() {
<div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between"> <div className="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<h2 className="text-lg font-semibold text-[#1C2B4A]">Invoices</h2> <h2 className="text-lg font-semibold text-[#1C2B4A]">Invoices</h2>
<div className="flex flex-wrap gap-2 text-sm"> <div className="flex flex-wrap gap-2 text-sm">
<button onClick={() => { setReportMsg(null); setEmailDialogOpen(true) }} className="rounded-lg border border-blue-200 bg-blue-50 px-3 py-2 text-blue-900 font-medium hover:bg-blue-100">Send Email Report</button>
<button onClick={() => exportBills('csv')} className="rounded-lg border border-gray-200 px-3 py-2 hover:bg-gray-50">Export CSV</button> <button onClick={() => exportBills('csv')} className="rounded-lg border border-gray-200 px-3 py-2 hover:bg-gray-50">Export CSV</button>
<button onClick={() => exportBills('pdf')} className="rounded-lg border border-gray-200 px-3 py-2 hover:bg-gray-50">Export PDF</button> <button onClick={() => exportBills('pdf')} className="rounded-lg border border-gray-200 px-3 py-2 hover:bg-gray-50">Export PDF</button>
<button onClick={reload} className="rounded-lg border border-gray-200 px-3 py-2 hover:bg-gray-50">Reload</button> <button onClick={reload} className="rounded-lg border border-gray-200 px-3 py-2 hover:bg-gray-50">Reload</button>
@ -222,6 +260,11 @@ export default function FinanceManagementPage() {
</div> </div>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
{reportMsg && (
<div className={`rounded-md border px-3 py-2 text-sm mb-3 ${reportMsg.type === 'success' ? 'border-green-200 bg-green-50 text-green-700' : 'border-red-200 bg-red-50 text-red-700'}`}>
{reportMsg.text}
</div>
)}
{invError && ( {invError && (
<div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 mb-3"> <div className="rounded-md border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-700 mb-3">
{invError} {invError}
@ -366,6 +409,47 @@ export default function FinanceManagementPage() {
onExport={(inv) => exportInvoice(inv)} onExport={(inv) => exportInvoice(inv)}
/> />
)} )}
{/* Email Report Dialog */}
{emailDialogOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="w-full max-w-md rounded-2xl bg-white p-6 shadow-2xl">
<h3 className="text-lg font-semibold text-[#1C2B4A] mb-1">Send Email Report</h3>
<div className="text-xs text-amber-700 bg-amber-50 border border-amber-200 rounded-lg px-3 py-2 mb-4">
Only <strong>paid</strong> invoices will be included in the report, regardless of the status filter.
{(billFilter.from || billFilter.to) && (
<span> The current date range filter ({billFilter.from || '…'} {billFilter.to || '…'}) will be applied.</span>
)}
</div>
<label className="block text-sm font-medium text-gray-700 mb-1">Recipient Email</label>
<input
type="email"
value={reportEmail}
onChange={e => setReportEmail(e.target.value)}
placeholder="email@example.com"
className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm text-gray-900 placeholder:text-gray-400 focus:ring-2 focus:ring-blue-900 focus:border-transparent"
autoFocus
onKeyDown={e => { if (e.key === 'Enter' && !sendingReport) sendEmailReport() }}
/>
<div className="mt-4 flex items-center justify-end gap-2">
<button
onClick={() => { setEmailDialogOpen(false); setReportEmail('') }}
disabled={sendingReport}
className="rounded-lg border border-gray-200 px-4 py-2 text-sm text-gray-700 hover:bg-gray-50 disabled:opacity-60"
>
Cancel
</button>
<button
onClick={sendEmailReport}
disabled={sendingReport || !reportEmail.trim()}
className="rounded-lg bg-[#1C2B4A] px-4 py-2 text-sm font-semibold text-white shadow hover:bg-[#1C2B4A]/90 disabled:opacity-60 disabled:cursor-not-allowed"
>
{sendingReport ? 'Sending…' : 'Send Report'}
</button>
</div>
</div>
</div>
)}
</section> </section>
</div> </div>
</div> </div>