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.
) : (
| Nr. | Datum | Fällig | Betrag | Offen | Status | |
{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 (
| {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)' : ''} |
|
)
})}
)}
)
}