diff --git a/domme-converters/assets/js/app.js b/domme-converters/assets/js/app.js new file mode 100644 index 0000000..e4995a9 --- /dev/null +++ b/domme-converters/assets/js/app.js @@ -0,0 +1,309 @@ +"use strict"; + +// Data komt uit products.js (window.PRODUCTS / window.CATEGORIES). +const PRODUCTS = window.PRODUCTS || []; +const CATEGORIES = window.CATEGORIES || [{ key: "all", label: "Alles" }]; + +const TAX_RATE = 0.21; + +// Winkelmandje: Map van product-id -> aantal. +const cart = new Map(); +let activeFilter = "all"; + +const els = { + grid: document.getElementById("grid"), + filters: document.getElementById("filters"), + productCount: document.getElementById("productCount"), + overlay: document.getElementById("overlay"), + drawer: document.getElementById("drawer"), + cartItems: document.getElementById("cartItems"), + cartCount: document.getElementById("cartCount"), + cartSub: document.getElementById("cartSub"), + cartTax: document.getElementById("cartTax"), + cartTotal: document.getElementById("cartTotal"), + checkoutBtn: document.getElementById("checkoutBtn"), + modalWrap: document.getElementById("modalWrap"), + modalContent: document.getElementById("modalContent"), + toast: document.getElementById("toast"), + openCart: document.getElementById("openCart"), + closeCart: document.getElementById("closeCart"), +}; + +// Hulpfuncties voor opmaak. +const formatPrice = (value) => "\u20AC" + value.toFixed(2).replace(".", ","); +const escapeHtml = (str) => + String(str).replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); +const stars = (rating) => "\u2605".repeat(rating) + "\u2606".repeat(5 - rating); +const findProduct = (id) => PRODUCTS.find((p) => p.id === id); + +// Tegelinhoud: toon de foto als die er is, val anders terug op de connector-tegel. +// onerror verwijdert een gebroken afbeelding en toont alsnog de connectors. +function thumbInner(p) { + const fallback = ` + ${escapeHtml(p.from)} + + ${escapeHtml(p.to)} + `; + if (p.image) { + return `${escapeHtml(p.name)}`; + } + return fallback; +} + +// Rendering. +function renderFilters() { + els.filters.innerHTML = CATEGORIES.map((c) => + `` + ).join(""); +} + +function visibleProducts() { + return activeFilter === "all" ? PRODUCTS : PRODUCTS.filter((p) => p.category === activeFilter); +} + +function renderCatalog() { + const list = visibleProducts(); + els.productCount.textContent = list.length + " producten" + (activeFilter === "all" ? "" : " in deze categorie"); + els.grid.innerHTML = list.map((p) => { + const fallback = `${escapeHtml(p.from)}${escapeHtml(p.to)}`; + const thumb = p.image + ? `${escapeHtml(p.name)}` + : fallback; + return ` +
+
+ ${thumb} +
+
+
+ ${escapeHtml(p.id)} + ${stars(p.rating)}(${p.reviews}) +
+

${escapeHtml(p.name)}

+

${escapeHtml(p.desc)}

+
+ ${formatPrice(p.price)}${formatPrice(p.old)} + +
+
+
+ `; + }).join(""); +} + +function cartTotals() { + let count = 0; + let subtotal = 0; + for (const [id, qty] of cart) { + const product = findProduct(id); + if (!product) continue; + count += qty; + subtotal += product.price * qty; + } + const tax = subtotal * TAX_RATE; + return { count, subtotal, tax, total: subtotal + tax }; +} + +function renderCart() { + const { count, subtotal, tax, total } = cartTotals(); + els.cartCount.textContent = String(count); + els.cartSub.innerHTML = formatPrice(subtotal); + els.cartTax.innerHTML = formatPrice(tax); + els.cartTotal.innerHTML = formatPrice(total); + els.checkoutBtn.disabled = count === 0; + + if (cart.size === 0) { + els.cartItems.innerHTML = '
Uw winkelmandje is leeg.
'; + return; + } + + const rows = []; + for (const [id, qty] of cart) { + const p = findProduct(id); + if (!p) continue; + const tile = p.image + ? `` + : escapeHtml(p.from); + rows.push(` +
+ +
+ ${escapeHtml(p.name)} + ${formatPrice(p.price)} per stuk +
+ + ${qty} + +
+
+ ${formatPrice(p.price * qty)} +
+ `); + } + els.cartItems.innerHTML = rows.join(""); +} + +// Toast. +let toastTimer; +function showToast(message) { + els.toast.textContent = message; + els.toast.classList.add("show"); + clearTimeout(toastTimer); + toastTimer = setTimeout(() => els.toast.classList.remove("show"), 2200); +} + +// Mandje-operaties. +function addToCart(id) { + const product = findProduct(id); + if (!product) return; + cart.set(id, (cart.get(id) || 0) + 1); + renderCart(); + showToast(product.name + " toegevoegd aan het mandje."); +} + +function stepQuantity(id, step) { + if (!cart.has(id)) return; + const next = cart.get(id) + step; + if (next <= 0) { + cart.delete(id); + } else { + cart.set(id, next); + } + renderCart(); +} + +function clearCart() { + cart.clear(); + renderCart(); +} + +// Drawer. +function openCart() { + els.overlay.classList.add("open"); + els.drawer.classList.add("open"); +} +function closeCart() { + els.overlay.classList.remove("open"); + els.drawer.classList.remove("open"); +} + +// Modal. +function openModal() { els.modalWrap.classList.add("open"); } +function closeModal() { els.modalWrap.classList.remove("open"); } + +function renderCheckoutForm() { + const { total } = cartTotals(); + els.modalContent.innerHTML = ` +

Afrekenen

+

Dit formulier verwerkt geen gegevens. Ingevoerde waarden worden niet opgeslagen of verzonden.

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ +

Demonstratieformulier. Er wordt geen betaling uitgevoerd.

+
+ `; + document.getElementById("payForm").addEventListener("submit", (event) => { + event.preventDefault(); + const form = event.currentTarget; + // Gebruik native validatie in plaats van ongeldige invoer te negeren. + if (!form.checkValidity()) { + form.reportValidity(); + return; + } + renderSuccess(); + }); +} + +function renderSuccess() { + const { count } = cartTotals(); + const orderId = "CL-" + Math.floor(100000 + Math.random() * 900000); + els.modalContent.innerHTML = ` +
+ +

Bestelling geregistreerd

+

${count} fictief artikel(en) zijn vastgelegd in deze demonstratie.

+
Referentie: ${escapeHtml(orderId)}
+

Verwachte levering: niet van toepassing.

+ +
+ `; + clearCart(); + document.getElementById("doneBtn").addEventListener("click", () => { + closeModal(); + showToast("Winkelmandje geleegd."); + }); +} + +// Event-koppeling (delegatie waar lijsten betrokken zijn). +els.filters.addEventListener("click", (event) => { + const button = event.target.closest("[data-filter]"); + if (!button) return; + activeFilter = button.dataset.filter; + renderFilters(); + renderCatalog(); +}); + +els.grid.addEventListener("click", (event) => { + const button = event.target.closest("[data-add]"); + if (button) addToCart(button.dataset.add); +}); + +els.cartItems.addEventListener("click", (event) => { + const button = event.target.closest("[data-step]"); + if (button) stepQuantity(button.dataset.id, Number(button.dataset.step)); +}); + +els.openCart.addEventListener("click", openCart); +els.closeCart.addEventListener("click", closeCart); +els.overlay.addEventListener("click", closeCart); + +els.checkoutBtn.addEventListener("click", () => { + if (cart.size === 0) return; + closeCart(); + renderCheckoutForm(); + openModal(); +}); + +els.modalWrap.addEventListener("click", (event) => { + if (event.target === els.modalWrap) closeModal(); +}); + +document.addEventListener("keydown", (event) => { + if (event.key === "Escape") { + closeModal(); + closeCart(); + } +}); + +// Eerste render. +document.getElementById("year").textContent = String(new Date().getFullYear()); +renderFilters(); +renderCatalog(); +renderCart();