Rueckmeldung 22.08.2026: "ich hab auf der verwaltungs seite 2 mal dass ich
den zugangscode eingeben muss und vanvan auch, ich will es nur einmal
eingeben muessen." Berechtigt.
Die Zugangswand hat serverseitig laengst geprueft, WER da ist -- Dogfather
oder VanVan, laut Masterplan S.4 beide gleichberechtigte
Volladministratoren. Ein zweites Passwort danach bringt keinen
zusaetzlichen Schutz, es kostet nur jedes Mal Zeit.
Warum es nicht einfach "Cookie mitschicken" ist: Die Verwaltungsdaten
liegen hinter postfach.dogfather-universe.com, einem ANDEREN Rechnernamen.
Das Sitzungs-Cookie gilt dort nicht -- so sind Cookies gebaut, und das ist
gut so.
Loesung: Die Zugangswand stellt unter /webdesign/api-ausweis einen
kurzlebigen, signierten Ausweis aus, den die interne API anerkennt.
- 15 Minuten gueltig, die Seite holt bei Bedarf still einen neuen
- traegt bereich="wd-admin", damit ein Sitzungs-Token der Zugangswand
hier NICHT durchgeht und umgekehrt
- nur mit gueltiger Zugangssitzung zu bekommen
- gilt AUSSCHLIESSLICH fuer die /webdesign-Endpunkte. Postfach,
Bewerbungen, Supporter und Teamverwaltung bleiben unberuehrt
Fehlt WEBDESIGN_API_SECRET auf einer der beiden Seiten, gilt kein Ausweis
und die Verwaltung fragt wie bisher nach dem Team-Code. Ein fehlender
Konfigurationswert darf niemals eine Tuer oeffnen -- nur eine schliessen.
Genau das prueft der letzte Testfall.
Bei einem 401 wird EINMAL still ein neuer Ausweis geholt und die Anfrage
wiederholt, statt jemanden mitten im Arbeiten rauszuwerfen.
10/10 Tests, darunter: gefaelschte Signatur, fremdes Geheimnis,
abgelaufen, falscher Bereich, erfundene Rolle.
Co-Authored-By: Claude Opus 5 <[email protected]>
882 lines
40 KiB
HTML
882 lines
40 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Verwaltung — Dogfather Webdesign</title>
|
||
<meta name="robots" content="noindex, nofollow, noarchive" />
|
||
<link rel="icon" type="image/png" href="/assets/img/favicon.png" />
|
||
<meta name="theme-color" content="#05070b" />
|
||
<link rel="manifest" href="/webdesign/app.webmanifest" />
|
||
<link rel="apple-touch-icon" href="/assets/img/wd-app-180.png" />
|
||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||
<meta name="apple-mobile-web-app-title" content="DF Verwaltung" />
|
||
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
|
||
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
|
||
<style>
|
||
/* =====================================================================
|
||
Verwaltungsbereich — bewusst NUR AUF DEUTSCH.
|
||
|
||
Der öffentliche Teil läuft in fünf Sprachen, weil dort Kunden landen.
|
||
Hier landen ausschließlich Dogfather und VanVan. Fünf Sprachen wären
|
||
fünffache Pflege und fünffache Fehlerfläche ohne einen einzigen
|
||
Nutzer, der sie braucht — genau wie im bestehenden internen Bereich
|
||
des Universe, der ebenfalls einsprachig ist.
|
||
===================================================================== */
|
||
|
||
/* ---------- Anmeldung ---------- */
|
||
.vw-login { max-width: 420px; margin: clamp(2rem,8vh,5rem) auto; }
|
||
|
||
/* ---------- Kopfleiste ---------- */
|
||
.vw-leiste {
|
||
display: flex; flex-wrap: wrap; align-items: center; gap: .8rem;
|
||
margin-bottom: 1.2rem;
|
||
}
|
||
.vw-leiste h1 { margin: 0; font-size: clamp(1.4rem, 3.4vw, 2rem); }
|
||
.vw-wer { margin-left: auto; display: flex; align-items: center; gap: .6rem; font-size: .9rem; color: var(--wd-text-soft); }
|
||
|
||
/* ---------- Kennzahlen ---------- */
|
||
.vw-zahlen { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: .7rem; margin-bottom: 1.4rem; }
|
||
.vw-zahl {
|
||
padding: .9rem 1rem; border-radius: 14px;
|
||
background: rgba(16,26,41,.6); border: 1px solid var(--wd-rand);
|
||
text-align: left;
|
||
}
|
||
.vw-zahl-wert { font-family: var(--font-head); font-weight: 800; font-size: 1.7rem; line-height: 1; color: var(--wd-blau); }
|
||
.vw-zahl-name { font-size: .78rem; letter-spacing: .06em; color: var(--wd-text-soft); margin-top: .3rem; }
|
||
.vw-zahl--achtung .vw-zahl-wert { color: var(--wd-gold); }
|
||
|
||
/* ---------- Filter ---------- */
|
||
.vw-filter { display: flex; flex-wrap: wrap; gap: .5rem; margin-bottom: 1rem; align-items: center; }
|
||
.vw-filter-knopf {
|
||
min-height: 44px; padding: .5rem .95rem; border-radius: 11px;
|
||
border: 1px solid var(--wd-rand); background: transparent;
|
||
color: var(--wd-text-mid); font-weight: 600; font-size: .9rem; cursor: pointer;
|
||
display: inline-flex; align-items: center; gap: .4rem;
|
||
}
|
||
.vw-filter-knopf:hover { border-color: var(--wd-rand-stark); color: var(--wd-text); }
|
||
.vw-filter-knopf.vw-an { background: rgba(127,208,232,.13); border-color: var(--wd-blau); color: var(--wd-blau); }
|
||
.vw-filter-zahl {
|
||
font-size: .74rem; padding: .1rem .4rem; border-radius: 6px;
|
||
background: rgba(127,208,232,.14); color: var(--wd-blau-hell);
|
||
}
|
||
.vw-suche { margin-left: auto; min-width: 200px; flex: 0 1 260px; }
|
||
|
||
/* ---------- Liste ---------- */
|
||
.vw-karte {
|
||
display: grid; grid-template-columns: auto 1fr auto; gap: .5rem 1rem;
|
||
align-items: center;
|
||
padding: 1rem 1.1rem; margin-bottom: .6rem;
|
||
border-radius: 14px; border: 1px solid var(--wd-rand);
|
||
background: rgba(16,26,41,.5);
|
||
cursor: pointer; width: 100%; text-align: left;
|
||
transition: border-color var(--wd-zeit-schnell) ease, background var(--wd-zeit-schnell) ease;
|
||
}
|
||
.vw-karte:hover { border-color: var(--wd-rand-stark); background: rgba(127,208,232,.05); }
|
||
.vw-karte-nr { font-family: var(--font-head); font-weight: 800; font-size: .82rem; color: var(--wd-blau); white-space: nowrap; }
|
||
.vw-karte-mitte { min-width: 0; }
|
||
.vw-karte-name { font-weight: 700; color: var(--wd-text); }
|
||
.vw-karte-info { font-size: .87rem; color: var(--wd-text-soft); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||
.vw-karte-rechts { text-align: right; white-space: nowrap; }
|
||
.vw-karte-zeit { font-size: .78rem; color: var(--wd-text-soft); margin-top: .25rem; }
|
||
@media (max-width: 600px) {
|
||
.vw-karte { grid-template-columns: 1fr auto; }
|
||
.vw-karte-nr { grid-column: 1 / -1; }
|
||
}
|
||
|
||
/* Statusfarben — Gold nur da, wo wirklich etwas zu tun ist. */
|
||
.vw-status { display: inline-block; padding: .22rem .6rem; border-radius: 999px; font-size: .74rem; font-weight: 700; }
|
||
.vw-status--neu { background: rgba(232,200,119,.16); color: var(--wd-gold); }
|
||
.vw-status--pruefung { background: rgba(127,208,232,.14); color: var(--wd-blau); }
|
||
.vw-status--rueckfrage { background: rgba(183,157,255,.16); color: var(--wd-lila); }
|
||
.vw-status--angebot { background: rgba(183,157,255,.16); color: var(--wd-lila); }
|
||
.vw-status--angenommen { background: rgba(111,214,163,.16); color: var(--wd-ok); }
|
||
.vw-status--abgeschlossen { background: rgba(111,214,163,.1); color: var(--wd-ok); }
|
||
.vw-status--abgelehnt { background: rgba(255,157,157,.12); color: var(--wd-fehler); }
|
||
|
||
/* ---------- Detailansicht ---------- */
|
||
.vw-detail { position: fixed; inset: 0; z-index: 100; display: none; }
|
||
.vw-detail.vw-auf { display: block; }
|
||
.vw-detail-schatten { position: absolute; inset: 0; background: rgba(2,4,8,.78); backdrop-filter: blur(3px); }
|
||
.vw-detail-blatt {
|
||
position: absolute; top: 0; right: 0; bottom: 0;
|
||
width: min(680px, 100%);
|
||
background: #080d16; border-left: 1px solid var(--wd-rand-stark);
|
||
overflow-y: auto; padding: 1.4rem clamp(1rem,3vw,1.8rem) 3rem;
|
||
animation: vw-rein .28s cubic-bezier(.22,1,.36,1) both;
|
||
}
|
||
@keyframes vw-rein { from { transform: translateX(24px); opacity: 0; } to { transform: none; opacity: 1; } }
|
||
@media (prefers-reduced-motion: reduce) { .vw-detail-blatt { animation: none; } }
|
||
.vw-detail-kopf { display: flex; align-items: flex-start; gap: 1rem; margin-bottom: 1.2rem; }
|
||
.vw-zu {
|
||
margin-left: auto; min-width: 44px; min-height: 44px;
|
||
border-radius: 10px; border: 1px solid var(--wd-rand);
|
||
background: transparent; color: var(--wd-text-mid); cursor: pointer; font-size: 1.1rem;
|
||
}
|
||
.vw-zu:hover { color: var(--wd-text); border-color: var(--wd-rand-stark); }
|
||
|
||
.vw-abschnitt { margin-bottom: 1.4rem; }
|
||
.vw-abschnitt > h3 {
|
||
font-size: .74rem; letter-spacing: .13em; text-transform: uppercase;
|
||
color: var(--wd-blau); margin-bottom: .6rem;
|
||
padding-bottom: .4rem; border-bottom: 1px solid var(--wd-rand);
|
||
}
|
||
.vw-paar { display: grid; grid-template-columns: minmax(0,.4fr) minmax(0,1fr); gap: .2rem .9rem; margin-bottom: .45rem; }
|
||
@media (max-width: 520px) { .vw-paar { grid-template-columns: 1fr; margin-bottom: .8rem; } }
|
||
.vw-paar dt { color: var(--wd-text-soft); font-size: .88rem; }
|
||
.vw-paar dd { margin: 0; color: var(--wd-text); font-size: .95rem; overflow-wrap: break-word; white-space: pre-wrap; }
|
||
.vw-paar dd.vw-leer { color: var(--wd-text-soft); font-style: italic; }
|
||
|
||
.vw-status-wahl { display: flex; flex-wrap: wrap; gap: .4rem; }
|
||
.vw-notiz {
|
||
padding: .8rem .95rem; border-radius: 12px; margin-bottom: .5rem;
|
||
background: rgba(127,208,232,.05); border: 1px solid var(--wd-rand);
|
||
}
|
||
.vw-notiz-kopf { font-size: .76rem; color: var(--wd-text-soft); margin-bottom: .3rem; }
|
||
.vw-notiz p { margin: 0; font-size: .93rem; white-space: pre-wrap; }
|
||
|
||
.vw-tabs { display: flex; gap: .4rem; }
|
||
.vw-link-anzeige {
|
||
padding: 1rem; border-radius: 14px;
|
||
background: rgba(232,200,119,.08); border: 1px solid rgba(232,200,119,.35);
|
||
}
|
||
.vw-link-anzeige code {
|
||
display: block; word-break: break-all; margin: .6rem 0;
|
||
font-size: .84rem; color: var(--wd-gold); line-height: 1.5;
|
||
}
|
||
.vw-leer-hinweis { text-align: center; padding: 3rem 1rem; color: var(--wd-text-soft); }
|
||
.vw-laedt { text-align: center; padding: 2.5rem 1rem; color: var(--wd-text-soft); }
|
||
</style>
|
||
</head>
|
||
<body class="wd">
|
||
|
||
<div id="wd-kopf" data-wd-kopf="minimal"></div>
|
||
|
||
<main id="wd-inhalt">
|
||
<section class="wd-abschnitt" style="padding-top:1.6rem">
|
||
<div class="wd-container">
|
||
|
||
<!-- ============ ANMELDUNG ============ -->
|
||
<div id="vw-anmeldung" class="wd-karte vw-login" hidden>
|
||
<h1 style="font-size:1.4rem">Verwaltung</h1>
|
||
<p class="wd-klein wd-leise">
|
||
Normalerweise landest du hier gar nicht: Wer durch die Zugangswand
|
||
gekommen ist, wird direkt hereingelassen. Diese Maske erscheint nur,
|
||
wenn das nicht möglich war — dann brauchst du deinen persönlichen
|
||
Zugangscode aus dem internen Bereich (derselbe wie fürs Postfach).
|
||
</p>
|
||
<form id="vw-login-form" novalidate>
|
||
<div class="wd-feld" id="vw-feld-code">
|
||
<label for="vw-code">Zugangscode</label>
|
||
<input type="password" id="vw-code" autocomplete="current-password"
|
||
autocapitalize="off" spellcheck="false" />
|
||
<p class="wd-feld-fehler"></p>
|
||
</div>
|
||
<button type="submit" class="wd-btn wd-btn--haupt wd-btn--breit">Anmelden</button>
|
||
</form>
|
||
</div>
|
||
|
||
<!-- ============ ARBEITSBEREICH ============ -->
|
||
<div id="vw-bereich" hidden>
|
||
<div class="vw-leiste">
|
||
<h1 id="vw-titel">Projektanfragen</h1>
|
||
<div class="vw-tabs">
|
||
<button type="button" class="vw-filter-knopf vw-an" data-tab="anfragen">Anfragen</button>
|
||
<button type="button" class="vw-filter-knopf" data-tab="kunden">Kunden</button>
|
||
</div>
|
||
<div class="vw-wer">
|
||
<span id="vw-name">—</span>
|
||
<button type="button" class="wd-btn wd-btn--still wd-btn--klein" id="vw-abmelden">Abmelden</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="vw-zahlen" id="vw-zahlen"></div>
|
||
|
||
<div class="vw-filter" id="vw-filter"></div>
|
||
|
||
<div class="wd-feld" style="margin-bottom:1rem">
|
||
<input type="search" id="vw-suche" class="vw-suche" placeholder="Nach Name, E-Mail oder Nummer suchen…"
|
||
style="width:100%" aria-label="Anfragen durchsuchen" />
|
||
</div>
|
||
|
||
<div class="wd-meldung" id="vw-meldung" hidden></div>
|
||
<div id="vw-liste"><div class="vw-laedt">Wird geladen…</div></div>
|
||
|
||
<!-- ============ KUNDEN ============ -->
|
||
<div id="vw-kunden-bereich" hidden>
|
||
<div class="wd-karte" style="margin-bottom:1.2rem">
|
||
<h2 style="font-size:1.15rem">Neuen Kundenzugang anlegen</h2>
|
||
<p class="wd-klein wd-leise">
|
||
Der Kunde bekommt einen Einladungslink und setzt sein Passwort selbst.
|
||
Den Link siehst du <strong>nur einmal</strong> — danach steht in der
|
||
Datenbank nur noch sein verschlüsselter Abdruck. Verloren? Einfach
|
||
einen neuen erzeugen.
|
||
</p>
|
||
<form id="vw-kunde-form" novalidate>
|
||
<div class="wd-grid wd-grid--2">
|
||
<div class="wd-feld" id="vw-feld-kname">
|
||
<label for="vw-kname">Name<span class="wd-pflicht">*</span></label>
|
||
<input type="text" id="vw-kname" autocomplete="off" />
|
||
<p class="wd-feld-fehler"></p>
|
||
</div>
|
||
<div class="wd-feld" id="vw-feld-kmail">
|
||
<label for="vw-kmail">E-Mail<span class="wd-pflicht">*</span></label>
|
||
<input type="email" id="vw-kmail" autocomplete="off" inputmode="email" />
|
||
<p class="wd-feld-fehler"></p>
|
||
</div>
|
||
<div class="wd-feld">
|
||
<label for="vw-kfirma">Firma <span class="wd-leise">optional</span></label>
|
||
<input type="text" id="vw-kfirma" autocomplete="off" />
|
||
</div>
|
||
<div class="wd-feld">
|
||
<label for="vw-ksprache">Sprache</label>
|
||
<select id="vw-ksprache">
|
||
<option value="de">Deutsch</option>
|
||
<option value="de-CH">Schwiizerdütsch</option>
|
||
<option value="en">English</option>
|
||
<option value="fr">Français</option>
|
||
<option value="pt">Português</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<label class="wd-wahl-kachel" style="display:block;margin-bottom:.9rem">
|
||
<input type="checkbox" id="vw-kfrei" checked />
|
||
<span class="wd-wahl-inhalt">
|
||
<span class="wd-wahl-titel">Sofort freischalten</span>
|
||
<span class="wd-wahl-text">Ohne Häkchen wird der Zugang angelegt, kommt aber noch nicht hinein — für Kunden, deren Projekt erst später startet.</span>
|
||
</span>
|
||
</label>
|
||
<div class="wd-btn-reihe">
|
||
<button type="submit" class="wd-btn wd-btn--haupt">Zugang anlegen</button>
|
||
<button type="button" class="wd-btn wd-btn--zweit" id="vw-testzugang">Testzugang für mich</button>
|
||
</div>
|
||
</form>
|
||
<div id="vw-link-box" hidden style="margin-top:1.2rem"></div>
|
||
</div>
|
||
|
||
<h2 style="font-size:1.15rem">Bestehende Zugänge</h2>
|
||
<div id="vw-kunden-liste"><div class="vw-laedt">Wird geladen…</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
</main>
|
||
|
||
<!-- ============ DETAILANSICHT ============ -->
|
||
<div class="vw-detail" id="vw-detail" role="dialog" aria-modal="true" aria-label="Anfrage im Detail">
|
||
<div class="vw-detail-schatten" data-schliessen></div>
|
||
<div class="vw-detail-blatt" id="vw-detail-blatt"></div>
|
||
</div>
|
||
|
||
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
|
||
<script>
|
||
/* =====================================================================
|
||
Verwaltung: Anfragen ansehen, Status ändern, Notizen, Archivieren.
|
||
|
||
Anmeldung: bewusst über die BESTEHENDE Team-Anmeldung des internen
|
||
Bereichs (/auth/login) statt einer zweiten eigenen. Zwei getrennte
|
||
Anmeldungen für dieselben zwei Personen wären doppelte Pflege und ein
|
||
zweiter Ort, an dem ein Zugang vergessen werden kann.
|
||
|
||
Das Sitzungstoken liegt in sessionStorage, NICHT in localStorage:
|
||
Es soll beim Schließen verschwinden — dieselbe Regel wie an der
|
||
Zugangswand ("jedes Mal wieder fragen").
|
||
===================================================================== */
|
||
(function () {
|
||
"use strict";
|
||
|
||
var TOKEN_SCHLUESSEL = "wd-vw-token";
|
||
var token = null;
|
||
var alleAnfragen = [];
|
||
var filterStatus = "";
|
||
var filterArchiv = false;
|
||
var suchText = "";
|
||
|
||
var STATUS_TEXT = {
|
||
neu: "Neu", pruefung: "In Prüfung", rueckfrage: "Rückfrage",
|
||
angebot: "Angebot raus", angenommen: "Angenommen",
|
||
abgeschlossen: "Abgeschlossen", abgelehnt: "Abgelehnt",
|
||
};
|
||
var PAKET_TEXT = {
|
||
onepager: "Onepager", website: "Professionelle Website",
|
||
shop: "Shop & umfangreiche Website", verwaltung: "Buchhaltungs- & Steuerverwaltung",
|
||
betreuung: "Laufende Betreuung", unklar: "Noch unklar",
|
||
};
|
||
var VORHANDEN_TEXT = { ja: "vorhanden", teilweise: "teilweise", nein: "fehlt noch" };
|
||
var BUDGET_TEXT = {
|
||
"bis-500": "bis 500 €", "500-1000": "500 – 1.000 €",
|
||
"1000-2500": "1.000 – 2.500 €", "ueber-2500": "über 2.500 €", unklar: "noch unklar",
|
||
};
|
||
|
||
var s = window.WD.sicher;
|
||
var meldung = document.getElementById("vw-meldung");
|
||
|
||
function tokenLesen() {
|
||
try { return sessionStorage.getItem(TOKEN_SCHLUESSEL); } catch (e) { return null; }
|
||
}
|
||
function tokenSchreiben(t) {
|
||
try { if (t) sessionStorage.setItem(TOKEN_SCHLUESSEL, t); else sessionStorage.removeItem(TOKEN_SCHLUESSEL); } 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);
|
||
}
|
||
|
||
/* ---------------- Anmeldung ---------------- */
|
||
function zeigeAnmeldung() {
|
||
document.getElementById("vw-anmeldung").hidden = false;
|
||
document.getElementById("vw-bereich").hidden = true;
|
||
var feld = document.getElementById("vw-code");
|
||
if (feld) setTimeout(function () { feld.focus(); }, 100);
|
||
}
|
||
function zeigeBereich() {
|
||
document.getElementById("vw-anmeldung").hidden = true;
|
||
document.getElementById("vw-bereich").hidden = false;
|
||
}
|
||
|
||
document.getElementById("vw-login-form").addEventListener("submit", async function (e) {
|
||
e.preventDefault();
|
||
var feld = document.getElementById("vw-feld-code");
|
||
var eingabe = document.getElementById("vw-code");
|
||
var knopf = e.target.querySelector("button");
|
||
feld.classList.remove("wd-feld--fehler");
|
||
|
||
var code = eingabe.value.trim();
|
||
if (!code) {
|
||
feld.classList.add("wd-feld--fehler");
|
||
feld.querySelector(".wd-feld-fehler").textContent = "Bitte den Zugangscode eingeben.";
|
||
return;
|
||
}
|
||
|
||
window.WD.knopfSperren(knopf, true, "Wird geprüft…");
|
||
try {
|
||
var antwort = await window.WD.api("/auth/login", { method: "POST", body: { code: code } });
|
||
token = antwort.token;
|
||
tokenSchreiben(token);
|
||
document.getElementById("vw-name").textContent = antwort.name || antwort.actor || "angemeldet";
|
||
zeigeBereich();
|
||
ladeAnfragen();
|
||
} catch (fehler) {
|
||
feld.classList.add("wd-feld--fehler");
|
||
feld.querySelector(".wd-feld-fehler").textContent = fehler.message || "Anmeldung fehlgeschlagen.";
|
||
eingabe.select();
|
||
} finally {
|
||
window.WD.knopfSperren(knopf, false);
|
||
}
|
||
});
|
||
|
||
document.getElementById("vw-abmelden").addEventListener("click", async function () {
|
||
try { await api("/auth/logout", { method: "POST", body: {} }); } catch (e) { /* egal */ }
|
||
token = null; tokenSchreiben(null);
|
||
location.reload();
|
||
});
|
||
|
||
/* ---------------- Liste ---------------- */
|
||
async function ladeAnfragen() {
|
||
var liste = document.getElementById("vw-liste");
|
||
liste.innerHTML = '<div class="vw-laedt">Wird geladen…</div>';
|
||
try {
|
||
var antwort = await api("/webdesign/admin/anfragen?archiviert=" + (filterArchiv ? "1" : "0"));
|
||
alleAnfragen = antwort.anfragen || [];
|
||
zeichneZahlen(antwort.zaehler || {});
|
||
zeichneFilter(antwort.zaehler || {});
|
||
zeichneListe();
|
||
window.WD.meldungAus(meldung);
|
||
} catch (fehler) {
|
||
if (fehler.status === 401) {
|
||
/* Der Ausweis der Zugangswand laeuft nach 15 Minuten ab. Statt
|
||
jemanden mitten im Arbeiten rauszuwerfen, EINMAL still einen
|
||
neuen holen und es nochmal versuchen. Klappt auch das nicht,
|
||
ist die Zugangssitzung selbst weg -- dann zurueck zur
|
||
Anmeldung, nicht zu einer leeren Liste. */
|
||
if (!ausweisVersucht) {
|
||
ausweisVersucht = true;
|
||
var frisch = await ausweisHolen();
|
||
if (frisch) {
|
||
token = frisch; tokenSchreiben(token);
|
||
ausweisVersucht = false;
|
||
return ladeAnfragen();
|
||
}
|
||
}
|
||
token = null; tokenSchreiben(null); zeigeAnmeldung();
|
||
return;
|
||
}
|
||
liste.innerHTML = "";
|
||
window.WD.meldung(meldung, fehler.message || "Die Anfragen konnten nicht geladen werden.", "fehler");
|
||
}
|
||
}
|
||
|
||
function zeichneZahlen(zaehler) {
|
||
var offen = (zaehler.neu || 0) + (zaehler.rueckfrage || 0);
|
||
var gesamt = Object.keys(zaehler).reduce(function (n, k) { return n + zaehler[k]; }, 0);
|
||
var kacheln = [
|
||
{ wert: zaehler.neu || 0, name: "neu, ungelesen", achtung: (zaehler.neu || 0) > 0 },
|
||
{ wert: zaehler.pruefung || 0, name: "in Prüfung" },
|
||
{ wert: zaehler.angebot || 0, name: "Angebot raus" },
|
||
{ wert: zaehler.angenommen || 0, name: "angenommen" },
|
||
{ wert: gesamt, name: "insgesamt offen" },
|
||
];
|
||
document.getElementById("vw-zahlen").innerHTML = kacheln.map(function (k) {
|
||
return '<div class="vw-zahl' + (k.achtung ? " vw-zahl--achtung" : "") + '">' +
|
||
'<div class="vw-zahl-wert">' + k.wert + "</div>" +
|
||
'<div class="vw-zahl-name">' + s(k.name) + "</div></div>";
|
||
}).join("");
|
||
}
|
||
|
||
function zeichneFilter(zaehler) {
|
||
var knoepfe = [{ wert: "", name: "Alle" }].concat(
|
||
Object.keys(STATUS_TEXT).map(function (k) { return { wert: k, name: STATUS_TEXT[k] }; })
|
||
);
|
||
var html = knoepfe.map(function (k) {
|
||
var n = k.wert ? (zaehler[k.wert] || 0) : "";
|
||
return '<button type="button" class="vw-filter-knopf' + (filterStatus === k.wert ? " vw-an" : "") +
|
||
'" data-status="' + k.wert + '">' + s(k.name) +
|
||
(n !== "" ? '<span class="vw-filter-zahl">' + n + "</span>" : "") + "</button>";
|
||
}).join("");
|
||
html += '<button type="button" class="vw-filter-knopf' + (filterArchiv ? " vw-an" : "") +
|
||
'" data-archiv="1" style="margin-left:.6rem">Archiv</button>';
|
||
var box = document.getElementById("vw-filter");
|
||
box.innerHTML = html;
|
||
box.querySelectorAll("[data-status]").forEach(function (b) {
|
||
b.addEventListener("click", function () { filterStatus = b.dataset.status; zeichneFilter({}); ladeAnfragen(); });
|
||
});
|
||
box.querySelector("[data-archiv]").addEventListener("click", function () {
|
||
filterArchiv = !filterArchiv; filterStatus = ""; ladeAnfragen();
|
||
});
|
||
}
|
||
|
||
function zeichneListe() {
|
||
var liste = document.getElementById("vw-liste");
|
||
var gefiltert = alleAnfragen.filter(function (a) {
|
||
if (filterStatus && a.status !== filterStatus) return false;
|
||
if (!suchText) return true;
|
||
var t = suchText.toLowerCase();
|
||
return (a.name || "").toLowerCase().indexOf(t) >= 0 ||
|
||
(a.email || "").toLowerCase().indexOf(t) >= 0 ||
|
||
(a.nummer || "").toLowerCase().indexOf(t) >= 0;
|
||
});
|
||
|
||
if (!gefiltert.length) {
|
||
liste.innerHTML = '<div class="vw-leer-hinweis">' +
|
||
(alleAnfragen.length ? "Keine Anfrage passt zu diesem Filter." :
|
||
filterArchiv ? "Im Archiv liegt noch nichts." : "Noch keine Anfragen eingegangen.") + "</div>";
|
||
return;
|
||
}
|
||
|
||
liste.innerHTML = gefiltert.map(function (a) {
|
||
return '<button type="button" class="vw-karte" data-id="' + s(a.id) + '">' +
|
||
'<span class="vw-karte-nr">' + s(a.nummer) + "</span>" +
|
||
'<span class="vw-karte-mitte">' +
|
||
'<span class="vw-karte-name">' + s(a.name) + "</span><br />" +
|
||
'<span class="vw-karte-info">' + s(PAKET_TEXT[a.paket] || a.paket) +
|
||
(a.budget ? " · " + s(BUDGET_TEXT[a.budget] || a.budget) : "") + "</span>" +
|
||
"</span>" +
|
||
'<span class="vw-karte-rechts">' +
|
||
'<span class="vw-status vw-status--' + s(a.status) + '">' + s(STATUS_TEXT[a.status] || a.status) + "</span>" +
|
||
'<span class="vw-karte-zeit">' + window.WD.datum(a.erstellt_am) + "</span>" +
|
||
"</span></button>";
|
||
}).join("");
|
||
|
||
liste.querySelectorAll("[data-id]").forEach(function (k) {
|
||
k.addEventListener("click", function () { oeffneDetail(k.dataset.id); });
|
||
});
|
||
}
|
||
|
||
document.getElementById("vw-suche").addEventListener("input", function (e) {
|
||
suchText = e.target.value.trim();
|
||
zeichneListe();
|
||
});
|
||
|
||
/* ---------------- Detail ---------------- */
|
||
var detail = document.getElementById("vw-detail");
|
||
var blatt = document.getElementById("vw-detail-blatt");
|
||
var offeneId = null;
|
||
|
||
function schliesseDetail() {
|
||
detail.classList.remove("vw-auf");
|
||
offeneId = null;
|
||
document.body.style.overflow = "";
|
||
}
|
||
detail.querySelector("[data-schliessen]").addEventListener("click", schliesseDetail);
|
||
document.addEventListener("keydown", function (e) {
|
||
if (e.key === "Escape" && detail.classList.contains("vw-auf")) schliesseDetail();
|
||
});
|
||
|
||
function paar(was, wert, roh) {
|
||
var leer = !wert;
|
||
return '<div class="vw-paar"><dt>' + s(was) + "</dt>" +
|
||
'<dd' + (leer ? ' class="vw-leer"' : "") + ">" +
|
||
(leer ? "keine Angabe" : s(roh ? roh : wert)) + "</dd></div>";
|
||
}
|
||
|
||
async function oeffneDetail(id) {
|
||
offeneId = id;
|
||
detail.classList.add("vw-auf");
|
||
document.body.style.overflow = "hidden";
|
||
blatt.innerHTML = '<div class="vw-laedt">Wird geladen…</div>';
|
||
|
||
try {
|
||
var antwort = await api("/webdesign/admin/anfragen/" + encodeURIComponent(id));
|
||
var a = antwort.anfrage;
|
||
var notizen = antwort.notizen || [];
|
||
|
||
var funktionen = (a.funktionen || []).join(", ");
|
||
var details = Object.keys(a.details || {}).map(function (k) {
|
||
return paar(k.replace(/_/g, " "), a.details[k]);
|
||
}).join("");
|
||
|
||
blatt.innerHTML =
|
||
'<div class="vw-detail-kopf">' +
|
||
"<div><div class=\"vw-karte-nr\" style=\"font-size:.9rem\">" + s(a.nummer) + "</div>" +
|
||
'<h2 style="margin:.2rem 0 .3rem;font-size:1.4rem">' + s(a.name) + "</h2>" +
|
||
'<span class="vw-status vw-status--' + s(a.status) + '">' + s(STATUS_TEXT[a.status] || a.status) + "</span></div>" +
|
||
'<button class="vw-zu" type="button" aria-label="Schließen">✕</button>' +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Kontakt</h3>' +
|
||
paar("E-Mail", a.email) +
|
||
paar("Telefon", a.telefon) +
|
||
paar("Bevorzugt", a.kontaktweg === "telefon" ? "Telefon" : "E-Mail") +
|
||
paar("Sprache", a.sprache) +
|
||
'<div class="wd-btn-reihe" style="margin-top:.7rem">' +
|
||
'<a class="wd-btn wd-btn--zweit wd-btn--klein" href="mailto:' + s(a.email) +
|
||
"?subject=" + encodeURIComponent("Deine Anfrage " + a.nummer + " bei Dogfather Webdesign") +
|
||
'">Antwort schreiben</a></div>' +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Vorhaben</h3>' +
|
||
paar("Paket", PAKET_TEXT[a.paket] || a.paket) +
|
||
paar("Ziel", a.ziel) +
|
||
paar("Zielgruppe", a.zielgruppe) +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Umfang</h3>' +
|
||
paar("Bereiche", a.seiten) +
|
||
paar("Funktionen", funktionen) +
|
||
details +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Vorhandene Inhalte</h3>' +
|
||
paar("Texte", VORHANDEN_TEXT[a.hat_texte] || a.hat_texte) +
|
||
paar("Bilder", VORHANDEN_TEXT[a.hat_bilder] || a.hat_bilder) +
|
||
paar("Logo", VORHANDEN_TEXT[a.hat_logo] || a.hat_logo) +
|
||
paar("Domain", VORHANDEN_TEXT[a.hat_domain] || a.hat_domain) +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Rahmen</h3>' +
|
||
paar("Budget", BUDGET_TEXT[a.budget] || a.budget) +
|
||
paar("Wunschtermin", a.termin) +
|
||
paar("Eingegangen", window.WD.datum(a.erstellt_am)) +
|
||
"</div>" +
|
||
|
||
(a.bemerkung ? '<div class="vw-abschnitt"><h3>Bemerkung</h3>' + paar("", a.bemerkung) + "</div>" : "") +
|
||
|
||
'<div class="vw-abschnitt"><h3>Status ändern</h3>' +
|
||
'<div class="vw-status-wahl">' +
|
||
Object.keys(STATUS_TEXT).map(function (k) {
|
||
return '<button type="button" class="vw-filter-knopf' + (a.status === k ? " vw-an" : "") +
|
||
'" data-neuer-status="' + k + '">' + s(STATUS_TEXT[k]) + "</button>";
|
||
}).join("") + "</div>" +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Interne Notizen (' + notizen.length + ")</h3>" +
|
||
'<div id="vw-notizen">' +
|
||
(notizen.length ? notizen.map(function (n) {
|
||
return '<div class="vw-notiz"><div class="vw-notiz-kopf">' + s(n.autor) + " · " +
|
||
window.WD.datum(n.erstellt_am) + '</div><p>' + s(n.text) + "</p></div>";
|
||
}).join("") : '<p class="wd-klein wd-leise">Noch keine Notiz.</p>') +
|
||
"</div>" +
|
||
'<div class="wd-feld" style="margin-top:.7rem;margin-bottom:.5rem">' +
|
||
'<textarea id="vw-neue-notiz" rows="2" placeholder="Notiz hinzufügen (nur intern sichtbar)"></textarea>' +
|
||
"</div>" +
|
||
'<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="vw-notiz-speichern">Notiz speichern</button>' +
|
||
"</div>" +
|
||
|
||
'<div class="vw-abschnitt"><h3>Aufräumen</h3>' +
|
||
'<p class="wd-klein wd-leise">Archivieren löscht nichts — die Anfrage bleibt vollständig erhalten und lässt sich jederzeit zurückholen.</p>' +
|
||
'<button type="button" class="wd-btn wd-btn--still wd-btn--klein" id="vw-archivieren">' +
|
||
(a.archiviert ? "Aus dem Archiv zurückholen" : "Archivieren") + "</button>" +
|
||
"</div>";
|
||
|
||
blatt.querySelector(".vw-zu").addEventListener("click", schliesseDetail);
|
||
|
||
blatt.querySelectorAll("[data-neuer-status]").forEach(function (b) {
|
||
b.addEventListener("click", async function () {
|
||
window.WD.knopfSperren(b, true, "…");
|
||
try {
|
||
await api("/webdesign/admin/anfragen/" + encodeURIComponent(id) + "/status",
|
||
{ method: "POST", body: { status: b.dataset.neuerStatus } });
|
||
await ladeAnfragen();
|
||
oeffneDetail(id);
|
||
} catch (f) {
|
||
window.WD.knopfSperren(b, false);
|
||
alert(f.message || "Status konnte nicht geändert werden.");
|
||
}
|
||
});
|
||
});
|
||
|
||
blatt.querySelector("#vw-notiz-speichern").addEventListener("click", async function (e) {
|
||
var feld = blatt.querySelector("#vw-neue-notiz");
|
||
var text = feld.value.trim();
|
||
if (!text) { feld.focus(); return; }
|
||
window.WD.knopfSperren(e.target, true, "Wird gespeichert…");
|
||
try {
|
||
await api("/webdesign/admin/anfragen/" + encodeURIComponent(id) + "/notiz",
|
||
{ method: "POST", body: { text: text } });
|
||
oeffneDetail(id);
|
||
} catch (f) {
|
||
window.WD.knopfSperren(e.target, false);
|
||
alert(f.message || "Notiz konnte nicht gespeichert werden.");
|
||
}
|
||
});
|
||
|
||
blatt.querySelector("#vw-archivieren").addEventListener("click", async function (e) {
|
||
window.WD.knopfSperren(e.target, true, "…");
|
||
try {
|
||
await api("/webdesign/admin/anfragen/" + encodeURIComponent(id) + "/archivieren",
|
||
{ method: "POST", body: { zurueck: !!a.archiviert } });
|
||
schliesseDetail();
|
||
ladeAnfragen();
|
||
} catch (f) {
|
||
window.WD.knopfSperren(e.target, false);
|
||
alert(f.message || "Konnte nicht archiviert werden.");
|
||
}
|
||
});
|
||
|
||
} catch (fehler) {
|
||
blatt.innerHTML = '<div class="vw-leer-hinweis">' + s(fehler.message || "Konnte nicht geladen werden.") +
|
||
'<br /><br /><button class="wd-btn wd-btn--still wd-btn--klein" type="button" onclick="this.closest(\'.vw-detail\').classList.remove(\'vw-auf\')">Schließen</button></div>';
|
||
}
|
||
}
|
||
|
||
|
||
/* =================================================================
|
||
KUNDEN
|
||
================================================================= */
|
||
var aktiverTab = "anfragen";
|
||
|
||
document.querySelectorAll("[data-tab]").forEach(function (b) {
|
||
b.addEventListener("click", function () {
|
||
aktiverTab = b.dataset.tab;
|
||
document.querySelectorAll("[data-tab]").forEach(function (x) {
|
||
x.classList.toggle("vw-an", x.dataset.tab === aktiverTab);
|
||
});
|
||
var istAnfragen = aktiverTab === "anfragen";
|
||
document.getElementById("vw-titel").textContent = istAnfragen ? "Projektanfragen" : "Kunden";
|
||
document.getElementById("vw-zahlen").hidden = !istAnfragen;
|
||
document.getElementById("vw-filter").hidden = !istAnfragen;
|
||
document.getElementById("vw-suche").closest(".wd-feld").hidden = !istAnfragen;
|
||
document.getElementById("vw-liste").hidden = !istAnfragen;
|
||
document.getElementById("vw-kunden-bereich").hidden = istAnfragen;
|
||
if (!istAnfragen) ladeKunden();
|
||
});
|
||
});
|
||
|
||
async function ladeKunden() {
|
||
var ziel = document.getElementById("vw-kunden-liste");
|
||
ziel.innerHTML = '<div class="vw-laedt">Wird geladen…</div>';
|
||
try {
|
||
var d = await api("/webdesign/admin/kunden");
|
||
if (!d.kunden.length) {
|
||
ziel.innerHTML = '<div class="vw-leer-hinweis">Noch keine Kundenzugaenge angelegt.</div>';
|
||
return;
|
||
}
|
||
ziel.innerHTML = d.kunden.map(function (k) {
|
||
/* Der Zustand eines Zugangs hat vier Stufen, und alle muessen auf
|
||
einen Blick unterscheidbar sein. Besonders wichtig ist
|
||
"freigeschaltet, aber noch kein Passwort gesetzt": das heisst,
|
||
die Einladung liegt noch offen und der Kunde war noch nie drin. */
|
||
var marke = k.gesperrt
|
||
? '<span class="vw-status vw-status--abgelehnt">gesperrt</span>'
|
||
: !k.freigeschaltet
|
||
? '<span class="vw-status vw-status--neu">nicht freigeschaltet</span>'
|
||
: !k.hat_passwort
|
||
? '<span class="vw-status vw-status--rueckfrage">Einladung offen</span>'
|
||
: '<span class="vw-status vw-status--angenommen">aktiv</span>';
|
||
return '<div class="vw-karte" style="cursor:default">' +
|
||
'<span class="vw-karte-nr">' + (k.projekte || 0) + ' Proj.</span>' +
|
||
'<span class="vw-karte-mitte">' +
|
||
'<span class="vw-karte-name">' + s(k.name) + (k.firma ? " · " + s(k.firma) : "") + "</span><br />" +
|
||
'<span class="vw-karte-info">' + s(k.email) + "</span>" +
|
||
"</span>" +
|
||
'<span class="vw-karte-rechts">' + marke +
|
||
'<span class="vw-karte-zeit">' +
|
||
(k.letzter_login ? "zuletzt " + window.WD.datum(k.letzter_login) : "noch nie angemeldet") +
|
||
"</span>" +
|
||
'<span style="display:block;margin-top:.5rem">' +
|
||
'<button type="button" class="wd-btn wd-btn--still wd-btn--klein" data-neuer-link="' + s(k.id) + '">Neuer Link</button> ' +
|
||
'<button type="button" class="wd-btn wd-btn--still wd-btn--klein" data-schalten="' + s(k.id) +
|
||
'" data-frei="' + (k.freigeschaltet ? "0" : "1") + '">' +
|
||
(k.freigeschaltet ? "Sperren" : "Freischalten") + "</button>" +
|
||
"</span>" +
|
||
"</span></div>";
|
||
}).join("");
|
||
|
||
ziel.querySelectorAll("[data-neuer-link]").forEach(function (b) {
|
||
b.addEventListener("click", async function () {
|
||
window.WD.knopfSperren(b, true, "…");
|
||
try {
|
||
var r = await api("/webdesign/admin/kunden/" + encodeURIComponent(b.dataset.neuerLink) + "/einladung",
|
||
{ method: "POST", body: {} });
|
||
zeigeLink(r.einladungsLink);
|
||
} catch (f) { window.WD.meldung(meldung, f.message, "fehler"); }
|
||
finally { window.WD.knopfSperren(b, false); }
|
||
});
|
||
});
|
||
|
||
ziel.querySelectorAll("[data-schalten]").forEach(function (b) {
|
||
b.addEventListener("click", async function () {
|
||
var frei = b.dataset.frei === "1";
|
||
window.WD.knopfSperren(b, true, "…");
|
||
try {
|
||
await api("/webdesign/admin/kunden/" + encodeURIComponent(b.dataset.schalten) + "/zugang",
|
||
{ method: "POST", body: { freigeschaltet: frei } });
|
||
ladeKunden();
|
||
} catch (f) {
|
||
window.WD.knopfSperren(b, false);
|
||
window.WD.meldung(meldung, f.message, "fehler");
|
||
}
|
||
});
|
||
});
|
||
} catch (fehler) {
|
||
if (fehler.status === 401) { token = null; tokenSchreiben(null); zeigeAnmeldung(); return; }
|
||
ziel.innerHTML = "";
|
||
window.WD.meldung(meldung, fehler.message, "fehler");
|
||
}
|
||
}
|
||
|
||
/* Der Link wird EINMAL angezeigt. Deshalb gross, kopierbar und mit einer
|
||
klaren Ansage, dass er nicht wiederkommt -- sonst klickt man ihn weg
|
||
und wundert sich. */
|
||
function zeigeLink(link) {
|
||
var box = document.getElementById("vw-link-box");
|
||
box.hidden = false;
|
||
box.innerHTML = '<div class="vw-link-anzeige">' +
|
||
'<p class="wd-hinweis-titel">Einladungslink — wird nur dieses eine Mal angezeigt</p>' +
|
||
"<code>" + s(link) + "</code>" +
|
||
'<div class="wd-btn-reihe">' +
|
||
'<button type="button" class="wd-btn wd-btn--haupt wd-btn--klein" id="vw-link-kopieren">Link kopieren</button>' +
|
||
'<a class="wd-btn wd-btn--zweit wd-btn--klein" href="' + s(link) + '" target="_blank" rel="noopener">Selbst oeffnen</a>' +
|
||
"</div>" +
|
||
'<p class="wd-klein wd-leise" style="margin:.6rem 0 0">Gueltig 14 Tage. Danach oder bei Verlust einfach einen neuen erzeugen.</p>' +
|
||
"</div>";
|
||
box.scrollIntoView({ behavior: "smooth", block: "center" });
|
||
|
||
document.getElementById("vw-link-kopieren").addEventListener("click", async function (e) {
|
||
try {
|
||
await navigator.clipboard.writeText(link);
|
||
e.target.textContent = "Kopiert ✓";
|
||
} catch (f) {
|
||
/* Die Zwischenablage kann blockiert sein (aelterer Browser, kein
|
||
HTTPS, verweigerte Berechtigung). Dann den Text markieren statt
|
||
einer Fehlermeldung, mit der niemand etwas anfangen kann. */
|
||
var bereich = document.createRange();
|
||
bereich.selectNodeContents(box.querySelector("code"));
|
||
var auswahl = getSelection();
|
||
auswahl.removeAllRanges();
|
||
auswahl.addRange(bereich);
|
||
e.target.textContent = "markiert — jetzt kopieren";
|
||
}
|
||
});
|
||
}
|
||
|
||
document.getElementById("vw-kunde-form").addEventListener("submit", async function (e) {
|
||
e.preventDefault();
|
||
await legeKundeAn({
|
||
name: document.getElementById("vw-kname").value.trim(),
|
||
email: document.getElementById("vw-kmail").value.trim(),
|
||
firma: document.getElementById("vw-kfirma").value.trim(),
|
||
sprache: document.getElementById("vw-ksprache").value,
|
||
freischalten: document.getElementById("vw-kfrei").checked,
|
||
}, e.target.querySelector('button[type="submit"]'));
|
||
});
|
||
|
||
/* "Testzugang fuer mich": legt einen Zugang mit einer eindeutig als Test
|
||
erkennbaren Adresse an. Bewusst NICHT die echte Geschaeftsadresse --
|
||
sonst kollidiert der Test spaeter mit einem echten Kundenkonto, und
|
||
die E-Mail-Adresse laesst sich aus gutem Grund nicht mehr aendern. */
|
||
document.getElementById("vw-testzugang").addEventListener("click", async function (e) {
|
||
var stempel = new Date().toISOString().slice(0, 10).replace(/-/g, "");
|
||
await legeKundeAn({
|
||
name: "Testzugang (" + stempel + ")",
|
||
email: "test+" + stempel + "@dogfather-universe.com",
|
||
firma: "Eigener Test",
|
||
sprache: window.WD.getSprache(),
|
||
freischalten: true,
|
||
}, e.target);
|
||
});
|
||
|
||
async function legeKundeAn(daten, knopf) {
|
||
["vw-feld-kname", "vw-feld-kmail"].forEach(function (id) {
|
||
document.getElementById(id).classList.remove("wd-feld--fehler");
|
||
});
|
||
window.WD.meldungAus(meldung);
|
||
window.WD.knopfSperren(knopf, true, "Wird angelegt…");
|
||
try {
|
||
var r = await api("/webdesign/admin/kunden", { method: "POST", body: daten });
|
||
zeigeLink(r.einladungsLink);
|
||
document.getElementById("vw-kunde-form").reset();
|
||
document.getElementById("vw-kfrei").checked = true;
|
||
ladeKunden();
|
||
} catch (fehler) {
|
||
window.WD.meldung(meldung, fehler.message, "fehler");
|
||
} finally {
|
||
window.WD.knopfSperren(knopf, false);
|
||
}
|
||
}
|
||
|
||
/* ---------------- Start ----------------
|
||
|
||
Reihenfolge bewusst so:
|
||
|
||
1. Ein bereits geholter Ausweis im Sitzungsspeicher.
|
||
2. Sonst: einen neuen bei der Zugangswand holen. Die hat serverseitig
|
||
laengst geprueft, wer hier ist -- ein zweites Passwort danach
|
||
bringt keinen zusaetzlichen Schutz und kostet nur Zeit
|
||
(Rueckmeldung 22.08.2026).
|
||
3. Erst wenn das nicht klappt (Ausweis nicht eingerichtet, Sitzung
|
||
abgelaufen), die eigene Anmeldemaske.
|
||
|
||
Der Ausweis laeuft nach 15 Minuten ab. Deshalb wird bei jedem 401
|
||
einmal stillschweigend ein neuer geholt und die Anfrage wiederholt --
|
||
sonst fliegt man mitten im Arbeiten raus. */
|
||
var ausweisVersucht = false;
|
||
|
||
async function ausweisHolen() {
|
||
try {
|
||
var antwort = await fetch("/webdesign/api-ausweis", { cache: "no-store" });
|
||
if (!antwort.ok) return null;
|
||
var daten = await antwort.json();
|
||
return daten && daten.ausweis ? daten.ausweis : null;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
async function start() {
|
||
token = tokenLesen();
|
||
if (token) { zeigeBereich(); ladeAnfragen(); return; }
|
||
|
||
var ausweis = await ausweisHolen();
|
||
if (ausweis) {
|
||
token = ausweis;
|
||
tokenSchreiben(token);
|
||
document.getElementById("vw-name").textContent =
|
||
(document.cookie.match(/dogiwd_rolle=([^;]+)/) || [])[1] === "vanvan" ? "VanVan" : "Dogfather";
|
||
zeigeBereich();
|
||
ladeAnfragen();
|
||
return;
|
||
}
|
||
zeigeAnmeldung();
|
||
}
|
||
|
||
if (window.WD) start();
|
||
else document.addEventListener("wd-bereit", start);
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|