Files

167 lines
7.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* Terminplaner im Kundenbereich: Aktionen auf Terminen mit Rueckfrage in der Seite (kein confirm()).
starteAktion(t, li, b, art, knopf) oeffnet die Rueckfrage unter dem Termin, fuehreAus schickt sie an /api/termine ab.
t ist der Zustand des Reiters "Termine": { ctx, offen, nachAenderung() } – nachAenderung laedt Liste und Zahl der Anfragen neu
(der Reiter setzt sie; so kennt dieses Modul den Reiter nicht). Fehler: 400 und belegte Zeit bleiben in der Rueckfrage,
alles andere erscheint als Meldung ueber den Reitern. */
import { MAX, ABSAGE_GRUENDE, datumKurz, heuteBerlin } from './termine-view.js'
import { allgemeinerFehler, feld, frage, h, knopfZeile, setzeFehler, zeigeFehler } from './termine-dom.js'
import { zeitWahl } from './termine-formulare.js'
export const AKTION_TEXT = {
annehmen: 'Annehmen', ablehnen: 'Ablehnen', verschieben: 'Verschieben', absagen: 'Absagen', nichterschienen: 'Nicht erschienen',
anonymisieren: 'Gastdaten löschen', entsperren: 'Sperre aufheben',
}
export function schliesseFrage(t, fokus = false) {
if (!t.offen) return
const { li, knopf } = t.offen
li.querySelector('[data-frage]')?.replaceChildren()
li.classList.remove('tm-offen')
t.offen = null
if (fokus && knopf?.isConnected) knopf.focus()
}
export function starteAktion(t, li, b, art, knopf) {
t.ctx.meldung('')
schliesseFrage(t)
const zu = () => schliesseFrage(t, true)
const fragen = { annehmen: frageAnnehmen, ablehnen: frageAblehnen, absagen: frageAbsagen, verschieben: frageVerschieben, nichterschienen: frageNichtErschienen, anonymisieren: frageAnonymisieren, entsperren: frageEntsperren }
const box = fragen[art](t, b, zu)
li.querySelector('[data-frage]').replaceChildren(box)
li.classList.add('tm-offen')
t.offen = { li, knopf, art }
// Fokus ins erste Eingabefeld; ohne Eingabefeld auf die Rueckfrage selbst, damit ein Enter nichts bestaetigt
const erster = box.querySelector('select, textarea, input') || box
erster.focus()
}
/**
* Aktion an die Schnittstelle schicken. erfolg = Text der Meldung. form = Rueckfrage-Formular fuer Fehler (400 und belegte Zeit
* bleiben dort), sonst Meldung ueber den Reitern; bei Fehlern, die auf einen geaenderten Stand deuten (404, 409), wird die Liste neu geladen.
*/
async function fuehreAus(t, b, body, erfolg, form, knopf) {
const { ctx } = t
knopf.disabled = true
try {
const entsperren = body.aktion === 'entsperren'
const antwort = entsperren
? await ctx.api.send('DELETE', `/buchungen/${encodeURIComponent(b.id)}`)
: await ctx.api.send('POST', `/buchungen/${encodeURIComponent(b.id)}/aktion`, body)
await t.nachAenderung() // erst die Liste, dann die Meldung: wer die Meldung sieht, sieht auch den neuen Stand
ctx.meldung(antwort.schon ? 'Das war schon so erledigt.' : erfolg)
} catch (err) {
const imFormular = form && (err.status === 400 || err.code === 'SLOT_VERGEBEN')
if (imFormular) {
zeigeFehler(form, err)
knopf.disabled = false
return
}
ctx.meldung(err.message, 'fehler')
if (err.status === 404 || err.status === 409) await t.nachAenderung()
else knopf.disabled = false
}
}
const mailHinweis = (b, text) => (b.gastEmail ? text : 'Der Gast bekommt keine E-Mail, weil keine Adresse hinterlegt ist.')
// Annehmen schliesst den Vertrag und verschickt die Bestaetigung (G02): wie jede Aktion erst nach einer Rueckfrage in der Seite
function frageAnnehmen(t, b, zu) {
const { ok, zeile } = knopfZeile('Annehmen', zu)
const form = frage('Anfrage annehmen?',
h('p', null, b.gastEmail
? `${b.gastName} bekommt eine Bestätigung per E-Mail. Mit dem Annehmen kommt der Termin zustande.`
: 'Der Gast bekommt keine E-Mail, weil keine Adresse hinterlegt ist. Mit dem Annehmen kommt der Termin zustande.'),
allgemeinerFehler(),
zeile)
const erfolg = b.gastEmail ? `Anfrage angenommen. ${b.gastName} bekommt eine Bestätigung per E-Mail.` : 'Anfrage angenommen.'
form.addEventListener('submit', () => fuehreAus(t, b, { aktion: 'annehmen' }, erfolg, form, ok))
return form
}
function frageAblehnen(t, b, zu) {
const nachricht = h('textarea', { rows: '3', maxlength: String(MAX.nachricht) })
const { ok, zeile } = knopfZeile('Anfrage ablehnen', zu)
const form = frage('Anfrage ablehnen?',
h('p', null, mailHinweis(b, 'Der Gast bekommt eine E-Mail, dass der Termin nicht möglich ist. Die Zeit wird wieder frei.')),
feld('nachricht', 'Nachricht an den Gast (optional)', nachricht),
allgemeinerFehler(),
zeile)
form.addEventListener('submit', () => fuehreAus(t, b, { aktion: 'ablehnen', nachricht: nachricht.value }, 'Anfrage abgelehnt.', form, ok))
return form
}
function frageAbsagen(t, b, zu) {
const grund = h('select', null, h('option', { value: '' }, 'Bitte wählen'), ABSAGE_GRUENDE.map((g) => h('option', { value: g }, g)))
const nachricht = h('textarea', { rows: '3', maxlength: String(MAX.nachricht) })
const { ok, zeile } = knopfZeile('Termin absagen', zu)
const form = frage('Termin absagen?',
h('p', null, mailHinweis(b, 'Der Gast bekommt eine E-Mail mit dem Grund. Die Zeit wird wieder frei.')),
feld('grund', 'Grund', grund),
feld('nachricht', 'Nachricht an den Gast (optional)', nachricht),
allgemeinerFehler(),
zeile)
form.addEventListener('submit', () => {
if (!grund.value) {
setzeFehler(form, { grund: 'Bitte wähle einen Grund aus.' })
grund.focus()
return
}
fuehreAus(t, b, { aktion: 'absagen', grund: grund.value, nachricht: nachricht.value }, 'Termin abgesagt.', form, ok)
})
return form
}
function frageNichtErschienen(t, b, zu) {
const { ok, zeile } = knopfZeile('Ja, nicht erschienen', zu)
const form = frage('Als „nicht erschienen“ markieren?',
h('p', null, 'Der Termin bleibt in der Liste und wird als „Nicht erschienen“ geführt. Der Gast bekommt keine E-Mail.'),
allgemeinerFehler(),
zeile)
form.addEventListener('submit', () => fuehreAus(t, b, { aktion: 'nichterschienen' }, 'Als „nicht erschienen“ markiert.', form, ok))
return form
}
function frageAnonymisieren(t, b, zu) {
const { ok, zeile } = knopfZeile('Gastdaten löschen', zu)
const form = frage('Gastdaten löschen?',
h('p', null, 'Name, E-Mail, Telefon und Nachricht werden gelöscht. Das lässt sich nicht rückgängig machen. Der Termin bleibt ohne Namen bestehen, die Links in alten E-Mails funktionieren danach nicht mehr.'),
allgemeinerFehler(),
zeile)
form.addEventListener('submit', () => fuehreAus(t, b, { aktion: 'anonymisieren' }, 'Gastdaten gelöscht.', form, ok))
return form
}
function frageEntsperren(t, b, zu) {
const { ok, zeile } = knopfZeile('Sperre aufheben', zu)
const form = frage('Sperre aufheben?',
h('p', null, 'Die Zeit ist danach wieder buchbar.'),
allgemeinerFehler(),
zeile)
form.addEventListener('submit', () => fuehreAus(t, b, { aktion: 'entsperren' }, 'Sperre aufgehoben.', form, ok))
return form
}
function frageVerschieben(t, b, zu) {
const { ctx } = t
const datum = h('input', { type: 'date', value: b.datum, min: heuteBerlin() })
const zeiten = zeitWahl(ctx, () => ({ ohne: b.id, tag: datum.value }))
const { ok, zeile } = knopfZeile('Verschieben', zu)
const form = frage('Termin verschieben',
h('p', null, mailHinweis(b, 'Der Gast bekommt eine E-Mail mit der neuen Zeit. Es gelten keine Fristen und kein Vorlauf.')),
feld('tag', 'Neuer Tag', datum),
zeiten.el,
allgemeinerFehler(),
zeile)
datum.addEventListener('change', () => zeiten.laden())
zeiten.laden()
form.addEventListener('submit', () => {
if (!zeiten.wert()) {
setzeFehler(form, { zeit: 'Bitte wähle eine Uhrzeit aus.' })
return
}
fuehreAus(t, b, { aktion: 'verschieben', tag: datum.value, zeit: zeiten.wert() }, `Termin verschoben auf ${datumKurz(datum.value)}, ${zeiten.wert()} Uhr.`, form, ok)
})
return form
}