Terminplaner TP2: Kachel mit Bestellweg, Abo-Felder in der Funktionen-Liste
GET /api/funktionen liefert bei termine.verfuegbar: true zusaetzlich bestellbar
(true | false | null bei Lesefehler im Abo), grund (kein_abo | gekuendigt |
nicht_aktiv | null), preis und einrichtung aus dem Register; nicht verfuegbar
bleibt exakt { verfuegbar: false }. Die Kachel zeigt je Zustand Pille und Knopf:
Aus mit Abo "Terminplaner einrichten · 149 €" bzw. "Fragebogen fortsetzen",
Aus ohne Abo keinen Knopf, sondern den Hinweis zum Abo, Zahlung offen und In
Einrichtung "Status ansehen", Aktiv "Termine oeffnen", Abgeschaltet "Termine
ansehen". Zielprojekt: aktiv, ordered/setup, abgeschaltet, Entwurf, erstes.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d628ef906
commit
6ae0f67457
5 files changed
+364
-43
No files matched your search
+55
-17
@@ -151,34 +151,72 @@ function analyseTile(projects) {
|
||||
}
|
||||
|
||||
/* Terminplaner: Kachel nur, wenn der Schalter es erlaubt (termine.verfuegbar), sonst "Kommt bald". Die Ansicht selbst
|
||||
(termine.js) wird von app.js geladen; hier steht nur die Kachel, damit funktionen.js keinen weiteren Import braucht. */
|
||||
(termine.js) wird von app.js geladen; hier steht nur die Kachel, damit funktionen.js keinen weiteren Import braucht.
|
||||
termine (Antwort von /api/funktionen) traegt bei verfuegbar: true bestellbar (true | false | null = Abo nicht lesbar),
|
||||
grund (kein_abo | gekuendigt | nicht_aktiv | null), preis und einrichtung. Das Abo betrifft nur die Bestellung: ohne laufendes
|
||||
Abo gibt es im Zustand Aus keinen Weg in die Ansicht; wer schon bestellt hat oder den Terminplaner nutzt, behaelt seinen. */
|
||||
const TERMINE_TEXT = 'Deine Kundschaft bucht Termine direkt über deine Website.'
|
||||
const termineOf = (project) => project.features?.termine || { state: 'off' }
|
||||
// Doppelt zu anfragenText in termine-view.js: funktionen.js darf dort nichts importieren (die Kachel testet test/funktionenTiles.test.js
|
||||
// mit einem umgebogenen Import). Aenderungen an beiden Stellen machen.
|
||||
const anfragenText = (n) => (n === 1 ? '1 Anfrage wartet' : `${n} Anfragen warten`)
|
||||
|
||||
function pickTermineProject(projects) {
|
||||
return projects.find((p) => termineOf(p).state === 'active') || projects.find((p) => termineOf(p).state === 'disabled') || projects[0]
|
||||
// Abo-Hinweise der Kachel. Kopie von aboMeldung (server/services/featureOrder.js, einzige Quelle der Abo-Hinweistexte) fuer den
|
||||
// Terminplaner; test/funktionenTiles.test.js haelt beide gleich. kein_abo steht wie bei der Analyse im Wortlaut der Kachel.
|
||||
const TERMINE_ABO_TEXTE = {
|
||||
gekuendigt: 'Dein Wartungs-Abo ist gekündigt. Für den Terminplaner brauchst du ein laufendes Abo.',
|
||||
nicht_aktiv: 'Dein Wartungs-Abo ist gerade nicht aktiv (zum Beispiel wegen einer offenen Zahlung). Sobald es wieder aktiv ist, kannst du den Terminplaner bestellen.',
|
||||
unbekannt: 'Das Abo konnte gerade nicht geprüft werden.',
|
||||
}
|
||||
const KEIN_ABO_HINWEIS = 'Im Wartungs-Abo enthalten. <button type="button" class="kb-link" data-view="vertraege">Zu Verträge</button>'
|
||||
|
||||
function termineAboHinweis(angebot) {
|
||||
if (angebot.bestellbar !== false) return TERMINE_ABO_TEXTE.unbekannt // null (Lesefehler) oder Feld fehlt: nicht geprueft
|
||||
if (angebot.grund === 'kein_abo') return KEIN_ABO_HINWEIS
|
||||
return Object.hasOwn(TERMINE_ABO_TEXTE, angebot.grund) && angebot.grund !== 'unbekannt' ? TERMINE_ABO_TEXTE[angebot.grund] : TERMINE_ABO_TEXTE.unbekannt
|
||||
}
|
||||
|
||||
function termineTile(projects) {
|
||||
const states = projects.map((p) => termineOf(p).state)
|
||||
const active = states.filter((s) => s === 'active').length
|
||||
// Entwurf: Funktionsdokument im Zustand Aus mit siteKey (entsteht beim ersten Speichern des Fragebogens)
|
||||
const hatEntwurf = (project) => termineOf(project).state === 'off' && Boolean(termineOf(project).siteKey)
|
||||
|
||||
// Zielprojekt: aktiv, dann ordered/setup, dann abgeschaltet, dann Entwurf, dann das erste
|
||||
function pickTermineProject(projects) {
|
||||
return projects.find((p) => termineOf(p).state === 'active')
|
||||
|| projects.find((p) => isSetup(termineOf(p).state))
|
||||
|| projects.find((p) => termineOf(p).state === 'disabled')
|
||||
|| projects.find(hatEntwurf)
|
||||
|| projects[0]
|
||||
}
|
||||
|
||||
function termineTile(projects, angebot = {}) {
|
||||
const active = projects.filter((p) => termineOf(p).state === 'active').length
|
||||
const target = pickTermineProject(projects)
|
||||
const disabled = !active && states.length > 0 && states.every((s) => s === 'disabled')
|
||||
const pill = active ? ['Aktiv', 'on'] : disabled ? ['Abgeschaltet', 'off'] : ['Aus', 'off']
|
||||
let extra
|
||||
let action
|
||||
if (active) {
|
||||
const state = target ? termineOf(target).state : 'off'
|
||||
const knopf = (text) => `<button type="button" class="kb-btn-primary fn-tile-cta" data-view="termine" data-project="${esc(target.projectId)}">${text}</button>`
|
||||
let pill = ['Aus', 'off']
|
||||
let extra = ''
|
||||
let action = ''
|
||||
if (state === 'active') {
|
||||
pill = ['Aktiv', 'on']
|
||||
extra = '<p class="fn-count" data-termine-count hidden></p>'
|
||||
action = `<button type="button" class="kb-btn-primary fn-tile-cta" data-view="termine" data-project="${esc(target.projectId)}">Termine öffnen</button>`
|
||||
} else if (disabled) {
|
||||
action = knopf('Termine öffnen')
|
||||
} else if (state === 'ordered') {
|
||||
pill = ['Zahlung offen', 'setup']
|
||||
action = knopf('Status ansehen')
|
||||
} else if (state === 'setup') {
|
||||
pill = ['In Einrichtung', 'setup']
|
||||
action = knopf('Status ansehen')
|
||||
} else if (state === 'disabled') {
|
||||
pill = ['Abgeschaltet', 'off']
|
||||
extra = '<p>Der Terminplaner ist für diese Website abgeschaltet. Deine Termine kannst du noch ansehen und exportieren.</p>'
|
||||
action = `<button type="button" class="kb-btn-primary fn-tile-cta" data-view="termine" data-project="${esc(target.projectId)}">Termine ansehen</button>`
|
||||
action = knopf('Termine ansehen')
|
||||
} else if (!target) {
|
||||
extra = '<p class="fn-count">Sobald deine Website angelegt ist, kannst du den Terminplaner einrichten.</p>'
|
||||
} else if (angebot.bestellbar === true) {
|
||||
const preis = typeof angebot.preis === 'string' && angebot.preis.trim() ? ` · ${esc(angebot.preis)}` : ''
|
||||
action = knopf(hatEntwurf(target) ? 'Fragebogen fortsetzen' : `Terminplaner einrichten${preis}`)
|
||||
} else {
|
||||
extra = '<p>Wir schalten den Terminplaner für dich frei.</p>'
|
||||
action = '<button type="button" class="kb-btn-primary fn-tile-cta" data-view="chat">Chat öffnen</button>'
|
||||
extra = `<p class="fn-count">${termineAboHinweis(angebot)}</p>`
|
||||
}
|
||||
const count = projects.length > 1 && active ? `<p class="fn-count">aktiv für ${active} von ${projects.length} Websites</p>` : ''
|
||||
return `
|
||||
@@ -217,7 +255,7 @@ export async function initFunktionen(root) {
|
||||
root.innerHTML = `
|
||||
<div class="fn-tiles">
|
||||
${newsTile(projects, newsOffer(features).price)}
|
||||
${termine?.verfuegbar ? termineTile(projects) : soonTile('calendar', 'Terminplaner', TERMINE_TEXT)}
|
||||
${termine?.verfuegbar ? termineTile(projects, termine) : soonTile('calendar', 'Terminplaner', TERMINE_TEXT)}
|
||||
${analyseTile(projects)}
|
||||
</div>`
|
||||
root.querySelector('[data-fn-tile="news"] .fn-tile-cta')?.addEventListener('click', (ev) => { selection.projectId = ev.currentTarget.dataset.project })
|
||||
|
||||
Reference in new issue
Block a user