Schritt 4 von 4 -- damit sind beide Wuensche vom 22.08.2026 vollstaendig:
"wenn sie drauf druecken sollen sie auch sehen was ich schon gemacht habe
von dem was im plan war" und "die kunden sollen mir auch nachrichten
hinterlassen koennen".
AUFGABENLISTE IM PROJEKT
Bisher sah der Kunde nur die Phase ("Design . 3/7") -- eine Zahl ohne
Inhalt. Sie beantwortet die eigentliche Frage nicht: WAS ist denn fertig?
Die Liste steht GANZ OBEN, direkt nach dem Projektkopf, noch vor
Zahlungen und Dateien. Die sind wichtig, aber sie sind nicht der Grund
fuer den Klick.
Zwei Entscheidungen praegen die Ansicht:
1. Offene Kundenpunkte stehen in einem EIGENEN Kasten ganz oben ("Das
brauche ich noch von dir"), nicht nur farblich markiert irgendwo
mittendrin. Wer eine lange Liste sieht, liest sie als Bericht ueber
fremde Arbeit und ueberliest seinen eigenen Teil -- genau daraus
entstehen die meisten Verzoegerungen. Ist nichts offen, steht das
auch da: "Von dir wird gerade nichts gebraucht." Eine gute Nachricht
darf ausgesprochen werden.
2. Der Kunde hakt seine eigenen Punkte selbst ab. Nur diese haben einen
Knopf; bei fremden Punkten ist das Zeichen reine Anzeige. Ein Knopf,
der nichts tut, laesst die Seite kaputt wirken. Geprueft: 4 Knoepfe
bei 2 offenen eigenen Punkten (sie erscheinen zweimal), 5 feste.
Grosse Zahl statt Prozent: "2 von 6 erledigt" ist greifbar, "33 %" ist
eine Rechnung, die niemand fuehlt. Der Balken traegt die vier
Prozessfarben -- dieselben wie auf der Ablaufseite und in der Verwaltung.
Der Ton ist bewusst gewaehlt. Der Kunde liest die Liste, wenn er unsicher
ist -- also im Zweifel schon angespannt. "Wir warten auf dich" waere
Druck, "Das brauche ich noch von dir" ist eine Bitte.
POSTFACH AUF DER UEBERSICHT
Bewusst auf der Uebersicht, nicht im Projekt: Wer kein Projekt hat -- oder
eine Frage, die zu keinem gehoert -- konnte vorher gar nicht schreiben und
musste zur E-Mail greifen. Damit war der Verlauf weg, sobald man ihn
brauchte.
Niedrigschwellig: kein Betreff, kein Pflichtfeld, der Projektbezug ist
freiwillig. Wer glaubt, eine Nachricht muesse eine "richtige" Anfrage
sein, schreibt gar nicht erst -- und genau die kurzen Fragen sollen hier
landen. Wird nachgeladen, damit die Uebersicht sofort dasteht.
Projekt- und Aufgabendaten werden gleichzeitig angefordert statt
nacheinander -- sonst waere die Wartezeit die Summe beider Anfragen. Die
Aufgabenliste darf dabei fehlschlagen, ohne die Seite mitzureissen: Wer
wegen einer leeren Liste seine Zahlungen nicht mehr saehe, waere
schlechter dran als vorher.
EIN SICHTBARER FEHLER GEFUNDEN
Auf dem Screenshot stand "Ideen & Aenderungswuensche". Ursache ist
eine Falle mit zwei Wegen: Texte ueber data-i18n landen als HTML in der
Seite, dort ist "&" richtig. Derselbe Text im JavaScript laeuft aber
durch die Absicherung und wird ein zweites Mal kodiert. Derselbe Baustein
ist also je nach Verwendungsort richtig oder falsch.
Das kann man nicht im Kopf behalten -- deshalb neu pruef-texte.mjs mit
ZWEI Pruefungen:
Anzeige: alle 11 Seiten x 5 Sprachen = 55 Kombinationen, sichtbarer
Text darf keine Kodierungsreste enthalten.
Quelltext: welcher Baustein mit "&" wird irgendwo abgesichert
eingesetzt?
Beide sind noetig. Gegengeprueft: Die Anzeigepruefung allein haette den
Fehler NICHT gefunden, weil die Ideen-Karte erst nach einer Anmeldung
erscheint. Die Quelltextpruefung findet ihn ohne Anzeige.
Auch die Quelltextpruefung selbst wurde zweimal gegengeprueft: Zuerst
meldete sie zusaetzlich po_senden ("Senden", voellig harmlos) -- ihre
Blockerkennung lief bis zum naechsten Schluessel und schluckte dabei
einen Kommentar mit "&". Jetzt zaehlt sie geschweifte Klammern. Ein
Fehlalarm im Pruefwerkzeug ist fast so schaedlich wie ein uebersehener
Fehler: Man gewoehnt sich daran, ihn wegzusehen.
GEPRUEFT
32 Pruefungen im Portal (Computer und Handy), alle bestanden. Darunter:
114 Texte in allen fuenf Sprachen vollstaendig, Kundenpunkte stehen vor
der langen Liste, Aufgaben vor den Zahlungen, Abhaken meldet den
richtigen Punkt, alle Kaestchen mindestens 24 px, keine Fehler im
Protokoll. Dazu 55 Seiten-Sprach-Kombinationen ohne Kodierungsreste.
Ein Fehler im Test selbst gefunden und behoben: Die Adressabfrage prueft
jetzt den PFAD statt der ganzen Adresse. Der API-Server heisst
"postfach.dogfather-universe.com" -- ein includes("/postfach") traf den
HOSTNAMEN und damit jede Anfrage, auch die Uebersicht.
Versionsstempel und Cache-Name auf v5.
Co-Authored-By: Claude Opus 5 <[email protected]>
783 lines
36 KiB
JavaScript
783 lines
36 KiB
JavaScript
/* =====================================================================
|
||
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 '<div class="po-phasen" aria-hidden="true">' +
|
||
PHASEN.map(function (p, i) {
|
||
return '<span class="po-phase' + (i < jetzt ? " po-erledigt" : i === jetzt ? " po-jetzt" : "") + '"></span>';
|
||
}).join("") + "</div>" +
|
||
'<div class="po-phasen-text">' + s(statusText(status)) + " · " +
|
||
(jetzt + 1) + "/" + PHASEN.length + "</div>";
|
||
}
|
||
|
||
function wartetMarke(wartet) {
|
||
if (wartet !== "kunde" && wartet !== "dogfather") return "";
|
||
return '<span class="po-wartet po-wartet--' + wartet + '">' +
|
||
s(window.WD.t(wartet === "kunde" ? "po_wartet_kunde" : "po_wartet_dogfather")) + "</span>";
|
||
}
|
||
|
||
/* ---------------- Aufgabenliste (22.08.2026) ----------------
|
||
"wenn sie drauf drücken sollen sie auch sehen was ich schon gemacht
|
||
habe von dem was im plan war."
|
||
|
||
Bis hierher sah der Kunde nur die Phase ("Design · 3/7") — eine Zahl
|
||
ohne Inhalt. Sie beantwortet die eigentliche Frage nicht: WAS ist
|
||
denn schon fertig?
|
||
|
||
Zwei Entscheidungen prägen diese Ansicht:
|
||
|
||
1. Punkte, die auf den KUNDEN warten, stehen ganz oben in einem
|
||
eigenen Kasten — nicht nur farblich markiert irgendwo mittendrin.
|
||
Wer eine lange Liste sieht, liest sie als Bericht über MEINE
|
||
Arbeit und übersieht seinen eigenen Teil. Genau daraus entstehen
|
||
die meisten Verzögerungen.
|
||
|
||
2. Der Kunde kann seine eigenen Punkte selbst abhaken. Das ist keine
|
||
Spielerei: Es gibt ihm einen sichtbaren Anteil am Fortschritt,
|
||
und ich sehe sofort, dass die Texte da sind, ohne dass er extra
|
||
schreiben muss.
|
||
---------------------------------------------------------------- */
|
||
var KATS = ["start", "gestaltung", "umsetzung", "abschluss"];
|
||
var KAT_KEY = { start: "po_k1", gestaltung: "po_k2", umsetzung: "po_k3", abschluss: "po_k4" };
|
||
|
||
function aufgabeZeile(a, hervor) {
|
||
var klasse = "po-aufg";
|
||
if (a.status === "erledigt") klasse += " po-aufg--fertig";
|
||
else if (a.status === "in_arbeit") klasse += " po-aufg--jetzt";
|
||
else if (a.status === "entfaellt") klasse += " po-aufg--weg";
|
||
var duBistDran = a.wer_dran === "kunde" && a.status !== "erledigt" && a.status !== "entfaellt";
|
||
if (duBistDran) klasse += " po-aufg--du";
|
||
if (hervor) klasse += " po-aufg--hervor";
|
||
|
||
/* Nur EIGENE offene Punkte bekommen einen Knopf. Bei allen anderen
|
||
ist das Zeichen reine Anzeige — sonst klickt der Kunde darauf,
|
||
nichts passiert, und er hält die Seite für kaputt. */
|
||
var eigen = a.wer_dran === "kunde" && !a.nicht_enthalten && a.status !== "entfaellt";
|
||
var zeichen = a.status === "erledigt" ? "✓" : a.status === "in_arbeit" ? "•" : a.status === "entfaellt" ? "–" : "";
|
||
|
||
var kaestchen = eigen
|
||
? '<button type="button" class="po-aufg-haken" data-haken="' + s(a.id) + '" title="' +
|
||
s(window.WD.t(a.status === "erledigt" ? "po_aufg_zurueck" : "po_aufg_hake")) + '" aria-label="' +
|
||
s(window.WD.t(a.status === "erledigt" ? "po_aufg_zurueck" : "po_aufg_hake")) + ": " + s(a.titel) + '">' +
|
||
zeichen + "</button>"
|
||
: '<span class="po-aufg-haken po-aufg-haken--fest" aria-hidden="true">' + zeichen + "</span>";
|
||
|
||
var marke = a.status === "erledigt"
|
||
? '<span class="po-aufg-marke po-aufg-marke--ok">' + s(window.WD.t("po_aufg_erledigt_marke")) + "</span>"
|
||
: a.status === "in_arbeit"
|
||
? '<span class="po-aufg-marke po-aufg-marke--jetzt">' + s(window.WD.t("po_aufg_arbeit_marke")) + "</span>"
|
||
: duBistDran
|
||
? '<span class="po-aufg-marke po-aufg-marke--du">' + s(window.WD.t("po_aufg_du_marke")) + "</span>"
|
||
: "";
|
||
|
||
return '<div class="' + klasse + '">' + kaestchen +
|
||
'<span class="po-aufg-titel">' + s(a.titel) +
|
||
(a.nicht_enthalten ? '<span class="po-aufg-nicht">' + s(window.WD.t("po_aufg_nicht")) + "</span>" : "") +
|
||
"</span>" +
|
||
'<span class="po-aufg-marken">' + marke + "</span>" +
|
||
(a.beschreibung || a.hinweis
|
||
? '<span class="po-aufg-text">' + s(a.beschreibung || "") +
|
||
(a.hinweis ? " " + s(a.hinweis) : "") + "</span>"
|
||
: "") +
|
||
"</div>";
|
||
}
|
||
|
||
function aufgabenKarte(d) {
|
||
var f = d.fortschritt || { gesamt: 0, erledigt: 0, beimKunden: 0, jeKategorie: {} };
|
||
var alle = d.aufgaben || [];
|
||
|
||
var html = '<div class="wd-karte" style="margin-bottom:1.2rem"><h3>' +
|
||
s(window.WD.t("po_aufgaben")) + "</h3>";
|
||
|
||
if (!alle.length) {
|
||
return html + '<p class="wd-leise wd-klein">' + s(window.WD.t("po_aufg_leer")) + "</p></div>";
|
||
}
|
||
|
||
html += '<p class="wd-klein wd-leise" style="margin-top:-.3rem">' +
|
||
s(window.WD.t("po_aufg_text")) + "</p>";
|
||
|
||
/* Grosse Zahl statt Prozent. "9 von 14 erledigt" ist greifbar,
|
||
"64 %" ist eine Rechnung, die niemand fuehlt. */
|
||
html += '<div class="po-aufg-stand"><b>' + f.erledigt + "</b> " +
|
||
s(window.WD.t("po_aufg_fertig").replace("{gesamt}", f.gesamt)) + "</div>";
|
||
|
||
/* Der Balken in den vier Prozessfarben — dieselben wie auf der
|
||
Ablaufseite und in der Verwaltung. */
|
||
html += '<div class="po-fortschritt">' + KATS.map(function (k) {
|
||
var z = (f.jeKategorie && f.jeKategorie[k]) || { gesamt: 0, erledigt: 0 };
|
||
var breite = z.gesamt ? Math.round((z.erledigt / z.gesamt) * 100) : 0;
|
||
return '<span class="po-fs-teil" data-k="' + k + '" title="' +
|
||
s(window.WD.t(KAT_KEY[k])) + ": " + z.erledigt + "/" + z.gesamt +
|
||
'"><i style="width:' + breite + '%"></i></span>';
|
||
}).join("") + "</div>";
|
||
|
||
/* --- Was auf den Kunden wartet, ZUERST und in einem eigenen Kasten. --- */
|
||
var meine = alle.filter(function (a) {
|
||
return a.wer_dran === "kunde" && a.status !== "erledigt" && a.status !== "entfaellt" && !a.nicht_enthalten;
|
||
});
|
||
|
||
if (meine.length) {
|
||
html += '<div class="po-dubist"><h4>' +
|
||
s(window.WD.t(meine.length === 1 ? "po_aufg_dubist_eins"
|
||
: "po_aufg_dubist_viele").replace("{n}", meine.length)) + "</h4>" +
|
||
'<p class="wd-klein wd-leise" style="margin:.1rem 0 .7rem">' +
|
||
s(window.WD.t("po_aufg_dubist")) + "</p>" +
|
||
meine.map(function (a) { return aufgabeZeile(a, true); }).join("") +
|
||
"</div>";
|
||
} else {
|
||
html += '<p class="po-allesgut">' + s(window.WD.t("po_aufg_allesbei")) + "</p>";
|
||
}
|
||
|
||
/* --- Danach die vollstaendige Liste nach Abschnitten. --- */
|
||
html += KATS.map(function (k) {
|
||
var drin = alle.filter(function (a) { return a.kategorie === k; });
|
||
if (!drin.length) return "";
|
||
var z = (f.jeKategorie && f.jeKategorie[k]) || { gesamt: 0, erledigt: 0 };
|
||
return '<div class="po-aufg-gruppe" data-k="' + k + '">' +
|
||
'<div class="po-aufg-kopf">' + s(window.WD.t(KAT_KEY[k])) +
|
||
'<span class="po-aufg-zahl">' + z.erledigt + " / " + z.gesamt + "</span></div>" +
|
||
drin.map(function (a) { return aufgabeZeile(a, false); }).join("") +
|
||
"</div>";
|
||
}).join("");
|
||
|
||
return html + "</div>";
|
||
}
|
||
|
||
async function ladeUebersicht() {
|
||
zeige("po-uebersicht");
|
||
var ziel = document.getElementById("po-inhalt");
|
||
ziel.innerHTML = '<div class="po-laedt">' + s(window.WD.t("laedt")) + "</div>";
|
||
|
||
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 += '<div class="wd-hinweis" style="margin-bottom:1.2rem">' +
|
||
'<p class="wd-hinweis-titel">' + s(window.WD.t("po_offene_zahlungen")) + "</p>" +
|
||
d.offeneZahlungen.map(function (z) {
|
||
return "<p><strong>" + s(z.betrag_text) + "</strong> · " + s(z.zweck_text) + "</p>";
|
||
}).join("") + "</div>";
|
||
}
|
||
|
||
html += "<h2>" + s(window.WD.t("po_projekte")) + "</h2>";
|
||
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 += '<div class="wd-karte po-leer">' +
|
||
'<div class="po-leer-symbol" aria-hidden="true">' +
|
||
'<img src="/assets/img/logo-dogfather-transparent.png" alt="" width="472" height="476" />' +
|
||
"</div>" +
|
||
"<h3>" + s(window.WD.t("po_leer_h3")) + "</h3>" +
|
||
'<p class="wd-leise">' + s(window.WD.t("po_keine_projekte")) + "</p>" +
|
||
/* ZEIGEN statt erzaehlen: eine echte, als Vorschau erkennbare
|
||
Projektkarte mit laufender Phasenleiste. Man versteht in zwei
|
||
Sekunden, was drei Saetze nicht erklaeren. */
|
||
'<div class="po-vorschau">' +
|
||
'<span class="po-vorschau-etikett">' + s(window.WD.t("po_vorschau")) + "</span>" +
|
||
'<span class="po-vorschau-nr">P-0000-0000</span>' +
|
||
"<h4>" + s(window.WD.t("po_vorschau_titel")) + "</h4>" +
|
||
'<div class="po-vorschau-phasen" aria-hidden="true">' +
|
||
"<span></span><span></span><span></span><span></span><span></span><span></span><span></span>" +
|
||
"</div>" +
|
||
'<div class="po-vorschau-zeile">' +
|
||
'<span class="po-vorschau-marke">' + s(window.WD.t("po_wartet_dogfather")) + "</span>" +
|
||
"<span>" + s(window.WD.t("po_st_design")) + " · 3/7</span>" +
|
||
"</div>" +
|
||
'<p class="po-vorschau-schritt"><strong>' + s(window.WD.t("po_naechster_schritt")) +
|
||
":</strong> " + s(window.WD.t("po_vorschau_schritt")) + "</p>" +
|
||
/* Legende: Farbe allein ist nie Information. Wer sie nicht
|
||
unterscheiden kann, liest hier trotzdem, was sie bedeutet. */
|
||
/* Vier Abschnitte statt drei Zustaende: Ob eine Phase
|
||
erledigt ist, sieht man an der Position. Wertvoller ist,
|
||
WAS gerade passiert. */
|
||
'<div class="po-legende">' +
|
||
'<span><i class="po-l-1"></i>' + s(window.WD.t("po_k1")) + "</span>" +
|
||
'<span><i class="po-l-2"></i>' + s(window.WD.t("po_k2")) + "</span>" +
|
||
'<span><i class="po-l-3"></i>' + s(window.WD.t("po_k3")) + "</span>" +
|
||
'<span><i class="po-l-4"></i>' + s(window.WD.t("po_k4")) + "</span>" +
|
||
"</div>" +
|
||
"</div>" +
|
||
|
||
'<div class="po-leer-liste">' +
|
||
[["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 '<div class="po-leer-punkt">' +
|
||
'<span class="po-leer-nr">' + (i + 1) + "</span>" +
|
||
"<div><strong>" + s(window.WD.t(paar[0])) + "</strong>" +
|
||
"<span>" + s(window.WD.t(paar[1])) + "</span></div></div>";
|
||
}).join("") +
|
||
"</div>" +
|
||
'<p class="wd-klein wd-leise po-leer-fuss">' + s(window.WD.t("po_leer_fuss")) + "</p>" +
|
||
"</div>";
|
||
} else {
|
||
html += d.projekte.map(function (p) {
|
||
return '<button type="button" class="wd-karte po-projekt" data-projekt="' + s(p.id) + '">' +
|
||
'<span class="po-projekt-kopf">' +
|
||
'<span class="po-projekt-nr">' + s(p.nummer) + "</span>" +
|
||
"<h3>" + s(p.titel) + "</h3>" +
|
||
wartetMarke(p.wartet_auf) +
|
||
"</span>" +
|
||
phasenLeiste(p.status) +
|
||
(p.naechster_schritt
|
||
? '<p class="wd-klein" style="margin:.7rem 0 .3rem"><strong>' +
|
||
s(window.WD.t("po_naechster_schritt")) + ":</strong> " + s(p.naechster_schritt) + "</p>"
|
||
: "") +
|
||
(p.richttermin
|
||
? '<p class="wd-klein wd-leise" style="margin:0">' + s(window.WD.t("po_richttermin")) +
|
||
": " + window.WD.datum(p.richttermin) + "</p>"
|
||
: "") +
|
||
(p.offene_aenderungen || p.ungelesene_nachrichten
|
||
? '<p class="wd-klein" style="margin:.5rem 0 0;color:var(--wd-blau)">' +
|
||
(p.ungelesene_nachrichten ? p.ungelesene_nachrichten + " " + s(window.WD.t("po_nachrichten")) : "") +
|
||
(p.ungelesene_nachrichten && p.offene_aenderungen ? " · " : "") +
|
||
(p.offene_aenderungen ? p.offene_aenderungen + " " + s(window.WD.t("po_ideen")) : "") +
|
||
"</p>"
|
||
: "") +
|
||
"</button>";
|
||
}).join("");
|
||
}
|
||
|
||
if (d.abos.length) {
|
||
html += '<h2 style="margin-top:1.8rem">' + s(window.WD.t("po_betreuung")) + "</h2>" +
|
||
'<div class="wd-karte"><ul class="po-liste">' +
|
||
d.abos.map(function (a) {
|
||
return '<li class="po-eintrag"><span class="po-eintrag-haupt">' +
|
||
'<span class="po-eintrag-name">' + s(a.paket) + "</span>" +
|
||
'<span class="po-eintrag-neben">' + s(a.preis_text) +
|
||
(a.naechste_zahlung ? " · " + s(window.WD.t("po_naechste_zahlung")) + ": " + window.WD.datum(a.naechste_zahlung) : "") +
|
||
"</span></span></li>";
|
||
}).join("") + "</ul></div>";
|
||
}
|
||
|
||
/* Der allgemeine Nachrichtenbereich steht auf der ÜBERSICHT, nicht
|
||
im Projekt. Genau das war der Punkt: Wer kein Projekt hat — oder
|
||
eine Frage, die zu keinem gehört — konnte vorher gar nicht
|
||
schreiben und musste zur E-Mail greifen. Damit war der Verlauf
|
||
weg, sobald man ihn brauchte.
|
||
|
||
Er wird nachgeladen, nicht mitgeliefert: Die Übersicht soll
|
||
sofort dastehen. Ein paar Zehntelsekunden später erscheinen die
|
||
Nachrichten — das ist besser, als die ganze Seite auf sie warten
|
||
zu lassen. */
|
||
html += '<div id="po-postfach" style="margin-top:1.8rem"></div>';
|
||
|
||
ziel.innerHTML = html;
|
||
ziel.querySelectorAll("[data-projekt]").forEach(function (b) {
|
||
b.addEventListener("click", function () { ladeProjekt(b.dataset.projekt); });
|
||
});
|
||
ladePostfach();
|
||
} catch (fehler) {
|
||
if (behandleFehler(fehler)) return;
|
||
ziel.innerHTML = "";
|
||
window.WD.meldung(meldung, fehler.message, "fehler");
|
||
}
|
||
}
|
||
|
||
/* ---------------- Projektdetail ---------------- */
|
||
/* ---------------- Allgemeines Postfach ----------------
|
||
"auf dieser seite sollen die kunden mir auch nachrichten hinterlassen
|
||
können, für noch kleinigkeiten oder sachen die ich brauch."
|
||
|
||
Bewusst niedrigschwellig: kein Betreff, kein Pflichtfeld, der Bezug
|
||
zu einem Projekt ist freiwillig. Wer glaubt, eine Nachricht müsse
|
||
eine "richtige" Anfrage sein, schreibt gar nicht erst — und genau
|
||
die kurzen Fragen sollen hier landen.
|
||
---------------------------------------------------------------- */
|
||
async function ladePostfach() {
|
||
var kasten = document.getElementById("po-postfach");
|
||
if (!kasten) return;
|
||
|
||
var d;
|
||
try {
|
||
d = await api("/webdesign/portal/postfach");
|
||
} catch (fehler) {
|
||
/* Still bleiben. Der Bereich ist eine Ergänzung — eine rote
|
||
Fehlermeldung über der Projektübersicht wäre alarmierender, als
|
||
der Anlass es hergibt. */
|
||
return;
|
||
}
|
||
|
||
var html = "<h2>" + s(window.WD.t("po_postfach")) + "</h2>" +
|
||
'<div class="wd-karte">' +
|
||
'<p class="wd-klein wd-leise" style="margin-top:0">' + s(window.WD.t("po_postfach_text")) + "</p>";
|
||
|
||
if (!d.nachrichten.length) {
|
||
html += '<p class="wd-leise wd-klein">' + s(window.WD.t("po_postfach_leer")) + "</p>";
|
||
} else {
|
||
html += '<div class="po-pf-verlauf" id="po-pf-verlauf">' + d.nachrichten.map(function (n) {
|
||
var vonMir = n.autor === "kunde";
|
||
return '<div class="po-nachricht po-nachricht--' + (vonMir ? "ich" : "du") + '">' +
|
||
'<div class="po-nachricht-kopf">' +
|
||
(vonMir ? "" : s(window.WD.t("po_von_dogfather")) + " · ") +
|
||
window.WD.datum(n.erstellt_am) +
|
||
(n.projekt_titel ? " · " + s(n.projekt_titel) : "") +
|
||
"</div><p>" + s(n.text) + "</p></div>";
|
||
}).join("") + "</div>";
|
||
}
|
||
|
||
html += '<div class="wd-feld" style="margin:1rem 0 .6rem" id="po-pf-feld">' +
|
||
'<textarea id="po-pf-text" rows="3" placeholder="' +
|
||
s(window.WD.t("po_postfach_platzhalter")) + '"></textarea>' +
|
||
'<p class="wd-feld-fehler"></p></div>';
|
||
|
||
if (d.projekte && d.projekte.length) {
|
||
html += '<div class="wd-feld" style="margin-bottom:.6rem">' +
|
||
'<label for="po-pf-projekt" class="wd-klein">' + s(window.WD.t("po_postfach_bezug")) + "</label>" +
|
||
'<select id="po-pf-projekt"><option value="">' +
|
||
s(window.WD.t("po_postfach_kein_bezug")) + "</option>" +
|
||
d.projekte.map(function (p) {
|
||
return '<option value="' + s(p.id) + '">' + s(p.titel) + "</option>";
|
||
}).join("") + "</select></div>";
|
||
}
|
||
|
||
html += '<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="po-pf-senden">' +
|
||
s(window.WD.t("po_senden")) + "</button></div>";
|
||
|
||
kasten.innerHTML = html;
|
||
|
||
var lauf = document.getElementById("po-pf-verlauf");
|
||
if (lauf) lauf.scrollTop = lauf.scrollHeight;
|
||
|
||
var eingabe = document.getElementById("po-pf-text");
|
||
var feld = document.getElementById("po-pf-feld");
|
||
var knopf = document.getElementById("po-pf-senden");
|
||
|
||
knopf.addEventListener("click", async function () {
|
||
var inhalt = eingabe.value.trim();
|
||
feld.classList.remove("wd-feld--fehler");
|
||
if (!inhalt) {
|
||
feld.classList.add("wd-feld--fehler");
|
||
feld.querySelector(".wd-feld-fehler").textContent = window.WD.t("po_postfach_leer_fehler");
|
||
eingabe.focus();
|
||
return;
|
||
}
|
||
var wahl = document.getElementById("po-pf-projekt");
|
||
window.WD.knopfSperren(knopf, true, "…");
|
||
try {
|
||
await api("/webdesign/portal/postfach", {
|
||
method: "POST",
|
||
body: { text: inhalt, projektId: wahl ? wahl.value : null },
|
||
});
|
||
window.WD.meldung(meldung, window.WD.t("po_postfach_gesendet"), "ok");
|
||
await ladePostfach();
|
||
} catch (fehler) {
|
||
if (behandleFehler(fehler)) return;
|
||
window.WD.knopfSperren(knopf, false);
|
||
window.WD.meldung(meldung, fehler.message, "fehler");
|
||
}
|
||
});
|
||
}
|
||
|
||
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 = '<div class="po-laedt">' + s(window.WD.t("laedt")) + "</div>";
|
||
|
||
try {
|
||
/* Beides gleichzeitig anfordern statt nacheinander. Nacheinander
|
||
waere die Wartezeit die Summe beider Anfragen — bei einer
|
||
Mobilfunkverbindung deutlich spuerbar.
|
||
|
||
Die Aufgabenliste darf dabei fehlschlagen, ohne die ganze Seite
|
||
mitzureissen: Sie ist eine Ergaenzung, keine Voraussetzung. Wer
|
||
wegen einer leeren Liste seine Zahlungen und Dateien nicht mehr
|
||
saehe, waere schlechter dran als vorher. */
|
||
var beides = await Promise.all([
|
||
api("/webdesign/portal/projekt/" + encodeURIComponent(id)),
|
||
api("/webdesign/portal/projekt/" + encodeURIComponent(id) + "/aufgaben")
|
||
.catch(function () { return { aufgaben: [], fortschritt: null }; }),
|
||
]);
|
||
var d = beides[0];
|
||
var aufg = beides[1];
|
||
var p = d.projekt;
|
||
var html = "";
|
||
|
||
html += '<div class="wd-karte" style="margin-bottom:1.2rem">' +
|
||
'<span class="po-projekt-nr">' + s(p.nummer) + "</span>" +
|
||
"<h2 style=\"margin:.2rem 0 .6rem\">" + s(p.titel) + "</h2>" +
|
||
wartetMarke(p.wartet_auf) +
|
||
phasenLeiste(p.status) +
|
||
(p.naechster_schritt
|
||
? '<p style="margin:.8rem 0 0"><strong>' + s(window.WD.t("po_naechster_schritt")) +
|
||
":</strong> " + s(p.naechster_schritt) + "</p>"
|
||
: "") +
|
||
"</div>";
|
||
|
||
/* Die Aufgabenliste steht GANZ OBEN, direkt nach dem Projektkopf.
|
||
Sie beantwortet die Frage, mit der man das Projekt oeffnet
|
||
("wie weit ist es?"). Zahlungen und Dateien sind wichtig, aber
|
||
sie sind nicht der Grund fuer den Klick. */
|
||
html += aufgabenKarte(aufg);
|
||
|
||
/* --- Zahlungen --- */
|
||
html += '<div class="wd-karte" style="margin-bottom:1.2rem"><h3>' + s(window.WD.t("po_zahlungen")) + "</h3>";
|
||
if (!d.zahlungen.length) {
|
||
html += '<p class="wd-leise wd-klein">' + s(window.WD.t("po_keine_zahlungen")) + "</p>";
|
||
} else {
|
||
html += '<ul class="po-liste">' + d.zahlungen.map(function (z) {
|
||
var offen = z.status === "offen" || z.status === "freigegeben";
|
||
return '<li class="po-eintrag"><span class="po-eintrag-haupt">' +
|
||
'<span class="po-eintrag-name">' + s(z.betrag_text) + "</span>" +
|
||
'<span class="po-eintrag-neben">' + s(z.zweck_text) + "</span></span>" +
|
||
'<span class="wd-tag' + (z.status === "bezahlt" ? " wd-tag--ok" : offen ? " wd-tag--gold" : "") + '">' +
|
||
s(window.WD.t("po_za_" + (z.status === "fehlge" ? "fehlge" : z.status))) + "</span>" +
|
||
(offen ? '<button type="button" class="wd-btn wd-btn--haupt wd-btn--klein" data-zahlung="' +
|
||
s(z.id) + '">' + s(window.WD.t("po_jetzt_bezahlen")) + "</button>" : "") +
|
||
"</li>";
|
||
}).join("") + "</ul>";
|
||
}
|
||
html += "</div>";
|
||
|
||
/* --- Dateien --- */
|
||
html += '<div class="wd-karte" style="margin-bottom:1.2rem"><h3>' + s(window.WD.t("po_dateien")) + "</h3>";
|
||
if (!d.dateien.length) {
|
||
html += '<p class="wd-leise wd-klein">' + s(window.WD.t("po_keine_dateien")) + "</p>";
|
||
} else {
|
||
html += '<ul class="po-liste">' + d.dateien.map(function (f) {
|
||
return '<li class="po-eintrag"><span class="po-eintrag-haupt">' +
|
||
'<span class="po-eintrag-name">' + s(f.anzeigename) + "</span>" +
|
||
'<span class="po-eintrag-neben">' + window.WD.datum(f.erstellt_am) +
|
||
(f.version > 1 ? " · " + s(window.WD.t("po_version")) + " " + f.version : "") +
|
||
"</span></span></li>";
|
||
}).join("") + "</ul>";
|
||
}
|
||
html += "</div>";
|
||
|
||
/* --- Ideen & Änderungswünsche --- */
|
||
html += '<div class="wd-karte" style="margin-bottom:1.2rem"><h3>' + s(window.WD.t("po_ideen")) + "</h3>" +
|
||
'<p class="wd-klein wd-leise">' + s(window.WD.t("po_ideen_text")) + "</p>";
|
||
|
||
if (d.aenderungen.length) {
|
||
html += d.aenderungen.map(function (a) {
|
||
var entscheidbar = a.status === "angeboten";
|
||
return '<div class="po-idee' + (entscheidbar ? " po-idee--offen" : "") + '">' +
|
||
'<div class="po-idee-kopf"><h4>' + s(a.titel) + "</h4>" +
|
||
'<span class="wd-tag' + (entscheidbar ? " wd-tag--gold" : a.status === "angenommen" ? " wd-tag--ok" : "") + '">' +
|
||
s(window.WD.t("po_ae_" + a.status)) + "</span></div>" +
|
||
'<p class="wd-klein" style="margin:0 0 .4rem">' + s(a.beschreibung) + "</p>" +
|
||
(a.aufwand_text ? '<p class="wd-klein wd-leise" style="margin:0">' + s(a.aufwand_text) + "</p>" : "") +
|
||
(a.zusatzpreis_text
|
||
? '<p class="wd-klein" style="margin:.3rem 0 0"><strong>' + s(window.WD.t("po_zusatzpreis")) +
|
||
": " + s(a.zusatzpreis_text) + "</strong>" +
|
||
(a.zusatzdauer_text ? " · " + s(window.WD.t("po_zusatzdauer")) + ": " + s(a.zusatzdauer_text) : "") +
|
||
"</p>"
|
||
: "") +
|
||
(entscheidbar
|
||
? '<div class="wd-btn-reihe" style="margin-top:.8rem">' +
|
||
'<button type="button" class="wd-btn wd-btn--haupt wd-btn--klein" data-ja="' + s(a.id) + '">' +
|
||
s(window.WD.t("po_annehmen")) + "</button>" +
|
||
'<button type="button" class="wd-btn wd-btn--still wd-btn--klein" data-nein="' + s(a.id) + '">' +
|
||
s(window.WD.t("po_ablehnen")) + "</button></div>"
|
||
: "") +
|
||
"</div>";
|
||
}).join("");
|
||
} else {
|
||
html += '<p class="wd-leise wd-klein">' + s(window.WD.t("po_keine_ideen")) + "</p>";
|
||
}
|
||
|
||
html += '<div class="wd-feld" style="margin:1rem 0 .6rem">' +
|
||
"<label for=\"po-idee-titel\">" + s(window.WD.t("po_idee_titel")) + "</label>" +
|
||
'<input type="text" id="po-idee-titel" maxlength="160" />' +
|
||
"</div>" +
|
||
'<div class="wd-feld" style="margin-bottom:.6rem" id="po-feld-idee">' +
|
||
"<label for=\"po-idee-text\">" + s(window.WD.t("po_idee_text")) + "</label>" +
|
||
'<textarea id="po-idee-text" rows="3"></textarea>' +
|
||
'<p class="wd-feld-fehler"></p></div>' +
|
||
'<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="po-idee-senden">' +
|
||
s(window.WD.t("po_idee_senden")) + "</button></div>";
|
||
|
||
/* --- Nachrichten --- */
|
||
html += '<div class="wd-karte"><h3>' + s(window.WD.t("po_nachrichten")) + "</h3>";
|
||
if (!d.nachrichten.length) {
|
||
html += '<p class="wd-leise wd-klein">' + s(window.WD.t("po_keine_nachrichten")) + "</p>";
|
||
} else {
|
||
html += d.nachrichten.map(function (n) {
|
||
var vonMir = n.autor === "kunde";
|
||
return '<div class="po-nachricht po-nachricht--' + (vonMir ? "ich" : "du") + '">' +
|
||
'<div class="po-nachricht-kopf">' + (vonMir ? "" : "Dogfather · ") +
|
||
window.WD.datum(n.erstellt_am) + "</div><p>" + s(n.text) + "</p></div>";
|
||
}).join("");
|
||
}
|
||
html += '<div class="wd-feld" style="margin:1rem 0 .6rem">' +
|
||
'<textarea id="po-nachricht-text" rows="2" placeholder="' +
|
||
s(window.WD.t("po_nachricht_platzhalter")) + '"></textarea></div>' +
|
||
'<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="po-nachricht-senden">' +
|
||
s(window.WD.t("po_senden")) + "</button></div>";
|
||
|
||
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");
|
||
}
|
||
});
|
||
});
|
||
|
||
/* Eigene Punkte abhaken. Der Punkt erscheint in der Liste zweimal
|
||
(oben unter "Das brauche ich noch von dir" und unten im
|
||
Abschnitt), deshalb wird nach dem Umschalten die ganze Ansicht
|
||
neu aufgebaut — sonst stuenden beide Fassungen widerspruechlich
|
||
nebeneinander. */
|
||
ziel.querySelectorAll("[data-haken]").forEach(function (b) {
|
||
b.addEventListener("click", async function () {
|
||
window.WD.knopfSperren(b, true, "…");
|
||
try {
|
||
await api("/webdesign/portal/aufgabe/" + encodeURIComponent(b.dataset.haken) + "/erledigt",
|
||
{ method: "POST", body: {} });
|
||
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);
|
||
})();
|