Terminplaner: Portal-Ansicht, Kachel, Termine

Kachel in Funktionen (Kommt bald / Aus / Aktiv mit Anfragen-Zahl / Abgeschaltet),
Ansicht #termine als Unterseite von Funktionen: Projektauswahl, Vorlagenauswahl
beim ersten Oeffnen, Reiter-Geruest (Platzhalter fuer Leistungen, Team & Plaetze,
Einstellungen) und der Reiter Termine mit Tag/Woche/Monat, Filtern, Aktionen mit
Rueckfragen in der Seite, Termin eintragen und Zeit sperren.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:44 +00:00
1 parent 70940e02c8
commit 43e4826ae3
10 files changed
+2552 -8

No files matched your search

+60 -2
View File
@@ -136,6 +136,58 @@ function analyseTile(projects) {
</article>`
}
/* 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. */
const TERMINE_TEXT = 'Deine Kundschaft bucht Termine direkt über deine Website.'
const termineOf = (project) => project.features?.termine || { state: 'off' }
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]
}
function termineTile(projects) {
const states = projects.map((p) => termineOf(p).state)
const active = states.filter((s) => s === '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) {
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) {
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>`
} 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>'
}
const count = projects.length > 1 && active ? `<p class="fn-count">aktiv für ${active} von ${projects.length} Websites</p>` : ''
return `
<article class="fn-tile" data-fn-tile="termine">
<div class="fn-tile-head">${svg('calendar')}<span class="fn-pill fn-pill-${pill[1]}">${pill[0]}</span></div>
<h3>Terminplaner</h3>
<p>${TERMINE_TEXT}</p>
${extra}
${count}
${action}
</article>`
}
// "{n} Anfragen warten" aus anfragenOffen der aktiven Websites; ein Fehler laesst die Kachel einfach ohne Zahl stehen
async function showTermineCount(root, projects) {
const el = root.querySelector('[data-termine-count]')
const active = projects.filter((p) => termineOf(p).state === 'active')
if (!el || !active.length) return
const counts = await Promise.all(active.map((p) => request(`/api/termine/einstellungen?projectId=${encodeURIComponent(p.projectId)}`).then((d) => Number(d.anfragenOffen) || 0, () => 0)))
const open = counts.reduce((sum, n) => sum + n, 0)
if (open < 1) return
el.textContent = anfragenText(open)
el.hidden = false
}
const soonTile = (icon, title, text) => `
<article class="fn-tile fn-tile-soon">
<div class="fn-tile-head">${svg(icon)}<span class="fn-pill fn-pill-soon">Kommt bald</span></div>
@@ -145,15 +197,21 @@ const soonTile = (icon, title, text) => `
export async function initFunktionen(root) {
if (!root) return
const { projects = [] } = await request('/api/funktionen')
const { projects = [], termine } = await request('/api/funktionen')
root.innerHTML = `
<div class="fn-tiles">
${newsTile(projects)}
${soonTile('calendar', 'Terminplaner', 'Deine Kundschaft bucht Termine direkt über deine Website.')}
${termine?.verfuegbar ? termineTile(projects) : 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 })
root.querySelector('[data-fn-tile="analyse"] .fn-tile-cta[data-project]')?.addEventListener('click', (ev) => { analyseSel.projectId = ev.currentTarget.dataset.project })
// Die Terminplaner-Ansicht liest das gewaehlte Projekt aus ihrem Behaelter (termine.js, initTermine)
root.querySelector('[data-fn-tile="termine"] .fn-tile-cta[data-project]')?.addEventListener('click', (ev) => {
const body = document.getElementById('termine-body')
if (body) body.dataset.wunschProjekt = ev.currentTarget.dataset.project
})
if (termine?.verfuegbar) await showTermineCount(root, projects)
}
/* ---------------------------------------------------------------- News --- */