Hustler-Keller: unter Stufe 6 leeres Register ohne Abruf, mit ehrlichem Text

BotLeadList bekommt die Stufe (WorkersPage reicht sie durch). Unterhalb von BOTLEADS_AB
(Stufe 6) ruft sie weder api.botleads noch ein JWT, zeigt keine Ladeanzeige und keinen
Fehlerzustand: Szene mit Arne, Register-Buch, Reiter und Suche stehen da, das Register ist leer
("Noch keine Bot-Leads. Hier legt Arne Betriebe ohne Website ab, die Bea nicht fuer eine Mail
ausgewaehlt hat. Die Branchen-Raeume ... suchen noch nicht."), ohne Zahl am Reiter. Reiter,
Suche, Zuklappen und Alle anzeigen bleiben bedienbar und loesen nie einen Abruf aus. Ab Stufe 6
unveraendert (Live-Daten); der Test-Laeufer startet den Keller standardmaessig mit Stufe 6.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-22 12:23:03 +00:00
1 parent 6eb4dd3c51
commit 1ab760be0c
5 files changed
+161 -15

No files matched your search

+13 -5
View File
@@ -4,6 +4,7 @@ import KellerSzene from './keller/KellerSzene'
import RegisterBuch from './keller/RegisterBuch'
import { pageInfo } from '../../lib/botleadsView'
import { eintragModell } from '../../lib/hustlerKeller'
import { HUSTLER_STUFE, botleadsAbrufbar } from '../../lib/hustlerStufe'
import { formatCount } from '../../lib/workersView'
import { restoreFocus } from '../../lib/workersFocus'
@@ -14,15 +15,20 @@ const browserDocument = () => (typeof document === 'undefined' ? null : document
// Bildschirm „Keller“ (Hustler-Pixelwelt T10, Spec E12/E13; Dateiname bleibt BotLeadList): die Archiv-Szene mit Arne (keller/KellerSzene), davor Arnes Register-Buch
// (keller/RegisterBuch) mit den Betrieben, die nicht angeschrieben werden, samt Grund, und die Akte zu einem Eintrag (keller/Akte). Reine Liste, nichts wird gesendet
// oder angerufen. Props: api, bot, onClearBot, kpis (botLeads für die Zahl am Schild), workers (Hustler-Tabelle der Seite: Arnes Status), paused (Hustler angehalten:
// die Szene steht still, Klasse hk-steht), onBack (◂ Hochhaus). Beim Einhängen nimmt die Überschrift „Bot-Leads“ (tabIndex -1) den Fokus, ohne dass die Seite springt (T13 M2).
// oder angerufen. Props: api, stufe (Standard HUSTLER_STUFE), bot, onClearBot, kpis (botLeads für die Zahl am Schild), workers (Hustler-Tabelle der Seite: Arnes Status),
// paused (Hustler angehalten: die Szene steht still, Klasse hk-steht), onBack (◂ Hochhaus). Beim Einhängen nimmt die Überschrift „Bot-Leads“ (tabIndex -1) den Fokus,
// ohne dass die Seite springt (T13 M2).
// Stufen-Sperre (hustlerStufe.js): unterhalb von Stufe 6 (BOTLEADS_AB) gibt es GET /botleads im Dienst noch nicht. Der Keller steht dann trotzdem da (Arne, Regale, Register-Buch,
// Reiter, Suche), das Register ist leer und sagt ehrlich warum (RegisterBuch, ohneDienst); es gibt keinen Abruf (auch kein JWT), keine Ladeanzeige, keinen Fehler. Reiter und Suche
// bleiben bedienbar und ändern nur die Anzeige (es gibt nichts zu filtern); die Bedienung entspricht sonst dem Keller mit Daten, also ohne Fokusfalle.
// Die Liste holt sich ihre Daten bei jeder Änderung von Raum, Suche, Seite oder bot selbst; es gibt kein Polling. Nur die jüngste Anfrage zählt: eine späte
// Antwort (auch ein später Fehler) einer älteren Anfrage oder nach dem Abbau wird verworfen. Scheitert eine Anfrage, steht der Satz des Dienstes mit „Erneut versuchen“,
// das genau diese Anfrage (Raum, Suche, Seite oder Eintrag) noch einmal stellt; Raum und Suche gehen weiterhin.
// bot (aus ?bot= der Adresse, Link aus dem Ereignisstrom): zeigt genau diesen Eintrag, hervorgehoben, mit „Alle anzeigen“ (onClearBot nimmt bot aus der Adresse).
// Alle Texte der Zeilen sind Textknoten; Links entstehen nur aus row.sourceHref (botleadRow prüft die Adresse mit safeHref: nur http und https, keine Zugangsdaten), und zwar
// nur in der Akte. Kein CSS-Import (WorkersPage hängt die Stile ein), keine Bilder, kein HTML-Einschub.
export default function BotLeadList({ api, bot, onClearBot, kpis, workers, paused = false, onBack }) {
export default function BotLeadList({ api, stufe = HUSTLER_STUFE, bot, onClearBot, kpis, workers, paused = false, onBack }) {
const abruf = botleadsAbrufbar(stufe)
const botId = typeof bot === 'string' ? bot : ''
const single = botId !== ''
const [view, setView] = useState(START_VIEW)
@@ -45,6 +51,7 @@ export default function BotLeadList({ api, bot, onClearBot, kpis, workers, pause
const key = JSON.stringify(single ? [botId, attempt] : [view.room, view.q, view.page, attempt])
useEffect(() => {
if (!abruf) return undefined // Stufe unter 6: kein Abruf, das Register bleibt leer
seqRef.current += 1
const mine = seqRef.current
// Ein früheres Ergebnis zu genau diesem Schlüssel (hin und zurück gewechselt, bevor der andere Schlüssel antwortete) ist veraltet: erst die neue Antwort zählt
@@ -70,7 +77,7 @@ export default function BotLeadList({ api, bot, onClearBot, kpis, workers, pause
}
load()
return () => { seqRef.current += 1 } // eine noch offene Anfrage zählt nicht mehr
}, [api, key])
}, [api, key, abruf])
useEffect(() => () => window.clearTimeout(timerRef.current), [])
@@ -101,7 +108,7 @@ export default function BotLeadList({ api, bot, onClearBot, kpis, workers, pause
timerRef.current = window.setTimeout(() => setView((v) => ({ ...v, q: value.trim(), page: 1 })), SEARCH_DELAY_MS)
}
const loading = result.key !== key
const loading = abruf && result.key !== key // ohne Abruf gibt es nichts zu laden
// Die Einzelansicht ist weg (die Seite hat bot aus der Adresse genommen): der Raum-Reiter ersetzt den gedrückten Knopf und bekommt den Fokus.
// Ohne den Knopf (Zurück-Taste des Browsers) bleibt der Fokus, wo er ist.
@@ -159,6 +166,7 @@ export default function BotLeadList({ api, bot, onClearBot, kpis, workers, pause
draft={draft}
filtered={filtered}
laden={loading}
ohneDienst={!abruf}
fehler={result.error}
zeilen={zeilen}
total={result.total}
+16 -7
View File
@@ -1,12 +1,18 @@
// Das Register-Buch des Kellers (Hustler-Pixelwelt T10, Spec E12/E13): Raum-Reiter, das aufgeschlagene Buch mit Titel, Hinweis, Suchfeld (oder, bei einem einzelnen
// Eintrag, „Alle anzeigen“), Kopfzeile, den Einträgen der Dienst-Seite (im Buch scrollbar), Leer-, Lade- und Fehlerzustand und der Seitenzeile mit ◂ ▸.
// Aufbau und Klassen wie keller/entwurf/BotLeads.dc.html mit hk- davor; die Inline-Stile der Vorlage stehen als Klassen in styles/hustler/hustler-keller-ui.css.
// ohneDienst (Stufe unter 6, der Dienst kennt GET /botleads noch nicht): das Register ist leer und sagt ehrlich warum; weder Fehler noch Ladeanzeige noch Zahl am Reiter.
// Rein darstellend: Abruf, Suche, Seiten und Fokus gehören BotLeadList. Alle Texte der Zeilen sind Textknoten; Links gibt es hier nicht (der Link zur Quelle steht in der
// Akte und kommt nur aus row.sourceHref). Kein CSS-Import, keine Bilder, kein HTML-Einschub.
import { REITER } from '../../../lib/hustlerKeller'
import { formatCount } from '../../../lib/workersView'
const SEARCH_MAX = 100 // der Dienst kürzt die Suche auf 100 Zeichen
// Leer-Text, solange der Dienst keine Bot-Leads liefert (Stufe unter 6): Bot-Leads entstehen erst, wenn die Branchen-Räume (P6) suchen
const LEER_OHNE_DIENST = [
'Noch keine Bot-Leads.',
'Hier legt Arne Betriebe ohne Website ab, die Bea nicht für eine Mail ausgewählt hat. Die Branchen-Räume (Ärzte, Handwerk, Selbstständige, Läden) suchen noch nicht.',
]
// Eine Zeile des Buchs: ein Knopf, der die Akte öffnet. Spalten: Raum-Symbol, Name (+ „gekürzt“) mit Ort und Funddatum, Punkte, Grund mit Symbol, Telefon.
function Zeile({ z, nr, aktuell, onOpen }) {
@@ -42,11 +48,12 @@ function Zeile({ z, nr, aktuell, onOpen }) {
// Props:
// einzeln (Einzelansicht über ?bot=), botId, room (gewählter Raum-Schlüssel), draft (Inhalt des Suchfelds), filtered (Raum oder Suche gesetzt)
// ohneDienst (der Dienst liefert noch keine Bot-Leads: ehrlicher Leer-Text)
// laden, fehler (Satz des Dienstes oder null), zeilen (eintragModell je Eintrag der Seite), total, info (pageInfo)
// onRoom(value), onSearch(ev), onClear(), onPage('prev' | 'next'), onRetry(), onOpen(nr, ev)
// reiterRef (am aktiven Reiter), zurueckRef, weiterRef (Blätterknöpfe): Fokus-Ziele von BotLeadList
export default function RegisterBuch({ einzeln, botId, room, draft, filtered, laden, fehler, zeilen, total, info, onRoom, onSearch, onClear, onPage, onRetry, onOpen, reiterRef, zurueckRef, weiterRef }) {
const zahl = laden || fehler ? null : total // die Zahl steht nur am aktiven Reiter und erst, wenn die Antwort da ist
export default function RegisterBuch({ einzeln, botId, room, draft, filtered, ohneDienst = false, laden, fehler, zeilen, total, info, onRoom, onSearch, onClear, onPage, onRetry, onOpen, reiterRef, zurueckRef, weiterRef }) {
const zahl = laden || fehler || ohneDienst ? null : total // die Zahl steht nur am aktiven Reiter und erst, wenn die Antwort da ist (ohne Dienst gibt es keine)
let koerper
if (laden) {
@@ -59,11 +66,13 @@ export default function RegisterBuch({ einzeln, botId, room, draft, filtered, la
</div>
)
} else if (zeilen.length === 0) {
const leer = einzeln
? ['Dieser Eintrag ist nicht (mehr) in der Liste.', 'Er wurde gekürzt oder ist kein Bot-Lead.']
: filtered
? ['Keine Treffer.', 'Für diesen Raum und diese Suche gibt es keine Bot-Leads.']
: ['Noch keine Bot-Leads.', 'Betriebe, die nicht angeschrieben werden, erscheinen hier mit ihrem Grund.']
const leer = ohneDienst
? LEER_OHNE_DIENST
: einzeln
? ['Dieser Eintrag ist nicht (mehr) in der Liste.', 'Er wurde gekürzt oder ist kein Bot-Lead.']
: filtered
? ['Keine Treffer.', 'Für diesen Raum und diese Suche gibt es keine Bot-Leads.']
: ['Noch keine Bot-Leads.', 'Betriebe, die nicht angeschrieben werden, erscheinen hier mit ihrem Grund.']
koerper = (
<div className="hk-kl-leer">
<p className="hk-kl-leer-titel">{leer[0]}</p>