/* ===================================================================== wd-portal.js — Kundenportal, Oberfläche (Masterplan S.12) Vier Ansichten in einer Seite: Anmeldung, Passwort festlegen, Übersicht, Projektdetail. Wichtig zum Verständnis: Diese Datei stellt nur DAR. Sie entscheidet nichts darüber, wer was sehen darf — das macht ausschließlich der Server (routes/webdesign-portal.js), weil dieser Code hier im Browser des Kunden läuft und dort beliebig veränderbar ist. Was hier ankommt, ist bereits gefiltert. ===================================================================== */ (function () { "use strict"; var TOKEN = "wd-portal-token"; var token = null; var s = window.WD.sicher; var meldung = document.getElementById("po-meldung"); /* Sitzungsspeicher, nicht dauerhaft: derselbe Grundsatz wie überall in diesem Bereich — beim Schließen ist der Zugang weg. */ function tokenLesen() { try { return sessionStorage.getItem(TOKEN); } catch (e) { return null; } } function tokenSchreiben(t) { try { if (t) sessionStorage.setItem(TOKEN, t); else sessionStorage.removeItem(TOKEN); } catch (e) { /* egal */ } } function api(pfad, optionen) { var opt = optionen || {}; opt.headers = Object.assign({ Authorization: "Bearer " + token }, opt.headers || {}); return window.WD.api(pfad, opt); } function zeige(welche) { ["po-anmeldung", "po-setzen", "po-uebersicht", "po-detail"].forEach(function (id) { document.getElementById(id).hidden = id !== welche; }); } /* Sitzung abgelaufen: zurück zur Anmeldung mit klarer Ansage. Eine leere Seite würde aussehen wie "du hast keine Projekte" — eine stille Falschaussage. */ function behandleFehler(fehler) { if (fehler && fehler.status === 401) { token = null; tokenSchreiben(null); zeige("po-anmeldung"); window.WD.meldung(meldung, window.WD.t("po_abgelaufen"), "fehler"); return true; } return false; } /* ---------------- Anmeldung ---------------- */ document.getElementById("po-login-form").addEventListener("submit", async function (e) { e.preventDefault(); var feld = document.getElementById("po-feld-passwort"); var knopf = e.target.querySelector("button"); feld.classList.remove("wd-feld--fehler"); window.WD.meldungAus(meldung); var email = document.getElementById("po-email").value.trim(); var passwort = document.getElementById("po-passwort").value; if (!email || !passwort) { feld.classList.add("wd-feld--fehler"); feld.querySelector(".wd-feld-fehler").textContent = window.WD.t("po_login_leer"); return; } window.WD.knopfSperren(knopf, true, window.WD.t("po_anmelden_laeuft")); try { var antwort = await window.WD.api("/webdesign/portal/anmelden", { method: "POST", body: { email: email, passwort: passwort }, }); token = antwort.token; tokenSchreiben(token); /* Sprache des Kunden übernehmen, wenn er eine hinterlegt hat — so kommt er in seiner Sprache an, ohne umschalten zu müssen. */ if (antwort.kunde && antwort.kunde.sprache) window.WD.setSprache(antwort.kunde.sprache); ladeUebersicht(); } catch (fehler) { feld.classList.add("wd-feld--fehler"); feld.querySelector(".wd-feld-fehler").textContent = fehler.message || window.WD.t("po_fehler_login"); document.getElementById("po-passwort").select(); } finally { window.WD.knopfSperren(knopf, false); } }); document.getElementById("po-abmelden").addEventListener("click", async function () { try { await api("/webdesign/portal/abmelden", { method: "POST", body: {} }); } catch (e) { /* egal */ } token = null; tokenSchreiben(null); location.href = "/webdesign/portal.html"; }); /* ---------------- Passwort setzen (Einladungslink) ---------------- */ var einladungsToken = new URLSearchParams(location.search).get("einladung"); document.getElementById("po-setz-form").addEventListener("submit", async function (e) { e.preventDefault(); var feld = document.getElementById("po-feld-neu"); var knopf = e.target.querySelector("button"); feld.classList.remove("wd-feld--fehler"); var passwort = document.getElementById("po-neu").value; window.WD.knopfSperren(knopf, true, window.WD.t("po_anmelden_laeuft")); try { await window.WD.api("/webdesign/portal/passwort", { method: "POST", body: { token: einladungsToken, passwort: passwort }, }); zeige("po-anmeldung"); window.WD.meldung(meldung, window.WD.t("po_setz_fertig"), "ok"); /* Adresszeile säubern: der Einladungslink ist verbraucht und hat in der Chronik nichts mehr verloren. */ history.replaceState(null, "", "/webdesign/portal.html"); } catch (fehler) { feld.classList.add("wd-feld--fehler"); feld.querySelector(".wd-feld-fehler").textContent = fehler.message; } finally { window.WD.knopfSperren(knopf, false); } }); /* ---------------- Übersicht ---------------- */ var PHASEN = ["briefing", "angebot", "design", "entwicklung", "tests", "abnahme", "uebergeben"]; function statusText(st) { return window.WD.t("po_st_" + st); } function phasenLeiste(status) { var jetzt = PHASEN.indexOf(status); /* Pausiert oder abgebrochen: keine Leiste. Ein Fortschrittsbalken bei einem gestoppten Projekt wäre irreführend. */ if (jetzt < 0) return ""; return '" + '
' + s(statusText(status)) + " · " + (jetzt + 1) + "/" + PHASEN.length + "
"; } function wartetMarke(wartet) { if (wartet !== "kunde" && wartet !== "dogfather") return ""; return '' + s(window.WD.t(wartet === "kunde" ? "po_wartet_kunde" : "po_wartet_dogfather")) + ""; } async function ladeUebersicht() { zeige("po-uebersicht"); var ziel = document.getElementById("po-inhalt"); ziel.innerHTML = '
' + s(window.WD.t("laedt")) + "
"; try { var d = await api("/webdesign/portal/uebersicht"); document.getElementById("po-name").textContent = d.kunde.name || ""; window.WD.meldungAus(meldung); var html = ""; /* Offene Zahlungen ganz oben — das ist das Einzige, was den Kunden zum Handeln auffordert. Gold, weil es wirklich wichtig ist. */ if (d.offeneZahlungen.length) { html += '
' + '

' + s(window.WD.t("po_offene_zahlungen")) + "

" + d.offeneZahlungen.map(function (z) { return "

" + s(z.betrag_text) + " · " + s(z.zweck_text) + "

"; }).join("") + "
"; } html += "

" + s(window.WD.t("po_projekte")) + "

"; if (!d.projekte.length) { html += '

' + s(window.WD.t("po_keine_projekte")) + "

"; } else { html += d.projekte.map(function (p) { return '"; }).join(""); } if (d.abos.length) { html += '

' + s(window.WD.t("po_betreuung")) + "

" + '
"; } ziel.innerHTML = html; ziel.querySelectorAll("[data-projekt]").forEach(function (b) { b.addEventListener("click", function () { ladeProjekt(b.dataset.projekt); }); }); } catch (fehler) { if (behandleFehler(fehler)) return; ziel.innerHTML = ""; window.WD.meldung(meldung, fehler.message, "fehler"); } } /* ---------------- Projektdetail ---------------- */ var aktuellesProjekt = null; document.getElementById("po-zurueck").addEventListener("click", function () { aktuellesProjekt = null; ladeUebersicht(); }); async function ladeProjekt(id) { aktuellesProjekt = id; zeige("po-detail"); var ziel = document.getElementById("po-detail-inhalt"); ziel.innerHTML = '
' + s(window.WD.t("laedt")) + "
"; try { var d = await api("/webdesign/portal/projekt/" + encodeURIComponent(id)); var p = d.projekt; var html = ""; html += '
' + '' + s(p.nummer) + "" + "

" + s(p.titel) + "

" + wartetMarke(p.wartet_auf) + phasenLeiste(p.status) + (p.naechster_schritt ? '

' + s(window.WD.t("po_naechster_schritt")) + ": " + s(p.naechster_schritt) + "

" : "") + "
"; /* --- Zahlungen --- */ html += '

' + s(window.WD.t("po_zahlungen")) + "

"; if (!d.zahlungen.length) { html += '

' + s(window.WD.t("po_keine_zahlungen")) + "

"; } else { html += '"; } html += "
"; /* --- Dateien --- */ html += '

' + s(window.WD.t("po_dateien")) + "

"; if (!d.dateien.length) { html += '

' + s(window.WD.t("po_keine_dateien")) + "

"; } else { html += '"; } html += "
"; /* --- Ideen & Änderungswünsche --- */ html += '

' + s(window.WD.t("po_ideen")) + "

" + '

' + s(window.WD.t("po_ideen_text")) + "

"; if (d.aenderungen.length) { html += d.aenderungen.map(function (a) { var entscheidbar = a.status === "angeboten"; return '
' + '

' + s(a.titel) + "

" + '' + s(window.WD.t("po_ae_" + a.status)) + "
" + '

' + s(a.beschreibung) + "

" + (a.aufwand_text ? '

' + s(a.aufwand_text) + "

" : "") + (a.zusatzpreis_text ? '

' + s(window.WD.t("po_zusatzpreis")) + ": " + s(a.zusatzpreis_text) + "" + (a.zusatzdauer_text ? " · " + s(window.WD.t("po_zusatzdauer")) + ": " + s(a.zusatzdauer_text) : "") + "

" : "") + (entscheidbar ? '
' + '" + '
" : "") + "
"; }).join(""); } else { html += '

' + s(window.WD.t("po_keine_ideen")) + "

"; } html += '
' + "" + '' + "
" + '
' + "" + '' + '

' + '
"; /* --- Nachrichten --- */ html += '

' + s(window.WD.t("po_nachrichten")) + "

"; if (!d.nachrichten.length) { html += '

' + s(window.WD.t("po_keine_nachrichten")) + "

"; } else { html += d.nachrichten.map(function (n) { var vonMir = n.autor === "kunde"; return '
' + '
' + (vonMir ? "" : "Dogfather · ") + window.WD.datum(n.erstellt_am) + "

" + s(n.text) + "

"; }).join(""); } html += '
' + '
' + '
"; ziel.innerHTML = html; /* --- Verknüpfungen --- */ ziel.querySelectorAll("[data-ja], [data-nein]").forEach(function (b) { b.addEventListener("click", async function () { var annehmen = !!b.dataset.ja; var aeId = b.dataset.ja || b.dataset.nein; /* Nur beim Annehmen nachfragen — das erzeugt eine Zahlungspflicht. Ablehnen ist folgenlos und braucht keine Hürde. */ if (annehmen && !confirm(window.WD.t("po_annehmen_frage"))) return; window.WD.knopfSperren(b, true, "…"); try { await api("/webdesign/portal/aenderung/" + encodeURIComponent(aeId) + "/entscheiden", { method: "POST", body: { annehmen: annehmen } }); ladeProjekt(id); } catch (f) { if (behandleFehler(f)) return; window.WD.knopfSperren(b, false); window.WD.meldung(meldung, f.message, "fehler"); } }); }); ziel.querySelector("#po-idee-senden").addEventListener("click", async function (e) { var titel = ziel.querySelector("#po-idee-titel").value.trim(); var beschreibung = ziel.querySelector("#po-idee-text").value.trim(); var feld = ziel.querySelector("#po-feld-idee"); feld.classList.remove("wd-feld--fehler"); if (!titel || !beschreibung) { feld.classList.add("wd-feld--fehler"); feld.querySelector(".wd-feld-fehler").textContent = window.WD.t("po_idee_leer"); return; } window.WD.knopfSperren(e.target, true, "…"); try { await api("/webdesign/portal/aenderung", { method: "POST", body: { projektId: id, titel: titel, beschreibung: beschreibung } }); ladeProjekt(id); } catch (f) { if (behandleFehler(f)) return; window.WD.knopfSperren(e.target, false); window.WD.meldung(meldung, f.message, "fehler"); } }); ziel.querySelector("#po-nachricht-senden").addEventListener("click", async function (e) { var feld = ziel.querySelector("#po-nachricht-text"); var inhalt = feld.value.trim(); if (!inhalt) { feld.focus(); return; } window.WD.knopfSperren(e.target, true, "…"); try { await api("/webdesign/portal/nachricht", { method: "POST", body: { projektId: id, text: inhalt } }); ladeProjekt(id); } catch (f) { if (behandleFehler(f)) return; window.WD.knopfSperren(e.target, false); window.WD.meldung(meldung, f.message, "fehler"); } }); /* Zahlungsknöpfe: die eigentliche PayPal-Anbindung folgt. Bis dahin ein ehrlicher Hinweis statt eines Knopfes, der nichts tut. */ ziel.querySelectorAll("[data-zahlung]").forEach(function (b) { b.addEventListener("click", function () { window.WD.meldung(meldung, "Die Zahlung über PayPal wird gerade eingerichtet. Bitte melde dich kurz bei Dogfather.", "fehler"); }); }); } catch (fehler) { if (behandleFehler(fehler)) return; ziel.innerHTML = ""; window.WD.meldung(meldung, fehler.message, "fehler"); } } /* ---------------- Start ---------------- */ async function start() { /* Einladungslink hat Vorrang: wer gerade sein Passwort setzen soll, will nicht zuerst eine Anmeldemaske sehen. */ if (einladungsToken) { try { var info = await window.WD.api("/webdesign/portal/einladung?token=" + encodeURIComponent(einladungsToken)); document.getElementById("po-setz-fuer").textContent = info.email || ""; zeige("po-setzen"); return; } catch (fehler) { zeige("po-anmeldung"); window.WD.meldung(meldung, fehler.message, "fehler"); return; } } token = tokenLesen(); if (!token) { zeige("po-anmeldung"); return; } ladeUebersicht(); } /* Nach einem Sprachwechsel die per JS gebauten Teile neu zeichnen. */ document.addEventListener("wd-sprache-geaendert", function () { if (!token) return; if (aktuellesProjekt) ladeProjekt(aktuellesProjekt); else if (!document.getElementById("po-uebersicht").hidden) ladeUebersicht(); }); if (window.WD) start(); else document.addEventListener("wd-bereit", start); })();