144 lines
4.4 KiB
JavaScript
144 lines
4.4 KiB
JavaScript
// Öffentliche Ansicht einer Liste über /s/<token>. 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();
|
||
}
|