Buchungskalender: im Abo-Modell Anfrage ohne Preis und Einschalten nach der Zusage

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-16 18:46:42 +00:00
1 parent 28da4180bd
commit 5f3d8f3e96
4 files changed
+122 -23

No files matched your search

+20 -14
View File
@@ -16,21 +16,27 @@ const artWert = (a) => (a === 'monteur' ? 'monteurzimmer' : a)
const labelVon = (liste, key) => liste.find(([k]) => k === key)?.[1] || ''
const enc = encodeURIComponent
/** Angaben der Anfrage als Definitionsliste (nur Text). */
export function anfrageListe(a) {
const s = a && typeof a === 'object' ? a : {}
const portale = (Array.isArray(s.portale) ? s.portale : []).map((p) => labelVon(PORTALE, p)).filter(Boolean).join(', ')
const zeilen = [
['Art der Unterkunft', labelVon(ARTEN, artWert(s.art))],
['Anzahl Einheiten', Number.isInteger(s.einheiten) ? String(s.einheiten) : ''],
/** Angaben der Anfrage als Zeilen [{ name, wert }] (Definitionsliste und Gruppe der Einschalt-Seite); ohne Objekt leer. */
export function anfrageZeilen(a) {
if (a === null || typeof a !== 'object') return []
const portale = (Array.isArray(a.portale) ? a.portale : []).map((p) => labelVon(PORTALE, p)).filter(Boolean).join(', ')
return [
['Art der Unterkunft', labelVon(ARTEN, artWert(a.art))],
['Anzahl Einheiten', Number.isInteger(a.einheiten) ? String(a.einheiten) : ''],
['Portale', portale || 'keine'],
['Nachricht', text(s.nachricht)],
].filter(([, wert]) => wert !== '')
return h('dl', { class: 'sh-fakten' }, zeilen.map(([name, wert]) => h('div', null, h('dt', null, name), h('dd', null, wert))))
['Nachricht', text(a.nachricht)],
].filter(([, wert]) => wert !== '').map(([name, wert]) => ({ name, wert }))
}
/** Formular „Buchungskalender anfragen“ in root; nachSenden(anfrage) wird nach dem Speichern aufgerufen (die Ansicht zeichnet sich neu). */
export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis = KALENDER_PREIS, nachSenden } = {}) {
/** Angaben der Anfrage als Definitionsliste (nur Text). */
export const anfrageListe = (a) => h('dl', { class: 'sh-fakten' }, anfrageZeilen(a).map(({ name, wert }) => h('div', null, h('dt', null, name), h('dd', null, wert))))
/**
* Formular „Buchungskalender anfragen“ in root; nachSenden(anfrage) wird nach dem Speichern aufgerufen (die Ansicht zeichnet sich neu).
* modell === 'abo': die Stichpunkte nennen „Im Wartungs-Abo enthalten.“ statt des Preises und „einschalten“ statt „bestellen“.
*/
export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis = KALENDER_PREIS, nachSenden, modell } = {}) {
const abo = modell === 'abo'
const v = vorgabe && typeof vorgabe === 'object' ? vorgabe : {}
const art = h('select', null, [h('option', { value: '' }, 'Bitte wählen …'), ...ARTEN.map(([w, n]) => h('option', { value: w }, n))])
art.value = ARTEN.some(([k]) => k === artWert(v.art)) ? artWert(v.art) : ''
@@ -91,10 +97,10 @@ export function renderAnfrageFormular(root, { projectId, vorgabe = null, preis =
h('h3', null, 'Buchungskalender anfragen'),
h('p', null, 'Mit dem Buchungskalender zeigst du auf deiner Website, wann deine Unterkunft frei ist. Gäste fragen direkt an, du gibst frei, und die Rechnung entsteht automatisch.'),
h('ul', null,
h('li', null, `${preis} einmalig für den Einbau in deine Website.`),
h('li', null, abo ? 'Im Wartungs-Abo enthalten.' : `${preis} einmalig für den Einbau in deine Website.`),
h('li', null, 'Belegungskalender mit Abgleich zu Booking.com und Airbnb (über iCal).'),
h('li', null, 'Rechnung in deinem Namen und optional Online-Zahlung auf dein eigenes Stripe-Konto.'),
h('li', null, 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du bestellen.')))
h('li', null, abo ? 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du einschalten.' : 'Wir sehen uns deine Anfrage kurz an. Erst nach unserer Zusage kannst du bestellen.')))
const karte = h('section', { class: 'kb-panel sh-anfrage' }, erklaerung, form)
root.replaceChildren(karte)
return karte
+21 -8
View File
@@ -5,8 +5,8 @@
Helfer: shop-dom.js (h, request, Karten), Styles: buchungen.css (bk-) und shop.css (sh-). Fremdtext nur ueber textContent. */
import { fehlerKarte, h, infoKarte, request } from './shop-dom.js'
import { statusKarte } from './funktion-bestellen.js'
import { anfrageKarte, renderAnfrageFormular } from './buchungen-anfrage.js'
import { renderEinschaltSeite, statusKarte } from './funktion-bestellen.js'
import { anfrageKarte, anfrageZeilen, renderAnfrageFormular } from './buchungen-anfrage.js'
import { meldeReiter, reiterEintraege } from './fn-reiter.js'
/** Die Reiter: jeder ein Modul mit render(panel, ctx), erst beim ersten Oeffnen geladen. */
@@ -34,22 +34,22 @@ const istAbo = (s) => s.modell === 'abo'
const keineReiter = (s) => { if (istAbo(s)) meldeReiter(s.root, { eintraege: [] }) }
const zustandVon = (projekt) => projekt?.features?.unterkunft?.state || 'off'
export function initBuchungen(root, { projectId } = {}) {
export function initBuchungen(root, { projectId, einschalten } = {}) {
if (!root) return Promise.resolve()
const wunsch = projectId || root.dataset?.wunschProjekt || '' // die Kachel in Funktionen legt ihr Projekt hier ab
if (root.dataset) delete root.dataset.wunschProjekt
return starte(root, wunsch)
return starte(root, wunsch, einschalten)
}
async function starte(root, projectId) {
async function starte(root, projectId, einschalten) {
if (projectId) auswahl.projectId = projectId
const s = { root, projects: [], modell: undefined, projekt: null, status: null, lauf: 0, buehne: null, meldungEl: null }
const s = { root, projects: [], modell: undefined, projekt: null, status: null, lauf: 0, buehne: null, meldungEl: null, einschalten }
S = s
let funktionen
try {
funktionen = await request('/api/funktionen')
} catch (err) {
if (S === s) root.replaceChildren(fehlerKarte(err.message, () => starte(root, auswahl.projectId)))
if (S === s) root.replaceChildren(fehlerKarte(err.message, () => starte(root, auswahl.projectId, einschalten)))
return
}
if (S !== s) return
@@ -136,7 +136,19 @@ function zeigeAnfrage(s, { status, aktuell }) {
const ansicht = status?.anfrage
// nach einem 409 bleibt die Meldung des Servers sichtbar, waehrend die Ansicht neu laedt
const neuLaden = async (o) => { await zeigeProjekt(s); if (o?.meldung && S === s) zeigeMeldung(s, o.meldung, 'fehler') }
if (ansicht?.status === 'angenommen') {
if (ansicht?.status === 'angenommen' && istAbo(s)) {
// Abo-Modell: die Zusage fuehrt zum Einschalten (kein Preis, keine Haken, keine Bestellung)
const wurzel = h('div', { class: 'sh-wurzel' })
s.buehne.replaceChildren(wurzel)
renderEinschaltSeite(wurzel, {
ctx: { projectId, aktuell },
daten: { offen: [], kundenansicht: false },
key: 'unterkunft',
zusammenfassung: () => (ansicht.anfrage ? [{ schritt: 'anfrage', titel: 'Deine Anfrage', zeilen: anfrageZeilen(ansicht.anfrage).map(({ name, wert }) => `${name}: ${wert}`) }] : []),
einschalten: s.einschalten,
nachEinschalten: () => zeigeProjekt(s),
})
} else if (ansicht?.status === 'angenommen') {
s.buehne.replaceChildren(infoKarte('Deine Anfrage wurde angenommen', 'Wir melden uns bei dir mit den nächsten Schritten zur Bestellung.', { chat: true }))
} else if (ansicht?.status === 'offen') {
s.buehne.replaceChildren(anfrageKarte(ansicht, { projectId, neuLaden }))
@@ -149,6 +161,7 @@ function zeigeAnfrage(s, { status, aktuell }) {
projectId,
vorgabe: ansicht?.anfrage || null,
preis: textOr(s.projekt?.features?.unterkunft?.priceLabel) || '199 €',
modell: s.modell,
nachSenden: async (_a, o) => { s.neueAnfrage = false; if (aktuell()) await neuLaden(o) },
})
}