/* Kundenansicht neu (P3): Bereich „Funktionen“ mit den Reitern Meine und Entdecken. Der erste Teil ist rein (Modell aus der Antwort von GET /api/funktionen im Abo-Modell, V5); die Ansicht darunter zeichnet es. Keine Preise: Der Katalog trägt keinen. Fremdtext (Funktions- und Websitenamen) wird nur als Text gesetzt. */ import { h } from './dom.js' import { ETAGE_FUER, anfrageStatusVon } from './funktion-einschalten.js' const ANFRAGE_SICHTBAR = ['offen', 'angenommen', 'abgelehnt'] // zurückgezogen zählt nicht /** Zeitpunkt oder 'JJJJ-MM-TT' → 'TT.MM.JJJJ' (Europe/Berlin); unlesbar ergibt ''. Ein Tagestext wird als Tag um 12:00 UTC gelesen. */ export function datumDe(wert) { if (typeof wert !== 'string' || wert === '') return '' const ms = Date.parse(/^\d{4}-\d{2}-\d{2}$/.test(wert) ? `${wert}T12:00:00Z` : wert) if (Number.isNaN(ms)) return '' return new Date(ms).toLocaleDateString('de-DE', { timeZone: 'Europe/Berlin', day: '2-digit', month: '2-digit', year: 'numeric' }) } const verfuegbare = (d) => (Array.isArray(d?.katalog) ? d.katalog.filter((e) => e && typeof e === 'object' && e.verfuegbar === true && typeof e.key === 'string') : []) const websites = (d) => (Array.isArray(d?.projects) ? d.projects.filter((p) => p && typeof p === 'object' && typeof p.projectId === 'string') : []) const zustandVon = (projekt, key) => (key === 'analyse' ? projekt.analyse?.state : projekt.features?.[key]?.state) /** Hat diese Website die Funktion schon (Zeile in Meine)? Eine Zeile ist Zustand, Entwurf des Fragebogens oder sichtbare Anfrage. */ function istInMeine(eintrag, projekt) { if (eintrag.key === 'analyse') return ['active', 'disabled'].includes(projekt.analyse?.state) const f = projekt.features?.[eintrag.key] if (!f || typeof f !== 'object') return false if (f.state !== 'off') return typeof f.state === 'string' if (eintrag.ablauf === 'fragebogen') return Boolean(f.siteKey) if (eintrag.ablauf === 'anfrage') return ANFRAGE_SICHTBAR.includes(anfrageStatusVon(projekt, eintrag.key)) return false } /** „Dein Abo ist beendet.“ mit dem Löschtag, wenn einer da ist. */ function aboEndeText(f) { const tag = datumDe(f.loeschungAm) return tag ? `Dein Abo ist beendet. Deine Daten bleiben bis ${tag} gespeichert.` : 'Dein Abo ist beendet.' } /** Pille und Zeile einer Funktion an einer Website (nur für Websites, bei denen istInMeine gilt). */ function statusVon(eintrag, projekt) { if (eintrag.key === 'analyse') { const a = projekt.analyse if (a.state === 'active') return { pille: { text: 'Aktiv', ton: 'gruen' }, status: 'Läuft auf deiner Website' } return { pille: { text: 'Gesperrt', ton: 'grau' }, status: a.disabledReason === 'abo_ende' ? aboEndeText(a) : 'Schreib uns im Chat' } } const f = projekt.features[eintrag.key] switch (f.state) { case 'setup': return { pille: { text: 'In Einrichtung', ton: 'blau' }, status: f.ticketWoid ? `Einrichtung läuft · Ticket #${f.ticketWoid}` : 'Einrichtung läuft' } case 'ordered': return { pille: { text: 'Zahlung offen', ton: 'orange' }, status: 'Wir warten auf deine Zahlung' } case 'active': { const seit = datumDe(f.activatedAt) return { pille: { text: 'Aktiv', ton: 'gruen' }, status: seit ? `Aktiv seit ${seit}` : 'Läuft auf deiner Website' } } case 'disabled': return { pille: { text: 'Abgeschaltet', ton: 'grau' }, status: f.disabledReason === 'abo_ende' ? aboEndeText(f) : 'Schreib uns im Chat, wenn du die Funktion wieder möchtest.' } default: { if (eintrag.ablauf === 'fragebogen') return { pille: { text: 'Entwurf', ton: 'grau' }, status: 'Fragebogen fortsetzen' } const anfrage = anfrageStatusVon(projekt, eintrag.key) if (anfrage === 'angenommen') return { pille: { text: 'Angenommen', ton: 'blau' }, status: 'Jetzt einschalten' } if (anfrage === 'abgelehnt') return { pille: { text: 'Abgelehnt', ton: 'rot' }, status: 'Schreib uns im Chat' } return { pille: { text: 'Angefragt', ton: 'violett' }, status: 'Wir prüfen deine Anfrage' } } } } /** * Die Zeilen von „Meine“: je Funktion und Website eine, in Katalogreihenfolge, dann Websitereihenfolge. Funktionen mit * verfuegbar !== true fehlen (C9). zeile2 ist bei mehreren Websites der Websitename, sonst die Statuszeile. * @returns {Array<{ key: string, name: string, projectId: string, projektName: string, zustand: string, anfrageStatus: string, pille: { text: string, ton: string }, status: string, zeile2: string }>} */ export function meineZeilen(daten) { const seiten = websites(daten) const mehrere = seiten.length > 1 const zeilen = [] for (const eintrag of verfuegbare(daten)) { for (const projekt of seiten) { if (!istInMeine(eintrag, projekt)) continue const { pille, status } = statusVon(eintrag, projekt) zeilen.push({ key: eintrag.key, name: String(eintrag.name ?? ''), projectId: projekt.projectId, projektName: String(projekt.projectName ?? ''), zustand: String(zustandVon(projekt, eintrag.key) ?? ''), anfrageStatus: eintrag.key === 'analyse' ? '' : anfrageStatusVon(projekt, eintrag.key), pille, status, zeile2: mehrere ? String(projekt.projectName ?? '') : status, }) } } return zeilen } /** * Die Einträge von „Entdecken“ in Katalogreihenfolge: solange eine Website die Funktion nicht hat oder es keine Website gibt. * kandidaten sind die Websites ohne die Funktion (Analyse: nur mit Zustand off; unknown und comingSoon sind kein Kandidat). * @returns {Array<{ eintrag: object, kandidaten: Array<{ projectId: string, projektName: string }> }>} */ export function entdeckenEintraege(daten) { const seiten = websites(daten) const ergebnis = [] for (const eintrag of verfuegbare(daten)) { const ohne = seiten.filter((p) => !istInMeine(eintrag, p)) if (seiten.length > 0 && ohne.length === 0) continue const kandidaten = (eintrag.key === 'analyse' ? ohne.filter((p) => p.analyse?.state === 'off') : ohne) .map((p) => ({ projectId: p.projectId, projektName: String(p.projectName ?? '') })) ergebnis.push({ eintrag, kandidaten }) } return ergebnis } /** Tipp auf eine Zeile in Meine: eine angenommene Anfrage geht zur Bestätigung, alles andere in die Etage der Funktion. */ export function zeilenWeg(zeile) { if (zeile.zustand === 'off' && zeile.anfrageStatus === 'angenommen') return { art: 'seite' } return { art: 'etage', etage: ETAGE_FUER[zeile.key] } } // Platzhalter bis Task 5 (zeichnet die Ansicht) export async function zeigeFunktionen(wurzel, { route, me, nav }) { wurzel.replaceChildren(h('p', { class: 'kn-leer' }, 'Funktionen (folgt)')) }