/* ===================================================================== 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) { /* Ein leerer Bereich mit einem Satz ist eine verpasste Gelegenheit. Wer hier zum ersten Mal landet, hat gerade sein Passwort gesetzt und weiss noch nicht, was ihn erwartet. Statt "hier ist nichts" zeigt der leere Zustand deshalb, WAS gleich hier stehen wird -- und was als Naechstes passiert. Das nimmt die Unsicherheit, statt sie zu erzeugen. */ html += '
' + '" + "

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

" + '

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

" + /* ZEIGEN statt erzaehlen: eine echte, als Vorschau erkennbare Projektkarte mit laufender Phasenleiste. Man versteht in zwei Sekunden, was drei Saetze nicht erklaeren. */ '
' + '' + s(window.WD.t("po_vorschau")) + "" + 'P-0000-0000' + "

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

" + '" + '
' + '' + s(window.WD.t("po_wartet_dogfather")) + "" + "" + s(window.WD.t("po_st_design")) + " · 3/7" + "
" + '

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

" + "
" + '
' + [["po_leer_1", "po_leer_1t"], ["po_leer_2", "po_leer_2t"], ["po_leer_3", "po_leer_3t"], ["po_leer_4", "po_leer_4t"]] .map(function (paar, i) { return '
' + '' + (i + 1) + "" + "
" + s(window.WD.t(paar[0])) + "" + "" + s(window.WD.t(paar[1])) + "
"; }).join("") + "
" + '

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

" + "
"; } 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); })();