diff --git a/src/components/workers/PultList.jsx b/src/components/workers/PultList.jsx index 61cce72..9fc8a89 100644 --- a/src/components/workers/PultList.jsx +++ b/src/components/workers/PultList.jsx @@ -1,439 +1,87 @@ +// „Dein Pult“ (Hustler-Pixelwelt T9, Spec E11/E13/E14): der Bildschirm Freigabe. Die Szene (pult/PultSzene.jsx) füllt den Raum, davor liegt das Klemmbrett +// (pult/Klemmbrett.jsx) mit den Umschlägen der Einträge, die ein Mensch absendet, übernimmt oder abschließt; ein Klick auf einen Umschlag öffnet den Brief +// (pult/Brief.jsx) mit den bisherigen Aktionen. Nichts hier versendet selbst etwas: „Kopieren & öffnen“ bringt die Antwort in die Zwischenablage und öffnet die +// Anzeige, abgeschickt wird dort von Hand. Aufbau und Klassen wie Freigabe.dc.html (Zeichenfläche) mit hk- davor; Wurzel .hk-pult. +// Die Datei bleibt PultList.jsx (Name und Default-Export wie bisher), sie hält Zustand und Fluss: Reiter, bearbeitete Texte und Feldtexte je Eintrag (sie überleben +// Schließen des Briefs, Filterwechsel und Neuladen), die Meldung nach einem Abschluss samt Fokus, den offenen Brief, das Interessenten-Panel, „Pult zuklappen“. +// +// Props: api (Dienst-API), me = { id, name } des Angemeldeten, kpis (Kennzahlen aus dem Zustand: pultOpen steuert die Zahl am Reiter „Wartet“, im Kopf und die Boten- +// Schleife), onBack() („◂ Hochhaus“). workers (Hustler-Tabelle) reicht die Seite durch, das Pult braucht sie nicht: ein Pult-Eintrag nennt keinen Hustler. +// Kein CSS-Import und keine Bilder (die Stile hängt WorkersPage ein), Fremdtext nur als Textknoten. import { useCallback, useEffect, useRef, useState } from 'react' -import Badge from '../ui/Badge' -import Button from '../ui/Button' -import EmptyState from '../ui/EmptyState' -import { copyTextChecked } from '../CopyableCredential' import { useEmployees } from '../../hooks/useEmployees' import { usePultFeed } from '../../hooks/usePultFeed' +import { reiterZahl, wartetZahl, botenLaufen } from '../../lib/hustlerPult' import { activeControl, restoreFocus } from '../../lib/workersFocus' -import { - ACTION_FAILED, PULT_FILTERS, REPLY_TEXT_MAX, actionErrorText, charCount, copyAndOpen, mailtoHref, pasteDoneText, rowModel, validateReplyText, -} from '../../lib/workersPult' +import { PULT_FILTERS, rowModel } from '../../lib/workersPult' +import Brief from './pult/Brief' +import Klemmbrett from './pult/Klemmbrett' +import PultSzene from './pult/PultSzene' import ProspectPanel from './ProspectPanel' -const CONFIRM_MS = 4000 // so lange gilt die Rückfrage „Wirklich verwerfen?“, „Wirklich freigeben?“ bzw. „Wirklich bauen?“ nach dem ersten Klick -// Kosten und Wirkung von „Vorschau bauen“ (Rückfrage und Hinweis am Knopf); bewusst ohne „nur wenn der Interessent Ja gesagt hat“: der Knopf gilt gerade für unklare Antworten -const BUILD_HINT = 'Startet einen Opus-Lauf und baut die Vorschau, als hätte der Interessent Ja gesagt.' const NO_FIELDS = Object.freeze({}) // Eintrag ohne bearbeitete Felder // Das document des Browsers; unter Node (Tests, Server) gibt es keins, dann bleibt der Fokus unangetastet const browserDocument = () => (typeof document === 'undefined' ? null : document) -// Selektor des Textfelds eines aufgeklappten Feldes (Antwort oder Einfügen) -const PANEL_FIELD = '.wk-pult-reply textarea, .wk-pult-paste textarea' -// Leerzustand je Filter -const EMPTY = { - offen: { title: 'Nichts wartet auf dich', hint: 'Neue Antworten erscheinen hier, sobald ein Entwurf bereit ist.' }, - veraltet: { title: 'Nichts ist veraltet', hint: 'Einträge, die zu lange liegen, landen hier und lassen sich weiter bearbeiten.' }, - erledigt: { title: 'Noch nichts erledigt', hint: 'Gesendete, verworfene und erledigte Einträge stehen hier.' }, -} - -// Neuer Tab ohne Rückbezug auf diese Seite -const openTab = (url) => window.open(url, '_blank', 'noopener,noreferrer') - -// Meldung nach „Kopieren & öffnen“: ok nur, wenn alles geklappt hat, was die Zeile anbietet (ohne Anzeigen-Adresse gibt es nur Kopieren) -function copyNote(result, hasUrl) { - if (!hasUrl) { - return result.copied - ? { kind: 'ok', text: 'Kopiert.' } - : { kind: 'err', text: 'Kopieren hat nicht geklappt. Bitte den Text von Hand markieren und kopieren.' } - } - if (result.copied && result.opened) return { kind: 'ok', text: 'Kopiert und geöffnet.' } - if (result.copied) return { kind: 'err', text: 'Kopiert, aber die Anzeige ließ sich nicht öffnen. Ein Pop-up-Blocker kann der Grund sein.' } - if (result.opened) return { kind: 'err', text: 'Geöffnet, aber das Kopieren hat nicht geklappt. Bitte den Text von Hand kopieren.' } - return { kind: 'err', text: 'Kopieren und Öffnen haben nicht geklappt.' } -} - -// Zugänglicher Name für ein Bedienelement, das in jeder Zeile wiederkehrt: das sichtbare Wort steht vorn (WCAG 2.5.3 „Beschriftung im -// Namen“), dahinter der Titel der Zeile, damit eine Liste aller „Details“-Knöpfe im Screenreader unterscheidbar ist. Ohne Titel -// (leer oder fehlend) gilt der schlichte Name, nie „… zu undefined“. -function rowName(label, title, joiner = ': ') { - const t = typeof title === 'string' ? title.trim() : '' - return t === '' ? label : `${label}${joiner}${t}` -} - -// Aufgeklapptes Feld unter den Aktionen: „Als Mensch antworten“ (.wk-pult-reply) und „Antwort des Kunden einfügen“ (.wk-pult-paste). -// Textfeld, Zähler (Zeichen wie der Dienst sie zählt, höchstens REPLY_TEXT_MAX) und zwei Knöpfe; die Prüfung macht die Zeile beim Absenden. -// Esc (aus dem ganzen Feld heraus, nicht nur aus dem Textfeld) und „Abbrechen“ schließen das Feld, der eingegebene Text bleibt in der Liste erhalten. -// Während einer Anfrage ist beides gesperrt: das Feld soll nicht mitten im Senden verschwinden. -function TextPanel({ className, label, hint, value, onChange, submitLabel, onSubmit, onClose, busy }) { - const count = charCount(value) - // preventDefault: die Handy-Leiste (App.jsx) schließt bei Esc nur, wenn das Ereignis nicht behandelt wurde - const onKeyDown = (e) => { - if (e.key !== 'Escape' || busy) return - e.preventDefault() - e.stopPropagation() - onClose() - } - return ( -
- {hint &&

{hint}

} -