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]>
520 lines
23 KiB
JavaScript
520 lines
23 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. Tippe eine an, um nur ihre 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';
|
||
});
|
||
var kannBewerben = !darfEntscheiden && !a.ich_beworben
|
||
&& (!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);
|
||
});
|
||
}
|
||
|
||
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);
|
||
});
|
||
}
|
||
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; }
|
||
},
|
||
};
|
||
})();
|