screen1 + A5 + B6: „wer zuerst Zeit hat" gilt wieder, und Aufgaben entstehen dort, wo die Person steht

DREI SACHEN, EIN ZUSAMMENHANG.

--- screen1 --------------------------------------------------------
Filipe zu meinem eigenen Satz „wer zuerst Zeit hat — genau das geht ja
nicht mehr": „falls das nicht mehr geht mach das es geht wenn es wieder
geht ist alles gut."

Er hat recht, und ich hatte zu schnell aufgegeben. „Wer zuerst Zeit
hat" muss nicht heissen, dass man sich selbst bedient -- es kann
genauso heissen, dass die ERSTE BEWERBUNG zuerst drankommt. Damit gilt
beides: Die Leitung entscheidet (sein Wunsch von vorhin), und wer
schnell ist, hat den Vorteil (sein Wunsch von eben).

Die Bewerbungen werden jetzt nach EINGANG sortiert, nicht nach Namen
-- die Abfrage sortiert sonst alphabetisch, und dann haette nicht der
Schnelle den Vorteil, sondern Frida. Der Erste bekommt die Marke
„zuerst da" (nur bei mehr als einer -- sonst ist es keine Auskunft,
sondern Fuellwerk).

Geprueft mit Absicht gegen das Alphabet: Nele bewirbt sich zuerst und
steht vorn, obwohl Frida im Alphabet vor ihr kaeme.

--- B6: der Knopf auf dem Aufgabenbrett ----------------------------
Filipe: „dieser button kann da jetzt doch endlich verschwinden, auf
dieser seite sollen ja keine aufgaben mehr verteilt werden."

ER HAT DABEI AUF DEN TEAM-DOGI-BILDSCHIRM GESEHEN, und das ist der
Unterschied zwischen „weg damit" und „weg damit, aber gemessen":

    Rolle/Haus        aufgaben.html  entwicklung.html  darfAnlegen
    manager/agentur   ja             NEIN              ja
    creator/agentur   ja             NEIN              ja
    scout/agentur     ja             NEIN              ja
    spicy/agentur     ja             NEIN              ja

Haette ich den Knopf einfach entfernt, koennten vier Rollen gar keine
Aufgabe mehr anlegen. Der Server nennt deshalb den ORT, und er leitet
ihn aus den KACHELN der Person ab -- nicht aus dem Seitenrecht (dann
haette DogFather auf der Agenturadresse den Knopf verloren, denn
oeffnen darf er die Seite, nur hat er dort keine Kachel dorthin) und
schon gar nicht aus dem Haus.

--- A5: anlegen, wo die Person steht -------------------------------
Filipe: „dieser buttion da soll nicht einen zu der seite aufgaben
fuehren sondern da in dieser seite die aufgaben erstellen und vergeben
koennen. plus man soll die aufgaben hier in dieser seite auch sehen."

Der Knopf war ein Link auf `aufgaben.html?neu=1`. Jetzt oeffnet er ein
Formular an Ort und Stelle. DREI FELDER, NICHT ZWOELF: Wer hier steht,
hat die Person schon gewaehlt; was fehlt, ist was, bis wann und ob
noch jemand mitmacht. Das grosse Formular auf dem Brett bleibt fuer
den Fall, dass man eine Aufgabe fuer irgendwen irgendwo anlegt -- zwei
vollstaendige Masken fuer dieselbe Sache waeren zwei Gelegenheiten,
eine davon zu vergessen.

Dazu die Aufgaben der gewaehlten Person, auf derselben Seite. Sie
kommen beim Auswaehlen und nicht auf Knopfdruck: Wer jemanden
durchgeht, will wissen, was bei ihm liegt.

Ohne Person ist der Knopf AUS statt weg -- sonst springt die Seite beim
Auswaehlen -- und der Satz daneben sagt, was fehlt.

Gemeldet wird, was der Server WIRKLICH gesetzt hat: `zuteilen` kann
eine Person weglassen (gesperrter Zugang). Wer das verschweigt, laesst
jemanden im Glauben, er habe drei Leute eingetragen.

--- EIN FUND AUF DEM EIGENEN BILDSCHIRMFOTO ------------------------
Die Bestaetigung „Clips vom Samstag schneiden steht jetzt bei Rieke."
stand in WARNROT -- `melde` schreibt immer in denselben Absatz, und
der heisst `.fehler`. Nicht schlimm und genau deshalb heimtueckisch:
Wer Bestaetigungen in Rot liest, sucht den Fehler, und wer sich daran
gewoehnt, ueberliest die echte Warnung. `melde` kennt jetzt eine gute
Nachricht; die Vorgabe bleibt „Fehler", damit die neun vorhandenen
Aufrufe nicht stillschweigend umgefaerbt werden.

GEPRUEFT
pruef-bewerbung-aufgaben 84/0 (von 66) -- neu: die Reihenfolge nach
Eingang mit Gegenprobe gegen das Alphabet, und ein Abschnitt, der A5
und B6 am echten Bildschirm durchspielt (Knopf weg auf dem Brett, kein
Link mehr auf der Entwicklungsseite, Formular oeffnet dort, Aufgabe
steht sofort in der Liste darunter UND wirklich in der Liste des
Servers, Bestaetigung als gute Nachricht gekennzeichnet).

Gruen: pruef-entwicklung, pruef-aufgabenbrett, pruef-css-klassen,
pruef-formulare, pruef-struktur.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-09-22 19:09:16 +02:00
co-authored by Claude Opus 5
parent 43545e4acd
commit 3b76a9aab5
47 changed files with 1324 additions and 589 deletions
+283 -6
View File
@@ -23,7 +23,26 @@
if (text !== undefined) n.textContent = text;
return n;
};
const melde = (t) => { const f = $('fehler'); if (f) { f.textContent = t; f.hidden = !t; } };
/* EINE GUTE NACHRICHT DARF NICHT AUSSEHEN WIE EIN FEHLER
(22.09.2026). Auf dem ersten Bildschirmfoto der neuen
Aufgabenanlage stand „Clips vom Samstag schneiden steht jetzt bei
Rieke." in Warnrot -- weil `melde` immer in denselben Absatz
schreibt und der `.fehler` heisst.
Es ist nicht schlimm und genau deshalb heimtueckisch: Wer eine
Bestaetigung in Rot liest, sucht den Fehler. Und wer sich daran
gewoehnt, ueberliest den echten.
Die Vorgabe bleibt „Fehler" -- alle neun bisherigen Aufrufe meinen
das, und ein Wechsel per Vorgabe haette sie alle stillschweigend
umgefaerbt. */
const melde = (t, gut) => {
const f = $('fehler');
if (!f) return;
f.textContent = t;
f.hidden = !t;
f.dataset.gut = gut ? 'ja' : 'nein';
};
const datum = (iso) => {
if (!iso) return '';
@@ -332,19 +351,266 @@
if (!darf) return;
const knopf = $('verteilen-knopf');
const satz = $('verteilen-satz');
/* WER UND WIE HEISST ER -- fuer das Formular darunter. Es steht
hier und nicht im Formular selbst: Die Auswahl geschieht
oberhalb, und zwei Stellen, die sich dieselbe Person merken,
laufen auseinander. */
fuerPerson = personId || null;
fuerName = name || null;
if (personId) {
knopf.href = `aufgaben.html?neu=1&fuer=${encodeURIComponent(personId)}`;
knopf.textContent = name ? `Aufgabe für ${name}` : 'Aufgabe anlegen';
knopf.disabled = false;
satz.textContent = name
? `Die Aufgabe landet direkt bei ${name} — mit einer Meldung.`
: 'Die Aufgabe landet direkt bei der gewählten Person.';
? `Sie landet direkt bei ${name} — mit einer Meldung.`
: 'Sie landet direkt bei der gewählten Person.';
} else {
knopf.href = 'aufgaben.html?neu=1';
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();
}
}
/* =====================================================================
AUFGABEN ANLEGEN -- HIER, NICHT AUF EINER ANDEREN SEITE (22.09.2026)
=====================================================================
Filipe: „dieser buttion da soll nicht einen zu der seite aufgaben
fuehren sondern da in dieser seite die aufgaben erstellen und
vergeben koennen. plus man soll die aufgaben hier in dieser seite
auch sehen."
DREI FELDER, NICHT ZWOELF. Wer hier steht, hat die Person schon
gewaehlt; was fehlt, ist was, bis wann und ob noch jemand
mitmacht. Das grosse Formular auf dem Brett bleibt, wo es ist --
fuer den Fall, dass man eine Aufgabe fuer irgendwen irgendwo
anlegt.
===================================================================== */
let fuerPerson = null;
let fuerName = null;
let mehrLeute = [];
let leute = [];
function formularZu() {
const f = $('e-neu');
if (!f) return;
f.hidden = true;
const auf = $('verteilen-knopf');
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');
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;
});
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();
}
function aufgabenFormularAufbauen() {
const auf = $('verteilen-knopf');
if (!auf) return;
auf.setAttribute('aria-expanded', 'false');
auf.setAttribute('aria-controls', 'e-neu');
auf.addEventListener('click', () => {
if ($('e-neu').hidden) formularAuf(); else formularZu();
});
$('e-neu-abbrechen').addEventListener('click', () => {
$('e-neu').reset();
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;
}
});
$('e-neu').addEventListener('submit', async (ev) => {
ev.preventDefault();
const melder = $('e-neu-fehler');
melder.textContent = '';
const titel = $('e-titel').value.trim();
if (titel.length < 2) {
melder.textContent = 'Schreib kurz, was zu tun ist.';
$('e-titel').focus();
return;
}
if (!fuerPerson) {
melder.textContent = 'Wähl links erst jemanden aus.';
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')
: 'einzeln';
const a = await fetch('/workspace/api/aufgaben', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
titel,
beschreibung: $('e-text').value.trim() || null,
frist: $('e-frist').value || null,
/* DIE GEWAEHLTE PERSON STEHT VORN. `zuteilen` leitet die
Art ab; bei einer Person wird daraus „einzeln", und
genau das ist gemeint. */
zuteilung: [Number(fuerPerson), ...mehrLeute],
verteilart: art,
}),
});
const r = await a.json().catch(() => ({}));
if (!a.ok) {
melder.textContent = window.sagWas?.(r.fehler, 'Ging nicht.') || 'Ging nicht.';
knopf.disabled = false;
return;
}
/* WAS DER SERVER WIRKLICH GESETZT HAT, nicht was ich wollte.
`zuteilen` kann eine Person weglassen (gesperrter Zugang)
oder die Art aendern -- wer das verschweigt, laesst jemanden
in dem Glauben, er habe drei Leute eingetragen. */
const gesetzt = r?.zuteilung?.gesetzt;
const fehlt = Array.isArray(gesetzt)
? [Number(fuerPerson), ...mehrLeute].filter((x) => !gesetzt.includes(x)).length
: 0;
$('e-neu').reset();
mehrLeute = [];
formularZu();
melde(fehlt
? `Angelegt — aber ${fehlt} Person(en) konnten nicht eingetragen werden.`
: `„${titel}" steht jetzt bei ${fuerName || 'ihm'}.`, !fehlt);
await aufgabenZeigen(fuerPerson, fuerName);
} catch {
melder.textContent = 'Das hat gerade nicht geklappt. Versuch es noch einmal.';
knopf.disabled = false;
} finally {
knopf.disabled = false;
}
});
}
/* ---------------------------------------------------------------------
UND DIE AUFGABEN DIESER PERSON, AUF DERSELBEN SEITE
---------------------------------------------------------------------
„plus man soll die aufgaben hier in dieser seite auch sehen."
GEFILTERT WIRD IM BROWSER, nicht mit einem eigenen Weg im Server:
Die Liste, die er ohnehin schickt, enthaelt die Zuteilung je
Aufgabe. Ein zweiter Weg waere eine zweite Rechnung fuer dieselbe
Frage -- und die Sichtbarkeitsregel muesste dort noch einmal
stehen.
--------------------------------------------------------------------- */
async function aufgabenZeigen(personId, name) {
const block = $('e-aufgaben-block');
if (!block) return;
if (!personId) { block.hidden = true; return; }
try {
const a = await fetch('/workspace/api/aufgaben', { credentials: 'same-origin' });
if (!a.ok) { block.hidden = true; return; }
const roh = await a.json();
const alle = Array.isArray(roh) ? roh : (roh?.aufgaben || []);
const nr = Number(personId);
const meine = alle.filter((x) => Number(x.verantwortlich_id) === nr
|| (x.zuteilung || []).some((z) => Number(z.person_id) === nr
&& z.zustand !== 'abgelehnt'));
block.hidden = false;
$('e-aufgaben-titel').textContent = name ? `Aufgaben von ${name}` : 'Ihre Aufgaben';
const offen = meine.filter((x) => x.status !== 'erledigt' && x.status !== 'abgebrochen');
$('e-aufgaben-satz').textContent = meine.length
? `${offen.length} offen, ${meine.length} insgesamt.`
: '';
const liste = $('e-aufgaben-liste');
liste.replaceChildren();
if (!meine.length) {
const p2 = document.createElement('p');
p2.className = 'e-aufgaben__leer';
p2.textContent = name
? `${name} hat gerade keine Aufgabe. Leg oben eine an, wenn etwas ansteht.`
: 'Gerade keine Aufgabe.';
liste.append(p2);
return;
}
/* OFFENE ZUERST. Was erledigt ist, interessiert danach -- und
eine Liste nach Anlegedatum haette die wichtigste Zeile
irgendwo in der Mitte. */
const sortiert = [...meine].sort((x, y) => {
const fertig = (z) => (z.status === 'erledigt' || z.status === 'abgebrochen' ? 1 : 0);
return fertig(x) - fertig(y)
|| String(x.frist || '9999').localeCompare(String(y.frist || '9999'));
});
for (const x of sortiert) {
const a2 = document.createElement('a');
a2.className = 'e-aufgabe';
a2.href = 'aufgaben.html#a' + x.id;
a2.dataset.status = x.status || 'offen';
const t = document.createElement('span');
t.className = 'e-aufgabe__titel';
t.textContent = x.titel;
a2.append(t);
const m = document.createElement('span');
m.className = 'e-aufgabe__stand';
m.textContent = STATUS_WORT[x.status] || x.status || 'offen';
a2.append(m);
if (x.frist) {
const f = document.createElement('span');
f.className = 'e-aufgabe__frist';
f.textContent = 'bis ' + datum(x.frist);
a2.append(f);
}
liste.append(a2);
}
} catch { block.hidden = true; }
}
const STATUS_WORT = {
offen: 'Offen', arbeit: 'In Arbeit', review: 'Zur Freigabe',
erledigt: 'Erledigt', abgebrochen: 'Abgebrochen',
};
async function karte(id, springen = false) {
offen = id;
/* WER IST GERADE OFFEN? Die Antwort gehoert auf die Kacheln, nicht
@@ -356,7 +622,13 @@
}
{
const k = document.querySelector(`#personen .e-person[data-person="${id}"]`);
verteilBandZeigen(id, k ? (k.dataset.name || k.textContent.trim()) : null);
const wieHeisst = k ? (k.dataset.name || k.textContent.trim()) : null;
verteilBandZeigen(id, wieHeisst);
/* UND IHRE AUFGABEN GLEICH MIT. „plus man soll die aufgaben
hier in dieser seite auch sehen." Sie kommen beim Auswaehlen,
nicht auf Knopfdruck: Wer jemanden durchgeht, will wissen,
was bei ihm liegt -- das ist keine Zusatzfrage. */
aufgabenZeigen(id, wieHeisst);
}
const ziel = $('karte');
const vorherY = window.scrollY;
@@ -860,8 +1132,13 @@
stufen = lage.stufen;
if (lage.leitung) {
/* DIE LEUTE FUER „NOCH JEMANDEN DAZUNEHMEN" kommen aus
derselben Antwort wie die Kacheln. Ein eigener Abruf waere
eine zweite Liste, die im Zweifel eine andere ist. */
leute = lage.personen || [];
/* Das Verteil-Band gehoert zur Leitungssicht -- ein Modi sieht
hier nur sich selbst und verteilt nichts. */
aufgabenFormularAufbauen();
verteilBandZeigen(null, null);
$('personen-block').hidden = false;
const ziel = $('personen');