Files
Webklar-Kundenbereich/public/zeiten-plaene.js
T
ClaudeandClaude Opus 5.5 242fbb01e5 Öffnungszeiten: Plan-löschen-Rückfrage zählt die Ausnahmen beim Öffnen
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>
2026-10-05 06:23:56 +00:00

371 lines
21 KiB
JavaScript
Raw 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.
/* 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
}