/* ===================================================================== postfach.js — Logik für postfach.html ===================================================================== */ const API_BASE_URL = "https://dogfather-universe-postfach.dogfather1608.workers.dev"; const { getSession, sessionHasPermission, apiCall, doLogin, doLogout } = window.AdminAuth; const STATUS_OPTIONS = ["neu", "in_bearbeitung", "beantwortet"]; function $(id) { return document.getElementById(id); } function showApp(session) { $("gate-container").style.display = "none"; $("session-bar").style.display = "block"; $("inbox-section").style.display = "block"; $("role-badge").textContent = session.isOwner ? "Eingeloggt als: Dogi (Owner)" : `Eingeloggt als: ${session.name} (${session.roleName})`; const canSeeTeam = session.isOwner || sessionHasPermission(session, "TEAM_VIEW"); $("team-link").style.display = canSeeTeam ? "inline-flex" : "none"; if (session.isOwner) { $("live-toggle-section").style.display = "block"; loadLiveStatus(); } loadInbox(); } function showGate() { $("gate-container").style.display = "block"; $("session-bar").style.display = "none"; $("live-toggle-section").style.display = "none"; $("inbox-section").style.display = "none"; } /* ---------------- Bewerbungen ---------------- */ async function loadInbox() { const session = getSession(); const includeArchived = $("show-archived").checked; const res = await apiCall(API_BASE_URL, "/admin/applications/list", { includeArchived }); if (!res.ok) { if (!getSession()) showGate(); return; } renderInbox(res.applications, session); } function renderInbox(applications, session) { const list = $("inbox-list"); if (!applications.length) { list.innerHTML = `

Keine Bewerbungen in dieser Ansicht.

`; return; } list.innerHTML = applications .map((a) => { const fields = a.data ? Object.entries(a.data).map(([k, v]) => `

${k}: ${v}

`).join("") : `

Keine Berechtigung, den vollen Inhalt zu öffnen.

`; const date = new Date(a.createdAt).toLocaleString("de-DE"); const actions = []; if (sessionHasPermission(session, "APPLICATIONS_TAKE_OVER")) { actions.push(``); } if (sessionHasPermission(session, "APPLICATIONS_MARK_ANSWERED")) { actions.push(``); } if (sessionHasPermission(session, "APPLICATIONS_ARCHIVE") && !a.archived) { actions.push(``); } if (sessionHasPermission(session, "APPLICATIONS_RESTORE") && a.archived) { actions.push(``); } if (sessionHasPermission(session, "APPLICATIONS_DELETE")) { actions.push(``); } if (sessionHasPermission(session, "NOTES_READ")) { actions.push(``); } const statusSelect = sessionHasPermission(session, "APPLICATIONS_CHANGE_STATUS") ? `` : `${a.status}`; return `
${a.type}${a.archived ? " · archiviert" : ""} ${statusSelect}

${date}

${fields}
${actions.join("")}

Lädt…

${ sessionHasPermission(session, "NOTES_WRITE") ? `
` : "" }
`; }) .join(""); list.querySelectorAll(".take-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/take-over", b.dataset.id))); list.querySelectorAll(".answered-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/mark-answered", b.dataset.id))); list.querySelectorAll(".archive-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/archive", b.dataset.id))); list.querySelectorAll(".restore-btn").forEach((b) => b.addEventListener("click", () => appAction("/admin/applications/restore", b.dataset.id))); list.querySelectorAll(".delete-btn").forEach((b) => b.addEventListener("click", () => { if (confirm("Diese Bewerbung wirklich endgültig löschen?")) appAction("/admin/applications/delete", b.dataset.id); }) ); list.querySelectorAll(".status-select").forEach((sel) => sel.addEventListener("change", () => appAction("/admin/applications/status", sel.dataset.id, { status: sel.value }, false)) ); list.querySelectorAll(".notes-toggle-btn").forEach((b) => b.addEventListener("click", () => { const box = $(`notes-${b.dataset.id}`); box.classList.toggle("open"); if (box.classList.contains("open")) loadNotes(b.dataset.id); }) ); list.querySelectorAll(".add-note-btn").forEach((b) => b.addEventListener("click", async () => { const textarea = document.querySelector(`.note-input[data-id="${b.dataset.id}"]`); const text = textarea.value.trim(); if (!text) return; const res = await apiCall(API_BASE_URL, "/admin/notes/add", { applicationId: b.dataset.id, text }); if (res.ok) { textarea.value = ""; loadNotes(b.dataset.id); } }) ); } async function appAction(path, id, extra, reload = true) { const res = await apiCall(API_BASE_URL, path, { id, ...(extra || {}) }); if (res.ok && reload) loadInbox(); else if (!res.ok) alert(res.error || "Fehler."); } async function loadNotes(applicationId) { const box = $(`notes-list-${applicationId}`); const res = await apiCall(API_BASE_URL, "/admin/notes/list", { applicationId }); if (!res.ok) { box.innerHTML = `

Konnte Notizen nicht laden.

`; return; } box.innerHTML = res.notes .map((n) => `
${n.author}: ${n.text}
${new Date(n.created_at).toLocaleString("de-DE")}
`) .join("") || `

Noch keine Notizen.

`; } /* ---------------- Live-Status ---------------- Der Status kommt automatisch von TikTok (Cron im Worker, jede Minute). Die Handschaltung ist nur ein zeitlich begrenzter Vorrang. ------------------------------------------------------------------- */ function formatSince(iso) { if (!iso) return null; const diffMin = Math.floor((Date.now() - new Date(iso).getTime()) / 60000); if (diffMin < 1) return "gerade eben"; if (diffMin === 1) return "vor 1 Minute"; if (diffMin < 60) return `vor ${diffMin} Minuten`; const h = Math.floor(diffMin / 60); return h === 1 ? "vor 1 Stunde" : `vor ${h} Stunden`; } function renderLiveStatus(data) { const live = !!(data && data.live); const dot = $("admin-live-dot"); dot.classList.remove("is-unknown"); dot.classList.toggle("is-live", live); $("admin-live-text").textContent = live ? "Jetzt live" : "Gerade offline"; const manual = data && data.source === "manuell"; $("go-auto-btn").style.display = manual ? "inline-flex" : "none"; const lines = []; if (manual) { lines.push("⚠️ Von Hand gesetzt — die Automatik ist gerade übersteuert."); if (data.overrideUntil) { const until = new Date(data.overrideUntil); lines.push(`Läuft aus um ${until.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit" })} Uhr.`); } if (data.autoLive !== null && data.autoLive !== undefined) { lines.push(`TikTok meldet gerade: ${data.autoLive ? "live" : "offline"}.`); } } else { if (data && data.title && live) lines.push(`Titel: ${data.title}`); const since = formatSince(data && data.autoCheckedAt); if (since) lines.push(`Automatisch geprüft ${since}.`); if (data && data.autoHealthy === false) { lines.push("⚠️ Der letzte Abruf bei TikTok hat nicht geklappt — angezeigt wird der letzte bekannte Stand."); } } $("admin-live-detail").innerHTML = lines.join("
"); } async function loadLiveStatus() { try { const res = await fetch(`${API_BASE_URL}/live-status`, { cache: "no-store" }); const data = await res.json(); if (res.ok && data.ok) renderLiveStatus(data); } catch { /* keine Blockade */ } } async function setLiveStatus(live) { const res = await apiCall(API_BASE_URL, "/live-status/set", { live }); if (res.ok) renderLiveStatus(res); } async function backToAutoLive() { const res = await apiCall(API_BASE_URL, "/live-status/set", { auto: true }); if (res.ok) renderLiveStatus(res); } /* ---------------- Login / Logout ---------------- */ document.addEventListener("DOMContentLoaded", () => { const existing = getSession(); if (existing) showApp(existing); else showGate(); $("code-gate").addEventListener("submit", async (e) => { e.preventDefault(); const status = $("gate-status"); status.className = "form-status show"; status.textContent = "Prüfe Code …"; const res = await doLogin(API_BASE_URL, $("code-input").value); if (res.ok) { status.className = "form-status"; showApp(getSession()); } else { status.textContent = res.error || "Falscher Code."; status.className = "form-status show error"; } }); $("logout-btn").addEventListener("click", async () => { await doLogout(API_BASE_URL); showGate(); }); $("refresh-btn").addEventListener("click", loadInbox); $("show-archived").addEventListener("change", loadInbox); $("go-live-btn").addEventListener("click", () => setLiveStatus(true)); $("go-offline-btn").addEventListener("click", () => setLiveStatus(false)); $("go-auto-btn").addEventListener("click", backToAutoLive); // Anzeige alle 30 Sekunden auffrischen, damit man die Automatik arbeiten sieht. setInterval(() => { if (!document.hidden && getSession()) loadLiveStatus(); }, 30000); });