Team-Verwaltung Runde 2: bestehende Mitglieder importieren + Kategorien/Seitentitel editierbar
Nutzer-Wunsch 21.08.2026: "wenn ich eine änderung mache ist es auch für
jeden für die die schon da sind und die neuen" + "ich will ich titel
und namen von kategorien und seiten ändern können in der verwaltung".
Backend (braucht Filipes manuellen Deploy):
- Migration 0008: team_members bekommt intro/bioHtml/extraCta-Spalten
-- volle Feld-Parität mit den von Hand gepflegten Profilen (Diene/
Patrick/Bananenstift/Marina nutzen diese Felder).
- routes/team.js: neue importLegacyMember()-Funktion -- übernimmt ein
bestehendes Profil 1:1 in die Datenbank, OHNE erneut zu übersetzen
(die vorhandenen, von Hand geschriebenen Übersetzungen bleiben
erhalten). Idempotent: mehrfacher Import erzeugt keine Duplikate.
- routes/site-texts.js (neu): admin-editierbare Kategorie-Namen und
Seitentitel, generischer key->{de,...}-Override über app_settings
(wie "Event des Jahres"), fester Schlüssel-Katalog aus
Sicherheitsgründen. Leeres Feld setzt auf den Standardtext zurück.
- 27 Tests in isolierter Testumgebung geprüft (kein better-sqlite3
lokal kompilierbar).
Frontend:
- window.dogiTeamZusammenfuehren() (main.js): admin-Mitglieder
ÜBERSCHREIBEN jetzt gleichnamige statische Einträge (per slug) statt
sie zu duplizieren -- eine Bearbeitung wirkt dadurch für alle.
- window.dogiSiteTexteLaden() + applyTranslations() erweitert um
data-site-text-key -- Kategorie-Seitentitel (team-modis.html/
team-scouts.html/creator.html/manager.html) sind jetzt live editierbar.
- Verwaltung: "📥 Bestehende Mitglieder importieren"-Knopf (holt VanVan/
Diene/Funny/Miss/Marina/Ghost/Patrick/Bananenstift aus den data-*.js-
Dateien, VanVan bewusst ausgenommen -- eigene Sonderkarte + Seite),
erweitertes Formular (Intro/ausführliche Vorstellung/zweiter Button,
eingeklappt unter "Erweitert"), neue Sektion "Kategorien &
Seitentitel" (4 Karten, sofort wirksam auf Website UND Verwaltung).
Beim Testen einen echten UX-Bug gefunden und gefixt: die "Gespeichert"-
Meldung nach dem Speichern eines Mitglieds wurde von der direkt
anschließenden Formular-Zurücksetzung sofort wieder überschrieben und
war nie sichtbar.
Cache-Busting-Version auf 20260821k erhöht.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+279
-8
@@ -19,10 +19,10 @@
|
||||
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-admin.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="DogiCrew-Verwaltung" />
|
||||
<meta name="theme-color" content="#0e0a16" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
verwaltung.html — Dogis privater Command-Center-Bereich.
|
||||
@@ -880,6 +880,20 @@
|
||||
Speichern automatisch übersetzt.
|
||||
</p>
|
||||
|
||||
<!-- 21.08.2026 (Runde 2, Nutzer-Wunsch: "wenn ich eine änderung mache
|
||||
ist es auch für jeden für die die schon da sind und die neuen"):
|
||||
holt VanVan, Diene, Funny, Miss, Marina, Ghost, Patrick,
|
||||
Bananenstift usw. einmalig aus den bestehenden data-*.js-Dateien
|
||||
in die Datenbank -- danach sind sie hier genauso bearbeitbar wie
|
||||
jedes neu angelegte Mitglied. Übernimmt die vorhandenen, von Hand
|
||||
geschriebenen Übersetzungen 1:1 (übersetzt NICHT erneut
|
||||
maschinell). Mehrfach klickbar, ohne Duplikate zu erzeugen --
|
||||
schon importierte Personen werden dabei einfach übersprungen. -->
|
||||
<div class="text-center" style="margin-bottom:1.6rem;">
|
||||
<button type="button" class="btn btn-outline" id="team-import-btn">📥 Bestehende Mitglieder importieren</button>
|
||||
<p class="small muted" id="team-import-status" style="margin-top:.5rem;"></p>
|
||||
</div>
|
||||
|
||||
<div class="card vw-card vw-shine-card vw-noise" style="max-width:640px;margin:0 auto 2rem;padding:1.5rem;">
|
||||
<h3 id="team-form-titel" style="margin-top:0;">Neues Mitglied</h3>
|
||||
<input type="hidden" id="team-id" value="" />
|
||||
@@ -919,6 +933,40 @@
|
||||
<label for="team-zitat">Zitat/Motto (Deutsch, optional)</label>
|
||||
<input type="text" id="team-zitat" maxlength="200" />
|
||||
</div>
|
||||
|
||||
<!-- 21.08.2026 (Runde 2): dieselbe Feld-Tiefe wie die von Hand
|
||||
gepflegten Profile (siehe data-modis.js: intro/bioHtml/
|
||||
extraCta) -- eingeklappt, weil die meisten Mitglieder das
|
||||
nicht brauchen (nur wenige bestehende Profile wie Diene/
|
||||
Patrick/Bananenstift/Marina nutzen das überhaupt). -->
|
||||
<details style="margin-bottom:1.2rem;">
|
||||
<summary style="cursor:pointer;color:var(--accent);font-weight:600;">Erweitert (optional)</summary>
|
||||
<div style="margin-top:1rem;">
|
||||
<div class="field">
|
||||
<label for="team-intro">Kurzer Intro-Text neben dem Foto (Deutsch, optional, HTML erlaubt)</label>
|
||||
<textarea id="team-intro" maxlength="2000" rows="2" placeholder="Ersetzt/ergänzt die kurze Vorstellung oben direkt neben dem Foto."></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-bio-html">Ausführliche Vorstellung (Deutsch, optional, HTML erlaubt: <p>/<h3>/<strong>/<ul><li>)</label>
|
||||
<textarea id="team-bio-html" maxlength="6000" rows="6" placeholder="Erscheint als breiter Lesebereich unterhalb des Steckbriefs, wie bei Diene/Patrick/Bananenstift."></textarea>
|
||||
</div>
|
||||
<div class="grid grid-2" style="gap:.8rem;">
|
||||
<div class="field">
|
||||
<label for="team-extra-cta-href">Zweiter Button: Ziel (z. B. manager.html)</label>
|
||||
<input type="text" id="team-extra-cta-href" maxlength="300" placeholder="z. B. manager.html" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-extra-cta-label">Zweiter Button: Text (Deutsch)</label>
|
||||
<input type="text" id="team-extra-cta-label" maxlength="60" placeholder="z. B. Zum Manager-Profil" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="small muted" style="margin:.3rem 0 0;">
|
||||
Zweiter Button erscheint zusätzlich zu "Profil ansehen" auf der Karte (aktuell nur bei
|
||||
Marina genutzt, Verlinkung zu manager.html).
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div id="team-rang-felder" style="display:none;">
|
||||
<div class="grid grid-2" style="gap:.8rem;">
|
||||
<div class="field">
|
||||
@@ -961,6 +1009,27 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== "Kategorien & Seitentitel" (21.08.2026, Nutzer-Wunsch: "ich
|
||||
will ich titel und namen von kategorien und seiten ändern können in
|
||||
der verwaltung"). Ändert die Kategorie-Namen (z.B. "Modi") und die
|
||||
Überschrift der jeweiligen öffentlichen Seite -- wirkt sofort
|
||||
überall, wo diese Kategorie auftaucht (siehe data-site-text-key in
|
||||
team-modis.html/team-scouts.html/creator.html/manager.html sowie
|
||||
hier in der Team-Verwaltung selbst). Leeres Feld = zurück zum
|
||||
eingebauten Standardtext. Gleiche TEAM_MANAGE-Berechtigung wie die
|
||||
Team-Verwaltung, weil beides zusammengehört. ===== -->
|
||||
<section class="section-tight" id="site-texts-root" style="display:none;padding-top:0;">
|
||||
<div class="container">
|
||||
<h2 class="text-center" style="margin-bottom:.3rem;">🏷️ Kategorien & Seitentitel</h2>
|
||||
<p class="small muted text-center" style="max-width:64ch;margin:0 auto 1.4rem;">
|
||||
Ändert, wie die vier Team-Kategorien überall auf der Seite (und hier in der Verwaltung)
|
||||
heißen. Deutsch eintippen, der Rest wird automatisch übersetzt. Feld leer lassen und
|
||||
speichern setzt wieder auf den Standardtext zurück.
|
||||
</p>
|
||||
<div class="grid grid-2" id="site-texts-grid" style="gap:1.2rem;max-width:900px;margin:0 auto;"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== Supporter-Liste ===== -->
|
||||
<section class="section-tight" id="supp-root" style="display:none;padding-top:0;">
|
||||
<div class="container" id="liste-ansicht">
|
||||
@@ -1135,8 +1204,15 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260821j"></script>
|
||||
<!-- 21.08.2026: für den "Bestehende Mitglieder importieren"-Knopf in der
|
||||
Team-Verwaltung -- liest die von Hand gepflegten Profile aus diesen
|
||||
Dateien aus und bietet an, sie 1:1 in die Datenbank zu übernehmen. -->
|
||||
<script src="assets/js/data-modis.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-creator.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-manager.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260821k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||
@@ -1219,6 +1295,8 @@
|
||||
if (window.AdminAuth.sessionHasPermission(session, "TEAM_MANAGE")) {
|
||||
document.getElementById("team-root").style.display = "";
|
||||
await ladeUndRenderTeam();
|
||||
document.getElementById("site-texts-root").style.display = "";
|
||||
await ladeUndRenderSiteTexte();
|
||||
}
|
||||
const pollsBox = document.getElementById("box-abstimmungen");
|
||||
if (window.AdminAuth.sessionHasPermission(session, "POLLS_MANAGE")) {
|
||||
@@ -1240,6 +1318,7 @@
|
||||
document.getElementById("special-event-root").style.display = "none";
|
||||
document.getElementById("testimonials-root").style.display = "none";
|
||||
document.getElementById("team-root").style.display = "none";
|
||||
document.getElementById("site-texts-root").style.display = "none";
|
||||
document.getElementById("box-abstimmungen").style.display = "none";
|
||||
}
|
||||
|
||||
@@ -1729,6 +1808,10 @@
|
||||
document.getElementById("team-tiktok").value = "";
|
||||
document.getElementById("team-bio").value = "";
|
||||
document.getElementById("team-zitat").value = "";
|
||||
document.getElementById("team-intro").value = "";
|
||||
document.getElementById("team-bio-html").value = "";
|
||||
document.getElementById("team-extra-cta-href").value = "";
|
||||
document.getElementById("team-extra-cta-label").value = "";
|
||||
document.getElementById("team-rang").value = "";
|
||||
document.getElementById("team-rang-label").value = "";
|
||||
document.getElementById("team-reihenfolge").value = "0";
|
||||
@@ -1737,6 +1820,7 @@
|
||||
document.getElementById("team-foto-input").value = "";
|
||||
document.getElementById("team-abbrechen").style.display = "none";
|
||||
document.getElementById("team-status").textContent = "";
|
||||
document.getElementById("team-intro").closest("details").open = false;
|
||||
teamHochgeladenesFoto = null;
|
||||
teamRangFelderAnpassen();
|
||||
}
|
||||
@@ -1750,10 +1834,18 @@
|
||||
document.getElementById("team-tiktok").value = m.tiktok || "";
|
||||
document.getElementById("team-bio").value = m.bio?.de || "";
|
||||
document.getElementById("team-zitat").value = m.zitat?.de || "";
|
||||
document.getElementById("team-intro").value = m.intro?.de || "";
|
||||
document.getElementById("team-bio-html").value = m.bioHtml?.de || "";
|
||||
document.getElementById("team-extra-cta-href").value = m.extraCta?.href || "";
|
||||
document.getElementById("team-extra-cta-label").value = m.extraCta?.label?.de || "";
|
||||
document.getElementById("team-rang").value = m.rang || "";
|
||||
document.getElementById("team-rang-label").value = m.rangLabel?.de || "";
|
||||
document.getElementById("team-reihenfolge").value = m.reihenfolge ?? 0;
|
||||
document.getElementById("team-aktiv").checked = m.status !== "pausiert";
|
||||
// "Erweitert" automatisch aufklappen, wenn schon etwas drinsteht --
|
||||
// sonst würde man leicht übersehen, dass z.B. Diene eine lange
|
||||
// bioHtml-Vorstellung hat, weil sie eingeklappt verborgen bliebe.
|
||||
document.getElementById("team-intro").closest("details").open = !!(m.intro?.de || m.bioHtml?.de || m.extraCta?.href);
|
||||
teamHochgeladenesFoto = m.foto || null;
|
||||
const vorschau = document.getElementById("team-foto-vorschau");
|
||||
if (teamHochgeladenesFoto) {
|
||||
@@ -1831,6 +1923,10 @@
|
||||
tiktok: document.getElementById("team-tiktok").value.trim(),
|
||||
bio: document.getElementById("team-bio").value.trim(),
|
||||
zitat: document.getElementById("team-zitat").value.trim(),
|
||||
intro: document.getElementById("team-intro").value.trim(),
|
||||
bioHtml: document.getElementById("team-bio-html").value.trim(),
|
||||
extraCtaHref: document.getElementById("team-extra-cta-href").value.trim(),
|
||||
extraCtaLabel: document.getElementById("team-extra-cta-label").value.trim(),
|
||||
rang: document.getElementById("team-rang").value,
|
||||
rangLabel: document.getElementById("team-rang-label").value.trim(),
|
||||
reihenfolge: document.getElementById("team-reihenfolge").value,
|
||||
@@ -1842,10 +1938,15 @@
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "✅ Gespeichert.";
|
||||
statusEl.style.color = "#baf7d6";
|
||||
teamFormularZuruecksetzen();
|
||||
await ladeUndRenderTeam();
|
||||
// WICHTIG: erst NACH teamFormularZuruecksetzen() setzen -- die Funktion
|
||||
// leert team-status als Teil des normalen Zurücksetzens, ein Erfolgs-
|
||||
// text davor wäre also sofort wieder verschwunden, bevor er je zu sehen
|
||||
// war (mit Playwright gefunden: der Status blieb nach dem Speichern
|
||||
// immer leer).
|
||||
document.getElementById("team-status").textContent = "✅ Gespeichert.";
|
||||
document.getElementById("team-status").style.color = "#baf7d6";
|
||||
});
|
||||
|
||||
document.getElementById("team-filter-bar").addEventListener("click", (e) => {
|
||||
@@ -1859,6 +1960,176 @@
|
||||
|
||||
teamRangFelderAnpassen();
|
||||
|
||||
/* "Bestehende Mitglieder importieren" (21.08.2026, Runde 2, Nutzer-Wunsch:
|
||||
"wenn ich eine änderung mache ist es auch für jeden für die die schon
|
||||
da sind und die neuen"). Liest VanVan/Diene/Funny/Miss/Marina/Ghost
|
||||
(window.MODIS, VanVan bewusst ausgenommen -- sie hat ihre eigene,
|
||||
abweichend gebaute Hervorhebungs-Karte + eigene Seite vanvan.html,
|
||||
passt nicht ins generische Schema), Patrick/Bananenstift
|
||||
(window.SCOUTS) sowie window.CREATORS/MANAGERS (aktuell leer) aus und
|
||||
schickt jede Person 1:1 an /admin/team/import. Übernimmt die
|
||||
VORHANDENEN, von Hand geschriebenen Übersetzungen -- KEINE erneute
|
||||
Maschinenübersetzung. Mehrfach klickbar (idempotent, siehe
|
||||
routes/team.js): bereits importierte Personen werden serverseitig
|
||||
einfach übersprungen, nie dupliziert. */
|
||||
function zuSprachObjekt(feld) {
|
||||
if (!feld) return null;
|
||||
if (typeof feld === "string") return { de: feld, "de-CH": feld, en: feld, fr: feld, pt: feld };
|
||||
if (typeof feld === "object" && feld.de) return feld;
|
||||
return null;
|
||||
}
|
||||
function slugAusNameClient(name) {
|
||||
return String(name || "")
|
||||
.toLowerCase()
|
||||
.replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss")
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
document.getElementById("team-import-btn").addEventListener("click", async () => {
|
||||
const btn = document.getElementById("team-import-btn");
|
||||
const statusEl = document.getElementById("team-import-status");
|
||||
btn.disabled = true;
|
||||
statusEl.textContent = "Importiere …";
|
||||
|
||||
const quellen = [
|
||||
...(window.MODIS || []).filter((p) => p.name !== "VanVan").map((p) => ({ p, kategorie: "modi" })),
|
||||
...(window.SCOUTS || []).map((p) => ({ p, kategorie: "scout" })),
|
||||
...(window.CREATORS || []).map((p) => ({ p, kategorie: "creator" })),
|
||||
...(window.MANAGERS || []).map((p) => ({ p, kategorie: "manager" })),
|
||||
];
|
||||
|
||||
let importiert = 0, uebersprungen = 0, fehler = 0;
|
||||
for (const { p, kategorie } of quellen) {
|
||||
const rolle = zuSprachObjekt(p.role);
|
||||
if (!rolle) { fehler++; continue; } // Pflichtfeld fehlt -- diese Person überspringen, nicht alle abbrechen
|
||||
const payload = {
|
||||
kategorie,
|
||||
name: p.name,
|
||||
slug: p.slug || slugAusNameClient(p.name),
|
||||
rolle,
|
||||
foto: p.photo || p.avatar || "",
|
||||
tiktok: p.social?.tiktok || null,
|
||||
bio: zuSprachObjekt(p.bio),
|
||||
intro: zuSprachObjekt(p.intro),
|
||||
bioHtml: zuSprachObjekt(p.bioHtml),
|
||||
zitat: zuSprachObjekt(p.quote),
|
||||
rang: p.rank || null,
|
||||
rangLabel: zuSprachObjekt(p.rankLabel),
|
||||
extraCta: p.extraCta?.href ? { href: p.extraCta.href, label: zuSprachObjekt(p.extraCta.label) } : null,
|
||||
};
|
||||
try {
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/team/import", payload);
|
||||
if (res.ok && res.uebersprungen) uebersprungen++;
|
||||
else if (res.ok) importiert++;
|
||||
else fehler++;
|
||||
} catch {
|
||||
fehler++;
|
||||
}
|
||||
}
|
||||
|
||||
statusEl.textContent = `✅ ${importiert} importiert, ${uebersprungen} schon vorhanden` + (fehler ? `, ${fehler} übersprungen (fehlerhaft)` : "") + ".";
|
||||
btn.disabled = false;
|
||||
await ladeUndRenderTeam();
|
||||
});
|
||||
|
||||
/* ===== "Kategorien & Seitentitel" (21.08.2026) =====
|
||||
Ein Kärtchen pro Kategorie mit zwei Feldern (Kategorie-Name,
|
||||
Seitentitel) -- speichert beide über denselben admin/site-texts/save-
|
||||
Endpunkt wie zwei unabhängige Overrides. Aktualisiert nach dem
|
||||
Speichern sofort die Kategorie-Badges in der Team-Liste, damit man den
|
||||
Effekt nicht erst durch Neuladen sehen muss. */
|
||||
const SITE_TEXT_KATEGORIEN = [
|
||||
{ kat: "modi", label: "Modi", kategorieKey: "kategorie_modi", seitentitelKey: "seitentitel_modi", kategoriePlatzhalter: "Modi", seitentitelPlatzhalter: "Das Modi-Team" },
|
||||
{ kat: "scout", label: "Scout", kategorieKey: "kategorie_scout", seitentitelKey: "seitentitel_scout", kategoriePlatzhalter: "Scouts", seitentitelPlatzhalter: "Mein Scout-Team" },
|
||||
{ kat: "creator", label: "Creator", kategorieKey: "kategorie_creator", seitentitelKey: "seitentitel_creator", kategoriePlatzhalter: "Creator", seitentitelPlatzhalter: "Creator im Team" },
|
||||
{ kat: "manager", label: "Manager", kategorieKey: "kategorie_manager", seitentitelKey: "seitentitel_manager", kategoriePlatzhalter: "Manager", seitentitelPlatzhalter: "Weitere Manager" },
|
||||
];
|
||||
|
||||
function siteTextKarteHtml(eintrag) {
|
||||
return `<div class="card vw-card vw-shine-card vw-noise" style="padding:1.3rem;" data-site-text-kat="${eintrag.kat}">
|
||||
<h3 style="margin-top:0;">${eintrag.label}</h3>
|
||||
<div class="field">
|
||||
<label>Kategorie-Name</label>
|
||||
<input type="text" class="site-text-kategorie-input" maxlength="40" placeholder="${eintrag.kategoriePlatzhalter}" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Seitentitel</label>
|
||||
<input type="text" class="site-text-seitentitel-input" maxlength="100" placeholder="${eintrag.seitentitelPlatzhalter}" />
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline site-text-speichern-btn" style="width:100%;">Speichern</button>
|
||||
<p class="small site-text-status" style="margin-top:.5rem;"></p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function teamKategorieLabelsAktualisieren(texteVorhanden) {
|
||||
let texte = texteVorhanden;
|
||||
if (!texte) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/site-texts`, { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
texte = data.ok ? data.texte || {} : {};
|
||||
} catch {
|
||||
texte = {};
|
||||
}
|
||||
}
|
||||
Object.keys(TEAM_KATEGORIE_LABELS).forEach((kat) => {
|
||||
const override = texte[`kategorie_${kat}`]?.de;
|
||||
if (override) TEAM_KATEGORIE_LABELS[kat] = override;
|
||||
});
|
||||
document.querySelectorAll("#team-filter-bar .filter-btn[data-team-filter]").forEach((btn) => {
|
||||
const kat = btn.dataset.teamFilter;
|
||||
if (TEAM_KATEGORIE_LABELS[kat]) btn.textContent = TEAM_KATEGORIE_LABELS[kat];
|
||||
});
|
||||
}
|
||||
|
||||
async function ladeUndRenderSiteTexte() {
|
||||
const grid = document.getElementById("site-texts-grid");
|
||||
grid.innerHTML = SITE_TEXT_KATEGORIEN.map(siteTextKarteHtml).join("");
|
||||
|
||||
let aktuelleTexte = {};
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/site-texts`, { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
if (data.ok) aktuelleTexte = data.texte || {};
|
||||
} catch {
|
||||
/* Formular bleibt leer (= eingebauter Standardtext), Speichern funktioniert trotzdem. */
|
||||
}
|
||||
|
||||
SITE_TEXT_KATEGORIEN.forEach((eintrag) => {
|
||||
const karte = grid.querySelector(`[data-site-text-kat="${eintrag.kat}"]`);
|
||||
karte.querySelector(".site-text-kategorie-input").value = aktuelleTexte[eintrag.kategorieKey]?.de || "";
|
||||
karte.querySelector(".site-text-seitentitel-input").value = aktuelleTexte[eintrag.seitentitelKey]?.de || "";
|
||||
|
||||
karte.querySelector(".site-text-speichern-btn").addEventListener("click", async () => {
|
||||
const statusEl = karte.querySelector(".site-text-status");
|
||||
const btn = karte.querySelector(".site-text-speichern-btn");
|
||||
btn.disabled = true;
|
||||
statusEl.textContent = "Wird gespeichert …";
|
||||
statusEl.style.color = "";
|
||||
const kategorieText = karte.querySelector(".site-text-kategorie-input").value.trim();
|
||||
const seitentitelText = karte.querySelector(".site-text-seitentitel-input").value.trim();
|
||||
const [r1, r2] = await Promise.all([
|
||||
window.AdminAuth.apiCall(API_BASE_URL, "/admin/site-texts/save", { key: eintrag.kategorieKey, text: kategorieText }),
|
||||
window.AdminAuth.apiCall(API_BASE_URL, "/admin/site-texts/save", { key: eintrag.seitentitelKey, text: seitentitelText }),
|
||||
]);
|
||||
btn.disabled = false;
|
||||
if (!r1.ok || !r2.ok) {
|
||||
statusEl.textContent = r1.error || r2.error || "Speichern fehlgeschlagen.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "✅ Gespeichert — wirkt sofort auf der Website.";
|
||||
statusEl.style.color = "#baf7d6";
|
||||
await teamKategorieLabelsAktualisieren();
|
||||
teamListeZeichnen();
|
||||
});
|
||||
});
|
||||
|
||||
await teamKategorieLabelsAktualisieren(aktuelleTexte);
|
||||
teamListeZeichnen();
|
||||
}
|
||||
|
||||
// Nutzer-Wunsch 04.08.2026 (6. Runde): zwei getrennte Kisten/Formulare
|
||||
// (Dogi + VanVan) statt einem gemeinsamen Feld — beide rufen denselben
|
||||
// Login-Endpunkt auf, die Fehlermeldung erscheint jeweils nur in der
|
||||
|
||||
Reference in New Issue
Block a user