Zweite Live-Uhr fuer "spezielles Live-Event" + mehr Farbe im Radar
Nutzer-Wunsch 20.08.2026: "wuensch mir nur noch bissl farbe und sehr wichtig ist dass ich auch spezielle live events da auch eintragen kann in der verwaltungsseite so dass da eine zweite uhr erscheint wo die zeit dann fuer dieses spezielle event laeuft." - Neue Verwaltungs-Sektion "Spezielles Live-Event": Titel (Deutsch, wird automatisch uebersetzt), echtes Datum+Uhrzeit, optionaler Link, Aktiv- Schalter -- nur mit Titel + Zukunftsdatum aktivierbar. - Zweite Uhr auf streamplan.html (Magenta/Violett statt Cyan/Gold, direkt neben der bestehenden), nur sichtbar wenn ein Event aktiviert ist und das Zieldatum noch nicht vorbei ist. Echter Countdown inkl. Tage (z.B. "2T 03:14:59"), "Jetzt"-Zeiger zeigt wie bei Uhr 1 die tatsaechliche Uhrzeit, der magentafarbene Fixpunkt markiert die Tageszeit des Events. Zieldatum wird als UTC gespeichert -- jede besuchende Person sieht den exakt richtigen Countdown in ihrer eigenen Zeitzone. - "Bissl Farbe": bunter Farbverlauf (Cyan/Violett/Gold) auf dem aeusseren Ring statt reinem Grauton, kraeftigerer Sweep-Farbschein. - Backend: neue routes/special-event.js (getSpecialEventPublic nur bei aktiv+zukuenftig, getSpecialEventAdmin fuer Entwuerfe, saveSpecialEvent mit Validierung), 13 automatisierte Tests gegen eine Fake-DB bestanden. - Bugfix unterwegs gefunden (Playwright-Screenshot, wiederholtes Muster auf dieser Seite): .stream-radar-wrap blieb trotz [hidden]-Attribut sichtbar (display:block ueberschreibt die eingebaute [hidden]-Regel bei gleicher Spezifitaet) -- explizite Regel ergaenzt, per Playwright erneut verifiziert (display:none bestaetigt). Backend-Teil (server-internal/) noch ohne Deploy-Zugriff -- Dogi muss ihn manuell auf dogiintern ausrollen, sonst bleibt die zweite Uhr unsichtbar. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+111
-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=20260820i" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260820i" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260820i" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260820i" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260820j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260820j" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
verwaltung.html — Dogis privater Command-Center-Bereich.
|
||||
@@ -803,6 +803,47 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== "Spezielles Live-Event" (Streamplan-Seite) — 20.08.2026,
|
||||
Nutzer-Wunsch: "spezielle live events da auch eintragen ... so dass
|
||||
da eine zweite uhr erscheint wo die zeit dann für dieses spezielle
|
||||
event läuft." Anders als "Event des Jahres" hier ein echtes
|
||||
Datum+Uhrzeit statt eines freien Textes -- daraus baut
|
||||
streamplan.html einen echten Countdown. Nur sichtbar mit derselben
|
||||
EVENTS_MANAGE-Berechtigung. -->
|
||||
<section class="section-tight" id="special-event-root" style="display:none;padding-top:0;">
|
||||
<div class="container">
|
||||
<h2 class="text-center" style="margin-bottom:.3rem;">⚡ Spezielles Live-Event</h2>
|
||||
<p class="small muted text-center" style="max-width:60ch;margin:0 auto 1.4rem;">
|
||||
Erscheint als zweite Uhr auf der Streamplan-Seite, sobald aktiviert. Titel bitte auf
|
||||
Deutsch eintippen, wird beim Speichern automatisch übersetzt.
|
||||
</p>
|
||||
<div class="card vw-card vw-shine-card vw-noise" style="max-width:520px;margin:0 auto;padding:1.5rem;">
|
||||
<div class="field">
|
||||
<label for="se-titel">Titel (Deutsch)</label>
|
||||
<input type="text" id="se-titel" maxlength="120" placeholder="z. B. Großer Q&A-Abend" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="se-datum">Datum & Uhrzeit</label>
|
||||
<input type="datetime-local" id="se-datum" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="se-link">Link (optional, muss mit https:// beginnen)</label>
|
||||
<input type="url" id="se-link" placeholder="https://…" />
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:.6rem;margin:1rem 0;cursor:pointer;">
|
||||
<input type="checkbox" id="se-aktiv" style="width:1.2rem;height:1.2rem;" />
|
||||
<span>Aktiv — zweite Uhr auf der Streamplan-Seite sichtbar</span>
|
||||
</label>
|
||||
<p class="small muted" style="margin:-.4rem 0 1rem;">
|
||||
Nur mit Titel + Datum in der Zukunft aktivierbar. Ist das Datum vorbei, verschwindet
|
||||
die zweite Uhr automatisch wieder.
|
||||
</p>
|
||||
<button type="button" class="btn btn-primary" id="se-speichern" style="width:100%;">Speichern</button>
|
||||
<p class="small" id="se-status" style="margin-top:.6rem;"></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== Supporter-Liste ===== -->
|
||||
<section class="section-tight" id="supp-root" style="display:none;padding-top:0;">
|
||||
<div class="container" id="liste-ansicht">
|
||||
@@ -977,8 +1018,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/main.js?v=20260820i"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260820i"></script>
|
||||
<script src="assets/js/main.js?v=20260820j"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260820j"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||
@@ -1051,6 +1092,8 @@
|
||||
if (window.AdminAuth.sessionHasPermission(session, "EVENTS_MANAGE")) {
|
||||
document.getElementById("events-root").style.display = "";
|
||||
await ladeUndRenderEvents(session);
|
||||
document.getElementById("special-event-root").style.display = "";
|
||||
await ladeUndRenderSpecialEvent();
|
||||
}
|
||||
const pollsBox = document.getElementById("box-abstimmungen");
|
||||
if (window.AdminAuth.sessionHasPermission(session, "POLLS_MANAGE")) {
|
||||
@@ -1069,6 +1112,7 @@
|
||||
document.getElementById("vw-stats-root").style.display = "none";
|
||||
document.getElementById("supp-root").style.display = "none";
|
||||
document.getElementById("events-root").style.display = "none";
|
||||
document.getElementById("special-event-root").style.display = "none";
|
||||
document.getElementById("box-abstimmungen").style.display = "none";
|
||||
}
|
||||
|
||||
@@ -1372,6 +1416,67 @@
|
||||
});
|
||||
}
|
||||
|
||||
/* ===== "Spezielles Live-Event" — Verwaltung (20.08.2026) =====
|
||||
Datum+Uhrzeit kommt vom Server als UTC-ISO-String, das <input
|
||||
type="datetime-local"> braucht aber "YYYY-MM-DDTHH:mm" in der
|
||||
LOKALEN Zeitzone der Person, die gerade in der Verwaltung sitzt —
|
||||
deshalb hier von Hand umgerechnet statt toISOString() (das würde UTC
|
||||
zurückgeben und beim erneuten Öffnen eine falsche Uhrzeit anzeigen). */
|
||||
function isoZuLokalemDatetimeLocal(iso) {
|
||||
const d = new Date(iso);
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
|
||||
}
|
||||
|
||||
async function ladeUndRenderSpecialEvent() {
|
||||
const statusEl = document.getElementById("se-status");
|
||||
let eintrag = null;
|
||||
try {
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/special-event", {});
|
||||
if (res.ok) eintrag = res.event;
|
||||
} catch {
|
||||
/* Formular bleibt leer, Speichern funktioniert trotzdem. */
|
||||
}
|
||||
if (eintrag) {
|
||||
document.getElementById("se-titel").value = eintrag.titel?.de || "";
|
||||
document.getElementById("se-datum").value = isoZuLokalemDatetimeLocal(eintrag.zieldatum);
|
||||
document.getElementById("se-link").value = eintrag.link || "";
|
||||
document.getElementById("se-aktiv").checked = !!eintrag.aktiv;
|
||||
}
|
||||
|
||||
document.getElementById("se-speichern").addEventListener("click", async () => {
|
||||
statusEl.textContent = "";
|
||||
const titel = document.getElementById("se-titel").value.trim();
|
||||
const datumRoh = document.getElementById("se-datum").value; // "YYYY-MM-DDTHH:mm", lokale Zeit
|
||||
if (!titel || !datumRoh) {
|
||||
statusEl.textContent = "Titel und Datum sind Pflicht.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "Wird gespeichert und übersetzt …";
|
||||
statusEl.style.color = "";
|
||||
// new Date("YYYY-MM-DDTHH:mm") interpretiert das als lokale Zeit dieses
|
||||
// Browsers -- .toISOString() macht daraus den korrekten UTC-Zeitpunkt,
|
||||
// den jede besuchende Person später in IHRER eigenen Ortszeit sieht.
|
||||
const zieldatum = new Date(datumRoh).toISOString();
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/special-event/save", {
|
||||
titel,
|
||||
zieldatum,
|
||||
link: document.getElementById("se-link").value.trim(),
|
||||
aktiv: document.getElementById("se-aktiv").checked,
|
||||
});
|
||||
if (!res.ok) {
|
||||
statusEl.textContent = res.error || "Speichern fehlgeschlagen.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = res.event.aktiv
|
||||
? "✅ Gespeichert — zweite Uhr ist live auf der Streamplan-Seite."
|
||||
: "✅ Als Entwurf gespeichert (noch nicht sichtbar).";
|
||||
statusEl.style.color = "#baf7d6";
|
||||
});
|
||||
}
|
||||
|
||||
// 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