{% extends 'base_home.html.twig' %}
{% block title %}Panier | MaketOu{% endblock %}
{% block body %}
<!-- Start Banner Area -->
<section class="banner-area organic-breadcrumb">
<div class="container">
<div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
<div class="col-first">
<h1>Panier</h1>
<nav class="d-flex align-items-center">
<a href="{{ path('ui_home') }}">Accueil<span class="lnr lnr-arrow-right"></span></a>
<a href="javascript:void(0)">Panier</a>
</nav>
</div>
</div>
</div>
</section>
<!-- End Banner Area -->
<!--================Cart Area =================-->
<section class="cart_area">
<div class="container">
<div class="row">
<div class="col-lg-8">
<div class="cart_inner">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th scope="col">Produit</th>
<th scope="col">Prix</th>
<th scope="col">Quantité</th>
<th scope="col">Total</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{% for item in items %}
<tr>
<td data-label="Produit">
<div class="cart-item">
{% set image = (item.image is defined and item.image|length > 0) ? item.image : asset('ui/img/category/s-p1.jpg') %}
<img src="{{ image }}" alt="{{ item.name }}" class="cart-item-image">
<div class="cart-item-info">
<a href="{{ path('ui_product_show', { slug: item.slug }) }}" class="cart-item-name">{{ item.name }}</a>
</div>
</div>
</td>
<td data-label="Prix">
<span class="cart-item-price">{{ item.price|number_format(2, '.', ' ') }} HTG</span>
</td>
<td data-label="Quantité">
<div class="quantity-controls">
<button class="quantity-btn quantity-btn-decrease" id="decrease-btn-{{ item.id }}" data-product-id="{{ item.id }}" onclick="handleDecreaseOrRemove({{ item.id }})">
{% if item.qty == 1 %}
<i class="lnr lnr-trash"></i>
{% else %}
<span>−</span>
{% endif %}
</button>
<input type="text" value="{{ item.qty }}" class="quantity-input" id="qty-{{ item.id }}" onchange="updateQuantity({{ item.id }}, this.value)">
<button class="quantity-btn" onclick="changeQuantity({{ item.id }}, 1)">+</button>
</div>
</td>
<td data-label="Total">
<span class="cart-item-total" id="total-{{ item.id }}">{{ (item.price * item.qty)|number_format(2, '.', ' ') }} HTG</span>
</td>
<td data-label="Action">
<button class="remove-btn" onclick="removeFromCart({{ item.id }})">Supprimer</button>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" style="text-align: center; padding: 50px;">
Votre panier est vide.
<br>
<a href="{{ path('ui_listing') }}" style="color: #007185;">Continuer vos achats</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="cart-summary">
<h3>Résumé de la commande</h3>
<div class="cart-subtotal">
<span id="cart-subtotal-label">Sous-total (<span id="cart-item-count">{{ items|length }}</span> article{{ items|length > 1 ? 's' : '' }})</span>
<span id="cart-subtotal">{{ subtotal|number_format(2, '.', ' ') }} HTG</span>
</div>
<div class="cart-total">
<span>Total</span>
<span id="cart-total">{{ subtotal|number_format(2, '.', ' ') }} HTG</span>
</div>
<button class="checkout-btn mt-3" onclick="proceedToCheckout()">Passer la commande</button>
<button class="continue-shopping" onclick="window.location.href='{{ path('ui_listing') }}'">Continuer vos achats</button>
</div>
</div>
</div>
</div>
</section>
<!--================End Cart Area =================-->
<!-- Modal personnalisé -->
<div id="customModal" class="custom-modal">
<div class="custom-modal-overlay"></div>
<div class="custom-modal-content">
<div class="custom-modal-header">
<h3 class="custom-modal-title" id="modalTitle">Titre</h3>
<button class="custom-modal-close" onclick="closeCustomModal()" aria-label="Fermer">
<span>×</span>
</button>
</div>
<div class="custom-modal-body" id="modalBody">
<p id="modalMessage">Message</p>
</div>
<div class="custom-modal-footer" id="modalFooter">
<button class="custom-modal-btn custom-modal-btn-primary" id="modalConfirmBtn" onclick="confirmModalAction()">Confirmer</button>
<button class="custom-modal-btn custom-modal-btn-secondary" id="modalCancelBtn" onclick="closeCustomModal()">Annuler</button>
</div>
</div>
</div>
{% endblock %}
{% block stylesheets %}
<style>
.cart_area {
background-color: #f8f8f8;
padding: 20px 0;
}
.cart_inner {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
.table th {
background-color: #f3f3f3;
border-bottom: 2px solid #ddd;
font-weight: 600;
color: #333;
padding: 15px;
}
.table td {
vertical-align: middle;
padding: 15px;
}
.cart-item {
display: flex;
align-items: center;
}
.cart-item-image {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
margin-right: 15px;
}
.cart-item-info {
flex-grow: 1;
}
.cart-item-name {
font-weight: 500;
color: #007185;
text-decoration: none;
margin-bottom: 5px;
}
.cart-item-name:hover {
text-decoration: underline;
}
.cart-item-price {
color: #333;
font-size: 18px;
font-weight: 700;
}
.quantity-controls {
display: flex !important;
align-items: center !important;
justify-content: center !important;
border: 1px solid #ddd !important;
border-radius: 4px !important;
width: 120px !important;
height: 40px !important;
position: relative !important;
background: white !important;
overflow: hidden !important;
}
.quantity-controls .quantity-btn {
background: #f8f8f8 !important;
border: none !important;
width: 35px !important;
height: 100% !important;
min-width: 35px !important;
cursor: pointer !important;
font-size: 18px !important;
font-weight: 600 !important;
color: #333 !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
padding: 0 !important;
margin: 0 !important;
line-height: 1 !important;
transition: background-color 0.2s ease !important;
position: relative !important;
z-index: 1 !important;
}
.quantity-controls .quantity-btn:first-child {
border-right: 1px solid #ddd !important;
}
.quantity-controls .quantity-btn:last-child {
border-left: 1px solid #ddd !important;
}
.quantity-controls .quantity-btn:hover {
background: #e7e7e7 !important;
color: #000 !important;
}
.quantity-controls .quantity-btn:active {
background: #d0d0d0 !important;
}
.quantity-controls .quantity-btn-remove {
background: #f8f8f8 !important;
color: #dc3545 !important;
}
.quantity-controls .quantity-btn-remove:hover {
background: #dc3545 !important;
color: white !important;
}
.quantity-controls .quantity-btn-remove i {
font-size: 16px !important;
}
.quantity-controls .quantity-btn-decrease span {
font-size: 24px !important;
font-weight: 300 !important;
line-height: 1 !important;
}
.quantity-controls .quantity-input {
border: none !important;
width: 50px !important;
min-width: 50px !important;
height: 100% !important;
text-align: center !important;
font-size: 15px !important;
font-weight: 500 !important;
padding: 0 !important;
margin: 0 !important;
background: white !important;
color: #333 !important;
outline: none !important;
box-shadow: none !important;
-moz-appearance: textfield !important;
position: relative !important;
z-index: 1 !important;
}
.quantity-controls .quantity-input::-webkit-outer-spin-button,
.quantity-controls .quantity-input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
margin: 0 !important;
}
/* Surcharger les styles externes qui pourraient interférer */
.cart_inner .table tbody tr td .quantity-controls,
.cart_inner .table tbody tr td .quantity-controls .quantity-btn,
.cart_inner .table tbody tr td .quantity-controls .quantity-input {
position: relative !important;
}
.cart_inner .table tbody tr td .quantity-controls .quantity-btn:before,
.cart_inner .table tbody tr td .quantity-controls .quantity-btn:after {
display: none !important;
content: none !important;
}
.cart-item-total {
font-size: 18px;
font-weight: 700;
color: #333;
}
/* Animation pour le badge du panier */
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-5px);
}
60% {
transform: translateY(-3px);
}
}
/* Responsive Styles */
@media (max-width: 991.98px) {
.cart_area {
padding: 15px 0;
}
.cart-summary {
margin-top: 2rem;
}
}
@media (max-width: 767.98px) {
.cart_inner {
border-radius: 0;
}
.table {
font-size: 0.875rem;
}
.table th {
padding: 10px 8px;
font-size: 0.8rem;
}
.table td {
padding: 10px 8px;
}
.cart-item {
flex-direction: column;
align-items: flex-start;
}
.cart-item-image {
width: 60px;
height: 60px;
margin-right: 0;
margin-bottom: 10px;
}
.cart-item-name {
font-size: 0.9rem;
}
.cart-item-price,
.cart-item-total {
font-size: 1rem;
}
.quantity-controls {
width: 110px !important;
height: 38px !important;
}
.quantity-controls .quantity-btn {
width: 32px !important;
font-size: 16px !important;
}
.quantity-controls .quantity-input {
width: 46px !important;
font-size: 14px !important;
}
.cart-summary {
margin-top: 1.5rem;
padding: 1.5rem;
}
.checkout-btn,
.continue-shopping {
width: 100%;
margin-bottom: 0.5rem;
}
}
@media (max-width: 575.98px) {
.table thead {
display: none;
}
.table tbody tr {
display: block;
margin-bottom: 1rem;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
}
.table tbody td {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0;
border: none;
text-align: right;
}
.table tbody td::before {
content: attr(data-label);
font-weight: bold;
text-align: left;
margin-right: 1rem;
}
.table tbody td:first-child {
flex-direction: column;
align-items: flex-start;
}
.cart-item-image {
width: 100%;
height: 200px;
margin-bottom: 1rem;
}
.cart-item-name {
font-size: 1rem;
margin-bottom: 0.5rem;
}
.quantity-controls {
width: 100% !important;
max-width: 150px !important;
margin: 0 auto !important;
justify-content: center !important;
height: 40px !important;
}
.quantity-controls .quantity-btn {
width: 40px !important;
font-size: 18px !important;
}
.quantity-controls .quantity-input {
width: 60px !important;
font-size: 16px !important;
}
.remove-btn {
width: 100%;
margin-top: 0.5rem;
}
}
.remove-btn {
background: #ff9900;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
.remove-btn:hover {
background: #e68900;
}
.cart-summary {
background: white;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
padding: 20px;
height: fit-content;
}
.cart-subtotal {
display: flex;
justify-content: space-between;
padding: 10px 0;
border-bottom: 1px solid #ddd;
margin-bottom: 15px;
}
.cart-total {
font-size: 20px;
font-weight: 700;
color: #333;
}
.checkout-btn {
background: #ffa200;
color: white;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
font-weight: 600;
}
.checkout-btn:hover {
background: #e68900;
}
.checkout-btn:disabled,
.checkout-btn.disabled {
background: #cccccc !important;
color: #666666 !important;
cursor: pointer !important; /* Garder le curseur pointer pour indiquer que c'est cliquable */
opacity: 0.6;
pointer-events: auto !important; /* S'assurer que le clic fonctionne */
}
.checkout-btn:disabled:hover,
.checkout-btn.disabled:hover {
background: #cccccc !important;
transform: none !important;
box-shadow: none !important;
}
.continue-shopping {
background: #095ad3;
color: #ffffff;
border: none;
padding: 12px 24px;
border-radius: 4px;
cursor: pointer;
width: 100%;
font-size: 16px;
font-weight: 600;
margin-top: 10px;
}
.continue-shopping:hover {
background: #e68900;
color: #ffffff
}
/* ============================================
MODAL PERSONNALISÉ
============================================ */
.custom-modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
opacity: 0;
transition: opacity 0.3s ease;
}
.custom-modal.show {
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
}
.custom-modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
}
.custom-modal-content {
position: relative;
background: white;
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow: hidden;
transform: scale(0.9) translateY(-20px);
transition: transform 0.3s ease;
z-index: 10001;
}
.custom-modal.show .custom-modal-content {
transform: scale(1) translateY(0);
}
.custom-modal-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 20px 24px;
border-bottom: 1px solid #e0e0e0;
background: linear-gradient(135deg, #ffa200 0%, #ff9900 100%);
}
.custom-modal-header.error {
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
}
.custom-modal-header.success {
background: linear-gradient(135deg, #28a745 0%, #218838 100%);
}
.custom-modal-header.warning {
background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%);
}
.custom-modal-header.info {
background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
}
.custom-modal-title {
margin: 0;
font-size: 20px;
font-weight: 600;
color: white;
display: flex;
align-items: center;
gap: 10px;
}
.custom-modal-title::before {
content: '';
width: 24px;
height: 24px;
display: inline-block;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
.custom-modal-title.success::before {
content: '✓';
font-size: 20px;
font-weight: bold;
}
.custom-modal-title.error::before {
content: '✕';
font-size: 20px;
font-weight: bold;
}
.custom-modal-title.warning::before {
content: '⚠';
font-size: 20px;
font-weight: bold;
}
.custom-modal-title.info::before {
content: 'ℹ';
font-size: 20px;
font-weight: bold;
}
.custom-modal-close {
background: rgba(255, 255, 255, 0.15);
border: 2px solid rgba(255, 255, 255, 0.3);
color: white;
font-size: 22px;
font-weight: 300;
line-height: 1;
width: 36px;
height: 36px;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
padding: 0;
margin: 0;
position: relative;
overflow: hidden;
}
.custom-modal-close::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
transform: translate(-50%, -50%);
transition: width 0.3s ease, height 0.3s ease;
}
.custom-modal-close:hover {
background: rgba(255, 255, 255, 0.25);
border-color: rgba(255, 255, 255, 0.5);
transform: rotate(90deg) scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.custom-modal-close:hover::before {
width: 100%;
height: 100%;
}
.custom-modal-close:active {
transform: rotate(90deg) scale(0.95);
background: rgba(255, 255, 255, 0.3);
}
.custom-modal-close span {
position: relative;
z-index: 1;
display: block;
line-height: 1;
}
.custom-modal-body {
padding: 24px;
font-size: 16px;
line-height: 1.6;
color: #333;
max-height: 60vh;
overflow-y: auto;
}
.custom-modal-body p {
margin: 0;
}
.custom-modal-footer {
display: flex;
gap: 12px;
justify-content: flex-end;
padding: 16px 24px;
border-top: 1px solid #e0e0e0;
background: #f8f9fa;
}
.custom-modal-footer.single-button {
justify-content: center;
}
.custom-modal-btn {
padding: 10px 24px;
border: none;
border-radius: 6px;
font-size: 15px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
min-width: 100px;
}
.custom-modal-btn-primary {
background: #ffa200;
color: white;
}
.custom-modal-btn-primary:hover {
background: #e68900;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(255, 162, 0, 0.3);
}
.custom-modal-btn-secondary {
background: #6c757d;
color: white;
}
.custom-modal-btn-secondary:hover {
background: #5a6268;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3);
}
.custom-modal-btn-success {
background: #28a745;
color: white;
}
.custom-modal-btn-success:hover {
background: #218838;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.3);
}
.custom-modal-btn-danger {
background: #dc3545;
color: white;
}
.custom-modal-btn-danger:hover {
background: #c82333;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
}
/* Animation pour les modals */
@keyframes modalFadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes modalSlideIn {
from {
transform: scale(0.9) translateY(-20px);
opacity: 0;
}
to {
transform: scale(1) translateY(0);
opacity: 1;
}
}
.custom-modal.show .custom-modal-overlay {
animation: modalFadeIn 0.3s ease;
}
.custom-modal.show .custom-modal-content {
animation: modalSlideIn 0.3s ease;
}
/* Responsive */
@media (max-width: 575.98px) {
.custom-modal-content {
width: 95%;
margin: 20px;
}
.custom-modal-header {
padding: 16px 20px;
}
.custom-modal-title {
font-size: 18px;
}
.custom-modal-body {
padding: 20px;
font-size: 15px;
}
.custom-modal-footer {
flex-direction: column;
padding: 16px 20px;
}
.custom-modal-btn {
width: 100%;
}
}
</style>
{% endblock %}
{% block javascripts %}
<script>
// Variables globales pour le modal
let modalConfirmCallback = null;
let modalCancelCallback = null;
// Fonctions pour gérer le modal personnalisé
function showCustomModal(options) {
const modal = document.getElementById('customModal');
const title = document.getElementById('modalTitle');
const message = document.getElementById('modalMessage');
const footer = document.getElementById('modalFooter');
const confirmBtn = document.getElementById('modalConfirmBtn');
const cancelBtn = document.getElementById('modalCancelBtn');
const header = modal.querySelector('.custom-modal-header');
// Réinitialiser les classes
header.className = 'custom-modal-header';
title.className = 'custom-modal-title';
footer.className = 'custom-modal-footer';
// Définir le titre et le message
title.textContent = options.title || 'Information';
message.textContent = options.message || '';
// Définir le type (success, error, warning, info)
const type = options.type || 'info';
header.classList.add(type);
title.classList.add(type);
// Configurer les boutons
if (options.showCancel !== false) {
cancelBtn.style.display = 'block';
footer.classList.remove('single-button');
} else {
cancelBtn.style.display = 'none';
footer.classList.add('single-button');
}
// Configurer le bouton de confirmation
if (options.confirmText) {
confirmBtn.textContent = options.confirmText;
}
if (options.confirmClass) {
confirmBtn.className = 'custom-modal-btn ' + options.confirmClass;
} else {
confirmBtn.className = 'custom-modal-btn custom-modal-btn-primary';
}
// Stocker les callbacks
modalConfirmCallback = options.onConfirm || null;
modalCancelCallback = options.onCancel || null;
// Afficher le modal
modal.classList.add('show');
document.body.style.overflow = 'hidden';
}
function closeCustomModal() {
const modal = document.getElementById('customModal');
const cancelCallback = modalCancelCallback; // Sauvegarder avant réinitialisation
modal.classList.remove('show');
document.body.style.overflow = '';
// Réinitialiser les callbacks
modalConfirmCallback = null;
modalCancelCallback = null;
// Appeler le callback d'annulation si présent
if (cancelCallback) {
cancelCallback();
}
}
function confirmModalAction() {
if (modalConfirmCallback) {
modalConfirmCallback();
}
closeCustomModal();
}
// Fermer le modal en cliquant sur l'overlay
document.addEventListener('DOMContentLoaded', function() {
const modal = document.getElementById('customModal');
const overlay = modal.querySelector('.custom-modal-overlay');
overlay.addEventListener('click', function() {
closeCustomModal();
});
// Fermer avec la touche Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.classList.contains('show')) {
closeCustomModal();
}
});
});
// Fonctions utilitaires pour différents types de modals
function showAlert(title, message, type = 'info') {
showCustomModal({
title: title,
message: message,
type: type,
showCancel: false,
confirmText: 'OK'
});
}
function showConfirm(title, message, onConfirm, onCancel = null, type = 'warning') {
showCustomModal({
title: title,
message: message,
type: type,
showCancel: true,
confirmText: 'Confirmer',
cancelText: 'Annuler',
onConfirm: onConfirm,
onCancel: onCancel
});
}
function proceedToCheckout() {
// Vérifier qu'il y a des articles dans le panier
if (Object.keys(cartData).length === 0) {
showCustomModal({
title: 'Panier vide',
message: 'Votre panier est vide. Veuillez ajouter des articles avant de procéder à la commande.',
type: 'warning',
showCancel: false,
confirmText: 'OK',
onConfirm: function() {
// Rediriger vers la page de listing pour encourager l'ajout d'articles
window.location.href = '{{ path('ui_listing') }}';
}
});
return;
}
// Rediriger vers la page de checkout
window.location.href = '{{ path('ui_checkout') }}';
}
function changeQuantity(productId, delta) {
const qtyInput = document.getElementById('qty-' + productId);
if (!qtyInput) return;
const currentQty = parseInt(qtyInput.value) || 0;
const newQty = Math.max(0, currentQty + delta);
updateQuantity(productId, newQty);
}
function handleDecreaseOrRemove(productId) {
const qtyInput = document.getElementById('qty-' + productId);
if (!qtyInput) return;
const currentQty = parseInt(qtyInput.value) || 0;
if (currentQty === 1) {
// Si la quantité est 1, supprimer le produit
removeFromCart(productId);
} else {
// Sinon, diminuer la quantité
changeQuantity(productId, -1);
}
}
// Initialiser les données du panier
let cartData = {}; // Stocker les données du panier côté client
{% for item in items %}
{% set image = (item.image is defined and item.image) ? item.image : asset('ui/img/category/s-p1.jpg') %}
cartData[{{ item.id }}] = {
id: {{ item.id }},
name: '{{ item.name }}',
price: {{ item.price }},
qty: {{ item.qty }},
image: '{{ image }}',
slug: '{{ item.slug }}'
};
{% endfor %}
function updateQuantity(productId, newQty) {
const item = cartData[productId];
if (!item) {
console.error('Produit non trouvé:', productId);
return;
}
// Validation de la quantité
newQty = Math.max(0, parseInt(newQty) || 0);
if (newQty === 0) {
removeFromCart(productId);
return;
}
if (newQty > 99) {
showAlert('Quantité maximale', 'La quantité maximale est de 99 articles.', 'warning');
return;
}
// Mettre à jour l'interface immédiatement (optimistic update)
updateCartDisplay(productId, newQty);
// Envoyer la requête au serveur
fetch('{{ path("ui_cart_update") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: 'productId=' + productId + '&qty=' + newQty
})
.then(response => {
console.log('🔍 Réponse HTTP:', response.status, response.statusText);
// Vérifier si la réponse est OK avant de parser
if (!response.ok) {
throw new Error('Erreur HTTP ' + response.status + ': ' + response.statusText);
}
return response.json();
})
.then(data => {
console.log('📦 Données reçues:', data);
if (data.ok) {
// Les données serveur confirment la mise à jour
// Mettre à jour le sous-total avec les valeurs exactes du serveur
if (data.subtotal !== undefined) {
updateSubtotalDisplay(data);
updateCartBadge(data.totalQty || 0);
}
console.log('✅ Panier mis à jour avec succès - Sous-total:', data.subtotal, 'HTG');
} else {
// En cas d'erreur, restaurer la valeur précédente
const oldQty = item.qty;
updateCartDisplay(productId, oldQty);
showAlert('Erreur', data.message || 'Erreur lors de la mise à jour du panier.', 'error');
}
})
.catch(error => {
console.error('❌ Erreur détaillée:', error);
console.error('❌ Type d\'erreur:', error.constructor.name);
console.error('❌ Message d\'erreur:', error.message);
// Restaurer la valeur précédente en cas d'erreur
const oldQty = item.qty;
updateCartDisplay(productId, oldQty);
// Afficher un message d'erreur plus détaillé
let errorMessage = 'Une erreur de connexion est survenue. Veuillez réessayer.';
if (error.message.includes('HTTP')) {
errorMessage = 'Erreur du serveur: ' + error.message;
} else if (error.message.includes('JSON')) {
errorMessage = 'Erreur de traitement des données. Veuillez recharger la page.';
}
showAlert('Erreur de connexion', errorMessage, 'error');
});
}
function updateCartDisplay(productId, qty) {
const item = cartData[productId];
if (!item) return;
// Mettre à jour les données locales
item.qty = qty;
// Mettre à jour la quantité dans l'input
const qtyInput = document.getElementById('qty-' + productId);
if (qtyInput) {
qtyInput.value = qty;
}
// Mettre à jour le bouton de diminution/suppression
const decreaseBtn = document.getElementById('decrease-btn-' + productId);
if (decreaseBtn) {
if (qty === 1) {
// Changer en bouton de suppression (poubelle)
decreaseBtn.innerHTML = '<i class="lnr lnr-trash"></i>';
decreaseBtn.classList.add('quantity-btn-remove');
decreaseBtn.classList.remove('quantity-btn-decrease');
} else {
// Changer en bouton de diminution (moins)
decreaseBtn.innerHTML = '<span>−</span>';
decreaseBtn.classList.add('quantity-btn-decrease');
decreaseBtn.classList.remove('quantity-btn-remove');
}
}
// Mettre à jour le total du produit
const totalElement = document.getElementById('total-' + productId);
if (totalElement) {
const itemTotal = (item.price * qty);
totalElement.textContent = itemTotal.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' HTG';
}
// Mettre à jour les totaux généraux
updateCartSummary();
}
function updateCartTotals(data) {
// Utiliser la fonction unifiée pour mettre à jour les totaux
updateSubtotalDisplay(data);
}
function updateCartSummary() {
// Calculer les totaux côté client depuis cartData
let subtotal = 0;
let totalItems = 0;
Object.values(cartData).forEach(item => {
subtotal += item.price * item.qty;
totalItems += item.qty;
});
// Mettre à jour l'affichage du sous-total
const subtotalElement = document.getElementById('cart-subtotal');
if (subtotalElement) {
subtotalElement.textContent = subtotal.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' HTG';
}
// Mettre à jour l'affichage du total
const totalElement = document.getElementById('cart-total');
if (totalElement) {
totalElement.textContent = subtotal.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' HTG';
}
// Mettre à jour le compteur d'articles
const itemCountElement = document.getElementById('cart-item-count');
if (itemCountElement) {
itemCountElement.textContent = totalItems;
}
// Mettre à jour le label du sous-total
const subtotalLabel = document.getElementById('cart-subtotal-label');
if (subtotalLabel) {
const itemText = totalItems + ' article' + (totalItems > 1 ? 's' : '');
subtotalLabel.innerHTML = 'Sous-total (<span id="cart-item-count">' + totalItems + '</span> ' + (totalItems > 1 ? 'articles' : 'article') + ')';
}
// Mettre à jour l'état du bouton checkout
updateCheckoutButtonState();
}
// Fonction pour mettre à jour l'état du bouton checkout
function updateCheckoutButtonState() {
const checkoutBtn = document.querySelector('.checkout-btn');
const isCartEmpty = Object.keys(cartData).length === 0;
if (checkoutBtn) {
if (isCartEmpty) {
checkoutBtn.classList.add('disabled');
checkoutBtn.title = 'Votre panier est vide';
} else {
checkoutBtn.classList.remove('disabled');
checkoutBtn.title = 'Procéder à la commande';
}
}
}
// Fonction pour mettre à jour le badge du panier
function updateCartBadge(totalQty) {
const cartBadge = document.querySelector('.cart-badge');
if (cartBadge) {
cartBadge.textContent = totalQty || 0;
console.log('🛒 Badge du panier mis à jour:', totalQty);
// Animation du badge si la quantité change
if (totalQty > 0) {
cartBadge.style.animation = 'none';
cartBadge.offsetHeight; // Trigger reflow
cartBadge.style.animation = 'bounce 0.5s ease';
}
}
}
// Fonction améliorée pour mettre à jour le sous-total avec débogage
function updateSubtotalDisplay(data = null) {
// Si des données serveur sont fournies, les utiliser (priorité absolue)
if (data && data.subtotal !== undefined) {
console.log('🔄 Mise à jour sous-total avec données serveur:', data.subtotal, 'HTG');
const subtotalElement = document.getElementById('cart-subtotal');
const totalElement = document.getElementById('cart-total');
const formattedSubtotal = parseFloat(data.subtotal).toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' HTG';
if (subtotalElement) {
subtotalElement.textContent = formattedSubtotal;
}
if (totalElement) {
totalElement.textContent = formattedSubtotal;
}
// Mettre à jour le compteur d'articles
const itemCountElement = document.getElementById('cart-item-count');
if (itemCountElement && data.totalQty !== undefined) {
const totalItems = data.totalQty;
console.log('🔢 Mise à jour compteur articles:', totalItems, 'articles au total');
const itemText = totalItems + ' article' + (totalItems > 1 ? 's' : '');
const subtotalLabel = document.getElementById('cart-subtotal-label');
if (subtotalLabel) {
subtotalLabel.innerHTML = 'Sous-total (<span id="cart-item-count">' + totalItems + '</span> ' + (totalItems > 1 ? 'articles' : 'article') + ')';
console.log('📊 Label mis à jour:', subtotalLabel.innerHTML);
}
}
// Vérification de cohérence (debug)
let clientSubtotal = 0;
let clientTotalItems = 0;
Object.values(cartData).forEach(item => {
clientSubtotal += parseFloat(item.price) * parseInt(item.qty);
clientTotalItems += parseInt(item.qty);
});
const serverSubtotal = parseFloat(data.subtotal);
if (Math.abs(clientSubtotal - serverSubtotal) > 0.01) {
console.warn('⚠️ Incohérence détectée:', {
client: clientSubtotal.toFixed(2),
server: serverSubtotal.toFixed(2),
difference: Math.abs(clientSubtotal - serverSubtotal).toFixed(2)
});
}
} else {
// Calcul de secours côté client (si pas de données serveur)
console.log('🔄 Calcul sous-total côté client (secours)');
let subtotal = 0;
let totalItems = 0;
Object.values(cartData).forEach(item => {
subtotal += parseFloat(item.price) * parseInt(item.qty);
totalItems += parseInt(item.qty);
});
const formattedSubtotal = subtotal.toLocaleString('fr-FR', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
}) + ' HTG';
// Mettre à jour l'affichage du sous-total
const subtotalElement = document.getElementById('cart-subtotal');
if (subtotalElement) {
subtotalElement.textContent = formattedSubtotal;
}
const totalElement = document.getElementById('cart-total');
if (totalElement) {
totalElement.textContent = formattedSubtotal;
}
// Mettre à jour le label du sous-total
const subtotalLabel = document.getElementById('cart-subtotal-label');
if (subtotalLabel) {
const itemText = totalItems + ' article' + (totalItems > 1 ? 's' : '');
subtotalLabel.innerHTML = 'Sous-total (<span id="cart-item-count">' + totalItems + '</span> ' + (totalItems > 1 ? 'articles' : 'article') + ')';
}
}
}
function removeFromCart(productId) {
const item = cartData[productId];
const itemName = item ? item.name : 'cet article';
showConfirm(
'Supprimer l\'article',
`Êtes-vous sûr de vouloir supprimer "${itemName}" de votre panier ?`,
function() {
// Mettre à jour l'interface immédiatement
const row = document.querySelector(`tr:has(#qty-${productId})`);
if (row) {
row.style.opacity = '0.5';
row.style.pointerEvents = 'none';
}
performRemoveFromCart(productId, row);
},
null,
'warning'
);
}
function performRemoveFromCart(productId, row) {
fetch('{{ path("ui_cart_remove") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: 'productId=' + productId
})
.then(response => response.json())
.then(data => {
if (data.ok) {
// Supprimer des données locales
delete cartData[productId];
// Supprimer la ligne du DOM
const row = document.querySelector(`tr:has(#qty-${productId})`);
if (row) {
row.remove();
}
// Mettre à jour les totaux
updateCartTotals(data);
// Mettre à jour le résumé du panier
updateCartSummary();
// Afficher un message de succès
showAlert('Article supprimé', 'L\'article a été supprimé de votre panier avec succès.', 'success');
// Vérifier si le panier est vide
if (Object.keys(cartData).length === 0) {
setTimeout(function() {
location.reload();
}, 1500);
}
} else {
// Restaurer l'affichage en cas d'erreur
if (row) {
row.style.opacity = '1';
row.style.pointerEvents = 'auto';
}
showAlert('Erreur', data.message || 'Erreur lors de la suppression de l\'article.', 'error');
}
})
.catch(error => {
console.error('Erreur:', error);
// Restaurer l'affichage en cas d'erreur réseau
if (row) {
row.style.opacity = '1';
row.style.pointerEvents = 'auto';
}
showAlert('Erreur de connexion', 'Une erreur de connexion est survenue. Veuillez réessayer.', 'error');
});
}
// Script pour forcer l'application des styles des boutons de quantité et initialiser l'état du bouton checkout
document.addEventListener('DOMContentLoaded', function() {
// Initialiser l'état du bouton checkout
updateCheckoutButtonState();
const quantityControls = document.querySelectorAll('.quantity-controls');
quantityControls.forEach(function(control) {
if (control) {
// Forcer les styles sur le conteneur
control.style.display = 'flex';
control.style.alignItems = 'center';
control.style.justifyContent = 'center';
control.style.border = '1px solid #ddd';
control.style.borderRadius = '4px';
control.style.width = '120px';
control.style.height = '40px';
control.style.position = 'relative';
control.style.background = 'white';
control.style.overflow = 'hidden';
// Forcer les styles sur les boutons
const buttons = control.querySelectorAll('.quantity-btn');
buttons.forEach(function(btn, index) {
btn.style.background = '#f8f8f8';
btn.style.border = 'none';
btn.style.width = '35px';
btn.style.height = '100%';
btn.style.cursor = 'pointer';
btn.style.fontSize = '18px';
btn.style.fontWeight = '600';
btn.style.color = '#333';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.padding = '0';
btn.style.margin = '0';
btn.style.lineHeight = '1';
btn.style.position = 'relative';
btn.style.zIndex = '1';
// Ajouter les bordures entre les boutons
if (index === 0) {
btn.style.borderRight = '1px solid #ddd';
} else if (index === buttons.length - 1) {
btn.style.borderLeft = '1px solid #ddd';
}
});
// Forcer les styles sur l'input
const input = control.querySelector('.quantity-input');
if (input) {
input.style.border = 'none';
input.style.width = '50px';
input.style.height = '100%';
input.style.textAlign = 'center';
input.style.fontSize = '15px';
input.style.fontWeight = '500';
input.style.padding = '0';
input.style.margin = '0';
input.style.background = 'white';
input.style.color = '#333';
input.style.outline = 'none';
input.style.boxShadow = 'none';
input.style.position = 'relative';
input.style.zIndex = '1';
}
}
});
});
</script>
{% endblock %}