Angebote in der Oberflaeche: schicken und im Portal zusagen
VERWALTUNG
Der Angebotsbereich steht in der Anfrage-Detailansicht ueber dem
direkten Annehmen. Der uebliche Weg ist: Angebot raus, Kunde sagt zu,
Projekt entsteht -- direkt anzunehmen ist der Sonderfall (man hat schon
telefoniert). Stuende der Sonderfall oben, waere er der naheliegende
Griff, und man verschenkte den Beleg, den eine Zusage im Portal erzeugt.
Vorbelegt sind Paket, Preis, Anzahlung, Laufzeit, Ablaufdatum und der
komplette Leistungstext. Zu tun bleibt: Paket bestaetigen, Preis
bestaetigen. Der Leistungstext ist eingeklappt statt die Seite zu
fluten, aber aenderbar.
Ein Paketwechsel laedt ALLES neu -- auch den Leistungstext. Ein
stehengebliebener Text vom vorigen Paket waere der schlimmste Fall: Er
ginge unbemerkt hinaus, und im Angebot staende etwas, das zum Preis
nicht passt. Genau das prueft der Test.
PORTAL
Die Angebotskarte steht ganz oben, vor allen Projekten: Ein Angebot ist
das Einzige im Portal, das eine ENTSCHEIDUNG verlangt -- alles andere
ist Auskunft. Der eingefrorene Leistungstext wird lesbar aufbereitet
(Zwischenueberschriften, Punkte), nicht als Textblock hingeworfen.
Vor der Zusage wird gefragt, und die Frage nennt die Folgen ('verbindlich',
'Anzahlung'). Nicht aus Foermlichkeit: Hier entsteht ein Vertrag, und ein
Knopf, der beim Danebentippen einen Auftrag ausloest, waere eine Falle.
Beim Ablehnen wird NICHT gefragt -- das ist folgenlos.
ZWEI FEHLER GEFUNDEN, BEIDE IM NORMALFALL
- Die Angebotskarten standen im Zweig 'es gibt Projekte'. Ein Kunde mit
offenem Angebot hat aber typischerweise noch KEIN Projekt -- genau der
Normalfall -- und saeh damit nichts. Der Server lieferte das Angebot,
die Seite zeichnete es nur nie. Nur im Browsertest sichtbar.
- Eine Antwort ohne 'nachrichten' liess das Postfach werfen, und weil der
Fehler den Aufbau abbrach, erschien danach GAR NICHTS mehr -- auch nicht
das Angebot, das ueber allem stehen sollte. Dieselbe Sorte wie zuvor im
Cockpit: Ein halber Server ist ein realistischer Fall.
Neun Sprachschluessel in fuenf Sprachen ergaenzt.
Geprueft mit 54 Pruefungen auf Computer und Handy, die messen, was
tatsaechlich an den Server geht. Alle bestehenden weiter gruen.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+127
-2
@@ -203,6 +203,70 @@
|
||||
return stuecke.join("");
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
ANGEBOTE
|
||||
|
||||
Stehen GANZ oben, vor allen Projekten. Ein Angebot ist das Einzige
|
||||
im Portal, das eine Entscheidung verlangt — alles andere ist
|
||||
Auskunft. Stünde es zwischen den laufenden Projekten, sähe man es
|
||||
erst nach dem Scrollen, und der Zusage-Knopf wäre eine Fussnote.
|
||||
|
||||
Der Leistungstext kommt FERTIG vom Server (er wurde beim Absenden
|
||||
eingefroren). Hier wird er nur noch dargestellt: **fett** wird zur
|
||||
Zwischenüberschrift, Zeilen mit · zu Punkten. Absichtlich keine
|
||||
allgemeine Auszeichnungssprache — es soll genau das erscheinen, was
|
||||
geschickt wurde, und nichts, was ein Text zufällig auslöst.
|
||||
--------------------------------------------------------------------- */
|
||||
function leistungenZeichnen(text) {
|
||||
if (!text) return "";
|
||||
return text.split("\n").map(function (z) {
|
||||
var t = z.trim();
|
||||
if (!t) return "";
|
||||
var fett = t.match(/^\*\*(.+)\*\*$/);
|
||||
if (fett) return '<h4 class="po-ag-gruppe">' + s(fett[1]) + "</h4>";
|
||||
if (t.indexOf("\u00b7") === 0) return '<li>' + s(t.slice(1).trim()) + "</li>";
|
||||
return "<p>" + s(t) + "</p>";
|
||||
}).join("").replace(/(<li>.*?<\/li>)+/g, function (m) {
|
||||
return '<ul class="po-ag-liste">' + m + "</ul>";
|
||||
});
|
||||
}
|
||||
|
||||
function angebotsKarten(angebote) {
|
||||
if (!angebote || !angebote.length) return "";
|
||||
return angebote.map(function (a) {
|
||||
var offen = a.status === "gesendet";
|
||||
return '<div class="wd-karte po-angebot' + (offen ? " po-angebot--offen" : "") +
|
||||
'" data-angebot="' + s(a.id) + '">' +
|
||||
'<span class="po-ag-marke">' +
|
||||
(a.status === "angenommen" ? s(window.WD.t("po_ag_angenommen"))
|
||||
: a.abgelaufen ? s(window.WD.t("po_ag_abgelaufen"))
|
||||
: s(window.WD.t("po_ag_neu"))) + "</span>" +
|
||||
"<h3>" + s(a.titel) + "</h3>" +
|
||||
'<p class="po-ag-preis">' + s(a.preis_text) + "</p>" +
|
||||
'<p class="wd-klein wd-leise">' + s(window.WD.t("po_ag_anzahlung"))
|
||||
.replace("{betrag}", s(a.anzahlung_text)) + "</p>" +
|
||||
(a.laufzeit_werktage
|
||||
? '<p class="wd-klein wd-leise">' + s(window.WD.t("po_ag_dauer"))
|
||||
.replace("{n}", a.laufzeit_werktage) + "</p>"
|
||||
: "") +
|
||||
'<div class="po-ag-leistungen">' + leistungenZeichnen(a.leistungen_text) + "</div>" +
|
||||
(a.hinweis_text ? '<p class="wd-klein">' + s(a.hinweis_text) + "</p>" : "") +
|
||||
(offen && a.annehmbar
|
||||
? '<p class="wd-klein wd-leise">' + s(window.WD.t("po_ag_gueltig"))
|
||||
.replace("{datum}", s(a.gueltig_bis || "")) + "</p>" +
|
||||
'<div class="po-ag-knoepfe">' +
|
||||
'<button type="button" class="wd-btn wd-btn--haupt" data-zusagen="' + s(a.id) + '">' +
|
||||
s(window.WD.t("po_ag_zusagen")) + "</button>" +
|
||||
'<button type="button" class="wd-btn wd-btn--still wd-btn--klein" data-ablehnen="' +
|
||||
s(a.id) + '">' + s(window.WD.t("po_ag_ablehnen")) + "</button>" +
|
||||
"</div>"
|
||||
: a.abgelaufen
|
||||
? '<p class="wd-klein">' + s(window.WD.t("po_ag_abgelaufen_text")) + "</p>"
|
||||
: "") +
|
||||
"</div>";
|
||||
}).join("");
|
||||
}
|
||||
|
||||
function wartetMarke(wartet) {
|
||||
if (wartet !== "kunde" && wartet !== "dogfather") return "";
|
||||
return '<span class="po-wartet po-wartet--' + wartet + '">' +
|
||||
@@ -359,6 +423,15 @@
|
||||
}
|
||||
|
||||
html += "<h2>" + s(window.WD.t("po_projekte")) + "</h2>";
|
||||
/* Angebote stehen VOR der Weiche, nicht in einem ihrer Zweige.
|
||||
|
||||
Der erste Entwurf setzte sie in den Zweig "es gibt Projekte".
|
||||
Ein Kunde mit einem offenen Angebot hat aber typischerweise noch
|
||||
KEIN Projekt -- genau der Normalfall -- und saeh damit nichts.
|
||||
Aufgefallen erst im Browsertest: Der Server lieferte das Angebot,
|
||||
die Seite zeichnete es nur nie. */
|
||||
html += angebotsKarten(d.angebote);
|
||||
|
||||
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
|
||||
@@ -502,10 +575,20 @@
|
||||
'<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) {
|
||||
/* Fehlt die Liste ganz, wird daraus eine leere -- statt eines
|
||||
Fehlers, der die ganze Portalseite mitreisst.
|
||||
|
||||
Aufgefallen am 24.08.2026: Eine Antwort ohne 'nachrichten' liess
|
||||
das Postfach werfen, und weil der Fehler den Aufbau abbrach,
|
||||
erschien danach GAR NICHTS mehr -- auch nicht das Angebot, das
|
||||
ueber allem stehen sollte. Dieselbe Sorte Fehler wie zuvor im
|
||||
Cockpit der Verwaltung: Ein halber Server ist ein realistischer
|
||||
Fall, und dann soll der Rest der Seite trotzdem stehen. */
|
||||
var nachrichten = d.nachrichten || [];
|
||||
if (!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) {
|
||||
html += '<div class="po-pf-verlauf" id="po-pf-verlauf">' + nachrichten.map(function (n) {
|
||||
var vonMir = n.autor === "kunde";
|
||||
return '<div class="po-nachricht po-nachricht--' + (vonMir ? "ich" : "du") + '">' +
|
||||
'<div class="po-nachricht-kopf">' +
|
||||
@@ -841,4 +924,46 @@
|
||||
|
||||
if (window.WD) start();
|
||||
else document.addEventListener("wd-bereit", start);
|
||||
|
||||
/* ---------------------------------------------------------------------
|
||||
ZUSAGE UND ABLEHNUNG
|
||||
|
||||
Über Ereignis-Delegation am Dokument, weil die Karten bei jedem
|
||||
Laden neu entstehen — einzeln angehängte Zuhörer wären nach der
|
||||
ersten Aktualisierung verschwunden.
|
||||
|
||||
Vor der Zusage wird gefragt. Nicht aus Förmlichkeit: Hier entsteht
|
||||
ein Vertrag, und ein Knopf, der beim Danebentippen einen Auftrag
|
||||
auslöst, wäre eine Falle. Beim Ablehnen wird NICHT gefragt — das
|
||||
ist folgenlos und jederzeit umkehrbar (man schreibt einfach).
|
||||
--------------------------------------------------------------------- */
|
||||
document.addEventListener("click", async function (e) {
|
||||
var ja = e.target.closest ? e.target.closest("[data-zusagen]") : null;
|
||||
var nein = e.target.closest ? e.target.closest("[data-ablehnen]") : null;
|
||||
if (!ja && !nein) return;
|
||||
|
||||
var id = (ja || nein).dataset[ja ? "zusagen" : "ablehnen"];
|
||||
var knopf = ja || nein;
|
||||
|
||||
if (ja && !confirm(window.WD.t("po_ag_sicher"))) return;
|
||||
|
||||
window.WD.knopfSperren(knopf, true, "…");
|
||||
try {
|
||||
var r = await window.WD.api(
|
||||
"/webdesign/portal/angebote/" + encodeURIComponent(id) + (ja ? "/zusagen" : "/ablehnen"),
|
||||
{ method: "POST", body: {} });
|
||||
if (ja) {
|
||||
/* Nach der Zusage vollständig neu laden: Es ist ein Projekt
|
||||
entstanden, eine Rechnung, und das Angebot hat einen anderen
|
||||
Zustand. Einzelne Stellen nachzuziehen hiesse, jede davon
|
||||
einzeln richtig zu treffen. */
|
||||
alert(r.hinweis || "Danke!");
|
||||
}
|
||||
location.reload();
|
||||
} catch (f) {
|
||||
window.WD.knopfSperren(knopf, false);
|
||||
alert(f.message || "Das hat leider nicht geklappt.");
|
||||
}
|
||||
});
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user