"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 `
`;
}
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
? `
`
: fallback;
return `
${thumb}
${escapeHtml(p.id)}
${stars(p.rating)}(${p.reviews})
${escapeHtml(p.name)}
${escapeHtml(p.desc)}
`;
}).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(`
${tile}
${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.
`;
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();