Verwaltungsseite perfektioniert: Team-Verwaltung für Modis/Scouts/Creator/Manager
Nutzer-Wunsch 21.08.2026: "überall wo Modis sind oder Scouts oder Manager, oder Creator, will ich dass ich die easy über meine Verwaltungsseite hinzufüge und die automatisch in der Website hinzugefügt werden ... auch mit Fotos und TikTok Link." Backend (server-internal, braucht Filipes manuellen Deploy): - Neue Tabelle team_members (Migration 0007) für alle vier Kategorien gemeinsam -- ergänzt, überschreibt NIE die von Hand gepflegten Einträge in data-modis.js/data-scouts.js/data-creator.js. - routes/team.js: öffentliches Lesen (nur aktive Mitglieder, optional nach Kategorie gefiltert), TEAM_MANAGE-geschütztes Anlegen/Bearbeiten/ Löschen, Foto-Upload (gleiches Muster wie "Event des Jahres"), automatische Übersetzung von Rolle/Bio/Zitat wie bei anderen admin-gepflegten Texten. Slug global eindeutig (mit automatischer Kollisionsauflösung), TikTok-Kurzname wird zu voller URL ergänzt. 27 Tests in isolierter Testumgebung geprüft (kein better-sqlite3 lokal kompilierbar). Frontend: - window.dogiTeamLaden() in main.js: holt admin-gepflegte Mitglieder einer (oder aller) Kategorien und bringt sie in exakt dieselbe Form wie die bestehenden data-*.js-Einträge. - team-scouts.html/team-modis.html/creator.html hängen das Ergebnis einfach an ihre bestehenden Arrays an und rendern erneut -- exakt derselbe Look wie die schon bestehenden Profile (Foto/Avatar-Initiale, Rolle, Zitat, TikTok-Button). creator.html hatte bisher ein "return" bei leerem CREATORS-Array, wodurch admin-Creator NIE geladen worden wären -- gefixt. team-modis.html berücksichtigt Rang/Rang-Bezeichnung für die Pyramide. - Neue Sektion "Weitere Manager" auf manager.html, komplett unsichtbar bis der erste Manager angelegt wird (data-manager.js neu, wie data-creator.js aktuell leer). - profil.html sucht jetzt kategorieübergreifend auch in admin-gepflegten Profilen, inkl. korrektem Theme/Zurück-Link auch für Manager. Verwaltung: neue Sektion "Team verwalten" (TEAM_MANAGE-Berechtigung) -- ein Formular für alle vier Kategorien mit Foto-Sofort-Upload, TikTok-Feld, Bio/Zitat, bedingten Rang-Feldern (nur Modi), Kategorie- Filterleiste und Bearbeiten/Löschen pro Eintrag. Beim Testen mit Playwright einen echten Syntaxfehler gefunden und gefixt (ASCII-Anführungszeichen statt schließendem „" in einem Statustext), der das GESAMTE Verwaltungs-Skript und damit die komplette Seite lahmgelegt hätte. Cache-Busting-Version auf 20260821j erhöht. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
+317
-6
@@ -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=20260821i" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821i" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821i" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821i" />
|
||||
<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" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
verwaltung.html — Dogis privater Command-Center-Bereich.
|
||||
@@ -858,6 +858,109 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== "Team verwalten" (21.08.2026) — Nutzer-Wunsch: "überall wo
|
||||
Modis sind oder Scouts oder Manager, oder Creator, will ich dass ich
|
||||
die easy über meine Verwaltungsseite hinzufüge und die automatisch
|
||||
in der Website hinzugefügt werden ... auch mit Fotos und TikTok
|
||||
Link". Ergänzt (überschreibt NICHT) die von Hand gepflegten
|
||||
Einträge in assets/js/data-modis.js/data-scouts.js/data-creator.js/
|
||||
data-manager.js -- neue Mitglieder erscheinen automatisch auf
|
||||
team-modis.html/team-scouts.html/creator.html/manager.html UND auf
|
||||
profil.html, exakt im selben Look wie die schon bestehenden (siehe
|
||||
window.dogiTeamLaden() in main.js). Rolle/Bio/Zitat werden wie
|
||||
"Event des Jahres" nur auf Deutsch eingetippt und beim Speichern
|
||||
automatisch übersetzt. -->
|
||||
<section class="section-tight" id="team-root" style="display:none;padding-top:0;">
|
||||
<div class="container">
|
||||
<h2 class="text-center" style="margin-bottom:.3rem;">🧑🤝🧑 Team verwalten</h2>
|
||||
<p class="small muted text-center" style="max-width:64ch;margin:0 auto 1.4rem;">
|
||||
Modis, Scouts, Creator und Manager — neue Mitglieder erscheinen automatisch auf der
|
||||
passenden Seite (mit eigenem Profil, Foto und TikTok-Button), genau wie die schon
|
||||
bestehenden. Rolle/Vorstellung/Zitat bitte auf Deutsch eintippen, der Rest wird beim
|
||||
Speichern automatisch übersetzt.
|
||||
</p>
|
||||
|
||||
<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="" />
|
||||
<div class="field">
|
||||
<label for="team-kategorie">Kategorie</label>
|
||||
<select id="team-kategorie">
|
||||
<option value="modi">Modi (Team Dogi)</option>
|
||||
<option value="scout">Scout (Spicy Media)</option>
|
||||
<option value="creator">Creator (Spicy Media)</option>
|
||||
<option value="manager">Manager (Spicy Media)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-name">Name</label>
|
||||
<input type="text" id="team-name" maxlength="60" placeholder="z. B. Foxy" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-rolle">Rolle (Deutsch)</label>
|
||||
<input type="text" id="team-rolle" maxlength="120" placeholder="z. B. Scout (Manager) — Rechte Hand" />
|
||||
</div>
|
||||
<div style="margin-bottom:1rem;text-align:center;">
|
||||
<img id="team-foto-vorschau" alt="" style="width:140px;height:140px;object-fit:cover;border-radius:50%;background:var(--bg-alt);display:none;" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-foto-input">Foto (optional, max. 5 MB, JPG/PNG/WebP)</label>
|
||||
<input type="file" id="team-foto-input" accept="image/jpeg,image/png,image/webp" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-tiktok">TikTok (Benutzername oder Link, optional)</label>
|
||||
<input type="text" id="team-tiktok" maxlength="200" placeholder="z. B. @foxy123 oder https://tiktok.com/@foxy123" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-bio">Kurze Vorstellung (Deutsch, optional)</label>
|
||||
<textarea id="team-bio" maxlength="800" rows="3"></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-zitat">Zitat/Motto (Deutsch, optional)</label>
|
||||
<input type="text" id="team-zitat" maxlength="200" />
|
||||
</div>
|
||||
<div id="team-rang-felder" style="display:none;">
|
||||
<div class="grid grid-2" style="gap:.8rem;">
|
||||
<div class="field">
|
||||
<label for="team-rang">Rang (Pyramiden-Stufe)</label>
|
||||
<input type="number" id="team-rang" min="1" max="99" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-rang-label">Rang-Bezeichnung (Deutsch)</label>
|
||||
<input type="text" id="team-rang-label" maxlength="40" placeholder="z. B. Rechte Hand" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="small muted" style="margin:.3rem 0 1rem;">
|
||||
Bestimmt die Ebene in der Modi-Pyramide — ohne Rang landet die Person automatisch
|
||||
ganz unten.
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-reihenfolge">Reihenfolge (kleinere Zahl erscheint zuerst)</label>
|
||||
<input type="number" id="team-reihenfolge" value="0" />
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:.6rem;margin:1rem 0;cursor:pointer;">
|
||||
<input type="checkbox" id="team-aktiv" checked style="width:1.2rem;height:1.2rem;" />
|
||||
<span>Aktiv — öffentlich sichtbar</span>
|
||||
</label>
|
||||
<div class="btn-row">
|
||||
<button type="button" class="btn btn-primary" id="team-speichern" style="flex:1;">Speichern</button>
|
||||
<button type="button" class="btn btn-outline" id="team-abbrechen" style="display:none;">Abbrechen</button>
|
||||
</div>
|
||||
<p class="small" id="team-status" style="margin-top:.6rem;"></p>
|
||||
</div>
|
||||
|
||||
<div class="btn-row" style="justify-content:center;margin-bottom:1.4rem;" id="team-filter-bar">
|
||||
<button type="button" class="filter-btn active" data-team-filter="alle">Alle</button>
|
||||
<button type="button" class="filter-btn" data-team-filter="modi">Modi</button>
|
||||
<button type="button" class="filter-btn" data-team-filter="scout">Scouts</button>
|
||||
<button type="button" class="filter-btn" data-team-filter="creator">Creator</button>
|
||||
<button type="button" class="filter-btn" data-team-filter="manager">Manager</button>
|
||||
</div>
|
||||
<div id="team-liste"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== Supporter-Liste ===== -->
|
||||
<section class="section-tight" id="supp-root" style="display:none;padding-top:0;">
|
||||
<div class="container" id="liste-ansicht">
|
||||
@@ -1032,8 +1135,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/main.js?v=20260821i"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260821i"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260821j"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||
@@ -1113,6 +1216,10 @@
|
||||
document.getElementById("testimonials-root").style.display = "";
|
||||
await ladeUndRenderTestimonials();
|
||||
}
|
||||
if (window.AdminAuth.sessionHasPermission(session, "TEAM_MANAGE")) {
|
||||
document.getElementById("team-root").style.display = "";
|
||||
await ladeUndRenderTeam();
|
||||
}
|
||||
const pollsBox = document.getElementById("box-abstimmungen");
|
||||
if (window.AdminAuth.sessionHasPermission(session, "POLLS_MANAGE")) {
|
||||
pollsBox.style.display = "";
|
||||
@@ -1132,6 +1239,7 @@
|
||||
document.getElementById("events-root").style.display = "none";
|
||||
document.getElementById("special-event-root").style.display = "none";
|
||||
document.getElementById("testimonials-root").style.display = "none";
|
||||
document.getElementById("team-root").style.display = "none";
|
||||
document.getElementById("box-abstimmungen").style.display = "none";
|
||||
}
|
||||
|
||||
@@ -1548,6 +1656,209 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ===== "Team verwalten" — Modis/Scouts/Creator/Manager (21.08.2026) =====
|
||||
Ein Formular für alle vier Kategorien (identische Felder), darunter
|
||||
eine nach Kategorie filterbare Liste aller bereits angelegten
|
||||
Mitglieder mit Bearbeiten/Löschen. Rolle/Bio/Zitat werden beim
|
||||
Speichern serverseitig automatisch übersetzt (siehe routes/team.js) --
|
||||
genau wie bei "Event des Jahres". Ein hochgeladenes Foto landet SOFORT
|
||||
bei Auswahl auf dem Server (nicht erst bei "Speichern"), gleiches
|
||||
Muster wie bei den Event-Bildern weiter oben. */
|
||||
const TEAM_KATEGORIE_LABELS = { modi: "Modi", scout: "Scout", creator: "Creator", manager: "Manager" };
|
||||
let teamHochgeladenesFoto = null;
|
||||
let teamAktiverFilter = "alle";
|
||||
|
||||
function teamKarteHtml(m) {
|
||||
const fotoHtml = m.foto
|
||||
? `<img src="${m.foto}" alt="${escapeHtml(m.name)}" style="width:56px;height:56px;border-radius:50%;object-fit:cover;flex-shrink:0;">`
|
||||
: `<span style="width:56px;height:56px;border-radius:50%;background:var(--bg-alt);display:flex;align-items:center;justify-content:center;font-weight:700;flex-shrink:0;">${escapeHtml((m.name || "?").charAt(0).toUpperCase())}</span>`;
|
||||
return `<div class="card vw-card vw-shine-card" style="margin-bottom:1rem;padding:1.1rem 1.3rem;display:flex;gap:1rem;align-items:center;flex-wrap:wrap;" data-team-id="${m.id}">
|
||||
${fotoHtml}
|
||||
<div style="flex:1;min-width:180px;">
|
||||
<strong>${escapeHtml(m.name)}</strong>
|
||||
<span class="vw-overview-chip">${TEAM_KATEGORIE_LABELS[m.kategorie] || m.kategorie}</span>
|
||||
<span class="vw-overview-chip" style="${m.status === "pausiert" ? "opacity:.6;" : ""}">${m.status === "pausiert" ? "⚪ Pausiert" : "🟢 Aktiv"}</span>
|
||||
<div class="small" style="color:var(--text-muted);">${escapeHtml(m.rolle?.de || "")}</div>
|
||||
${m.tiktok ? `<a href="${escapeHtml(m.tiktok)}" target="_blank" rel="noopener" class="small">♪ TikTok</a>` : ""}
|
||||
</div>
|
||||
<div class="btn-row" style="gap:.5rem;">
|
||||
<button type="button" class="vw-refresh-btn team-bearbeiten-btn">Bearbeiten</button>
|
||||
<button type="button" class="vw-refresh-btn team-loeschen-btn" style="color:#ff8a8a;">Löschen</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
let teamAlleMitglieder = [];
|
||||
|
||||
function teamListeZeichnen() {
|
||||
const listeEl = document.getElementById("team-liste");
|
||||
const gefiltert = teamAktiverFilter === "alle" ? teamAlleMitglieder : teamAlleMitglieder.filter((m) => m.kategorie === teamAktiverFilter);
|
||||
listeEl.innerHTML = gefiltert.length ? gefiltert.map(teamKarteHtml).join("") : `<p class="vw-leer">Noch keine Mitglieder in dieser Kategorie.</p>`;
|
||||
|
||||
listeEl.querySelectorAll(".team-bearbeiten-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", () => {
|
||||
const id = btn.closest("[data-team-id]").dataset.teamId;
|
||||
const m = teamAlleMitglieder.find((x) => x.id === id);
|
||||
if (m) teamFormularBefuellen(m);
|
||||
});
|
||||
});
|
||||
listeEl.querySelectorAll(".team-loeschen-btn").forEach((btn) => {
|
||||
btn.addEventListener("click", async () => {
|
||||
const id = btn.closest("[data-team-id]").dataset.teamId;
|
||||
const m = teamAlleMitglieder.find((x) => x.id === id);
|
||||
if (!confirm(`${m?.name || "Dieses Mitglied"} unwiderruflich löschen?`)) return;
|
||||
btn.disabled = true;
|
||||
await window.AdminAuth.apiCall(API_BASE_URL, "/admin/team/delete", { id });
|
||||
await ladeUndRenderTeam();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function ladeUndRenderTeam() {
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/team/list", {});
|
||||
teamAlleMitglieder = res.ok ? (res.mitglieder || []) : [];
|
||||
teamListeZeichnen();
|
||||
}
|
||||
|
||||
function teamFormularZuruecksetzen() {
|
||||
document.getElementById("team-id").value = "";
|
||||
document.getElementById("team-form-titel").textContent = "Neues Mitglied";
|
||||
document.getElementById("team-kategorie").value = "modi";
|
||||
document.getElementById("team-name").value = "";
|
||||
document.getElementById("team-rolle").value = "";
|
||||
document.getElementById("team-tiktok").value = "";
|
||||
document.getElementById("team-bio").value = "";
|
||||
document.getElementById("team-zitat").value = "";
|
||||
document.getElementById("team-rang").value = "";
|
||||
document.getElementById("team-rang-label").value = "";
|
||||
document.getElementById("team-reihenfolge").value = "0";
|
||||
document.getElementById("team-aktiv").checked = true;
|
||||
document.getElementById("team-foto-vorschau").style.display = "none";
|
||||
document.getElementById("team-foto-input").value = "";
|
||||
document.getElementById("team-abbrechen").style.display = "none";
|
||||
document.getElementById("team-status").textContent = "";
|
||||
teamHochgeladenesFoto = null;
|
||||
teamRangFelderAnpassen();
|
||||
}
|
||||
|
||||
function teamFormularBefuellen(m) {
|
||||
document.getElementById("team-id").value = m.id;
|
||||
document.getElementById("team-form-titel").textContent = `„${m.name}“ bearbeiten`;
|
||||
document.getElementById("team-kategorie").value = m.kategorie;
|
||||
document.getElementById("team-name").value = m.name;
|
||||
document.getElementById("team-rolle").value = m.rolle?.de || "";
|
||||
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-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";
|
||||
teamHochgeladenesFoto = m.foto || null;
|
||||
const vorschau = document.getElementById("team-foto-vorschau");
|
||||
if (teamHochgeladenesFoto) {
|
||||
vorschau.src = teamHochgeladenesFoto;
|
||||
vorschau.style.display = "";
|
||||
} else {
|
||||
vorschau.style.display = "none";
|
||||
}
|
||||
document.getElementById("team-abbrechen").style.display = "";
|
||||
document.getElementById("team-status").textContent = "";
|
||||
teamRangFelderAnpassen();
|
||||
document.getElementById("team-root").scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}
|
||||
|
||||
function teamRangFelderAnpassen() {
|
||||
const istModi = document.getElementById("team-kategorie").value === "modi";
|
||||
document.getElementById("team-rang-felder").style.display = istModi ? "" : "none";
|
||||
}
|
||||
document.getElementById("team-kategorie").addEventListener("change", teamRangFelderAnpassen);
|
||||
document.getElementById("team-abbrechen").addEventListener("click", teamFormularZuruecksetzen);
|
||||
|
||||
document.getElementById("team-foto-input").addEventListener("change", async () => {
|
||||
const input = document.getElementById("team-foto-input");
|
||||
const datei = input.files?.[0];
|
||||
if (!datei) return;
|
||||
const statusEl = document.getElementById("team-status");
|
||||
statusEl.textContent = "Foto wird hochgeladen …";
|
||||
statusEl.style.color = "";
|
||||
const form = new FormData();
|
||||
form.append("image", datei);
|
||||
const session = window.AdminAuth.getSession();
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/admin/team/upload-photo`, {
|
||||
method: "POST",
|
||||
headers: session?.token ? { Authorization: `Bearer ${session.token}` } : {},
|
||||
body: form,
|
||||
});
|
||||
const data = await res.json();
|
||||
if (!data.ok) {
|
||||
statusEl.textContent = data.error || "Foto-Upload fehlgeschlagen.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
teamHochgeladenesFoto = data.url;
|
||||
const vorschau = document.getElementById("team-foto-vorschau");
|
||||
vorschau.src = data.url;
|
||||
vorschau.style.display = "";
|
||||
statusEl.textContent = "Foto hochgeladen — jetzt noch auf „Speichern“ klicken.";
|
||||
} catch {
|
||||
statusEl.textContent = "Foto-Upload fehlgeschlagen (keine Verbindung).";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("team-speichern").addEventListener("click", async () => {
|
||||
const statusEl = document.getElementById("team-status");
|
||||
const name = document.getElementById("team-name").value.trim();
|
||||
const rolle = document.getElementById("team-rolle").value.trim();
|
||||
if (!name || !rolle) {
|
||||
statusEl.textContent = "Name und Rolle sind Pflicht.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById("team-speichern");
|
||||
btn.disabled = true;
|
||||
statusEl.textContent = "Wird gespeichert und übersetzt …";
|
||||
statusEl.style.color = "";
|
||||
const id = document.getElementById("team-id").value || undefined;
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/team/save", {
|
||||
id,
|
||||
kategorie: document.getElementById("team-kategorie").value,
|
||||
name,
|
||||
rolle,
|
||||
foto: teamHochgeladenesFoto,
|
||||
tiktok: document.getElementById("team-tiktok").value.trim(),
|
||||
bio: document.getElementById("team-bio").value.trim(),
|
||||
zitat: document.getElementById("team-zitat").value.trim(),
|
||||
rang: document.getElementById("team-rang").value,
|
||||
rangLabel: document.getElementById("team-rang-label").value.trim(),
|
||||
reihenfolge: document.getElementById("team-reihenfolge").value,
|
||||
status: document.getElementById("team-aktiv").checked ? "aktiv" : "pausiert",
|
||||
});
|
||||
btn.disabled = false;
|
||||
if (!res.ok) {
|
||||
statusEl.textContent = res.error || "Speichern fehlgeschlagen.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "✅ Gespeichert.";
|
||||
statusEl.style.color = "#baf7d6";
|
||||
teamFormularZuruecksetzen();
|
||||
await ladeUndRenderTeam();
|
||||
});
|
||||
|
||||
document.getElementById("team-filter-bar").addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".filter-btn");
|
||||
if (!btn) return;
|
||||
teamAktiverFilter = btn.dataset.teamFilter;
|
||||
document.querySelectorAll("#team-filter-bar .filter-btn").forEach((b) => b.classList.remove("active"));
|
||||
btn.classList.add("active");
|
||||
teamListeZeichnen();
|
||||
});
|
||||
|
||||
teamRangFelderAnpassen();
|
||||
|
||||
// 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