Der Hinweis „Die N Ausnahmen, die nur für diesen Plan gelten, werden mit gelöscht“ wurde beim Bauen der Karte berechnet und war nach dem Speichern einer Ausnahme veraltet. Er wird jetzt beim Öffnen der Rückfrage aus dem aktuellen Stand gelesen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
371 lines
21 KiB
JavaScript
371 lines
21 KiB
JavaScript
/* Oeffnungszeiten live im Kundenbereich: Editor, Bereich 1 „Zeitplaene“ (Spec 3.4) und die Eingabe eines Tages, die auch Feiertage und
|
||
Sondertage nutzen (tagFelder). plaene(t) ist ein Abschnitt des Editors; t = { ctx, sp, zeichne, wurzel } kommt aus zeiten-editor.js
|
||
(sp = Speicher des Editors, zeichne(['ausnahmen', …]) zeichnet einzelne Abschnitte neu). Jede Aenderung speichert die ganzen Daten (zeiten-speichern.js).
|
||
Nach einem Speichern wird nur neu gezeichnet, was sich geaendert hat: die Karte des gespeicherten Plans (ersetzePlanKarte), die Liste der Plaene bei
|
||
Hinzufuegen, Loeschen und Hauptplan (zeichnePlanListe, mit den ungespeicherten Eingaben der anderen Karten), die Ausnahmen bei neuem oder geloeschtem Plan
|
||
und bei einem neuen Namen. Alles andere in der Seite bleibt mit seinen ungespeicherten Eingaben stehen.
|
||
Ein Plan: Name, Kurzname (nur Anzeige: der Server vergibt ihn beim ersten Speichern endgueltig und nennt ihn in der Antwort, R33), je
|
||
Wochentag der Haken „geöffnet“ mit zwei Zeitpaaren (<input type="time" step="300">) und der Wahl „zu festen Zeiten“, „rund um die Uhr“,
|
||
„nach Vereinbarung“; Knoepfe Speichern, Zum Hauptplan machen, Plan löschen (nicht der einzige, nicht der Hauptplan).
|
||
Vor dem ersten Speichern (ctx.stand.daten null) gibt es nur den leeren Hauptplan „Öffnungszeiten“ (ctx.entwurfsplan); er gehoert zu
|
||
ctx.leereDaten(), mit denen das erste Speichern geht. Pruefungen im Browser (wochenFehler aus zeiten-view.js) zeigen Fehler nur frueher an,
|
||
entscheiden tut der Server. Abgeschaltete Funktion (ctx.nurLesen): dieselben Felder gesperrt, ohne Knoepfe zum Aendern. */
|
||
|
||
import { WOCHENTAGE_PLAN } from './termine-view.js'
|
||
import { abschnitt, allgemeinerFehler, feld, gesperrt, h, neuerEintrag, zeigeFehler } from './termine-dom.js'
|
||
import { MODI, ZEITEN_LIMITS, ZEITEN_MAX, hauptplanWechseln, kurzname, leererPlan, planLoeschenGrund, wochenFehler } from './zeiten-view.js'
|
||
import { entfernenKnopf, nachSpeichern, stelleVon } from './zeiten-speichern.js'
|
||
|
||
/** Betriebsart eines Tages, Ausnahme oder Feiertags: Wert und Beschriftung. */
|
||
export const MODUS_WAHL = Object.freeze([
|
||
['zu', 'geschlossen'], ['zeiten', 'geöffnet zu festen Zeiten'], ['rund', 'rund um die Uhr'], ['vereinbarung', 'nach Vereinbarung'],
|
||
])
|
||
const ART_WAHL = MODUS_WAHL.filter(([wert]) => wert !== 'zu') // Wochentag: „geöffnet“ ist der Haken, die Art steht daneben
|
||
|
||
/** Kurzfassung eines Tages: „geschlossen“, „09:00–12:30, 13:30–18:00“, „rund um die Uhr“, „nach Vereinbarung“. */
|
||
export function tagText(tag) {
|
||
if (tag?.modus === 'rund') return 'rund um die Uhr'
|
||
if (tag?.modus === 'vereinbarung') return 'nach Vereinbarung'
|
||
if (tag?.modus === 'zeiten' && Array.isArray(tag.fenster) && tag.fenster.length) return tag.fenster.map((f) => `${f[0]}–${f[1]}`).join(', ')
|
||
return 'geschlossen'
|
||
}
|
||
|
||
/** <input type="time"> kennt kein 24:00: ein gespeichertes Ende um 24:00 steht als 00:00 da (dasselbe Ende: „bis Mitternacht“). */
|
||
const zeitWert = (wert) => (wert === '24:00' ? '00:00' : typeof wert === 'string' ? wert : '')
|
||
|
||
let idZaehler = 0
|
||
|
||
/** Gibt es in der Auswahl einen Wert wert? (Eine Eingabe aus einer anderen Liste, z. B. nach einem Wechsel des Bundeslands, passt eventuell nicht mehr.) */
|
||
const hatOption = (auswahl, wert) => [...auswahl.querySelectorAll('option')].some((o) => o.value === wert)
|
||
|
||
/**
|
||
* Eingabe eines Tages (Wochentag, Feiertag, Sondertag): Betriebsart und bis zu zwei Zeitpaare.
|
||
* haken: Wochentag – Haken „geöffnet“ (ohne Haken: geschlossen) und daneben die Wahl der Art; sonst eine Auswahl der Betriebsart (wahl: Liste
|
||
* [Wert, Text]; ein Wert '' steht fuer „Vorgabe“ und liest sich als modus '').
|
||
* pfad: Name des Feldes (data-feld) und des Fehlers (data-fehler) – so findet ihn die Antwort des Servers; fensterPfad: Pfad des Fehlers bei den
|
||
* Zeitfenstern, wenn der Server fuer Betriebsart und Fenster getrennte Pfade nennt (Sondertag: …modus und …fenster).
|
||
* sichtbar: die Beschriftung der Auswahl steht in der Seite (sonst nur fuer Screenreader).
|
||
* => { el, lies, merke, stelleWieder }: lies() liefert { modus, fenster } oder { fehler: { [pfad]: Text } } (ein Paar nur halb ausgefuellt). Die Regeln der
|
||
* Fenster pruefen der Aufrufer (tagFehler/wochenFehler) und der Server. merke() haelt die Eingaben so fest, wie sie im Formular stehen (auch Halbfertiges),
|
||
* stelleWieder(merke()) setzt sie in einem neu gebauten Feld wieder ein: so gehen ungespeicherte Eingaben nicht verloren, wenn die Karte neu gezeichnet wird.
|
||
*/
|
||
export function tagFelder({ pfad, fensterPfad = pfad, label, modus = 'zu', fenster = [], haken = false, wahl = MODUS_WAHL, sichtbar = false }) {
|
||
const start = typeof modus === 'string' && (MODI.includes(modus) || modus === '') ? modus : 'zu'
|
||
const zeitFeld = (i, teil, wert) => h('input', { type: 'time', step: '300', value: zeitWert(wert), 'aria-label': `${label}, Zeitfenster ${i + 1}, ${teil}` })
|
||
const paare = [0, 1].map((i) => ({ von: zeitFeld(i, 'von', fenster?.[i]?.[0]), bis: zeitFeld(i, 'bis', fenster?.[i]?.[1]) }))
|
||
if (fensterPfad !== pfad) paare[0].von.dataset.feld = fensterPfad
|
||
const zeiten = h('div', { class: 'zt-zeiten' }, paare.map((paar, i) => h('div', { class: 'zt-fenster' },
|
||
h('span', { class: 'zt-fenster-name' }, i === 0 ? 'Zeitfenster' : '2. Zeitfenster (optional)'),
|
||
h('label', { class: 'zt-zeit' }, h('span', null, 'von'), paar.von),
|
||
h('label', { class: 'zt-zeit' }, h('span', null, 'bis'), paar.bis))))
|
||
|
||
let kopf
|
||
let felder
|
||
let modusWert
|
||
let zeige
|
||
let setzeModus
|
||
if (haken) {
|
||
const geoeffnet = h('input', { type: 'checkbox', checked: start !== 'zu' && start !== '' })
|
||
geoeffnet.dataset.feld = pfad
|
||
const art = h('select', { 'aria-label': `${label}: Art der Öffnung` }, ART_WAHL.map(([wert, text]) => h('option', { value: wert, selected: wert === (start === 'zu' || start === '' ? 'zeiten' : start) }, text)))
|
||
kopf = h('div', { class: 'tm-feld tm-feld-haken' }, h('label', { class: 'tm-haken' }, geoeffnet, h('span', null, h('strong', null, label), ' geöffnet')))
|
||
felder = h('div', { class: 'zt-tag-felder' }, art, zeiten)
|
||
modusWert = () => (geoeffnet.checked ? art.value : 'zu')
|
||
setzeModus = (wert) => {
|
||
geoeffnet.checked = wert.an === true
|
||
if (hatOption(art, wert.art)) art.value = wert.art
|
||
}
|
||
zeige = () => {
|
||
felder.hidden = !geoeffnet.checked
|
||
zeiten.hidden = !(geoeffnet.checked && art.value === 'zeiten')
|
||
}
|
||
geoeffnet.addEventListener('change', zeige)
|
||
art.addEventListener('change', zeige)
|
||
} else {
|
||
const id = `zt-t${++idZaehler}`
|
||
const auswahl = h('select', { id }, wahl.map(([wert, text]) => h('option', { value: wert, selected: wert === start }, text)))
|
||
auswahl.dataset.feld = pfad
|
||
kopf = h('div', { class: 'tm-feld' }, h('label', { for: id, class: sichtbar ? 'tm-label' : 'tm-sr' }, sichtbar ? 'Öffnung' : `${label}: Öffnung`), auswahl)
|
||
felder = h('div', { class: 'zt-tag-felder' }, zeiten)
|
||
modusWert = () => auswahl.value
|
||
setzeModus = (wert) => { if (hatOption(auswahl, wert.wahl)) auswahl.value = wert.wahl }
|
||
zeige = () => { zeiten.hidden = auswahl.value !== 'zeiten' }
|
||
auswahl.addEventListener('change', zeige)
|
||
}
|
||
zeige()
|
||
|
||
const el = h('div', { class: 'zt-tag' }, kopf, felder,
|
||
h('p', { class: 'tm-fehler', 'data-fehler': pfad, role: 'alert' }),
|
||
fensterPfad !== pfad ? h('p', { class: 'tm-fehler', 'data-fehler': fensterPfad, role: 'alert' }) : null)
|
||
|
||
function lies() {
|
||
const art = modusWert()
|
||
if (art !== 'zeiten') return { modus: art, fenster: [] }
|
||
const gelesen = []
|
||
for (const paar of paare) {
|
||
const von = paar.von.value
|
||
const bis = paar.bis.value
|
||
if (!von && !bis) continue
|
||
if (!von || !bis) return { fehler: { [fensterPfad]: 'Bitte gib Beginn und Ende des Zeitfensters an.' } }
|
||
gelesen.push([von, bis])
|
||
}
|
||
return { modus: art, fenster: gelesen }
|
||
}
|
||
const merke = () => ({
|
||
an: haken ? modusWert() !== 'zu' : undefined,
|
||
art: haken ? (el.querySelector('select')?.value ?? '') : undefined,
|
||
wahl: haken ? undefined : modusWert(),
|
||
zeiten: paare.flatMap((paar) => [paar.von.value, paar.bis.value]),
|
||
})
|
||
const stelleWieder = (gemerkt) => {
|
||
if (!gemerkt) return
|
||
setzeModus(gemerkt)
|
||
paare.forEach((paar, i) => {
|
||
paar.von.value = gemerkt.zeiten?.[2 * i] ?? ''
|
||
paar.bis.value = gemerkt.zeiten?.[2 * i + 1] ?? ''
|
||
})
|
||
zeige()
|
||
}
|
||
return { el, lies, merke, stelleWieder }
|
||
}
|
||
|
||
/* ----------------------------------------------------------------- Zeitplaene --- */
|
||
const LOESCHEN_WARNUNG = 'Steht dieser Plan auf deiner Website, zeigt sie dort wieder die feste Tabelle.'
|
||
|
||
/** Der Plan mit dieser ID im aktuellen Stand (nach dem Speichern: mit dem Kurznamen des Servers). */
|
||
const planMitId = (ctx, id) => (ctx.stand.daten?.plaene || []).find((p) => p?.id === id)
|
||
const planSelektor = (id, rest) => `details[data-plan="${CSS.escape(id)}"] ${rest}`
|
||
const grenzText = `Mehr als ${ZEITEN_LIMITS.plaene} Zeitpläne sind nicht möglich.`
|
||
|
||
// Pro Karte, wie man ihre Eingaben festhaelt und wieder einsetzt (merke/stelleWieder): die Liste der Plaene wird bei „Plan hinzufügen“, „Löschen“
|
||
// und „Zum Hauptplan machen“ neu gezeichnet; was in den anderen Karten ungespeichert steht, geht dabei nicht verloren.
|
||
const KARTEN = new WeakMap()
|
||
const kartenIn = (t) => [...(t.wurzel.querySelector('.zt-plaene')?.children ?? [])]
|
||
|
||
export function plaene(t) {
|
||
const { ctx } = t
|
||
const daten = ctx.stand.daten
|
||
const liste = daten ? daten.plaene : [ctx.entwurfsplan]
|
||
const neu = daten ? neuerEintrag({
|
||
nurLesen: ctx.nurLesen, text: '+ Plan hinzufügen', werkzeug: 'plan-neu',
|
||
baue: (schliessen) => neuFormular(t, { schliessen }),
|
||
}) : null
|
||
const voll = liste.length >= ZEITEN_LIMITS.plaene
|
||
const knopf = neu?.leiste?.querySelector('button')
|
||
if (knopf) knopf.disabled = voll
|
||
return abschnitt('Zeitpläne',
|
||
'Wann hast du geöffnet? Der Hauptplan steuert den Live-Status und die Leisten auf deiner Website. Weitere Pläne (zum Beispiel für Küche oder Lieferdienst) bekommen einen eigenen Kurznamen, unter dem das Team sie auf deiner Website einbauen kann.',
|
||
neu?.leiste,
|
||
neu ? h('p', { class: 'tm-hinweis zt-grenze', 'data-grenze': '' }, voll ? grenzText : '') : null,
|
||
neu?.box,
|
||
h('div', { class: 'zt-plaene' }, liste.map((plan) => planKarte(t, plan))))
|
||
}
|
||
|
||
/** Nach dem Speichern eines Plans: nur seine Karte neu zeichnen (aus dem Stand des Servers); alle anderen Formulare bleiben, wie sie sind. */
|
||
function ersetzePlanKarte(t, id) {
|
||
const plan = planMitId(t.ctx, id)
|
||
const alt = kartenIn(t).find((karte) => karte.getAttribute('data-plan') === id)
|
||
if (!plan || !alt) return
|
||
alt.parentNode.insertBefore(planKarte(t, plan), alt)
|
||
alt.remove()
|
||
}
|
||
|
||
/**
|
||
* Nach „Plan hinzufügen“, „Plan löschen“ und „Zum Hauptplan machen“: die Karten neu zeichnen (Hauptplan-Marke, Knoepfe, neue und entfernte Plaene)
|
||
* und den Knopf „+ Plan hinzufügen“ auf den Stand bringen. Die Eingaben der Karten, die es noch gibt, werden wieder eingesetzt (ungespeichert, wie sie waren);
|
||
* das Formular „Plan hinzufügen“ (neben der Liste) bleibt unberuehrt.
|
||
*/
|
||
function zeichnePlanListe(t) {
|
||
const { ctx } = t
|
||
const behaelter = t.wurzel.querySelector('.zt-plaene')
|
||
if (!behaelter || !ctx.stand.daten) return t.zeichne(['plaene'])
|
||
const gemerkt = new Map(kartenIn(t).map((karte) => [karte.getAttribute('data-plan'), KARTEN.get(karte)?.merke()]))
|
||
const karten = ctx.stand.daten.plaene.map((plan) => {
|
||
const karte = planKarte(t, plan)
|
||
if (gemerkt.get(plan.id)) KARTEN.get(karte)?.stelleWieder(gemerkt.get(plan.id))
|
||
return karte
|
||
})
|
||
behaelter.replaceChildren(...karten)
|
||
const voll = ctx.stand.daten.plaene.length >= ZEITEN_LIMITS.plaene
|
||
const knopf = t.wurzel.querySelector('[data-werkzeug="plan-neu"]')
|
||
if (knopf) knopf.disabled = voll
|
||
const grenze = t.wurzel.querySelector('[data-grenze]')
|
||
if (grenze) grenze.textContent = voll ? grenzText : ''
|
||
}
|
||
|
||
/** Formular „Plan hinzufügen“: Name und die Vorschau des Kurznamens (wie ihn der Server bildet; endgueltig ist der Wert aus der Antwort). */
|
||
function neuFormular(t, { schliessen }) {
|
||
const { ctx, sp } = t
|
||
const name = h('input', { type: 'text', maxlength: ZEITEN_MAX.name, autocomplete: 'off', placeholder: 'z. B. Küche oder Lieferdienst' })
|
||
const vorschau = h('code', { 'data-kurz-vorschau': '' }, '')
|
||
name.addEventListener('input', () => {
|
||
vorschau.textContent = name.value.trim() ? kurzname(name.value, ctx.stand.daten.plaene.map((p) => p.kurz)) : ''
|
||
})
|
||
const form = h('form', { class: 'kb-panel tm-formular', novalidate: true, 'aria-label': 'Plan hinzufügen' },
|
||
h('h3', null, 'Neuer Zeitplan'),
|
||
feld('name', 'Name des Zeitplans', name, 'Die Zeiten trägst du danach im neuen Plan ein.'),
|
||
h('p', { class: 'tm-hinweis' }, 'Kurzname: ', vorschau, ' – wird beim Speichern festgelegt und bleibt dann immer gleich.'),
|
||
allgemeinerFehler(),
|
||
h('div', { class: 'tm-frage-knoepfe' },
|
||
h('button', { type: 'submit', class: 'kb-btn-primary', 'data-aktion': 'plan-anlegen' }, 'Plan anlegen'),
|
||
h('button', { type: 'button', class: 'kb-btn-outline', onclick: schliessen }, 'Abbrechen')))
|
||
form.addEventListener('submit', async (ev) => {
|
||
ev.preventDefault()
|
||
const gewaehlt = name.value.trim()
|
||
let neuerPlan = null
|
||
const ok = await sp.speichere((entwurf) => {
|
||
if (!gewaehlt) {
|
||
zeigeFehler(form, { fehler: { name: 'Bitte gib dem Zeitplan einen Namen.' } })
|
||
return false
|
||
}
|
||
neuerPlan = leererPlan({ name: gewaehlt, kurz: kurzname(gewaehlt, entwurf.plaene.map((p) => p.kurz)) })
|
||
entwurf.plaene = [...entwurf.plaene, neuerPlan]
|
||
return true
|
||
}, { container: form })
|
||
if (!ok || !sp.nochHier()) return
|
||
ctx.offen.add(neuerPlan.id)
|
||
const gespeichert = planMitId(ctx, neuerPlan.id) // Name und Kurzname gelten so, wie der Server sie gespeichert hat
|
||
nachSpeichern(t, `Der Zeitplan „${gespeichert?.name ?? gewaehlt}“ ist angelegt, sein Kurzname ist „${gespeichert?.kurz ?? neuerPlan.kurz}“. Trage jetzt seine Zeiten ein.`, planSelektor(neuerPlan.id, 'summary'), () => {
|
||
zeichnePlanListe(t)
|
||
schliessen() // das Formular hat seine Arbeit getan
|
||
t.zeichne(['ausnahmen']) // „Gilt für“ kennt den neuen Plan
|
||
})
|
||
})
|
||
return form
|
||
}
|
||
|
||
/** Ein Plan als aufklappbare Karte: Kopf (Name, Hauptplan, Kurzname) und das Formular mit Namen und Wochentagen. */
|
||
function planKarte(t, plan) {
|
||
const { ctx, sp } = t
|
||
const daten = ctx.stand.daten
|
||
const p = `plaene.${plan.id}`
|
||
const nurLesen = ctx.nurLesen
|
||
const details = h('details', { class: 'zt-plan', 'data-plan': plan.id, open: ctx.offen.has(plan.id) })
|
||
details.addEventListener('toggle', () => {
|
||
if (details.hasAttribute('open')) ctx.offen.add(plan.id)
|
||
else ctx.offen.delete(plan.id)
|
||
})
|
||
|
||
const name = h('input', { type: 'text', maxlength: ZEITEN_MAX.name, autocomplete: 'off', value: plan.name })
|
||
const kurzEl = h('code', { 'data-kurz': '' }, plan.kurz)
|
||
// Vor dem ersten Speichern bildet der Server den Kurznamen aus dem Namen von jetzt; danach bleibt er (der Name kann sich aendern, der Kurzname nicht)
|
||
if (!daten) name.addEventListener('input', () => { kurzEl.textContent = kurzname(name.value.trim() || plan.name) })
|
||
const tage = WOCHENTAGE_PLAN.map(([nr, tagName]) => ({
|
||
nr,
|
||
tag: tagFelder({ pfad: `${p}.tage.${nr}`, label: tagName, haken: true, modus: plan.tage?.[nr]?.modus, fenster: plan.tage?.[nr]?.fenster }),
|
||
}))
|
||
for (const { nr, tag } of tage) tag.el.dataset.tag = nr
|
||
|
||
/** Name und Tage aus dem Formular: { name, tage } oder { fehler } (Pfad => Text); wochenFehler zeigt, was der Server auch abweisen wuerde. */
|
||
function lies() {
|
||
const fehler = {}
|
||
const gewaehlt = name.value.trim()
|
||
if (!gewaehlt) fehler[`${p}.name`] = 'Bitte gib dem Zeitplan einen Namen.'
|
||
const gelesen = {}
|
||
for (const { nr, tag } of tage) {
|
||
const r = tag.lies()
|
||
if (r.fehler) Object.assign(fehler, r.fehler)
|
||
else gelesen[nr] = { modus: r.modus, fenster: r.fenster }
|
||
}
|
||
for (const [nr, text] of Object.entries(wochenFehler({ tage: gelesen }))) if (!Object.hasOwn(fehler, `${p}.tage.${nr}`)) fehler[`${p}.tage.${nr}`] = text
|
||
return Object.keys(fehler).length ? { fehler } : { name: gewaehlt, tage: gelesen }
|
||
}
|
||
|
||
const speichern = h('button', { type: 'submit', class: 'kb-btn-primary', 'data-aktion': 'speichern' }, 'Speichern')
|
||
const grund = daten && !nurLesen ? planLoeschenGrund(daten, plan.id) : null
|
||
// Wie viele Ausnahmen nur fuer diesen Plan gelten, steht erst beim Oeffnen der Rueckfrage fest: Ausnahmen zeichnen die Karte nicht neu
|
||
const mitAusnahmen = () => (Array.isArray(ctx.stand.daten?.ausnahmen) ? ctx.stand.daten.ausnahmen : []).filter((a) => a?.plan === plan.id).length
|
||
const loeschenText = () => {
|
||
const n = mitAusnahmen()
|
||
return [
|
||
LOESCHEN_WARNUNG,
|
||
n === 0 ? '' : n === 1
|
||
? 'Die eine Ausnahme, die nur für diesen Plan gilt, wird mit gelöscht.'
|
||
: `Die ${n} Ausnahmen, die nur für diesen Plan gelten, werden mit gelöscht.`,
|
||
]
|
||
}
|
||
const entfernen = daten && !nurLesen && grund === null ? entfernenKnopf(sp, {
|
||
knopfText: 'Plan löschen',
|
||
ja: 'Ja, löschen',
|
||
titel: `Zeitplan „${plan.name}“ löschen?`,
|
||
text: loeschenText,
|
||
fehlerPfad: p,
|
||
// Ausnahmen, die nur fuer diesen Plan galten, haetten danach keinen Plan mehr (R10): sie gehen mit
|
||
aendere: (entwurf) => {
|
||
entwurf.plaene = entwurf.plaene.filter((x) => x.id !== plan.id)
|
||
entwurf.ausnahmen = (entwurf.ausnahmen || []).filter((a) => a?.plan !== plan.id)
|
||
},
|
||
nachher: () => {
|
||
ctx.offen.delete(plan.id)
|
||
nachSpeichern(t, `Der Zeitplan „${plan.name}“ ist gelöscht.`, '[data-werkzeug="plan-neu"]', () => {
|
||
zeichnePlanListe(t)
|
||
t.zeichne(['ausnahmen']) // die Ausnahmen dieses Plans sind mit weg, „Gilt für“ kennt den Plan nicht mehr
|
||
})
|
||
},
|
||
}) : null
|
||
|
||
const hauptKnopf = daten && !nurLesen && !plan.haupt ? h('button', { type: 'button', class: 'kb-btn-outline', 'data-aktion': 'haupt' }, 'Zum Hauptplan machen') : null
|
||
|
||
const form = h('form', { class: 'zt-plan-form', novalidate: true, 'aria-label': `Zeitplan ${plan.name}` },
|
||
gesperrt(nurLesen, [
|
||
feld(`${p}.name`, 'Name des Zeitplans', name, daten
|
||
? 'Der Kurzname bleibt gleich, auch wenn du den Namen änderst.'
|
||
: 'Der Kurzname wird beim ersten Speichern aus dem Namen gebildet und bleibt dann immer gleich.'),
|
||
h('div', { class: 'zt-tage' }, tage.map(({ tag }) => tag.el)),
|
||
]),
|
||
allgemeinerFehler(),
|
||
nurLesen ? null : h('div', { class: 'tm-frage-knoepfe' }, speichern, hauptKnopf, entfernen?.knopf),
|
||
grund ? h('p', { class: 'tm-hinweis', 'data-hinweis': 'loeschen' }, grund) : null)
|
||
|
||
form.addEventListener('submit', async (ev) => {
|
||
ev.preventDefault()
|
||
const ok = await sp.speichere((entwurf) => {
|
||
const stelle = stelleVon(entwurf.plaene, plan.id, form, 'Dieser Zeitplan')
|
||
if (stelle < 0) return false
|
||
const gelesen = lies()
|
||
if (gelesen.fehler) {
|
||
zeigeFehler(form, { fehler: gelesen.fehler })
|
||
return false
|
||
}
|
||
const kurz = daten ? entwurf.plaene[stelle].kurz : kurzname(gelesen.name)
|
||
entwurf.plaene[stelle] = { ...entwurf.plaene[stelle], kurz, name: gelesen.name, tage: gelesen.tage }
|
||
return true
|
||
}, { container: form })
|
||
if (!ok || !sp.nochHier()) return
|
||
const gespeichert = planMitId(ctx, plan.id)
|
||
nachSpeichern(t, `Die Zeiten von „${gespeichert?.name ?? plan.name}“ sind gespeichert.`, planSelektor(plan.id, '[data-aktion="speichern"]'), () => {
|
||
if (!daten) { // das erste Speichern: jetzt gibt es auch Feiertage und Ausnahmen, der ganze Editor ist neu (es gibt sonst noch kein Formular)
|
||
t.zeichne()
|
||
return
|
||
}
|
||
ersetzePlanKarte(t, plan.id)
|
||
if (gespeichert && gespeichert.name !== plan.name) t.zeichne(['ausnahmen']) // die Liste der Ausnahmen nennt den Plan beim Namen
|
||
})
|
||
})
|
||
|
||
hauptKnopf?.addEventListener('click', async () => {
|
||
const ok = await sp.speichere((entwurf) => {
|
||
if (stelleVon(entwurf.plaene, plan.id, form, 'Dieser Zeitplan') < 0) return false
|
||
entwurf.plaene = hauptplanWechseln(entwurf, plan.id).plaene
|
||
return true
|
||
}, { container: form })
|
||
if (!ok || !sp.nochHier()) return
|
||
nachSpeichern(t, `Der Zeitplan „${planMitId(ctx, plan.id)?.name ?? plan.name}“ ist jetzt der Hauptplan. Er steuert den Live-Status und die Leisten auf deiner Website.`, planSelektor(plan.id, 'summary'), () => zeichnePlanListe(t))
|
||
})
|
||
|
||
KARTEN.set(details, {
|
||
merke: () => ({ name: name.value, tage: tage.map(({ tag }) => tag.merke()) }),
|
||
stelleWieder: (gemerkt) => {
|
||
name.value = gemerkt.name
|
||
tage.forEach(({ tag }, i) => tag.stelleWieder(gemerkt.tage[i]))
|
||
},
|
||
})
|
||
details.append(
|
||
h('summary', { class: 'zt-plan-kopf' },
|
||
h('strong', { class: 'tm-name' }, plan.name),
|
||
plan.haupt ? h('span', { class: 'tm-pille tm-pille-ok' }, 'Hauptplan') : null,
|
||
h('span', { class: 'tm-meta' }, 'Kurzname: ', kurzEl)),
|
||
h('div', { class: 'tm-editor' }, form, entfernen?.box))
|
||
return details
|
||
}
|