templates/home/shops_list.html.twig line 1

Open in your IDE?
  1. {% extends 'base_home.html.twig' %}
  2. {% block title %}Boutiques | MaketOu{% endblock %}
  3. {% block body %}
  4.     <!-- Start Banner Area -->
  5.     {% if selectedCategory and selectedCategory.bannerImage %}
  6.         <section class="banner-area shop-category-banner  organic-breadcrumb" style="background-image: url('{{ asset(selectedCategory.bannerImage) }}'); background-size: cover; background-position: center; min-height: 300px; position: relative;object-fit: cover;height:400px;">
  7.             <div class="banner-overlay" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.3);"></div>
  8.             <div class="container" style="position: relative; z-index: 1;">
  9.                 <div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
  10.                     <div class="col-first text-white">
  11.                         <h1 style="color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.5);">{{ selectedCategory.name }}</h1>
  12.                         <nav class="d-flex align-items-center">
  13.                             <a href="{{ path('ui_home') }}" style="color: white;">Accueil<span class="lnr lnr-arrow-right"></span></a>
  14.                             <a href="{{ path('ui_shops_list') }}" style="color: white;">Boutiques<span class="lnr lnr-arrow-right"></span></a>
  15.                             <a href="javascript:void(0);" style="color: white;">{{ selectedCategory.name }}</a>
  16.                         </nav>
  17.                     </div>
  18.                 </div>
  19.             </div>
  20.         </section>
  21.     {% else %}
  22.         <section class="banner-area organic-breadcrumb">
  23.             <div class="container">
  24.                 <div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
  25.                     <div class="col-first">
  26.                         <h1>{% if selectedCategory %}{{ selectedCategory.name }}{% else %}Boutiques{% endif %}</h1>
  27.                         <nav class="d-flex align-items-center">
  28.                             <a href="{{ path('ui_home') }}">Accueil<span class="lnr lnr-arrow-right"></span></a>
  29.                             <a href="javascript:void(0);">Boutiques</a>
  30.                         </nav>
  31.                     </div>
  32.                 </div>
  33.             </div>
  34.         </section>
  35.     {% endif %}
  36.     <!-- End Banner Area -->
  37.     <div class="container mt-4 mb-4">
  38.         <div class="row">
  39.             <div class="col-xl-3 col-lg-4 col-md-5">
  40.                 <!-- Filtres des boutiques -->
  41.                 <div class="sidebar-categories">
  42.                     <div class="head">Catégories de boutiques</div>
  43.                     <ul class="main-categories">
  44.                         {% for category in shopCategories %}
  45.                             <li class="main-nav-list">
  46.                                 <a href="{{ path('ui_shops_list', {'category': category.slug}) }}" 
  47.                                    class="category-link {% if app.request.query.get('category') == category.slug %}active{% endif %}">
  48.                                     <i class="lnr lnr-store"></i>
  49.                                     {{ category.name }}
  50.                                     <span class="number">({{ category.shops|length }})</span>
  51.                                 </a>
  52.                             </li>
  53.                         {% endfor %}
  54.                     </ul>
  55.                 </div>
  56.                 <!-- Statistiques modernes inline -->
  57.                 <div class="sidebar-filter mt-4">
  58.                     <div class="top-filter-head">Statistiques</div>
  59.                     <div class="modern-stats">
  60.                         <div class="stat-item-inline mt-3">
  61.                             <i class="lnr lnr-store"></i>
  62.                             <div>
  63.                                 <div class="stat-value">{{ total_shops|default(0) }}</div>
  64.                                 <div class="stat-label">Total</div>
  65.                             </div>
  66.                         </div>
  67.                         <div class="stat-item-inline">
  68.                             <i class="lnr lnr-star"></i>
  69.                             <div>
  70.                                 <div class="stat-value">{{ shops|filter(shop => shop.isActive)|length }}</div>
  71.                                 <div class="stat-label">Actives</div>
  72.                             </div>
  73.                         </div>
  74.                         <div class="stat-item-inline">
  75.                             <i class="lnr lnr-checkmark-circle"></i>
  76.                             <div>
  77.                                 <div class="stat-value">{{ shops|filter(shop => shop.isVerified)|length }}</div>
  78.                                 <div class="stat-label">Vérifiées</div>
  79.                             </div>
  80.                         </div>
  81.                     </div>
  82.                 </div>
  83.             </div>
  84.             <div class="col-xl-9 col-lg-8 col-md-7">
  85.                 <!-- Liste des boutiques -->
  86.                 <div class="row" id="shopsContainer">
  87.                     {% for shop in shops|slice(0, 9) %}
  88.                         <div class="mt-4 col-lg-4 col-md-6 shop-item" data-shop-id="{{ shop.id }}">
  89.                             <div class="single-shop h-100">
  90.                                 <div class="shop-image">
  91.                                     <a href="{{ path('ui_shop_show', {'slug': shop.slug}) }}">
  92.                                         {% if shop.logo %}
  93.                                             <img class="img-fluid" src="{{ asset(shop.logo) }}" alt="{{ shop.name }}">
  94.                                         {% else %}
  95.                                             <img class="img-fluid" src="{{ asset('ui/img/shop/default-shop.jpg') }}" alt="{{ shop.name }}">
  96.                                         {% endif %}
  97.                                     </a>
  98.                                     {% if shop.isVerified %}
  99.                                         <div class="verified-badge">
  100.                                             <i class="lnr lnr-checkmark-circle"></i>
  101.                                         </div>
  102.                                     {% endif %}
  103.                                 </div>
  104.                                 
  105.                                 <div class="shop-details">
  106.                                     <div class="shop-header">
  107.                                         <h6 class="shop-name">
  108.                                             <a href="{{ path('ui_shop_show', {'slug': shop.slug}) }}">{{ shop.name }}</a>
  109.                                         </h6>
  110.                                         {% if shop.shopCategory %}
  111.                                             <span class="shop-category">{{ shop.shopCategory.name }}</span>
  112.                                         {% endif %}
  113.                                     </div>
  114.                                     
  115.                                     <div class="shop-stats">
  116.                                         <div class="stat-item">
  117.                                             <span class="stat-value">{{ shop.getActiveProductsCount() }}</span>
  118.                                             <span class="stat-label">Produits</span>
  119.                                         </div>
  120.                         <div class="stat-item">
  121.                             <span class="stat-value followers-count" data-shop-id="{{ shop.id }}">{{ shop.getActiveFollowersCount() }}</span>
  122.                                             <span class="stat-label">Suiveurs</span>
  123.                                         </div>
  124.                                         <div class="stat-item">
  125.                                             <span class="stat-value">{{ shop.viewCount }}</span>
  126.                                             <span class="stat-label">Vues</span>
  127.                                         </div>
  128.                                     </div>
  129.                                     
  130.                                     <div class="shop-description">
  131.                                         <p>{{ shop.description|striptags|length > 100 ? shop.description|striptags|slice(0, 100) ~ '...' : shop.description|striptags }}</p>
  132.                                     </div>
  133.                                     
  134.                                     <div class="shop-actions">
  135.                                         <a href="{{ path('ui_shop_show', {'slug': shop.slug}) }}" class="btn btn-primary btn-sm">
  136.                                             <i class="lnr lnr-eye me-1"></i> Voir
  137.                                         </a>
  138.                                         {% if app.user %}
  139.                                             {% set followed_shop_ids = followed_shop_ids|default([]) %}
  140.                                             {% set is_following = shop.id in followed_shop_ids %}
  141.                                             <button class="btn btn-sm follow-btn {{ is_following ? 'btn-outline-danger' : 'btn-outline-secondary' }}" 
  142.                                                     data-shop-id="{{ shop.id }}"
  143.                                                     data-following="{{ is_following ? 'true' : 'false' }}"
  144.                                                     onclick="toggleFollow({{ shop.id }}, this)">
  145.                                                 <i class="lnr {{ is_following ? 'lnr-cross' : 'lnr-plus' }} me-1"></i>
  146.                                                 <span class="follow-text">
  147.                                                     {{ is_following ? 'Ne plus suivre' : 'Suivre' }}
  148.                                                 </span>
  149.                                             </button>
  150.                                         {% endif %}
  151.                                     </div>
  152.                                 </div>
  153.                             </div>
  154.                         </div>
  155.                     {% endfor %}
  156.                 </div>
  157.                 <!-- Bouton Voir plus -->
  158.                 {% if shops|length > 9 or current_page < total_pages %}
  159.                     <div class="text-center mt-4 mb-4" id="loadMoreContainer">
  160.                         <button class="btn btn-primary btn-lg" id="loadMoreBtn" onclick="loadMoreShops()">
  161.                             <i class="lnr lnr-plus-circle me-2"></i>Voir plus de boutiques
  162.                         </button>
  163.                     </div>
  164.                 {% endif %}
  165.                 <!-- Message si aucune boutique -->
  166.                 {% if shops|length == 0 %}
  167.                     <div class="text-center py-5">
  168.                         <i class="lnr lnr-store" style="font-size: 4rem; color: #ccc;"></i>
  169.                         <h4 class="mt-3">Aucune boutique trouvée</h4>
  170.                         <p class="text-muted">Aucune boutique ne correspond à vos critères de recherche.</p>
  171.                     </div>
  172.                 {% endif %}
  173.             </div>
  174.         </div>
  175.     </div>
  176. {% endblock %}
  177. {% block stylesheets %}
  178. <style>
  179. .single-shop {
  180.     background: white;
  181.     border-radius: 12px;
  182.     padding: 20px;
  183.     margin-bottom: 30px;
  184.     box-shadow: 0 2px 10px rgba(0,0,0,0.1);
  185.     transition: all 0.3s ease;
  186.     display: flex;
  187.     flex-direction: column;
  188.     height: 100%;
  189.     overflow: hidden; /* Empêcher le débordement */
  190. }
  191. .single-shop:hover {
  192.     transform: translateY(-5px);
  193.     box-shadow: 0 8px 25px rgba(0,0,0,0.15);
  194. }
  195. .shop-image {
  196.     position: relative;
  197.     margin-bottom: 15px;
  198. }
  199. .shop-image img {
  200.     width: 100%;
  201.     height: 200px;
  202.     object-fit: cover;
  203.     border-radius: 8px;
  204. }
  205. .verified-badge {
  206.     position: absolute;
  207.     top: 10px;
  208.     right: 10px;
  209.     background: #28a745;
  210.     color: white;
  211.     padding: 5px 8px;
  212.     border-radius: 12px;
  213.     font-size: 0.8rem;
  214. }
  215. .shop-header {
  216.     margin-bottom: 15px;
  217. }
  218. .shop-name {
  219.     margin: 0 0 5px 0;
  220.     font-size: 1.1rem;
  221.     font-weight: 600;
  222. }
  223. .shop-name a {
  224.     color: #333;
  225.     text-decoration: none;
  226. }
  227. .shop-name a:hover {
  228.     color: #007bff;
  229. }
  230. .shop-category {
  231.     background: #f8f9fa;
  232.     color: #666;
  233.     padding: 2px 8px;
  234.     border-radius: 12px;
  235.     font-size: 0.8rem;
  236. }
  237. .shop-stats {
  238.     display: flex;
  239.     justify-content: space-around;
  240.     margin: 15px 0;
  241.     padding: 15px 0;
  242.     border-top: 1px solid #f0f0f0;
  243.     border-bottom: 1px solid #f0f0f0;
  244. }
  245. .stat-item {
  246.     text-align: center;
  247. }
  248. .stat-value {
  249.     display: block;
  250.     font-size: 1.2rem;
  251.     font-weight: 600;
  252.     color: #333;
  253. }
  254. .stat-label {
  255.     font-size: 0.8rem;
  256.     color: #666;
  257.     text-transform: uppercase;
  258.     letter-spacing: 0.5px;
  259. }
  260. .shop-description {
  261.     margin: 15px 0;
  262.     color: #666;
  263.     font-size: 0.9rem;
  264.     line-height: 1.4;
  265. }
  266. .shop-actions {
  267.     display: flex;
  268.     gap: 8px;
  269.     margin-top: auto;
  270.     padding-top: 15px;
  271.     flex-wrap: wrap;
  272. }
  273. .shop-actions .btn {
  274.     flex: 1 1 auto;
  275.     min-width: 0;
  276.     font-size: 0.85rem;
  277.     white-space: nowrap;
  278.     overflow: hidden;
  279.     text-overflow: ellipsis;
  280.     padding: 8px 12px;
  281. }
  282. .shop-actions .btn i {
  283.     margin-right: 4px;
  284.     flex-shrink: 0;
  285. }
  286. /* Assurer que shop-details utilise flexbox pour garder les boutons en bas */
  287. .shop-details {
  288.     display: flex;
  289.     flex-direction: column;
  290.     flex: 1;
  291.     min-height: 0; /* Important pour le flexbox */
  292. }
  293. /* Statistiques modernes inline */
  294. .modern-stats {
  295.     display: flex;
  296.     flex-direction: column;
  297.     gap: 15px;
  298. }
  299. .stat-item-inline {
  300.     display: flex;
  301.     align-items: center;
  302.     gap: 15px;
  303.     padding: 15px;
  304.     background: #f8f9fa;
  305.     border-radius: 10px;
  306.     transition: all 0.3s ease;
  307. }
  308. .stat-item-inline:hover {
  309.     background: #e9ecef;
  310.     transform: translateX(5px);
  311. }
  312. .stat-item-inline i {
  313.     font-size: 1.5rem;
  314.     color: #007bff;
  315.     width: 40px;
  316.     text-align: center;
  317. }
  318. .stat-item-inline .stat-value {
  319.     font-size: 1.5rem;
  320.     font-weight: 700;
  321.     color: #333;
  322.     line-height: 1;
  323. }
  324. .stat-item-inline .stat-label {
  325.     font-size: 0.85rem;
  326.     color: #666;
  327.     text-transform: uppercase;
  328.     letter-spacing: 0.5px;
  329.     margin-top: 5px;
  330. }
  331. .stat-number {
  332.     font-size: 1.5rem;
  333.     font-weight: 700;
  334.     color: #007bff;
  335.     margin-top: 5px;
  336. }
  337. @media (max-width: 768px) {
  338.     .single-shop {
  339.         padding: 15px;
  340.     }
  341.     
  342.     .shop-stats {
  343.         flex-direction: column;
  344.         gap: 10px;
  345.     }
  346.     
  347.     .shop-actions {
  348.         flex-direction: column;
  349.     }
  350. }
  351. </style>
  352. {% endblock %}
  353. {% block javascripts %}
  354. <script>
  355. let currentPage = {{ current_page }};
  356. let totalPages = {{ total_pages }};
  357. let isLoading = false;
  358. function sortShops(sortValue) {
  359.     console.log('Tri des boutiques:', sortValue);
  360.     // TODO: Implémenter le tri AJAX
  361. }
  362. function toggleFollow(shopId, button) {
  363.     if (!button) {
  364.         button = document.querySelector(`.follow-btn[data-shop-id="${shopId}"]`);
  365.         if (!button) return;
  366.     }
  367.     const isFollowing = button.getAttribute('data-following') === 'true';
  368.     const originalHtml = button.innerHTML;
  369.     button.disabled = true;
  370.     button.innerHTML = '<span class="spinner-border spinner-border-sm"></span>';
  371.     fetch(`{{ path('ui_api_shop_toggle_follow', {'id': 'SHOP_ID'}) }}`.replace('SHOP_ID', shopId), {
  372.         method: 'POST',
  373.         headers: {
  374.             'Content-Type': 'application/json',
  375.             'X-Requested-With': 'XMLHttpRequest'
  376.         },
  377.         body: JSON.stringify({ action: isFollowing ? 'unfollow' : 'follow' })
  378.     })
  379.     .then(response => response.json())
  380.     .then(data => {
  381.         if (data.success) {
  382.             // Mettre à jour l'état du bouton
  383.             const isFollowing = data.isFollowing === true;
  384.             button.setAttribute('data-following', isFollowing ? 'true' : 'false');
  385.             
  386.             // Mettre à jour les classes CSS
  387.             button.classList.remove('btn-outline-secondary', 'btn-outline-danger');
  388.             if (isFollowing) {
  389.                 button.classList.add('btn-outline-danger');
  390.             } else {
  391.                 button.classList.add('btn-outline-secondary');
  392.             }
  393.             // Reconstruire le HTML complet du bouton
  394.             const iconClass = isFollowing ? 'lnr-cross' : 'lnr-plus';
  395.             const textContent = isFollowing ? 'Ne plus suivre' : 'Suivre';
  396.             button.innerHTML = `<i class="lnr ${iconClass} me-1"></i><span class="follow-text">${textContent}</span>`;
  397.             // Mettre à jour le compteur
  398.             const followersCounter = document.querySelector(`.followers-count[data-shop-id="${shopId}"]`);
  399.             if (followersCounter && typeof data.followersCount !== 'undefined') {
  400.                 followersCounter.textContent = data.followersCount;
  401.             }
  402.             if (data.message) {
  403.                 console.log(data.message);
  404.             }
  405.             
  406.             button.disabled = false;
  407.         } else {
  408.             alert(data.message || 'Impossible de mettre à jour le suivi.');
  409.             button.innerHTML = originalHtml;
  410.             button.disabled = false;
  411.         }
  412.     })
  413.     .catch(error => {
  414.         console.error('Erreur suivi boutique:', error);
  415.         alert('Erreur lors de la mise à jour du suivi.');
  416.         button.innerHTML = originalHtml;
  417.         button.disabled = false;
  418.     });
  419. }
  420. function loadMoreShops() {
  421.     if (isLoading || currentPage >= totalPages) return;
  422.     
  423.     isLoading = true;
  424.     const btn = document.getElementById('loadMoreBtn');
  425.     const originalText = btn.innerHTML;
  426.     btn.disabled = true;
  427.     btn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Chargement...';
  428.     
  429.     currentPage++;
  430.     const url = new URL(window.location.href);
  431.     url.searchParams.set('page', currentPage);
  432.     
  433.     fetch(url.toString(), {
  434.         headers: {
  435.             'X-Requested-With': 'XMLHttpRequest'
  436.         }
  437.     })
  438.     .then(response => response.json())
  439.     .then(data => {
  440.         if (data.success && data.shops.length > 0) {
  441.             const container = document.getElementById('shopsContainer');
  442.             data.shops.forEach(shop => {
  443.                 const shopHtml = createShopCard(shop);
  444.                 container.insertAdjacentHTML('beforeend', shopHtml);
  445.             });
  446.             
  447.             // Mettre à jour le bouton
  448.             if (!data.hasMore) {
  449.                 document.getElementById('loadMoreContainer').style.display = 'none';
  450.             } else {
  451.                 btn.disabled = false;
  452.                 btn.innerHTML = originalText;
  453.             }
  454.         } else {
  455.             document.getElementById('loadMoreContainer').style.display = 'none';
  456.         }
  457.         isLoading = false;
  458.     })
  459.     .catch(error => {
  460.         console.error('Erreur:', error);
  461.         btn.disabled = false;
  462.         btn.innerHTML = originalText;
  463.         isLoading = false;
  464.     });
  465. }
  466. function createShopCard(shop) {
  467.     const logoUrl = shop.logo ? '/' + shop.logo : '{{ asset("ui/img/shop/default-shop.jpg") }}';
  468.     const verifiedBadge = shop.isVerified ? 
  469.         '<div class="verified-badge"><i class="lnr lnr-checkmark-circle"></i></div>' : '';
  470.     const category = shop.category ? 
  471.         `<span class="shop-category">${shop.category}</span>` : '';
  472.     
  473.     return `
  474.         <div class="mt-4 col-lg-4 col-md-6 shop-item" data-shop-id="${shop.id}">
  475.             <div class="single-shop h-100">
  476.                 <div class="shop-image">
  477.                     <a href="/shop/${shop.slug}">
  478.                         <img class="img-fluid" src="${logoUrl}" alt="${shop.name}">
  479.                     </a>
  480.                     ${verifiedBadge}
  481.                 </div>
  482.                 <div class="shop-details">
  483.                     <div class="shop-header">
  484.                         <h6 class="shop-name">
  485.                             <a href="/shop/${shop.slug}">${shop.name}</a>
  486.                         </h6>
  487.                         ${category}
  488.                     </div>
  489.                     <div class="shop-stats">
  490.                         <div class="stat-item">
  491.                             <span class="stat-value">${shop.productsCount || 0}</span>
  492.                             <span class="stat-label">Produits</span>
  493.                         </div>
  494.                         <div class="stat-item">
  495.                             <span class="stat-value">${shop.followersCount || 0}</span>
  496.                             <span class="stat-label">Suiveurs</span>
  497.                         </div>
  498.                         <div class="stat-item">
  499.                             <span class="stat-value">${shop.viewCount || 0}</span>
  500.                             <span class="stat-label">Vues</span>
  501.                         </div>
  502.                     </div>
  503.                     <div class="shop-description">
  504.                         <p>${(shop.description || '').substring(0, 100)}${(shop.description || '').length > 100 ? '...' : ''}</p>
  505.                     </div>
  506.                     <div class="shop-actions">
  507.                         <a href="/shop/${shop.slug}" class="btn btn-primary btn-sm" style="flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
  508.                             <i class="lnr lnr-eye me-1"></i> Voir
  509.                         </a>
  510.                         {% if app.user %}
  511.                         <button class="btn btn-sm follow-btn ${shop.following ? 'btn-outline-danger' : 'btn-outline-secondary'}" 
  512.                                 data-shop-id="${shop.id}"
  513.                                 data-following="${shop.following ? 'true' : 'false'}"
  514.                                 onclick="toggleFollow(${shop.id}, this)"
  515.                                 style="flex: 1 1 auto; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">
  516.                             <i class="lnr ${shop.following ? 'lnr-cross' : 'lnr-plus'} me-1"></i>
  517.                             <span class="follow-text">${shop.following ? 'Ne plus suivre' : 'Suivre'}</span>
  518.                         </button>
  519.                         {% endif %}
  520.                     </div>
  521.                 </div>
  522.             </div>
  523.         </div>
  524.     `;
  525. }
  526. </script>
  527. {% endblock %}