/* Kundenansicht neu: Zähler für Chat (ungelesen) und Verträge (offene Rechnungen plus Rückfragen, "!" bei Zahlungsproblem). Vertrag V3. Jede der vier Quellen wird einzeln abgefragt; nur eine gültige Antwort ersetzt den gemerkten Wert, ein Fehler lässt den letzten Wert stehen. Kein DOM-Zugriff, api und Zeitgeber kommen als Parameter. */ export const CHAT_POLL_MS = 45000 const QUELLEN = { chat: { pfad: '/api/chat/unread-count', nimm: (a) => (typeof a?.unread === 'number' && Number.isFinite(a.unread) ? Math.max(0, Math.trunc(a.unread)) : undefined) }, rechnungen: { pfad: '/api/invoices', nimm: (a) => (Array.isArray(a?.invoices) ? a.invoices : undefined) }, rueckfragen: { pfad: '/api/abo/takeover-credits', nimm: (a) => (Array.isArray(a?.requests) ? a.requests : undefined) }, // switchPending ("Wechsel läuft") ist kein Zahlungsproblem und löst kein "!" aus abo: { pfad: '/api/abo', nimm: (a) => (a && typeof a === 'object' && 'subscription' in a ? a.subscription?.paymentProblem === true : undefined) }, } const GRUPPEN = { chat: ['chat'], vertraege: ['rechnungen', 'rueckfragen', 'abo'], alle: ['chat', 'rechnungen', 'rueckfragen', 'abo'] } /** Verträge-Zähler: Rechnungen mit offenem Betrag plus Rückfragen zur Übernahmegutschrift; "!" nur bei Zahlungsproblem. */ export function berechneVertraege({ rechnungen = [], rueckfragen = [], paymentProblem = false } = {}) { const offen = rechnungen.filter((r) => Number(r?.balance) > 0).length const fragen = rueckfragen.filter((r) => r?.status === 'rueckfrage').length return { zahl: offen + fragen, ausruf: paymentProblem === true } } export function erzeugeZaehler({ api, onChange = () => {}, sichtbar = () => true, imChat = () => false, zeitgeber = { setze: (fn, ms) => { const id = setInterval(fn, ms); id.unref?.(); return id }, loesche: (id) => clearInterval(id) } }) { const daten = { chat: 0, rechnungen: [], rueckfragen: [], abo: false } const lauf = {} // je Quelle die Nummer der jüngsten Anfrage: eine ältere, später eintreffende Antwort gilt nicht let letzte = '' let laeuft = false let timer const stand = () => ({ chat: daten.chat, vertraege: berechneVertraege({ rechnungen: daten.rechnungen, rueckfragen: daten.rueckfragen, paymentProblem: daten.abo }) }) letzte = JSON.stringify(stand()) function melde() { const s = stand() const schluessel = JSON.stringify(s) if (schluessel === letzte) return letzte = schluessel onChange(s) } async function hole(name) { const nr = (lauf[name] = (lauf[name] ?? 0) + 1) let wert try { wert = QUELLEN[name].nimm(await api(QUELLEN[name].pfad)) } catch { wert = undefined } if (nr !== lauf[name] || wert === undefined) return daten[name] = wert } /** was: 'chat' | 'vertraege' | 'alle'. Wirft nie. */ async function aktualisiere(was = 'alle') { await Promise.all((GRUPPEN[was] ?? GRUPPEN.alle).map(hole)) melde() } /** Chat-Zähler von außen setzen (z. B. 0 beim Öffnen des Chats); eine laufende Abfrage überschreibt ihn nicht. */ function setzeChat(zahl) { lauf.chat = (lauf.chat ?? 0) + 1 daten.chat = Math.max(0, Math.trunc(Number(zahl)) || 0) melde() } async function takt() { if (!sichtbar() || imChat()) return await aktualisiere('chat') } async function start() { if (!laeuft) { laeuft = true; timer = zeitgeber.setze(takt, CHAT_POLL_MS) } await aktualisiere('alle') } function stop() { if (!laeuft) return laeuft = false zeitgeber.loesche(timer) } return { start, stop, aktualisiere, setzeChat, stand } }