Filipe, 22.09.2026: "ersetze die zentrale durch, Die IrrenAnstalt.
genau so geschrieben bitte. und alles was treff heißt oder wo treff
steht soll durch Rudel ersetzt werden bitte."
Die Schreibweise "IrrenAnstalt" mit grossem A in der Mitte ist so
gewollt. Das steht als Hinweis daneben, damit sie beim naechsten Mal
niemand "korrigiert".
WAS UMBENANNT WURDE: alles, was jemand LIEST -- Kachelnamen,
Ueberschriften, Markenzeilen, Saetze, Meldungen, Aufgabenvorlagen.
116 Zeilen in 42 Dateien.
WAS BLEIBT: Adressen (treff-regeln.html), Bezeichner (TREFF_ROLLEN),
Datenbankwerte (bereich = 'treff'), Abfrageteile (b=treff). Eine
Adresse umzubenennen bricht jedes Lesezeichen, und ein Datenbankwert
umzuschreiben waere eine Umstellung ohne Gegenwert. Dieselbe
Entscheidung wie heute frueh bei Dogi-Media, wo material.html auch
material.html geblieben ist.
DREI GRAMMATIKFEHLER IM EIGENEN ENTWURF, alle vor dem Ausliefern
gefunden -- ein blindes Ersetzen reicht hier nicht:
1. "der Treff" ist maennlich, "das Rudel" saechlich. Ohne Tabelle
waere ueberall "Der Rudel" gestanden. (Und "Treffer" waere zu
"Rudeler" geworden, "Treffen" zu "Rudelen" -- deshalb greift die
Regel nur bei Wortgrenze und nie vor einem Kleinbuchstaben.)
2. BINDESTRICH-ZUSAMMENSETZUNGEN. In "der Treff-Chat" gehoert der
Artikel zu "Chat", nicht zu "Treff". Der erste Durchlauf machte
daraus "das Rudel-Chat", "das Rudel-Kacheln" und "ein Rudel-Raum".
Jetzt greift die Artikelregel nur, wenn das Wort allein steht.
3. GESCHUETZTE LEERZEICHEN. In den Markenzeilen steht
`Der Treff`, damit die zwei Woerter nicht umbrechen. Das
Muster hat daran vorbeigegriffen: "Der Rudel", auf fuenf
Seiten. Jetzt wird das Trennzeichen mitgefasst und unveraendert
wieder eingesetzt.
Gefunden wurden alle drei, weil jede einzelne der 116 Zeilen vor dem
Schreiben als ALT/NEU ausgegeben und gelesen wurde -- und danach
gezielt nach falschen Artikeln gesucht ("den Rudel", "der Rudel",
"einen Rudel"). Uebrig blieben sechs Treffer, und die sind alle
richtig: "einen Rudel-Raum", "der Rudel-Chat", "den Rudel-Regeln" --
Zusammensetzungen, bei denen der Artikel zum letzten Wort gehoert.
UND ZWEI PRUEFUNGEN, die die Umbenennung nicht mitbekommen haetten:
`/Treff/.test(...)` und `/Treff/i.test(...)`. Ein regulaerer Ausdruck
ist keine Zeichenkette -- sie haetten ab sofort nach einem Wort
gesucht, das die Seite nicht mehr sagt, und waeren still rot geworden,
ohne dass etwas kaputt ist.
Geprueft, alle gruen: pruef-treff, pruef-treffchat,
pruef-deutsche-texte, pruef-uebernahme, pruef-crew-adresse,
pruef-willkommen, pruef-wege-nach-draussen, pruef-vorlagen,
pruef-start-ansicht, pruef-struktur.
Co-Authored-By: Claude Opus 5 <[email protected]>
330 lines
13 KiB
JavaScript
330 lines
13 KiB
JavaScript
/* =====================================================================
|
||
DER EINGANG FÜR DIE ANFRAGEN AUS DER COMMUNITY (17.09.2026)
|
||
|
||
Filipe: „und dan soll ich annehmen ablehnen, in warteschlange stellen
|
||
oder sofort kommunizieren können."
|
||
|
||
VIER WEGE, EINE ANTWORT. Die Reihenfolge kommt vom Server und ist
|
||
dort begründet: „Reden" steht vorne, weil die häufigste richtige
|
||
Antwort auf eine Bewerbung eine Rückfrage ist und nicht eine
|
||
Entscheidung. Stünde „Annehmen" oben, würde es geklickt.
|
||
|
||
„SOFORT KOMMUNIZIEREN" HEISST HIER: EIN SATZ, DEN SIE LIEST.
|
||
|
||
Nicht ein Chat -- ein Zugang aus der Community steht ausdrücklich
|
||
nicht in den Chatlisten (AUSSEN_ROLLEN, 17.09.). Es gibt also genau
|
||
einen Weg, dieser Person etwas zu sagen: den Satz, der zur
|
||
Entscheidung gehört. Er erscheint auf ihrer eigenen Seite. Deshalb
|
||
ist er bei „Reden" und „Ablehnen" Pflicht -- welche das sind, steht
|
||
im Katalog und nicht hier.
|
||
|
||
WAS DIESE SEITE NICHT TUT: sortieren lassen, filtern, suchen. Die
|
||
Reihenfolge ist die Arbeitsanweisung (neu zuerst, dann was liegt),
|
||
und wer sie umstellen kann, arbeitet irgendwann die bequemen zuerst
|
||
ab. Bei zwanzig Anfragen im Jahr ist Suchen ohnehin keine Frage.
|
||
===================================================================== */
|
||
(() => {
|
||
const $ = (id) => document.getElementById(id);
|
||
|
||
const melde = (t) => {
|
||
const f = $('fehler');
|
||
if (f) { f.textContent = t || ''; f.hidden = !t; }
|
||
};
|
||
|
||
const el = (art, klasse, text) => {
|
||
const k = document.createElement(art);
|
||
if (klasse) k.className = klasse;
|
||
if (text != null) k.textContent = text;
|
||
return k;
|
||
};
|
||
|
||
const ZUSTAND_WORT = {
|
||
neu: 'Neu', gespraech: 'Im Gespräch', warteschlange: 'Warteliste',
|
||
angenommen: 'Angenommen', abgelehnt: 'Abgelehnt',
|
||
};
|
||
|
||
/* WIE LANGE SIE SCHON LIEGT, in Worten. Eine Zahl allein sagt
|
||
nichts -- „seit 23 Tagen" sagt, dass etwas schiefläuft. */
|
||
const liegtSeit = (tage) => {
|
||
if (tage <= 0) return 'heute gekommen';
|
||
if (tage === 1) return 'seit gestern';
|
||
return 'liegt seit ' + tage + ' Tagen';
|
||
};
|
||
|
||
let daten = null;
|
||
/* WER BIN ICH? Nötig, um „Du kümmerst dich" von „jemand anderes
|
||
kümmert sich" zu unterscheiden. Ein Vergleich über den NAMEN wäre
|
||
der naheliegende Fehler: Zwei Leute dürfen gleich heißen, und dann
|
||
gibt der eine dem anderen die Anfrage aus der Hand. */
|
||
let ich = null;
|
||
|
||
const zeichnen = () => {
|
||
/* ---------- Die Zahlen oben ---------- */
|
||
const z = $('zahlen');
|
||
z.textContent = '';
|
||
z.hidden = false;
|
||
const offen = (daten.zahlen.neu || 0) + (daten.zahlen.gespraech || 0)
|
||
+ (daten.zahlen.warteschlange || 0);
|
||
const paare = [
|
||
['neu', 'Neu', daten.zahlen.neu || 0],
|
||
['offen', 'Offen insgesamt', offen],
|
||
['dabei', 'Angenommen', daten.zahlen.angenommen || 0],
|
||
];
|
||
for (const [art, schild, wert] of paare) {
|
||
const kasten = el('div', 'bilanz-zahl');
|
||
kasten.dataset.art = art;
|
||
kasten.append(el('strong', 'bilanz-zahl__wert', String(wert)));
|
||
kasten.append(el('span', 'bilanz-zahl__schild', schild));
|
||
z.append(kasten);
|
||
}
|
||
|
||
/* ---------- Die Anfragen ---------- */
|
||
const liste = $('liste');
|
||
liste.textContent = '';
|
||
|
||
if (!daten.bewerbungen.length) {
|
||
const leer = el('div', 'bw-leer');
|
||
leer.append(el('p', 'bw-leer__wort', 'Noch hat sich niemand gemeldet.'));
|
||
leer.append(el('p', 'leise',
|
||
'Der Weg dorthin steht im Rudel unter „Mitmachen“. Wenn dort '
|
||
+ 'nichts ankommt, liegt es fast nie an den Leuten, sondern '
|
||
+ 'daran, dass niemand weiß, dass es geht – sag es im Stream.'));
|
||
liste.append(leer);
|
||
return;
|
||
}
|
||
|
||
for (const b of daten.bewerbungen) {
|
||
const karte = el('article', 'bw-karte');
|
||
karte.dataset.zustand = b.zustand;
|
||
|
||
/* ---- Kopf ---- */
|
||
const kopf = el('header', 'bw-karte__kopf');
|
||
const links = el('div', 'bw-karte__wer');
|
||
links.append(el('h2', 'bw-karte__name', b.nenn_name || 'ohne Namen'));
|
||
const unter = el('p', 'bw-karte__unter');
|
||
unter.append(document.createTextNode(liegtSeit(b.tage)));
|
||
/* DER KONTONAME STEHT DABEI, aber klein und dahinter. Er ist
|
||
nötig, um die Person wiederzufinden -- gemeint ist sie aber
|
||
mit dem Namen, den sie selbst genannt hat. */
|
||
if (b.konto_name && b.konto_name !== b.nenn_name) {
|
||
unter.append(document.createTextNode(' · Zugang: ' + b.konto_name));
|
||
}
|
||
links.append(unter);
|
||
kopf.append(links);
|
||
kopf.append(el('span', 'bw-marke', ZUSTAND_WORT[b.zustand] || b.zustand));
|
||
karte.append(kopf);
|
||
|
||
/* ---- WER SICH KÜMMERT (17.09.2026) ------------------------------
|
||
|
||
Bis heute sahen DogFather und die rechte Hand jede Anfrage, und
|
||
niemand war benannt. Das ist nicht „geteilt", das ist „jeder
|
||
dachte, der andere macht es" -- und hier wartet ein Mensch
|
||
darauf.
|
||
|
||
MAN NIMMT SELBST. Kein „zuweisen": Das wäre eine Rangordnung,
|
||
und die gehört hier nicht hin. Wer Zeit hat, übernimmt.
|
||
|
||
DER UNBESETZTE ZUSTAND IST DER AUFFÄLLIGE. „Kümmert sich
|
||
niemand" steht als Knopf da, „Du kümmerst dich" als ruhige
|
||
Zeile. Andersherum wäre die Liste ein Feld aus Namen, in dem
|
||
die Lücke nicht auffällt -- und genau die Lücke ist die
|
||
Auskunft. */
|
||
if (b.zustand !== 'angenommen' && b.zustand !== 'abgelehnt') {
|
||
const kum = el('div', 'bw-kuemmern');
|
||
const meins = b.zustaendig_id === ich?.id;
|
||
if (b.zustaendig_id) kum.dataset.wer = meins ? 'ich' : 'anderer';
|
||
|
||
const knopf = el('button', 'bw-kuemmern__knopf');
|
||
knopf.type = 'button';
|
||
knopf.textContent = b.zustaendig_id
|
||
? (meins ? 'Du kümmerst dich · abgeben' : `${b.zustaendig_name} kümmert sich`)
|
||
: 'Ich kümmere mich';
|
||
/* Fremdes bleibt stehen -- wer es trotzdem will, lässt erst
|
||
abgeben. Ein Knopf, der fremdes Zuständigsein still
|
||
überschreibt, nimmt dem anderen die Anfrage aus der Hand. */
|
||
knopf.disabled = !!b.zustaendig_id && !meins;
|
||
knopf.addEventListener('click', async () => {
|
||
knopf.disabled = true;
|
||
melde('');
|
||
try {
|
||
const a2 = await fetch(`/workspace/api/bewerbung/${b.id}/kuemmern`, {
|
||
method: 'PUT', credentials: 'same-origin',
|
||
});
|
||
if (!a2.ok) {
|
||
const d = await a2.json().catch(() => ({}));
|
||
melde(sagWas(d.fehler, 'Ging nicht.'));
|
||
knopf.disabled = false;
|
||
return;
|
||
}
|
||
await laden();
|
||
} catch { melde('Ging nicht.'); knopf.disabled = false; }
|
||
});
|
||
kum.append(knopf);
|
||
if (b.zustaendig_seit) {
|
||
kum.append(el('span', 'bw-kuemmern__seit', 'seit ' + liegtSeit(
|
||
Math.max(0, Math.floor(
|
||
(Date.now() - Date.parse(b.zustaendig_seit)) / 86400000)))));
|
||
}
|
||
karte.append(kum);
|
||
}
|
||
|
||
/* ---- Was schon entschieden wurde ---- */
|
||
if (b.zustand !== 'neu' && (b.grund || b.von_name)) {
|
||
const alt = el('div', 'bw-karte__frueher');
|
||
if (b.grund) alt.append(el('p', 'bw-karte__grund', b.grund));
|
||
if (b.von_name) {
|
||
alt.append(el('p', 'leise', 'entschieden von ' + b.von_name));
|
||
}
|
||
karte.append(alt);
|
||
}
|
||
|
||
/* ---- Die Antworten, nach Gruppen ---- */
|
||
const bogen = el('div', 'bw-karte__bogen');
|
||
for (const g of daten.gruppen) {
|
||
const fragen = daten.fragen.filter(
|
||
(f) => f.gruppe === g.schluessel && b.antworten[f.schluessel]);
|
||
if (!fragen.length) continue;
|
||
bogen.append(el('h3', 'bw-karte__gruppe', g.name));
|
||
for (const f of fragen) {
|
||
const paar = el('div', 'bw-antwort');
|
||
paar.append(el('p', 'bw-antwort__frage', f.titel));
|
||
paar.append(el('p', 'bw-antwort__text', b.antworten[f.schluessel]));
|
||
bogen.append(paar);
|
||
}
|
||
}
|
||
/* WAS NICHT BEANTWORTET WURDE, WIRD GENANNT -- nicht verschwiegen.
|
||
Eine freiwillige Frage, die jemand überspringt, ist selbst eine
|
||
Auskunft; sie einfach wegzulassen versteckt sie. */
|
||
const offenFragen = daten.fragen.filter((f) => !b.antworten[f.schluessel]);
|
||
if (offenFragen.length) {
|
||
bogen.append(el('p', 'bw-karte__ohne',
|
||
'Ohne Antwort: ' + offenFragen.map((f) => f.titel).join(' · ')));
|
||
}
|
||
karte.append(bogen);
|
||
|
||
/* ---- Die Entscheidung ---- */
|
||
const wahl = el('div', 'bw-wahl');
|
||
const schild = el('p', 'bw-wahl__schild', 'Was passiert damit?');
|
||
wahl.append(schild);
|
||
|
||
const feldkasten = el('div', 'bw-wahl__feld');
|
||
feldkasten.hidden = true;
|
||
const feldSchild = el('label', 'bw-wahl__feldschild');
|
||
const feld = document.createElement('textarea');
|
||
feld.className = 'bw-wahl__eingabe';
|
||
feld.rows = 3;
|
||
feld.maxLength = 600;
|
||
feld.id = 'grund-' + b.id;
|
||
feldSchild.htmlFor = feld.id;
|
||
feldkasten.append(feldSchild, feld);
|
||
|
||
const reihe = el('div', 'bw-wahl__knoepfe');
|
||
let gewaehlt = null;
|
||
|
||
const senden = el('button', 'knopf', 'Übernehmen');
|
||
senden.type = 'button';
|
||
senden.hidden = true;
|
||
|
||
for (const w of daten.wege) {
|
||
const k = el('button', 'bw-weg', w.name);
|
||
k.type = 'button';
|
||
k.dataset.weg = w.schluessel;
|
||
k.setAttribute('aria-pressed', 'false');
|
||
k.title = w.text || '';
|
||
if (w.schluessel === b.zustand) k.disabled = true;
|
||
k.addEventListener('click', () => {
|
||
gewaehlt = w;
|
||
for (const a of reihe.querySelectorAll('.bw-weg')) {
|
||
a.setAttribute('aria-pressed', String(a === k));
|
||
}
|
||
/* OB EIN SATZ NÖTIG IST, sagt der Weg selbst -- die Regel
|
||
kommt aus derselben Antwort, die der Server prüft. */
|
||
feldkasten.hidden = false;
|
||
feldSchild.textContent = w.nachrichtSchild || 'Ein Satz dazu';
|
||
feld.placeholder = w.nachricht === 'pflicht' ? 'nötig' : 'kann leer bleiben';
|
||
senden.hidden = false;
|
||
senden.textContent = w.name + ' – übernehmen';
|
||
});
|
||
reihe.append(k);
|
||
}
|
||
|
||
senden.addEventListener('click', async () => {
|
||
if (!gewaehlt) return;
|
||
melde('');
|
||
const grund = feld.value.trim();
|
||
if (gewaehlt.nachricht === 'pflicht' && grund.length < (daten.nachricht_min || 10)) {
|
||
melde('Dazu gehört ein Satz – er steht danach auf ihrer Seite.');
|
||
feld.focus();
|
||
return;
|
||
}
|
||
senden.disabled = true;
|
||
senden.textContent = 'einen Moment …';
|
||
try {
|
||
const a = await fetch('/workspace/api/bewerbung/' + b.id + '/weg', {
|
||
method: 'PUT',
|
||
headers: { 'content-type': 'application/json' },
|
||
credentials: 'same-origin',
|
||
body: JSON.stringify({ weg: gewaehlt.schluessel, grund }),
|
||
});
|
||
const d = await a.json().catch(() => ({}));
|
||
if (!a.ok) {
|
||
melde(sagWas(d.fehler, 'Das hat nicht geklappt.'));
|
||
senden.disabled = false;
|
||
senden.textContent = gewaehlt.name + ' – übernehmen';
|
||
return;
|
||
}
|
||
/* NACH EINER ANNAHME steht der Weg zum Talent da -- und zwar
|
||
als Angebot, nicht als Sprung. Wer gerade fünf Anfragen
|
||
durchgeht, will nicht nach der ersten auf einer anderen
|
||
Seite landen. */
|
||
await laden();
|
||
if (d.weiter) {
|
||
const hin = el('p', 'bw-hinweis');
|
||
const a2 = el('a', 'schritt', 'Bei den Talenten ansehen');
|
||
a2.href = d.weiter.replace('/workspace/', '');
|
||
hin.append(document.createTextNode('Daraus wurde ein Talent. '));
|
||
hin.append(a2);
|
||
$('liste').prepend(hin);
|
||
}
|
||
} catch {
|
||
melde('Das hat nicht geklappt.');
|
||
senden.disabled = false;
|
||
senden.textContent = gewaehlt.name + ' – übernehmen';
|
||
}
|
||
});
|
||
|
||
wahl.append(reihe, feldkasten, senden);
|
||
karte.append(wahl);
|
||
liste.append(karte);
|
||
}
|
||
};
|
||
|
||
const laden = async () => {
|
||
if (!ich) {
|
||
try {
|
||
const i = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
|
||
if (i.ok) { ich = await i.json(); window.werZeigen?.(ich); }
|
||
} catch { /* dann bleibt der Vergleich aus -- kein Absturz */ }
|
||
}
|
||
let a;
|
||
try {
|
||
a = await fetch('/workspace/api/bewerbungen', { credentials: 'same-origin' });
|
||
} catch {
|
||
melde('Die Anfragen sind gerade nicht erreichbar.');
|
||
return;
|
||
}
|
||
if (a.status === 401) { location.assign('/workspace/'); return; }
|
||
if (a.status === 404 || a.status === 403) {
|
||
$('ladezeile').hidden = true;
|
||
melde('Diese Seite ist für DogFather und die rechte Hand.');
|
||
return;
|
||
}
|
||
if (!a.ok) { melde('Die Anfragen sind gerade nicht erreichbar.'); return; }
|
||
daten = await a.json();
|
||
$('ladezeile').hidden = true;
|
||
zeichnen();
|
||
};
|
||
|
||
laden();
|
||
})();
|