Terminplaner: Portal-Ansicht Leistungen, Team, Einstellungen

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 650919b022
commit 0b75b31009
15 files changed
+2479 -79

No files matched your search

+33 -24
View File
@@ -1,15 +1,19 @@
/* Terminplaner im Kundenbereich (ES-Modul; app.js laedt es erst beim Oeffnen der Ansicht #termine). Das ist die Huelle:
initTermine(rootEl, { projectId }): Projektauswahl, Vorlagenauswahl beim ersten Oeffnen, Reiter-Geruest und die Meldung ueber den Reitern.
Die Reiter stehen in REITER: jeder ist ein Modul mit render(panel, ctx) => void | Promise (Termine: termine-tab-termine.js; Leistungen,
Team & Plaetze und Einstellungen (Aufgabe 21b) ersetzen dort nur ihren Platzhalter).
ctx = { s, projectId, daten, nurLesen, auswahl, aktuell(), api, meldung, zumReiter, neuLaden }: Zustand und Pruefung "bin ich noch
aktuell?" bekommen die Reiter nur ueber ctx, nie ueber Modulvariablen. daten ist die Antwort von GET /api/termine/einstellungen,
nurLesen gilt fuer abgeschaltete Funktionen. Server: server/routes/termine.js. Tage sind Berliner Kalendertage 'JJJJ-MM-TT';
Die Reiter stehen in REITER: jeder ist ein Modul mit render(panel, ctx) => void | Promise (termine-tab-termine.js, termine-tab-leistungen.js,
termine-tab-team.js, termine-tab-einstellungen.js; die drei letzten speichern ueber termine-speichern.js).
ctx = { s, projectId, daten, nurLesen, auswahl, aktuell(), api, meldung, hinweise, zumReiter, neuLaden }: Zustand und Pruefung "bin ich noch
aktuell?" bekommen die Reiter nur ueber ctx, nie ueber Modulvariablen. daten ist die Antwort von GET /api/termine/einstellungen
(die Reiter, die speichern, halten daten.einstellung und daten.version aktuell), nurLesen gilt fuer abgeschaltete Funktionen,
hinweise() zeichnet die Hinweisleiste ueber den Reitern neu (z. B. wenn Telefon und Anschrift nach dem Speichern nicht mehr fehlen). Server: server/routes/termine.js. Tage sind Berliner Kalendertage 'JJJJ-MM-TT';
gerechnet wird in termine-view.js. Helfer ohne Zustand: termine-dom.js. Styles: termine.css. */
import { fehltText, heuteBerlin } from './termine-view.js'
import { exportUrl, fehlerKarte, h, infoKarte, request } from './termine-dom.js'
import { exportLink, fehlerKarte, h, infoKarte, request } from './termine-dom.js'
import { render as renderTermine } from './termine-tab-termine.js'
import { render as renderLeistungen } from './termine-tab-leistungen.js'
import { render as renderTeam } from './termine-tab-team.js'
import { render as renderEinstellungen } from './termine-tab-einstellungen.js'
// Auswahl bleibt beim Wechsel zwischen Funktionen und Terminplaner erhalten (der Tag nicht: er beginnt immer bei heute)
const auswahl = { projectId: '', reiter: 'termine', ansicht: 'tag', filter: 'alle' }
@@ -61,7 +65,7 @@ const ladeDaten = (projectId) => request(`/api/termine/einstellungen?projectId=$
/** Hinweis fuer abgeschaltete Funktionen: nur noch ansehen und exportieren. */
const hinweisAbgeschaltet = (projectId) => h('div', { class: 'tm-box tm-box-warn', role: 'note' },
'Der Terminplaner ist für diese Website abgeschaltet. Du kannst Termine nur noch ansehen und ',
h('a', { href: exportUrl(projectId), download: 'termine.csv' }, 'exportieren'),
exportLink(projectId, 'exportieren'),
'.')
/**
@@ -131,16 +135,12 @@ function zeigeVorlagen(s, { projekt, aktuell }) {
}
/* -------------------------------------------------------- Reiter-Gerüst --- */
const platzhalter = (titel, text) => (panel) => {
panel.replaceChildren(h('div', { class: 'kb-panel tm-info' }, h('h3', null, titel), h('p', null, text)))
}
/** key, Beschriftung, Funktion (panel, ctx). Aufgabe 21b ersetzt die Platzhalter, sonst bleibt alles wie es ist. */
/** key, Beschriftung, Funktion (panel, ctx). */
const REITER = [
{ key: 'termine', label: 'Termine', render: renderTermine },
{ key: 'leistungen', label: 'Leistungen', render: platzhalter('Leistungen', 'Hier legst du bald fest, was deine Kundschaft buchen kann – mit Dauer, Preis und Reihenfolge.') },
{ key: 'team', label: 'Team & Plätze', render: platzhalter('Team & Plätze', 'Hier pflegst du bald Personen und Plätze mit ihren Zeiten, Urlaub und Kalender-Links.') },
{ key: 'einstellungen', label: 'Einstellungen', render: platzhalter('Einstellungen', 'Hier ändern wir bald Betriebsangaben, Regeln und Texte für deine Gäste.') },
{ key: 'leistungen', label: 'Leistungen', render: renderLeistungen },
{ key: 'team', label: 'Team & Plätze', render: renderTeam },
{ key: 'einstellungen', label: 'Einstellungen', render: renderEinstellungen },
]
/**
@@ -177,26 +177,35 @@ function zeigeReiter(s, { projekt, aktuell }) {
? request(`/api/termine${pfad}?${new URLSearchParams({ projectId })}`, { method })
: request(`/api/termine${pfad}`, { method, body: { projectId, ...body } })),
},
meldung: (text, art) => zeigeMeldung(s, text, art),
// eine Meldung, die erst nach dem Wechsel der Website oder der Ansicht eintrifft, gehoert nicht mehr auf diese Seite
meldung: (text, art) => { if (aktuell()) zeigeMeldung(s, text, art) },
hinweise: () => { if (aktuell()) zeichneHinweise() },
zumReiter: (key) => waehleReiter(key),
neuLaden: async () => {
const daten = await ladeDaten(projectId)
if (aktuell()) {
s.daten = daten
ctx.daten = daten
zeichneHinweise()
}
return daten
},
}
const hinweise = []
if (nurLesen) {
hinweise.push(hinweisAbgeschaltet(projectId))
} else if ((s.daten.fehlt || []).length) {
hinweise.push(h('div', { class: 'tm-box tm-box-warn', role: 'note' },
h('span', null, `Noch nicht buchbar: Es fehlt ${fehltText(s.daten.fehlt) || 'eine Angabe zu deinem Betrieb'}. Solange das fehlt, können Gäste über deine Website nicht buchen.`),
h('button', { type: 'button', class: 'kb-btn-outline', onclick: () => waehleReiter('einstellungen') }, 'Zu den Einstellungen')))
// Hinweisleiste: abgeschaltet, oder Pflichtangaben fuer Gaeste fehlen (GET /einstellungen: fehlt). Sie folgt s.daten.
const hinweisBox = h('div', { class: 'tm-hinweise' })
function zeichneHinweise() {
const hinweise = []
if (nurLesen) {
hinweise.push(hinweisAbgeschaltet(projectId))
} else if ((s.daten.fehlt || []).length) {
hinweise.push(h('div', { class: 'tm-box tm-box-warn', role: 'note' },
h('span', null, `Noch nicht buchbar: Es fehlt ${fehltText(s.daten.fehlt) || 'eine Angabe zu deinem Betrieb'}. Solange das fehlt, können Gäste über deine Website nicht buchen.`),
h('button', { type: 'button', class: 'kb-btn-outline', onclick: () => waehleReiter('einstellungen') }, 'Zu den Einstellungen')))
}
hinweisBox.replaceChildren(...hinweise)
}
zeichneHinweise()
const panel = h('div', { class: 'tm-panel', role: 'tabpanel', id: 'tm-panel', tabindex: '0' })
const knoepfe = REITER.map((r) => h('button', {
@@ -229,7 +238,7 @@ function zeigeReiter(s, { projekt, aktuell }) {
.catch((err) => { if (aktuell()) panel.replaceChildren(fehlerKarte(err.message, () => waehleReiter(reiter.key))) })
}
s.buehne.replaceChildren(...hinweise, s.meldungEl, leiste, panel)
s.buehne.replaceChildren(hinweisBox, s.meldungEl, leiste, panel)
waehleReiter(auswahl.reiter)
}