From 097e80b845a1a65de2c63e61e4f63e81349ce4d4 Mon Sep 17 00:00:00 2001 From: Dogfather Date: Sat, 22 Aug 2026 21:40:27 +0200 Subject: [PATCH] Projekte anlegen und ein leerer Zustand, der fuehrt statt zu enttaeuschen Rueckmeldung 22.08.2026 mit Bildschirmfoto aus dem eigenen Testzugang: "die seite soll jetzt schon bitte richtig krass sein und hoch profissionel, auch sehr detailliert." Zwei Luecken, beide geschlossen: 1) Projekte liessen sich nur ueber die Schnittstelle anlegen. Jetzt in der Verwaltung: pro Kunde ein "+ Projekt" mit Titel, Paket, Preis, Richttermin und naechstem Schritt. Bewusst als Einblendung direkt bei der Kundenzeile statt als eigene Seite -- man legt ein Projekt IMMER fuer einen bestimmten Kunden an, nie im luftleeren Raum. Der Kundenname steht deshalb gross im Formular, damit es nicht versehentlich dem Falschen angehaengt wird. Die 30 % Anzahlung wird beim Tippen live mitgerechnet. Sie wird zwar serverseitig berechnet, aber wer sie beim Eintippen sieht, merkt eine falsche Null sofort -- und nicht erst, wenn der Kunde ueberweisen soll. 2) Der leere Zustand im Portal war ein einziger Satz. Das ist eine verpasste Gelegenheit: Wer dort zum ersten Mal landet, hat gerade sein Passwort gesetzt und weiss noch nicht, was ihn erwartet -- genau dann entscheidet sich, ob die Seite souveraen wirkt oder unfertig. Jetzt zeigt er in vier Punkten, WAS gleich hier stehen wird (Projektstand, wer am Zug ist, Dateien und Nachrichten, Ideen jederzeit) und schliesst mit der Zusicherung, dass nichts zu tun ist. Fuenfsprachig. 45/45 Handy-Abnahme, i18n vollstaendig. Co-Authored-By: Claude Opus 5 --- assets/css/webdesign.css | 43 ++++++++++++++ assets/js/i18n-wd-portal.js | 48 +++++++++++++++ assets/js/wd-portal.js | 25 +++++++- webdesign/verwaltung.html | 113 ++++++++++++++++++++++++++++++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) diff --git a/assets/css/webdesign.css b/assets/css/webdesign.css index 106e6839..ed4af86b 100644 --- a/assets/css/webdesign.css +++ b/assets/css/webdesign.css @@ -917,3 +917,46 @@ a.wd-karte:hover, .wd-karte--klickbar:hover { .wd-karte { border: 1px solid #bbb; background: #fff; box-shadow: none; } .wd h1, .wd h2, .wd h3, .wd p, .wd li { color: #000 !important; } } + +/* --------------------------------------------------------------------- + 18. LEERER ZUSTAND IM KUNDENPORTAL + + Ein leerer Bereich mit einem einzigen Satz ist eine verpasste + Gelegenheit. Wer hier zum ersten Mal landet, hat gerade sein Passwort + gesetzt und weiß noch nicht, was ihn erwartet — genau dann entscheidet + sich, ob die Seite souverän wirkt oder unfertig. + + Deshalb zeigt der leere Zustand, WAS gleich hier stehen wird, statt + nur festzustellen, dass gerade nichts da ist. + --------------------------------------------------------------------- */ +.po-leer { text-align: center; padding: clamp(1.6rem, 4vw, 2.6rem) clamp(1rem, 3vw, 2rem); } +.po-leer-symbol { width: clamp(64px, 12vw, 92px); margin: 0 auto .9rem; position: relative; } +.po-leer-symbol img { width: 100%; height: auto; opacity: .9; } +.po-leer-symbol::before { + content: ""; position: absolute; inset: -24%; z-index: -1; border-radius: 50%; + background: radial-gradient(circle, rgba(127,208,232,.26), transparent 70%); + filter: blur(20px); +} +.po-leer h3 { margin-bottom: .3em; } +.po-leer > p { max-width: 48ch; margin-left: auto; margin-right: auto; } + +.po-leer-liste { + display: grid; grid-template-columns: repeat(2, 1fr); gap: .9rem; + margin: 1.6rem 0 1.2rem; text-align: left; +} +@media (max-width: 640px) { .po-leer-liste { grid-template-columns: 1fr; } } +.po-leer-punkt { + display: grid; grid-template-columns: auto 1fr; gap: .8rem; align-items: start; + padding: .9rem 1rem; border-radius: 14px; + background: rgba(127,208,232,.05); border: 1px solid var(--wd-rand); +} +.po-leer-nr { + width: 28px; height: 28px; border-radius: 50%; + display: flex; align-items: center; justify-content: center; + font-family: var(--font-head); font-weight: 800; font-size: .85rem; + color: var(--wd-blau); background: rgba(127,208,232,.12); + border: 1px solid var(--wd-rand-stark); +} +.po-leer-punkt strong { display: block; color: var(--wd-text); font-size: .98rem; margin-bottom: .15rem; } +.po-leer-punkt span { display: block; font-size: .88rem; color: var(--wd-text-soft); line-height: 1.5; } +.po-leer-fuss { margin: 0; } diff --git a/assets/js/i18n-wd-portal.js b/assets/js/i18n-wd-portal.js index 7503efce..c426dc85 100644 --- a/assets/js/i18n-wd-portal.js +++ b/assets/js/i18n-wd-portal.js @@ -71,6 +71,54 @@ window.I18N_WD = { fr: "Ton projet apparaîtra ici dès sa création.", pt: "O teu projeto aparece aqui assim que for criado.", }, + /* --- Leerer Zustand: fuehrt, statt zu enttaeuschen --- */ + po_leer_h3: { + de: "Gleich geht es los", + "de-CH": "Glii gohts los", + en: "It starts soon", + fr: "Ça commence bientôt", + pt: "Está quase a começar", + }, + po_leer_1: { de: "Projektstand", "de-CH": "Projektstand", en: "Project status", fr: "État du projet", pt: "Estado do projeto" }, + po_leer_1t: { + de: "Eine Leiste zeigt dir jederzeit, in welcher Phase dein Projekt gerade ist — vom Briefing bis zur Übergabe.", + "de-CH": "E Leischte zeigt dir jederziit, i welere Phase dis Projekt grad isch — vom Briefing bis zur Übergab.", + en: "A bar shows you at any time which phase your project is in — from briefing to handover.", + fr: "Une barre t'indique à tout moment la phase de ton projet — du briefing à la remise.", + pt: "Uma barra mostra-te a qualquer momento em que fase está o teu projeto — do briefing à entrega.", + }, + po_leer_2: { de: "Wer gerade dran ist", "de-CH": "Wär grad dra isch", en: "Whose turn it is", fr: "À qui le tour", pt: "De quem é a vez" }, + po_leer_2t: { + de: "Du siehst sofort, ob gerade auf dich gewartet wird oder ob Dogfather am Zug ist. Kein Nachfragen nötig.", + "de-CH": "Du gsehsch sofort, ob grad uf dich gwartet wird oder ob dr Dogfather am Zug isch. Kes Nachefroge nötig.", + en: "You see immediately whether we are waiting for you or Dogfather is working on it. No need to ask.", + fr: "Tu vois immédiatement si l'on t'attend ou si Dogfather s'en occupe. Pas besoin de demander.", + pt: "Vês logo se estamos à tua espera ou se o Dogfather está a tratar. Sem precisares de perguntar.", + }, + po_leer_3: { de: "Dateien und Nachrichten", "de-CH": "Datei und Nachrichte", en: "Files and messages", fr: "Fichiers et messages", pt: "Ficheiros e mensagens" }, + po_leer_3t: { + de: "Angebote, Entwürfe und Freigaben liegen hier gebündelt — nichts geht in E-Mails unter.", + "de-CH": "Aagebot, Entwürf und Freigabe liged do bündlet — nüt goht i E-Mails under.", + en: "Quotes, drafts and approvals are kept together here — nothing gets lost in e-mails.", + fr: "Devis, maquettes et validations sont regroupés ici — rien ne se perd dans les e-mails.", + pt: "Orçamentos, esboços e aprovações ficam reunidos aqui — nada se perde em e-mails.", + }, + po_leer_4: { de: "Ideen jederzeit", "de-CH": "Ideä jederziit", en: "Ideas anytime", fr: "Des idées à tout moment", pt: "Ideias a qualquer momento" }, + po_leer_4t: { + de: "Fällt dir später etwas ein, trägst du es hier ein. Du bekommst Aufwand und Preis genannt und entscheidest in Ruhe.", + "de-CH": "Fallt dir spöter öppis ii, treisch es do ii. Du überchunnsch Ufwand und Priis gnennt und entscheidsch i Rueh.", + en: "If something occurs to you later, note it here. You get effort and price quoted and decide at your own pace.", + fr: "Si une idée te vient plus tard, note-la ici. Tu reçois l'effort et le prix, et tu décides tranquillement.", + pt: "Se te ocorrer algo mais tarde, aponta aqui. Recebes esforço e preço e decides com calma.", + }, + po_leer_fuss: { + de: "Sobald dein Projekt angelegt ist, erscheint es automatisch hier — du musst nichts tun.", + "de-CH": "Sobald dis Projekt aagleit isch, erschiint es automatisch do — du muesch nüt mache.", + en: "As soon as your project is created it appears here automatically — you do not have to do anything.", + fr: "Dès que ton projet est créé, il apparaît ici automatiquement — tu n'as rien à faire.", + pt: "Assim que o teu projeto for criado, aparece aqui automaticamente — não tens de fazer nada.", + }, + po_betreuung: { de: "Laufende Betreuung", "de-CH": "Laufendi Betreuig", en: "Ongoing support", fr: "Suivi en cours", pt: "Acompanhamento em curso" }, po_offene_zahlungen: { de: "Offene Zahlungen", "de-CH": "Offni Zahlige", en: "Open payments", fr: "Paiements en attente", pt: "Pagamentos em aberto" }, po_jetzt_bezahlen: { de: "Jetzt bezahlen", "de-CH": "Jetzt zahle", en: "Pay now", fr: "Payer maintenant", pt: "Pagar agora" }, diff --git a/assets/js/wd-portal.js b/assets/js/wd-portal.js index 2d120d99..9708948f 100644 --- a/assets/js/wd-portal.js +++ b/assets/js/wd-portal.js @@ -168,7 +168,30 @@ html += "

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

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

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

"; + /* 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")) + "

" + + '
' + + [["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 ' ' + ' ' + '' + + '' + + '' + + ''; + box.scrollIntoView({ behavior: "smooth", block: "center" }); + setTimeout(function () { document.getElementById("vw-ptitel").focus(); }, 100); + + /* Die Anzahlung live mitrechnen. Sie wird zwar serverseitig berechnet, + aber wer sie beim Eintippen sieht, merkt eine falsche Null sofort -- + und nicht erst, wenn der Kunde den Betrag ueberweisen soll. */ + var preisFeld = document.getElementById("vw-ppreis"); + preisFeld.addEventListener("input", function () { + var wert = Number(preisFeld.value); + document.getElementById("vw-panzahlung").textContent = + Number.isFinite(wert) && wert > 0 + ? "30 % Anzahlung: " + (Math.round(wert * 100 * 0.3) / 100).toFixed(2).replace(".", ",") + " €" + : "Die 30 % Anzahlung wird daraus automatisch berechnet."; + }); + + document.getElementById("vw-projekt-abbrechen").addEventListener("click", function () { + box.hidden = true; box.innerHTML = ""; + }); + + document.getElementById("vw-projekt-speichern").addEventListener("click", async function (e) { + var titel = document.getElementById("vw-ptitel").value.trim(); + var feld = document.getElementById("vw-feld-ptitel"); + feld.classList.remove("wd-feld--fehler"); + if (!titel) { + feld.classList.add("wd-feld--fehler"); + feld.querySelector(".wd-feld-fehler").textContent = "Bitte einen Projekttitel angeben."; + return; + } + window.WD.knopfSperren(e.target, true, "Wird angelegt…"); + try { + var r = await api("/webdesign/admin/projekte", { + method: "POST", + body: { + kundeId: kundeId, + titel: titel, + paket: document.getElementById("vw-ppaket").value, + preisEuro: Number(document.getElementById("vw-ppreis").value) || undefined, + richttermin: document.getElementById("vw-ptermin").value.trim(), + naechsterSchritt: document.getElementById("vw-pschritt").value.trim(), + }, + }); + box.innerHTML = ''; + ladeKunden(); + } catch (f) { + window.WD.knopfSperren(e.target, false); + window.WD.meldung(meldung, f.message, "fehler"); + } + }); + } + /* ---------------- Start ---------------- Reihenfolge bewusst so: