<?php
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Extension\CoreExtension;
use Twig\Extension\SandboxExtension;
use Twig\Markup;
use Twig\Sandbox\SecurityError;
use Twig\Sandbox\SecurityNotAllowedTagError;
use Twig\Sandbox\SecurityNotAllowedFilterError;
use Twig\Sandbox\SecurityNotAllowedFunctionError;
use Twig\Source;
use Twig\Template;
use Twig\TemplateWrapper;
/* home/cart.html.twig */
class __TwigTemplate_120158e7ea77c8a776f55513944d6d80 extends Template
{
private Source $source;
/**
* @var array<string, Template>
*/
private array $macros = [];
public function __construct(Environment $env)
{
parent::__construct($env);
$this->source = $this->getSourceContext();
$this->blocks = [
'title' => [$this, 'block_title'],
'body' => [$this, 'block_body'],
'stylesheets' => [$this, 'block_stylesheets'],
'javascripts' => [$this, 'block_javascripts'],
];
}
protected function doGetParent(array $context): bool|string|Template|TemplateWrapper
{
// line 1
return "base_home.html.twig";
}
protected function doDisplay(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "home/cart.html.twig"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "template", "home/cart.html.twig"));
$this->parent = $this->load("base_home.html.twig", 1);
yield from $this->parent->unwrap()->yield($context, array_merge($this->blocks, $blocks));
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
}
// line 3
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_title(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "title"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "title"));
yield "Panier | MaketOu";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 5
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_body(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "body"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "body"));
// line 6
yield " <!-- 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=\"";
// line 13
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_home");
yield "\">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>
";
// line 40
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(($context["items"] ?? null));
$context['_iterated'] = false;
foreach ($context['_seq'] as $context["_key"] => $context["item"]) {
// line 41
yield " <tr>
<td data-label=\"Produit\">
<div class=\"cart-item\">
";
// line 44
$context["image"] = (((CoreExtension::getAttribute($this->env, $this->source, $context["item"], "image", [], "any", true, true, false, 44) && (Twig\Extension\CoreExtension::length($this->env->getCharset(), CoreExtension::getAttribute($this->env, $this->source, $context["item"], "image", [], "any", false, false, false, 44)) > 0))) ? (CoreExtension::getAttribute($this->env, $this->source, $context["item"], "image", [], "any", false, false, false, 44)) : ($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("ui/img/category/s-p1.jpg")));
// line 45
yield " <img src=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["image"] ?? null), "html", null, true);
yield "\" alt=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "name", [], "any", false, false, false, 45), "html", null, true);
yield "\" class=\"cart-item-image\">
<div class=\"cart-item-info\">
<a href=\"";
// line 47
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_product_show", ["slug" => CoreExtension::getAttribute($this->env, $this->source, $context["item"], "slug", [], "any", false, false, false, 47)]), "html", null, true);
yield "\" class=\"cart-item-name\">";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "name", [], "any", false, false, false, 47), "html", null, true);
yield "</a>
</div>
</div>
</td>
<td data-label=\"Prix\">
<span class=\"cart-item-price\">";
// line 52
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "price", [], "any", false, false, false, 52), 2, ".", " "), "html", null, true);
yield " HTG</span>
</td>
<td data-label=\"Quantité\">
<div class=\"quantity-controls\">
<button class=\"quantity-btn quantity-btn-decrease\" id=\"decrease-btn-";
// line 56
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 56), "html", null, true);
yield "\" data-product-id=\"";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 56), "html", null, true);
yield "\" onclick=\"handleDecreaseOrRemove(";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 56), "html", null, true);
yield ")\">
";
// line 57
if ((CoreExtension::getAttribute($this->env, $this->source, $context["item"], "qty", [], "any", false, false, false, 57) == 1)) {
// line 58
yield " <i class=\"lnr lnr-trash\"></i>
";
} else {
// line 60
yield " <span>−</span>
";
}
// line 62
yield " </button>
<input type=\"text\" value=\"";
// line 63
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "qty", [], "any", false, false, false, 63), "html", null, true);
yield "\" class=\"quantity-input\" id=\"qty-";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 63), "html", null, true);
yield "\" onchange=\"updateQuantity(";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 63), "html", null, true);
yield ", this.value)\">
<button class=\"quantity-btn\" onclick=\"changeQuantity(";
// line 64
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 64), "html", null, true);
yield ", 1)\">+</button>
</div>
</td>
<td data-label=\"Total\">
<span class=\"cart-item-total\" id=\"total-";
// line 68
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 68), "html", null, true);
yield "\">";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber((CoreExtension::getAttribute($this->env, $this->source, $context["item"], "price", [], "any", false, false, false, 68) * CoreExtension::getAttribute($this->env, $this->source, $context["item"], "qty", [], "any", false, false, false, 68)), 2, ".", " "), "html", null, true);
yield " HTG</span>
</td>
<td data-label=\"Action\">
<button class=\"remove-btn\" onclick=\"removeFromCart(";
// line 71
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 71), "html", null, true);
yield ")\">Supprimer</button>
</td>
</tr>
";
$context['_iterated'] = true;
}
// line 74
if (!$context['_iterated']) {
// line 75
yield " <tr>
<td colspan=\"5\" style=\"text-align: center; padding: 50px;\">
Votre panier est vide.
<br>
<a href=\"";
// line 79
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_listing");
yield "\" style=\"color: #007185;\">Continuer vos achats</a>
</td>
</tr>
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['item'], $context['_parent'], $context['_iterated']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 83
yield " </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\">";
// line 92
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(Twig\Extension\CoreExtension::length($this->env->getCharset(), ($context["items"] ?? null)), "html", null, true);
yield "</span> article";
yield (((Twig\Extension\CoreExtension::length($this->env->getCharset(), ($context["items"] ?? null)) > 1)) ? ("s") : (""));
yield ")</span>
<span id=\"cart-subtotal\">";
// line 93
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(($context["subtotal"] ?? null), 2, ".", " "), "html", null, true);
yield " HTG</span>
</div>
<div class=\"cart-total\">
<span>Total</span>
<span id=\"cart-total\">";
// line 97
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape($this->extensions['Twig\Extension\CoreExtension']->formatNumber(($context["subtotal"] ?? null), 2, ".", " "), "html", null, true);
yield " HTG</span>
</div>
<button class=\"checkout-btn mt-3\" onclick=\"proceedToCheckout()\">Passer la commande</button>
<button class=\"continue-shopping\" onclick=\"window.location.href='";
// line 100
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_listing");
yield "'\">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>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 130
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_stylesheets(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "stylesheets"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "stylesheets"));
// line 131
yield "<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>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
// line 871
/**
* @return iterable<null|scalar|\Stringable>
*/
public function block_javascripts(array $context, array $blocks = []): iterable
{
$macros = $this->macros;
$__internal_5a27a8ba21ca79b61932376b2fa922d2 = $this->extensions["Symfony\\Bundle\\WebProfilerBundle\\Twig\\WebProfilerExtension"];
$__internal_5a27a8ba21ca79b61932376b2fa922d2->enter($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "javascripts"));
$__internal_6f47bbe9983af81f1e7450e9a3e3768f = $this->extensions["Symfony\\Bridge\\Twig\\Extension\\ProfilerExtension"];
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->enter($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof = new \Twig\Profiler\Profile($this->getTemplateName(), "block", "javascripts"));
// line 872
yield "<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 = '";
// line 1006
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_listing");
yield "';
}
});
return;
}
// Rediriger vers la page de checkout
window.location.href = '";
// line 1013
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_checkout");
yield "';
}
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
";
// line 1044
$context['_parent'] = $context;
$context['_seq'] = CoreExtension::ensureTraversable(($context["items"] ?? null));
foreach ($context['_seq'] as $context["_key"] => $context["item"]) {
// line 1045
yield " ";
$context["image"] = (((CoreExtension::getAttribute($this->env, $this->source, $context["item"], "image", [], "any", true, true, false, 1045) && CoreExtension::getAttribute($this->env, $this->source, $context["item"], "image", [], "any", false, false, false, 1045))) ? (CoreExtension::getAttribute($this->env, $this->source, $context["item"], "image", [], "any", false, false, false, 1045)) : ($this->extensions['Symfony\Bridge\Twig\Extension\AssetExtension']->getAssetUrl("ui/img/category/s-p1.jpg")));
// line 1046
yield "cartData[";
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 1046), "html", null, true);
yield "] = {
id: ";
// line 1047
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "id", [], "any", false, false, false, 1047), "html", null, true);
yield ",
name: '";
// line 1048
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "name", [], "any", false, false, false, 1048), "html", null, true);
yield "',
price: ";
// line 1049
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "price", [], "any", false, false, false, 1049), "html", null, true);
yield ",
qty: ";
// line 1050
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "qty", [], "any", false, false, false, 1050), "html", null, true);
yield ",
image: '";
// line 1051
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(($context["image"] ?? null), "html", null, true);
yield "',
slug: '";
// line 1052
yield $this->env->getRuntime('Twig\Runtime\EscaperRuntime')->escape(CoreExtension::getAttribute($this->env, $this->source, $context["item"], "slug", [], "any", false, false, false, 1052), "html", null, true);
yield "'
};
";
}
$_parent = $context['_parent'];
unset($context['_seq'], $context['_key'], $context['item'], $context['_parent']);
$context = array_intersect_key($context, $_parent) + $_parent;
// line 1055
yield "
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('";
// line 1081
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_cart_update");
yield "', {
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('";
// line 1377
yield $this->extensions['Symfony\Bridge\Twig\Extension\RoutingExtension']->getPath("ui_cart_remove");
yield "', {
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>
";
$__internal_6f47bbe9983af81f1e7450e9a3e3768f->leave($__internal_6f47bbe9983af81f1e7450e9a3e3768f_prof);
$__internal_5a27a8ba21ca79b61932376b2fa922d2->leave($__internal_5a27a8ba21ca79b61932376b2fa922d2_prof);
yield from [];
}
/**
* @codeCoverageIgnore
*/
public function getTemplateName(): string
{
return "home/cart.html.twig";
}
/**
* @codeCoverageIgnore
*/
public function isTraitable(): bool
{
return false;
}
/**
* @codeCoverageIgnore
*/
public function getDebugInfo(): array
{
return array ( 1641 => 1377, 1342 => 1081, 1314 => 1055, 1305 => 1052, 1301 => 1051, 1297 => 1050, 1293 => 1049, 1289 => 1048, 1285 => 1047, 1280 => 1046, 1277 => 1045, 1273 => 1044, 1239 => 1013, 1229 => 1006, 1093 => 872, 1080 => 871, 331 => 131, 318 => 130, 278 => 100, 272 => 97, 265 => 93, 259 => 92, 248 => 83, 238 => 79, 232 => 75, 230 => 74, 222 => 71, 214 => 68, 207 => 64, 199 => 63, 196 => 62, 192 => 60, 188 => 58, 186 => 57, 178 => 56, 171 => 52, 161 => 47, 153 => 45, 151 => 44, 146 => 41, 141 => 40, 111 => 13, 102 => 6, 89 => 5, 66 => 3, 43 => 1,);
}
public function getSourceContext(): Source
{
return new Source("{% 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 %}", "home/cart.html.twig", "/home/u540977899/domains/maketou-ht.com/public_html/templates/home/cart.html.twig");
}
}