Navigation leichter: Handy-Leiste, Schwarz nur für die Hauptaktion, Verträge mit zwei Reitern, kürzeres Konto (V3, V5, V7, V8)

V3: In der Fahrstuhl-Zeile läuft unter dem aktiven Wort eine dünne Linie statt des schwarzen Blocks; das Wort wird dunkel und fett. Die weiße Overlay-Kopie bleibt im Baum, wird aber nicht mehr gezeigt.
V5: Terminplaner: Tag/Woche/Monat als hellgrauer Umschalter mit weißer Auswahl, Filter als leise Chips. „Einschalten“ in der Liste Entdecken ist ein heller Knopf; schwarz bleibt der Knopf der Bestätigungsseite.
V7: Verträge nur noch mit Abo und Rechnungen. Das Stundenkonto steht beim Wartungs-Abo als Abschnitt unter dem Abo (ohne doppelte Warnungen), #stunden springt dorthin. Der E-Mail-Speicher ist die Unterseite #speicher (Link „ändern“ in der Abo-Zeile), ohne Widerrufsstreifen wie die anderen Bestellseiten. Alte Links bleiben gültig.
V8: Konto mit vier Zeilen (Passwort, Verträge kündigen, Vertrag widerrufen, Abmelden), Datenschutz und Impressum klein darunter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-29 17:27:30 +00:00
1 parent a270ff51da
commit 83ac903ab3
18 files changed
+152 -96

No files matched your search

+28 -21
View File
@@ -1,6 +1,8 @@
/* Kundenansicht neu (P2): Bereich Verträge mit den Reitern Rechnungen, Abo, Stunden und E-Mail. Der Rahmen lädt Abo und Rechnungen
parallel (jede Quelle mit eigenem Fehlerweg), wählt den Reiter allein aus route.reiter und gibt die schon geladenen Antworten an
die Reiter-Inhalte weiter. Bestellen, Wechsel und Gutschrift sind Etagen, die die Hülle direkt ruft. */
/* Kundenansicht neu (P2): Bereich Verträge mit den Reitern Abo und Rechnungen (Navigation leichter V7, vorher dazu Stunden und E-Mail).
Das Stundenkonto des Wartungs-Abos steht als Abschnitt unter dem Abo; #stunden öffnet das Abo und springt dorthin. Der E-Mail-Speicher ist
die Etage #speicher (Link „ändern“ im Abo). Der Rahmen lädt Abo und Rechnungen parallel (jede Quelle mit eigenem Fehlerweg), wählt den
Reiter allein aus route.reiter und gibt die schon geladenen Antworten an die Reiter-Inhalte weiter. Bestellen, Wechsel, Gutschrift und
Speicher sind Etagen, die die Hülle direkt ruft. */
import { api } from './api.js'
import { h, leeren } from './dom.js'
@@ -11,26 +13,24 @@ import { offeneRechnungen } from './rechnung-offen.js'
import { zeigeRechnungen } from './vertraege-rechnungen.js'
import { zeigeAbo } from './vertraege-abo.js'
import { zeigeStunden } from './ansicht-stunden.js'
import { zeigeSpeicher } from './ansicht-speicher.js'
/** Nach ?abo=success: so oft und in diesem Abstand wird auf ein laufendes Abo gewartet (Tests verkürzen es). */
export const AUF_ABO_WARTEN = { versuche: 10, pauseMs: 2000 }
const LABEL = { rechnungen: 'Rechnungen', abo: 'Abo', stunden: 'Stunden', email: 'E-Mail' }
const LABEL = { abo: 'Abo', rechnungen: 'Rechnungen' }
// Reiterklicks navigieren mit den ausdrücklichen Hashes (P1 schreibt #wartung und #abo kanonisch um)
const HASH = { rechnungen: '#rechnungen', abo: '#abo', stunden: '#stunden', email: '#speicher' }
const HASH = { abo: '#abo', rechnungen: '#rechnungen' }
const laeufe = new WeakMap() // Wurzel → Nummer des jüngsten Aufrufs
let reiterWechsel = null // Reiterklick mit laufendem Router: der nächste Aufruf nimmt die schon geladenen Antworten
/** Sichtbare Reiter: ohne laufendes Abo Rechnungen und Abo; Hosting dazu E-Mail; Wartung dazu Stunden und E-Mail. */
export function sichtbareReiter(daten) {
const sub = daten?.subscription
const reiter = ['rechnungen', 'abo']
if (!sub?.active) return reiter
if (sub.productKey === 'hosting') return [...reiter, 'email']
return [...reiter, 'stunden', 'email']
/** Sichtbare Reiter: immer Abo und Rechnungen (Navigation leichter V7). Stunden stehen im Abo, der E-Mail-Speicher ist die Etage #speicher. */
export function sichtbareReiter() {
return ['abo', 'rechnungen']
}
/** Gibt es ein Stundenkonto unter dem Abo? Nur mit laufendem Wartungs-Abo. */
export const mitStundenkonto = (daten) => Boolean(daten?.subscription?.active && daten.subscription.productKey === 'wartung')
/** Reiterwahl allein aus route.reiter (routeReiter) und den Daten, nie aus der Adressleiste. Nur der Bereichs-Einstieg (ohne Reiter) springt auf Rechnungen. */
export function waehleReiter({ routeReiter, sichtbar, offeneRechnungen: offen }) {
if (routeReiter) return sichtbar.includes(routeReiter) ? routeReiter : 'abo'
@@ -79,7 +79,8 @@ export async function zeigeVertraege(wurzel, { route, me, nav } = {}) {
const sichtbar = sichtbareReiter(daten)
const offen = offeneRechnungen(rechnungen).length
const gewaehlt = waehleReiter({ routeReiter: route?.reiter, sichtbar, offeneRechnungen: offen })
const stundenOhneWartung = route?.reiter === 'stunden' && !sichtbar.includes('stunden')
const stundenGewuenscht = route?.reiter === 'stunden' // #stunden: Abo zeigen und zum Stundenkonto springen
const stundenkonto = mitStundenkonto(daten)
const hinweis = nimmHinweis()
const inhalt = h('div', { class: 'kn-reiter-inhalt' })
@@ -87,14 +88,20 @@ export async function zeigeVertraege(wurzel, { route, me, nav } = {}) {
leeren(inhalt)
if (key === 'rechnungen') return zeigeRechnungen(inhalt, { me, antwort: rechnungen ?? undefined })
if (!daten) return Promise.resolve()
if (key === 'abo') {
if (stundenOhneWartung) inhalt.append(h('p', { class: 'kn-hinweis' }, 'Ein Stundenkonto gibt es im Wartungs-Abo.'))
const platz = h('div', { class: 'kn-reiter-abo' })
inhalt.append(platz)
return zeigeAbo(platz, { daten, me })
if (stundenGewuenscht && !stundenkonto) inhalt.append(h('p', { class: 'kn-hinweis' }, 'Ein Stundenkonto gibt es im Wartungs-Abo.'))
const platz = h('div', { class: 'kn-reiter-abo' })
inhalt.append(platz)
const fertig = [zeigeAbo(platz, { daten, me })]
if (stundenkonto) {
const konto = h('div')
const abschnitt = h('section', { class: 'kn-sektion kn-stundenkonto', id: 'kn-stundenkonto', 'aria-labelledby': 'kn-stundenkonto-titel' },
h('h2', { class: K.abschnitt, id: 'kn-stundenkonto-titel' }, 'Stundenkonto'), konto)
inhalt.append(abschnitt)
fertig.push(zeigeStunden(konto, { route, me, nav, daten, eingebettet: true }).then(() => {
if (stundenGewuenscht && nav !== 'back' && typeof abschnitt.scrollIntoView === 'function') abschnitt.scrollIntoView({ block: 'start' })
}))
}
if (key === 'stunden') return zeigeStunden(inhalt, { route, me, nav, daten })
return zeigeSpeicher(inhalt, { route, me, nav, daten })
return Promise.all(fertig)
}
const bei = (key) => {