Files
vans-diy-bastelbedarf/src/pages/warenkorb/index.astro
T
qcigano 993c4f9265 Warenkorb: Positionsliste steht jetzt auch in der Zusammenfassung
Die Zwischensumme-Box zeigte bisher nur die Endsumme, ohne die
einzelnen Artikel aufzulisten. Jetzt steht dort — genau wie beim
Checkout — eine kompakte Rechnungsübersicht (Menge × Artikel →
Zeilenpreis) direkt über der Zwischensumme.

Verifiziert per Puppeteer mit zwei verschiedenen Artikeln im Warenkorb:
Positionsliste zeigt korrekte Mengen/Preise, keine Konsolenfehler.
2026-08-02 20:15:14 +02:00

182 lines
9.1 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
import Layout from "../../layouts/Layout.astro";
import type { Locale } from "../../i18n/config";
import { useTranslations } from "../../i18n/ui";
const lang: Locale = "de";
const t = useTranslations(lang);
---
<Layout title="Warenkorb" description="Dein Warenkorb bei Van's DIY & Bastelbedarf." lang={lang} path="/warenkorb/">
<section class="section-tight">
<div class="container">
<span class="eyebrow">{t.cart.eyebrow}</span>
<h1>{t.cart.title}</h1>
</div>
</section>
<section class="section-tight">
<div class="container">
<div id="cart-empty" class="card" style="display:none;">
<p>{t.cart.empty}</p>
<a class="btn btn-primary" href="/shop/">{t.common.browseShop}</a>
</div>
<div id="cart-content" class="cart-layout" style="display:none;">
<div class="card cart-items" id="cart-items"></div>
<aside class="card cart-summary">
<h3>🧾 {t.cart.subtotal}</h3>
<div id="summary-items" class="checkout-summary"></div>
<div class="summary-row"><span>{t.cart.subtotal}</span><span id="sum-subtotal">0,00 €</span></div>
<div class="summary-row discount-row" id="discount-row" style="display:none;"><span>{t.cart.discount}</span><span id="sum-discount">-0,00 €</span></div>
<div class="coupon-row">
<label for="coupon-input">{t.cart.couponLabel}</label>
<div class="coupon-input-group">
<input type="text" id="coupon-input" placeholder={t.cart.couponPlaceholder} />
<button type="button" id="coupon-apply" class="btn btn-outline btn-sm">{t.cart.couponApply}</button>
</div>
<p class="small" id="coupon-status"></p>
</div>
<div class="ship-country-row">
<label for="cart-land">📦 {t.checkout.country}</label>
<select id="cart-land">
<option value="de">Deutschland</option>
<option value="at">Österreich</option>
<option value="ch">Schweiz</option>
<option value="lu">Luxemburg</option>
</select>
</div>
<div class="summary-row"><span>{t.cart.shipping}</span><span id="sum-shipping">{t.cart.shippingCalculated}</span></div>
<p class="free-shipping-badge" id="shipping-hint"></p>
<hr class="divider" />
<div class="summary-row total"><span>{t.cart.total}</span><span id="sum-total">0,00 €</span></div>
<p class="small">{t.common.vatNote}</p>
<a class="btn btn-primary btn-block" href="/checkout/">{t.cart.toCheckout}</a>
</aside>
</div>
</div>
</section>
</Layout>
<script define:vars={{ freeShipFrom: 75, removeLabel: t.cart.remove, perItemLabel: t.cart.perItem, remainingTemplate: t.cart.remaining("__V__"), freeShippingText: t.cart.freeShipping, freeLabel: t.checkout.free, cartLang: lang, mengenrabattTemplate: t.cart.mengenrabatt("__P__"), couponInvalid: t.cart.couponInvalid, couponAppliedTemplate: t.cart.couponApplied("__C__"), couponRemoveLabel: t.cart.couponRemove }}>
// WICHTIG: define:vars-Skripte laufen als IIFE, keine "import"-Anweisungen möglich.
window.__cartVars = { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel };
</script>
<script type="module">
import { getCart, updateQuantity, removeFromCart, cartTotal, appliedCoupon, couponDiscount, setCouponCode, clearCoupon } from "../../scripts/cart";
import { formatPrice } from "../../i18n/format";
import { getProduct, products } from "../../data/products";
import { berechneVersandkosten } from "../../data/versand";
import { effektiverZeilenpreis, mengenrabattProzent } from "../../data/rabatt";
const { freeShipFrom, removeLabel, perItemLabel, remainingTemplate, freeShippingText, freeLabel, cartLang, mengenrabattTemplate, couponInvalid, couponAppliedTemplate, couponRemoveLabel } = window.__cartVars;
const landSelect = document.getElementById("cart-land");
function render() {
const cart = getCart();
const empty = document.getElementById("cart-empty");
const content = document.getElementById("cart-content");
const itemsEl = document.getElementById("cart-items");
if (cart.length === 0) {
empty.style.display = "block";
content.style.display = "none";
return;
}
empty.style.display = "none";
content.style.display = "grid";
const zeilen = cart.map((i) => {
const produkt = getProduct(i.slug);
const zeilenpreis = produkt ? effektiverZeilenpreis(produkt, i.menge) : i.menge * i.preis;
const mengenProzent = produkt ? mengenrabattProzent(produkt, i.menge) : 0;
return { item: i, produkt, zeilenpreis, mengenProzent };
});
itemsEl.innerHTML = zeilen.map(({ item: i, produkt, zeilenpreis, mengenProzent }) => {
const foto = produkt?.bilder?.[0];
const thumb = foto
? `<img class="thumb product-photo" src="${foto}" alt="${i.name}" loading="lazy" />`
: `<div class="img-placeholder thumb" role="img" aria-label="${i.name}"></div>`;
const mengenBadge = mengenProzent > 0 ? `<div class="small mengenrabatt-badge">${mengenrabattTemplate.replace("__P__", mengenProzent)}</div>` : "";
return `
<div class="cart-item">
${thumb}
<div class="info">
<strong>${i.name}</strong>
<div class="small">${formatPrice(zeilenpreis / i.menge, cartLang)} ${perItemLabel}</div>
${mengenBadge}
</div>
<div class="qty-controls">
<button data-action="dec" data-slug="${i.slug}" aria-label=""></button>
<span>${i.menge}</span>
<button data-action="inc" data-slug="${i.slug}" aria-label="+">+</button>
</div>
<div class="line-total">${formatPrice(zeilenpreis, cartLang)}</div>
<a href="#" class="remove" data-action="remove" data-slug="${i.slug}">✕ ${removeLabel}</a>
</div>
`;
}).join("");
// Rechnungs-Übersicht in der Zusammenfassung — dieselbe Positionsliste wie im Warenkorb
// links, nur kompakt als "Beleg" (Menge × Name → Zeilenpreis), passend zum Checkout.
document.getElementById("summary-items").innerHTML = zeilen.map(({ item: i, zeilenpreis }) =>
`<div class="row"><span>${i.menge}× ${i.name}</span><span>${formatPrice(zeilenpreis, cartLang)}</span></div>`
).join("");
const subtotal = cartTotal();
const remaining = Math.max(0, freeShipFrom - subtotal);
const shipping = berechneVersandkosten(landSelect.value, cart.map((i) => ({ slug: i.slug, preis: i.preis, menge: i.menge })), products);
const coupon = appliedCoupon();
const discount = couponDiscount(subtotal);
const total = Math.max(0, subtotal - discount) + shipping;
document.getElementById("sum-subtotal").textContent = formatPrice(subtotal, cartLang);
document.getElementById("sum-shipping").textContent = shipping === 0 ? freeLabel : formatPrice(shipping, cartLang);
document.getElementById("sum-total").textContent = formatPrice(total, cartLang);
document.getElementById("shipping-hint").textContent =
remaining > 0 ? remainingTemplate.replace("__V__", formatPrice(remaining, cartLang)) : `🩵 ${freeShippingText}`;
const discountRow = document.getElementById("discount-row");
const couponStatus = document.getElementById("coupon-status");
const couponInput = document.getElementById("coupon-input");
if (coupon && discount > 0) {
discountRow.style.display = "flex";
document.getElementById("sum-discount").textContent = "-" + formatPrice(discount, cartLang);
couponStatus.innerHTML = `✅ ${couponAppliedTemplate.replace("__C__", coupon.code)} <a href="#" id="coupon-clear">✕ ${couponRemoveLabel}</a>`;
couponInput.value = coupon.code;
const clearLink = document.getElementById("coupon-clear");
if (clearLink) clearLink.addEventListener("click", (e) => { e.preventDefault(); clearCoupon(); render(); });
} else {
discountRow.style.display = "none";
couponStatus.textContent = couponInput.dataset.invalid === "1" ? couponInvalid : "";
}
itemsEl.querySelectorAll("button, a.remove").forEach((el) => {
el.addEventListener("click", (e) => {
e.preventDefault();
const target = e.currentTarget;
const slug = target.dataset.slug;
const action = target.dataset.action;
const item = getCart().find((i) => i.slug === slug);
if (!item) return;
if (action === "inc") updateQuantity(slug, item.menge + 1);
if (action === "dec") updateQuantity(slug, item.menge - 1);
if (action === "remove") removeFromCart(slug);
render();
});
});
}
document.getElementById("coupon-apply").addEventListener("click", () => {
const input = document.getElementById("coupon-input");
const code = input.value.trim();
if (!code) { clearCoupon(); render(); return; }
setCouponCode(code);
input.dataset.invalid = appliedCoupon() ? "0" : "1";
render();
});
render();
window.addEventListener("cart:changed", render);
landSelect.addEventListener("change", render);
</script>