Erste Produktivversion
This commit is contained in:
143
web/html/js/views/public.js
Normal file
143
web/html/js/views/public.js
Normal file
@@ -0,0 +1,143 @@
|
||||
// Ö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();
|
||||
}
|
||||
Reference in New Issue
Block a user