Hustler-Stufe: P4-Aktionen im Pult erst ab Stufe 4 (rowModel-Parameter stufe, PultList reicht ihn durch)

Unterhalb von Stufe 4 sind canReply, canPaste, canApprovePreview und canStartBuild immer false: Antwort des Kunden einfügen, Als Mensch antworten / Jetzt senden, Freigeben & senden
und Vorschau bauen verschwinden samt Feldern und Rückfragen, die Aktionsgruppe schließt sich ohne Lücke. P3-Aktionen (übernehmen, Kopieren & öffnen, als gesendet markieren, verwerfen,
erledigt, E-Mail schreiben) bleiben. Bestehende P4-Tests laufen ausdrücklich auf Stufe 6; neue Tests prüfen Stufe 3 gerendert und bedient (Reihenfolge der Knöpfe, kein Feld, Esc, Verwerfen mit Rückfrage).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-22 10:36:44 +00:00
1 parent 7cd74a23d2
commit 5e48fbf264
5 files changed
+207 -15

No files matched your search

+5 -3
View File
@@ -7,7 +7,8 @@
//
// 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), paused (Hustler angehalten: die Szene steht still, Klasse hk-steht), onBack() („◂ Hochhaus“). workers (Hustler-Tabelle) reicht die Seite durch, das Pult
// braucht sie nicht: ein Pult-Eintrag nennt keinen Hustler.
// braucht sie nicht: ein Pult-Eintrag nennt keinen Hustler. stufe (Standard HUSTLER_STUFE, hustlerStufe.js) geht an rowModel: unterhalb der Antwort-Stufe (4) bietet kein
// Brief die P4-Aktionen an (Antwort einfügen, als Mensch antworten, Vorschau freigeben, Vorschau bauen).
// Laufende Aktionen (T13 P1): welche Einträge gerade eine Anfrage an den Dienst laufen haben, merkt PultList (laufRef, nach Eintrags-Id), nicht der Brief: wer den Brief
// während der Anfrage schließt und denselben Eintrag wieder öffnet, findet alle Aktionsknöpfe gesperrt und kann keine zweite Anfrage auslösen (sonst fügte „Antwort
// einfügen“ doppelt ein); sobald die Anfrage endet, sind sie frei.
@@ -17,6 +18,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
import { useEmployees } from '../../hooks/useEmployees'
import { usePultFeed } from '../../hooks/usePultFeed'
import { reiterZahl, wartetZahl, botenLaufen } from '../../lib/hustlerPult'
import { HUSTLER_STUFE } from '../../lib/hustlerStufe'
import { activeControl, restoreFocus } from '../../lib/workersFocus'
import { PULT_FILTERS, rowModel } from '../../lib/workersPult'
import Brief from './pult/Brief'
@@ -29,7 +31,7 @@ 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)
export default function PultList({ api, me, kpis, paused = false, onBack }) {
export default function PultList({ api, me, kpis, paused = false, onBack, stufe = HUSTLER_STUFE }) {
const [filterId, setFilterId] = useState('offen')
const [drafts, setDrafts] = useState({}) // bearbeitete Texte je Eintrag; was fehlt, zeigt den Entwurf des Dienstes
const [fields, setFields] = useState({}) // Texte der Felder „Als Mensch antworten“ (reply) und „Antwort einfügen“ (paste) je Eintrag
@@ -123,7 +125,7 @@ export default function PultList({ api, me, kpis, paused = false, onBack }) {
return true
}, [])
const rows = items.map((item) => ({ item, row: rowModel(item, { me, nowMs, employees }) }))
const rows = items.map((item) => ({ item, row: rowModel(item, { me, nowMs, employees, stufe }) }))
const offen = briefId === null ? null : rows.find(({ row }) => row.id === briefId) ?? null
// Ein Brief, dessen Eintrag nicht mehr in der Liste steht (ein Kollege war schneller), schließt sich: auf einem alten Stand wird nichts mehr gesendet
useEffect(() => {
+9 -5
View File
@@ -4,6 +4,7 @@
// Plattform-Antworten schickt der Mensch selbst ab (Plattform) und markiert sie danach als gesendet; Mail-Antworten und die Freigabe
// einer Vorschau gehen über den Dienst (api.pultAction), der sie durch das Sende-Tor führt.
import { HUSTLER_STUFE, antwortenOffen } from './hustlerStufe'
import { DOWN_TEXT, SLOW_PULT_ACTIONS, UNCERTAIN_STATUS } from './workersApi'
import { roleOf } from './workersRoster'
@@ -179,8 +180,10 @@ function doneByPhrase(by, employees) {
* Zeilenmodell eines Pult-Eintrags (GET /pult: Eintrag plus `prospect`). me = { id, name } des Angemeldeten, employees = Mitarbeiterliste.
* Rechte nach Status (offen, in_arbeit, veraltet sind bearbeitbar) und Art (nur Antwort-Arten sind sendbar); die Zeile sperrt
* nichts wegen eines Kollegen, der übernommen hat: der Dienst entscheidet (409 mit deutschem Satz).
* stufe (Standard HUSTLER_STUFE, hustlerStufe.js): unterhalb der Antwort-Stufe (4, Dienst P4) sind die vier P4-Aktionen canReply, canPaste, canApprovePreview und
* canStartBuild immer false, denn deren Endpunkte gibt es im Dienst noch nicht; die P3-Aktionen (übernehmen, gesendet, verwerfen, erledigt, Mail-Link) bleiben.
*/
export function rowModel(item, { me, nowMs, employees }) {
export function rowModel(item, { me, nowMs, employees, stufe = HUSTLER_STUFE }) {
const editable = BEARBEITBAR.includes(item.status)
const sendable = SENDABLE_KINDS.includes(item.kind)
const prospect = item.prospect ?? null
@@ -197,6 +200,7 @@ export function rowModel(item, { me, nowMs, employees }) {
// P4: Antworten, Freigeben und Bauen nur an einem bearbeitbaren Eintrag, den kein anderer hält (Einfügen kennt diese Sperre nicht, siehe canPaste)
const free = editable && !claimedByOther
const freigabe = item.kind === 'vorschau_freigabe'
const p4 = antwortenOffen(stufe) // ohne P4 im Dienst bietet die Zeile keine der vier neuen Aktionen an
const draft = typeof item.draft === 'string' ? item.draft : ''
let mail = ''
@@ -228,14 +232,14 @@ export function rowModel(item, { me, nowMs, employees }) {
// Erledigt würde eine Vorschau-Freigabe schließen, ohne dass der Link hinausgeht: dort bleibt nur Freigeben oder Verwerfen (D20)
canDone: !sendable && editable && !freigabe,
// Mail-Antwort als Mensch: braucht einen Interessenten mit Adresse („Mail ohne Faden“ und Plattform-Interessenten haben keinen Mail-Weg)
canReply: REPLY_KINDS.includes(item.kind) && free && prospect !== null && filled(prospect.email),
canApprovePreview: freigabe && free,
canReply: p4 && REPLY_KINDS.includes(item.kind) && free && prospect !== null && filled(prospect.email),
canApprovePreview: p4 && freigabe && free,
// Bau starten wie ein „Ja“: Eintrag „unsicher“ mit Interessent. Ein toter Interessent (gesperrt, verloren, Deal) bekommt den Knopf nicht; ohne
// Kurzfassung (prospect null) oder ohne Status entscheidet der Dienst (409 mit deutschem Satz, auch bei einer schon vorhandenen Vorschau)
canStartBuild: item.kind === 'unsicher' && free && filled(item.prospectId) && !(prospect !== null && DEAD_PROSPECT.includes(prospect.status)),
canStartBuild: p4 && item.kind === 'unsicher' && free && filled(item.prospectId) && !(prospect !== null && DEAD_PROSPECT.includes(prospect.status)),
// Die Rückmeldung des Kunden kommt meist erst, nachdem jemand Claras Entwurf auf der Plattform gesendet hat: auch bei gesendet,
// erledigt oder von einem Kollegen übernommen. Nur ein verworfener Eintrag nimmt nichts mehr an.
canPaste: PASTE_KINDS.includes(item.kind) && item.status !== 'verworfen' && prospect !== null,
canPaste: p4 && PASTE_KINDS.includes(item.kind) && item.status !== 'verworfen' && prospect !== null,
claimedByMe,
claimedByOther,
claimedByLabel,