217 lines
8.9 KiB
JavaScript
217 lines
8.9 KiB
JavaScript
/* =====================================================================
|
|
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 = `<p class="muted">Keine Bewerbungen in dieser Ansicht.</p>`;
|
|
return;
|
|
}
|
|
|
|
list.innerHTML = applications
|
|
.map((a) => {
|
|
const fields = a.data
|
|
? Object.entries(a.data).map(([k, v]) => `<p class="small"><strong>${k}:</strong> ${v}</p>`).join("")
|
|
: `<p class="small muted">Keine Berechtigung, den vollen Inhalt zu öffnen.</p>`;
|
|
const date = new Date(a.createdAt).toLocaleString("de-DE");
|
|
|
|
const actions = [];
|
|
if (sessionHasPermission(session, "APPLICATIONS_TAKE_OVER")) {
|
|
actions.push(`<button class="btn btn-outline take-btn" data-id="${a.id}">Übernehmen</button>`);
|
|
}
|
|
if (sessionHasPermission(session, "APPLICATIONS_MARK_ANSWERED")) {
|
|
actions.push(`<button class="btn btn-outline answered-btn" data-id="${a.id}">Als beantwortet markieren</button>`);
|
|
}
|
|
if (sessionHasPermission(session, "APPLICATIONS_ARCHIVE") && !a.archived) {
|
|
actions.push(`<button class="btn btn-outline archive-btn" data-id="${a.id}">Archivieren</button>`);
|
|
}
|
|
if (sessionHasPermission(session, "APPLICATIONS_RESTORE") && a.archived) {
|
|
actions.push(`<button class="btn btn-outline restore-btn" data-id="${a.id}">Wiederherstellen</button>`);
|
|
}
|
|
if (sessionHasPermission(session, "APPLICATIONS_DELETE")) {
|
|
actions.push(`<button class="btn btn-outline delete-btn" data-id="${a.id}" style="color:var(--accent);">Löschen</button>`);
|
|
}
|
|
if (sessionHasPermission(session, "NOTES_READ")) {
|
|
actions.push(`<button class="btn btn-outline notes-toggle-btn" data-id="${a.id}">Notizen</button>`);
|
|
}
|
|
|
|
const statusSelect = sessionHasPermission(session, "APPLICATIONS_CHANGE_STATUS")
|
|
? `<select class="status-select" data-id="${a.id}">
|
|
${STATUS_OPTIONS.map((s) => `<option value="${s}" ${s === a.status ? "selected" : ""}>${s}</option>`).join("")}
|
|
</select>`
|
|
: `<span class="tag">${a.status}</span>`;
|
|
|
|
return `
|
|
<div class="card app-card">
|
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
|
<span class="tag">${a.type}${a.archived ? " · archiviert" : ""}</span>
|
|
${statusSelect}
|
|
</div>
|
|
<p class="small muted" style="margin:0;">${date}</p>
|
|
<div class="fields">${fields}</div>
|
|
<div class="actions">${actions.join("")}</div>
|
|
<div class="notes-box" id="notes-${a.id}">
|
|
<div class="notes-list" id="notes-list-${a.id}"><p class="small muted">Lädt…</p></div>
|
|
${
|
|
sessionHasPermission(session, "NOTES_WRITE")
|
|
? `<div class="field" style="margin-top:.6rem;">
|
|
<textarea class="note-input" data-id="${a.id}" placeholder="Neue Notiz…" style="min-height:60px;"></textarea>
|
|
<button class="btn btn-outline add-note-btn" data-id="${a.id}" style="margin-top:.4rem;">Notiz speichern</button>
|
|
</div>`
|
|
: ""
|
|
}
|
|
</div>
|
|
</div>`;
|
|
})
|
|
.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 = `<p class="small muted">Konnte Notizen nicht laden.</p>`;
|
|
return;
|
|
}
|
|
box.innerHTML =
|
|
res.notes
|
|
.map((n) => `<div class="note-item"><strong>${n.author}</strong>: ${n.text}<br><span class="small muted">${new Date(n.created_at).toLocaleString("de-DE")}</span></div>`)
|
|
.join("") || `<p class="small muted">Noch keine Notizen.</p>`;
|
|
}
|
|
|
|
/* ---------------- Live-Status ---------------- */
|
|
function renderLiveStatus(live) {
|
|
$("admin-live-dot").classList.toggle("is-live", !!live);
|
|
$("admin-live-text").textContent = live ? "Live jetzt!" : "Aktuell offline";
|
|
}
|
|
|
|
async function loadLiveStatus() {
|
|
try {
|
|
const res = await fetch(`${API_BASE_URL}/live-status`);
|
|
const data = await res.json();
|
|
if (res.ok && data.ok) renderLiveStatus(data.live);
|
|
} catch {
|
|
/* keine Blockade */
|
|
}
|
|
}
|
|
|
|
async function setLiveStatus(live) {
|
|
const res = await apiCall(API_BASE_URL, "/live-status/set", { live });
|
|
if (res.ok) renderLiveStatus(res.live);
|
|
}
|
|
|
|
/* ---------------- 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));
|
|
});
|