import { useState, useEffect, useCallback } from 'react' import { FaFilePdf, FaPlus, FaLink, FaSpinner, FaRotate } from 'react-icons/fa6' import { databases, DATABASE_ID, COLLECTIONS } from '../lib/appwrite' import { integrationsApi, formatMoney, INVOICE_STATUS } from '../lib/integrationsApi' import { useInvoices } from '../hooks/useInvoices' import { customerStage, isInvoiceReady } from '../lib/leads' const card = { background: 'rgba(45, 55, 72, 0.5)', borderRadius: '12px', padding: '16px', border: '1px solid rgba(16, 185, 129, 0.2)', } export default function InvoicePanel({ ticket }) { const [customer, setCustomer] = useState(null) const [customerLoading, setCustomerLoading] = useState(true) const [customerError, setCustomerError] = useState(null) const loadCustomer = useCallback(async () => { if (!ticket.customerId) { setCustomer(null) setCustomerLoading(false) return } setCustomerLoading(true) try { const doc = await databases.getDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, ticket.customerId) setCustomer(doc) setCustomerError(null) } catch (err) { setCustomerError(err.message) setCustomer(null) } finally { setCustomerLoading(false) } }, [ticket.customerId]) useEffect(() => { loadCustomer() }, [loadCustomer]) const clientId = customer?.invoiceNinjaClientId || null const { invoices, loading, error, refresh } = useInvoices(clientId) if (customerLoading) { return (
Kunde wird geladen...
) } if (!ticket.customerId) { return (

Kein Kunde am Ticket verknüpft, keine Rechnungen verfügbar.

) } return (
Rechnungen {customer?.name ? `- ${customer.name}` : ''}
{clientId && ( )}
{customerError &&
{customerError}
} {customer && customerStage(customer) === 'lead' ? (

Dieser Kunde ist noch ein Lead. Erst zum festen Kunden machen (im Kunden-Bereich, vollständige Daten nötig), dann sind Rechnungen möglich.

) : !clientId && !isInvoiceReady(customer) ? (

Rechnungsdaten unvollständig. Bitte im Kunden-Bereich Unternehmen, E-Mail, Straße, PLZ und Stadt ergänzen.

) : !clientId ? ( ) : ( )}
) } // ---------------------------------------------------- Verknuepfung ----------- function LinkClient({ customer, onLinked }) { const [search, setSearch] = useState(customer?.name || '') const [results, setResults] = useState([]) const [busy, setBusy] = useState(false) const [error, setError] = useState(null) const [saving, setSaving] = useState(false) const doSearch = async () => { setBusy(true) setError(null) try { const data = await integrationsApi.listClients(search) setResults(data.clients || []) } catch (err) { setError(err.message) } finally { setBusy(false) } } useEffect(() => { doSearch() // eslint-disable-next-line react-hooks/exhaustive-deps }, []) const saveMapping = async (clientId) => { setSaving(true) setError(null) try { await databases.updateDocument(DATABASE_ID, COLLECTIONS.CUSTOMERS, customer.$id, { invoiceNinjaClientId: clientId, }) onLinked() } catch (err) { setError(err.message) setSaving(false) } } const createAndLink = async () => { setSaving(true) setError(null) try { const data = await integrationsApi.createClient({ name: customer.name || customer.companyName || 'Neuer Kunde', email: customer.email || '', phone: customer.phone || '', address: customer.location || '', }) if (data.client?.id) { await saveMapping(data.client.id) } else { throw new Error('Client konnte nicht angelegt werden') } } catch (err) { setError(err.message) setSaving(false) } } return (

Dieser Kunde ist noch nicht mit InvoiceNinja verknüpft.

setSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && doSearch()} />
{error &&
{error}
}
{results.map((c) => (
{c.name} {c.number ? · {c.number} : null} {c.email ?
{c.email}
: null}
))} {!busy && results.length === 0 && (

Keine Treffer.

)}
) } // ---------------------------------------------------- Rechnungsliste --------- // Nur anzeigen: neue Rechnungen entstehen direkt in InvoiceNinja function InvoiceList({ invoices, loading, error }) { const [openingId, setOpeningId] = useState(null) const openPdf = async (id) => { setOpeningId(id) try { await integrationsApi.openInvoicePdf(id) } catch (err) { alert('PDF konnte nicht geöffnet werden: ' + err.message) } finally { setOpeningId(null) } } return (
{error &&
{error}
} {loading ? (

Rechnungen werden geladen...

) : invoices.length === 0 ? (

Noch keine Rechnungen für diesen Kunden.

) : (
{invoices.map((inv) => { const st = INVOICE_STATUS[inv.status_id] || { label: '–', color: '#a0aec0' } const overdue = Number(inv.balance) > 0 && inv.due_date && new Date(inv.due_date) < new Date() return ( ) })}
Nr.DatumFälligBetragOffenStatus
{inv.number || inv.id?.slice(-6)} {inv.date || '-'} {inv.due_date || '-'} {formatMoney(inv.amount)} 0 ? '#f59e0b' : '#10b981' }}>{formatMoney(inv.balance)} {st.label}{overdue ? ' (überfällig)' : ''}
)}
) }