/* Tab "Funktionen" und News-Pinnwand im Kundenbereich (ES-Modul, wird von app.js erst beim Oeffnen geladen).
initFunktionen(rootEl): Kacheln der Funktionen. initNews(rootEl, { projectId }): je nach Zustand der Funktion
Buchungsseite (off), Einrichtungsstatus (ordered/setup) oder Editor (active). Styles: funktionen.css.
Kundentexte kommen nur ueber esc() bzw. textContent in die Seite. */
import { esc, safeUrl, request } from '/fn-util.js'
import { analyseOf, analyseState, pickAnalyseProject, analyseSel } from '/analyse-view.js'
const PRICE = '30 €'
const LEISTUNGSBESCHREIBUNG = '/vertragsunterlagen/funktionen-leistungsbeschreibung.html'
const PHOTO_ACCEPT = 'image/jpeg,image/png,image/webp' // iOS wandelt HEIC dann selbst in JPEG um
const PHOTO_MAX_BYTES = 10 * 1024 * 1024
const TITLE_MAX = 60
const TEXT_MAX = 400
const DURATIONS = [
['1w', '1 Woche'], ['2w', '2 Wochen'], ['1m', '1 Monat'], ['3m', '3 Monate'],
['open', 'Bis ich sie abnehme'], ['date', 'Bis zu einem Datum'],
]
const REHANG_DURATIONS = DURATIONS.filter(([key]) => key !== 'date')
const PAPERS = [['sun', 'Sonnengelb'], ['sky', 'Himmelblau'], ['coral', 'Koralle'], ['white', 'Weiß']]
const TYPES = [
['note', 'Text', 'ein Zettel', ''],
['photo', 'Foto', 'ein Polaroid', ''],
['combo', 'Foto + Text', 'beides', ''],
]
function fmtDay(value) {
const d = new Date(value)
if (!value || Number.isNaN(d.getTime())) return ''
return d.toLocaleDateString('de-DE', { timeZone: 'Europe/Berlin', day: '2-digit', month: '2-digit', year: 'numeric' })
}
// "2026-10-01" in der Zeitzone Berlin, plus Tage (fuer min/max des Datumsfelds)
function berlinIsoDay(offsetDays = 0) {
const d = new Date(Date.now() + offsetDays * 86400000)
return d.toLocaleDateString('sv-SE', { timeZone: 'Europe/Berlin' })
}
/* ------------------------------------------------------------ Zustand --- */
// Das im News-Bereich gewaehlte Projekt bleibt beim Wechseln zwischen Kacheln und News erhalten
const selection = { projectId: '' }
const newsOf = (project) => project.features?.news || { state: 'off' }
const isSetup = (state) => state === 'ordered' || state === 'setup'
// Vorauswahl: zuerst ein aktives Projekt, dann eins in Einrichtung, sonst das erste
function pickProject(projects) {
return projects.find((p) => newsOf(p).state === 'active') || projects.find((p) => isSetup(newsOf(p).state)) || projects.find((p) => newsOf(p).state === 'off') || projects[0]
}
/* ------------------------------------------------------------ Kacheln --- */
const ICONS = {
news: '',
calendar: '',
chart: '',
}
const svg = (name) => ``
function newsTile(projects) {
const states = projects.map((p) => newsOf(p).state)
const active = states.filter((s) => s === 'active').length
let pill = ['Aus', 'off']
let cta = `Einrichten lassen · ${PRICE}`
if (active) { pill = ['Aktiv', 'on']; cta = 'Beiträge schreiben' } else if (states.some(isSetup)) { pill = ['In Einrichtung', 'setup']; cta = 'Status ansehen' } else if (states.length && states.every((s) => s === 'disabled')) { pill = ['Abgeschaltet', 'off']; cta = 'Status ansehen' }
const target = pickProject(projects)
const count = projects.length > 1 && active ? `
aktiv für ${active} von ${projects.length} Websites
` : ''
return `
${svg('news')}${pill[0]}
News-Pinnwand
Schreib kurze Neuigkeiten mit Text und Foto – sie hängen wie Zettel an einer Pinnwand auf deiner Website.
${count}
${target
? ``
: '
Sobald deine Website angelegt ist, kannst du die Pinnwand buchen.
'}
`
}
/* Analyse: Kachel je nach Zustand der Websites (comingSoon = Schalter aus) */
const DEV_HINT = 'Im Dev-Modus wird nicht gemessen.'
const ANALYSE_TEXT = 'Sieh, wie viele Besucher deine Website hat und was sie sich ansehen.'
function analyseTile(projects) {
const target = pickAnalyseProject(projects)
const a = target && analyseOf(target)
if (!target || a.comingSoon) return soonTile('chart', 'Analyse', ANALYSE_TEXT)
const state = analyseState(a)
const devHint = state === 'active' && a.devMode ? `
${DEV_HINT}
` : ''
const active = projects.filter((p) => analyseState(analyseOf(p)) === 'active').length
const pill = state === 'active' ? ['Aktiv', 'on'] : state === 'disabled' ? ['Gesperrt', 'off'] : ['Aus', 'off']
let action
let text = `${ANALYSE_TEXT} Ohne Cookies, auf einem Server in Deutschland.`
if (state === 'unknown') action = '
Die Daten konnten gerade nicht geladen werden.
'
else if (state === 'disabled') { text = 'Bitte melde dich bei uns'; action = '' }
else if (state === 'active') action = ``
else if (state === 'off') action = ``
else if (a.reason === 'kein_abo') action = '
Im Wartungs-Abo enthalten.
'
else action = `
${esc(a.reasonText || 'Für diese Website gerade nicht verfügbar.')}
`
const count = projects.length > 1 && active ? `
aktiv für ${active} von ${projects.length} Websites
` : ''
return `
${svg('chart')}${pill[0]}
Analyse
${text}
${count}
${devHint}
${action}
`
}
/* Terminplaner: Kachel nur, wenn der Schalter es erlaubt (termine.verfuegbar), sonst "Kommt bald". Die Ansicht selbst
(termine.js) wird von app.js geladen; hier steht nur die Kachel, damit funktionen.js keinen weiteren Import braucht. */
const TERMINE_TEXT = 'Deine Kundschaft bucht Termine direkt über deine Website.'
const termineOf = (project) => project.features?.termine || { state: 'off' }
// Doppelt zu anfragenText in termine-view.js: funktionen.js darf dort nichts importieren (die Kachel testet test/funktionenTiles.test.js
// mit einem umgebogenen Import). Aenderungen an beiden Stellen machen.
const anfragenText = (n) => (n === 1 ? '1 Anfrage wartet' : `${n} Anfragen warten`)
function pickTermineProject(projects) {
return projects.find((p) => termineOf(p).state === 'active') || projects.find((p) => termineOf(p).state === 'disabled') || projects[0]
}
function termineTile(projects) {
const states = projects.map((p) => termineOf(p).state)
const active = states.filter((s) => s === 'active').length
const target = pickTermineProject(projects)
const disabled = !active && states.length > 0 && states.every((s) => s === 'disabled')
const pill = active ? ['Aktiv', 'on'] : disabled ? ['Abgeschaltet', 'off'] : ['Aus', 'off']
let extra
let action
if (active) {
extra = ''
action = ``
} else if (disabled) {
extra = '
Der Terminplaner ist für diese Website abgeschaltet. Deine Termine kannst du noch ansehen und exportieren.
Wir bauen die Pinnwand in deine Website ${esc(project.projectName)} ein. Danach schreibst du deine Neuigkeiten selbst hier im Kundenbereich – mit Text, Foto und Anzeigedauer.
Einrichtung und Einbau durch uns
Beiträge jederzeit selbst schreiben, ändern und abnehmen
${feature.state === 'ordered' ? 'Sobald deine Zahlung bei uns ankommt, legen wir los.' : 'Sobald sie fertig ist, kannst du hier deine ersten Beiträge schreiben.'}