Shop G1: Verdrahtung #webshop, Webshop-Kachel, Bestellseite mit key und optionaler Zusammenfassung

Dashboard-Abschnitt view-webshop, shop.css und Platzhalter shop.js, app.js (Hash, Refresher, Label, Elternansicht), Kachel in funktionen.js nach shop.verfuegbar, GET /api/funktionen liefert shop.verfuegbar, funktion-bestellen.js nimmt key/name/zusammenfassung (Standard: Terminplaner unverändert).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-05 08:57:02 +00:00
1 parent 0ebd2e6663
commit 809da80c9b
11 files changed
+333 -19

No files matched your search

+55 -1
View File
@@ -71,6 +71,7 @@ const ICONS = {
news: '<path d="M12 17v5"/><path d="M9 10.8a2 2 0 0 1-1.1 1.8l-1.8.9A2 2 0 0 0 5 15.2V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.8a2 2 0 0 0-1.1-1.8l-1.8-.9A2 2 0 0 1 15 10.8V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/>',
calendar: '<rect x="3" y="5" width="18" height="16" rx="3"/><path d="M8 3v4M16 3v4M3 10h18"/>',
chart: '<path d="M4 20V10M10 20V4M16 20v-7M22 20H2"/>',
shop: '<circle cx="9" cy="20" r="1.5"/><circle cx="18" cy="20" r="1.5"/><path d="M2 3h3l2.7 12.4a1 1 0 0 0 1 .8h9.1a1 1 0 0 0 1-.8L21 7H6"/>',
}
const svg = (name) => `<svg class="fn-ico" viewBox="0 0 24 24" aria-hidden="true">${ICONS[name]}</svg>`
@@ -211,6 +212,53 @@ async function showTermineCount(root, projects) {
el.hidden = false
}
/* Webshop: Kachel nur, wenn der Server den Shop fuer diesen Kunden freigibt (shop.verfuegbar der Antwort, Schalter SHOP_ENABLED); sonst
gar keine Kachel (der Shop startet im Pilot und wird nicht vorab angekuendigt). Die Ansicht #webshop (shop.js) zeigt die Anfrage und ihren
Stand; die Kachel kennt nur den Zustand des Funktionsdokuments (projects[].features.shop: off | ordered | setup | active | disabled) und
baut sich inline wie die Terminplaner-Kachel, damit funktionen.js keinen weiteren Import braucht. Der Preis kommt aus dem Register
(features.shop.priceLabel), Rueckfall 299 €. */
const SHOP_PREIS = '299 €'
const shopOf = (project) => project.features?.shop || { state: 'off' }
// Zielprojekt: aktiv, dann ordered/setup, dann abgeschaltet, dann das erste
function pickShopProject(projects) {
return projects.find((p) => shopOf(p).state === 'active')
|| projects.find((p) => isSetup(shopOf(p).state))
|| projects.find((p) => shopOf(p).state === 'disabled')
|| projects[0]
}
function shopTile(projects, features) {
const preis = textOr(features?.shop?.priceLabel, SHOP_PREIS)
const active = projects.filter((p) => shopOf(p).state === 'active').length
const target = pickShopProject(projects)
const state = target ? shopOf(target).state : 'off'
const knopf = (text) => `<button type="button" class="kb-btn-primary fn-tile-cta" data-view="webshop" data-project="${esc(target.projectId)}">${text}</button>`
// Zustand -> Pille und Knopf (aus: noch keine Funktion, die Anfrage selbst zeigt die Ansicht #webshop)
const ZUSTAENDE = {
off: [['Aus', 'off'], 'Webshop anfragen'],
ordered: [['Zahlung offen', 'setup'], 'Zahlung offen'],
setup: [['In Einrichtung', 'setup'], 'In Einrichtung'],
active: [['Aktiv', 'on'], 'Webshop öffnen'],
disabled: [['Abgeschaltet', 'off'], 'Webshop ansehen'],
}
const [pill, knopfText] = ZUSTAENDE[state] || ZUSTAENDE.off
const action = target ? knopf(knopfText) : ''
let extra = ''
if (!target) extra = '<p class="fn-count">Sobald deine Website angelegt ist, kannst du den Webshop anfragen.</p>'
else if (state === 'disabled') extra = '<p>Der Webshop ist für diese Website abgeschaltet. Deine Bestellungen kannst du noch ansehen.</p>'
const count = projects.length > 1 && active ? `<p class="fn-count">aktiv für ${active} von ${projects.length} Websites</p>` : ''
return `
<article class="fn-tile" data-fn-tile="shop">
<div class="fn-tile-head">${svg('shop')}<span class="fn-pill fn-pill-${pill[1]}">${pill[0]}</span></div>
<h3>Webshop</h3>
<p>Dein eigener Online-Shop auf Deiner Website. Zahlungen gehen direkt auf Dein Stripe-Konto. ${esc(preis)} einmalig.</p>
${extra}
${count}
${action}
</article>`
}
const soonTile = (icon, title, text) => `
<article class="fn-tile fn-tile-soon">
<div class="fn-tile-head">${svg(icon)}<span class="fn-pill fn-pill-soon">Kommt bald</span></div>
@@ -220,11 +268,12 @@ const soonTile = (icon, title, text) => `
export async function initFunktionen(root) {
if (!root) return
const { projects = [], termine, features } = await request('/api/funktionen')
const { projects = [], termine, features, shop } = await request('/api/funktionen')
root.innerHTML = `
<div class="fn-tiles">
${newsTile(projects, newsOffer(features).price)}
${termine?.verfuegbar ? termineTile(projects, termine) : soonTile('calendar', 'Terminplaner', TERMINE_TEXT)}
${shop?.verfuegbar ? shopTile(projects, features) : ''}
${analyseTile(projects)}
</div>`
root.querySelector('[data-fn-tile="news"] .fn-tile-cta')?.addEventListener('click', (ev) => { selection.projectId = ev.currentTarget.dataset.project })
@@ -234,6 +283,11 @@ export async function initFunktionen(root) {
const body = document.getElementById('termine-body')
if (body) body.dataset.wunschProjekt = ev.currentTarget.dataset.project
})
// Die Webshop-Ansicht liest das gewaehlte Projekt aus ihrem Behaelter (shop.js, initShop)
root.querySelector('[data-fn-tile="shop"] .fn-tile-cta[data-project]')?.addEventListener('click', (ev) => {
const body = document.getElementById('webshop-body')
if (body) body.dataset.wunschProjekt = ev.currentTarget.dataset.project
})
if (termine?.verfuegbar) await showTermineCount(root, projects)
}