From 8fc1ac7a7a4a95bf7619cf091c884856afe10668 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Wed, 16 Sep 2026 01:46:07 +0200 Subject: [PATCH] Alles aus der Excel-Datei -- und der Fund, der alles blockiert haette Filipe: "ich will dass alles von der excel datei genommen wird. perfektionnier das, aber wenn ich dir runter lade soll alles notiert und angezeigt werden." Dazu eine echte Ausgabe als Vorlage. DER SCHWERSTE FUND STECKTE VOR DEN DATEN, NICHT IN IHNEN. Seine Ausgabe "Creator_innendaten" hat DREI Spalten, die nach Person aussehen: "Creator*in-ID", "Creator*innen-Anmeldename" und "Agent". `personSpaltenRaten` nahm die erste mit dem Wort "creator" darin -- die ID. Danach wurde nach einem Creator namens "700001" gesucht. Nachgemessen an seiner echten Kopfzeile: handle = KEINE, name = "Creator*in-ID". Diese Datei haette KEINE EINZIGE Zeile zugeordnet, mit einem Hinweis ("steht bei keinem Creator im Feld TikTok"), der in die voellig falsche Richtung zeigt. Jetzt ist "Anmeldename" der Handle, eine Kennnummer ist fuer beide Spalten ausgeschlossen, und "creator" allein reicht nicht mehr als Namensspalte -- sonst haette weiter hinten "Neue*r LIVE-Creator*innen" (Wert: "Nein") die Stelle uebernommen. An fuenf Kopfzeilen gemessen. UND DANN: NICHTS FAELLT MEHR WEG. 41 Spalten in der Datei, acht werden gedeutet. Die restlichen 33 -- letzter Monat, fuenf Prozentwerte, Matches, Multi-Gast-LIVEs, Fanclub, Graduierungs- und Stufenstatus -- wurden lautlos weggeworfen. KEINE 33 NEUEN SPALTEN, sondern eine Zeile je Spalte mit dem NAMEN als Schluessel. Eine abgeschriebene Spaltenliste hat in diesem Haus schon zweimal Daten gekostet und waere beim naechsten Backstage-Update falsch. Gegenprobe in der Pruefung: eine erfundene Spalte ("Sternenstaub pro Woche") kommt genauso durch -- es wird also keine Liste gepflegt, die Datei entscheidet. An SEINER echten Datei gemessen, ohne sie irgendwo hineinzuschreiben: 40 von 41 Spalten gespeichert (die 41. ist leer), Zeitraum 01.09.- 13.09. erkannt, 32 als Zahl, 8 als Text. UND EIN MESSFEHLER, DER LEHRREICH IST: Meine erste Pruefung meldete "zugeklappt ist die Liste 141 px hoch", im Bildschirmfoto war dort nichts. An einem Miniaturfall nachgemessen: getBoundingClientRect, offsetHeight, offsetParent und getClientRects liefern bei einem
in BEIDEN Zustaenden identische Werte -- Chromium verbirgt den Inhalt mit content-visibility:hidden, und das behaelt die letzte Ausmessung. Nur checkVisibility() kann es unterscheiden. Die Messung log, nicht die Seite. pruef-backstage-import 160 (war 133), pruef-xlsx 75, pruef-leistung-optik 59, pruef-leistung, pruef-css-klassen und pruef-auskunft (46, DSGVO -- die neue Tabelle ist automatisch dabei, weil die Auskunft ihre Liste aus PRAGMA foreign_key_list ableitet): alle gruen. Co-Authored-By: Claude Opus 5 --- server/pruef-backstage-import.mjs | 302 +++++++++++++++++++++++++++++- server/workspace-leistung.js | 187 +++++++++++++++++- server/workspace.js | 46 +++++ workspace/app.webmanifest | 8 +- workspace/assets/css/leistung.css | 63 +++++++ workspace/assets/js/leistung.js | 52 ++++- workspace/aufgaben.html | 24 +-- workspace/automation.html | 26 +-- workspace/befinden.html | 24 +-- workspace/bereich.html | 34 ++-- workspace/calls.html | 28 +-- workspace/chat.html | 26 +-- workspace/content.html | 30 +-- workspace/crew-index.html | 10 +- workspace/crew.webmanifest | 8 +- workspace/dateien.html | 26 +-- workspace/entwicklung.html | 24 +-- workspace/index.html | 8 +- workspace/kalender.html | 28 +-- workspace/leistung.html | 26 +-- workspace/personen.html | 26 +-- workspace/profil.html | 28 +-- workspace/rechte.html | 24 +-- workspace/report.html | 28 +-- workspace/scouting.html | 26 +-- workspace/start.html | 24 +-- workspace/startcheck.html | 28 +-- workspace/steckbrief.html | 28 +-- workspace/talente.html | 24 +-- workspace/team.html | 26 +-- workspace/teamlage.html | 24 +-- workspace/treff-moderation.html | 24 +-- workspace/treff-regeln.html | 26 +-- workspace/uebersicht.html | 26 +-- workspace/wissen.html | 26 +-- 35 files changed, 999 insertions(+), 369 deletions(-) diff --git a/server/pruef-backstage-import.mjs b/server/pruef-backstage-import.mjs index 3f27e73d..e0acff0e 100644 --- a/server/pruef-backstage-import.mjs +++ b/server/pruef-backstage-import.mjs @@ -469,6 +469,219 @@ for (const [rolle, code, wer] of [ } +/* ======================================================================= + ALLE 41 SPALTEN -- nichts faellt mehr weg (16.09.2026) + ======================================================================= + + Filipe: "ich will dass alles von der excel datei genommen wird. + perfektionnier das, aber wenn ich dir runter lade soll alles notiert + und angezeigt werden." + + --------------------------------------------------------------------- + DIE VORLAGE IST SEINE ECHTE AUSGABE vom 14.09.2026 ("Creator:innen- + daten"). Sie hat EINUNDVIERZIG Spalten; erkannt und gedeutet werden + acht. Die restlichen 33 wurden beim Einlesen weggeworfen, ohne dass + irgendwo stand, dass es sie gab. + + DIE ECHTE DATEI STEHT NICHT IM REPO -- in ihr stehen Name, ID und + Agent seiner Creator. Was hier steht, sind ihre UEBERSCHRIFTEN (die + gehoeren TikTok, nicht ihm) mit erfundenen Werten. Die echte Datei + hat die Form belegt; diese Pruefung haelt sie fest. + + WAS GEMESSEN WIRD, und jedes davon hat einen Grund: + * Alle 41 Spalten kommen an -- gezaehlt, nicht gehofft. + * Eine Spalte, die es in KEINEM Muster gibt ("Graduierungsstatus"), + kommt mit ihrem TEXT an. Waere nur die Zahl gespeichert, stuende + dort nichts. + * Eine LEERE Zelle erzeugt keine Zeile -- sonst stuende auf der + Karte "Status: (nichts)", eine Auskunft, die die Datei nicht gibt. + * Eine ERFUNDENE Spalte kommt genauso durch. Das ist die + eigentliche Gegenprobe: Sie beweist, dass hier keine Liste + gepflegt wird, sondern die Datei entscheidet -- also auch eine + Spalte durchkommt, die TikTok erst naechstes Jahr hinzufuegt. + * Ein ZWEITER Durchgang mit weniger Spalten laesst keine Leichen + zurueck. Ohne das Wegraeumen vorher stuende ein Wert aus einer + Datei da, die es nicht mehr gibt. + ======================================================================= */ +console.log(""); +console.log("=== Alle Spalten der echten Ausgabe ==="); +{ + /* Die 41 Ueberschriften, wortgleich aus seiner Ausgabe. */ + const KOPF = [ + "Datenzeitraum", "Creator*in-ID", "Creator*innen-Anmeldename", "Gruppe", + "Agent", "Beitrittszeit", "Tage seit dem Beitritt", "Diamanten", + "LIVE-Dauer", "Gültige LIVE-Gehen-Tage", "Neue Follower*innen", "Livestreams", + "Diamanten im letzten Monat", "LIVE-Dauer (in Std.) im letzten Monat", + "Gültige LIVE-Gehen-Tage im letzten Monat", + "Neue Follower*innen im letzten Monat", "Livestreams im letzten Monat", + "Diamanten - Prozent erreicht", "LIVE-Dauer - Prozent erreicht", + "Gültige LIVE-Gehen-Tage - Prozent erreicht", + "Neue Follower*innen - Prozent erreicht", "Livestreams - Prozent erreicht", + "Diamanten - im Vergleich zu letztem Monat", + "LIVE-Dauer - im Vergleich zu letztem Monat", + "Gültige LIVE-Gehen-Tage - im Vergleich zu letztem Monat", + "Neue Follower*innen - im Vergleich zu letztem Monat", + "Livestreams - im Vergleich zu letztem Monat", + "Matches", "Diamanten aus Matches", "Neue*r LIVE-Creator*innen", + "Diamanten von Multi-Gast-LIVEs", "Diamanten von Multi-Gast-LIVEs (als Host)", + "Diamanten von Multi-Gast-LIVEs (als Gäst*in)", + "Graduierungsstatus", "Stufen-Status", "Neue Fans", + "Gesamte Diamanten des Fanclubs", "Beitrag durch Fans in %", + "Fans insgesamt", "Aktive Fans vom Fanclub", "Status", + ]; + ok(KOPF.length === 41, `die Vorlage hat ${KOPF.length} Spalten`); + + /* Erfundene Werte in der FORM der echten -- mit allen Eigenheiten, + die dort vorkommen: Prozent mit englischem Punkt, eine Dauer mit + Einheiten, ein Bruch, der trotz "%" im Namen KEINER ist, Text + statt Zahl, und eine leere Zelle am Ende. */ + const WERT = [ + "2026-08-01 ~ 2026-08-31", "700001", "@Nova_Live", "Dogfather", + "Scout Sam", "2026-07-09 19:30:57 (UTC+0)", "68", "13210", + "6Std. 24Min. 7Sek.", "5", "97", "5", + "29488", "13Std. 22Min. 40Sek.", "9", "126", "16", + "44.79%", "47.85%", "55.55%", "76.98%", "31.25%", + "98.55%", "83.68%", "150.00%", "361.90%", "0.00%", + "13", "8665", "Nein", + "26", "26", "0", + "Nicht graduiert", "Nicht beibehalten", "17", + "13128", "0.99379", "1182", "75", "", + ]; + ok(WERT.length === KOPF.length, + `und genauso viele Werte (${WERT.length})`); + + const alles = [KOPF.join("\t"), WERT.join("\t")].join("\n"); + const v = await ruf("/workspace/api/leistung/netzwerk-import/vorschau", + keksDogi, { text: alles }); + ok(v.daten?.spalten_gesamt === 41, + `die Vorschau nennt alle Spalten der Datei (${v.daten?.spalten_gesamt})`); + ok(v.daten?.felder === 40, + `und wie viele davon einen Wert tragen (${v.daten?.felder} -- "Status" ist leer)`); + + const a = await ruf("/workspace/api/leistung/netzwerk-import", keksDogi, { text: alles }); + ok(a.daten?.felder === 40, `uebernommen werden ${a.daten?.felder} Werte`); + + const d4 = new DatabaseSync(DB, { readOnly: true }); + const felder = d4.prepare(`SELECT nr, name, text, zahl FROM leistung_zeitraum_feld + WHERE creator_id = ? AND von = ? AND bis = ? ORDER BY nr`) + .all(idNova, "2026-08-01", "2026-08-31"); + d4.close(); + ok(felder.length === 40, + `in der Datenbank stehen ${felder.length} Felder (41 Spalten, eine leer)`); + + const nach = new Map(felder.map((f) => [f.name, f])); + ok(felder.length > 0 && !nach.has("Status"), + "die leere Zelle erzeugt KEINE Zeile -- sonst staende dort \"Status: nichts\""); + + /* ---- WER IST DAS UEBERHAUPT? (16.09.2026) -------------------------- + DER TEUERSTE FEHLER DIESES TAGES, und er stand nicht im Verdacht. + + Seine Ausgabe hat DREI Spalten, die nach Person aussehen: + "Creator*in-ID", "Creator*innen-Anmeldename" und "Agent". Die alte + Regel nahm die erste mit dem Wort "creator" darin -- die ID. Danach + wurde nach einem Creator namens "700001" gesucht, und die ganze + Datei kam als "nicht zugeordnet" zurueck, mit einem Hinweis, der + in die voellig falsche Richtung zeigte. + + Die Pruefung oben faellt darauf herein wie er: Ohne Zuordnung gibt + es keinen Zeitraum, also auch keine Felder. Deshalb steht es hier + noch einmal ausdruecklich -- damit beim naechsten Mal die MELDUNG + sagt, was los ist, und nicht nur eine Zahl fehlt. */ + ok(nach.get("Creator*innen-Anmeldename")?.text === "@Nova_Live", + "die Zeile wurde ueber den Anmeldenamen zugeordnet, nicht ueber die ID"); + ok(nach.get("Creator*in-ID")?.text === "700001", + "und die ID steht trotzdem in der Ablage -- sie ist eine Auskunft, kein Schluessel"); + { + /* GEGENPROBE: Eine Tabelle, in der NUR eine Kennnummer steht, darf + niemanden zuordnen -- und muss das sagen, statt stillschweigend + nichts zu tun. */ + const nurId = [ + ["Datenzeitraum", "Creator*in-ID", "Diamanten"].join("\t"), + ["2026-08-01 ~ 2026-08-31", "700001", "500"].join("\t"), + ].join("\n"); + const r = await ruf("/workspace/api/leistung/netzwerk-import/vorschau", + keksDogi, { text: nurId }); + ok(r.status === 400 && /Name oder Handle/.test(r.daten?.fehler || ""), + `eine Tabelle mit nur einer Kennnummer wird begruendet abgelehnt ` + + `(HTTP ${r.status}: "${String(r.daten?.fehler || "").slice(0, 40)}…")`); + } + + /* Eine Spalte, die KEIN Muster kennt, und ihr Wert ist TEXT. */ + ok(nach.get("Graduierungsstatus")?.text === "Nicht graduiert", + `eine Spalte ohne Muster kommt woertlich an ("${nach.get("Graduierungsstatus")?.text}")`); + ok(nach.get("Graduierungsstatus")?.zahl === null, + "und traegt keine erfundene Zahl"); + + /* Prozent mit englischem Punkt -- die Falle, an der eine deutsche + Zahlenlesung 4479 statt 44,79 liefern wuerde. */ + ok(nach.get("Diamanten - Prozent erreicht")?.zahl === 44.79, + `"44.79%" wird zu ${nach.get("Diamanten - Prozent erreicht")?.zahl}, nicht zu 4479`); + /* Und der Bruch, der trotz "%" im Spaltennamen keiner ist. */ + ok(nach.get("Beitrag durch Fans in %")?.zahl === 0.99379, + `"0.99379" bleibt ${nach.get("Beitrag durch Fans in %")?.zahl} ` + + "-- der Spaltenname sagt Prozent, der Wert ist ein Bruch"); + /* Die Reihenfolge der Datei bleibt erhalten. */ + ok(felder[0].name === "Datenzeitraum" && felder[felder.length - 1].name === "Aktive Fans vom Fanclub", + `die Reihenfolge der Datei bleibt (${felder[0].name} … ${felder[felder.length - 1].name})`); + + /* ---- DIE EIGENTLICHE GEGENPROBE ------------------------------------ + Eine Spalte, die es bei TikTok (noch) gar nicht gibt. Kommt sie + durch, wird hier keine Liste gepflegt -- sondern die Datei + entscheidet. Genau das soll die Bauart leisten. */ + { + const erfunden = [ + [...KOPF, "Sternenstaub pro Woche"].join("\t"), + [...WERT, "4711"].join("\t"), + ].join("\n"); + await ruf("/workspace/api/leistung/netzwerk-import", keksDogi, { text: erfunden }); + const d5 = new DatabaseSync(DB, { readOnly: true }); + const f = d5.prepare(`SELECT text, zahl FROM leistung_zeitraum_feld + WHERE creator_id = ? AND von = ? AND bis = ? AND name = ?`) + .get(idNova, "2026-08-01", "2026-08-31", "Sternenstaub pro Woche"); + d5.close(); + ok(f?.zahl === 4711, + `auch eine Spalte, die es heute nicht gibt, kommt an (${f?.zahl})`); + } + + /* ---- UND KEINE LEICHEN --------------------------------------------- + Dieselbe Datei noch einmal, diesmal OHNE die erfundene Spalte. Sie + muss verschwinden. Ohne das Wegraeumen vor dem Schreiben stuende + dort ein Wert aus einer Datei, die es nicht mehr gibt -- und + niemand koennte ihn von einem frischen unterscheiden. */ + { + await ruf("/workspace/api/leistung/netzwerk-import", keksDogi, { text: alles }); + const d6 = new DatabaseSync(DB, { readOnly: true }); + const n = d6.prepare(`SELECT COUNT(*) AS n FROM leistung_zeitraum_feld + WHERE creator_id = ? AND von = ? AND bis = ?`) + .get(idNova, "2026-08-01", "2026-08-31").n; + const weg = d6.prepare(`SELECT 1 FROM leistung_zeitraum_feld + WHERE creator_id = ? AND name = ?`).get(idNova, "Sternenstaub pro Woche"); + d6.close(); + ok(!weg && n === 40, + `beim zweiten Einlesen bleibt keine Leiche zurueck (${n} Felder)`); + } + + /* ---- UND DIE SEITE BEKOMMT SIE AUCH ------------------------------ */ + { + const antwort = await (await fetch(BASIS + `/workspace/api/leistung/${idNova}`, + { headers: { Cookie: keksDogi } })).json(); + /* GEZIELT DEN AUGUST, nicht "den ersten". Nova hat inzwischen zwei + Zeitraeume; `[0]` waere der jüngere und die Pruefung haette ohne + Vorwarnung etwas anderes gemessen als gemeint. Genau dieser + Fehler ist mir am 14.09. schon einmal unterlaufen. */ + const z = (antwort?.zeitraeume || []).find((x) => x.von === "2026-08-01"); + ok(Array.isArray(z?.felder) && z.felder.length === 40, + `die Seite bekommt alle Werte mitgeliefert (${z?.felder?.length})`); + ok(z?.felder?.[0]?.name === "Datenzeitraum", + "und zwar in der Reihenfolge der Datei"); + /* Und der ANDERE Zeitraum hat seine eigenen -- die Felder haengen + am Zeitraum, nicht am Creator. */ + const sept = (antwort?.zeitraeume || []).find((x) => x.von === "2026-09-01"); + ok((sept?.felder || []).length === 5, + `der andere Zeitraum hat seine eigenen ${(sept?.felder || []).length} Werte`); + } +} + { /* ---- DER EINZEL-IMPORT KENNT DEN DRITTEN AUSGANG (14.09.2026) ---- Als tagAusZelle() den Zeitraum dazubekam, kannte ihn nur der @@ -903,6 +1116,88 @@ console.log("\n=== Die Seite ==="); { path: join(HIER, "pruef-spannen.png") }); } + /* ---- "ALLES ANGEZEIGT" HEISST: MAN KANN ES SEHEN (16.09.2026) ---- + Filipe: "wenn ich dir runter lade soll alles notiert und angezeigt + werden." Dass die Werte in der Datenbank stehen, ist oben belegt. + Ob sie auf dem Bildschirm ankommen, ist eine ANDERE Frage -- und + genau die Sorte, die hier schon einmal keiner gestellt hat (die + Teilnehmerwahl im Kalender, 05.09.). + + GEMESSEN WIRD MIT `checkVisibility()`, UND DAS IST KEINE + GESCHMACKSFRAGE (16.09.2026, an einem Miniaturfall nachgemessen): + + zugeklappt getBoundingClientRect().height 72 + aufgeklappt getBoundingClientRect().height 72 + + Dieselbe Zahl. Auch `offsetHeight`, `offsetParent` und + `getClientRects()` sind in beiden Zustaenden identisch -- Chromium + verbirgt den Inhalt eines geschlossenen
mit + `content-visibility: hidden`, und das behaelt die letzte + Ausmessung. Wer die Hoehe misst, bekommt also fuer "unsichtbar" + und "sichtbar" dieselbe Antwort und merkt es nicht. + + Genau daran ist mein erster Anlauf gescheitert: Er meldete + "zugeklappt 141 px hoch" -- im Bildschirmfoto war an derselben + Stelle nichts zu sehen. Die Messung log, nicht die Seite. + + `checkVisibility()` unterscheidet beides sauber (false / true) und + ist das einzige der fuenf Verfahren, das es kann. */ + { + const deckel = await seite.$$(".spanne__alles-kopf"); + ok(deckel.length >= 1, `es gibt ${deckel.length} Deckel "Alle Werte aus Backstage"`); + const zuVorher = await seite.evaluate(() => + document.querySelector(".spanne__alles-liste")?.checkVisibility() ?? null); + ok(zuVorher === false, `zugeklappt ist die Liste nicht zu sehen (${zuVorher})`); + + /* Der LETZTE Deckel gehoert zum August -- dem Zeitraum mit allen + 41 Spalten. Der erste haette nur fuenf und die Pruefung waere + gruen, ohne das Gemeinte gemessen zu haben. */ + await deckel[deckel.length - 1].click(); + await seite.waitForTimeout(350); + const auf = await seite.evaluate(() => { + const offen = document.querySelector(".spanne__alles[open]"); + if (!offen) return null; + const namen = [...offen.querySelectorAll(".spanne__alles-name")]; + const werte = [...offen.querySelectorAll(".spanne__alles-wert")]; + const liste = offen.querySelector(".spanne__alles-liste"); + return { + namen: namen.length, werte: werte.length, + /* SICHTBAR, nicht "hat eine Hoehe" -- siehe die Begruendung + oben. Die Hoehe steht daneben, aber nur als Auskunft. */ + sichtbar: liste.checkVisibility(), + hoehe: Math.round(liste.getBoundingClientRect().height), + erster: namen[0]?.textContent || "", + letzter: namen[namen.length - 1]?.textContent || "", + /* Steht ein Wert, den es OHNE diese Liste nirgends gaebe? */ + graduierung: namen.some((e) => e.textContent === "Graduierungsstatus") + ? werte[namen.findIndex((e) => e.textContent === "Graduierungsstatus")]?.textContent + : "", + kleinste: Math.min(...[...namen, ...werte].map((e) => + parseFloat(getComputedStyle(e).fontSize))), + }; + }); + ok(auf && auf.namen === 40 && auf.werte === 40, + `aufgeklappt stehen ${auf?.namen} Namen und ${auf?.werte} Werte da`); + ok(auf && auf.sichtbar === true, + `und sie sind jetzt wirklich sichtbar (${auf?.sichtbar}, ${auf?.hoehe} px hoch)`); + ok(auf && auf.erster === "Datenzeitraum", + `in der Reihenfolge der Datei (zuerst "${auf?.erster}")`); + ok(auf && auf.graduierung === "Nicht graduiert", + `ein Wert, den es sonst NIRGENDS gaebe, steht da ("${auf?.graduierung}")`); + ok(auf && auf.kleinste >= 11.5, + `und nichts darin ist kleiner als 11,5 px (${auf?.kleinste} px)`); + + if (process.env.SCHIRM) { + await seite.locator(".spanne__alles[open]").screenshot( + { path: join(HIER, "pruef-spannen-alles.png") }); + } + /* Wieder zu -- das Bild der Tafel oben zeigt den Normalfall, und + die Kontrastmessung danach misst den Kopf, nicht eine offene + Liste, die die Seite verschoben hat. */ + await deckel[deckel.length - 1].click(); + await seite.waitForTimeout(250); + } + /* GEGENPROBE -- und sie ist hier das Wichtigste. "Null rote Bildpunkte" koennte auch heissen, dass gar kein Foto @@ -966,8 +1261,13 @@ console.log("\n=== Die Seite ==="); `keine einzige sichtbare Null mehr (${ohneTage.nullen})`); ok(/kein einziger Tag erfasst/.test(ohneTage.satz), `sie sagt, WAS los ist ("${ohneTage.satz.slice(0, 46)}…")`); + /* MIT DEM, WAS SIE GESEHEN HAT. Diese Zeile scheiterte am + 16.09.2026 und sagte nur "nennt den Zeitraum nicht" -- wo der + Fehler lag, war daran nicht zu erkennen. Eine Pruefung, die ihren + Befund nicht zeigt, kostet genau die Zeit, die sie sparen soll. */ ok(/01\.09\./.test(ohneTage.satz) && /13 Tage/.test(ohneTage.satz), - "und nennt den Zeitraum, der stattdessen da ist"); + `und nennt den Zeitraum, der stattdessen da ist ` + + `("${ohneTage.satz.replace(/\s+/g, " ").slice(0, 150)}")`); ok(/EINEN Tag/.test(ohneTage.satz), "und den Weg, wie man die Wochenzahlen doch fuellt"); diff --git a/server/workspace-leistung.js b/server/workspace-leistung.js index 54f46c1d..a69b7409 100644 --- a/server/workspace-leistung.js +++ b/server/workspace-leistung.js @@ -252,6 +252,31 @@ leistungRouter.get("/workspace/api/leistung/:creatorId", (req, res) => { FROM leistung_zeitraum WHERE creator_id = ? ORDER BY bis DESC, von DESC LIMIT 6`).all(creatorId); + /* ALLES, WAS IN DER DATEI STAND -- IN EINER EINZIGEN ABFRAGE. + + Filipe: "wenn ich dir runter lade soll alles notiert und + angezeigt werden." Notiert allein reicht nicht; was niemand + sieht, ist so gut wie nicht da. + + EINE Abfrage fuer alle sechs Zeitraeume statt sechs einzelner -- + dieselbe Regel wie bei den Chat-Reaktionen. Sortiert nach `nr`, + also in der Reihenfolge der Datei: Backstage gruppiert seine + Spalten bereits sinnvoll (dieser Zeitraum, letzter Monat, + Prozente, Matches, Fanclub). Eine eigene Gruppierung waere eine + erfundene Ordnung, die beim naechsten Backstage-Update falsch + ist. */ + if (zeitraeume.length) { + const nach = new Map(zeitraeume.map((z) => [z.von + "|" + z.bis, z])); + for (const z of zeitraeume) z.felder = []; + for (const f of db().prepare(` + SELECT von, bis, nr, name, text, zahl + FROM leistung_zeitraum_feld WHERE creator_id = ? + ORDER BY nr`).all(creatorId)) { + nach.get(f.von + "|" + f.bis)?.felder.push( + { nr: f.nr, name: f.name, text: f.text, zahl: f.zahl }); + } + } + res.json({ zeitraeume, creator_id: creatorId, @@ -586,6 +611,59 @@ export function zahlLesen(roh) { return Number.isFinite(n) ? n : null; } +/* ===================================================================== + ALLES, WAS IN DER ZEILE STAND (16.09.2026) + + Filipe: "ich will dass alles von der excel datei genommen wird ... + wenn ich dir runter lade soll alles notiert und angezeigt werden." + + Seine echte Ausgabe hat 41 Spalten; `spaltenRaten` kennt acht davon. + Die restlichen 33 fielen beim Einlesen weg -- lautlos, ohne dass + irgendwo stand, dass es sie gab: Zahlen vom letzten Monat, fuenf + Prozentwerte, Matches, Multi-Gast-LIVEs, Fanclub, Graduierungs- und + Stufenstatus. + + DIESE FUNKTION DEUTET NICHTS. Sie nimmt Ueberschrift und Zelle, + so wie sie dastehen. Das ist Absicht: Was "Diamanten - Prozent + erreicht" genau bedeutet, weiss TikTok, nicht ich -- und eine + erfundene Bedeutung waere schlimmer als gar keine. Nachgerechnet + habe ich es trotzdem einmal (13.210 / 29.488 = 44,79 %, und dasselbe + Verhaeltnis stimmt bei allen fuenf Groessen), aber eine Herleitung + aus EINER Zeile ist kein Beweis, und sie steht deshalb nicht im Code. + + WAS UEBERSPRUNGEN WIRD, und warum genau das: + * Spalten OHNE Ueberschrift -- ohne Namen gibt es keinen + Schluessel, und "Spalte 37" ist keine Auskunft. + * DOPPELTE Ueberschriften -- die erste gilt. Der Schluessel in der + Datenbank ist der Name; zwei gleiche wuerden einander + ueberschreiben, und dann haenge es von der Reihenfolge ab, welche + gewinnt. + * LEERE Zellen -- eine leere Zelle ist kein Wert. In Filipes Datei + ist "Status" leer; eine Zeile "Status: (nichts)" waere eine + Auskunft, die die Datei gar nicht gibt. + + BEIDES WIRD BEHALTEN: der Text woertlich und, falls lesbar, die + Zahl. Nur die Zahl hiesse "Nicht graduiert" wegzuwerfen; nur den + Text hiesse, nicht rechnen zu koennen. */ +export function felderAus(kopf, zeile) { + const felder = []; + const gesehen = new Set(); + for (let i = 0; i < kopf.length; i++) { + const name = String(kopf[i] ?? "").trim(); + if (!name || gesehen.has(name)) continue; + gesehen.add(name); + const text = String(zeile[i] ?? "").trim(); + if (!text) continue; + felder.push({ + nr: i, + name: name.slice(0, 120), + text: text.slice(0, 300), + zahl: zahlLesen(text), + }); + } + return felder; +} + /** Macht aus "86Std. 10Min. 52Sek." Minuten. * * GEMESSEN AN DER ECHTEN BACKSTAGE-AUSGABE (14.09.2026): Die Spalte @@ -874,9 +952,48 @@ function handleSchluessel(roh) { * erst das Handle (eindeutig), dann der Name (mehrdeutig, aber * besser als nichts). */ function personSpaltenRaten(kopf) { - const handle = kopf.findIndex((n) => /handle|username|user.?name|tiktok.?id|@/i.test(String(n))); - const name = kopf.findIndex((n, i) => i !== handle - && /nick|anzeige|display|name|creator/i.test(String(n))); + /* AN SEINER ECHTEN AUSGABE GEMESSEN (16.09.2026) -- und sie ist hier + durchgefallen, bevor eine einzige Zahl ankam. + + "Creator_innendaten" hat DREI Spalten, die nach Person aussehen: + + Creator*in-ID 700001 + Creator*innen-Anmeldename nova_live + Agent + + Die alte Regel nahm die ERSTE, die das Wort "creator" enthaelt -- + also die ID. Danach wurde nach einem Creator namens "700001" + gesucht, und den gibt es nicht. Ergebnis: die ganze Datei + unzugeordnet, mit der Meldung "steht bei keinem Creator im Feld + TikTok". Der Hinweis war richtig formuliert und zeigte in die + voellig falsche Richtung. + + ZWEI AENDERUNGEN, und beide sind Ausschluesse, keine Zusaetze: + + 1. EINE ID IST KEIN NAME. Was wie eine Kennnummer heisst, kommt + fuer beide Spalten nicht in Frage. Eine Nummer trifft nie einen + Namen -- sie kann also nur schaden. + 2. "ANMELDENAME" IST DER HANDLE. So heisst bei TikTok der Name + mit dem @. Damit wird die richtige Spalte zum Handle, statt gar + keine zu finden. + + UND "creator" ALLEIN REICHT NICHT MEHR als Namensspalte. In + derselben Datei steht weiter hinten "Neue*r LIVE-Creator*innen" + mit dem Wert "Nein" -- die alte Regel haette nach dem Ausschluss + der ID genau dort weitergesucht und einen Creator namens "Nein" + gesucht. Ein Spaltenname zaehlt nur noch, wenn er WIRKLICH nach + Name klingt (nick, anzeige, display, name) oder fuer sich allein + "Creator" heisst -- so wie in den aelteren Ausgaben. + + KEINE BACKSLASHES IM MUSTER, mit Absicht: Diese Datei wird oefter + ueber Textwerkzeuge bearbeitet, und ein verschlucktes Zeichen in + einer Regex faellt erst auf, wenn Daten fehlen. */ + const istKennung = /(^|[^a-z])id([^a-z]|$)|nummer/i; + const handle = kopf.findIndex((n) => !istKennung.test(String(n)) + && /handle|username|user.?name|anmeldename|anmelde.?name|login|@/i.test(String(n))); + const name = kopf.findIndex((n, i) => i !== handle && !istKennung.test(String(n)) + && (/nick|anzeige|display|name/i.test(String(n)) + || /^[\s]*creator[\s*:in]*[\s]*$/i.test(String(n)))); return { handle, name }; } @@ -974,7 +1091,12 @@ function netzwerkLesen(text, person, tagVorgabe) { const werte = spanne ? { creator_id: p.id, name: p.name, von: spanne.von, bis: spanne.bis, - tage: tageImZeitraum(spanne.von, spanne.bis) } + tage: tageImZeitraum(spanne.von, spanne.bis), + /* ALLE Spalten der Zeile, nicht nur die acht erkannten. + Nur beim Zeitraum: Die Tageszeilen haben ihre eigene + Tabelle, und ein halb gebautes Feld dort waere Datenhaltung + ohne Anzeige -- also nichts, was jemand je saehe. */ + felder: felderAus(kopf, z) } : { creator_id: p.id, name: p.name, tag }; let hatZahl = false; for (const feld of messwerte) { @@ -1039,6 +1161,18 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import/vorschau", gleicheH tage: e.treffer.find((t) => t.von).tage } : null, zugeordnet: e.treffer.length, neu, ersetzt, + /* WIE VIELE SPALTEN LANDEN WIRKLICH IN DER ABLAGE? + + Filipes Sorge war genau die: "ich will dass alles von der + excel datei genommen wird." Das gehoert VOR das Bestaetigen, + nicht hinterher -- sonst muesste er der Zusage glauben, + statt die Zahl zu sehen. + + Gemessen am ERSTEN Zeitraum der Datei, nicht ueber alle + summiert: Alle Zeilen haben dieselben Spalten, eine Summe + ueber 40 Creator waere eine grosse Zahl ohne Aussage. */ + spalten_gesamt: (e.kopf || []).length, + felder: (e.treffer.find((t) => t.felder) || {}).felder?.length ?? 0, unbekannt: e.unbekannt, probleme: e.schlecht.slice(0, 12), probleme_gesamt: e.schlecht.length, creator_gesamt: e.creator_gesamt, @@ -1082,12 +1216,28 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import", gleicheHerkunft, erfasst, erfasst_von) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`); + /* ERST WEGRAEUMEN, DANN SCHREIBEN. Ohne das Loeschen bliebe eine + Spalte, die beim ersten Mal gefuellt war und beim zweiten leer + ist, mit ihrem ALTEN Wert stehen -- ein Wert aus einer Datei, + die es nicht mehr gibt, und niemand koennte ihn von einem + frischen unterscheiden. */ + const felderWeg = d.prepare(`DELETE FROM leistung_zeitraum_feld + WHERE creator_id = ? AND von = ? AND bis = ?`); + const feldSchreiben = d.prepare(`INSERT OR REPLACE INTO leistung_zeitraum_feld + (creator_id, von, bis, nr, name, text, zahl) VALUES (?,?,?,?,?,?,?)`); + let geschrieben = 0; let spannen = 0; + let felder = 0; d.exec("BEGIN"); try { for (const t of e.treffer) { if (t.von) { + felderWeg.run(t.creator_id, t.von, t.bis); + for (const f of (t.felder || [])) { + feldSchreiben.run(t.creator_id, t.von, t.bis, f.nr, f.name, f.text, f.zahl); + felder++; + } spanneSchreiben.run(t.creator_id, t.von, t.bis, t.tage, t.diamanten ?? null, t.dauer_min ?? null, t.gueltige_tage ?? null, t.zuschauer_avg ?? null, t.zuschauer_max ?? null, @@ -1113,10 +1263,10 @@ leistungRouter.post("/workspace/api/leistung/netzwerk-import", gleicheHerkunft, protokolliere("leistung_netzwerk_import", { personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), - detail: `${geschrieben} Tage, ${spannen} Zeitraeume, ` + detail: `${geschrieben} Tage, ${spannen} Zeitraeume, ${felder} Felder, ` + `${e.unbekannt.length} unbekannt`.slice(0, 120), }); - res.json({ geschrieben, zeitraeume: spannen, + res.json({ geschrieben, zeitraeume: spannen, felder, unbekannt: e.unbekannt, probleme: e.schlecht.length }); } catch (fehler) { console.error("[leistung] Netzwerk-Import:", fehler?.message); @@ -1368,6 +1518,22 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft erfasst, erfasst_von) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)`); + /* UND ALLE UEBRIGEN SPALTEN -- WORTGLEICH WIE IM ANDEREN WEG. + + Es gibt zwei Wege in dieses System (eine Datei je Creator und + eine Tabelle fuer alle), und sie sind in diesem Modul schon + dreimal auseinandergelaufen: beim Zeitraumschutz, bei der + Dauer-Einheit und beim dritten Ausgang. Jedes Mal, weil eine + Sache an zwei Stellen geschrieben stand. + + Deshalb kommt die Liste der Felder hier NICHT aus einer zweiten + Rechnung, sondern aus derselben Funktion `felderAus`. */ + const felderWeg = d.prepare(`DELETE FROM leistung_zeitraum_feld + WHERE creator_id = ? AND von = ? AND bis = ?`); + const feldSchreiben = d.prepare(`INSERT OR REPLACE INTO leistung_zeitraum_feld + (creator_id, von, bis, nr, name, text, zahl) VALUES (?,?,?,?,?,?,?)`); + let felder = 0; + let geschrieben = 0, uebersprungen = 0, spannen = 0; d.exec("BEGIN"); try { @@ -1391,6 +1557,11 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft wz("zuschauer_avg"), wz("zuschauer_max"), wz("verweildauer_s"), wz("schenker"), wz("follower_neu"), jetzt(), req.person.id); + felderWeg.run(creatorId, von, bis); + for (const f of felderAus(zeilen[0], z)) { + feldSchreiben.run(creatorId, von, bis, f.nr, f.name, f.text, f.zahl); + felder++; + } spannen++; continue; } @@ -1420,9 +1591,9 @@ leistungRouter.post("/workspace/api/leistung/:creatorId/import", gleicheHerkunft protokolliere("leistung_import", { personId: req.person.id, rolle: req.person.rolle, ip: echteIp(req), - detail: `Creator #${creatorId}: ${geschrieben} Tage`, + detail: `Creator #${creatorId}: ${geschrieben} Tage, ${spannen} Zeitraeume, ${felder} Felder`, }); - res.json({ ok: true, geschrieben, zeitraeume: spannen, uebersprungen }); + res.json({ ok: true, geschrieben, zeitraeume: spannen, felder, uebersprungen }); } catch (fehler) { console.error("[leistung] Import:", fehler?.message); res.status(503).json({ fehler: "nicht_verfuegbar" }); diff --git a/server/workspace.js b/server/workspace.js index 0633e7c9..5789560a 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -3415,6 +3415,52 @@ export function db() { ON leistung_zeitraum (creator_id, bis DESC); CREATE INDEX IF NOT EXISTS idx_leistung_tag ON leistung (tag DESC); + /* ALLES, WAS IN DER DATEI STAND (16.09.2026) + + Filipe: "ich will dass alles von der excel datei genommen wird + ... wenn ich dir runter lade soll alles notiert und angezeigt + werden." + + Seine echte Backstage-Ausgabe hat 41 SPALTEN. Die Tabelle + darueber nimmt acht davon. Dreiunddreissig Spalten -- Zahlen + vom letzten Monat, Prozentwerte, Matches, Multi-Gast-LIVEs, + Fanclub, Graduierungs- und Stufenstatus -- wurden beim Einlesen + weggeworfen, ohne dass irgendwo stand, dass es sie gab. + + WARUM NICHT 33 WEITERE SPALTEN OBEN? Weil das eine ABGESCHRIEBENE + LISTE waere, und die hat in diesem Haus schon zweimal Daten + gekostet (siehe die Notiz zur Agentur-Umstellung). Sie waere + ausserdem beim naechsten Backstage-Update falsch: TikTok + benennt Spalten um und fuegt welche hinzu, ohne jemanden zu + fragen. + + EINE ZEILE JE SPALTE, und der NAME ist der Schluessel. Damit + gilt: Was in der Datei steht, steht danach in der Datenbank -- + auch eine Spalte, die es heute noch gar nicht gibt. Nichts zu + pflegen, nichts zu vergessen. + + Die Spalte nr IST DIE REIHENFOLGE DER DATEI, nicht die Identitaet. Wer + die Reihenfolge zum Schluessel machte, verwechselte zwei + Spalten, sobald Backstage eine dazwischenschiebt. + + BEIDES WIRD GESPEICHERT: der Text woertlich, wie er dastand + (daraus kann man immer noch alles herleiten), und die Zahl, wenn + es eine ist. Nur die Zahl zu behalten hiesse, "Nicht graduiert" + und "6Std. 24Min. 7Sek." zu verlieren; nur den Text zu behalten + hiesse, nicht rechnen zu koennen. */ + CREATE TABLE IF NOT EXISTS leistung_zeitraum_feld ( + creator_id INTEGER NOT NULL REFERENCES personen(id) ON DELETE CASCADE, + von TEXT NOT NULL, + bis TEXT NOT NULL, + nr INTEGER NOT NULL, + name TEXT NOT NULL, + text TEXT, + zahl REAL, + PRIMARY KEY (creator_id, von, bis, name) + ); + CREATE INDEX IF NOT EXISTS idx_leistung_zfeld + ON leistung_zeitraum_feld (creator_id, von, bis, nr); + /* ZIELE je Creator. Aus dem 90-Tage-Plan, den es im Profil schon als FREITEXT gibt -- daraus wird hier eine pruefbare Groesse. "Mehr Reichweite" ist kein Ziel, "4 LIVE-Tage pro Woche" ist diff --git a/workspace/app.webmanifest b/workspace/app.webmanifest index 780bc775..7d8274e8 100644 --- a/workspace/app.webmanifest +++ b/workspace/app.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/workspace-192.png?v=202609160059", + "src": "/assets/img/app-symbole/workspace-192.png?v=202609160146", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-512.png?v=202609160059", + "src": "/assets/img/app-symbole/workspace-512.png?v=202609160146", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609160059", + "src": "/assets/img/app-symbole/workspace-192-maskable.png?v=202609160146", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609160059", + "src": "/assets/img/app-symbole/workspace-512-maskable.png?v=202609160146", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/assets/css/leistung.css b/workspace/assets/css/leistung.css index b1b335a4..1dfe9d07 100644 --- a/workspace/assets/css/leistung.css +++ b/workspace/assets/css/leistung.css @@ -779,6 +779,69 @@ font-variant-numeric: tabular-nums; } +/* ---------- Alles, was in der Datei stand ------------------------------ */ +/* Filipe: "wenn ich dir runter lade soll alles notiert und angezeigt + werden." Achtunddreissig Werte je Zeitraum -- offen waeren sie eine + Wand, weg waeren sie eine Luecke. Also ein Deckel, der die ZAHL + nennt: Man sieht ohne Oeffnen, dass nichts fehlt. + + ZWEI SPALTEN, NICHT ZWEI ZEILEN. Name links, Wert rechts, mit einer + gepunkteten Fuehrung dazwischen -- so liest man 38 Paare von oben + nach unten, ohne die Zeile zu verlieren. Untereinander waeren es 76 + Zeilen. */ +.spanne__alles { + margin-top: 12px; + border-top: 1px solid rgba(150, 186, 220, .13); + padding-top: 10px; +} +.spanne__alles-kopf { + cursor: pointer; + list-style: none; + display: flex; align-items: center; gap: 7px; + font-size: .76rem; letter-spacing: .04em; + color: var(--text-leise); +} +.spanne__alles-kopf::-webkit-details-marker { display: none; } +/* Ein eigener Pfeil statt des Standarddreiecks -- das sieht auf jedem + Gerät anders aus, und hier soll alles gleich aussehen. */ +.spanne__alles-kopf::before { + content: ""; + width: 7px; height: 7px; flex: none; + border-right: 1.5px solid currentColor; + border-bottom: 1.5px solid currentColor; + transform: rotate(-45deg); + transition: transform var(--tempo, .2s); +} +.spanne__alles[open] > .spanne__alles-kopf::before { transform: rotate(45deg); } +.spanne__alles-kopf:hover { color: var(--text); } + +.spanne__alles-liste { + margin: 10px 0 0; + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: 0 12px; + align-items: baseline; +} +.spanne__alles-name { + margin: 0; padding: 4px 0; + font-size: .74rem; color: var(--text-still); + min-width: 0; + /* Die gepunktete Fuehrung liegt auf dem NAMEN, nicht als eigenes + Element: ein Unterrand, der genau so breit ist wie der Text. */ + border-bottom: 1px dotted rgba(150, 186, 220, .16); +} +.spanne__alles-wert { + margin: 0; padding: 4px 0; + font-size: .78rem; color: var(--text); + text-align: right; white-space: nowrap; + font-variant-numeric: tabular-nums; + border-bottom: 1px dotted rgba(150, 186, 220, .16); +} + +@media (prefers-reduced-motion: reduce) { + .spanne__alles-kopf::before { transition: none; } +} + @media (max-width: 620px) { .spannen-tafel { --sp-fase: 12px; } .spannen-tafel__kopf { padding: 12px 14px; } diff --git a/workspace/assets/js/leistung.js b/workspace/assets/js/leistung.js index ec49bc6c..6abcbe9e 100644 --- a/workspace/assets/js/leistung.js +++ b/workspace/assets/js/leistung.js @@ -633,6 +633,7 @@ const teile = []; if (r.geschrieben) teile.push(`${r.geschrieben} Tag${r.geschrieben === 1 ? '' : 'e'}`); if (r.zeitraeume) teile.push(`${r.zeitraeume} Zeitraum${r.zeitraeume === 1 ? '' : 'e'}`); + if (r.felder) teile.push(`${r.felder} Werte aus der Datei`); melde(teile.length ? `${teile.join(' und ')} übernommen.` : 'Nichts zu übernehmen.'); setTimeout(() => melde(''), 5000); } catch { /* umgeleitet */ } finally { knopf.disabled = false; } @@ -746,6 +747,38 @@ zeig('Neue Follower', z.follower_neu, zahl); if (werte.children.length) karte.append(werte); + /* ---- UND ALLES ANDERE, WAS IN DER DATEI STAND (16.09.2026) ---- + Filipe: "ich will dass alles von der excel datei genommen wird. + ... wenn ich dir runter lade soll alles notiert und angezeigt + werden." + + Seine Ausgabe hat 41 Spalten; oben stehen die sechs, die das + System deutet. Der Rest -- letzter Monat, fünf Prozentwerte, + Matches, Multi-Gast-LIVEs, Fanclub, Graduierungs- und + Stufenstatus -- steht hier, WÖRTLICH wie in der Datei. + + IN DER REIHENFOLGE DER DATEI, nicht in einer eigenen: Backstage + gruppiert seine Spalten schon sinnvoll, und jede Ordnung, die + ich erfinde, ist beim nächsten Backstage-Update falsch. + + ZUGEKLAPPT, ABER MIT DER ZAHL IM DECKEL. Achtunddreißig Zeilen + offen unter jedem Zeitraum wären die Karte nicht mehr lesbar -- + und genau die Lesbarkeit war gestern sein Thema. Im Deckel + steht, wie viele es sind; damit sieht man ohne Öffnen, dass + nichts fehlt. */ + if (Array.isArray(z.felder) && z.felder.length) { + const alles = el('details', 'spanne__alles'); + alles.append(el('summary', 'spanne__alles-kopf', + `Alle ${z.felder.length} Werte aus Backstage`)); + const liste = el('dl', 'spanne__alles-liste'); + for (const f of z.felder) { + liste.append(el('dt', 'spanne__alles-name', f.name)); + liste.append(el('dd', 'spanne__alles-wert', f.text)); + } + alles.append(liste); + karte.append(alles); + } + ziel.append(karte); } } @@ -873,6 +906,20 @@ zeile(`Erkannte Spalten: ${Object.keys(d.spalten).join(', ') || 'keine'}` + (d.aus_tabelle ? ' · Datum aus der Tabelle' : ' · Datum aus dem Feld oben')); + /* WAS MIT DEM REST PASSIERT -- und das ist seit dem 16.09. die + Zeile, auf die es Filipe ankommt: "ich will dass alles von der + excel datei genommen wird." + + Die Zeile darueber nennt nur die ACHT Spalten, die das System + deutet. Seine echte Ausgabe hat 41. Ohne diese Zeile sieht es + aus, als fielen 33 davon weg -- sie werden aber vollstaendig + mitgeschrieben und stehen danach auf der Karte. */ + if (d.spalten_gesamt) { + zeile(`Alle ${d.spalten_gesamt} Spalten der Datei werden übernommen` + + (d.felder ? ` · ${d.felder} davon tragen einen Wert` : '') + + ' · sie stehen danach unter „Alle Werte aus Backstage“', 'gut'); + } + /* DIE UNBEKANNTEN SIND DER WICHTIGE TEIL. Wer sie übersieht, hat stillschweigend Leute ausgelassen -- und merkt es erst, wenn die Zahlen nicht stimmen. Deshalb stehen sie mit Namen da und nicht @@ -889,7 +936,10 @@ } for (const b of d.beispiele) { const zahlen = Object.entries(b) - .filter(([k]) => !['creator_id', 'name', 'tag'].includes(k)) + /* `felder` ist eine LISTE und gehoert nicht in diese Zeile -- + ohne diesen Ausschluss stand hier woertlich + "felder [object Object],[object Object]". */ + .filter(([k]) => !['creator_id', 'name', 'tag', 'felder'].includes(k)) .filter(([, w]) => w !== null && w !== undefined) .map(([k, w]) => `${k} ${w}`).join(' · '); zeile(`${b.name} · ${b.tag} · ${zahlen}`, 'still'); diff --git a/workspace/aufgaben.html b/workspace/aufgaben.html index 2bb833a0..43260a6f 100644 --- a/workspace/aufgaben.html +++ b/workspace/aufgaben.html @@ -12,19 +12,19 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - + + + + + + - + @@ -282,10 +282,10 @@ - - - - - + + + + + diff --git a/workspace/automation.html b/workspace/automation.html index 21e3f2ea..813f0fee 100644 --- a/workspace/automation.html +++ b/workspace/automation.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -168,10 +168,10 @@ - - - - - + + + + + diff --git a/workspace/befinden.html b/workspace/befinden.html index 8d777661..a0cb0525 100644 --- a/workspace/befinden.html +++ b/workspace/befinden.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -105,9 +105,9 @@ - - - - + + + + diff --git a/workspace/bereich.html b/workspace/bereich.html index 12132c98..264fc074 100644 --- a/workspace/bereich.html +++ b/workspace/bereich.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -181,14 +181,14 @@ - - - - - - - - - + + + + + + + + + diff --git a/workspace/calls.html b/workspace/calls.html index 4e03eb2a..ba4c43ad 100644 --- a/workspace/calls.html +++ b/workspace/calls.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -89,11 +89,11 @@ - - - - - - + + + + + + diff --git a/workspace/chat.html b/workspace/chat.html index 112b5d31..7c8de7d4 100644 --- a/workspace/chat.html +++ b/workspace/chat.html @@ -12,26 +12,26 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -341,10 +341,10 @@ - - - - - + + + + + diff --git a/workspace/content.html b/workspace/content.html index a5acf6e4..6860740c 100644 --- a/workspace/content.html +++ b/workspace/content.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -179,12 +179,12 @@ - - - - - - - + + + + + + + diff --git a/workspace/crew-index.html b/workspace/crew-index.html index e522dc51..6d4eaadd 100644 --- a/workspace/crew-index.html +++ b/workspace/crew-index.html @@ -36,15 +36,15 @@ - - - + + + - + @@ -339,6 +339,6 @@ - + diff --git a/workspace/crew.webmanifest b/workspace/crew.webmanifest index 61e6c25d..881681bc 100644 --- a/workspace/crew.webmanifest +++ b/workspace/crew.webmanifest @@ -13,25 +13,25 @@ "theme_color": "#06090f", "icons": [ { - "src": "/assets/img/app-symbole/crew-192.png?v=202609160059", + "src": "/assets/img/app-symbole/crew-192.png?v=202609160146", "sizes": "192x192", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-512.png?v=202609160059", + "src": "/assets/img/app-symbole/crew-512.png?v=202609160146", "sizes": "512x512", "type": "image/png", "purpose": "any" }, { - "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609160059", + "src": "/assets/img/app-symbole/crew-192-maskable.png?v=202609160146", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609160059", + "src": "/assets/img/app-symbole/crew-512-maskable.png?v=202609160146", "sizes": "512x512", "type": "image/png", "purpose": "maskable" diff --git a/workspace/dateien.html b/workspace/dateien.html index 7f39f524..67401beb 100644 --- a/workspace/dateien.html +++ b/workspace/dateien.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -108,10 +108,10 @@ - - - - - + + + + + diff --git a/workspace/entwicklung.html b/workspace/entwicklung.html index 294cff03..10738752 100644 --- a/workspace/entwicklung.html +++ b/workspace/entwicklung.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -102,9 +102,9 @@ - - - - + + + + diff --git a/workspace/index.html b/workspace/index.html index 0c01645c..81a97679 100644 --- a/workspace/index.html +++ b/workspace/index.html @@ -30,9 +30,9 @@ - - - + + + @@ -401,6 +401,6 @@ - + diff --git a/workspace/kalender.html b/workspace/kalender.html index 40e06277..67b55e24 100644 --- a/workspace/kalender.html +++ b/workspace/kalender.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -377,11 +377,11 @@ - - - - - - + + + + + + diff --git a/workspace/leistung.html b/workspace/leistung.html index bd82d185..59eaf0d6 100644 --- a/workspace/leistung.html +++ b/workspace/leistung.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -444,10 +444,10 @@ - - - - - + + + + + diff --git a/workspace/personen.html b/workspace/personen.html index beb8e9b1..497c728f 100644 --- a/workspace/personen.html +++ b/workspace/personen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -423,10 +423,10 @@ - - - - - + + + + + diff --git a/workspace/profil.html b/workspace/profil.html index 151be0e8..9ab15347 100644 --- a/workspace/profil.html +++ b/workspace/profil.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -214,11 +214,11 @@ - - - - - - + + + + + + diff --git a/workspace/rechte.html b/workspace/rechte.html index b8b3ecb7..a4b4247d 100644 --- a/workspace/rechte.html +++ b/workspace/rechte.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -134,9 +134,9 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - + + + + diff --git a/workspace/report.html b/workspace/report.html index bc823e16..13c9bf94 100644 --- a/workspace/report.html +++ b/workspace/report.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -106,11 +106,11 @@ - - - - - - + + + + + + diff --git a/workspace/scouting.html b/workspace/scouting.html index 33134d47..87206890 100644 --- a/workspace/scouting.html +++ b/workspace/scouting.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -173,10 +173,10 @@ - - - - - + + + + + diff --git a/workspace/start.html b/workspace/start.html index 05d736a6..4cf15668 100644 --- a/workspace/start.html +++ b/workspace/start.html @@ -12,21 +12,21 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - + + - + @@ -674,10 +674,10 @@

- - - - - + + + + + diff --git a/workspace/startcheck.html b/workspace/startcheck.html index e50772ad..b4821b9a 100644 --- a/workspace/startcheck.html +++ b/workspace/startcheck.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -85,11 +85,11 @@ - - - - - - + + + + + + diff --git a/workspace/steckbrief.html b/workspace/steckbrief.html index 23be98d9..6ac04f96 100644 --- a/workspace/steckbrief.html +++ b/workspace/steckbrief.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -146,11 +146,11 @@ - - - - - - + + + + + + diff --git a/workspace/talente.html b/workspace/talente.html index 2dbacdc3..8421bcf4 100644 --- a/workspace/talente.html +++ b/workspace/talente.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -94,9 +94,9 @@ - - - - + + + + diff --git a/workspace/team.html b/workspace/team.html index 6127f0a3..eb427b3f 100644 --- a/workspace/team.html +++ b/workspace/team.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + @@ -110,10 +110,10 @@ - - - - - + + + + + diff --git a/workspace/teamlage.html b/workspace/teamlage.html index 6bd5107f..2b64a75a 100644 --- a/workspace/teamlage.html +++ b/workspace/teamlage.html @@ -7,20 +7,20 @@ - - - - - - - + + + + + + + - + - - - - + + + + diff --git a/workspace/treff-moderation.html b/workspace/treff-moderation.html index a3959018..05c97662 100644 --- a/workspace/treff-moderation.html +++ b/workspace/treff-moderation.html @@ -7,14 +7,14 @@ - - - - - - - - + + + + + + + + @@ -110,9 +110,9 @@ - - - - + + + + diff --git a/workspace/treff-regeln.html b/workspace/treff-regeln.html index c35d15c9..d83bcfca 100644 --- a/workspace/treff-regeln.html +++ b/workspace/treff-regeln.html @@ -7,18 +7,18 @@ - - - - - - - + + + + + + + - + @@ -210,10 +210,10 @@ Beim ersten Anlauf fehlte die Zeile auf genau diesen beiden neuen Seiten. Gefunden hat das nicht das Auge, sondern pruef-css-klassen, die seit dem 07.09. jede Seite danach absucht. --> - - - - - + + + + + diff --git a/workspace/uebersicht.html b/workspace/uebersicht.html index ebebcc4a..a50fa056 100644 --- a/workspace/uebersicht.html +++ b/workspace/uebersicht.html @@ -12,22 +12,22 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - + + + + - - - + + + - + @@ -149,11 +149,11 @@ - + - - - - + + + + diff --git a/workspace/wissen.html b/workspace/wissen.html index fa555d2c..9e305b8c 100644 --- a/workspace/wissen.html +++ b/workspace/wissen.html @@ -12,20 +12,20 @@ Ton wie die Kopfleiste darunter (#06090f), damit Leiste und Seite eine Flaeche sind statt zweier. --> - - - - - - - + + + + + + + - + @@ -190,10 +190,10 @@ - - - - - + + + + +