109 lines
7.4 KiB
JavaScript
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)
|
|
}
|