Files
Webklar-Kundenbereich/public/buchungen-ical.js
T
2026-10-07 14:11:51 +00:00

109 lines
7.4 KiB
JavaScript

/* Buchungskalender: iCal-Bereich einer Unterkunft (Importe von Booking.com/Airbnb, Export-Link, Kurzanleitungen).
unterkunft = Eintrag aus GET /api/unterkunft/unterkuenfte: { id, icalImporte:[{ id, name, url, letzterAbrufAt, letzterErfolgAt, fehler }], icalExportUrl }.
POST /unterkuenfte/:id/ical-importe { name, url } · DELETE …/ical-importe/:importId · POST …/ical-export/erneuern => { icalExportUrl }.
Nach jeder Aenderung ruft der Aufrufer aenderung() (laedt die Unterkunft neu). */
import { abschnitt, allgemeinerFehler, datumText, feld, frage, h, zeigeFehler } from './shop-dom.js'
import { enc, zeichen, zeigeApiFehler } from './buchungen-uk-hilfe.js'
export function zeichneIcal(ctx, unterkunft, aenderung) {
const basis = `/api/unterkunft/unterkuenfte/${enc(unterkunft.id)}`
const q = `projectId=${enc(ctx.projectId)}`
const importe = Array.isArray(unterkunft.icalImporte) ? unterkunft.icalImporte : []
const dialog = h('div', { class: 'bk-dialog' })
const status = (i) => {
if (i.fehler) return `Der letzte Abgleich hat nicht geklappt: ${zeichen(i.fehler)}${i.letzterErfolgAt ? ` (zuletzt erfolgreich ${datumText(i.letzterErfolgAt)})` : ''}`
if (i.letzterErfolgAt) return `Zuletzt abgeglichen ${datumText(i.letzterErfolgAt)}`
return 'Noch nicht abgeglichen. Das passiert automatisch alle 15 Minuten.'
}
const importListe = importe.length
? h('ul', { class: 'bk-liste' }, importe.map((i) => h('li', { class: 'bk-import', 'data-import': i.id },
h('strong', null, zeichen(i.name)),
h('span', { class: i.fehler ? 'sh-fehler' : 'sh-hinweis', 'data-status': true }, status(i)),
ctx.nurLesen ? null : h('button', { type: 'button', class: 'kb-btn-outline', 'data-aktion': 'import-weg', onclick: () => entfernenDialog(i) }, 'Entfernen'))))
: h('p', { class: 'sh-hinweis' }, 'Noch kein Kalender angebunden.')
function entfernenDialog(i) {
const box = frage('Kalender entfernen', h('p', null, `Die Belegung aus „${zeichen(i.name)}“ verschwindet dann aus deinem Kalender.`), allgemeinerFehler(),
h('div', { class: 'bk-aktionen' },
h('button', {
type: 'button', class: 'kb-btn', 'data-aktion': 'import-weg-ok',
onclick: async (ev) => {
ev.currentTarget.disabled = true
try {
await ctx.request(`${basis}/ical-importe/${enc(i.id)}?${q}`, { method: 'DELETE' })
if (ctx.aktuell()) await aenderung('Der Kalender ist entfernt.')
} catch (err) { ev.currentTarget.disabled = false; zeigeApiFehler(box, err) }
},
}, 'Entfernen'),
h('button', { type: 'button', class: 'kb-btn-outline', onclick: () => dialog.replaceChildren() }, 'Abbrechen')))
dialog.replaceChildren(box)
}
const name = h('input', { type: 'text', id: 'bk-i-name', maxlength: '60', autocomplete: 'off', placeholder: 'Booking.com' })
const url = h('input', { type: 'text', id: 'bk-i-url', inputmode: 'url', autocomplete: 'off', placeholder: 'https://…' })
const neu = ctx.nurLesen ? null : h('form', { novalidate: true, class: 'bk-importform', onsubmit: (ev) => ev.preventDefault() },
feld('name', 'Name', name), feld('url', 'Link (iCal-Adresse)', url, 'Der Link beginnt mit https:// und endet oft auf .ics.'), allgemeinerFehler(),
h('button', {
type: 'button', class: 'kb-btn', 'data-aktion': 'import-dazu',
onclick: async (ev) => {
const form = ev.currentTarget.form || ev.currentTarget.closest('form')
const body = { name: String(name.value).trim(), url: String(url.value).trim() }
if (!body.name || !/^https:\/\//i.test(body.url)) return zeigeFehler(form, { message: '', fehler: { _: 'Bitte gib einen Namen und einen Link an, der mit https:// beginnt.' } })
ev.currentTarget.disabled = true
try {
await ctx.request(`${basis}/ical-importe?${q}`, { method: 'POST', body })
if (ctx.aktuell()) await aenderung('Der Kalender ist angebunden.')
} catch (err) { ev.currentTarget.disabled = false; zeigeApiFehler(form, err) }
},
}, 'Kalender hinzufügen'))
const exportUrl = zeichen(unterkunft.icalExportUrl)
const kopiert = h('p', { class: 'sh-hinweis', role: 'status', 'data-kopiert': true })
const erneuern = () => {
const box = frage('Link erneuern', h('p', null, 'Der alte Link funktioniert danach nicht mehr. Du musst den neuen Link bei Booking.com und Airbnb eintragen.'), allgemeinerFehler(),
h('div', { class: 'bk-aktionen' },
h('button', {
type: 'button', class: 'kb-btn', 'data-aktion': 'erneuern-ok',
onclick: async (ev) => {
ev.currentTarget.disabled = true
try {
await ctx.request(`${basis}/ical-export/erneuern?${q}`, { method: 'POST' })
if (ctx.aktuell()) await aenderung('Der Link ist erneuert. Trage den neuen Link bei den Portalen ein.')
} catch (err) { ev.currentTarget.disabled = false; zeigeApiFehler(box, err) }
},
}, 'Link erneuern'),
h('button', { type: 'button', class: 'kb-btn-outline', onclick: () => dialog.replaceChildren() }, 'Abbrechen')))
dialog.replaceChildren(box)
}
const export_ = h('div', { class: 'bk-link' },
h('p', { class: 'sh-hinweis' }, 'Mit diesem Link sehen Booking.com und Airbnb, wann deine Unterkunft belegt ist. Es werden keine Gastdaten weitergegeben.'),
exportUrl ? h('input', { type: 'text', readonly: true, value: exportUrl, 'aria-label': 'Export-Link', 'data-export': true }) : h('p', { class: 'sh-hinweis' }, 'Der Link ist noch nicht verfügbar.'),
h('div', { class: 'bk-aktionen' },
exportUrl ? h('button', {
type: 'button', class: 'kb-btn-outline', 'data-aktion': 'export-kopieren',
onclick: async () => {
try { await navigator.clipboard.writeText(exportUrl); kopiert.textContent = 'Link kopiert.' } catch { kopiert.textContent = 'Kopieren hat nicht geklappt. Markiere den Link und kopiere ihn von Hand.' }
},
}, 'Kopieren') : null,
ctx.nurLesen ? null : h('button', { type: 'button', class: 'kb-btn-outline', 'data-aktion': 'export-erneuern', onclick: erneuern }, 'Link erneuern')),
kopiert)
const anleitung = h('div', { class: 'bk-anleitung' },
h('details', { 'data-anleitung': 'booking' }, h('summary', null, 'Anleitung Booking.com'),
h('ol', null,
h('li', null, 'Unterkunft → Tarife & Verfügbarkeit → Kalender synchronisieren → Kalender importieren → Link einfügen.'),
h('li', null, 'Den Export-Link von oben dort einfügen und speichern.'),
h('li', null, 'Für die andere Richtung: Booking.com zeigt dir dort auch einen Link zum Exportieren. Den trägst du oben bei „Kalender anbinden“ ein.'))),
h('details', { 'data-anleitung': 'airbnb' }, h('summary', null, 'Anleitung Airbnb'),
h('ol', null,
h('li', null, 'Inserat → Verfügbarkeit → Kalender synchronisieren → Kalender verbinden.'),
h('li', null, 'Den Export-Link von oben dort einfügen und speichern.'),
h('li', null, 'Airbnb zeigt dir unter „Kalender exportieren“ einen Link. Den trägst du oben bei „Kalender anbinden“ ein.'))))
return abschnitt('Kalender-Abgleich (iCal)', 'So verhinderst du Doppelbuchungen: Belegungen von Booking.com oder Airbnb kommen in deinen Kalender, und deine Belegungen gehen dorthin.',
h('h4', null, 'Kalender anbinden'), importListe, neu,
h('h4', null, 'Dein Export-Link'), export_, anleitung, dialog)
}