Oberfläche entschlackt: Adressdaten ohne Neu-laden und Erklärtexte, Diagnose ohne Rohnachricht
Der Adressdaten-Bereich verliert den Neu-laden-Knopf samt Telefonbuch-Abgleich und Wartedialog – die Ablage hält sich über Cache-Push und die Abfragen der Suche aktuell. Anrufen erscheint nur noch bei bestehender Verbindung zur Tray-App, eine Suche ohne Treffer zeigt nichts weiter an. Erklärtexte in Adressdaten und Tabs entfernt, ebenso das Rohnachricht-Formular im Diagnose-Bereich. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -185,10 +185,6 @@ export default function BrowserTabsPanel({ active, disabled, busy, onList, onOpe
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<p className="note">
|
|
||||||
Die Tabs gehören zum Firefox auf dem Rechner der SwyxTray-App. Ohne verbundenes Plugin
|
|
||||||
beantwortet die App die drei Kommandos nach etwa fünf Sekunden mit einem Fehler.
|
|
||||||
</p>
|
|
||||||
{hint && <p className="note note--error">{hint}</p>}
|
{hint && <p className="note note--error">{hint}</p>}
|
||||||
{outcome && <p className="note note--success">{outcome}</p>}
|
{outcome && <p className="note note--success">{outcome}</p>}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { describeContact, type Contact } from '../swyx/protocol'
|
import { describeContact, type Contact } from '../swyx/protocol'
|
||||||
import type { Directory, DirectoryProgress, SweepOptions } from '../swyx/directory'
|
|
||||||
import { fetchAddresses } from '../addresses'
|
import { fetchAddresses } from '../addresses'
|
||||||
import LoadingDialog from './LoadingDialog'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
/** Panel ist sichtbar – erst dann wird abgefragt. */
|
/** Panel ist sichtbar – erst dann wird abgefragt. */
|
||||||
active: boolean
|
active: boolean
|
||||||
/** Keine Verbindung zur SwyxTray-App – Anrufen und Telefonbuch-Abgleich entfallen. */
|
/** Keine Verbindung zur SwyxTray-App – Anrufen entfällt. */
|
||||||
disabled: boolean
|
disabled: boolean
|
||||||
/**
|
/**
|
||||||
* Adress-Cache, den die App unaufgefordert schickt. Er wird hier nicht
|
* Adress-Cache, den die App unaufgefordert schickt. Er wird hier nicht
|
||||||
@@ -15,7 +13,6 @@ interface Props {
|
|||||||
* hat ihn gerade hineingeschrieben.
|
* hat ihn gerade hineingeschrieben.
|
||||||
*/
|
*/
|
||||||
cache: Contact[] | null
|
cache: Contact[] | null
|
||||||
onLoad: (options: SweepOptions) => Promise<Directory | null>
|
|
||||||
onDial: (number: string) => void
|
onDial: (number: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,30 +33,22 @@ const SEARCH_DEBOUNCE_MS = 300
|
|||||||
* Abfrageergebnis; der Bereich funktioniert damit auch ohne Verbindung zur
|
* Abfrageergebnis; der Bereich funktioniert damit auch ohne Verbindung zur
|
||||||
* SwyxTray-App.
|
* SwyxTray-App.
|
||||||
*
|
*
|
||||||
* Das Telefonbuch der App fließt auf zwei Wegen in die Ablage:
|
* Das Telefonbuch der App fließt über den **Cache-Push** beim Verbinden
|
||||||
*
|
* (Protokoll 8) in die Ablage – die Startseite meldet ihn ans Backend; das
|
||||||
* 1. Der **Cache-Push** beim Verbinden (Protokoll 8) – die Startseite meldet
|
* Panel fragt die Ablage danach neu ab.
|
||||||
* ihn ans Backend; das Panel fragt die Ablage danach neu ab.
|
|
||||||
* 2. „Neu laden" bei bestehender Verbindung: erst das Telefonbuch der App
|
|
||||||
* holen (Cache oder rund 110 Einzelabfragen), das Backend legt es ab,
|
|
||||||
* dann die Ablage abfragen.
|
|
||||||
*/
|
*/
|
||||||
export default function ContactsPanel({ active, disabled, cache, onLoad, onDial }: Props) {
|
export default function ContactsPanel({ active, disabled, cache, onDial }: Props) {
|
||||||
// Das jeweils letzte Abfrageergebnis – kein Bestand, nur die Anzeige.
|
// Das jeweils letzte Abfrageergebnis – kein Bestand, nur die Anzeige.
|
||||||
const [results, setResults] = useState<Contact[] | null>(null)
|
const [results, setResults] = useState<Contact[] | null>(null)
|
||||||
const [loadError, setLoadError] = useState<string | null>(null)
|
const [loadError, setLoadError] = useState<string | null>(null)
|
||||||
// Warnung aus dem letzten Telefonbuch-Abgleich: dort fehlen evtl. Einträge.
|
|
||||||
const [incomplete, setIncomplete] = useState(false)
|
|
||||||
const [progress, setProgress] = useState<DirectoryProgress | null>(null)
|
|
||||||
// Bewusst nicht gemerkt: Nach einem Neustart beginnt die Suche leer.
|
// Bewusst nicht gemerkt: Nach einem Neustart beginnt die Suche leer.
|
||||||
const [filter, setFilter] = useState('')
|
const [filter, setFilter] = useState('')
|
||||||
|
|
||||||
const abortRef = useRef<AbortController | null>(null)
|
|
||||||
// Zählt die Abfragen: Eine überholte darf das Ergebnis einer neueren nicht
|
// Zählt die Abfragen: Eine überholte darf das Ergebnis einer neueren nicht
|
||||||
// mehr überschreiben – Antworten kommen nicht zwingend in Reihenfolge.
|
// mehr überschreiben – Antworten kommen nicht zwingend in Reihenfolge.
|
||||||
const queryIdRef = useRef(0)
|
const queryIdRef = useRef(0)
|
||||||
// Der aktuelle Suchbegriff für Abfragen außerhalb des Eingabe-Effekts
|
// Der aktuelle Suchbegriff für Abfragen außerhalb des Eingabe-Effekts
|
||||||
// (Cache-Push, „Neu laden") – ohne den Effekt neu anzustoßen.
|
// (Cache-Push) – ohne den Effekt neu anzustoßen.
|
||||||
const filterRef = useRef(filter)
|
const filterRef = useRef(filter)
|
||||||
filterRef.current = filter
|
filterRef.current = filter
|
||||||
|
|
||||||
@@ -89,42 +78,6 @@ export default function ContactsPanel({ active, disabled, cache, onLoad, onDial
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [active, filter, search])
|
}, [active, filter, search])
|
||||||
|
|
||||||
/**
|
|
||||||
* „Neu laden": Bei bestehender Verbindung zuerst das Telefonbuch der App
|
|
||||||
* holen – das Backend schreibt es dabei in die Ablage –, danach in jedem
|
|
||||||
* Fall die Ablage frisch abfragen.
|
|
||||||
*/
|
|
||||||
const reload = useCallback(async () => {
|
|
||||||
abortRef.current?.abort()
|
|
||||||
const controller = new AbortController()
|
|
||||||
abortRef.current = controller
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (!disabled) {
|
|
||||||
setProgress({ done: 0, total: 0, found: 0 })
|
|
||||||
const result = await onLoad({
|
|
||||||
signal: controller.signal,
|
|
||||||
onProgress: (next) => {
|
|
||||||
if (abortRef.current === controller) setProgress(next)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if (abortRef.current !== controller) return
|
|
||||||
// null heißt: Fehler – der steht bereits in der Fehleranzeige der Seite.
|
|
||||||
setIncomplete(result !== null && !result.complete)
|
|
||||||
// Das Backend schreibt nebenbei; kurz warten, damit die Abfrage den
|
|
||||||
// frischen Stand schon sieht.
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, ARCHIVE_WRITE_DELAY_MS))
|
|
||||||
if (abortRef.current !== controller) return
|
|
||||||
}
|
|
||||||
await search()
|
|
||||||
} finally {
|
|
||||||
if (abortRef.current === controller) {
|
|
||||||
abortRef.current = null
|
|
||||||
setProgress(null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [disabled, onLoad, search])
|
|
||||||
|
|
||||||
// Telefonbuch-Push der App: Die Startseite hat ihn ans Backend gemeldet;
|
// Telefonbuch-Push der App: Die Startseite hat ihn ans Backend gemeldet;
|
||||||
// nach einer kurzen Schreibfrist die Ablage neu abfragen.
|
// nach einer kurzen Schreibfrist die Ablage neu abfragen.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,25 +86,9 @@ export default function ContactsPanel({ active, disabled, cache, onLoad, onDial
|
|||||||
return () => clearTimeout(timer)
|
return () => clearTimeout(timer)
|
||||||
}, [cache, search])
|
}, [cache, search])
|
||||||
|
|
||||||
// Ein laufender Telefonbuch-Abgleich soll nicht weiterfragen, wenn die
|
|
||||||
// Seite verschwindet.
|
|
||||||
useEffect(() => () => abortRef.current?.abort(), [])
|
|
||||||
|
|
||||||
const loading = progress !== null
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="card__header">
|
<h2>Adressdaten</h2>
|
||||||
<h2>Adressdaten</h2>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="button button--ghost"
|
|
||||||
onClick={() => void reload()}
|
|
||||||
disabled={loading}
|
|
||||||
>
|
|
||||||
{loading ? 'Lädt …' : 'Neu laden'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Die `.row` ist nicht bloß Zierde: `.field` wächst (`flex: 1 1 260px`)
|
{/* 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*
|
und würde als direktes Kind der Karte – einer Spalte – in die *Höhe*
|
||||||
@@ -180,22 +117,15 @@ export default function ContactsPanel({ active, disabled, cache, onLoad, onDial
|
|||||||
<>
|
<>
|
||||||
<p className="note">
|
<p className="note">
|
||||||
{filter.trim() ? `${results.length} Treffer` : `${results.length} Einträge`}
|
{filter.trim() ? `${results.length} Treffer` : `${results.length} Einträge`}
|
||||||
{' · direkt aus der Adress-Ablage des Backends'}
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{incomplete && (
|
|
||||||
<p className="note note--error">
|
|
||||||
Der letzte Telefonbuch-Abgleich war unvollständig; es können Einträge fehlen.
|
|
||||||
„Neu laden" versucht es erneut.
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{results.length === 0 ? (
|
{results.length === 0 ? (
|
||||||
<p className="note">
|
!filter.trim() && (
|
||||||
{filter.trim()
|
<p className="note">
|
||||||
? `Kein Eintrag zu „${filter.trim()}".`
|
Die Ablage ist noch leer – sie füllt sich über die Webhooks und über das
|
||||||
: 'Die Ablage ist noch leer – sie füllt sich über die Webhooks und über das Telefonbuch der SwyxTray-App.'}
|
Telefonbuch der SwyxTray-App.
|
||||||
</p>
|
</p>
|
||||||
|
)
|
||||||
) : (
|
) : (
|
||||||
<ul className="contacts">
|
<ul className="contacts">
|
||||||
{results.map((contact, index) => (
|
{results.map((contact, index) => (
|
||||||
@@ -212,12 +142,13 @@ export default function ContactsPanel({ active, disabled, cache, onLoad, onDial
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{contact.number && <span className="contacts__number">{contact.number}</span>}
|
{contact.number && <span className="contacts__number">{contact.number}</span>}
|
||||||
{contact.number && (
|
{/* Ohne Verbindung zur Tray-App gibt es nichts zu wählen –
|
||||||
|
der Knopf erscheint dann gar nicht erst. */}
|
||||||
|
{contact.number && !disabled && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="button button--small"
|
className="button button--small"
|
||||||
onClick={() => onDial(contact.number!)}
|
onClick={() => onDial(contact.number!)}
|
||||||
disabled={disabled}
|
|
||||||
>
|
>
|
||||||
Anrufen
|
Anrufen
|
||||||
</button>
|
</button>
|
||||||
@@ -228,24 +159,6 @@ export default function ContactsPanel({ active, disabled, cache, onLoad, onDial
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<p className="note">
|
|
||||||
Jede Suche fragt die Adress-Ablage des Backends (MongoDB) direkt ab – die per Webhook
|
|
||||||
gemeldeten Kunden und Kuriere sowie das Telefonbuch des Swyx-Clients; im Browser wird
|
|
||||||
kein Bestand vorgehalten. Das Telefonbuch fließt bei bestehender Verbindung zur
|
|
||||||
SwyxTray-App in die Ablage ein – beim Verbinden von selbst, bei „Neu laden" auf
|
|
||||||
Anforderung.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
{loading && (
|
|
||||||
<LoadingDialog
|
|
||||||
title="Telefonbuch wird abgeglichen"
|
|
||||||
done={progress.done}
|
|
||||||
total={progress.total}
|
|
||||||
detail={progress.found > 0 ? `${progress.found} Einträge bisher` : undefined}
|
|
||||||
onCancel={() => abortRef.current?.abort()}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ export default function HomePage() {
|
|||||||
const [url, setUrl] = useState(DEFAULT_WS_URL)
|
const [url, setUrl] = useState(DEFAULT_WS_URL)
|
||||||
const [configNote, setConfigNote] = useState<string | null>(null)
|
const [configNote, setConfigNote] = useState<string | null>(null)
|
||||||
const [tab, setTab] = useState<TabName>('calls')
|
const [tab, setTab] = useState<TabName>('calls')
|
||||||
const [draft, setDraft] = useState('')
|
|
||||||
|
|
||||||
const tray = useSwyxTray()
|
const tray = useSwyxTray()
|
||||||
const { status, error, log, history, calls, ringingCall, busy, tray: appState } = tray
|
const { status, error, log, history, calls, ringingCall, busy, tray: appState } = tray
|
||||||
@@ -119,11 +118,6 @@ export default function HomePage() {
|
|||||||
tray.disconnect()
|
tray.disconnect()
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSendRaw(event: FormEvent) {
|
|
||||||
event.preventDefault()
|
|
||||||
const message = draft.trim()
|
|
||||||
if (message && tray.sendRaw(message)) setDraft('')
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page">
|
<div className="page">
|
||||||
@@ -193,9 +187,6 @@ export default function HomePage() {
|
|||||||
active={tab === 'contacts'}
|
active={tab === 'contacts'}
|
||||||
disabled={!isConnected}
|
disabled={!isConnected}
|
||||||
cache={tray.addressCache}
|
cache={tray.addressCache}
|
||||||
// Unverpackt weitergereicht: der Rückruf ist stabil, damit das
|
|
||||||
// Panel nicht bei jedem Rendern neu lädt.
|
|
||||||
onLoad={tray.loadDirectory}
|
|
||||||
onDial={(number) => void tray.dial(number)}
|
onDial={(number) => void tray.dial(number)}
|
||||||
/>
|
/>
|
||||||
{error && <p className="note note--error">{error}</p>}
|
{error && <p className="note note--error">{error}</p>}
|
||||||
@@ -279,26 +270,8 @@ export default function HomePage() {
|
|||||||
<section className="card">
|
<section className="card">
|
||||||
<h2>Diagnose</h2>
|
<h2>Diagnose</h2>
|
||||||
<MessageLog entries={log} />
|
<MessageLog entries={log} />
|
||||||
<form className="row" onSubmit={handleSendRaw}>
|
<div className="row">
|
||||||
<label className="field">
|
|
||||||
<span className="field__label">Rohnachricht senden</span>
|
|
||||||
<input
|
|
||||||
className="field__input"
|
|
||||||
type="text"
|
|
||||||
value={draft}
|
|
||||||
disabled={!isConnected}
|
|
||||||
placeholder={'{"id":1,"cmd":"status"}'}
|
|
||||||
onChange={(e) => setDraft(e.target.value)}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<div className="row__actions">
|
<div className="row__actions">
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="button button--primary"
|
|
||||||
disabled={!isConnected || !draft.trim()}
|
|
||||||
>
|
|
||||||
Senden
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="button"
|
className="button"
|
||||||
@@ -316,7 +289,7 @@ export default function HomePage() {
|
|||||||
Log leeren
|
Log leeren
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
Reference in New Issue
Block a user