Aufgaben verteilen: der Knopf ist nie mehr tot, und die Bewerbung steht im Formular

screen1 -- „dogfather, die rechte hand und die linke hand sollen immer
noch aufgaben werden teilen können plus die option dass die modis sich
für aufgaben bewerben können. weil gerade hängt das und weiß nicht
wieso."

WAS WIRKLICH LOS WAR -- gemessen, nicht geraten

Auf entwicklung.html stand oben „Aufgabe verteilen · Wähl LINKS
jemanden aus", und der Knopf war so lange abgeschaltet. Im Browser
nachgemessen (1420 px, als DogFather):

    Band          oben 284 px, linke Kante 130 px
    Personenwahl  oben 490 px, linke Kante 130 px
    -> UNTER dem Band, 127 px darunter, gleiche linke Kante

Links war nichts. Wer der Anweisung folgte, schaute auf eine leere
Fläche und hatte einen Knopf, der nicht ging. Das Recht selbst war die
ganze Zeit richtig: `darfAufgabenVerteilen` = Leitung oder Hand, also
DogFather, rechte Hand UND linke Hand -- alle drei mit
`darf_verteilen: true` gemessen.

Kurz: Die Regel stimmte, der Weg dorthin nicht.

WAS JETZT ANDERS IST

1. DER KNOPF IST IMMER BENUTZBAR. Er öffnet das Formular, auch ohne
   vorherige Auswahl.
2. DIE FRAGE „für wen" STEHT IM FORMULAR, als erste Zeile, mit allen
   Personen zur Wahl. Wer unten eine Kachel angeklickt hat, findet sie
   angehakt wieder -- beide Wege führen zum selben Ziel.
3. EINE LISTE STATT ZWEIER. „Noch jemanden dazunehmen" ist weg. Es
   waren zwei Bedienungen für dieselbe Frage -- und die
   Bewerbungs-Option steckte ausgerechnet in der zugeklappten
   zweiten: Sie war nur zu finden, wenn man erst aufklappte UND dort
   jemanden ankreuzte. Eine Möglichkeit, die man nicht findet, gibt es
   nicht.
4. DIE BEWERBUNG STEHT DA, sobald zwei Leute angehakt sind: „Wer
   zuerst Zeit hat, übernimmt. Sie bewerben sich, du nimmst an oder
   lehnst ab."

ZWEI FEHLER, DIE DABEI AUFFIELEN

* Nach dem Anlegen wurde `fuerPerson` auf null gesetzt und DANACH
  `aufgabenZeigen(fuerPerson, …)` aufgerufen -- die Liste, die zeigen
  soll, was gerade entstanden ist, blendete sich damit aus. Die Namen
  werden jetzt festgehalten, bevor `reset()` sie abräumt.
* Die Bestätigung sagte immer „steht jetzt bei ihm" -- `fuerName` wird
  nur beim Klick auf eine Kachel gefüllt. Sie sagt jetzt, was wirklich
  passiert ist, und bei einem Pool, dass eine Bewerbung kommt.
* `e-mehr-art` heisst `e-art`: Der Name sagte „gehört zu ‚noch
  jemand'", und das gibt es nicht mehr. Dieselbe Sorte Falle wie
  `tperson__namen` heute Nachmittag.

DIE PRÜFUNG HATTE MEINEN FEHLER FESTGESCHRIEBEN

`pruef-bewerbung-aufgaben.mjs` enthielt wörtlich:

    ok(vorher.aus === true,
      "und ist aus, solange niemand gewaehlt ist");

Sie lief, sie war grün, und sie hat den toten Knopf verteidigt. Das
ist die unangenehmste Sorte: nicht übersprungen, nicht kaputt --
sondern eine Bestätigung meines Entwurfs statt der Sache. Jetzt wird
das Gegenteil verlangt.

Dazu zwei neue Abschnitte (84 -> 101 Prüfungen, 0 Fehler):
  6b  verteilen OHNE vorherige Auswahl, als Pool, samt Nachweis am
      Server (verteilart=pool, zwei Leute darin) und der Meldung, die
      nicht „bei ihm" sagen darf
  6c  alle drei Rollen am Bildschirm: DogFather, rechte Hand, linke
      Hand -- mit Gegenprobe, dass ein Modi das Band NICHT sieht

EIGENER MESSFEHLER, offen notiert: Mein erster Versuch, die Bewerbung
nachzustellen, meldete „kein Abruf, nichts passiert". Der Knopf öffnet
eine Rückfrage, und die hatte ich nicht beantwortet. Die Bewerbung war
nie kaputt -- meine Messung war es. Erst der zweite Anlauf zeigte den
ganzen Weg: POST /api/aufgaben/1/bewerben -> {"ok":true,"zustand":"beworben"}.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-22 20:18:35 +02:00
co-authored by Claude Opus 5
parent ba0e326585
commit 803750edbc
41 changed files with 963 additions and 663 deletions
+148 -48
View File
@@ -358,21 +358,35 @@
fuerPerson = personId || null;
fuerName = name || null;
/* DER KNOPF IST NIE AUS (22.09.2026 abends).
Vorher: Ohne vorherige Auswahl war er abgeschaltet, und
daneben stand „Wähl links jemanden aus". Gemessen steht die
Auswahl aber gar nicht links, sondern 206 px WEITER UNTEN, bei
derselben linken Kante. Wer der Anweisung folgte, schaute auf
eine leere Fläche -- und hatte einen Knopf, der nicht ging.
Filipe: „weil gerade hängt das und weiß nicht wieso."
Ein toter Knopf mit einer falschen Erklärung daneben ist die
schlechteste Kombination: Man sucht den Fehler bei sich. Die
Auswahl steht jetzt IM Formular; der Knopf öffnet es immer.
Wer unten schon jemanden angeklickt hat, findet ihn angehakt
wieder. */
knopf.disabled = false;
if (personId) {
knopf.textContent = name ? `Aufgabe für ${name}` : 'Aufgabe anlegen';
knopf.disabled = false;
satz.textContent = name
? `Sie landet direkt bei ${name} — mit einer Meldung.`
: 'Sie landet direkt bei der gewählten Person.';
} else {
knopf.textContent = 'Aufgabe anlegen';
/* OHNE PERSON GIBT ES NICHTS ZU VERTEILEN. Der Knopf bleibt
sichtbar (sonst springt die Seite beim Auswaehlen), aber
abgeschaltet -- und der Satz sagt, was fehlt. */
knopf.disabled = true;
satz.textContent = 'Wähl links jemanden aus — dann legst du die Aufgabe direkt für ihn an.';
formularZu();
satz.textContent = 'Aufgabe anlegen und an eine oder mehrere Personen geben.';
}
/* Steht das Formular schon offen, zieht die Auswahl darin mit --
sonst haette man unten jemanden angeklickt und oben stuende
weiterhin der alte Haken. */
if (!$('e-neu')?.hidden) fuerLeuteBauen();
}
/* =====================================================================
@@ -402,42 +416,87 @@
if (auf) auf.setAttribute('aria-expanded', 'false');
}
function formularAuf() {
const f = $('e-neu');
if (!f || !fuerPerson) return;
f.hidden = false;
$('verteilen-knopf').setAttribute('aria-expanded', 'true');
$('e-neu-fehler').textContent = '';
/* DIE ANDEREN ZUR AUSWAHL -- ohne die schon gewaehlte Person.
Sie zweimal anzubieten hiesse, man koennte sie doppelt
eintragen; der Server wuerde es abfangen, aber die Frage
haette gar nicht erst gestellt werden duerfen. */
const ziel = $('e-mehr-leute');
/* =====================================================================
DIE EINE PERSONENLISTE
Sie ersetzt zwei: „Für wen?" (oben, vorher gar nicht im Formular)
und „Noch jemanden dazunehmen" (unten, zugeklappt). Zwei Listen
für dieselbe Frage waren zwei Bedienungen -- und die
Bewerbungs-Option steckte ausgerechnet in der zugeklappten.
DIE REIHENFOLGE DER HAKEN ENTSCHEIDET, WER VORN STEHT: Der erste
Angehakte ist die Hauptperson, die weiteren kommen dazu. Das
steht hier an einer Stelle, damit `fuerPerson` und `mehrLeute`
nicht auseinanderlaufen können -- sie werden beide aus derselben
Liste abgeleitet und nirgends sonst gesetzt.
===================================================================== */
function auswahlLesen() {
const ziel = $('e-fuer-leute');
if (!ziel) return;
const an = [...ziel.querySelectorAll('input:checked')].map((x) => Number(x.value));
fuerPerson = an.length ? an[0] : null;
mehrLeute = an.slice(1);
/* DIE ART FRAGT NUR, WER MEHR ALS EINEN EINTRAEGT. Bei einer
Person gibt es nichts zu verteilen -- die Frage waere
Fuellwerk. */
$('e-art').hidden = an.length < 2;
if (an.length < 2) {
const einzeln = $('e-art').querySelector('input[value="mehrere"]');
if (einzeln) einzeln.checked = true;
}
const knopf = $('verteilen-knopf');
const name = an.length
? (leute.find((x) => String(x.id) === String(fuerPerson))?.name || null) : null;
if (knopf) {
knopf.textContent = an.length > 1
? `Aufgabe für ${an.length} Personen`
: name ? `Aufgabe für ${name}` : 'Aufgabe anlegen';
}
const satz = $('verteilen-satz');
if (satz) {
satz.textContent = an.length > 1
? 'Sie geht an alle Angehakten — wie, sagst du gleich darunter.'
: name ? `Sie landet direkt bei ${name} — mit einer Meldung.`
: 'Aufgabe anlegen und an eine oder mehrere Personen geben.';
}
}
function fuerLeuteBauen() {
const ziel = $('e-fuer-leute');
if (!ziel) return;
/* WAS SCHON ANGEHAKT WAR, BLEIBT ANGEHAKT. Die Liste wird auch
neu gebaut, wenn man unten eine andere Person anklickt --
wer dabei seine Haken verliert, fängt von vorn an. */
const warAn = new Set([...ziel.querySelectorAll('input:checked')].map((x) => x.value));
if (fuerPerson !== null && fuerPerson !== undefined) warAn.add(String(fuerPerson));
ziel.replaceChildren();
mehrLeute = [];
for (const p2 of (leute || [])) {
if (String(p2.id) === String(fuerPerson)) continue;
const zeile = document.createElement('label');
zeile.className = 'z-wahl__zeile';
const k = document.createElement('input');
k.type = 'checkbox';
k.value = String(p2.id);
k.addEventListener('change', () => {
mehrLeute = [...ziel.querySelectorAll('input:checked')].map((x) => Number(x.value));
/* DIE ART FRAGT NUR, WER MEHR ALS EINEN EINTRAEGT. Bei einer
Person gibt es nichts zu verteilen -- die Frage waere
Fuellwerk. */
$('e-mehr-art').hidden = mehrLeute.length === 0;
});
k.checked = warAn.has(String(p2.id));
k.addEventListener('change', auswahlLesen);
const t = document.createElement('span');
t.textContent = p2.name || 'Jemand';
zeile.append(k, t);
ziel.append(zeile);
}
$('e-mehr-art').hidden = true;
$('e-mehr').hidden = true;
$('e-mehr-auf').setAttribute('aria-expanded', 'false');
$('e-titel').focus();
auswahlLesen();
}
function formularAuf() {
const f = $('e-neu');
if (!f) return;
f.hidden = false;
$('verteilen-knopf').setAttribute('aria-expanded', 'true');
$('e-neu-fehler').textContent = '';
fuerLeuteBauen();
/* OHNE AUSWAHL IST DIE ERSTE FRAGE „für wen" -- dorthin der
Blick. Steht sie schon fest, fehlt nur noch der Titel. */
if (fuerPerson) $('e-titel').focus();
else $('e-fuer-leute').querySelector('input')?.focus();
}
function aufgabenFormularAufbauen() {
@@ -450,20 +509,20 @@
});
$('e-neu-abbrechen').addEventListener('click', () => {
$('e-neu').reset();
mehrLeute = [];
/* ZURUECK AUF DIE KACHEL, NICHT AUF NICHTS. Wer abbricht, hat
unten meist trotzdem jemanden ausgewaehlt -- der Knopf soll
dann wieder „Aufgabe fuer X" heissen und nicht so tun, als
waere die Auswahl auch weg. Sie wird aus der Kachel GELESEN
und nicht daneben gemerkt: zwei Stellen, die sich dieselbe
Person merken, laufen auseinander. */
const kachel = document.querySelector('#personen .e-person[aria-pressed="true"]');
verteilBandZeigen(kachel ? Number(kachel.dataset.person) : null,
kachel ? (kachel.dataset.name || null) : null);
formularZu();
});
$('e-mehr-auf').addEventListener('click', () => {
const k = $('e-mehr');
k.hidden = !k.hidden;
$('e-mehr-auf').setAttribute('aria-expanded', String(!k.hidden));
$('e-mehr-auf').textContent = k.hidden
? 'Noch jemanden dazunehmen' : 'Doch nur eine Person';
if (k.hidden) {
for (const x of $('e-mehr-leute').querySelectorAll('input:checked')) x.checked = false;
mehrLeute = [];
$('e-mehr-art').hidden = true;
}
});
/* Der Klapp-Knopf „Noch jemanden dazunehmen" ist weg -- es gibt
nur noch eine Liste, und sie ist von Anfang an offen. */
$('e-neu').addEventListener('submit', async (ev) => {
ev.preventDefault();
@@ -476,14 +535,18 @@
return;
}
if (!fuerPerson) {
melder.textContent = 'Wähl links erst jemanden aus.';
/* KEIN „links" MEHR. Dort steht nichts -- gemessen. Der
Hinweis zeigt jetzt auf die Zeile, die im selben Formular
direkt darüber steht. */
melder.textContent = 'Hak oben an, für wen die Aufgabe ist.';
$('e-fuer-leute')?.querySelector('input')?.focus();
return;
}
const knopf = $('e-neu-speichern');
knopf.disabled = true;
try {
const art = mehrLeute.length
? ($('e-mehr-art').querySelector('input[name="e-verteilart"]:checked')?.value || 'mehrere')
? ($('e-art').querySelector('input[name="e-verteilart"]:checked')?.value || 'mehrere')
: 'einzeln';
const a = await fetch('/workspace/api/aufgaben', {
method: 'POST',
@@ -511,16 +574,53 @@
oder die Art aendern -- wer das verschweigt, laesst jemanden
in dem Glauben, er habe drei Leute eingetragen. */
const gesetzt = r?.zuteilung?.gesetzt;
const alleIds = [Number(fuerPerson), ...mehrLeute];
const fehlt = Array.isArray(gesetzt)
? [Number(fuerPerson), ...mehrLeute].filter((x) => !gesetzt.includes(x)).length
? alleIds.filter((x) => !gesetzt.includes(x)).length
: 0;
/* ERST MERKEN, DANN AUFRAEUMEN (22.09.2026 abends).
Hier wurde `fuerPerson` auf null gesetzt und danach
`aufgabenZeigen(fuerPerson, …)` aufgerufen -- die Liste,
die gerade zeigen soll, was eben entstanden ist, wurde
damit ausgeblendet. Und die Meldung las `fuerName`, das
nur beim Klick auf eine Kachel gefuellt wird; wer im
Formular ankreuzte, bekam „steht jetzt bei ihm".
Die Namen kommen jetzt aus derselben Liste wie die Haken
und werden festgehalten, BEVOR `reset()` sie abraeumt. */
const namenVon = (id) =>
(leute.find((x) => String(x.id) === String(id))?.name || null);
const ersterName = fuerName || namenVon(fuerPerson);
const wieViele = alleIds.length;
const zeigeFuer = Number(fuerPerson);
const zeigeName = ersterName;
const istPool = art === 'pool';
$('e-neu').reset();
mehrLeute = [];
fuerPerson = null;
formularZu();
/* DIE MELDUNG SAGT, WAS WIRKLICH PASSIERT IST -- nicht immer
denselben Satz. Bei einem Pool ist das Entscheidende, dass
jetzt eine Bewerbung kommt und nicht etwa schon jemand
dransitzt. */
melde(fehlt
? `Angelegt — aber ${fehlt} Person(en) konnten nicht eingetragen werden.`
: `„${titel}" steht jetzt bei ${fuerName || 'ihm'}.`, !fehlt);
await aufgabenZeigen(fuerPerson, fuerName);
: wieViele === 1
? `„${titel}" steht jetzt bei ${ersterName || 'der gewählten Person'}.`
: istPool
? `„${titel}" liegt für ${wieViele} Leute bereit — wer zuerst Zeit hat,`
+ ' bewirbt sich, und du entscheidest.'
: `„${titel}" steht jetzt bei ${wieViele} Leuten — jede hat ihren`
+ ' eigenen Stand.',
!fehlt);
/* UND DIE LISTE DARUNTER ZEIGT DIE ERSTE PERSON. Sie ist die,
die im Knopf stand; wer mehrere eingetragen hat, findet den
Rest ueber deren Kacheln. */
await aufgabenZeigen(zeigeFuer, zeigeName);
} catch {
melder.textContent = 'Das hat gerade nicht geklappt. Versuch es noch einmal.';
knopf.disabled = false;