// Öffentliche Ansicht einer Liste über /s/. Kein Konto nötig. // Nur ansehen und - sofern erlaubt - abhaken. Auf Druck ausgelegt. "use strict"; import { get, post } from "../api.js"; import { clear, el, euro, formatPack, mount } from "../dom.js"; function formatDate(iso) { return new Date(iso).toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", year: "numeric", }); } export async function publicView(root, { token }) { let view = null; let banner = null; async function load() { view = await get(`/api/public/${encodeURIComponent(token)}`); } function say(message, kind = "error") { banner = { message, kind }; render(); setTimeout(() => { banner = null; render(); }, 6000); } function itemRow(item) { const bought = item.status === "bought"; const check = view.allow_check ? el("button.check", { type: "button", "aria-pressed": bought ? "true" : "false", title: bought ? "Als offen markieren" : "Als gekauft markieren", onclick: async () => { const next = bought ? "open" : "bought"; item.status = next; // sofort anzeigen render(); try { await post(`/api/public/${encodeURIComponent(token)}/items/${item.id}`, { status: next }); await load(); } catch (err) { await load().catch(() => {}); say(err.message); } render(); }, }, bought ? "✓" : "") : el("span.check.readonly", { "aria-hidden": "true" }, bought ? "✓" : ""); const pack = formatPack(item.pack_size, item.pack_unit); return el("li", { className: `item${bought ? " bought" : ""}` }, el("div.item-main", {}, check, el("span", { className: `item-count${item.count > 1 ? " many" : ""}`, }, `${item.count || 1}\u00d7`), el("div.item-text", {}, el("span.name", {}, item.article_name), pack || item.variant || item.note ? el("span.sub", {}, [pack, item.variant, item.note].filter(Boolean).join(" · ")) : null), item.price_cents ? el("span.price-static", {}, euro(item.total_cents ?? item.price_cents)) : null ) ); } function render() { mount(root, el("header.public-head", {}, el("h1", {}, view.list_name), el("p.sub", {}, "Geteilte Ansicht", view.allow_check ? " – du kannst Artikel abhaken." : " – nur zum Ansehen.", ` Gültig bis ${formatDate(view.expires_at)}.`) ), banner ? el("p.error", {}, banner.message) : null, view.markets.length ? view.markets.map((market) => el("section.market", {}, el("h2", {}, el("span", {}, market.market_name), el("span.market-meta", {}, market.open_count === 1 ? "1 offen" : `${market.open_count} offen`, market.total_cents ? ` · ${euro(market.total_cents)}` : "")), market.categories.map((cat) => el("div.category", {}, el("h3", {}, cat.category_name), el("ul.items", {}, cat.items.map(itemRow)))))) : el("p.empty", {}, "Die Liste ist leer."), el("footer.summary", {}, el("div.total", {}, el("span", {}, "Gesamt"), el("strong", {}, euro(view.grand_total_cents)))), el("p.footnote.no-print", {}, // Eigene Druckseite statt window.print(): Sie ist auf A4 // ausgelegt und funktioniert auch, wenn der Empfänger die // Ansicht nur weiterleiten will. el("a.linklike", { href: `/api/public/${encodeURIComponent(token)}/print`, target: "_blank", rel: "noopener", }, "Drucken"), " · ", el("button.linklike", { type: "button", onclick: async () => { try { await load(); render(); } catch (err) { say(err.message); } }, }, "Aktualisieren")) ); } try { await load(); } catch (err) { mount(root, el("section.card", {}, el("h1", {}, "Liste nicht verfügbar"), el("p.error", {}, err.message), el("p.lead", {}, "Bitte die Person, die den Link geteilt hat, um einen neuen.")) ); return; } render(); }