import { useCallback, useEffect, useRef, useState } from 'react' import { describeContact, type Contact } from '../swyx/protocol' import { fetchAddresses } from '../addresses' interface Props { /** Panel ist sichtbar – erst dann wird abgefragt. */ active: boolean /** Keine Verbindung zur SwyxTray-App – Anrufen entfällt. */ disabled: boolean /** * Adress-Cache, den die App unaufgefordert schickt. Er wird hier nicht * angezeigt, sondern ist das Signal, die Ablage neu abzufragen – das Backend * hat ihn gerade hineingeschrieben. */ cache: Contact[] | null onDial: (number: string) => void } /** So lange bekommt das Backend Zeit, den Telefonbuch-Push wegzuschreiben. */ const ARCHIVE_WRITE_DELAY_MS = 1500 /** Entprellung der Eingabe – erst dann geht die Abfrage an die Datenbank. */ const SEARCH_DEBOUNCE_MS = 300 /** * Adressdaten aus der **Adress-Ablage des Backends** (MongoDB-Sammlung * `addresses`) – dem zusammengeführten Bestand aus den Webhooks (Kunden, * Kuriere) und dem Telefonbuch des Swyx-Clients. * * Es gibt keinen Bestand im Browser: Jede Eingabe im Suchfeld fragt – * entprellt – die Datenbank ab, gesucht wird dort als Teilzeichenkette in * Name, Rufnummer und Beschreibung. Angezeigt wird immer das frische * Abfrageergebnis; der Bereich funktioniert damit auch ohne Verbindung zur * SwyxTray-App. * * Das Telefonbuch der App fließt über den **Cache-Push** beim Verbinden * (Protokoll 8) in die Ablage – die Startseite meldet ihn ans Backend; das * Panel fragt die Ablage danach neu ab. */ export default function ContactsPanel({ active, disabled, cache, onDial }: Props) { // Das jeweils letzte Abfrageergebnis – kein Bestand, nur die Anzeige. const [results, setResults] = useState(null) const [loadError, setLoadError] = useState(null) // Bewusst nicht gemerkt: Nach einem Neustart beginnt die Suche leer. const [filter, setFilter] = useState('') // Zählt die Abfragen: Eine überholte darf das Ergebnis einer neueren nicht // mehr überschreiben – Antworten kommen nicht zwingend in Reihenfolge. const queryIdRef = useRef(0) // Der aktuelle Suchbegriff für Abfragen außerhalb des Eingabe-Effekts // (Cache-Push) – ohne den Effekt neu anzustoßen. const filterRef = useRef(filter) filterRef.current = filter /** Fragt die Ablage mit dem aktuellen Suchbegriff ab. */ const search = useCallback(async () => { const ticket = ++queryIdRef.current try { const entries = await fetchAddresses(filterRef.current) if (ticket !== queryIdRef.current) return setResults(entries) setLoadError(null) } catch (e) { if (ticket !== queryIdRef.current) return setLoadError(e instanceof Error ? e.message : String(e)) } }, []) // Jede Eingabe fragt die Datenbank ab – entprellt; die erste Abfrage nach // dem Öffnen läuft sofort. Beim erneuten Öffnen des Bereichs ebenfalls // frisch abfragen, damit nie ein alter Stand stehen bleibt. useEffect(() => { if (!active) return const timer = setTimeout(() => void search(), results === null ? 0 : SEARCH_DEBOUNCE_MS) return () => clearTimeout(timer) // `results` absichtlich nicht in den Abhängigkeiten: Es würde nach jeder // Antwort eine weitere Abfrage anstoßen. // eslint-disable-next-line react-hooks/exhaustive-deps }, [active, filter, search]) // Telefonbuch-Push der App: Die Startseite hat ihn ans Backend gemeldet; // nach einer kurzen Schreibfrist die Ablage neu abfragen. useEffect(() => { if (!cache || cache.length === 0) return const timer = setTimeout(() => void search(), ARCHIVE_WRITE_DELAY_MS) return () => clearTimeout(timer) }, [cache, search]) return ( <>

Adressdaten

{/* Die `.row` ist nicht bloß Zierde: `.field` wächst (`flex: 1 1 260px`) und würde als direktes Kind der Karte – einer Spalte – in die *Höhe* wachsen und den freien Platz aufsaugen. In der Zeile wächst es in die Breite, wie in allen anderen Bereichen auch. */}
{loadError &&

{loadError}

} {results === null ? ( !loadError &&

Adressdaten werden abgefragt …

) : ( <>

{filter.trim() ? `${results.length} Treffer` : `${results.length} Einträge`}

{results.length === 0 ? ( !filter.trim() && (

Die Ablage ist noch leer – sie füllt sich über die Webhooks und über das Telefonbuch der SwyxTray-App.

) ) : ( )} )} ) }