import { useState, useEffect, useCallback, useMemo } from 'react' import { FaFilePdf, FaArrowUpRightFromSquare, FaSpinner, FaRotate, FaPlus } from 'react-icons/fa6' import { integrationsApi, formatMoney, INVOICE_STATUS, INVOICE_NINJA_PUBLIC_URL } from '../lib/integrationsApi' import { useFinanceSummary } from '../hooks/useFinanceSummary' import { useCustomers } from '../hooks/useCustomers' import { PageHeader, Card, Button, Kpi, EmptyState, Field } from '../components/ui' export default function FinancePage() { const { summary, loading: sumLoading, refresh: refreshSummary } = useFinanceSummary() const { customers } = useCustomers() const [invoices, setInvoices] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [filter, setFilter] = useState('all') // all | open | overdue | paid const [search, setSearch] = useState('') const [openingId, setOpeningId] = useState(null) const [showCreate, setShowCreate] = useState(false) const load = useCallback(async () => { setLoading(true) setError(null) try { const data = await integrationsApi.listInvoices(undefined, 400) setInvoices(data.invoices || []) } catch (err) { setError(err.message) } finally { setLoading(false) } }, []) useEffect(() => { load() }, [load]) const today = new Date().toISOString().slice(0, 10) const filtered = useMemo(() => { return invoices.filter((inv) => { const overdue = Number(inv.balance) > 0 && inv.due_date && inv.due_date < today if (filter === 'open' && !(Number(inv.balance) > 0)) return false if (filter === 'overdue' && !overdue) return false if (filter === 'paid' && inv.status_id !== 4) return false if (search) { const s = search.toLowerCase() if (!`${inv.number} ${inv.client_name}`.toLowerCase().includes(s)) return false } return true }) }, [invoices, filter, search, today]) const openPdf = async (id) => { setOpeningId(id) try { await integrationsApi.openInvoicePdf(id) } catch (err) { alert('PDF-Fehler: ' + err.message) } finally { setOpeningId(null) } } return (
} />
{showCreate && (
{ setShowCreate(false); load(); refreshSummary() }} onCancel={() => setShowCreate(false)} />
)}
{[['all', 'Alle'], ['open', 'Offen'], ['overdue', 'Ueberfaellig'], ['paid', 'Bezahlt']].map(([id, label]) => ( ))}
setSearch(e.target.value)} style={{ maxWidth: 280 }} />
{loading ? (
) : error ? (
{error}
) : filtered.length === 0 ? ( ) : (
{filtered.map((inv) => { const st = INVOICE_STATUS[inv.status_id] || { label: '—', color: 'var(--text-muted)' } const overdue = Number(inv.balance) > 0 && inv.due_date && inv.due_date < today return ( ) })}
Nr.KundeDatumFaelligBetragOffenStatus
{inv.number || inv.id?.slice(-6)} {inv.client_name || '-'} {inv.date || '-'} {inv.due_date || '-'} {formatMoney(inv.amount)} 0 ? 'var(--warn)' : 'var(--ok)' }}>{formatMoney(inv.balance)} {st.label}{overdue ? ' (ueberf.)' : ''}
)}
) } function CreateInvoiceForm({ customers, onDone, onCancel }) { const linkable = (customers || []).filter((c) => c.invoiceNinjaClientId) const [clientId, setClientId] = useState('') const [description, setDescription] = useState('') const [cost, setCost] = useState('') const [quantity, setQuantity] = useState('1') const [busy, setBusy] = useState(false) const [error, setError] = useState(null) const submit = async (e) => { e.preventDefault() if (!clientId) { setError('Bitte Kunde waehlen'); return } setBusy(true); setError(null) try { await integrationsApi.createInvoice({ client_id: clientId, public_notes: description, line_items: [{ notes: description, cost: Number(cost || 0), quantity: Number(quantity || 1) }], }) onDone() } catch (err) { setError(err.message); setBusy(false) } } return ( {linkable.length === 0 ? (

Kein Kunde ist mit InvoiceNinja verknuepft. Bitte zuerst im Kunden-Bereich verknuepfen.

) : (