Wie geht's dir? bekommt eine eigene Seite
Filipe, zum Bildschirmfoto der Kachel: "ich will dass du diese seite
perfektionnierst den gerade wenn ich drauf druecke geht die
entwicklungsseite auf."
Der Fehler war schlimmer als ein falscher Verweis. Auf der Kachel
steht "die Antworten sieht nur du" -- und sie oeffnete eine Seite
voller Namen und Beobachtungen ueber ANDERE Menschen. Wer ihr glaubt
und drauftippt, sieht im ersten Moment das Gegenteil dessen, was
draufsteht.
Jetzt: eine Seite, ein Zweck.
workspace/befinden.html + assets/js/befinden.js die eigene Seite
server/workspace-befinden.js Rhythmus + Verlauf
entwicklung.html nur noch ein Verweis
Was die Recherche zu Puls-Abfragen ergeben hat (Quellen im Kopf des
Moduls): kurz halten (fuenf bis fuenfzehn Fragen -- sechs bleiben
sechs), WIEDERHOLEN (zweiwoechentlich), und der VERLAUF ist die
Auskunft, nicht der Einzelwert. Bisher beantwortete man die sechs
Fragen einmal, und die Antwort stand fuer immer -- ein Befinden von
vor drei Monaten ist keine Auskunft mehr, sondern ein Andenken.
Der Verlauf gehoert ihr allein: eigene Tabelle, kein von_id, kein
anderer Weg im Haus liest sie. Im Protokoll steht nur, DASS eine
Runde war, nie was darin stand. Die Ampel zaehlt weiterhin nur den
aktuellen Stand und kennt keine Namen.
Zwei Dinge, die erst die Pruefung gefunden hat:
- Wer die neue Seite oeffnete, ohne dass vorher jemand die
Entwicklungsseite besucht hatte, bekam "no such table" und eine
503. entwicklung_stand wird beim ersten Aufruf angelegt, nicht
beim Start. Die Tabelle wird jetzt angefordert, nicht ein zweites
Mal abgeschrieben.
- pruef-entwicklung suchte die Kachel ueber ziel ===
"entwicklung.html" und hat den gemeldeten Fehler damit
mitgetragen. Sie sucht jetzt die eigene Seite -- und prueft
zusaetzlich, dass keine Kachel mehr ersatzweise dorthin fuehrt.
Ausserdem weg: die tote Funktion meins() in entwicklung.js (sie
zeichnete in drei Stellen, die es nicht mehr gibt) und der Titel
"Wie geht's dir?", den diese Seite fuer einen Modi trug -- er
versprach etwas anderes als die Seite zeigt, also derselbe Fehler
wie an der Kachel.
server/pruef-befinden.mjs 48 Pruefungen, 0 Fehler (Port 4419)
pruef-entwicklung 43 (vorher 40), 0 Fehler
pruef-nachwuchs 123, pruef-rechtetafel 19, pruef-rechte-umstellen 46,
pruef-css-klassen -- alle 0 Fehler
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -427,3 +427,75 @@
|
||||
/* „Darüber seid ihr euch nicht einig“ ist keine Warnung, sondern eine
|
||||
Einladung zu fragen – deshalb leise und ohne Farbe. */
|
||||
.e-meine__uneinig { font-size: .78rem; color: var(--text-still); font-style: italic; }
|
||||
|
||||
/* =====================================================================
|
||||
WIE GEHT'S DIR? -- die eigene Seite (15.09.2026)
|
||||
===================================================================== */
|
||||
|
||||
/* Der Takt steht ueber den Fragen: Wer hereinkommt, soll zuerst
|
||||
wissen, ob gerade etwas von ihm erwartet wird. */
|
||||
.b-takt {
|
||||
margin: 0 0 22px; padding: 14px 17px;
|
||||
background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%);
|
||||
border: 1px solid var(--rand);
|
||||
border-left: 3px solid var(--rand-hell);
|
||||
display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
|
||||
}
|
||||
/* Faellig heisst: es ist Zeit. Kein Alarm -- eine Einladung. */
|
||||
.b-takt[data-lage="faellig"] { border-left-color: color-mix(in srgb, #8ec9ff 60%, transparent); }
|
||||
.b-takt[data-lage="bereit"] { border-left-color: color-mix(in srgb, #668e6e 60%, transparent); }
|
||||
.b-takt__satz {
|
||||
margin: 0; flex: 1 1 24ch; max-width: 70ch;
|
||||
font-size: .86rem; line-height: 1.6; color: var(--text-leise);
|
||||
}
|
||||
|
||||
.b-verlauf { margin: 34px 0 0; }
|
||||
.b-verlauf__satz {
|
||||
margin: 0 0 14px; max-width: 68ch;
|
||||
font-size: .85rem; line-height: 1.6; color: var(--text-leise);
|
||||
}
|
||||
.b-verlauf__gitter {
|
||||
display: grid; gap: 4px;
|
||||
padding: 14px 16px;
|
||||
background: color-mix(in srgb, var(--tinte) 94%, #ffffff 6%);
|
||||
border: 1px solid var(--rand);
|
||||
overflow-x: auto;
|
||||
}
|
||||
.b-verlauf__zeile {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(12ch, 1fr) repeat(auto-fit, minmax(2.4rem, 2.4rem));
|
||||
align-items: center; gap: 6px;
|
||||
}
|
||||
.b-verlauf__frage {
|
||||
font-size: .8rem; line-height: 1.35; color: var(--text-leise);
|
||||
padding-right: 8px;
|
||||
}
|
||||
.b-verlauf__datum {
|
||||
font-size: .72rem; color: var(--text-still); text-align: center;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.b-verlauf__zeile--kopf { margin-bottom: 4px; }
|
||||
/* Das Zeichen sagt die Antwort, die Farbe verstaerkt sie nur -- wer
|
||||
Farben schlecht sieht, liest dasselbe Zeichen. */
|
||||
.b-verlauf__punkt {
|
||||
display: grid; place-items: center;
|
||||
width: 2.2rem; height: 1.9rem;
|
||||
font-size: .9rem; font-weight: 700;
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-still);
|
||||
background: rgba(0, 0, 0, .25);
|
||||
border: 1px solid var(--rand);
|
||||
}
|
||||
.b-verlauf__punkt[data-stufe="laeuft"] { color: #7fb08a; border-color: color-mix(in srgb, #668e6e 55%, transparent); }
|
||||
.b-verlauf__punkt[data-stufe="waechst"] { color: #8ec9ff; border-color: color-mix(in srgb, #8ec9ff 45%, transparent); }
|
||||
.b-verlauf__punkt[data-stufe="hakt"] { color: #d8a76a; border-color: color-mix(in srgb, #cc9451 55%, transparent); }
|
||||
.b-verlauf__punkt[data-stufe="leer"] { opacity: .4; }
|
||||
|
||||
/* Der Verweis auf der Entwicklungsseite -- ruhig, er ist kein Angebot,
|
||||
sondern ein Hinweis. */
|
||||
.e-meins--verweis { margin-top: 30px; }
|
||||
|
||||
@media (max-width: 560px) {
|
||||
.b-verlauf__zeile { grid-template-columns: minmax(9ch, 1fr) repeat(auto-fit, minmax(2rem, 2rem)); }
|
||||
.b-verlauf__punkt { width: 1.9rem; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/* =====================================================================
|
||||
Wie geht's dir? (15.09.2026)
|
||||
|
||||
Eine Seite, ein Zweck. Vorher führte die Kachel auf die
|
||||
Entwicklungsseite — also auf Namen und Beobachtungen über andere
|
||||
Menschen, während auf der Kachel „die Antworten sieht nur du" stand.
|
||||
|
||||
---------------------------------------------------------------------
|
||||
DREI DINGE, DIE DIESE SEITE ANDERS MACHT ALS EIN FRAGEBOGEN
|
||||
|
||||
1. SIE SPEICHERT SOFORT. Kein „Absenden", das man vergessen kann.
|
||||
Jeder Klick steht. Der Knopf am Ende schließt nur die RUNDE ab —
|
||||
er trägt den Stand in den Verlauf ein.
|
||||
|
||||
2. SIE FRAGT WIEDER. Nach vierzehn Tagen. Ein Befinden von vor drei
|
||||
Monaten ist keine Auskunft mehr, sondern ein Andenken.
|
||||
|
||||
3. SIE ZEIGT DEN VERLAUF. Das Warnzeichen ist nicht ein einzelnes
|
||||
„da hakt es" — das ist ein schlechter Tag. Es ist dieselbe Antwort
|
||||
über mehrere Runden. Sehen kann das nur die Person selbst.
|
||||
===================================================================== */
|
||||
(() => {
|
||||
const $ = (id) => document.getElementById(id);
|
||||
const el = (t, k, text) => {
|
||||
const n = document.createElement(t);
|
||||
if (k) n.className = k;
|
||||
if (text !== undefined) n.textContent = text;
|
||||
return n;
|
||||
};
|
||||
const melde = (t) => { const f = $('fehler'); if (f) { f.textContent = t; f.hidden = !t; } };
|
||||
|
||||
async function hole(pfad, optionen = {}) {
|
||||
const a = await fetch(pfad, { credentials: 'same-origin', ...optionen });
|
||||
if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); }
|
||||
if (a.status === 404) { location.assign('/workspace/start.html'); throw new Error('unbekannt'); }
|
||||
return a;
|
||||
}
|
||||
|
||||
let stufen = [];
|
||||
let daten = null;
|
||||
|
||||
/* ---- Die vier Knöpfe zu einer Frage --------------------------------- */
|
||||
function knopfsatz(gewaehlt, beiKlick) {
|
||||
const reihe = el('div', 'e-wahl');
|
||||
for (const s of stufen) {
|
||||
const b = el('button', 'e-wahl__knopf');
|
||||
b.type = 'button';
|
||||
b.dataset.stufe = s.schluessel;
|
||||
b.setAttribute('aria-pressed', String(gewaehlt === s.schluessel));
|
||||
b.append(el('span', 'e-wahl__zeichen', s.zeichen));
|
||||
b.append(document.createTextNode(s.name));
|
||||
b.addEventListener('click', () => beiKlick(s.schluessel, b));
|
||||
reihe.append(b);
|
||||
}
|
||||
return reihe;
|
||||
}
|
||||
|
||||
/* ---- Der Rhythmus --------------------------------------------------- */
|
||||
function takt() {
|
||||
const r = daten.rhythmus;
|
||||
const kasten = $('takt');
|
||||
const satz = $('takt-satz');
|
||||
const knopf = $('takt-knopf');
|
||||
kasten.hidden = false;
|
||||
|
||||
const offen = daten.punkte.filter((p) => !p.meins).length;
|
||||
const alle = daten.punkte.length;
|
||||
|
||||
if (!r.letzte) {
|
||||
satz.textContent = offen
|
||||
? `Noch ${offen} von ${alle} Fragen offen. Wenn du durch bist, halte den Stand fest – `
|
||||
+ `dann siehst du beim nächsten Mal, was sich verändert hat.`
|
||||
: 'Alle sechs beantwortet. Halte den Stand fest, dann hast du beim nächsten Mal '
|
||||
+ 'einen Vergleich.';
|
||||
kasten.dataset.lage = offen ? 'offen' : 'bereit';
|
||||
} else if (r.faellig) {
|
||||
satz.textContent = `Deine letzte Runde ist ${r.tage} Tage her. `
|
||||
+ (offen ? `Noch ${offen} von ${alle} Fragen offen.`
|
||||
: 'Geh die sechs Fragen noch einmal durch – es dauert zwei Minuten.');
|
||||
kasten.dataset.lage = 'faellig';
|
||||
} else {
|
||||
const rest = daten.rhythmus_tage - r.tage;
|
||||
/* „Vor 0 Tagen" ist keine Zeitangabe, sondern eine Rechnung, die
|
||||
man laut vorliest. Drei Faelle, drei Saetze -- derselbe Grund,
|
||||
aus dem die Ampel nebenan drei hat. */
|
||||
const wann = r.tage === 0 ? 'Heute festgehalten.'
|
||||
: r.tage === 1 ? 'Gestern festgehalten.'
|
||||
: `Zuletzt vor ${r.tage} Tagen festgehalten.`;
|
||||
satz.textContent = `${wann} `
|
||||
+ (rest <= 1 ? 'Morgen fragt die Seite wieder – '
|
||||
: `In ${rest} Tagen fragt die Seite wieder – `)
|
||||
+ `du kannst aber jederzeit etwas ändern.`;
|
||||
kasten.dataset.lage = 'ruhig';
|
||||
}
|
||||
|
||||
/* DER KNOPF ERSCHEINT NUR, WENN ER ETWAS TUN KANN. Ein Knopf, der
|
||||
"erst alle sechs beantworten" sagt, ist eine Absage mit
|
||||
Umweg -- der Satz darüber sagt es schon. */
|
||||
knopf.hidden = offen > 0;
|
||||
}
|
||||
|
||||
/* ---- Die sechs Fragen ------------------------------------------------ */
|
||||
function fragen() {
|
||||
const ziel = $('fragen');
|
||||
ziel.textContent = '';
|
||||
ziel.setAttribute('aria-busy', 'false');
|
||||
|
||||
for (const pt of daten.punkte) {
|
||||
const z = el('article', 'e-punkt e-punkt--meins');
|
||||
if (pt.meins) z.dataset.stufe = pt.meins.stufe;
|
||||
z.append(el('h3', 'e-punkt__titel', pt.titel));
|
||||
z.append(el('p', 'e-punkt__text', pt.text));
|
||||
z.append(knopfsatz(pt.meins?.stufe || null, async (stufe, knopf) => {
|
||||
for (const b of knopf.parentElement.querySelectorAll('.e-wahl__knopf')) {
|
||||
b.setAttribute('aria-pressed', String(b === knopf));
|
||||
}
|
||||
z.dataset.stufe = stufe;
|
||||
const s = await hole(`/workspace/api/entwicklung/mich/${pt.schluessel}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ stufe }),
|
||||
});
|
||||
if (!s.ok) { melde('Ging nicht.'); return; }
|
||||
melde('');
|
||||
/* Den Stand im Kopf mitführen, statt die ganze Seite neu zu
|
||||
holen: Ein Neuaufbau nach jedem Klick würde die Seite unter
|
||||
den Fingern springen lassen. */
|
||||
pt.meins = { ...(pt.meins || {}), stufe };
|
||||
takt();
|
||||
}));
|
||||
ziel.append(z);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Der Verlauf ----------------------------------------------------- */
|
||||
function verlauf() {
|
||||
const block = $('verlauf-block');
|
||||
const v = daten.verlauf || [];
|
||||
if (v.length < 2) {
|
||||
/* MIT EINER EINZIGEN RUNDE GIBT ES KEINEN VERLAUF. Eine Spalte
|
||||
allein sähe aus wie ein Muster und wäre keins. */
|
||||
block.hidden = true;
|
||||
return;
|
||||
}
|
||||
block.hidden = false;
|
||||
$('verlauf-satz').textContent =
|
||||
`Deine letzten ${v.length} Stände. Was zählt, ist nicht ein einzelnes Kreuz – `
|
||||
+ `sondern dieselbe Antwort mehrmals hintereinander.`;
|
||||
|
||||
const ziel = $('verlauf');
|
||||
ziel.textContent = '';
|
||||
|
||||
/* Älteste links: Ein Verlauf liest sich von links nach rechts. */
|
||||
const runden = [...v].reverse();
|
||||
const kopf = el('div', 'b-verlauf__zeile b-verlauf__zeile--kopf');
|
||||
kopf.append(el('span', 'b-verlauf__frage', ''));
|
||||
for (const r of runden) {
|
||||
const d = new Date(r.runde);
|
||||
kopf.append(el('span', 'b-verlauf__datum',
|
||||
d.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit' })));
|
||||
}
|
||||
ziel.append(kopf);
|
||||
|
||||
const name = {};
|
||||
for (const s of stufen) name[s.schluessel] = s;
|
||||
|
||||
for (const pt of daten.punkte) {
|
||||
const zeile = el('div', 'b-verlauf__zeile');
|
||||
zeile.append(el('span', 'b-verlauf__frage', pt.titel));
|
||||
for (const r of runden) {
|
||||
const stufe = r.antworten[pt.schluessel] || null;
|
||||
const p = el('span', 'b-verlauf__punkt', stufe ? name[stufe]?.zeichen || '·' : '·');
|
||||
p.dataset.stufe = stufe || 'leer';
|
||||
/* DAS ZEICHEN ALLEIN IST NICHT DIE AUSKUNFT -- der Name steht
|
||||
im Titel, damit auch ein Vorleseprogramm ihn ansagt. */
|
||||
p.title = stufe ? `${pt.titel}: ${name[stufe]?.name || stufe}` : 'keine Antwort';
|
||||
p.setAttribute('aria-label', p.title);
|
||||
zeile.append(p);
|
||||
}
|
||||
ziel.append(zeile);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Runde abschließen ----------------------------------------------- */
|
||||
$('takt-knopf')?.addEventListener('click', async (e) => {
|
||||
const k = e.currentTarget;
|
||||
k.disabled = true;
|
||||
try {
|
||||
const a = await hole('/workspace/api/befinden/runde',
|
||||
{ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' });
|
||||
const j = await a.json().catch(() => ({}));
|
||||
if (!a.ok) {
|
||||
melde(j.fehler === 'unvollstaendig'
|
||||
? `Es fehlen noch ${j.offen} von ${j.gesamt} Fragen.`
|
||||
: 'Ging nicht.');
|
||||
return;
|
||||
}
|
||||
melde('Festgehalten. Beim nächsten Mal siehst du, was sich verändert hat.');
|
||||
await laden();
|
||||
} finally { k.disabled = false; }
|
||||
});
|
||||
|
||||
async function laden() {
|
||||
const a = await hole('/workspace/api/befinden');
|
||||
if (!a.ok) { melde('Ging nicht.'); return; }
|
||||
daten = await a.json();
|
||||
stufen = daten.stufen || [];
|
||||
takt();
|
||||
fragen();
|
||||
verlauf();
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const a = await fetch('/workspace/api/ich', { credentials: 'same-origin' });
|
||||
if (a.ok) window.werZeigen?.(await a.json());
|
||||
} catch { /* der Kopf holt es sonst selbst */ }
|
||||
try { await laden(); } catch { /* umgeleitet */ }
|
||||
})();
|
||||
})();
|
||||
@@ -310,39 +310,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Die eigenen sechs Fragen ---------------------------------------- */
|
||||
async function meins() {
|
||||
const a = await hole('/workspace/api/entwicklung/mich');
|
||||
if (!a.ok) return;
|
||||
const r = await a.json();
|
||||
stufen = stufen.length ? stufen : r.stufen;
|
||||
$('meins-block').hidden = false;
|
||||
$('meins-titel').textContent = r.block.name;
|
||||
$('meins-satz').textContent = r.block.text;
|
||||
const ziel = $('meins');
|
||||
ziel.textContent = '';
|
||||
ziel.setAttribute('aria-busy', 'false');
|
||||
for (const pt of r.punkte) {
|
||||
const z = el('article', 'e-punkt e-punkt--meins');
|
||||
if (pt.meins) z.dataset.stufe = pt.meins.stufe;
|
||||
z.append(el('h4', 'e-punkt__titel', pt.titel));
|
||||
z.append(el('p', 'e-punkt__text', pt.text));
|
||||
z.append(knopfsatz(pt.meins?.stufe || null, async (stufe, knopf) => {
|
||||
for (const b of knopf.parentElement.querySelectorAll('.e-wahl__knopf')) {
|
||||
b.setAttribute('aria-pressed', String(b === knopf));
|
||||
}
|
||||
z.dataset.stufe = stufe;
|
||||
const s = await hole(`/workspace/api/entwicklung/mich/${pt.schluessel}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ stufe }),
|
||||
});
|
||||
if (!s.ok) melde('Ging nicht.');
|
||||
}));
|
||||
ziel.append(z);
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- Start ------------------------------------------------------------ */
|
||||
(async () => {
|
||||
try {
|
||||
@@ -363,16 +330,27 @@
|
||||
for (const p of lage.personen) ziel.append(personKarte(p, lage));
|
||||
await ampel();
|
||||
} else {
|
||||
/* Für einen Modi ist DIESE Seite die sechs Fragen. Der Titel
|
||||
sagt das auch — sonst steht über sechs Fragen das Wort
|
||||
„Entwicklung", und das liest sich wie eine Beurteilung. */
|
||||
$('titel').textContent = "Wie geht's dir?";
|
||||
/* Für einen Modi ist DIESE Seite seine eigene Karte — das,
|
||||
was die Leitung über ihn notiert hat, und was er selbst
|
||||
dazu sagt. Nicht „Entwicklung" als Überschrift: Das liest
|
||||
sich wie eine Beurteilung, und darum geht es hier nicht.
|
||||
|
||||
Bis zum 15.09.2026 stand hier „Wie geht's dir?" — damals
|
||||
bestand die Seite für ihn aus den sechs Fragen. Die haben
|
||||
jetzt ihre eigene Seite, also stimmte der Titel nicht mehr.
|
||||
Ein Titel, der etwas anderes verspricht als die Seite zeigt,
|
||||
ist derselbe Fehler wie der an der Kachel. */
|
||||
$('titel').textContent = 'Deine Entwicklung';
|
||||
$('unterzeile').textContent =
|
||||
'Sechs Fragen über dich. Niemand aus dem Team sieht deine Antworten.';
|
||||
'Was im Team über deine Arbeit notiert ist – und was du selbst dazu sagst. '
|
||||
+ 'Keine Noten, keine Punktzahl.';
|
||||
}
|
||||
|
||||
await meineKarte();
|
||||
await meins();
|
||||
/* Die sechs Fragen stehen seit dem 15.09.2026 auf einer eigenen
|
||||
Seite. Hier bleibt nur der Verweis -- er wird fuer alle
|
||||
gezeigt, die dort ueberhaupt etwas zu beantworten haben. */
|
||||
if ($('meins-block')) $('meins-block').hidden = false;
|
||||
} catch { /* umgeleitet */ }
|
||||
})();
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user