templates/home/cart.html.twig line 1

Open in your IDE?
  1. {% extends 'base_home.html.twig' %}
  2. {% block title %}Panier | MaketOu{% endblock %}
  3. {% block body %}
  4.     <!-- Start Banner Area -->
  5.     <section class="banner-area organic-breadcrumb">
  6.         <div class="container">
  7.             <div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
  8.                 <div class="col-first">
  9.                     <h1>Panier</h1>
  10.                     <nav class="d-flex align-items-center">
  11.                         <a href="{{ path('ui_home') }}">Accueil<span class="lnr lnr-arrow-right"></span></a>
  12.                         <a href="javascript:void(0)">Panier</a>
  13.                     </nav>
  14.                 </div>
  15.             </div>
  16.         </div>
  17.     </section>
  18.     <!-- End Banner Area -->
  19.     <!--================Cart Area =================-->
  20.     <section class="cart_area">
  21.         <div class="container">
  22.             <div class="row">
  23.                 <div class="col-lg-8">
  24.                     <div class="cart_inner">
  25.                         <div class="table-responsive">
  26.                             <table class="table">
  27.                                 <thead>
  28.                                     <tr>
  29.                                         <th scope="col">Produit</th>
  30.                                         <th scope="col">Prix</th>
  31.                                         <th scope="col">Quantité</th>
  32.                                         <th scope="col">Total</th>
  33.                                         <th scope="col">Action</th>
  34.                                     </tr>
  35.                                 </thead>
  36.                                 <tbody>
  37.                                     {% for item in items %}
  38.                                     <tr>
  39.                                         <td data-label="Produit">
  40.                                             <div class="cart-item">
  41.                                                 {% set image = (item.image is defined and item.image|length > 0) ? item.image : asset('ui/img/category/s-p1.jpg') %}
  42.                                                 <img src="{{ image }}" alt="{{ item.name }}" class="cart-item-image">
  43.                                                 <div class="cart-item-info">
  44.                                                     <a href="{{ path('ui_product_show', { slug: item.slug }) }}" class="cart-item-name">{{ item.name }}</a>
  45.                                                 </div>
  46.                                             </div>
  47.                                         </td>
  48.                                         <td data-label="Prix">
  49.                                             <span class="cart-item-price">{{ item.price|number_format(2, '.', ' ') }} HTG</span>
  50.                                         </td>
  51.                                         <td data-label="Quantité">
  52.                                             <div class="quantity-controls">
  53.                                                 <button class="quantity-btn" onclick="changeQuantity({{ item.id }}, -1)">-</button>
  54.                                                 <input type="text" value="{{ item.qty }}" class="quantity-input" id="qty-{{ item.id }}" onchange="updateQuantity({{ item.id }}, this.value)">
  55.                                                 <button class="quantity-btn" onclick="changeQuantity({{ item.id }}, 1)">+</button>
  56.                                             </div>
  57.                                         </td>
  58.                                         <td data-label="Total">
  59.                                             <span class="cart-item-total" id="total-{{ item.id }}">{{ (item.price * item.qty)|number_format(2, '.', ' ') }} HTG</span>
  60.                                         </td>
  61.                                         <td data-label="Action">
  62.                                             <button class="remove-btn" onclick="removeFromCart({{ item.id }})">Supprimer</button>
  63.                                         </td>
  64.                                     </tr>
  65.                                     {% else %}
  66.                                         <tr>
  67.                                             <td colspan="5" style="text-align: center; padding: 50px;">
  68.                                                 Votre panier est vide.
  69.                                                 <br>
  70.                                                 <a href="{{ path('ui_listing') }}" style="color: #007185;">Continuer vos achats</a>
  71.                                             </td>
  72.                                         </tr>
  73.                                     {% endfor %}
  74.                                 </tbody>
  75.                             </table>
  76.                         </div>
  77.                     </div>
  78.                 </div>
  79.                 <div class="col-lg-4">
  80.                     <div class="cart-summary">
  81.                         <h3>Résumé de la commande</h3>
  82.                         <div class="cart-subtotal">
  83.                             <span id="cart-subtotal-label">Sous-total (<span id="cart-item-count">{{ items|length }}</span> article{{ items|length > 1 ? 's' : '' }})</span>
  84.                             <span id="cart-subtotal">{{ subtotal|number_format(2, '.', ' ') }} HTG</span>
  85.                         </div>
  86.                         <div class="cart-total">
  87.                             <span>Total</span>
  88.                             <span id="cart-total">{{ subtotal|number_format(2, '.', ' ') }} HTG</span>
  89.                         </div>
  90.                         <button class="checkout-btn mt-3" onclick="proceedToCheckout()">Passer la commande</button>
  91.                         <button class="continue-shopping" onclick="window.location.href='{{ path('ui_listing') }}'">Continuer vos achats</button>
  92.                     </div>
  93.                 </div>
  94.             </div>
  95.         </div>
  96.     </section>
  97.     <!--================End Cart Area =================-->
  98.     <!-- Modal personnalisé -->
  99.     <div id="customModal" class="custom-modal">
  100.         <div class="custom-modal-overlay"></div>
  101.         <div class="custom-modal-content">
  102.             <div class="custom-modal-header">
  103.                 <h3 class="custom-modal-title" id="modalTitle">Titre</h3>
  104.                 <button class="custom-modal-close" onclick="closeCustomModal()" aria-label="Fermer">
  105.                     <span>&times;</span>
  106.                 </button>
  107.             </div>
  108.             <div class="custom-modal-body" id="modalBody">
  109.                 <p id="modalMessage">Message</p>
  110.             </div>
  111.             <div class="custom-modal-footer" id="modalFooter">
  112.                 <button class="custom-modal-btn custom-modal-btn-primary" id="modalConfirmBtn" onclick="confirmModalAction()">Confirmer</button>
  113.                 <button class="custom-modal-btn custom-modal-btn-secondary" id="modalCancelBtn" onclick="closeCustomModal()">Annuler</button>
  114.             </div>
  115.         </div>
  116.     </div>
  117. {% endblock %}
  118. {% block stylesheets %}
  119. <style>
  120. .cart_area {
  121.     background-color: #f8f8f8;
  122.     padding: 20px 0;
  123. }
  124. .cart_inner {
  125.     background: white;
  126.     border-radius: 8px;
  127.     box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  128.     overflow: hidden;
  129. }
  130. .table th {
  131.     background-color: #f3f3f3;
  132.     border-bottom: 2px solid #ddd;
  133.     font-weight: 600;
  134.     color: #333;
  135.     padding: 15px;
  136. }
  137. .table td {
  138.     vertical-align: middle;
  139.     padding: 15px;
  140. }
  141. .cart-item {
  142.     display: flex;
  143.     align-items: center;
  144. }
  145. .cart-item-image {
  146.     width: 80px;
  147.     height: 80px;
  148.     object-fit: cover;
  149.     border-radius: 8px;
  150.     margin-right: 15px;
  151. }
  152. .cart-item-info {
  153.     flex-grow: 1;
  154. }
  155. .cart-item-name {
  156.     font-weight: 500;
  157.     color: #007185;
  158.     text-decoration: none;
  159.     margin-bottom: 5px;
  160. }
  161. .cart-item-name:hover {
  162.     text-decoration: underline;
  163. }
  164. .cart-item-price {
  165.     color: #333;
  166.     font-size: 18px;
  167.     font-weight: 700;
  168. }
  169. .quantity-controls {
  170.     display: flex !important;
  171.     align-items: center !important;
  172.     justify-content: center !important;
  173.     border: 1px solid #ddd !important;
  174.     border-radius: 4px !important;
  175.     width: 120px !important;
  176.     height: 40px !important;
  177.     position: relative !important;
  178.     background: white !important;
  179.     overflow: hidden !important;
  180. }
  181. .quantity-controls .quantity-btn {
  182.     background: #f8f8f8 !important;
  183.     border: none !important;
  184.     width: 35px !important;
  185.     height: 100% !important;
  186.     min-width: 35px !important;
  187.     cursor: pointer !important;
  188.     font-size: 18px !important;
  189.     font-weight: 600 !important;
  190.     color: #333 !important;
  191.     display: flex !important;
  192.     align-items: center !important;
  193.     justify-content: center !important;
  194.     padding: 0 !important;
  195.     margin: 0 !important;
  196.     line-height: 1 !important;
  197.     transition: background-color 0.2s ease !important;
  198.     position: relative !important;
  199.     z-index: 1 !important;
  200. }
  201. .quantity-controls .quantity-btn:first-child {
  202.     border-right: 1px solid #ddd !important;
  203. }
  204. .quantity-controls .quantity-btn:last-child {
  205.     border-left: 1px solid #ddd !important;
  206. }
  207. .quantity-controls .quantity-btn:hover {
  208.     background: #e7e7e7 !important;
  209.     color: #000 !important;
  210. }
  211. .quantity-controls .quantity-btn:active {
  212.     background: #d0d0d0 !important;
  213. }
  214. .quantity-controls .quantity-input {
  215.     border: none !important;
  216.     width: 50px !important;
  217.     min-width: 50px !important;
  218.     height: 100% !important;
  219.     text-align: center !important;
  220.     font-size: 15px !important;
  221.     font-weight: 500 !important;
  222.     padding: 0 !important;
  223.     margin: 0 !important;
  224.     background: white !important;
  225.     color: #333 !important;
  226.     outline: none !important;
  227.     box-shadow: none !important;
  228.     -moz-appearance: textfield !important;
  229.     position: relative !important;
  230.     z-index: 1 !important;
  231. }
  232. .quantity-controls .quantity-input::-webkit-outer-spin-button,
  233. .quantity-controls .quantity-input::-webkit-inner-spin-button {
  234.     -webkit-appearance: none !important;
  235.     margin: 0 !important;
  236. }
  237. /* Surcharger les styles externes qui pourraient interférer */
  238. .cart_inner .table tbody tr td .quantity-controls,
  239. .cart_inner .table tbody tr td .quantity-controls .quantity-btn,
  240. .cart_inner .table tbody tr td .quantity-controls .quantity-input {
  241.     position: relative !important;
  242. }
  243. .cart_inner .table tbody tr td .quantity-controls .quantity-btn:before,
  244. .cart_inner .table tbody tr td .quantity-controls .quantity-btn:after {
  245.     display: none !important;
  246.     content: none !important;
  247. }
  248. .cart-item-total {
  249.     font-size: 18px;
  250.     font-weight: 700;
  251.     color: #333;
  252. }
  253. /* Responsive Styles */
  254. @media (max-width: 991.98px) {
  255.     .cart_area {
  256.         padding: 15px 0;
  257.     }
  258.     
  259.     .cart-summary {
  260.         margin-top: 2rem;
  261.     }
  262. }
  263. @media (max-width: 767.98px) {
  264.     .cart_inner {
  265.         border-radius: 0;
  266.     }
  267.     
  268.     .table {
  269.         font-size: 0.875rem;
  270.     }
  271.     
  272.     .table th {
  273.         padding: 10px 8px;
  274.         font-size: 0.8rem;
  275.     }
  276.     
  277.     .table td {
  278.         padding: 10px 8px;
  279.     }
  280.     
  281.     .cart-item {
  282.         flex-direction: column;
  283.         align-items: flex-start;
  284.     }
  285.     
  286.     .cart-item-image {
  287.         width: 60px;
  288.         height: 60px;
  289.         margin-right: 0;
  290.         margin-bottom: 10px;
  291.     }
  292.     
  293.     .cart-item-name {
  294.         font-size: 0.9rem;
  295.     }
  296.     
  297.     .cart-item-price,
  298.     .cart-item-total {
  299.         font-size: 1rem;
  300.     }
  301.     
  302.     .quantity-controls {
  303.         width: 110px !important;
  304.         height: 38px !important;
  305.     }
  306.     
  307.     .quantity-controls .quantity-btn {
  308.         width: 32px !important;
  309.         font-size: 16px !important;
  310.     }
  311.     
  312.     .quantity-controls .quantity-input {
  313.         width: 46px !important;
  314.         font-size: 14px !important;
  315.     }
  316.     
  317.     .cart-summary {
  318.         margin-top: 1.5rem;
  319.         padding: 1.5rem;
  320.     }
  321.     
  322.     .checkout-btn,
  323.     .continue-shopping {
  324.         width: 100%;
  325.         margin-bottom: 0.5rem;
  326.     }
  327. }
  328. @media (max-width: 575.98px) {
  329.     .table thead {
  330.         display: none;
  331.     }
  332.     
  333.     .table tbody tr {
  334.         display: block;
  335.         margin-bottom: 1rem;
  336.         border: 1px solid #dee2e6;
  337.         border-radius: 8px;
  338.         padding: 1rem;
  339.     }
  340.     
  341.     .table tbody td {
  342.         display: flex;
  343.         justify-content: space-between;
  344.         align-items: center;
  345.         padding: 0.5rem 0;
  346.         border: none;
  347.         text-align: right;
  348.     }
  349.     
  350.     .table tbody td::before {
  351.         content: attr(data-label);
  352.         font-weight: bold;
  353.         text-align: left;
  354.         margin-right: 1rem;
  355.     }
  356.     
  357.     .table tbody td:first-child {
  358.         flex-direction: column;
  359.         align-items: flex-start;
  360.     }
  361.     
  362.     .cart-item-image {
  363.         width: 100%;
  364.         height: 200px;
  365.         margin-bottom: 1rem;
  366.     }
  367.     
  368.     .cart-item-name {
  369.         font-size: 1rem;
  370.         margin-bottom: 0.5rem;
  371.     }
  372.     
  373.     .quantity-controls {
  374.         width: 100% !important;
  375.         max-width: 150px !important;
  376.         margin: 0 auto !important;
  377.         justify-content: center !important;
  378.         height: 40px !important;
  379.     }
  380.     
  381.     .quantity-controls .quantity-btn {
  382.         width: 40px !important;
  383.         font-size: 18px !important;
  384.     }
  385.     
  386.     .quantity-controls .quantity-input {
  387.         width: 60px !important;
  388.         font-size: 16px !important;
  389.     }
  390.     
  391.     .remove-btn {
  392.         width: 100%;
  393.         margin-top: 0.5rem;
  394.     }
  395. }
  396. .remove-btn {
  397.     background: #ff9900;
  398.     color: white;
  399.     border: none;
  400.     padding: 8px 16px;
  401.     border-radius: 4px;
  402.     cursor: pointer;
  403.     font-size: 12px;
  404. }
  405. .remove-btn:hover {
  406.     background: #e68900;
  407. }
  408. .cart-summary {
  409.     background: white;
  410.     border-radius: 8px;
  411.     box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  412.     padding: 20px;
  413.     height: fit-content;
  414. }
  415. .cart-subtotal {
  416.     display: flex;
  417.     justify-content: space-between;
  418.     padding: 10px 0;
  419.     border-bottom: 1px solid #ddd;
  420.     margin-bottom: 15px;
  421. }
  422. .cart-total {
  423.     font-size: 20px;
  424.     font-weight: 700;
  425.     color: #333;
  426. }
  427. .checkout-btn {
  428.     background: #ffa200;
  429.     color: white;
  430.     border: none;
  431.     padding: 12px 24px;
  432.     border-radius: 4px;
  433.     cursor: pointer;
  434.     width: 100%;
  435.     font-size: 16px;
  436.     font-weight: 600;
  437. }
  438. .checkout-btn:hover {
  439.     background: #e68900;
  440. }
  441. .continue-shopping {
  442.     background: #095ad3;
  443.     color: #ffffff;
  444.     border: none;
  445.     padding: 12px 24px;
  446.     border-radius: 4px;
  447.     cursor: pointer;
  448.     width: 100%;
  449.     font-size: 16px;
  450.     font-weight: 600;
  451.     margin-top: 10px;
  452. }
  453. .continue-shopping:hover {
  454.     background: #e68900;
  455.     color: #ffffff
  456. }
  457. /* ============================================
  458.    MODAL PERSONNALISÉ
  459.    ============================================ */
  460. .custom-modal {
  461.     display: none;
  462.     position: fixed;
  463.     top: 0;
  464.     left: 0;
  465.     width: 100%;
  466.     height: 100%;
  467.     z-index: 10000;
  468.     opacity: 0;
  469.     transition: opacity 0.3s ease;
  470. }
  471. .custom-modal.show {
  472.     display: flex;
  473.     align-items: center;
  474.     justify-content: center;
  475.     opacity: 1;
  476. }
  477. .custom-modal-overlay {
  478.     position: absolute;
  479.     top: 0;
  480.     left: 0;
  481.     width: 100%;
  482.     height: 100%;
  483.     background: rgba(0, 0, 0, 0.5);
  484.     backdrop-filter: blur(4px);
  485.     -webkit-backdrop-filter: blur(4px);
  486. }
  487. .custom-modal-content {
  488.     position: relative;
  489.     background: white;
  490.     border-radius: 12px;
  491.     box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
  492.     max-width: 500px;
  493.     width: 90%;
  494.     max-height: 90vh;
  495.     overflow: hidden;
  496.     transform: scale(0.9) translateY(-20px);
  497.     transition: transform 0.3s ease;
  498.     z-index: 10001;
  499. }
  500. .custom-modal.show .custom-modal-content {
  501.     transform: scale(1) translateY(0);
  502. }
  503. .custom-modal-header {
  504.     display: flex;
  505.     align-items: center;
  506.     justify-content: space-between;
  507.     padding: 20px 24px;
  508.     border-bottom: 1px solid #e0e0e0;
  509.     background: linear-gradient(135deg, #ffa200 0%, #ff9900 100%);
  510. }
  511. .custom-modal-header.error {
  512.     background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);
  513. }
  514. .custom-modal-header.success {
  515.     background: linear-gradient(135deg, #28a745 0%, #218838 100%);
  516. }
  517. .custom-modal-header.warning {
  518.     background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%);
  519. }
  520. .custom-modal-header.info {
  521.     background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
  522. }
  523. .custom-modal-title {
  524.     margin: 0;
  525.     font-size: 20px;
  526.     font-weight: 600;
  527.     color: white;
  528.     display: flex;
  529.     align-items: center;
  530.     gap: 10px;
  531. }
  532. .custom-modal-title::before {
  533.     content: '';
  534.     width: 24px;
  535.     height: 24px;
  536.     display: inline-block;
  537.     background-size: contain;
  538.     background-repeat: no-repeat;
  539.     background-position: center;
  540. }
  541. .custom-modal-title.success::before {
  542.     content: '✓';
  543.     font-size: 20px;
  544.     font-weight: bold;
  545. }
  546. .custom-modal-title.error::before {
  547.     content: '✕';
  548.     font-size: 20px;
  549.     font-weight: bold;
  550. }
  551. .custom-modal-title.warning::before {
  552.     content: '⚠';
  553.     font-size: 20px;
  554.     font-weight: bold;
  555. }
  556. .custom-modal-title.info::before {
  557.     content: 'ℹ';
  558.     font-size: 20px;
  559.     font-weight: bold;
  560. }
  561. .custom-modal-close {
  562.     background: rgba(255, 255, 255, 0.15);
  563.     border: 2px solid rgba(255, 255, 255, 0.3);
  564.     color: white;
  565.     font-size: 22px;
  566.     font-weight: 300;
  567.     line-height: 1;
  568.     width: 36px;
  569.     height: 36px;
  570.     border-radius: 50%;
  571.     cursor: pointer;
  572.     display: flex;
  573.     align-items: center;
  574.     justify-content: center;
  575.     transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  576.     padding: 0;
  577.     margin: 0;
  578.     position: relative;
  579.     overflow: hidden;
  580. }
  581. .custom-modal-close::before {
  582.     content: '';
  583.     position: absolute;
  584.     top: 50%;
  585.     left: 50%;
  586.     width: 0;
  587.     height: 0;
  588.     border-radius: 50%;
  589.     background: rgba(255, 255, 255, 0.2);
  590.     transform: translate(-50%, -50%);
  591.     transition: width 0.3s ease, height 0.3s ease;
  592. }
  593. .custom-modal-close:hover {
  594.     background: rgba(255, 255, 255, 0.25);
  595.     border-color: rgba(255, 255, 255, 0.5);
  596.     transform: rotate(90deg) scale(1.1);
  597.     box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  598. }
  599. .custom-modal-close:hover::before {
  600.     width: 100%;
  601.     height: 100%;
  602. }
  603. .custom-modal-close:active {
  604.     transform: rotate(90deg) scale(0.95);
  605.     background: rgba(255, 255, 255, 0.3);
  606. }
  607. .custom-modal-close span {
  608.     position: relative;
  609.     z-index: 1;
  610.     display: block;
  611.     line-height: 1;
  612. }
  613. .custom-modal-body {
  614.     padding: 24px;
  615.     font-size: 16px;
  616.     line-height: 1.6;
  617.     color: #333;
  618.     max-height: 60vh;
  619.     overflow-y: auto;
  620. }
  621. .custom-modal-body p {
  622.     margin: 0;
  623. }
  624. .custom-modal-footer {
  625.     display: flex;
  626.     gap: 12px;
  627.     justify-content: flex-end;
  628.     padding: 16px 24px;
  629.     border-top: 1px solid #e0e0e0;
  630.     background: #f8f9fa;
  631. }
  632. .custom-modal-footer.single-button {
  633.     justify-content: center;
  634. }
  635. .custom-modal-btn {
  636.     padding: 10px 24px;
  637.     border: none;
  638.     border-radius: 6px;
  639.     font-size: 15px;
  640.     font-weight: 500;
  641.     cursor: pointer;
  642.     transition: all 0.2s ease;
  643.     min-width: 100px;
  644. }
  645. .custom-modal-btn-primary {
  646.     background: #ffa200;
  647.     color: white;
  648. }
  649. .custom-modal-btn-primary:hover {
  650.     background: #e68900;
  651.     transform: translateY(-1px);
  652.     box-shadow: 0 4px 8px rgba(255, 162, 0, 0.3);
  653. }
  654. .custom-modal-btn-secondary {
  655.     background: #6c757d;
  656.     color: white;
  657. }
  658. .custom-modal-btn-secondary:hover {
  659.     background: #5a6268;
  660.     transform: translateY(-1px);
  661.     box-shadow: 0 4px 8px rgba(108, 117, 125, 0.3);
  662. }
  663. .custom-modal-btn-success {
  664.     background: #28a745;
  665.     color: white;
  666. }
  667. .custom-modal-btn-success:hover {
  668.     background: #218838;
  669.     transform: translateY(-1px);
  670.     box-shadow: 0 4px 8px rgba(40, 167, 69, 0.3);
  671. }
  672. .custom-modal-btn-danger {
  673.     background: #dc3545;
  674.     color: white;
  675. }
  676. .custom-modal-btn-danger:hover {
  677.     background: #c82333;
  678.     transform: translateY(-1px);
  679.     box-shadow: 0 4px 8px rgba(220, 53, 69, 0.3);
  680. }
  681. /* Animation pour les modals */
  682. @keyframes modalFadeIn {
  683.     from {
  684.         opacity: 0;
  685.     }
  686.     to {
  687.         opacity: 1;
  688.     }
  689. }
  690. @keyframes modalSlideIn {
  691.     from {
  692.         transform: scale(0.9) translateY(-20px);
  693.         opacity: 0;
  694.     }
  695.     to {
  696.         transform: scale(1) translateY(0);
  697.         opacity: 1;
  698.     }
  699. }
  700. .custom-modal.show .custom-modal-overlay {
  701.     animation: modalFadeIn 0.3s ease;
  702. }
  703. .custom-modal.show .custom-modal-content {
  704.     animation: modalSlideIn 0.3s ease;
  705. }
  706. /* Responsive */
  707. @media (max-width: 575.98px) {
  708.     .custom-modal-content {
  709.         width: 95%;
  710.         margin: 20px;
  711.     }
  712.     
  713.     .custom-modal-header {
  714.         padding: 16px 20px;
  715.     }
  716.     
  717.     .custom-modal-title {
  718.         font-size: 18px;
  719.     }
  720.     
  721.     .custom-modal-body {
  722.         padding: 20px;
  723.         font-size: 15px;
  724.     }
  725.     
  726.     .custom-modal-footer {
  727.         flex-direction: column;
  728.         padding: 16px 20px;
  729.     }
  730.     
  731.     .custom-modal-btn {
  732.         width: 100%;
  733.     }
  734. }
  735. </style>
  736. {% endblock %}
  737. {% block javascripts %}
  738. <script>
  739. // Variables globales pour le modal
  740. let modalConfirmCallback = null;
  741. let modalCancelCallback = null;
  742. // Fonctions pour gérer le modal personnalisé
  743. function showCustomModal(options) {
  744.     const modal = document.getElementById('customModal');
  745.     const title = document.getElementById('modalTitle');
  746.     const message = document.getElementById('modalMessage');
  747.     const footer = document.getElementById('modalFooter');
  748.     const confirmBtn = document.getElementById('modalConfirmBtn');
  749.     const cancelBtn = document.getElementById('modalCancelBtn');
  750.     const header = modal.querySelector('.custom-modal-header');
  751.     
  752.     // Réinitialiser les classes
  753.     header.className = 'custom-modal-header';
  754.     title.className = 'custom-modal-title';
  755.     footer.className = 'custom-modal-footer';
  756.     
  757.     // Définir le titre et le message
  758.     title.textContent = options.title || 'Information';
  759.     message.textContent = options.message || '';
  760.     
  761.     // Définir le type (success, error, warning, info)
  762.     const type = options.type || 'info';
  763.     header.classList.add(type);
  764.     title.classList.add(type);
  765.     
  766.     // Configurer les boutons
  767.     if (options.showCancel !== false) {
  768.         cancelBtn.style.display = 'block';
  769.         footer.classList.remove('single-button');
  770.     } else {
  771.         cancelBtn.style.display = 'none';
  772.         footer.classList.add('single-button');
  773.     }
  774.     
  775.     // Configurer le bouton de confirmation
  776.     if (options.confirmText) {
  777.         confirmBtn.textContent = options.confirmText;
  778.     }
  779.     
  780.     if (options.confirmClass) {
  781.         confirmBtn.className = 'custom-modal-btn ' + options.confirmClass;
  782.     } else {
  783.         confirmBtn.className = 'custom-modal-btn custom-modal-btn-primary';
  784.     }
  785.     
  786.     // Stocker les callbacks
  787.     modalConfirmCallback = options.onConfirm || null;
  788.     modalCancelCallback = options.onCancel || null;
  789.     
  790.     // Afficher le modal
  791.     modal.classList.add('show');
  792.     document.body.style.overflow = 'hidden';
  793. }
  794. function closeCustomModal() {
  795.     const modal = document.getElementById('customModal');
  796.     const cancelCallback = modalCancelCallback; // Sauvegarder avant réinitialisation
  797.     
  798.     modal.classList.remove('show');
  799.     document.body.style.overflow = '';
  800.     
  801.     // Réinitialiser les callbacks
  802.     modalConfirmCallback = null;
  803.     modalCancelCallback = null;
  804.     
  805.     // Appeler le callback d'annulation si présent
  806.     if (cancelCallback) {
  807.         cancelCallback();
  808.     }
  809. }
  810. function confirmModalAction() {
  811.     if (modalConfirmCallback) {
  812.         modalConfirmCallback();
  813.     }
  814.     closeCustomModal();
  815. }
  816. // Fermer le modal en cliquant sur l'overlay
  817. document.addEventListener('DOMContentLoaded', function() {
  818.     const modal = document.getElementById('customModal');
  819.     const overlay = modal.querySelector('.custom-modal-overlay');
  820.     
  821.     overlay.addEventListener('click', function() {
  822.         closeCustomModal();
  823.     });
  824.     
  825.     // Fermer avec la touche Escape
  826.     document.addEventListener('keydown', function(e) {
  827.         if (e.key === 'Escape' && modal.classList.contains('show')) {
  828.             closeCustomModal();
  829.         }
  830.     });
  831. });
  832. // Fonctions utilitaires pour différents types de modals
  833. function showAlert(title, message, type = 'info') {
  834.     showCustomModal({
  835.         title: title,
  836.         message: message,
  837.         type: type,
  838.         showCancel: false,
  839.         confirmText: 'OK'
  840.     });
  841. }
  842. function showConfirm(title, message, onConfirm, onCancel = null, type = 'warning') {
  843.     showCustomModal({
  844.         title: title,
  845.         message: message,
  846.         type: type,
  847.         showCancel: true,
  848.         confirmText: 'Confirmer',
  849.         cancelText: 'Annuler',
  850.         onConfirm: onConfirm,
  851.         onCancel: onCancel
  852.     });
  853. }
  854. function proceedToCheckout() {
  855.     // Vérifier qu'il y a des articles dans le panier
  856.     if (Object.keys(cartData).length === 0) {
  857.         showAlert('Panier vide', 'Votre panier est vide. Veuillez ajouter des articles avant de continuer.', 'warning');
  858.         return;
  859.     }
  860.     
  861.     // Rediriger vers la page de checkout
  862.     window.location.href = '{{ path('ui_checkout') }}';
  863. }
  864. function changeQuantity(productId, delta) {
  865.     const qtyInput = document.getElementById('qty-' + productId);
  866.     if (!qtyInput) return;
  867.     
  868.     const currentQty = parseInt(qtyInput.value) || 0;
  869.     const newQty = Math.max(0, currentQty + delta);
  870.     
  871.     updateQuantity(productId, newQty);
  872. }
  873. let cartData = {}; // Stocker les données du panier côté client
  874. // Initialiser les données du panier
  875. {% for item in items %}
  876.     {% set image = (item.images is defined and item.images|length > 0) ? item.images[0] : asset('ui/img/category/s-p1.jpg') %}
  877. cartData[{{ item.id }}] = {
  878.     id: {{ item.id }},
  879.     name: '{{ item.name }}',
  880.     price: {{ item.price }},
  881.     qty: {{ item.qty }},
  882.     image: '{{ image }}',
  883.     slug: '{{ item.slug }}'
  884. };
  885. {% endfor %}
  886. function updateQuantity(productId, newQty) {
  887.     const item = cartData[productId];
  888.     
  889.     if (!item) {
  890.         console.error('Produit non trouvé:', productId);
  891.         return;
  892.     }
  893.     
  894.     // Validation de la quantité
  895.     newQty = Math.max(0, parseInt(newQty) || 0);
  896.     
  897.     if (newQty === 0) {
  898.         removeFromCart(productId);
  899.         return;
  900.     }
  901.     
  902.     if (newQty > 99) {
  903.         showAlert('Quantité maximale', 'La quantité maximale est de 99 articles.', 'warning');
  904.         return;
  905.     }
  906.     
  907.     // Mettre à jour l'interface immédiatement (optimistic update)
  908.     updateCartDisplay(productId, newQty);
  909.     
  910.     // Envoyer la requête au serveur
  911.     fetch('{{ path("ui_cart_update") }}', {
  912.         method: 'POST',
  913.         headers: { 
  914.             'Content-Type': 'application/x-www-form-urlencoded',
  915.             'X-Requested-With': 'XMLHttpRequest'
  916.         },
  917.         body: 'productId=' + productId + '&qty=' + newQty
  918.     })
  919.     .then(response => response.json())
  920.     .then(data => {
  921.         if (data.ok) {
  922.             // Les données serveur confirment la mise à jour
  923.             // cartData est déjà à jour depuis l'optimistic update
  924.             console.log('Panier mis à jour avec succès');
  925.         } else {
  926.             // En cas d'erreur, restaurer la valeur précédente
  927.             const oldQty = item.qty;
  928.             updateCartDisplay(productId, oldQty);
  929.             showAlert('Erreur', data.message || 'Erreur lors de la mise à jour du panier.', 'error');
  930.         }
  931.     })
  932.     .catch(error => {
  933.         console.error('Erreur:', error);
  934.         // Restaurer la valeur précédente en cas d'erreur réseau
  935.         const oldQty = item.qty;
  936.         updateCartDisplay(productId, oldQty);
  937.         showAlert('Erreur de connexion', 'Une erreur de connexion est survenue. Veuillez réessayer.', 'error');
  938.     });
  939. }
  940. function updateCartDisplay(productId, qty) {
  941.     const item = cartData[productId];
  942.     if (!item) return;
  943.     
  944.     // Mettre à jour les données locales
  945.     item.qty = qty;
  946.     
  947.     // Mettre à jour la quantité dans l'input
  948.     const qtyInput = document.getElementById('qty-' + productId);
  949.     if (qtyInput) {
  950.         qtyInput.value = qty;
  951.     }
  952.     
  953.     // Mettre à jour le total du produit
  954.     const totalElement = document.getElementById('total-' + productId);
  955.     if (totalElement) {
  956.         const itemTotal = (item.price * qty);
  957.         totalElement.textContent = itemTotal.toLocaleString('fr-FR', {
  958.             minimumFractionDigits: 2,
  959.             maximumFractionDigits: 2
  960.         }) + ' HTG';
  961.     }
  962.     
  963.     // Mettre à jour les totaux généraux
  964.     updateCartSummary();
  965. }
  966. function updateCartTotals(data) {
  967.     // Mettre à jour le sous-total depuis les données serveur
  968.     const subtotalElement = document.getElementById('cart-subtotal');
  969.     const totalElement = document.getElementById('cart-total');
  970.     
  971.     if (subtotalElement) {
  972.         subtotalElement.textContent = parseFloat(data.subtotal).toLocaleString('fr-FR', {
  973.             minimumFractionDigits: 2,
  974.             maximumFractionDigits: 2
  975.         }) + ' HTG';
  976.     }
  977.     
  978.     if (totalElement) {
  979.         totalElement.textContent = parseFloat(data.subtotal).toLocaleString('fr-FR', {
  980.             minimumFractionDigits: 2,
  981.             maximumFractionDigits: 2
  982.         }) + ' HTG';
  983.     }
  984.     
  985.     // Mettre à jour le compteur d'articles
  986.     const itemCountElement = document.querySelector('.cart-subtotal span');
  987.     if (itemCountElement) {
  988.         const totalItems = Object.values(cartData).reduce((sum, item) => sum + item.qty, 0);
  989.         const itemText = totalItems + ' article' + (totalItems > 1 ? 's' : '');
  990.         itemCountElement.textContent = itemText;
  991.     }
  992. }
  993. function updateCartSummary() {
  994.     // Calculer les totaux côté client depuis cartData
  995.     let subtotal = 0;
  996.     let totalItems = 0;
  997.     
  998.     Object.values(cartData).forEach(item => {
  999.         subtotal += item.price * item.qty;
  1000.         totalItems += item.qty;
  1001.     });
  1002.     
  1003.     // Mettre à jour l'affichage du sous-total
  1004.     const subtotalElement = document.getElementById('cart-subtotal');
  1005.     if (subtotalElement) {
  1006.         subtotalElement.textContent = subtotal.toLocaleString('fr-FR', {
  1007.             minimumFractionDigits: 2,
  1008.             maximumFractionDigits: 2
  1009.         }) + ' HTG';
  1010.     }
  1011.     
  1012.     // Mettre à jour l'affichage du total
  1013.     const totalElement = document.getElementById('cart-total');
  1014.     if (totalElement) {
  1015.         totalElement.textContent = subtotal.toLocaleString('fr-FR', {
  1016.             minimumFractionDigits: 2,
  1017.             maximumFractionDigits: 2
  1018.         }) + ' HTG';
  1019.     }
  1020.     
  1021.     // Mettre à jour le compteur d'articles
  1022.     const itemCountElement = document.getElementById('cart-item-count');
  1023.     if (itemCountElement) {
  1024.         itemCountElement.textContent = totalItems;
  1025.     }
  1026.     
  1027.     // Mettre à jour le label du sous-total
  1028.     const subtotalLabel = document.getElementById('cart-subtotal-label');
  1029.     if (subtotalLabel) {
  1030.         const itemText = totalItems + ' article' + (totalItems > 1 ? 's' : '');
  1031.         subtotalLabel.innerHTML = 'Sous-total (<span id="cart-item-count">' + totalItems + '</span> ' + (totalItems > 1 ? 'articles' : 'article') + ')';
  1032.     }
  1033. }
  1034. function removeFromCart(productId) {
  1035.     const item = cartData[productId];
  1036.     const itemName = item ? item.name : 'cet article';
  1037.     
  1038.     showConfirm(
  1039.         'Supprimer l\'article',
  1040.         `Êtes-vous sûr de vouloir supprimer "${itemName}" de votre panier ?`,
  1041.         function() {
  1042.             // Mettre à jour l'interface immédiatement
  1043.             const row = document.querySelector(`tr:has(#qty-${productId})`);
  1044.             if (row) {
  1045.                 row.style.opacity = '0.5';
  1046.                 row.style.pointerEvents = 'none';
  1047.             }
  1048.             
  1049.             performRemoveFromCart(productId, row);
  1050.         },
  1051.         null,
  1052.         'warning'
  1053.     );
  1054. }
  1055. function performRemoveFromCart(productId, row) {
  1056.     
  1057.     fetch('{{ path("ui_cart_remove") }}', {
  1058.         method: 'POST',
  1059.         headers: { 
  1060.             'Content-Type': 'application/x-www-form-urlencoded',
  1061.             'X-Requested-With': 'XMLHttpRequest'
  1062.         },
  1063.         body: 'productId=' + productId
  1064.     })
  1065.     .then(response => response.json())
  1066.     .then(data => {
  1067.         if (data.ok) {
  1068.             // Supprimer des données locales
  1069.             delete cartData[productId];
  1070.             
  1071.             // Supprimer la ligne du DOM
  1072.             const row = document.querySelector(`tr:has(#qty-${productId})`);
  1073.             if (row) {
  1074.                 row.remove();
  1075.             }
  1076.             
  1077.             // Mettre à jour les totaux
  1078.             updateCartTotals(data);
  1079.             
  1080.             // Mettre à jour le résumé du panier
  1081.             updateCartSummary();
  1082.             
  1083.             // Afficher un message de succès
  1084.             showAlert('Article supprimé', 'L\'article a été supprimé de votre panier avec succès.', 'success');
  1085.             
  1086.             // Vérifier si le panier est vide
  1087.             if (Object.keys(cartData).length === 0) {
  1088.                 setTimeout(function() {
  1089.                     location.reload();
  1090.                 }, 1500);
  1091.             }
  1092.         } else {
  1093.             // Restaurer l'affichage en cas d'erreur
  1094.             if (row) {
  1095.                 row.style.opacity = '1';
  1096.                 row.style.pointerEvents = 'auto';
  1097.             }
  1098.             showAlert('Erreur', data.message || 'Erreur lors de la suppression de l\'article.', 'error');
  1099.         }
  1100.     })
  1101.     .catch(error => {
  1102.         console.error('Erreur:', error);
  1103.         // Restaurer l'affichage en cas d'erreur réseau
  1104.         if (row) {
  1105.             row.style.opacity = '1';
  1106.             row.style.pointerEvents = 'auto';
  1107.         }
  1108.         showAlert('Erreur de connexion', 'Une erreur de connexion est survenue. Veuillez réessayer.', 'error');
  1109.     });
  1110. }
  1111. // Script pour forcer l'application des styles des boutons de quantité
  1112. document.addEventListener('DOMContentLoaded', function() {
  1113.     const quantityControls = document.querySelectorAll('.quantity-controls');
  1114.     quantityControls.forEach(function(control) {
  1115.         if (control) {
  1116.             // Forcer les styles sur le conteneur
  1117.             control.style.display = 'flex';
  1118.             control.style.alignItems = 'center';
  1119.             control.style.justifyContent = 'center';
  1120.             control.style.border = '1px solid #ddd';
  1121.             control.style.borderRadius = '4px';
  1122.             control.style.width = '120px';
  1123.             control.style.height = '40px';
  1124.             control.style.position = 'relative';
  1125.             control.style.background = 'white';
  1126.             control.style.overflow = 'hidden';
  1127.             
  1128.             // Forcer les styles sur les boutons
  1129.             const buttons = control.querySelectorAll('.quantity-btn');
  1130.             buttons.forEach(function(btn, index) {
  1131.                 btn.style.background = '#f8f8f8';
  1132.                 btn.style.border = 'none';
  1133.                 btn.style.width = '35px';
  1134.                 btn.style.height = '100%';
  1135.                 btn.style.cursor = 'pointer';
  1136.                 btn.style.fontSize = '18px';
  1137.                 btn.style.fontWeight = '600';
  1138.                 btn.style.color = '#333';
  1139.                 btn.style.display = 'flex';
  1140.                 btn.style.alignItems = 'center';
  1141.                 btn.style.justifyContent = 'center';
  1142.                 btn.style.padding = '0';
  1143.                 btn.style.margin = '0';
  1144.                 btn.style.lineHeight = '1';
  1145.                 btn.style.position = 'relative';
  1146.                 btn.style.zIndex = '1';
  1147.                 
  1148.                 // Ajouter les bordures entre les boutons
  1149.                 if (index === 0) {
  1150.                     btn.style.borderRight = '1px solid #ddd';
  1151.                 } else if (index === buttons.length - 1) {
  1152.                     btn.style.borderLeft = '1px solid #ddd';
  1153.                 }
  1154.             });
  1155.             
  1156.             // Forcer les styles sur l'input
  1157.             const input = control.querySelector('.quantity-input');
  1158.             if (input) {
  1159.                 input.style.border = 'none';
  1160.                 input.style.width = '50px';
  1161.                 input.style.height = '100%';
  1162.                 input.style.textAlign = 'center';
  1163.                 input.style.fontSize = '15px';
  1164.                 input.style.fontWeight = '500';
  1165.                 input.style.padding = '0';
  1166.                 input.style.margin = '0';
  1167.                 input.style.background = 'white';
  1168.                 input.style.color = '#333';
  1169.                 input.style.outline = 'none';
  1170.                 input.style.boxShadow = 'none';
  1171.                 input.style.position = 'relative';
  1172.                 input.style.zIndex = '1';
  1173.             }
  1174.         }
  1175.     });
  1176. });
  1177. </script>
  1178. {% endblock %}