release: dev -> main (P1 conception v0.2 + front P5 + admin shell) #1
2 changed files with 237 additions and 0 deletions
138
src/public/borne/assets/js/page-cart.js
Normal file
138
src/public/borne/assets/js/page-cart.js
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* page-cart.js — Shopping cart screen.
|
||||
*
|
||||
* Displays all cart lines with quantity controls and totals.
|
||||
*
|
||||
* TVA: 10% (taux normal restauration, France 2024 — simplification MVPp).
|
||||
* TODO: verify exact applicable TVA rate with an accountant in P3.
|
||||
* The real rate depends on sur-place vs a-emporter, alcohol content, etc.
|
||||
*
|
||||
* The total displayed is TTC (tax inclusive) because French consumer law
|
||||
* requires prices shown to end-consumers to include all taxes.
|
||||
*/
|
||||
|
||||
import { getCart, removeFromCart, updateQuantity, getTotalCents, clearCart, formatPrice } from './state.js';
|
||||
import { refreshCartBadge } from './nav.js';
|
||||
|
||||
/* TVA rate used for display breakdown only — stored prices are already TTC */
|
||||
const TVA_RATE = 0.10;
|
||||
|
||||
const cartList = document.getElementById('cart-list');
|
||||
const emptyBlock = document.getElementById('cart-empty');
|
||||
const summaryBlock= document.getElementById('cart-summary');
|
||||
const totalTTC = document.getElementById('total-ttc');
|
||||
const totalHT = document.getElementById('total-ht');
|
||||
const totalTVA = document.getElementById('total-tva');
|
||||
const payBtn = document.getElementById('pay-btn');
|
||||
const abandonBtn = document.getElementById('abandon-btn');
|
||||
|
||||
function renderCart() {
|
||||
const items = getCart();
|
||||
refreshCartBadge();
|
||||
|
||||
if (!items.length) {
|
||||
cartList.innerHTML = '';
|
||||
emptyBlock.hidden = false;
|
||||
summaryBlock.hidden = true;
|
||||
if (payBtn) payBtn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
emptyBlock.hidden = false; /* keep structure; toggle via class */
|
||||
emptyBlock.hidden = true;
|
||||
summaryBlock.hidden = false;
|
||||
if (payBtn) payBtn.disabled = false;
|
||||
|
||||
cartList.innerHTML = '';
|
||||
items.forEach((item, index) => {
|
||||
const lineTotalCents = item.prix_cents * item.quantite;
|
||||
|
||||
const row = document.createElement('li');
|
||||
row.className = 'cart-line';
|
||||
row.setAttribute('aria-label', `${item.libelle}, quantite ${item.quantite}`);
|
||||
|
||||
row.innerHTML = `
|
||||
<img
|
||||
class="cart-line__image"
|
||||
src="${item.image}"
|
||||
alt="${item.libelle}"
|
||||
onerror="this.src='assets/images/ui/logo.png'; this.alt='Image non disponible';"
|
||||
>
|
||||
<div class="cart-line__info">
|
||||
<span class="cart-line__name">${item.libelle}</span>
|
||||
<span class="cart-line__unit-price">${formatPrice(item.prix_cents)} / unite</span>
|
||||
</div>
|
||||
<div class="cart-line__qty" role="group" aria-label="Quantite de ${item.libelle}">
|
||||
<button
|
||||
class="qty-btn qty-btn--minus"
|
||||
data-index="${index}"
|
||||
aria-label="Diminuer la quantite de ${item.libelle}"
|
||||
type="button"
|
||||
>-</button>
|
||||
<span class="qty-value" aria-live="polite">${item.quantite}</span>
|
||||
<button
|
||||
class="qty-btn qty-btn--plus"
|
||||
data-index="${index}"
|
||||
aria-label="Augmenter la quantite de ${item.libelle}"
|
||||
type="button"
|
||||
>+</button>
|
||||
</div>
|
||||
<span class="cart-line__total">${formatPrice(lineTotalCents)}</span>
|
||||
<button
|
||||
class="cart-line__remove"
|
||||
data-index="${index}"
|
||||
aria-label="Supprimer ${item.libelle} du panier"
|
||||
type="button"
|
||||
>
|
||||
<img src="assets/images/ui/trash.png" alt="" aria-hidden="true" width="24" height="24">
|
||||
</button>
|
||||
`;
|
||||
cartList.appendChild(row);
|
||||
});
|
||||
|
||||
/* Attach event listeners after render */
|
||||
cartList.querySelectorAll('.qty-btn--minus').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.index, 10);
|
||||
const cart = getCart();
|
||||
updateQuantity(idx, cart[idx].quantite - 1);
|
||||
renderCart();
|
||||
});
|
||||
});
|
||||
|
||||
cartList.querySelectorAll('.qty-btn--plus').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.index, 10);
|
||||
const cart = getCart();
|
||||
updateQuantity(idx, cart[idx].quantite + 1);
|
||||
renderCart();
|
||||
});
|
||||
});
|
||||
|
||||
cartList.querySelectorAll('.cart-line__remove').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const idx = parseInt(btn.dataset.index, 10);
|
||||
removeFromCart(idx);
|
||||
renderCart();
|
||||
});
|
||||
});
|
||||
|
||||
/* Update totals */
|
||||
const ttcCents = getTotalCents();
|
||||
/* Back-calculate HT from TTC (prices assumed to be TTC already) */
|
||||
const htCents = Math.round(ttcCents / (1 + TVA_RATE));
|
||||
const tvaCents = ttcCents - htCents;
|
||||
|
||||
if (totalTTC) totalTTC.textContent = formatPrice(ttcCents);
|
||||
if (totalHT) totalHT.textContent = formatPrice(htCents);
|
||||
if (totalTVA) totalTVA.textContent = formatPrice(tvaCents);
|
||||
}
|
||||
|
||||
if (abandonBtn) {
|
||||
abandonBtn.addEventListener('click', () => {
|
||||
clearCart();
|
||||
window.location.href = 'categories.html';
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', renderCart);
|
||||
99
src/public/borne/cart.html
Normal file
99
src/public/borne/cart.html
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="description" content="Wakdo - Votre panier">
|
||||
<title>Wakdo - Panier</title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body class="cart-page">
|
||||
|
||||
<!--
|
||||
cart.html — Shopping cart.
|
||||
page-cart.js renders all cart lines, handles qty controls and removal.
|
||||
|
||||
TVA: 10% (restauration France 2024 — simplified rate).
|
||||
TODO: verify exact rate with accountant in P3 — actual rate depends
|
||||
on sur-place vs a-emporter and product type (alcohol, etc.).
|
||||
|
||||
The stored prices are TTC. HT is back-calculated at display time only.
|
||||
-->
|
||||
|
||||
<header class="site-header">
|
||||
<a
|
||||
class="site-header__back"
|
||||
href="categories.html"
|
||||
aria-label="Continuer mes achats"
|
||||
>
|
||||
← Continuer
|
||||
</a>
|
||||
<img
|
||||
class="site-header__logo"
|
||||
src="assets/images/ui/logo.png"
|
||||
alt="Wakdo"
|
||||
>
|
||||
<span class="mode-badge site-header__mode" data-mode-badge aria-label="Mode de consommation">Sur place</span>
|
||||
</header>
|
||||
|
||||
<main class="cart-main" aria-label="Votre panier">
|
||||
|
||||
<h1 class="cart-main__heading">Votre panier</h1>
|
||||
|
||||
<!-- Empty cart state -->
|
||||
<div id="cart-empty" class="cart-empty" hidden>
|
||||
<p class="cart-empty__message">Votre panier est vide.</p>
|
||||
<a class="btn btn--secondary" href="categories.html">Decouvrir nos produits</a>
|
||||
</div>
|
||||
|
||||
<!-- Cart lines -->
|
||||
<ul id="cart-list" class="cart-list" aria-label="Lignes du panier">
|
||||
<!-- Filled by page-cart.js -->
|
||||
</ul>
|
||||
|
||||
<!-- Order summary -->
|
||||
<aside id="cart-summary" class="cart-summary" hidden aria-label="Recapitulatif de commande">
|
||||
<div class="cart-summary__line">
|
||||
<span>Total HT</span>
|
||||
<span id="total-ht">—</span>
|
||||
</div>
|
||||
<div class="cart-summary__line">
|
||||
<!-- TVA 10% — taux restauration FR 2024 (simplifie, voir commentaire ci-dessus) -->
|
||||
<span>TVA (10%)</span>
|
||||
<span id="total-tva">—</span>
|
||||
</div>
|
||||
<div class="cart-summary__line cart-summary__line--total">
|
||||
<span>Total TTC</span>
|
||||
<strong id="total-ttc">—</strong>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="cart-actions">
|
||||
<button
|
||||
id="abandon-btn"
|
||||
class="btn btn--secondary"
|
||||
type="button"
|
||||
aria-label="Abandonner la commande et retourner aux categories"
|
||||
>
|
||||
Abandonner
|
||||
</button>
|
||||
<a
|
||||
id="pay-btn"
|
||||
class="btn btn--primary"
|
||||
href="payment.html"
|
||||
role="button"
|
||||
aria-label="Passer au paiement"
|
||||
aria-disabled="true"
|
||||
>
|
||||
Valider ma commande
|
||||
</a>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
|
||||
<script type="module" src="assets/js/nav.js"></script>
|
||||
<script type="module" src="assets/js/page-cart.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Reference in a new issue