B7 -- "wenn ich auf diese sachen drücke werd ich auf die startseite geschickt" und "ich will das es ein zwei kategorien mehr gibt und das soll geiler aussehen und jede seine eigene farbe, stark erkennbar" 1. DER RAUSWURF. Ein Druck auf einen Stufen-Knopf schickte auf die Startseite. Ursache: `hole()` stand ACHTMAL Zeichen fuer Zeichen gleich in acht Dateien, und darin warf JEDER 404 hinaus -- auch der auf eine blosse Handlung. Jetzt eine gemeinsame `assets/js/holen.js` mit der engeren, abgeleiteten Regel: Ein 404 wirft nur, solange die Seite noch gar nichts bekommen hat. 2. ZWEI STUFEN MEHR, und keine davon erfunden: "Fortgeschritten" gibt es im Entwicklungs-Katalog laengst (ENTWICKLUNG_ERWARTUNG), hier fehlte genau diese Mitte; "Vertretung" stand bis heute IN der Beschreibung von Senior. Keine Datenbankaenderung noetig -- die Spalte ist absichtlich ein TEXT ohne CHECK. 3. DIE FARBEN SIND GERECHNET. Nachgemessen lagen "Probe" und "Standard" bei 0,0576 OKLab-Abstand; MINDEST_ABSTAND ist 0,090 -- die beiden waren nebeneinander dieselbe Farbe, und alle drei lagen unter MINDEST_BUNTHEIT. Die neuen fuenf sind ein Faecher aus fuenf Farbwinkeln im Abstand von 72 Grad; der Versatz ist der, bei dem der kleinste Abstand am groessten wird. Ergebnis: 0,1531 untereinander, 0,1108 zur Rollenmarke daneben, Kontrast 7,4 bis 8,6. B8 -- "die sollen viel krasser geiler und übersichtlicher sein ... und die kacheln sollen viel krass geiler und spezieller aussehen auch der hintergrund, veränder das komplett" 4. DIE KARTE IST NEU: Kopfband in der Farbe der Stufe bis an die Kanten, Zeichen von 44 auf 52 px mit Ring, Zahlenband als eigene Flaeche, neuer Hintergrund (Farbschein unter dem Kopfband, feine Schraffur, dunklere Platte). 781 px hoch gemessen, danach 746. 5. DIE ROLLE STEHT JETZT DA -- als Marke auf der Karte und als Ueberschrift ueber jeder Gruppe. Sortiert war schon vorher nach Rolle; man konnte es nur nicht sehen. DREI FEHLER, DIE DABEI AUFFIELEN * `auto-fit` liess die einzelne Karte der rechten Hand ueber die ganze Bildschirmbreite laufen, sobald gruppiert wurde. `auto-fill` haelt die leeren Spalten offen. * Der Name der Person trug `tperson__namen` statt `tperson__name` -- ein Buchstabe, und damit die Klasse fuer graues Kleingedrucktes. Auf einer Seite ueber Personen war der Name kleiner als die Beschriftung darunter. Gefunden hat es die neue Pruefung, die ueberall "?" las. * `stufeSetzen()` suchte das Stufen-Schild mit `[class*="tmerkmal--"]:not(...)` -- einer Aufzaehlung dessen, was es NICHT ist. Sobald die Rollenmarke davorstand, traf die Suche sie: Ein Druck auf "Standard" haette aus "Modi" das Wort "Standard" gemacht. Und die Karte faerbte sich beim Setzen nicht mit um. DREI PRUEFUNGEN, ALLE MIT GEGENPROBE * pruef-holen.mjs (NEU, 14): fuehrt die echte Datei aus und misst fuenf Wege; dieselben fuenf noch einmal an der ALTEN Fassung, die bei genau zwei durchfallen muss. Dazu: laedt jede der acht Seiten holen.js, und zwar VOR ihrem Skript. * pruef-teamlage-karten.mjs (NEU, 22): im Browser. Gruppen, Rollenwort, Stufenknoepfe nur bei Modis, der Druck selbst (kein Sprung, ein Abruf, Schild und Kartenfarbe ziehen mit), Gegenprobe mit dem zweiten Druck, und die neuen Baender auf 390 px. * pruef-team-stufen.mjs (28 -> 47): die abgeschriebene Liste ["probe","standard","senior"] ist raus -- geprueft werden jetzt Eigenschaften und die Farbabstaende, gelesen aus team.css. Gegenprobe ist der Stand von gestern, der durch dieselbe Messung faellt. Co-Authored-By: Claude Opus 5 <[email protected]>
830 lines
36 KiB
JavaScript
830 lines
36 KiB
JavaScript
/* =====================================================================
|
||
DIE LAGE DES TEAMS -- eine Seite fuer die, die sie sehen duerfen.
|
||
|
||
Wunsch Filipe (10.09.2026): eine Stelle, an der er und VanVan alles
|
||
sehen und behandeln koennen.
|
||
|
||
SIE ZAEHLT NUR, SIE BEWERTET NICHT. Bewertet wird dort, wo die
|
||
Punkte stehen -- je Bereich fuehrt ein Verweis dorthin, mit der
|
||
Person schon vorausgewaehlt. Eine zweite Stelle zum Bewerten waere
|
||
eine zweite Stelle, an der es auseinanderlaeuft.
|
||
|
||
KEINE NEUEN CSS-KLASSEN. Fuer genau diese Art Karte gibt es sie
|
||
schon (`tperson`, `tz`, `schritt` -- benutzt von team.html). Elf
|
||
neue haetten in mehreren Stilvorlagen gepflegt werden muessen, fuer
|
||
ein Aussehen, das es bereits gibt. Zwei Bauarten fuer dieselbe Sache
|
||
sind ausserdem zwei Dinge zum Lernen statt einem.
|
||
|
||
IN DIESER DATEI STEHT KEIN ROLLENNAME. Sie wird wie jede Datei unter
|
||
/workspace an JEDEN ausgeliefert, der die Seite oeffnet. Wer sie
|
||
bekommt, entscheiden der Server (404 fuer alle anderen) und die
|
||
Kachel, die ebenfalls vom Server kommt.
|
||
===================================================================== */
|
||
(() => {
|
||
const $ = (id) => document.getElementById(id);
|
||
const el = (tag, klasse, text) => {
|
||
const k = document.createElement(tag);
|
||
if (klasse) k.className = klasse;
|
||
if (text !== undefined) k.textContent = text;
|
||
return k;
|
||
};
|
||
const melde = (t) => { const f = $('fehler'); if (f) f.textContent = t || ''; };
|
||
|
||
let ich = null;
|
||
/* Die Stufen kommen mit der Antwort des Servers -- eine eigene Liste
|
||
hier waere die zweite, und die zweite laeuft irgendwann auseinander. */
|
||
let STUFEN = [];
|
||
const stufenName = (wert) =>
|
||
STUFEN.find((s) => s.wert === wert)?.name || STUFEN[0]?.name || '';
|
||
|
||
/* Der Abruf steht seit dem 22.09.2026 in assets/js/holen.js.
|
||
Er stand ACHTMAL da, Zeichen fuer Zeichen gleich -- und als
|
||
die Regel darin sich als falsch herausstellte (jeder 404
|
||
warf auf die Startseite, auch bei einer blossen Handlung),
|
||
haette man sie achtmal aendern muessen. */
|
||
const hole = (pfad, optionen) => window.holen(pfad, optionen);
|
||
|
||
/* DIE ROLLENWOERTER. Sie stehen hier und nicht im Server, weil die
|
||
Antwort sie nicht mitschickt -- und zwei Woerter nachzuladen waere
|
||
ein Abruf fuer nichts. Faellt eine Rolle durch, steht ihr
|
||
technischer Name da: sichtbar falsch ist besser als leer. */
|
||
const ROLLEN_WORT = {
|
||
hand: 'Rechte Hand',
|
||
linke: 'Linke Hand',
|
||
modi: 'Modi',
|
||
};
|
||
|
||
const tageHer = (iso) => {
|
||
if (!iso) return null;
|
||
const d = new Date(iso);
|
||
if (Number.isNaN(d.getTime())) return null;
|
||
return Math.floor((Date.now() - d.getTime()) / 86400000);
|
||
};
|
||
|
||
/* Die drei Bereiche in derselben Reihenfolge wie auf der Startseite.
|
||
Die Namen stehen hier, weil die Seite sonst eine zweite Abfrage
|
||
braeuchte, nur um drei Woerter zu holen. */
|
||
const BEREICHE = [
|
||
{ schluessel: 'live', name: 'Live-Ablauf' },
|
||
{ schluessel: 'community', name: 'Community' },
|
||
{ schluessel: 'technik', name: 'Technik' },
|
||
];
|
||
|
||
function zahl(wert, schild) {
|
||
const k = el('div', 'tz');
|
||
k.append(el('strong', 'tz__wert', String(wert)));
|
||
k.append(el('span', 'tz__schild', schild));
|
||
return k;
|
||
}
|
||
|
||
function karte(m) {
|
||
const k = el('article', 'tperson');
|
||
/* DIE KARTE TRAEGT DIE FARBE DER STUFE (10.09.2026).
|
||
|
||
Vorher fiel `--r` auf `--text-still` zurueck -- ein helles Grau,
|
||
aus dem das Kantenlicht einen fahlen Schleier ueber die ganze
|
||
Karte legte. Auf dem Bildschirmfoto sah sie neben den beiden
|
||
dunklen Spalten daneben aus wie ueberbelichtet.
|
||
|
||
Die Stufe als Farbe zu nehmen loest zwei Dinge auf einmal: Der
|
||
Schleier wird zu einem klaren Ton, und man sieht schon am Rand
|
||
der Karte, wer wo steht -- ohne das Schild zu lesen. */
|
||
k.dataset.stufe = m.stufe || 'probe';
|
||
if (!m.aktiv) k.dataset.pause = 'ja';
|
||
/* ==== DIE RECHTE HAND SIEHT ANDERS AUS (20.09.2026) ==============
|
||
|
||
Filipe: "die kiste von rechte hand soll auch noch vieeeeeel
|
||
krasser und spezieller aussehen wie die von den modis."
|
||
|
||
ALS MERKMAL AM ELEMENT, nicht als eigene Bauweise: Die Karte
|
||
bleibt dieselbe Karte -- gleiche Felder, gleiche Reihenfolge,
|
||
gleiche Knoepfe. Nur das Aussehen aendert sich, und das
|
||
entscheidet das CSS. Zwei verschiedene Karten zu bauen hiesse,
|
||
jede kuenftige Aenderung zweimal zu machen; beim dritten Mal
|
||
vergisst man eine.
|
||
|
||
`rolle` kam bis heute gar nicht erst mit -- die Abfrage im
|
||
Server las sie nicht einmal.
|
||
|
||
KEIN RUECKFALL AUF EINEN ROLLENNAMEN (21.09.2026). Hier stand
|
||
`m.rolle || 'modi'`. Der Rueckfall war doppelt daneben:
|
||
|
||
(1) Er stand in einer Datei, die JEDER herunterlaedt, der den
|
||
Arbeitsplatz oeffnet -- auch jeder Manager, Scout und
|
||
Creator auf der Agenturadresse, und zwar OHNE Anmeldung.
|
||
Rollennamen der Community gehoeren dort nicht hin; darueber
|
||
wacht eine eigene Pruefung im Serverordner.
|
||
|
||
(2) Er hat nie etwas bewirkt. Nachgemessen: Die Stilvorlage
|
||
kennt zu dieser Karte nur `[data-rolle="hand"]`. Fuer jeden
|
||
anderen Wert sieht sie gleich aus -- ob dort ein Rollenname
|
||
steht oder nichts.
|
||
|
||
Der Server liefert `rolle` seit dem 20.09. immer mit (die
|
||
Abfrage holt sie, und sie ist auf zwei Werte begrenzt). Fehlt
|
||
sie doch einmal, bleibt das Merkmal weg statt falsch. */
|
||
if (m.rolle) k.dataset.rolle = m.rolle;
|
||
|
||
const kopf = el('div', 'tperson__kopf');
|
||
/* DAS BILD BEKOMMT SEINE KLASSE -- UND DAS WAR EIN ECHTER FEHLER.
|
||
|
||
Hier stand ein blankes <img> IN der Buchstaben-Blase. Ein Bild
|
||
ohne Groessenangabe nimmt seine natuerliche -- und die Blase hat
|
||
kein `overflow: hidden`. Ergebnis: Das Profilfoto lief ueber die
|
||
ganze Kachel, quer ueber Zahlen und Balken. Filipe hat es im
|
||
Bildschirmfoto gesehen, bevor ich es gemessen hatte.
|
||
|
||
`tperson__bild` gibt es laengst und traegt alles Noetige: 44 px,
|
||
rund, `object-fit: cover`, Rand in der Rollenfarbe. Sie steht
|
||
neben der Blase, nicht darin -- entweder ein Bild ODER ein
|
||
Buchstabe, nie beides ineinander. */
|
||
let zeichen;
|
||
if (m.bild) {
|
||
zeichen = document.createElement('img');
|
||
zeichen.className = 'tperson__bild';
|
||
zeichen.src = m.bild;
|
||
zeichen.alt = '';
|
||
zeichen.loading = 'lazy';
|
||
} else {
|
||
zeichen = el('span', 'tperson__zeichen');
|
||
zeichen.textContent = (m.name || '?').trim().charAt(0).toUpperCase();
|
||
}
|
||
kopf.append(zeichen);
|
||
|
||
const text = el('div', 'tperson__text');
|
||
/* `tperson__name`, NICHT `tperson__namen` (22.09.2026).
|
||
|
||
Zwei Klassen, ein Buchstabe Unterschied, zwei voellig
|
||
verschiedene Aufgaben:
|
||
|
||
.tperson__name der NAME einer Person -- 1,06 rem, kraeftig,
|
||
volle Textfarbe
|
||
.tperson__namen eine Aufzaehlung von Namen im Kleingedruckten
|
||
("Wen genau") -- 0,84 rem, grau, leise
|
||
|
||
Hier stand die zweite. Der Name der Person auf einer Seite, die
|
||
von Personen handelt, war damit kleiner und blasser als die
|
||
Beschriftung darunter. Aufgefallen ist es nicht beim Lesen des
|
||
Codes -- dort sieht ein "n" wie ein Tippfehler aus, den man
|
||
ueberliest --, sondern weil pruef-teamlage-karten.mjs die Namen
|
||
nicht finden konnte und ueberall "?" meldete.
|
||
|
||
Dass jemand es geahnt hat, steht in module.css: Dort faerbt eine
|
||
Regel ausdruecklich `.tperson[data-rolle="hand"]
|
||
.tperson__namen` golden. Sie faerbte also den Namen ueber die
|
||
falsche Klasse nach -- und deckte damit die Ursache zu. Beide
|
||
Klassen stehen dort jetzt nebeneinander.
|
||
|
||
Die beiden anderen Stellen in dieser Datei (Titel einer
|
||
Rueckmeldung, Titel eines Angebots) bleiben bei
|
||
`tperson__namen`: Dort IST es Kleingedrucktes. */
|
||
text.append(el('p', 'tperson__name', m.name));
|
||
/* WANN ZULETZT DA -- als Tatsache, nicht als Vorwurf. Wer zwei
|
||
Wochen nicht da war, hat vielleicht Urlaub; die Zahl sagt nur,
|
||
dass man nachfragen koennte. */
|
||
const her = tageHer(m.letzter_login);
|
||
text.append(el('span', 'tperson__zuletzt', her === null
|
||
? 'war noch nie angemeldet'
|
||
: her === 0 ? 'heute da gewesen'
|
||
: her === 1 ? 'gestern da gewesen' : `vor ${her} Tagen da gewesen`));
|
||
kopf.append(text);
|
||
k.append(kopf);
|
||
|
||
/* ===================================================================
|
||
DIE MITGLIEDER-KACHEL AUS KAPITEL 4 (10.09.2026)
|
||
|
||
Der Blueprint nennt dort Name und Foto, Zustaendigkeiten, den
|
||
Status (aktiv/pausiert), die Zahl offener Aufgaben, das Datum
|
||
des Eintritts und Schnellaktionen. Name, Foto und die Zahlen
|
||
standen schon; Status, "dabei seit" und die Stufe kommen hier
|
||
dazu.
|
||
================================================================ */
|
||
const merkmale = el('div', 'tperson__merkmale');
|
||
|
||
/* PAUSIERT ZUERST. Wer pausiert ist, erklaert jede andere Zahl
|
||
darunter -- null Aufgaben sind dann kein Grund nachzufragen. */
|
||
if (!m.aktiv) merkmale.append(el('span', 'tmerkmal tmerkmal--pause', 'pausiert'));
|
||
|
||
/* DIE ROLLE STEHT JETZT DA (22.09.2026).
|
||
|
||
Filipe: „und auch den rollen entsprechend reihenfolge. nicht
|
||
einfach so sondern die rolle soll die reihenfolge ausmachen."
|
||
|
||
GEMESSEN WAR DIE REIHENFOLGE SCHON RICHTIG -- die Abfrage
|
||
sortiert seit Langem nach ROLLEN_SORTIERUNG. Auf der Karte stand
|
||
aber nur die STUFE („Probe"), nie die Rolle. Eine Ordnung, die
|
||
man nicht sehen kann, sieht aus wie Zufall; genau deshalb hat er
|
||
sie nicht wiedererkannt.
|
||
|
||
Dieselbe Sache wie bei der ersten Bewerbung heute frueh: Erst
|
||
das Wort macht aus einer Reihenfolge eine Auskunft. */
|
||
merkmale.append(el('span', `tmerkmal tmerkmal--rolle tmerkmal--r-${m.rolle || 'modi'}`,
|
||
ROLLEN_WORT[m.rolle] || m.rolle || 'Modi'));
|
||
|
||
/* Die Stufe nur, wo es eine gibt. Bei der rechten Hand stand
|
||
bisher „Probe" -- sie hat aber gar keine Stufe, und der Server
|
||
lehnt jede Aenderung daran mit 404 ab. Ein Wort, das niemand
|
||
setzen kann, ist keine Auskunft, sondern eine Irrefuehrung. */
|
||
if (m.stufe_aenderbar) {
|
||
/* `tmerkmal--stufe` IST DER GRIFF, an dem stufeSetzen() diese
|
||
Marke spaeter wiederfindet. Bis eben suchte es mit
|
||
`[class*="tmerkmal--"]:not(...)` -- einer Aufzaehlung dessen,
|
||
was es NICHT ist. Kaum stand die Rollenmarke davor, traf die
|
||
Suche sie: Ein Druck auf "Standard" haette aus "Modi" das Wort
|
||
"Standard" gemacht. Ein Griff, der sagt, was er ist, kann das
|
||
nicht passieren. */
|
||
merkmale.append(el('span',
|
||
`tmerkmal tmerkmal--stufe tmerkmal--${m.stufe || 'probe'}`,
|
||
stufenName(m.stufe)));
|
||
}
|
||
|
||
if (m.seit) {
|
||
const tage = tageHer(m.seit);
|
||
merkmale.append(el('span', 'tmerkmal tmerkmal--leise',
|
||
tage === null ? 'dabei'
|
||
: tage < 1 ? 'seit heute dabei'
|
||
: tage < 31 ? `seit ${tage} Tagen dabei`
|
||
: `seit ${Math.round(tage / 30.4)} Monaten dabei`));
|
||
}
|
||
k.append(merkmale);
|
||
|
||
/* ===================================================================
|
||
DIE VERFUEGBARKEITS-AMPEL (Blueprint 7.1)
|
||
|
||
"Owner/rechte Hand sehen nur die Verfuegbarkeits-Ampel, nicht den
|
||
Klartext." Sieben Tage, drei Farben, kein Wort darueber, was
|
||
jemand vorhat -- der Server schickt auch gar nichts anderes.
|
||
|
||
WOCHENTAGSBUCHSTABE STATT DATUM: Sieben Datumsangaben
|
||
nebeneinander liest niemand. Der Buchstabe reicht, um "Donnerstag
|
||
ist dicht" zu erkennen; das genaue Datum steht im Titel, den man
|
||
beim Draufzeigen bekommt.
|
||
|
||
DER TITEL SAGT NICHT, WAS ANLIEGT, sondern nur wie viel: "3
|
||
Termine, 2 Std 30". Das ist die Grenze aus dem Blueprint, und sie
|
||
laeuft hier mitten durch einen Satz -- deshalb steht sie hier
|
||
noch einmal. */
|
||
if (Array.isArray(m.verfuegbar) && m.verfuegbar.length) {
|
||
const woche = el('div', 'tampel');
|
||
/* DIE ÜBERSCHRIFT SAGTE „Kann heute" ÜBER SIEBEN TAGEN
|
||
(11.09.2026).
|
||
|
||
Darunter stand eine ganze Woche. Wer nur die Überschrift liest
|
||
-- und das tut man bei einer Zeile in Versalien --, hält den
|
||
ganzen Streifen für den heutigen Tag und liest sechs Kästchen
|
||
falsch. Der Satz DARUNTER war immer richtig; er spricht
|
||
ausdrücklich von heute. Zwei Aussagen über demselben Bild, und
|
||
die auffälligere war die falsche.
|
||
|
||
Gefunden nicht beim Lesen des Codes, sondern beim Nachmessen
|
||
am fertigen Bildschirm: sieben Kästchen, eine Überschrift im
|
||
Singular. */
|
||
woche.append(el('span', 'tampel__schild', 'Die nächsten sieben Tage'));
|
||
const streifen = el('div', 'tampel__woche');
|
||
m.verfuegbar.forEach((t, i) => {
|
||
const d = new Date(`${t.tag}T12:00:00Z`);
|
||
const tag = el('span', `tampel__tag tampel__tag--${t.ampel}`,
|
||
['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'][d.getUTCDay()]);
|
||
/* HEUTE WIRD MARKIERT. Ohne das sind es sieben gleich aussehende
|
||
Kästchen, und man muss den Wochentag im Kopf ausrechnen, um zu
|
||
wissen, wo man steht. Der erste Eintrag IST heute -- die
|
||
Abfrage im Server beginnt dort. */
|
||
if (i === 0) tag.dataset.heute = 'ja';
|
||
const dauer = t.minuten
|
||
? `${Math.floor(t.minuten / 60)} Std ${String(t.minuten % 60).padStart(2, '0')}`
|
||
: null;
|
||
tag.title = `${d.toLocaleDateString('de-DE', { weekday: 'long', day: 'numeric', month: 'long' })}`
|
||
+ (t.termine ? ` · ${t.termine} ${t.termine === 1 ? 'Termin' : 'Termine'}, ${dauer}` : ' · nichts eingetragen');
|
||
streifen.append(tag);
|
||
});
|
||
woche.append(streifen);
|
||
/* Der Satz sagt, was die Farben bedeuten -- einmal je Karte, nicht
|
||
als Legende irgendwo. Wer die Seite zum ersten Mal sieht, soll
|
||
nicht raten. */
|
||
const heuteWert = m.verfuegbar[0];
|
||
woche.append(el('span', 'tampel__satz',
|
||
heuteWert.ampel === 'frei' ? 'Heute nichts eingetragen – ansprechbar.'
|
||
: heuteWert.ampel === 'teils' ? 'Heute teilweise verplant.'
|
||
: 'Heute weitgehend verplant.'));
|
||
k.append(woche);
|
||
}
|
||
|
||
/* DIE STUFE SETZEN -- nur DogFather. Die rechte Hand sieht sie,
|
||
aendert sie aber nicht: Wer im Team auf welcher Stufe steht, ist
|
||
eine Personalentscheidung (Blueprint 4.1, "Rechte anpassen"
|
||
liegt beim Owner). Der Server sagt dasselbe noch einmal. */
|
||
/* UND NUR DORT, WO ES EINE STUFE GIBT (22.09.2026).
|
||
|
||
Bis heute standen die drei Knoepfe an JEDER Karte -- auch an der
|
||
der rechten Hand, die gar keine Stufe hat. Der Server antwortete
|
||
mit 404, und der 404 warf von der Seite. Zwei Fehler
|
||
hintereinander, und der sichtbare war der zweite.
|
||
|
||
`stufe_aenderbar` kommt vom Server, aus derselben Funktion, die
|
||
auch den Weg zum Setzen absichert. Eine Rollenabfrage hier waere
|
||
die zweite Wahrheit. */
|
||
if (ich?.rolle === 'admin' && m.stufe_aenderbar) {
|
||
const wahl = el('div', 'tstufen');
|
||
wahl.append(el('span', 'tstufen__schild', 'Stufe'));
|
||
for (const st of STUFEN) {
|
||
const b = el('button', 'tstufen__knopf', st.name);
|
||
b.type = 'button';
|
||
b.dataset.stufe = st.wert;
|
||
if ((m.stufe || STUFEN[0]?.wert) === st.wert) b.dataset.an = 'ja';
|
||
b.addEventListener('click', () => stufeSetzen(wahl, m, st.wert));
|
||
wahl.append(b);
|
||
}
|
||
k.append(wahl);
|
||
}
|
||
|
||
/* SCHNELLAKTION: schreiben. `?mit=` legt das Gespraech beim ersten
|
||
Mal an und findet beim zweiten dasselbe wieder -- der Weg gibt es
|
||
seit dem 06.09. und wird hier nur benutzt, nicht neu gebaut.
|
||
Nachgemessen: Die rechte Hand und die Modis duerfen einander
|
||
schreiben, ein Manager ist fuer beide nicht erreichbar. */
|
||
/* EIN KNOPF, KEIN UNTERSTRICHENER SATZ (11.09.2026). Die Klasse
|
||
`schritt` zeichnet einen Textweg -- über die volle Breite einer
|
||
Karte gezogen sah er aus wie eine Überschrift mit Unterstrich.
|
||
`tschreiben` ist derselbe Weg, nur als Knopf erkennbar. */
|
||
const aktionen = el('div', 'tpipe__reihe');
|
||
const schreiben = el('a', 'tschreiben', `${m.name} schreiben`);
|
||
schreiben.href = `chat.html?mit=${m.id}`;
|
||
aktionen.append(schreiben);
|
||
k.append(aktionen);
|
||
|
||
const zahlen = el('div', 'tperson__zahlen');
|
||
zahlen.append(zahl(m.aufgaben.offen, 'Aufgaben offen'));
|
||
zahlen.append(zahl(m.aufgaben.ueberfaellig, 'überfällig'));
|
||
zahlen.append(zahl(m.ideen, 'Ideen'));
|
||
zahlen.append(zahl(m.nachrichten, 'Rückmeldungen'));
|
||
k.append(zahlen);
|
||
|
||
/* ===================================================================
|
||
DIE DREI BEREICHE ALS BALKEN (10.09.2026)
|
||
|
||
Hier standen bis eben drei weitere Zahlenkaesten ("40 Live-Ablauf:
|
||
offen") und darunter noch einmal drei Verweise mit denselben
|
||
Zahlen in Klammern. Sechs Kaesten fuer drei Aussagen -- und die
|
||
wichtigste stand in keinem davon: WIE WEIT sind wir?
|
||
|
||
"40 offen" beantwortet das nicht. Vierzig von vierzig ist nicht
|
||
dasselbe wie vierzig von hundert, und genau das ist die Frage,
|
||
die man beim Hinsehen hat.
|
||
|
||
Ein Balken beantwortet sie ohne Rechnen: gut und "verbessern"
|
||
fuellen ihn von links, der Rest ist offen. Zwei Farben, weil es
|
||
zwei verschiedene Dinge sind -- "besprochen und in Ordnung" und
|
||
"besprochen, da ist was". Beide sind ERLEDIGT im Sinne von
|
||
"angesehen", und deshalb fuellen beide den Balken.
|
||
|
||
Die ganze Zeile ist der Verweis. Vorher war es ein eigener Knopf
|
||
darunter; ein Balken, der aussieht wie eine Anzeige, aber klickbar
|
||
ist, muss auch wie ein Ziel aussehen -- deshalb Rahmen und
|
||
Hover. */
|
||
const fortschritt = el('div', 'tfort');
|
||
for (const b of BEREICHE) {
|
||
const z = m.bereiche[b.schluessel];
|
||
if (!z) continue;
|
||
const gesamt = Math.max(z.gesamt || 0, 1);
|
||
const beurteilt = (z.gut || 0) + (z.verbessern || 0);
|
||
|
||
const a = el('a', 'tfort__zeile');
|
||
a.href = `bereich.html?b=${b.schluessel}&creator_id=${m.id}`;
|
||
|
||
const kopfz = el('span', 'tfort__kopf');
|
||
kopfz.append(el('span', 'tfort__name', b.name));
|
||
kopfz.append(el('span', 'tfort__zahl', `${beurteilt} von ${z.gesamt}`));
|
||
a.append(kopfz);
|
||
|
||
const bahn = el('span', 'tfort__bahn');
|
||
const gut = el('i', 'tfort__gut');
|
||
gut.style.width = `${((z.gut || 0) / gesamt) * 100}%`;
|
||
const schlecht = el('i', 'tfort__verbessern');
|
||
schlecht.style.width = `${((z.verbessern || 0) / gesamt) * 100}%`;
|
||
bahn.append(gut, schlecht);
|
||
a.append(bahn);
|
||
|
||
/* Die Aufschluesselung in Worten -- fuer alle, die den Balken
|
||
nicht sehen koennen, und fuer die Faelle, in denen zwei
|
||
Prozentpunkte Unterschied nicht auffallen. */
|
||
a.append(el('span', 'tfort__unter',
|
||
z.verbessern
|
||
? `${z.gut} in Ordnung · ${z.verbessern} zu besprechen · ${z.offen} offen`
|
||
: `${z.gut} in Ordnung · ${z.offen} offen`));
|
||
fortschritt.append(a);
|
||
}
|
||
if (fortschritt.children.length) k.append(fortschritt);
|
||
|
||
/* UND HIER WIRD BEHANDELT. Ein Verweis je Bereich, der die Person
|
||
mitnimmt -- ohne sie muesste man dort noch einmal auswaehlen,
|
||
und beim zweiten Mal weiss man nicht mehr, warum man hier war. */
|
||
return k;
|
||
}
|
||
|
||
/* =====================================================================
|
||
DER EINGANG -- was die Modis gemeldet haben.
|
||
|
||
Er steht OBEN, vor den Karten: Das ist das, was eine Antwort
|
||
braucht. Die Zahlen darunter kann man sich ansehen; hier wartet
|
||
jemand.
|
||
|
||
ZWEI ANTWORTEN, und beide kommen bei ihm an. Eine Meldung, auf
|
||
die nichts folgt, sieht fuer ihn aus wie eine, die niemand gelesen
|
||
hat -- und beim dritten Mal meldet er nichts mehr.
|
||
===================================================================== */
|
||
/* ===================================================================
|
||
DIE STUFE SETZEN
|
||
|
||
Sofort sichtbar, dann speichern. Andersherum -- erst der Server,
|
||
dann die Anzeige -- fuehlt sich bei jedem Druck traege an, obwohl
|
||
nichts langsam ist. Schlaegt es fehl, springt die Auswahl zurueck
|
||
UND es steht eine Meldung da: Eine Anzeige, die sich lautlos
|
||
zurueckdreht, sieht wie ein Wackelkontakt aus.
|
||
================================================================ */
|
||
async function stufeSetzen(wahl, m, stufe) {
|
||
const knoepfe = [...wahl.querySelectorAll('.tstufen__knopf')];
|
||
const vorher = knoepfe.find((b) => b.dataset.an === 'ja')?.dataset.stufe || null;
|
||
if (vorher === stufe) return;
|
||
|
||
const zeigen = (wert) => {
|
||
for (const b of knoepfe) {
|
||
if (b.dataset.stufe === wert) b.dataset.an = 'ja';
|
||
else delete b.dataset.an;
|
||
}
|
||
};
|
||
zeigen(stufe);
|
||
for (const b of knoepfe) b.disabled = true;
|
||
try {
|
||
const a = await hole('/workspace/api/teamlage/stufe', {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ person_id: m.id, stufe }),
|
||
});
|
||
if (!a.ok) throw new Error('abgelehnt');
|
||
m.stufe = stufe;
|
||
/* Das Schild oben in derselben Karte zieht mit -- sonst stuende
|
||
dort die alte Stufe neben der neuen Auswahl. */
|
||
const schild = wahl.parentElement?.querySelector('.tmerkmal--stufe');
|
||
if (schild) {
|
||
schild.className = `tmerkmal tmerkmal--stufe tmerkmal--${stufe}`;
|
||
schild.textContent = stufenName(stufe);
|
||
}
|
||
/* UND DIE KARTE SELBST ZIEHT MIT (22.09.2026).
|
||
|
||
Die Stufe faerbt seit dem 10.09. die ganze Karte
|
||
(`[data-stufe]` setzt `--r`, und daraus kommen Kantenlicht,
|
||
Zeichen und Kopfband). Beim Setzen wurde bisher nur das
|
||
Schild getauscht -- die Karte blieb in der alten Farbe stehen,
|
||
bis jemand die Seite neu laedt. Mit drei blassen Toenen fiel
|
||
das kaum auf; mit fuenf kraeftigen ist es der Unterschied
|
||
zwischen "gespeichert" und "irgendwas stimmt nicht". */
|
||
wahl.closest('.tperson')?.setAttribute('data-stufe', stufe);
|
||
melde('');
|
||
} catch {
|
||
zeigen(vorher);
|
||
melde('Die Stufe konnte nicht gespeichert werden.');
|
||
} finally {
|
||
for (const b of knoepfe) b.disabled = false;
|
||
}
|
||
}
|
||
|
||
function eingangBauen(liste) {
|
||
const k = el('section', 'tperson');
|
||
k.append(el('p', 'feldschild', 'Rückmeldungen aus dem Team'));
|
||
if (!liste.length) {
|
||
k.append(el('p', 'tperson__zuletzt',
|
||
'Nichts offen – auf alles, was gemeldet wurde, habt ihr geantwortet.'));
|
||
return k;
|
||
}
|
||
for (const m of liste) {
|
||
const zeile = el('div', 'tpipe');
|
||
zeile.append(el('p', 'tperson__namen', m.titel));
|
||
if (m.begruendung) zeile.append(el('p', 'tperson__unter', m.begruendung));
|
||
zeile.append(el('span', 'tperson__zuletzt',
|
||
`${m.von || 'jemand'} · ${(m.am || '').slice(0, 10)}`));
|
||
|
||
const knoepfe = el('div', 'tpipe__reihe');
|
||
const melden = (t) => { melde(t); };
|
||
|
||
const ja = el('button', 'schritt', 'Daraus wird eine Aufgabe');
|
||
ja.type = 'button';
|
||
ja.addEventListener('click', async () => {
|
||
ja.disabled = true;
|
||
try {
|
||
const a = await hole('/workspace/api/teamlage/behandeln', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
bereich: m.bereich, schluessel: m.schluessel,
|
||
person_id: m.person_id, art: 'aufgabe',
|
||
}),
|
||
});
|
||
if (!a.ok) {
|
||
melden(sagWas((await a.json().catch(() => ({}))).fehler, 'Das hat nicht geklappt.'));
|
||
ja.disabled = false; return;
|
||
}
|
||
melden('');
|
||
await laden();
|
||
} catch { ja.disabled = false; }
|
||
});
|
||
knoepfe.append(ja);
|
||
|
||
const nein = el('button', 'abmelden', 'Nicht nötig');
|
||
nein.type = 'button';
|
||
nein.addEventListener('click', async () => {
|
||
/* EINE ABSAGE OHNE GRUND IST KEINE ANTWORT. Deshalb wird
|
||
gefragt -- und ein leeres Feld bricht ab, statt still nichts
|
||
zu tun.
|
||
|
||
WAR BIS ZUM 22.09.2026 EIN window.prompt(). Der letzte im
|
||
Haus, und er hatte drei Nachteile, die man erst am Handy
|
||
sieht: Er haelt die ganze Seite an, er sieht auf jedem
|
||
Browser anders aus als der Rest der Oberflaeche, und sein
|
||
Text laesst sich nicht gliedern -- Titel, Erklaerung und
|
||
Feld standen als ein Absatz untereinander.
|
||
|
||
`frageNach` ist derselbe Dialog wie an den 42 anderen
|
||
Stellen. Er kann ausserdem etwas, das prompt() nicht kann:
|
||
sagen, was BLEIBT, wenn man absagt. Genau das ist hier die
|
||
Frage, die jemand vor dem Klicken hat. */
|
||
const antwort = await window.frageNach({
|
||
titel: 'Nicht nötig?',
|
||
was: '„' + m.titel + '“',
|
||
bleibt: 'Die Meldung verschwindet aus der Liste. Dein Grund steht '
|
||
+ 'dabei, damit spaeter nachvollziehbar ist, warum.',
|
||
ja: 'Absagen',
|
||
grund: 'Warum machen wir das nicht?', grundPflicht: true,
|
||
platzhalter: 'z. B. Haben wir letzte Woche schon anders geloest.',
|
||
});
|
||
if (!antwort) return;
|
||
const grund = antwort.grund || '';
|
||
if (!grund.trim()) { melden('Ohne Grund bleibt die Meldung stehen.'); return; }
|
||
nein.disabled = true;
|
||
try {
|
||
const a = await hole('/workspace/api/teamlage/behandeln', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
bereich: m.bereich, schluessel: m.schluessel,
|
||
person_id: m.person_id, art: 'abgelehnt', grund: grund.trim(),
|
||
}),
|
||
});
|
||
if (!a.ok) {
|
||
melden(sagWas((await a.json().catch(() => ({}))).fehler, 'Das hat nicht geklappt.'));
|
||
nein.disabled = false; return;
|
||
}
|
||
melden('');
|
||
await laden();
|
||
} catch { nein.disabled = false; }
|
||
});
|
||
knoepfe.append(nein);
|
||
zeile.append(knoepfe);
|
||
k.append(zeile);
|
||
}
|
||
return k;
|
||
}
|
||
|
||
/* =====================================================================
|
||
DIE ANGEBOTE -- Vorschlaege, ueber die ihr entscheidet.
|
||
|
||
Sie stehen im selben Eingang wie die Rueckmeldungen: EIN Ort, an
|
||
dem etwas auf eine Antwort wartet. Wer zwei Eingaenge hat, sieht
|
||
irgendwann in den falschen.
|
||
===================================================================== */
|
||
const AUFWAND = { hoch: 'viel Aufwand', mittel: 'mittlerer Aufwand', niedrig: 'wenig Aufwand' };
|
||
|
||
async function entscheiden(knopf, koerper) {
|
||
knopf.disabled = true;
|
||
try {
|
||
const a = await hole('/workspace/api/teamlage/angebot', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(koerper),
|
||
});
|
||
if (!a.ok) {
|
||
melde(sagWas((await a.json().catch(() => ({}))).fehler, 'Das hat nicht geklappt.'));
|
||
knopf.disabled = false; return;
|
||
}
|
||
const d = await a.json().catch(() => ({}));
|
||
melde(d.aufgaben ? (d.aufgaben === 1 ? 'Eine Aufgabe angelegt.' : `${d.aufgaben} Aufgaben angelegt.`) : '');
|
||
await laden();
|
||
} catch { knopf.disabled = false; }
|
||
}
|
||
|
||
function angeboteBauen(liste) {
|
||
const k = el('section', 'tperson');
|
||
k.append(el('p', 'feldschild', 'Angebote aus dem Team'));
|
||
if (!liste.length) {
|
||
k.append(el('p', 'tperson__zuletzt', 'Gerade liegt kein Vorschlag zur Entscheidung.'));
|
||
return k;
|
||
}
|
||
for (const a of liste) {
|
||
const zeile = el('div', 'tpipe');
|
||
zeile.append(el('p', 'tperson__namen', a.titel));
|
||
if (a.text) zeile.append(el('p', 'tperson__unter', a.text));
|
||
/* WAS ES FUER DICH HEISST, steht direkt dabei -- sonst muss man
|
||
die Entscheidung treffen, ohne den eigenen Teil zu kennen. */
|
||
if (a.einsatz) {
|
||
zeile.append(el('p', 'tperson__unter', `Dein Teil: ${a.einsatz}`));
|
||
}
|
||
zeile.append(el('span', 'tperson__zuletzt',
|
||
`${a.von_name || 'jemand'} · Nutzen ${a.bewertung ?? '–'}/5 · `
|
||
+ `${AUFWAND[a.dringlichkeit] || a.dringlichkeit}`));
|
||
|
||
const knoepfe = el('div', 'tpipe__reihe');
|
||
const ja = el('button', 'schritt', 'Annehmen');
|
||
ja.type = 'button';
|
||
ja.addEventListener('click', () => entscheiden(ja, { id: a.id, art: 'angenommen' }));
|
||
knoepfe.append(ja);
|
||
|
||
const nein = el('button', 'abmelden', 'Nicht umsetzen');
|
||
nein.type = 'button';
|
||
nein.addEventListener('click', async () => {
|
||
/* Derselbe Dialog wie ueberall sonst (22.09.2026). Vorher ein
|
||
window.prompt() -- der zweite und letzte im Haus. */
|
||
const antwort = await window.frageNach({
|
||
titel: 'Nicht umsetzen?',
|
||
was: '„' + a.titel + '“',
|
||
bleibt: 'Das Angebot verschwindet aus der Liste. Dein Grund steht '
|
||
+ 'dabei, damit die Person weiss, woran es lag.',
|
||
ja: 'Nicht umsetzen',
|
||
grund: 'Warum nicht?', grundPflicht: true,
|
||
platzhalter: 'z. B. Passt gerade nicht zum Plan – vielleicht spaeter.',
|
||
});
|
||
if (!antwort) return;
|
||
const grund = antwort.grund || '';
|
||
if (!grund.trim()) { melde('Ohne Grund bleibt das Angebot stehen.'); return; }
|
||
entscheiden(nein, { id: a.id, art: 'abgelehnt', grund: grund.trim() });
|
||
});
|
||
knoepfe.append(nein);
|
||
zeile.append(knoepfe);
|
||
k.append(zeile);
|
||
}
|
||
return k;
|
||
}
|
||
|
||
async function laden() {
|
||
const ziel = $('lage');
|
||
try {
|
||
const a = await hole('/workspace/api/teamlage');
|
||
if (!a.ok) { melde('Die Lage konnte nicht geladen werden.'); return; }
|
||
const daten = await a.json();
|
||
/* VOR dem Bauen der Karten: Sie fragen die Liste ab. Stuende
|
||
diese Zeile darunter, waere die Stufe beim ersten Aufbau leer
|
||
und erst nach dem naechsten Laden da -- ein Fehler, den man
|
||
beim Entwickeln nie sieht, weil man ohnehin neu laedt. */
|
||
STUFEN = Array.isArray(daten.stufen) && daten.stufen.length ? daten.stufen : STUFEN;
|
||
ziel.textContent = '';
|
||
ziel.setAttribute('aria-busy', 'false');
|
||
|
||
const leute = daten.leute || [];
|
||
const offenGesamt = leute.reduce((s, m) =>
|
||
s + Object.values(m.bereiche).reduce((x, b) => x + b.offen, 0), 0);
|
||
/* Eine leere Seite ohne Erklaerung sieht aus wie ein Fehler --
|
||
deshalb steht hier auch, was zu tun waere. */
|
||
const wartend = (daten.eingang || []).length + (daten.angebote || []).length;
|
||
$('kopf-unter').textContent = leute.length
|
||
? `${leute.length} ${leute.length === 1 ? 'Person' : 'Personen'} im Team`
|
||
: 'Noch niemand im Team. Zugänge legst du unter „Personen & Zugänge“ an.';
|
||
|
||
/* ===================================================================
|
||
DIE DREI ZAHLEN, DIE DIE SEITE BEANTWORTET (10.09.2026)
|
||
|
||
Sie standen als Fliesstext in einer Zeile: "1 Person · 0
|
||
Rueckmeldungen warten · 101 Punkte noch nicht beurteilt".
|
||
Genau das sind aber die drei Fragen, wegen derer man diese
|
||
Seite ueberhaupt aufmacht -- und in einem Satz muss man sie
|
||
lesen statt zu sehen.
|
||
|
||
DIE DRITTE BEKOMMT EINEN BALKEN. "101 noch nicht beurteilt"
|
||
beantwortet nicht, wie weit man ist: Hundertein von hundertzwei
|
||
ist etwas anderes als hundertein von dreihundert. Der Balken
|
||
beantwortet es ohne Rechnen.
|
||
|
||
BEI NIEMANDEM IM TEAM bleibt die Leiste ganz weg. Drei Nullen
|
||
mit Balken auf null sehen aus wie ein Fehler; der Satz oben
|
||
sagt dann, was zu tun ist. */
|
||
const punkteGesamt = leute.reduce((sum, m) =>
|
||
sum + Object.values(m.bereiche).reduce((x, b) => x + (b.gesamt || 0), 0), 0);
|
||
const beurteilt = Math.max(punkteGesamt - offenGesamt, 0);
|
||
const pausiert = leute.filter((m) => !m.aktiv).length;
|
||
|
||
if (leute.length) {
|
||
const leiste = el('div', 'tkopfzahlen');
|
||
|
||
const eins = el('div', 'tkz');
|
||
eins.append(el('strong', 'tkz__wert', String(leute.length)));
|
||
eins.append(el('span', 'tkz__schild',
|
||
pausiert ? `im Team · ${pausiert} pausiert` : 'im Team'));
|
||
leiste.append(eins);
|
||
|
||
const zwei = el('div', 'tkz');
|
||
if (wartend) zwei.dataset.dran = 'ja';
|
||
zwei.append(el('strong', 'tkz__wert', String(wartend)));
|
||
zwei.append(el('span', 'tkz__schild',
|
||
wartend === 1 ? 'wartet auf dich' : 'warten auf dich'));
|
||
leiste.append(zwei);
|
||
|
||
const drei = el('div', 'tkz');
|
||
drei.append(el('strong', 'tkz__wert', `${beurteilt}`));
|
||
drei.append(el('span', 'tkz__schild', `von ${punkteGesamt} Punkten besprochen`));
|
||
const bahn = el('span', 'tkz__bahn');
|
||
const voll = el('i', '');
|
||
voll.style.width = `${punkteGesamt ? (beurteilt / punkteGesamt) * 100 : 0}%`;
|
||
bahn.append(voll);
|
||
drei.append(bahn);
|
||
leiste.append(drei);
|
||
|
||
ziel.append(leiste);
|
||
}
|
||
|
||
/* ===================================================================
|
||
ZWEI REIHEN STATT EINER (10.09.2026)
|
||
|
||
Vorher lagen alle fuenf Kaesten in EINEM Raster: die beiden
|
||
Wartelisten und je eine Karte pro Person. Zwei Folgen, beide
|
||
schlecht:
|
||
|
||
* DIE SPALTEN WURDEN ZU SCHMAL. Bei fuenf nebeneinander blieb
|
||
jeder rund 150 px, und im Bildschirmfoto stand "Community
|
||
12 von" -- abgeschnitten mitten in der Zahl.
|
||
* DIE LAENGSTE BESTIMMTE DIE HOEHE ALLER. Zehn Rueckmeldungen
|
||
machten die Reihe so hoch wie zehn Rueckmeldungen; daneben
|
||
standen drei kurze Karten in sehr viel Luft.
|
||
|
||
Es sind auch zwei verschiedene Dinge: oben, was eine ANTWORT
|
||
braucht, unten, wer im Team ist. Getrennte Reihen sagen das,
|
||
ohne es hinzuschreiben. */
|
||
const wartet = el('div', 't-wartet');
|
||
wartet.append(eingangBauen(daten.eingang || []));
|
||
wartet.append(angeboteBauen(daten.angebote || []));
|
||
ziel.append(wartet);
|
||
|
||
/* ===================================================================
|
||
NACH ROLLE GRUPPIERT, MIT UEBERSCHRIFT (22.09.2026)
|
||
|
||
„die rolle soll die reihenfolge ausmachen."
|
||
|
||
Die Reihenfolge kam schon vom Server (ROLLEN_SORTIERUNG). Was
|
||
fehlte, war die Zaesur: Vier Karten hintereinander sehen
|
||
gleich aus, egal in welcher Reihenfolge sie stehen. Eine
|
||
Ueberschrift je Rolle macht aus der Sortierung eine sichtbare
|
||
Gliederung -- und sagt nebenbei, wie viele es je Rolle gibt.
|
||
|
||
DIE GRUPPEN ENTSTEHEN AUS DER LISTE, nicht aus einer
|
||
Aufzaehlung hier: Wer eine Rolle hinzufuegt, bekommt ihre
|
||
Gruppe von selbst. Eine feste Liste waere die, die beim
|
||
naechsten Rollenzuschnitt eine Gruppe verschluckt -- und die
|
||
Leute darin mit ihr.
|
||
|
||
Bei nur EINER Gruppe faellt die Ueberschrift weg: Dann ist sie
|
||
keine Gliederung, sondern eine Zeile, die sagt, was man sieht. */
|
||
const nachRolle = [];
|
||
for (const m of leute) {
|
||
const letzte = nachRolle[nachRolle.length - 1];
|
||
if (letzte && letzte.rolle === m.rolle) letzte.leute.push(m);
|
||
else nachRolle.push({ rolle: m.rolle, leute: [m] });
|
||
}
|
||
for (const gruppe of nachRolle) {
|
||
if (nachRolle.length > 1) {
|
||
const kopf2 = el('h2', 't-gruppe');
|
||
kopf2.append(el('span', 't-gruppe__wort',
|
||
ROLLEN_WORT[gruppe.rolle] || gruppe.rolle || 'Team'));
|
||
kopf2.append(el('span', 't-gruppe__zahl', String(gruppe.leute.length)));
|
||
kopf2.dataset.rolle = gruppe.rolle || 'modi';
|
||
ziel.append(kopf2);
|
||
}
|
||
const runde = el('div', 't-leute');
|
||
for (const m of gruppe.leute) runde.append(karte(m));
|
||
ziel.append(runde);
|
||
}
|
||
} catch { /* umgeleitet */ }
|
||
}
|
||
|
||
(async () => {
|
||
try {
|
||
const a = await hole('/workspace/api/ich');
|
||
if (!a.ok) { location.assign('/workspace/'); return; }
|
||
/* Wer zusieht, entscheidet, ob die Stufenwahl erscheint. Die
|
||
Antwort wurde bisher nur weitergereicht -- gemerkt wird sie
|
||
erst, seit es hier etwas gibt, das nicht jeder darf.
|
||
|
||
DER SERVER ENTSCHEIDET TROTZDEM: Diese Zeile blendet nur aus.
|
||
Wer den Aufruf von Hand nachbaut, bekommt 404 -- das steht in
|
||
workspace-teamlage.js und nicht hier. */
|
||
ich = await a.json();
|
||
window.werZeigen?.(ich);
|
||
} catch {
|
||
/* NICHT MEHR STUMM (20.09.2026) -- siehe meldung.js. */
|
||
window.ladefehler?.();
|
||
return;
|
||
}
|
||
$('abmelden')?.addEventListener('click', async () => {
|
||
try { await fetch('/workspace/api/abmelden', { method: 'POST', credentials: 'same-origin' }); }
|
||
catch { /* egal */ }
|
||
location.assign('/workspace/');
|
||
});
|
||
await laden();
|
||
})();
|
||
})();
|