import { useCallback, useEffect, useRef, useState } from 'react' import { Card, Badge, Button, Field } from '../ui' import KundenansichtButton from '../customer/KundenansichtButton' import { applyCredit, calcCredit, decideCredit, getCredit } from '../../lib/teamApi' import { applyMessage, BILLING_MONTHS, calcLine, checkGlyph, checkTone, creditStatus, decisionConfirm, decisionError, decisionMessage, fmtBytes, historyLabel } from '../../lib/creditFormat' import { fmtDate, fmtDateTime, formatCents, productLabelOf } from '../../lib/format' import { storageLabel } from '../../lib/storageFormat' const staffOf = (by) => (by && by !== 'customer' && by !== 'system' ? ` · ${by}` : '') const toCents = (value) => Math.round(Number(value) * 100) /** * Antrag auf Uebernahmegutschrift: Pruefung und Entscheidung wie im frueheren Admin-Panel. * Waehrend einer Entscheidung sind alle Knoepfe gesperrt (busyRef verhindert auch Doppelklicks vor dem Neuzeichnen). */ export default function CreditDetail({ id, onClose, onChanged, onOpen }) { const [d, setD] = useState(null) const [error, setError] = useState(null) const [message, setMessage] = useState('') const [form, setForm] = useState(null) const [calcText, setCalcText] = useState('') const [actionError, setActionError] = useState('') const [busy, setBusy] = useState(false) const [busyAction, setBusyAction] = useState('') const busyRef = useRef(false) const amountTouched = useRef(false) const calcTimer = useRef(null) const load = useCallback(async (msg = '') => { setError(null) try { const detail = await getCredit(id) const r = detail.request setD(detail) setMessage(msg) setActionError('') amountTouched.current = false setForm({ checks: Object.fromEntries(detail.requiredChecks.map((k) => [k.key, false])), invoiceAmount: (r.invoiceAmountCents / 100).toFixed(2), billingMonths: r.billingMonths, paidUntil: r.paidUntilDate || '', takeoverFrom: r.takeoverFromDate || '', amount: ((detail.calc?.suggestedCents || 0) / 100).toFixed(2), note: '', }) setCalcText(detail.calc ? calcLine(detail.calc) : 'Angaben unvollständig') } catch (err) { setError(err.message || 'Antrag konnte nicht geladen werden.') } }, [id]) useEffect(() => { setD(null) load() return () => clearTimeout(calcTimer.current) }, [load]) const figures = (f) => ({ invoiceAmountCents: toCents(f.invoiceAmount), billingMonths: Number(f.billingMonths), paidUntil: f.paidUntil, takeoverFrom: f.takeoverFrom }) const setFigure = (key, value) => { const next = { ...form, [key]: value } setForm(next) clearTimeout(calcTimer.current) calcTimer.current = setTimeout(async () => { try { const { calc } = await calcCredit(figures(next)) setCalcText(calcLine(calc)) if (!amountTouched.current) setForm((f) => ({ ...f, amount: (calc.suggestedCents / 100).toFixed(2) })) } catch (err) { setCalcText(err.message) } }, 300) } const decide = async (action) => { if (busyRef.current) return setActionError('') const problem = decisionError(action, form.note) if (problem) { setActionError(problem); return } const amountCents = toCents(form.amount) if (!window.confirm(decisionConfirm(action, amountCents, d.customer?.name))) return if (busyRef.current) return busyRef.current = true setBusy(true) setBusyAction(action) try { const result = await decideCredit(id, { action, note: form.note.trim(), amountCents, checks: form.checks, ...figures(form) }) onChanged?.() await load(decisionMessage(action, result)) } catch (err) { setActionError(err.message) } finally { busyRef.current = false setBusy(false) setBusyAction('') } } const apply = async () => { if (busyRef.current) return busyRef.current = true setBusy(true) setBusyAction('apply') try { const result = await applyCredit(id) onChanged?.() await load(applyMessage(result)) } catch (err) { setActionError(err.message) } finally { busyRef.current = false setBusy(false) setBusyAction('') } } if (error) return

{error}

if (!d || !form) return
Wird geladen …
const r = d.request const c = d.customer const s = d.subscription const st = creditStatus(r.status) const pending = r.status === 'offen' || r.status === 'rueckfrage' const fileNames = new Map(r.files.map((f) => [f.id, f.name])) const allChecked = Object.values(form.checks).every(Boolean) const canApprove = allChecked && Number(form.amount) > 0 && !busy const errorLine = actionError ?

{actionError}

: null return ( Schließen}>

eingegangen {fmtDateTime(r.createdAt)} {st.label}

{message &&

{message}

}
Leistung
{r.services}
Bisher bei
{r.provider}
Domain
{r.domain ? {r.domain} : '–'}
Rechnung
{formatCents(r.invoiceAmountCents)} für {r.billingMonths} Monat{r.billingMonths === 1 ? '' : 'e'}
Bezahlt bis
{fmtDate(r.paidUntil)}
Antwort an
{r.email}
Bestätigt
Angaben und Bedingungen (Stand {r.conditionsVersion || '–'}) am {fmtDateTime(r.consentAt)}

{r.message}

{r.files.length ? ( r.files.map((f) => (
{f.name} ({fmtBytes(f.size)}, {fmtDate(f.uploadedAt)})
)) ) :

Keine Nachweise (mehr) vorhanden.

}
{r.history.map((h, i) => (
{historyLabel(h.type)} {fmtDateTime(h.at)}{staffOf(h.by)} {h.text &&

{h.text}

} {(h.files || []).length > 0 &&
Dateien: {h.files.map((fid) => fileNames.get(fid) || 'gelöscht').join(', ')}
}
))}
Kunde
{c.name || c.companyName}{c.code ? ` · Kd.-Nr. ${c.code}` : ''}
E-Mail
{c.email || '–'}
Abo
{s ? <>{productLabelOf(s.productKey)} {s.status}
Zusatzspeicher: {storageLabel(s.storageUnits)}
: kein Abo}
{s && ( <>
Leistungsbeginn
{fmtDate(s.serviceStart)}
Nächste Abbuchung
{s.active ? fmtDate(s.currentPeriodEnd) : '–'}
{s.cancelAt && <>
Gekündigt zum
{fmtDate(s.cancelAt)}
}
Stripe-Guthaben
{s.creditBalanceCents == null ? '–' : formatCents(s.creditBalanceCents)}
)}
{s?.stripeUrl && }
{d.otherRequests.length > 0 && ( {d.otherRequests.map((o) => { const ost = creditStatus(o.status) return (
· {o.services} · {fmtDate(o.createdAt)} {ost.label}{o.amountCents ? ` ${formatCents(o.amountCents)}` : ''}
) })}
)} {d.autoChecks.map((k) => (
{checkGlyph(k.status)} {k.label}{k.detail &&
{k.detail}
}
))}
{pending ? ( <>
{d.requiredChecks.map((k) => ( ))}
setFigure('invoiceAmount', e.target.value)} />
setFigure('paidUntil', e.target.value)} /> setFigure('takeoverFrom', e.target.value)} />

{calcText}

Werte weichen von der Rechnung ab? Hier korrigieren – die Korrektur wird beim Genehmigen im Verlauf vermerkt. „Übernahme ab“ ist der Leistungsbeginn des Abos.

{ amountTouched.current = true; setForm({ ...form, amount: e.target.value }) }} />