Deutsch oeffnet mit „ und schliesst mit “. An 199 Stellen, die ein
Mensch liest, stand als Schlusszeichen ein GERADES " -- „Nächstes"
statt „Nächstes“. Auf sechzehn oeffentlichen Seiten, in vierzig
Dateien des Workspace und in zwoelf Servermodulen, die Texte
verschicken.
Auf dem Bildschirm sieht man den Unterschied sofort. Beim Schreiben
nicht: Das gerade " liegt auf der Tastatur, die anderen nicht.
WARUM EIN ERSTER ANLAUF ZURUECKGENOMMEN WURDE
Ein gerades " ist an vielen Stellen SYNTAX und kein Schriftzeichen --
Grenze einer Zeichenkette, Grenze eines HTML-Attributs, Zeichen in
einem regulaeren Ausdruck. Wer stumpf ersetzt, macht aus
„<a href="https://… ein kaputtes Attribut
/^["'„»\s]+|["'“«.\s]+$/ einen kaputten Ausdruck
"… nichts „mal " + "eben …" eine kaputte Zeichenkette
DIE UNTERSCHEIDUNG LAEUFT AN MERKMALEN, NICHT AN EINER LISTE
< > = dazwischen -> HTML-Marke oder Attribut
endet auf Leerzeichen -> die Zeichenkette hoert hier auf, der
Satz geht in der naechsten Zeile weiter.
Ein deutsches Schlusszeichen steht NIE
hinter einem Leerzeichen.
Rueckstrich mittendrin -> regulaerer Ausdruck
${ ohne } -> mitten in einem Ausdruck
Eine Liste erlaubter Ausnahmen waere die naechste, die niemand
pflegt. Zwoelf Stellen bleiben dadurch stehen, alle zwoelf einzeln
angesehen und alle zwoelf zu Recht -- dort steht das richtige
Schlusszeichen ohnehin weiter unten im Satz.
Fuenf davon waren allerdings ECHTE Fehler HINTER dem Link
(`…>HasiDog</a>".`) -- die erste Regel hatte nur das Attribut
gesehen, nicht den Satz danach. Gezielt nachgezogen.
Ein maskiertes `\"` in workspace-vorlagen.js (28 Hooks) wird zu “ --
ohne Rueckstrich, denn “ begrenzt nichts.
KOMMENTARE BLEIBEN, WIE SIE SIND. Dort liest es niemand ausser mir;
eine Wache, die auch Kosmetik anmahnt, wird weggeklickt. Beim ersten
Messen fielen ausserdem acht Stellen aus buehne.html faelschlich an,
weil `/* */` in HTML (in <style> und <script>) nicht ausgeblendet
war -- jetzt schon.
DIE WACHE DAZU
pruef-struktur prueft es ab sofort mit derselben Regel: 322 Dateien
mit sichtbarem Text, 0 Funde, und die zwoelf bewussten Ausnahmen
werden GEZAEHLT und genannt (erlaubt: 12). Eine Ausnahme, die niemand
sieht, waechst -- und irgendwann steht der echte Fall darin.
GEPRUEFT
pruef-struktur 59 -> 68 Pruefungen, 0 Fehler
node --check auf allen geaenderten JS-Dateien
nachgemessen: 199 geaendert, 12 mit Grund stehen geblieben
gruen geblieben: bewerbung-aufgaben 163, nachwuchs 262,
reaktion 421, support 63, content 45, terminregel 35, treff 85
Und nachgesehen, ob eine Pruefung noch die alte Schreibweise
ERWARTET: 13 Fundstellen, alle dreizehn nur Text in ihrer eigenen
Ausgabe, keine einzige ein Vergleich mit dem Seitentext.
GEGENPROBE: In reaktion.html ein Schlusszeichen zurueckgedreht ->
„workspace/reaktion.html:546 „Nächstes"", mit Datei und Zeile. Und
die Erkennung einzeln gegen HTML-Attribut, fortgesetzte
Zeichenkette, regulaeren Ausdruck und eingesetzten Wert geprueft.
Stempel gesetzt: workspace 670 Verweise in 45 Dateien, oeffentlich
554 in 48 Seiten.
Co-Authored-By: Claude Opus 5 <[email protected]>
567 lines
25 KiB
JavaScript
567 lines
25 KiB
JavaScript
/* =====================================================================
|
||
zuteilung.js — WER HAT DIE AUFGABE, UND WIE STEHT SIE BEI IHM
|
||
=====================================================================
|
||
|
||
Auftrag vom 21.09.2026, Abschnitte 1 bis 5. Die Oberfläche dazu.
|
||
|
||
EIN EIGENES MODUL, weil aufgaben.js schon 1900 Zeilen hat. Alles,
|
||
was mit Zuteilung zu tun hat, steht hier an einer Stelle; aufgaben.js
|
||
ruft an genau zwei Stellen hierher.
|
||
|
||
WAS ES BAUT:
|
||
|
||
Band oben Für einen Modi sein persönliches Resümee, für
|
||
DogFather und die Hände die Übersicht über alle.
|
||
An der Karte Wer sie hat, wie es bei jedem steht, und die
|
||
Knöpfe, die für DIESEN Menschen gerade sinnvoll
|
||
sind.
|
||
|
||
DIE WICHTIGSTE REGEL DARIN: Es wird nur angeboten, was auch geht.
|
||
Ein "Annehmen" an einer Aufgabe, die man nicht bekommen hat, wäre
|
||
ein Knopf, der mit 403 antwortet — und die Meldung erscheint oben
|
||
auf der Seite, wo man sie bei einer Karte weiter unten gar nicht
|
||
sieht. Diese Lektion steht in aufgaben.js schon einmal; sie gilt
|
||
hier genauso.
|
||
|
||
DIE TEXTE STEHEN NICHT HIER, SONDERN KOMMEN VOM SERVER
|
||
(`zustand_text`, `bewertung_text_kurz`). Zwei Listen derselben
|
||
Wörter laufen auseinander, und dann heißt derselbe Zustand an zwei
|
||
Stellen anders. */
|
||
(function () {
|
||
'use strict';
|
||
|
||
var el = function (was, klasse, text) {
|
||
var e = document.createElement(was);
|
||
if (klasse) e.className = klasse;
|
||
if (text !== undefined) e.textContent = text;
|
||
return e;
|
||
};
|
||
|
||
/* Damit die Meldung dort steht, wo der Mensch hinsieht, und nicht
|
||
ganz oben auf der Seite. */
|
||
function sagen(wohin, text, gut) {
|
||
if (!wohin) return;
|
||
wohin.textContent = text;
|
||
wohin.dataset.gut = gut ? 'ja' : 'nein';
|
||
wohin.hidden = false;
|
||
}
|
||
|
||
async function schicken(pfad, koerper) {
|
||
var a = await fetch(pfad, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(koerper || {}),
|
||
});
|
||
var j = null;
|
||
try { j = await a.json(); } catch (e) { /* leere Antwort */ }
|
||
return { ok: a.ok, code: a.status, json: j || {} };
|
||
}
|
||
|
||
/* =====================================================================
|
||
DAS BAND OBEN
|
||
===================================================================== */
|
||
|
||
/** Eine Zahl mit Wort darunter. Die Zahl groß, damit man sie von
|
||
* oben erfasst, ohne zu lesen. */
|
||
function zahlKachel(wert, wort, art) {
|
||
var k = el('div', 'zk' + (art ? ' zk--' + art : ''));
|
||
k.append(el('span', 'zk__zahl', String(wert)));
|
||
k.append(el('span', 'zk__wort', wort));
|
||
return k;
|
||
}
|
||
|
||
/** Das persönliche Resümee eines Menschen. */
|
||
function meinBand(r) {
|
||
var kasten = el('section', 'z-band');
|
||
var kopf = el('div', 'z-band__kopf');
|
||
kopf.append(el('h2', 'z-band__titel', 'Deine Aufgaben'));
|
||
/* EIN SATZ, DER DIE ZAHLEN EINORDNET. Fünf nackte Zahlen sind
|
||
eine Tabelle; ein Satz ist eine Auskunft. */
|
||
var satz = r.dran === 0
|
||
? (r.gesamt === 0
|
||
? 'Dir ist im Moment nichts zugeteilt.'
|
||
: 'Alles erledigt — nichts offen.')
|
||
: (r.ueberfaellig > 0
|
||
? r.dran + (r.dran === 1 ? ' Aufgabe ist dran' : ' Aufgaben sind dran')
|
||
+ ', ' + r.ueberfaellig + ' davon überfällig.'
|
||
: r.dran + (r.dran === 1 ? ' Aufgabe ist dran.' : ' Aufgaben sind dran.'));
|
||
kopf.append(el('p', 'z-band__satz', satz));
|
||
kasten.append(kopf);
|
||
|
||
var reihe = el('div', 'z-band__zahlen');
|
||
reihe.append(zahlKachel(r.offen, 'noch offen', r.offen > 0 ? 'wach' : null));
|
||
reihe.append(zahlKachel(r.angenommen + r.arbeit, 'in Arbeit'));
|
||
reihe.append(zahlKachel(r.erledigt, 'erledigt', 'gut'));
|
||
if (r.ueberfaellig > 0) reihe.append(zahlKachel(r.ueberfaellig, 'überfällig', 'warn'));
|
||
if (r.gelobt > 0) reihe.append(zahlKachel(r.gelobt, 'gut gemacht', 'gut'));
|
||
if (r.zu_verbessern > 0) reihe.append(zahlKachel(r.zu_verbessern, 'mit Rückmeldung', 'wach'));
|
||
kasten.append(reihe);
|
||
return kasten;
|
||
}
|
||
|
||
/** Die Übersicht über alle — für DogFather und die Hände. */
|
||
function teamBand(daten) {
|
||
var kasten = el('section', 'z-band z-band--team');
|
||
var kopf = el('div', 'z-band__kopf');
|
||
kopf.append(el('h2', 'z-band__titel', 'Wer hat gerade was'));
|
||
kopf.append(el('p', 'z-band__satz',
|
||
/* "Eine Zeile je Person" stimmte, solange es Zeilen waren. Seit
|
||
dem 23.09.2026 stehen die Leute nebeneinander -- der Satz
|
||
beschrieb also eine Anordnung, die es nicht mehr gibt. Ein
|
||
Hinweistext, der etwas anderes sagt als der Bildschirm zeigt,
|
||
ist schlimmer als keiner: Man sucht dann nach Zeilen. */
|
||
'Tippe jemanden an, um nur seine Aufgaben zu sehen.'));
|
||
kasten.append(kopf);
|
||
|
||
var leute = daten.leute || [];
|
||
if (!leute.length) {
|
||
kasten.append(el('p', 'z-leer', 'Es gibt noch niemanden im Team.'));
|
||
return kasten;
|
||
}
|
||
|
||
var liste = el('div', 'z-leute');
|
||
leute.forEach(function (p) {
|
||
var z = el('button', 'z-person');
|
||
z.type = 'button';
|
||
z.dataset.person = String(p.id);
|
||
z.append(el('span', 'z-person__name', p.name));
|
||
var m = el('span', 'z-person__marken');
|
||
/* NUR WAS ETWAS SAGT. Eine Null neben jeder Person ist Rauschen;
|
||
wer nichts offen hat, soll das auch als Ruhe sehen. */
|
||
if (p.offen) m.append(el('span', 'z-marke z-marke--wach', p.offen + ' offen'));
|
||
if (p.angenommen + p.arbeit) {
|
||
m.append(el('span', 'z-marke', (p.angenommen + p.arbeit) + ' in Arbeit'));
|
||
}
|
||
if (p.ueberfaellig) m.append(el('span', 'z-marke z-marke--warn', p.ueberfaellig + ' überfällig'));
|
||
if (p.abgelehnt) m.append(el('span', 'z-marke z-marke--still', p.abgelehnt + ' abgelehnt'));
|
||
if (p.erledigt) m.append(el('span', 'z-marke z-marke--gut', p.erledigt + ' erledigt'));
|
||
if (!m.childElementCount) m.append(el('span', 'z-marke z-marke--still', 'nichts zugeteilt'));
|
||
z.append(m);
|
||
liste.append(z);
|
||
});
|
||
kasten.append(liste);
|
||
return kasten;
|
||
}
|
||
|
||
/* =====================================================================
|
||
AN DER KARTE
|
||
===================================================================== */
|
||
|
||
/** Baut den Zuteilungsblock einer Karte — oder gibt null zurück,
|
||
* wenn es nichts zu zeigen gibt. */
|
||
function karteBlock(a, ich, nachLaden) {
|
||
var zu = a.zuteilung || [];
|
||
/* BIS ZUM 22.09.2026 STAND HIER `if (!zu.length) return null;`.
|
||
Das war richtig, solange es an einer Aufgabe ohne Zuteilung
|
||
nichts zu tun gab. Seit es das Bewerben gibt, ist genau die
|
||
unverteilte Aufgabe die interessanteste -- und sie haette
|
||
keinen Platz fuer den Knopf gehabt.
|
||
|
||
Leer bleibt der Block trotzdem, wenn wirklich nichts drinsteht:
|
||
Am Ende wird geprueft, ob ueberhaupt etwas angehaengt wurde. */
|
||
var darfEntscheiden = !!(ich && ich.darf_entscheiden);
|
||
var bewerbungen = a.bewerbungen || [];
|
||
|
||
var kasten = el('div', 'z-karte');
|
||
|
||
/* (1) WER HAT SIE. Bei "pool" steht statt der Liste, wer sie
|
||
genommen hat -- das ist die Auskunft, die zählt. */
|
||
if (a.verteilart === 'pool' && a.uebernommen_von) {
|
||
kasten.append(el('p', 'z-karte__zeile',
|
||
a.uebernommen_von + ' hat sie übernommen'));
|
||
} else if (a.verteilart === 'pool') {
|
||
/* DER SATZ HING AN EINER REGEL, DIE ES NICHT MEHR GIBT
|
||
(22.09.2026). „Wer zuerst Zeit hat" war die Beschreibung des
|
||
Pools, solange sich jeder selbst bedienen durfte. Seit Modis
|
||
und die linke Hand sich bewerben, stimmt er fuer genau die
|
||
Leute nicht mehr, die ihn lesen -- und ein Satz, der etwas
|
||
Falsches verspricht, ist schlimmer als keiner.
|
||
|
||
Er sagt deshalb jedem, was FUER IHN gilt. */
|
||
var angeboten = zu.filter(function (z) { return z.zustand !== 'abgelehnt'; }).length;
|
||
/* „WER ZUERST ZEIT HAT" GILT WIEDER -- nur zaehlt jetzt, wer
|
||
sich zuerst bewirbt. Der Satz stand einen Nachmittag lang
|
||
anders da („bewirb dich, wenn du Zeit hast"), und das war
|
||
zwar wahr, aber kleiner als das, was gemeint war. */
|
||
kasten.append(el('p', 'z-karte__zeile', darfEntscheiden
|
||
? 'Frei für ' + angeboten + ' Leute — wer zuerst Zeit hat'
|
||
: 'Frei für ' + angeboten + ' Leute — wer zuerst Zeit hat, bewirbt sich zuerst'));
|
||
}
|
||
|
||
var wer = el('ul', 'z-wer');
|
||
/* Bewerbungen stehen weiter unten in ihrem eigenen Block -- hier
|
||
noch einmal waeren sie dieselbe Sache zweimal. */
|
||
zu.filter(function (z) { return z.zustand !== 'beworben'; }).forEach(function (z) {
|
||
var zeile = el('li', 'z-wer__zeile');
|
||
zeile.dataset.zustand = z.zustand;
|
||
zeile.append(el('span', 'z-wer__name', z.person_name || 'Jemand'));
|
||
zeile.append(el('span', 'z-wer__stand', z.zustand_text));
|
||
/* DER GRUND GEHÖRT DANEBEN, nicht hinter einen zweiten Klick:
|
||
Wer "abgelehnt" liest, will als Nächstes wissen, warum. */
|
||
if (z.zustand === 'abgelehnt' && z.grund) {
|
||
zeile.append(el('span', 'z-wer__grund', '„' + z.grund + '“'));
|
||
}
|
||
if (z.bewertung) {
|
||
var b = el('span', 'z-wer__bewertung', z.bewertung_text_kurz);
|
||
b.dataset.art = z.bewertung;
|
||
zeile.append(b);
|
||
if (z.bewertung_text) zeile.append(el('span', 'z-wer__grund', z.bewertung_text));
|
||
}
|
||
/* WAS DIE LEITUNG DAZU GESAGT HAT. Bei einer angenommenen oder
|
||
abgelehnten Bewerbung ist das die Antwort auf die Bitte --
|
||
sie gehoert daneben, nicht hinter einen zweiten Klick. */
|
||
if (z.entscheid_text) {
|
||
zeile.append(el('span', 'z-wer__grund',
|
||
'„' + z.entscheid_text + '“'
|
||
+ (z.entschieden_von_name ? ' – ' + z.entschieden_von_name : '')));
|
||
}
|
||
wer.append(zeile);
|
||
});
|
||
if (wer.childElementCount) kasten.append(wer);
|
||
|
||
var meldung = el('p', 'z-meldung');
|
||
meldung.hidden = true;
|
||
|
||
/* (2) MEINE KNÖPFE. Nur, was für MICH gerade geht. */
|
||
var mein = a.meine_zuteilung;
|
||
var knoepfe = el('div', 'z-knoepfe');
|
||
|
||
function knopf(text, klasse, tun) {
|
||
var b = el('button', 'knopf ' + (klasse || ''), text);
|
||
b.type = 'button';
|
||
b.addEventListener('click', function () { tun(b); });
|
||
knoepfe.append(b);
|
||
return b;
|
||
}
|
||
|
||
/* SICH BEWERBEN (22.09.2026).
|
||
|
||
Filipe: „ich will dass die sich fuer aufgaben bewerben koennen
|
||
aber die rechte hand oder dogfather muessen annehmen oder
|
||
ablehnen koennen."
|
||
|
||
DER KNOPF STEHT NUR DA, WO ER AUCH ETWAS BEWIRKT. Drei
|
||
Bedingungen, und jede hat einen Grund:
|
||
* ich entscheide nicht selbst -- wer entscheidet, traegt
|
||
sich ein statt zu bitten
|
||
* ich habe sie noch nicht -- sonst nehme ich an oder ab
|
||
* sie ist noch frei -- eine Bewerbung auf etwas
|
||
Vergebenes ist eine
|
||
Enttaeuschung mit Anlauf
|
||
|
||
Der Text ist freiwillig. Beim ABLEHNEN einer zugeteilten
|
||
Aufgabe ist die Begruendung Pflicht -- dort sagt jemand ab, der
|
||
gefragt wurde. Hier bittet jemand; ein „bitte" braucht keine
|
||
Begruendung. */
|
||
var schonWer = a.uebernommen_von
|
||
|| zu.some(function (z) {
|
||
return z.zustand === 'angenommen' || z.zustand === 'arbeit' || z.zustand === 'erledigt';
|
||
});
|
||
/* ==== UND NICHT AUF DIE EIGENE AUFGABE (30.09.2026) ==========
|
||
|
||
VanVan im Support: „Wenn ein Modi sich auf eine Aufgabe beworben
|
||
hat und die von uns angenommen wurde, dann steht beim Modi unter
|
||
der Aufgabe immer noch ich bewerbe mich."
|
||
|
||
Die Ursache lag im Server (eine Aufgabe aus der Vorlage bekam
|
||
keine Zuteilungszeile, `mein` war deshalb leer, und der erste
|
||
Zweig unten lautet `!mein`). Das ist behoben.
|
||
|
||
DIESE ZEILE IST TROTZDEM RICHTIG, und zwar unabhaengig davon:
|
||
Auf eine Aufgabe, die mir schon gehoert, bewirbt man sich nicht.
|
||
Sie faengt jeden weiteren Weg ab, der eine Aufgabe ohne
|
||
Zuteilungszeile anlegt -- gemessen hatten 8 von 11 Aufgaben mit
|
||
Verantwortlichem keine, und nicht alle kamen aus der Vorlage. */
|
||
var gehoertMir = !!(ich && a.verantwortlich_id != null
|
||
&& a.verantwortlich_id === ich.id);
|
||
|
||
var kannBewerben = !darfEntscheiden && !a.ich_beworben && !gehoertMir
|
||
&& (!mein || mein.zustand === 'abgelehnt'
|
||
|| (mein.zustand === 'offen' && a.verteilart === 'pool'))
|
||
&& !(a.verteilart === 'pool' && schonWer);
|
||
|
||
if (a.ich_beworben) {
|
||
kasten.append(el('p', 'z-karte__zeile z-karte__zeile--warte',
|
||
'Du hast dich beworben — DogFather oder die rechte Hand melden sich.'));
|
||
knopf('Bewerbung zurückziehen', '', async function (b) {
|
||
b.disabled = true;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/bewerbung-zurueck');
|
||
if (r.ok) { nachLaden(); return; }
|
||
b.disabled = false;
|
||
sagen(meldung, window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.', false);
|
||
});
|
||
} else if (kannBewerben) {
|
||
knopf('Ich bewerbe mich', 'knopf--klar', async function () {
|
||
var antwort = await window.frageNach({
|
||
titel: 'Für diese Aufgabe bewerben?',
|
||
was: 'DogFather oder die rechte Hand entscheiden, ob du sie bekommst.',
|
||
bleibt: 'Du kannst deine Bewerbung jederzeit wieder zurückziehen.',
|
||
grund: 'Willst du etwas dazuschreiben? (freiwillig)',
|
||
grundPflicht: false,
|
||
platzhalter: 'z. B. ich habe Freitag Zeit dafür',
|
||
ja: 'Bewerben', nein: 'Doch nicht',
|
||
});
|
||
if (!antwort) return;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/bewerben',
|
||
{ text: (antwort && antwort.grund) || '' });
|
||
if (r.ok) { nachLaden(); return; }
|
||
sagen(meldung, r.code === 409 && r.json.wer
|
||
? (r.json.wer + ' war schneller — die Aufgabe ist schon vergeben.')
|
||
: (window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.'), false);
|
||
});
|
||
}
|
||
|
||
if (mein && mein.zustand === 'offen') {
|
||
if (a.verteilart === 'pool') {
|
||
/* NUR WER ENTSCHEIDET, NIMMT SICH SELBST ETWAS. Fuer alle
|
||
anderen steht oben der Bewerben-Knopf. */
|
||
if (darfEntscheiden) {
|
||
knopf('Ich übernehme das', 'knopf--klar', async function (b) {
|
||
b.disabled = true;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/uebernehmen');
|
||
if (r.ok) { nachLaden(); return; }
|
||
b.disabled = false;
|
||
sagen(meldung, r.code === 409 && r.json.wer
|
||
? (r.json.wer + ' war schneller — die Aufgabe ist schon vergeben.')
|
||
: (window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.'), false);
|
||
});
|
||
}
|
||
} else {
|
||
knopf('Annehmen', 'knopf--klar', async function (b) {
|
||
b.disabled = true;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/annehmen');
|
||
if (r.ok) { nachLaden(); return; }
|
||
b.disabled = false;
|
||
sagen(meldung, window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.', false);
|
||
});
|
||
}
|
||
/* ABLEHNEN GIBT ES NUR, WO ETWAS ZUGETRAGEN WURDE. Bei einer
|
||
Pool-Aufgabe, fuer die man sich bewerben soll, waere „Ablehnen"
|
||
eine Absage auf eine Frage, die niemand gestellt hat. */
|
||
if (!(a.verteilart === 'pool' && !darfEntscheiden))
|
||
knopf('Ablehnen', '', async function () {
|
||
/* MIT BEGRÜNDUNG, und sie ist Pflicht -- der Server lehnt
|
||
sonst ab. Der Dialog sagt das VOR dem Tippen, statt es
|
||
hinterher als Fehler zu melden. */
|
||
var antwort = await window.frageNach({
|
||
titel: 'Aufgabe ablehnen?',
|
||
was: 'Sie geht dann zurück an den, der sie verteilt hat.',
|
||
bleibt: 'Dein Satz bleibt an der Aufgabe stehen — damit niemand '
|
||
+ 'nachfragen muss.',
|
||
grund: 'Warum nicht?', grundPflicht: true,
|
||
platzhalter: 'z. B. bin diese Woche im Urlaub',
|
||
ja: 'Ablehnen', nein: 'Doch nicht',
|
||
});
|
||
if (!antwort || !antwort.grund) return;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/ablehnen',
|
||
{ grund: antwort.grund });
|
||
if (r.ok) { nachLaden(); return; }
|
||
sagen(meldung, window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.', false);
|
||
});
|
||
}
|
||
|
||
/* ==== EINE DAUERHAFTE AUFGABE WIRD NICHT FERTIG (30.09.2026) ==
|
||
|
||
VanVan im Support: „… dass die Aufgabe dauerhaft sein soll und
|
||
somit nicht vom Modi in den Status erledigt gesetzt werden
|
||
kann."
|
||
|
||
Sie ist keine Aufgabe, die man abarbeitet, sondern eine, die man
|
||
TUT. „Neue begrüßen" ist nicht fertig, wenn man es einmal
|
||
gemacht hat.
|
||
|
||
DIE MARKE STEHT FÜR ALLE DA, nicht nur für den Zugeteilten: Wer
|
||
die Karte ansieht, soll wissen, warum dort kein „Fertig" steht.
|
||
Ein fehlender Knopf ohne Erklärung liest sich wie ein Fehler. */
|
||
if (a.dauerhaft) {
|
||
var dauer = el('p', 'z-karte__zeile z-karte__zeile--dauer',
|
||
'Dauerhafte Aufgabe — sie wird nicht abgehakt, sie gehört dazu.');
|
||
kasten.append(dauer);
|
||
}
|
||
|
||
if (mein && (mein.zustand === 'angenommen' || mein.zustand === 'arbeit')) {
|
||
if (mein.zustand === 'angenommen') {
|
||
knopf('Ich fange an', '', async function (b) {
|
||
b.disabled = true;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/mein-stand',
|
||
{ zustand: 'arbeit' });
|
||
if (r.ok) { nachLaden(); return; }
|
||
b.disabled = false;
|
||
sagen(meldung, window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.', false);
|
||
});
|
||
}
|
||
/* KEIN „FERTIG" BEI EINER DAUERHAFTEN AUFGABE. Der Server
|
||
lehnt es ohnehin ab (`dauerhafte_aufgabe`) -- ein Knopf, der
|
||
eine Absage holt, ist schlimmer als keiner. Die Zeile
|
||
darüber sagt, warum er fehlt. */
|
||
if (!a.dauerhaft)
|
||
knopf('Fertig', 'knopf--klar', async function (b) {
|
||
b.disabled = true;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/mein-stand',
|
||
{ zustand: 'erledigt' });
|
||
if (r.ok) { nachLaden(); return; }
|
||
b.disabled = false;
|
||
sagen(meldung, window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.', false);
|
||
});
|
||
}
|
||
|
||
/* (2b) DIE BEWERBUNGEN — nur fuer die, die entscheiden duerfen.
|
||
|
||
JEDE BEWERBUNG BEKOMMT IHRE EIGENE ZEILE mit zwei Knoepfen.
|
||
Eine Auswahlliste mit einem Absenden-Knopf waere kuerzer und
|
||
falsch: Man entscheidet ueber einen Menschen, nicht ueber einen
|
||
Listeneintrag -- und wer drei Bewerbungen hat, will jede
|
||
einzeln beantworten koennen.
|
||
|
||
DER KOMMENTAR IST FREIWILLIG, bei beiden Knoepfen. Filipes Wort
|
||
ist „als moeglichkeit". */
|
||
if (darfEntscheiden && bewerbungen.length) {
|
||
var bBlock = el('div', 'z-bewerbungen');
|
||
bBlock.append(el('p', 'z-bewerbungen__schild',
|
||
bewerbungen.length === 1
|
||
? 'Eine Bewerbung wartet auf deine Antwort:'
|
||
: bewerbungen.length + ' Bewerbungen warten auf deine Antwort:'));
|
||
|
||
bewerbungen.forEach(function (z, i) {
|
||
var wem = z.person_name || 'Jemand';
|
||
var reihe = el('div', 'z-bewerbung');
|
||
reihe.append(el('span', 'z-bewerbung__name', wem));
|
||
/* DER ERSTE WIRD BENANNT, nicht nur oben hingestellt. Eine
|
||
Reihenfolge allein sieht man nicht an -- man haelt sie fuer
|
||
Zufall oder Alphabet. Erst das Wort macht daraus eine
|
||
Auskunft: Wer zuerst da war, steht als Erster da, und man
|
||
sieht, dass das kein Zufall ist.
|
||
|
||
Nur bei mehr als einer: Bei einer einzigen Bewerbung ist
|
||
„zuerst da" keine Auskunft, sondern Fuellwerk. */
|
||
if (i === 0 && bewerbungen.length > 1) {
|
||
reihe.append(el('span', 'z-bewerbung__zuerst', 'zuerst da'));
|
||
}
|
||
if (z.grund) reihe.append(el('span', 'z-bewerbung__wort', '„' + z.grund + '“'));
|
||
|
||
[['annehmen', 'Annehmen', 'knopf--klar'],
|
||
['ablehnen', 'Ablehnen', ''],
|
||
].forEach(function (w) {
|
||
var weg = w[0], schild = w[1], klasse = w[2];
|
||
var b = el('button', 'knopf knopf--klein ' + klasse, schild);
|
||
b.type = 'button';
|
||
b.addEventListener('click', async function () {
|
||
var antwort = await window.frageNach({
|
||
titel: schild + ' — ' + wem,
|
||
was: weg === 'annehmen'
|
||
? wem + ' bekommt die Aufgabe und wird benachrichtigt.'
|
||
: wem + ' bekommt sie nicht. Die Aufgabe bleibt frei.',
|
||
bleibt: 'Dein Kommentar bleibt an der Aufgabe stehen und '
|
||
+ wem + ' kann ihn lesen.',
|
||
grund: 'Willst du etwas dazuschreiben? (freiwillig)',
|
||
grundPflicht: false,
|
||
platzhalter: weg === 'annehmen'
|
||
? 'z. B. danke dir, mach das gern'
|
||
: 'z. B. das macht diesmal jemand anders',
|
||
ja: schild, nein: 'Abbrechen',
|
||
});
|
||
if (!antwort) return;
|
||
var r = await schicken(
|
||
'/workspace/api/aufgaben/' + a.id + '/bewerbung/' + weg,
|
||
{ person_id: z.person_id, kommentar: (antwort && antwort.grund) || '' });
|
||
if (r.ok) { nachLaden(); return; }
|
||
sagen(meldung, r.code === 409 && r.json.wer
|
||
? (r.json.wer + ' hat sie inzwischen — die Bewerbung ist hinfällig.')
|
||
: (window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.'), false);
|
||
});
|
||
reihe.append(b);
|
||
});
|
||
bBlock.append(reihe);
|
||
});
|
||
kasten.append(bBlock);
|
||
}
|
||
|
||
/* (3) BEWERTEN -- nur wer verteilen darf, und nur bei Menschen,
|
||
die wirklich fertig sind. */
|
||
if (ich && ich.darf_verteilen) {
|
||
/* DREI BENANNTE KNÖPFE STATT EINER AUSWAHL IM DIALOG.
|
||
|
||
Abschnitt 5 nennt drei Möglichkeiten. Sie als Auswahl in einen
|
||
Dialog zu legen hieße: erst klicken, dann lesen, dann wählen.
|
||
Als drei Knöpfe stehen sie da, man sieht sofort, was es gibt,
|
||
und ein Klick genügt. "Wenn Verbesserungsmöglichkeiten
|
||
vorhanden sind, soll ein Eingabebereich erscheinen" — genau
|
||
das passiert: Bei diesem einen Knopf ist der Text Pflicht, bei
|
||
den anderen beiden freiwillig. */
|
||
zu.filter(function (z) { return z.zustand === 'erledigt' && !z.bewertung; })
|
||
.forEach(function (z) {
|
||
var wem = z.person_name || 'sie';
|
||
var reihe = el('div', 'z-bewerten');
|
||
reihe.append(el('span', 'z-bewerten__schild', 'Rückmeldung an ' + wem + ':'));
|
||
|
||
[['gut', 'Gut gemacht', false],
|
||
['nicht_gut', 'Nicht gut gemacht', false],
|
||
['verbessern', 'Verbesserungsmöglichkeiten', true],
|
||
].forEach(function (w) {
|
||
var art = w[0], schild = w[1], pflicht = w[2];
|
||
var b = el('button', 'knopf z-bewerten__knopf', schild);
|
||
b.type = 'button';
|
||
b.dataset.art = art;
|
||
b.addEventListener('click', async function () {
|
||
var antwort = await window.frageNach({
|
||
titel: schild + ' — ' + wem,
|
||
was: pflicht
|
||
? 'Schreib konkret, was beim nächsten Mal besser gehen kann.'
|
||
: 'Du kannst noch einen Satz dazuschreiben — musst aber nicht.',
|
||
bleibt: 'Die Rückmeldung bleibt dauerhaft an dieser Aufgabe und '
|
||
+ wem + ' kann sie lesen.',
|
||
grund: pflicht ? 'Was genau?' : 'Noch etwas dazu? (freiwillig)',
|
||
grundPflicht: pflicht,
|
||
platzhalter: pflicht
|
||
? 'z. B. beim nächsten Mal bitte die Zeitstempel mitschreiben'
|
||
: 'z. B. sehr schnell gemacht, danke',
|
||
ja: 'Absenden', nein: 'Abbrechen',
|
||
});
|
||
if (!antwort) return;
|
||
var r = await schicken('/workspace/api/aufgaben/' + a.id + '/bewerten',
|
||
{ person_id: z.person_id, bewertung: art, text: antwort.grund || '' });
|
||
if (r.ok) { nachLaden(); return; }
|
||
sagen(meldung, r.json.fehler === 'text_fehlt'
|
||
? 'Bei „Verbesserungsmöglichkeiten“ gehört ein Satz dazu — sonst weiß '
|
||
+ wem + ' nicht, was gemeint ist.'
|
||
: (window.sagWas ? window.sagWas(r.json.fehler) : 'Hat nicht geklappt.'), false);
|
||
});
|
||
reihe.append(b);
|
||
});
|
||
kasten.append(reihe);
|
||
});
|
||
}
|
||
|
||
if (knoepfe.childElementCount) kasten.append(knoepfe);
|
||
/* NICHTS ZU ZEIGEN HEISST NICHTS ZEIGEN. Seit der Block auch bei
|
||
Aufgaben ohne Zuteilung gebaut wird, kann er leer bleiben -- ein
|
||
leerer Kasten unter jeder Karte waere Rauschen. */
|
||
if (!kasten.childElementCount) return null;
|
||
kasten.append(meldung);
|
||
return kasten;
|
||
}
|
||
|
||
/* =====================================================================
|
||
NACH AUSSEN
|
||
===================================================================== */
|
||
window.Zuteilung = {
|
||
meinBand: meinBand,
|
||
teamBand: teamBand,
|
||
karteBlock: karteBlock,
|
||
/* Holt beides und baut das passende Band. Der Aufrufer muss nicht
|
||
wissen, welche Rolle welches bekommt. */
|
||
bandBauen: async function (ich) {
|
||
try {
|
||
if (ich && ich.darf_verteilen) {
|
||
var a = await fetch('/workspace/api/aufgaben/team-resuemee');
|
||
if (!a.ok) return null;
|
||
return teamBand(await a.json());
|
||
}
|
||
var b = await fetch('/workspace/api/aufgaben/resuemee');
|
||
if (!b.ok) return null;
|
||
return meinBand(await b.json());
|
||
} catch (e) { return null; }
|
||
},
|
||
};
|
||
})();
|