Terminplaner: Portal-Ansicht – Fokus und aria-busy

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Sonnet 5.5 committed 2026-10-03 08:17:44 +00:00
1 parent 3ad056f261
commit 4be886a4de
3 files changed
+85 -21

No files matched your search

+13 -5
View File
@@ -1,6 +1,6 @@
/* Terminplaner im Kundenbereich: Speichern der Einstellungen – gemeinsam fuer die Reiter Leistungen, Team & Plaetze und Einstellungen.
Jede Aenderung ist ein Speichern: PATCH /api/termine/einstellungen schickt die GANZE Einstellung mit der Version, auf der sie beruht
(server/routes/termine.js). Der Reiter gibt nur an, was sich aendert: speichere(aendere, { container, knopf }) holt eine Kopie des
(server/routes/termine.js). Der Reiter gibt nur an, was sich aendert: speichere(aendere, { container }) holt eine Kopie des
aktuellen Standes (ctx.daten.einstellung), laesst aendere(entwurf) sie aendern und schickt sie ab.
- Erfolg: die zurueckgegebene Einstellung und Version gelten (ctx.daten), die Hinweisleiste der Huelle wird neu gezeichnet und die Daten
(Kalender-Links) werden neu geladen; Rueckgabe true.
@@ -16,6 +16,9 @@
import { allgemeinerFehler, frage, h, knopfZeile, setzeFehler, zeigeFehler } from './termine-dom.js'
/** Wie viele Speichern gerade auf einem Tabpanel laufen (aria-busy bleibt, bis das letzte fertig ist). */
const belegt = new WeakMap()
export const VERALTET_TEXT = 'Die Einstellungen wurden inzwischen geändert. Bitte neu laden.'
const WORT = { leistungen: 'Leistung', abwesenheiten: 'Abwesenheit' }
@@ -62,18 +65,23 @@ export function neuerSpeicher(ctx, wurzel, neuZeichnen) {
}
})
// Ein Speichern zur Zeit: waehrenddessen aria-busy am Reiter und alle Knoepfe darin gesperrt (danach wieder frei, der Fokus kehrt zurueck)
// Ein Speichern zur Zeit: waehrenddessen aria-busy am Reiter und alle Knoepfe darin gesperrt (danach wieder frei, der Fokus kehrt zurueck).
// Das Tabpanel ist fuer alle Reiter dasselbe Element und wechselt der Nutzer waehrend des Speicherns den Reiter, ist wurzel beim Freigeben
// schon ausgehaengt: darum merkt sich die Sperre das Panel, das sie gesetzt hat, und aria-busy faellt erst, wenn kein Speichern mehr darauf laeuft.
let laeuft = false
function sperre(an, gesperrt) {
const tafel = wurzel.closest('[role="tabpanel"]') || wurzel
if (an) {
const tafel = wurzel.closest('[role="tabpanel"]') || wurzel
belegt.set(tafel, (belegt.get(tafel) || 0) + 1)
tafel.setAttribute('aria-busy', 'true')
const knoepfe = [...wurzel.querySelectorAll('button:not(:disabled)')]
const fokus = document.activeElement
for (const knopf of knoepfe) knopf.disabled = true
return { knoepfe, fokus }
return { tafel, knoepfe, fokus }
}
tafel.removeAttribute('aria-busy')
const rest = (belegt.get(gesperrt.tafel) || 1) - 1
belegt.set(gesperrt.tafel, rest)
if (rest === 0) gesperrt.tafel.removeAttribute('aria-busy')
for (const knopf of gesperrt.knoepfe) knopf.disabled = false
// Der Fokus war auf einem gesperrten Knopf und ist weg: zurueck dorthin; hat der Reiter ihn inzwischen anders gesetzt (z. B. auf ein Feld mit Fehler), bleibt das so
const jetzt = document.activeElement
+23 -16
View File
@@ -188,22 +188,29 @@ function kalenderLink(t, r) {
allgemeinerFehler(),
zeile)
// exklusiv wie ein Speichern: das Erzeugen aendert die Version, ein gleichzeitiges Speichern bekaeme sonst die alte
box.addEventListener('submit', () => t.sp.exklusiv(async () => {
try {
const antwort = await ctx.api.send('POST', `/ressourcen/${encodeURIComponent(r.id)}/feed`)
if (!ctx.aktuell() || !eingabe.isConnected) return
ctx.daten.version = antwort.version // das Erzeugen zaehlt die Version hoch: das naechste Speichern baut darauf auf
ctx.daten.feeds = { ...ctx.daten.feeds, [r.id]: antwort.url }
eingabe.value = antwort.url
frageBox.replaceChildren()
status.textContent = 'Der neue Link ist da. Der alte funktioniert nicht mehr.'
ctx.meldung(`Neuer Kalender-Link für „${r.name}“ erzeugt.`)
ausloeser.focus()
} catch (err) {
if (!ctx.aktuell() || !eingabe.isConnected) return
zeigeFehler(box, err)
}
}))
box.addEventListener('submit', async () => {
const erzeugt = await t.sp.exklusiv(async () => {
try {
const antwort = await ctx.api.send('POST', `/ressourcen/${encodeURIComponent(r.id)}/feed`)
if (!ctx.aktuell()) return false
ctx.daten.version = antwort.version // das Erzeugen zaehlt die Version hoch: das naechste Speichern baut darauf auf
ctx.daten.feeds = { ...ctx.daten.feeds, [r.id]: antwort.url }
if (!eingabe.isConnected) return true // der Reiter wurde inzwischen neu gezeichnet und zeigt schon den neuen Link
eingabe.value = antwort.url
frageBox.replaceChildren()
status.textContent = 'Der neue Link ist da. Der alte funktioniert nicht mehr.'
ctx.meldung(`Neuer Kalender-Link für „${r.name}“ erzeugt.`)
return true
} catch (err) {
if (ctx.aktuell() && eingabe.isConnected) zeigeFehler(box, err)
return false
}
})
// Fokus erst nach dem Freigeben der Knoepfe (im gesperrten Zustand nimmt ein Knopf keinen Fokus an); ersetzt, den Knopf derselben Person suchen
if (!erzeugt || !t.sp.nochHier()) return
const knopf = ausloeser.isConnected ? ausloeser : t.wurzel.querySelector(`li[data-id="${r.id}"] [data-aktion="link-neu"]`)
knopf?.focus()
})
frageBox.replaceChildren(box)
box.focus()
}