templates/shop/reviews/index.html.twig line 1

Open in your IDE?
  1. {% extends 'base_home.html.twig' %}
  2. {% block title %}Avis - {{ shop.name }} | MaketOu{% endblock %}
  3. {% block stylesheets %}
  4.     {{ parent() }}
  5.     <style>
  6.         /* Styles spécifiques aux reviews - tous scoped pour éviter les conflits avec le template de base */
  7.         .shop-reviews-page .review-card {
  8.             border: 1px solid #e0e0e0;
  9.             border-radius: 10px;
  10.             padding: 20px;
  11.             margin-bottom: 20px;
  12.             background: white;
  13.             box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  14.         }
  15.         
  16.         .shop-reviews-page .review-header {
  17.             display: flex;
  18.             justify-content: space-between;
  19.             align-items: center;
  20.             margin-bottom: 15px;
  21.         }
  22.         
  23.         .shop-reviews-page .review-user {
  24.             display: flex;
  25.             align-items: center;
  26.         }
  27.         
  28.         .shop-reviews-page .review-user-avatar {
  29.             width: 40px;
  30.             height: 40px;
  31.             border-radius: 50%;
  32.             background: linear-gradient(45deg, #667eea, #764ba2);
  33.             display: flex;
  34.             align-items: center;
  35.             justify-content: center;
  36.             color: white;
  37.             font-weight: bold;
  38.             margin-right: 12px;
  39.         }
  40.         
  41.         .shop-reviews-page .review-user-info h6 {
  42.             margin: 0;
  43.             font-weight: 600;
  44.             color: #333;
  45.         }
  46.         
  47.         .shop-reviews-page .review-user-info small {
  48.             color: #666;
  49.         }
  50.         
  51.         .shop-reviews-page .review-rating {
  52.             display: flex;
  53.             align-items: center;
  54.             gap: 5px;
  55.         }
  56.         
  57.         .shop-reviews-page .stars {
  58.             color: #ffc107;
  59.             font-size: 18px;
  60.         }
  61.         
  62.         .shop-reviews-page .review-date {
  63.             color: #666;
  64.             font-size: 14px;
  65.         }
  66.         
  67.         .shop-reviews-page .review-content {
  68.             margin-bottom: 15px;
  69.             line-height: 1.6;
  70.             color: #333;
  71.         }
  72.         
  73.         .shop-reviews-page .review-type {
  74.             display: inline-block;
  75.             background: #f8f9fa;
  76.             color: #495057;
  77.             padding: 4px 8px;
  78.             border-radius: 4px;
  79.             font-size: 12px;
  80.             margin-bottom: 10px;
  81.         }
  82.         
  83.         .shop-reviews-page .review-actions {
  84.             display: flex;
  85.             gap: 10px;
  86.             align-items: center;
  87.         }
  88.         
  89.         .shop-reviews-page .helpful-btn {
  90.             background: none;
  91.             border: 1px solid #dee2e6;
  92.             padding: 5px 10px;
  93.             border-radius: 4px;
  94.             font-size: 12px;
  95.             color: #6c757d;
  96.             cursor: pointer;
  97.             transition: all 0.3s ease;
  98.         }
  99.         
  100.         .shop-reviews-page .helpful-btn:hover {
  101.             background: #f8f9fa;
  102.             border-color: #adb5bd;
  103.         }
  104.         
  105.         .shop-reviews-page .helpful-btn.active {
  106.             background: #28a745;
  107.             color: white;
  108.             border-color: #28a745;
  109.         }
  110.         
  111.         .shop-reviews-page .stats-card {
  112.             background: white;
  113.             border-radius: 10px;
  114.             padding: 20px;
  115.             box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  116.             margin-bottom: 20px;
  117.         }
  118.         
  119.         .shop-reviews-page .stats-header {
  120.             display: flex;
  121.             align-items: center;
  122.             margin-bottom: 20px;
  123.         }
  124.         
  125.         .shop-reviews-page .overall-rating {
  126.             font-size: 48px;
  127.             font-weight: bold;
  128.             color: #333;
  129.             margin-right: 20px;
  130.         }
  131.         
  132.         .shop-reviews-page .rating-breakdown {
  133.             flex: 1;
  134.         }
  135.         
  136.         .shop-reviews-page .rating-bar {
  137.             display: flex;
  138.             align-items: center;
  139.             margin-bottom: 8px;
  140.         }
  141.         
  142.         .shop-reviews-page .rating-label {
  143.             width: 60px;
  144.             font-size: 14px;
  145.             color: #666;
  146.         }
  147.         
  148.         .shop-reviews-page .rating-progress {
  149.             flex: 1;
  150.             height: 8px;
  151.             background: #e9ecef;
  152.             border-radius: 4px;
  153.             margin: 0 10px;
  154.             overflow: hidden;
  155.         }
  156.         
  157.         .shop-reviews-page .rating-fill {
  158.             height: 100%;
  159.             background: linear-gradient(45deg, #ffc107, #ff8c00);
  160.             transition: width 0.3s ease;
  161.         }
  162.         
  163.         .shop-reviews-page .rating-count {
  164.             width: 40px;
  165.             text-align: right;
  166.             font-size: 14px;
  167.             color: #666;
  168.         }
  169.         
  170.         .shop-reviews-page .filters-card {
  171.             background: white;
  172.             border-radius: 10px;
  173.             padding: 20px;
  174.             box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  175.             margin-bottom: 20px;
  176.         }
  177.         
  178.         .shop-reviews-page .filter-group {
  179.             margin-bottom: 15px;
  180.         }
  181.         
  182.         .shop-reviews-page .filter-group label {
  183.             font-weight: 600;
  184.             margin-bottom: 5px;
  185.             display: block;
  186.         }
  187.         
  188.         .shop-reviews-page .rating-filter {
  189.             display: flex;
  190.             gap: 10px;
  191.             flex-wrap: wrap;
  192.         }
  193.         
  194.         .shop-reviews-page .rating-btn {
  195.             padding: 8px 12px;
  196.             border: 1px solid #dee2e6;
  197.             background: white;
  198.             border-radius: 4px;
  199.             cursor: pointer;
  200.             transition: all 0.3s ease;
  201.             font-size: 14px;
  202.         }
  203.         
  204.         .shop-reviews-page .rating-btn:hover {
  205.             background: #f8f9fa;
  206.         }
  207.         
  208.         .shop-reviews-page .rating-btn.active {
  209.             background: #007bff;
  210.             color: white;
  211.             border-color: #007bff;
  212.         }
  213.         
  214.         .shop-reviews-page .verified-badge {
  215.             background: #28a745;
  216.             color: white;
  217.             padding: 2px 6px;
  218.             border-radius: 3px;
  219.             font-size: 10px;
  220.             font-weight: bold;
  221.             margin-left: 5px;
  222.         }
  223.         
  224.         .shop-reviews-page .pagination-wrapper {
  225.             display: flex;
  226.             justify-content: center;
  227.             margin-top: 30px;
  228.         }
  229.         
  230.         .shop-reviews-page .btn-review {
  231.             background: linear-gradient(45deg, #667eea, #764ba2);
  232.             border: none;
  233.             color: white;
  234.             padding: 12px 24px;
  235.             border-radius: 6px;
  236.             font-weight: 600;
  237.             text-decoration: none;
  238.             display: inline-block;
  239.             transition: all 0.3s ease;
  240.         }
  241.         
  242.         .shop-reviews-page .btn-review:hover {
  243.             transform: translateY(-2px);
  244.             box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
  245.             color: white;
  246.         }
  247.         
  248.         .shop-reviews-page .empty-reviews {
  249.             text-align: center;
  250.             padding: 60px 20px;
  251.             color: #666;
  252.         }
  253.         
  254.         .shop-reviews-page .empty-reviews i {
  255.             font-size: 64px;
  256.             color: #ddd;
  257.             margin-bottom: 20px;
  258.         }
  259.         
  260.         @media (max-width: 768px) {
  261.             .shop-reviews-page .review-header {
  262.                 flex-direction: column;
  263.                 align-items: flex-start;
  264.             }
  265.             
  266.             .shop-reviews-page .stats-header {
  267.                 flex-direction: column;
  268.                 text-align: center;
  269.             }
  270.             
  271.             .shop-reviews-page .overall-rating {
  272.                 margin-right: 0;
  273.                 margin-bottom: 15px;
  274.             }
  275.             
  276.             .shop-reviews-page .rating-filter {
  277.                 justify-content: center;
  278.             }
  279.         }
  280.     </style>
  281. {% endblock %}
  282. {% block body %}
  283.     <div class="shop-reviews-page">
  284.     <!-- Breadcrumb -->
  285.     <section class="banner-area organic-breadcrumb">
  286.         <div class="container">
  287.             <div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
  288.                 <div class="col-first">
  289.                     <h1>Avis - {{ shop.name }}</h1>
  290.                     <nav class="d-flex align-items-center">
  291.                         <a href="{{ path('ui_home') }}">Accueil<span class="lnr lnr-arrow-right"></span></a>
  292.                         <a href="{{ path('ui_shop_show', {'slug': shop.slug}) }}">{{ shop.name }}<span class="lnr lnr-arrow-right"></span></a>
  293.                         <a href="javascript:void(0)">Avis</a>
  294.                     </nav>
  295.                 </div>
  296.             </div>
  297.         </div>
  298.     </section>
  299.     <section class="blog_area">
  300.         <div class="container my-5">
  301.             <div class="row">
  302.                 <!-- Statistiques des avis -->
  303.                 <div class="col-12">
  304.                     <div class="stats-card">
  305.                         <div class="stats-header">
  306.                             <div class="overall-rating">
  307.                                 {{ stats.averageRating|number_format(1) }}
  308.                                 <div class="stars">{{ stats.averageRating|rating_stars }}</div>
  309.                             </div>
  310.                             <div class="rating-breakdown">
  311.                                 {% set ratingMap = {
  312.                                     5: 'fiveStars',
  313.                                     4: 'fourStars',
  314.                                     3: 'threeStars',
  315.                                     2: 'twoStars',
  316.                                     1: 'oneStar'
  317.                                 } %}
  318.                                 {% for rating in 5..1 %}
  319.                                     {% set ratingKey = ratingMap[rating] %}
  320.                                     {% set ratingCount = attribute(stats, ratingKey)|default(0) %}
  321.                                     {% set percentage = stats.totalReviews > 0 ? (ratingCount / stats.totalReviews * 100) : 0 %}
  322.                                     <div class="rating-bar">
  323.                                         <span class="rating-label">{{ rating }} étoiles</span>
  324.                                         <div class="rating-progress">
  325.                                             <div class="rating-fill" style="width: {{ percentage }}%"></div>
  326.                                         </div>
  327.                                         <span class="rating-count">{{ ratingCount }}</span>
  328.                                     </div>
  329.                                 {% endfor %}
  330.                             </div>
  331.                         </div>
  332.                         <div class="row text-center">
  333.                             <div class="col-md-3">
  334.                                 <h4>{{ stats.totalReviews }}</h4>
  335.                                 <p class="text-muted mb-0">Avis total</p>
  336.                             </div>
  337.                             <div class="col-md-3">
  338.                                 <h4>{{ stats.positivePercentage }}%</h4>
  339.                                 <p class="text-muted mb-0">Positifs</p>
  340.                             </div>
  341.                             <div class="col-md-3">
  342.                                 <h4>{{ stats.verifiedReviews }}</h4>
  343.                                 <p class="text-muted mb-0">Vérifiés</p>
  344.                             </div>
  345.                             <div class="col-md-3">
  346.                                 <h4>{{ shop.followersCount|default(0) }}</h4>
  347.                                 <p class="text-muted mb-0">Followers</p>
  348.                             </div>
  349.                         </div>
  350.                     </div>
  351.                 </div>
  352.                 <!-- Filtres -->
  353.                 <div class="col-md-3">
  354.                     <div class="filters-card">
  355.                         <h5 class="mb-3">Filtrer les avis</h5>
  356.                         
  357.                         <div class="filter-group">
  358.                             <label>Note</label>
  359.                             <div class="rating-filter">
  360.                                 <button class="rating-btn {{ filters.rating == '' ? 'active' : '' }}" data-rating="">
  361.                                     Toutes
  362.                                 </button>
  363.                                 {% for rating in 5..1 %}
  364.                                     <button class="rating-btn {{ filters.rating == rating ? 'active' : '' }}" data-rating="{{ rating }}">
  365.                                         {{ rating }}★
  366.                                     </button>
  367.                                 {% endfor %}
  368.                             </div>
  369.                         </div>
  370.                         <div class="filter-group">
  371.                             <label>
  372.                                 <input type="checkbox" {{ filters.verified ? 'checked' : '' }} id="verified-filter">
  373.                                 Avis vérifiés uniquement
  374.                             </label>
  375.                         </div>
  376.                         <div class="filter-group">
  377.                             <label>Rechercher</label>
  378.                             <input type="text" class="form-control" id="search-filter" placeholder="Rechercher dans les avis..." value="{{ filters.search }}">
  379.                         </div>
  380.                         <button class="btn btn-primary w-100" onclick="applyFilters()">
  381.                             Appliquer les filtres
  382.                         </button>
  383.                     </div>
  384.                     {% if canReview %}
  385.                         <div class="text-center mt-3">
  386.                             <a href="{{ path('shop_reviews_new', {'slug': shop.slug}) }}" class="btn-review">
  387.                                 <i class="lnr lnr-star"></i> Laisser un avis
  388.                             </a>
  389.                         </div>
  390.                     {% endif %}
  391.                 </div>
  392.                 <!-- Liste des avis -->
  393.                 <div class="col-md-9">
  394.                     {% if reviews|length > 0 %}
  395.                         <div id="reviews-container">
  396.                             {% for review in reviews %}
  397.                                 <div class="review-card">
  398.                                     <div class="review-header">
  399.                                         <div class="review-user">
  400.                                             <div class="review-user-avatar">
  401.                                                 {{ review.user.firstName|first|upper|default(review.user.email|first|upper) }}
  402.                                             </div>
  403.                                             <div class="review-user-info">
  404.                                                 <h6>{{ review.user.firstName|default('Utilisateur') }}</h6>
  405.                                                 <small>{{ review.getTimeAgo() }}</small>
  406.                                             </div>
  407.                                         </div>
  408.                                         <div class="review-rating">
  409.                                             <div class="stars">{{ review.getRatingStars() }}</div>
  410.                                             {% if review.isVerified %}
  411.                                                 <span class="verified-badge">✓ Vérifié</span>
  412.                                             {% endif %}
  413.                                         </div>
  414.                                     </div>
  415.                                     {% if review.reviewType %}
  416.                                         <div class="review-type">{{ review.getReviewTypeLabel() }}</div>
  417.                                     {% endif %}
  418.                                     <div class="review-content">
  419.                                         {{ review.comment|nl2br }}
  420.                                     </div>
  421.                                     <div class="review-actions">
  422.                                         <button class="helpful-btn" onclick="markAsHelpful({{ review.id }})">
  423.                                             <i class="lnr lnr-thumbs-up"></i> Utile ({{ review.helpfulCount }})
  424.                                         </button>
  425.                                         <button class="helpful-btn" onclick="markAsNotHelpful({{ review.id }})">
  426.                                             <i class="lnr lnr-thumbs-down"></i> Pas utile ({{ review.notHelpfulCount }})
  427.                                         </button>
  428.                                         
  429.                                         {% if review.user == app.user %}
  430.                                             <div class="ms-auto">
  431.                                                 <a href="{{ path('shop_reviews_edit', {'slug': shop.slug, 'id': review.id}) }}" class="btn btn-sm btn-outline-primary">
  432.                                                     <i class="lnr lnr-pencil"></i> Modifier
  433.                                                 </a>
  434.                                                 <form method="post" action="{{ path('shop_reviews_delete', {'slug': shop.slug, 'id': review.id}) }}" class="d-inline" onsubmit="return confirm('Supprimer cet avis ?')">
  435.                                                     <input type="hidden" name="_token" value="{{ csrf_token('delete' ~ review.id) }}">
  436.                                                     <button type="submit" class="btn btn-sm btn-outline-danger">
  437.                                                         <i class="lnr lnr-trash"></i> Supprimer
  438.                                                     </button>
  439.                                                 </form>
  440.                                             </div>
  441.                                         {% endif %}
  442.                                     </div>
  443.                                 </div>
  444.                             {% endfor %}
  445.                         </div>
  446.                         <!-- Pagination -->
  447.                         {% if pagination.pages > 1 %}
  448.                             <div class="pagination-wrapper">
  449.                                 <nav>
  450.                                     <ul class="pagination">
  451.                                         {% if pagination.page > 1 %}
  452.                                             <li class="page-item">
  453.                                                 <a class="page-link" href="{{ path('shop_reviews_index', {'slug': shop.slug, 'page': pagination.page - 1}) }}">Précédent</a>
  454.                                             </li>
  455.                                         {% endif %}
  456.                                         
  457.                                         {% for page in 1..pagination.pages %}
  458.                                             {% if page == pagination.page %}
  459.                                                 <li class="page-item active">
  460.                                                     <span class="page-link">{{ page }}</span>
  461.                                                 </li>
  462.                                             {% else %}
  463.                                                 <li class="page-item">
  464.                                                     <a class="page-link" href="{{ path('shop_reviews_index', {'slug': shop.slug, 'page': page}) }}">{{ page }}</a>
  465.                                                 </li>
  466.                                             {% endif %}
  467.                                         {% endfor %}
  468.                                         
  469.                                         {% if pagination.page < pagination.pages %}
  470.                                             <li class="page-item">
  471.                                                 <a class="page-link" href="{{ path('shop_reviews_index', {'slug': shop.slug, 'page': pagination.page + 1}) }}">Suivant</a>
  472.                                             </li>
  473.                                         {% endif %}
  474.                                     </ul>
  475.                                 </nav>
  476.                             </div>
  477.                         {% endif %}
  478.                     {% else %}
  479.                         <div class="empty-reviews">
  480.                             <i class="lnr lnr-star"></i>
  481.                             <h3>Aucun avis pour le moment</h3>
  482.                             <p>Soyez le premier à laisser un avis sur cette boutique !</p>
  483.                             {% if canReview %}
  484.                                 <a href="{{ path('shop_reviews_new', {'slug': shop.slug}) }}" class="btn-review">
  485.                                     <i class="lnr lnr-star"></i> Laisser un avis
  486.                                 </a>
  487.                             {% endif %}
  488.                         </div>
  489.                     {% endif %}
  490.                 </div>
  491.             </div>
  492.         </div>
  493.     </section>
  494.     </div>
  495. {% endblock %}
  496. {% block javascripts %}
  497.     {{ parent() }}
  498.     <script>
  499.         // Scoper toutes les fonctions dans le contexte de la page reviews pour éviter les conflits
  500.         (function() {
  501.             'use strict';
  502.             
  503.             function applyFilters() {
  504.                 const ratingBtn = document.querySelector('.shop-reviews-page .rating-btn.active');
  505.                 if (!ratingBtn) return;
  506.                 
  507.                 const rating = ratingBtn.dataset.rating;
  508.                 const verified = document.getElementById('verified-filter')?.checked || false;
  509.                 const search = document.getElementById('search-filter')?.value || '';
  510.                 
  511.                 const params = new URLSearchParams();
  512.                 if (rating) params.append('rating', rating);
  513.                 if (verified) params.append('verified', '1');
  514.                 if (search) params.append('search', search);
  515.                 
  516.                 window.location.href = '{{ path("shop_reviews_index", {"slug": shop.slug}) }}?' + params.toString();
  517.             }
  518.             function markAsHelpful(reviewId) {
  519.                 fetch('{{ path("shop_reviews_helpful", {"slug": shop.slug, "id": "REVIEW_ID"}) }}'.replace('REVIEW_ID', reviewId), {
  520.                     method: 'POST',
  521.                     headers: {
  522.                         'X-Requested-With': 'XMLHttpRequest'
  523.                     }
  524.                 })
  525.                 .then(response => response.json())
  526.                 .then(data => {
  527.                     if (data.success) {
  528.                         location.reload();
  529.                     } else {
  530.                         alert(data.message || 'Une erreur est survenue');
  531.                     }
  532.                 })
  533.                 .catch(error => {
  534.                     console.error('Erreur:', error);
  535.                     alert('Une erreur est survenue lors du vote');
  536.                 });
  537.             }
  538.             function markAsNotHelpful(reviewId) {
  539.                 fetch('{{ path("shop_reviews_not_helpful", {"slug": shop.slug, "id": "REVIEW_ID"}) }}'.replace('REVIEW_ID', reviewId), {
  540.                     method: 'POST',
  541.                     headers: {
  542.                         'X-Requested-With': 'XMLHttpRequest'
  543.                     }
  544.                 })
  545.                 .then(response => response.json())
  546.                 .then(data => {
  547.                     if (data.success) {
  548.                         location.reload();
  549.                     } else {
  550.                         alert(data.message || 'Une erreur est survenue');
  551.                     }
  552.                 })
  553.                 .catch(error => {
  554.                     console.error('Erreur:', error);
  555.                     alert('Une erreur est survenue lors du vote');
  556.                 });
  557.             }
  558.             // Exposer les fonctions globalement pour les boutons onclick
  559.             window.applyFilters = applyFilters;
  560.             window.markAsHelpful = markAsHelpful;
  561.             window.markAsNotHelpful = markAsNotHelpful;
  562.             // Gestion des boutons de filtre par note - scoped à la page reviews
  563.             document.addEventListener('DOMContentLoaded', function() {
  564.                 const reviewPage = document.querySelector('.shop-reviews-page');
  565.                 if (!reviewPage) return;
  566.                 
  567.                 reviewPage.querySelectorAll('.rating-btn').forEach(btn => {
  568.                     btn.addEventListener('click', function() {
  569.                         reviewPage.querySelectorAll('.rating-btn').forEach(b => b.classList.remove('active'));
  570.                         this.classList.add('active');
  571.                     });
  572.                 });
  573.             });
  574.         })();
  575.     </script>
  576. {% endblock %}