{% extends 'base_home.html.twig' %}
{% block title %}
{{ product.name }}
| MaketOu
{% endblock %}
{% block stylesheets %}
<style>
/* Styles pour l'affichage de la boutique */
.shop-info {
padding: 10px 0;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 15px;
}
.shop-link {
color: #ffa200;
text-decoration: none;
font-weight: 500;
}
.shop-link:hover {
color: #e8910a;
text-decoration: underline;
}
/* Styles pour les statistiques du produit */
.product-stats {
background: #f8f9fa;
padding: 15px;
border-radius: 8px;
border: 1px solid #e9ecef;
}
.product-stats .stat-item {
text-align: center;
}
.product-stats .stat-number {
display: block;
font-size: 1.2rem;
font-weight: bold;
color: #ffa200;
}
.product-stats .stat-label {
color: #666;
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Message de confirmation */
#cart-message {
border: none;
background: #d4edda;
color: #155724;
border-radius: 6px;
padding: 12px 16px;
margin-top: 15px;
}
/* Amélioration des boutons */
.card_area .primary-btn {
background: #ffa200;
border: none;
padding: 12px 24px;
border-radius: 6px;
color: white;
font-weight: 500;
transition: all 0.3s ease;
}
.card_area .primary-btn:hover {
background: #e8910a;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 162, 0, 0.3);
}
.icon_btn:hover {
background: #ffa200;
color: white;
border-color: #ffa200;
}
/* Styles pour la galerie moderne de produits */
.product-gallery-modern {
display: flex;
gap: 15px;
position: relative;
}
.thumbnails-container {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
width: 80px;
flex-shrink: 0;
}
/* Cacher les boutons jusqu'au survol */
.thumbnails-container .thumbnail-nav-btn {
opacity: 0;
visibility: hidden;
transition: opacity 0.2s ease, visibility 0.2s ease;
}
.thumbnails-container:hover .thumbnail-nav-btn {
opacity: 1;
visibility: visible;
}
.product-thumbnails {
display: flex;
flex-direction: column;
gap: 10px;
width: 100%;
height: 400px;
overflow: hidden;
position: relative;
}
.thumbnails-wrapper {
display: flex;
flex-direction: column;
gap: 10px;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
will-change: transform;
}
.thumbnail-nav-btn {
width: 30px;
height: 30px;
border-radius: 50%;
background: white;
border: 2px solid #e0e0e0;
display: flex !important;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
margin: 0;
color: #666;
z-index: 10;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
position: relative;
flex-shrink: 0;
}
.thumbnail-nav-btn:hover {
background: #ffa200;
border-color: #ffa200;
color: white;
transform: scale(1.1);
box-shadow: 0 4px 12px rgba(255, 162, 0, 0.3);
}
.thumbnail-nav-btn:active {
transform: scale(0.95);
}
.thumbnail-nav-btn.disabled {
opacity: 0.3;
cursor: not-allowed;
pointer-events: none;
filter: grayscale(1);
}
.thumbnail-nav-btn i {
font-size: 20px;
font-weight: bold;
}
.thumbnail-item {
width: 80px;
height: 80px;
min-width: 80px;
min-height: 80px;
max-width: 80px;
max-height: 80px;
border: 2px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: border-color 0.3s ease, border-width 0.3s ease, box-shadow 0.3s ease;
position: relative;
background: #f8f9fa;
border-bottom: 2px solid #e0e0e0;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.thumbnail-item:hover {
border-color: #ffa200;
border-bottom-color: #ffa200;
border-width: 3px;
box-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2);
}
.thumbnail-item.active {
border-color: #ffa200 !important;
border-bottom-color: #ffa200 !important;
border-width: 3px !important;
box-shadow: 0 0 0 2px rgba(255, 162, 0, 0.2) !important;
}
.thumbnail-item::after,
.thumbnail-item::before {
display: none !important;
content: none !important;
}
.thumbnail-item img {
width: 100%;
height: 100%;
object-fit: cover !important;
object-position: center !important;
transition: transform 0.3s ease;
}
.thumbnail-item:hover img {
transform: scale(1.05);
}
.thumbnail-item.active img {
transform: scale(1.05);
}
.product-main-image-wrapper {
flex: 1;
position: relative;
background: #fff;
height: 400px;
border-radius: 12px;
overflow: visible;
border: 1px solid #e0e0e0;
z-index: 1;
}
.product-main-image {
position: relative;
width: 100%;
padding-top: 100%;
overflow: visible;
background: #f8f9fa;
display: flex;
align-items: center;
justify-content: center;
z-index: 1;
}
.product-main-image img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: contain !important;
object-position: center !important;
transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
opacity: 1;
}
.product-main-image img.loading {
opacity: 0.5;
}
.product-main-image:hover img {
transform: scale(1.08);
}
.product-main-image img.fade-in {
animation: fadeInImage 0.4s ease;
}
@keyframes fadeInImage {
from {
opacity: 0;
transform: scale(0.95);
}
to {
opacity: 1;
transform: scale(1);
}
}
.image-overlay-icons {
position: absolute !important;
top: 20px !important;
right: 20px !important;
display: flex !important;
flex-direction: column !important;
gap: 12px !important;
z-index: 10 !important;
opacity: 0 !important;
transition: opacity 0.3s ease, transform 0.3s ease !important;
transform: translateX(10px) !important;
}
.product-main-image-wrapper:hover .image-overlay-icons {
opacity: 1 !important;
transform: translateX(0) !important;
}
.product-main-image-wrapper .icon-btn,
.image-overlay-icons .icon-btn {
width: 48px !important;
height: 48px !important;
min-width: 48px !important;
min-height: 48px !important;
max-width: 48px !important;
max-height: 48px !important;
border-radius: 50% !important;
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(10px) !important;
border: 2px solid rgba(255, 255, 255, 0.8) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
color: #333 !important;
position: relative !important;
overflow: hidden !important;
padding: 0 !important;
margin: 0 !important;
font-size: inherit !important;
font-weight: normal !important;
line-height: 1 !important;
text-align: center !important;
text-decoration: none !important;
white-space: nowrap !important;
vertical-align: middle !important;
font-family: inherit !important;
text-transform: none !important;
letter-spacing: normal !important;
}
.product-main-image-wrapper .icon-btn::before,
.image-overlay-icons .icon-btn::before {
content: '' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
width: 0 !important;
height: 0 !important;
border-radius: 50% !important;
background: rgba(255, 162, 0, 0.2) !important;
transform: translate(-50%, -50%) !important;
transition: width 0.4s ease, height 0.4s ease !important;
}
.product-main-image-wrapper .icon-btn:hover::before,
.image-overlay-icons .icon-btn:hover::before {
width: 100% !important;
height: 100% !important;
}
.product-main-image-wrapper .icon-btn:hover,
.image-overlay-icons .icon-btn:hover {
background: #ffa200 !important;
border-color: #ffa200 !important;
color: white !important;
transform: scale(1.15) rotate(5deg) !important;
box-shadow: 0 6px 20px rgba(255, 162, 0, 0.5) !important;
}
.product-main-image-wrapper .icon-btn:active,
.image-overlay-icons .icon-btn:active {
transform: scale(1.05) rotate(0deg) !important;
}
.product-main-image-wrapper .icon-btn i,
.image-overlay-icons .icon-btn i {
font-size: 20px !important;
position: relative !important;
z-index: 1 !important;
transition: transform 0.3s ease !important;
margin: 0 !important;
display: block !important;
}
.product-main-image-wrapper .icon-btn:hover i,
.image-overlay-icons .icon-btn:hover i {
transform: scale(1.1) !important;
}
.product-main-image-wrapper .favorite-btn.active,
.image-overlay-icons .favorite-btn.active {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%) !important;
border-color: #ff6b6b !important;
color: white !important;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.4) !important;
}
.product-main-image-wrapper .favorite-btn.active:hover,
.image-overlay-icons .favorite-btn.active:hover {
background: linear-gradient(135deg, #ee5a6f 0%, #dd4a5f 100%) !important;
transform: scale(1.15) rotate(-5deg) !important;
}
.product-main-image-wrapper .image-counter,
.image-counter {
position: absolute !important;
bottom: 20px !important;
right: 20px !important;
background: rgba(0, 0, 0, 0.75) !important;
backdrop-filter: blur(8px) !important;
color: white !important;
padding: 8px 16px !important;
border-radius: 25px !important;
font-size: 14px !important;
font-weight: 600 !important;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3) !important;
z-index: 5 !important;
display: flex !important;
align-items: center !important;
gap: 6px !important;
}
.product-main-image-wrapper .image-counter span,
.image-counter span {
font-weight: 700 !important;
color: #ffa200 !important;
}
.product-main-image-wrapper .image-indicators,
#image-indicators {
position: absolute !important;
bottom: 20px !important;
left: 50% !important;
transform: translateX(-50%) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
gap: 10px !important;
background: rgba(0, 0, 0, 0.4) !important;
backdrop-filter: blur(8px) !important;
padding: 8px 16px !important;
border-radius: 25px !important;
z-index: 5 !important;
}
.product-main-image-wrapper .indicator,
#image-indicators .indicator,
.image-indicators .indicator {
width: 12px !important;
height: 12px !important;
min-width: 12px !important;
min-height: 12px !important;
max-width: 12px !important;
max-height: 12px !important;
border-radius: 50% !important;
border: 2px solid rgba(255, 255, 255, 0.6) !important;
background: rgba(255, 255, 255, 0.3) !important;
cursor: pointer !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
padding: 0 !important;
margin: 0 !important;
position: relative !important;
display: block !important;
text-align: center !important;
text-decoration: none !important;
white-space: nowrap !important;
vertical-align: middle !important;
font-size: 0 !important;
line-height: 0 !important;
box-sizing: border-box !important;
}
.product-main-image-wrapper .indicator::before,
#image-indicators .indicator::before,
.image-indicators .indicator::before {
content: '' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
transform: translate(-50%, -50%) scale(0) !important;
width: 20px !important;
height: 20px !important;
border-radius: 50% !important;
background: rgba(255, 162, 0, 0.3) !important;
transition: transform 0.3s ease !important;
}
.product-main-image-wrapper .indicator:hover,
#image-indicators .indicator:hover,
.image-indicators .indicator:hover {
border-color: #ffa200 !important;
background: rgba(255, 162, 0, 0.6) !important;
transform: scale(1.2) !important;
}
.product-main-image-wrapper .indicator:hover::before,
#image-indicators .indicator:hover::before,
.image-indicators .indicator:hover::before {
transform: translate(-50%, -50%) scale(1) !important;
}
.product-main-image-wrapper .indicator.active,
#image-indicators .indicator.active,
.image-indicators .indicator.active {
border-color: #ffa200 !important;
background: #ffa200 !important;
box-shadow: 0 0 0 3px rgba(255, 162, 0, 0.3), 0 2px 8px rgba(255, 162, 0, 0.5) !important;
transform: scale(1.3) !important;
}
.product-main-image-wrapper .indicator.active::before,
#image-indicators .indicator.active::before,
.image-indicators .indicator.active::before {
transform: translate(-50%, -50%) scale(1) !important;
background: rgba(255, 162, 0, 0.4) !important;
}
/* Modal de zoom d'image avec transitions modernes */
.image-zoom-modal {
display: none;
position: fixed;
z-index: 9999;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0);
overflow: auto;
opacity: 0;
transition: opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease;
backdrop-filter: blur(0px);
}
.image-zoom-modal.active {
display: flex;
align-items: center;
justify-content: center;
opacity: 1;
background: rgba(0, 0, 0, 0.95);
backdrop-filter: blur(10px);
}
.zoom-modal-content {
position: relative;
max-width: 90%;
max-height: 90%;
margin: auto;
opacity: 0;
transform: scale(0.8);
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
}
.image-zoom-modal.active .zoom-modal-content {
opacity: 1;
transform: scale(1);
}
.zoom-modal-content img {
width: 100%;
height: auto;
max-height: 90vh;
object-fit: contain !important;
object-position: center !important;
border-radius: 8px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
opacity: 0;
transition: opacity 0.3s ease;
}
.zoom-modal-content img.loaded {
opacity: 1;
}
/* Loader moderne */
.zoom-loader {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.zoom-loader-spinner {
width: 60px;
height: 60px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-top-color: #ffa200;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.zoom-loader-text {
color: white;
margin-top: 15px;
text-align: center;
font-size: 14px;
font-weight: 500;
}
.close-zoom {
position: absolute;
top: 30px;
right: 30px;
color: white;
font-size: 32px;
font-weight: 300;
cursor: pointer;
z-index: 10000;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.1);
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
opacity: 0;
transform: scale(0.8);
}
.image-zoom-modal.active .close-zoom {
opacity: 1;
transform: scale(1);
transition-delay: 0.2s;
}
.close-zoom:hover {
background: rgba(255, 162, 0, 0.9);
border-color: #ffa200;
transform: scale(1.1) rotate(90deg);
box-shadow: 0 4px 20px rgba(255, 162, 0, 0.4);
}
/* Styles pour le contrôle de quantité amélioré */
.product_count {
display: flex !important;
align-items: center !important;
gap: 15px !important;
margin-bottom: 24px !important;
position: relative !important;
}
.product_count label {
font-size: 14px !important;
color: #777777 !important;
font-family: "Roboto", sans-serif !important;
font-weight: normal !important;
margin-bottom: 0 !important;
white-space: nowrap !important;
padding-right: 10px !important;
}
.quantity-controls-wrapper {
display: flex !important;
align-items: center !important;
border: 1px solid #eeeeee !important;
border-radius: 4px !important;
overflow: hidden !important;
background: #fff !important;
position: relative !important;
}
.quantity-btn {
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 40px !important;
height: 40px !important;
border: none !important;
background: #f8f9fa !important;
color: #666 !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
font-size: 16px !important;
padding: 0 !important;
margin: 0 !important;
position: relative !important;
top: auto !important;
bottom: auto !important;
right: auto !important;
left: auto !important;
}
.quantity-btn:hover {
background: #e9ecef !important;
color: #ffa200 !important;
}
.quantity-btn:active {
background: #dee2e6 !important;
transform: scale(0.95) !important;
}
.quantity-btn i {
font-size: 14px !important;
}
.quantity-input {
width: 60px !important;
height: 40px !important;
border: none !important;
border-left: 1px solid #eeeeee !important;
border-right: 1px solid #eeeeee !important;
text-align: center !important;
font-size: 14px !important;
font-weight: 500 !important;
padding: 0 !important;
margin: 0 !important;
outline: none !important;
background: #fff !important;
position: relative !important;
}
.quantity-input:focus {
border-left-color: #ffa200 !important;
border-right-color: #ffa200 !important;
}
/* Surcharger les styles existants pour les boutons */
.product_count .quantity-btn {
position: relative !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
width: 40px !important;
height: 40px !important;
border: none !important;
background: #f8f9fa !important;
color: #666 !important;
cursor: pointer !important;
transition: all 0.3s ease !important;
font-size: 16px !important;
padding: 0 !important;
margin: 0 !important;
top: auto !important;
bottom: auto !important;
right: auto !important;
left: auto !important;
}
.product_count .quantity-btn:hover {
background: #e9ecef !important;
color: #ffa200 !important;
}
.product_count .quantity-btn:active {
background: #dee2e6 !important;
transform: scale(0.95) !important;
}
.product_count .quantity-btn i,
.product_count .quantity-btn .quantity-icon {
font-size: 20px !important;
font-weight: 300 !important;
line-height: 1 !important;
display: inline-block !important;
}
.product_count .quantity-btn .quantity-icon {
font-size: 24px !important;
font-weight: 300 !important;
line-height: 1 !important;
}
/* Désactiver les boutons si nécessaire */
.quantity-btn:disabled {
opacity: 0.5 !important;
cursor: not-allowed !important;
}
.quantity-btn:disabled:hover {
background: #f8f9fa !important;
color: #666 !important;
}
/* Surcharger les styles pour le wrapper */
.product_count .quantity-controls-wrapper {
display: flex !important;
align-items: center !important;
border: 1px solid #eeeeee !important;
border-radius: 4px !important;
overflow: hidden !important;
background: #fff !important;
position: relative !important;
}
/* Surcharger tous les styles de main.css pour les boutons dans product_count */
.product_count button.quantity-btn,
.product_count .quantity-btn {
display: flex !important;
position: relative !important;
top: auto !important;
bottom: auto !important;
right: auto !important;
left: auto !important;
width: 40px !important;
height: 40px !important;
background: #f8f9fa !important;
color: #666 !important;
border: none !important;
box-shadow: none !important;
}
.product_count button.quantity-btn:hover,
.product_count .quantity-btn:hover {
background: #e9ecef !important;
color: #ffa200 !important;
}
/* Surcharger les styles pour l'input */
.product_count .quantity-input,
.product_count input.quantity-input {
width: 60px !important;
height: 40px !important;
border: none !important;
border-left: 1px solid #eeeeee !important;
border-right: 1px solid #eeeeee !important;
text-align: center !important;
padding: 0 !important;
padding-left: 0 !important;
position: relative !important;
}
/* Boutons de navigation sur l'image principale */
.main-image-nav {
position: absolute;
top: 50%;
left: 0;
right: 0;
transform: translateY(-50%);
display: flex;
justify-content: space-between;
padding: 0 12px;
pointer-events: none !important;
z-index: 10 !important;
}
.main-image-nav .main-image-nav-btn,
.main-image-nav-btn {
pointer-events: all !important;
z-index: 1000 !important;
position: relative !important;
cursor: pointer !important;
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
user-select: none !important;
user-select: none !important;
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
width: 48px !important;
height: 48px !important;
min-width: 48px !important;
min-height: 48px !important;
max-width: 48px !important;
max-height: 48px !important;
border-radius: 50% !important;
background: rgba(255, 255, 255, 0.95) !important;
backdrop-filter: blur(10px) !important;
border: 2px solid rgba(255, 255, 255, 0.8) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2) !important;
color: #333 !important;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
cursor: pointer !important;
overflow: hidden !important;
padding: 0 !important;
margin: 0 !important;
font-size: inherit !important;
font-weight: normal !important;
line-height: 1 !important;
text-align: center !important;
text-decoration: none !important;
white-space: nowrap !important;
vertical-align: middle !important;
font-family: inherit !important;
text-transform: none !important;
letter-spacing: normal !important;
-webkit-tap-highlight-color: transparent !important;
touch-action: manipulation !important;
}
.main-image-nav .main-image-nav-btn::before,
.main-image-nav-btn::before {
content: '' !important;
position: absolute !important;
top: 50% !important;
left: 50% !important;
width: 0 !important;
height: 0 !important;
border-radius: 50% !important;
background: rgba(255, 162, 0, 0.2) !important;
transform: translate(-50%, -50%) !important;
transition: width 0.4s ease, height 0.4s ease !important;
}
.main-image-nav .main-image-nav-btn:hover::before,
.main-image-nav-btn:hover::before {
width: 100% !important;
height: 100% !important;
}
.main-image-nav .main-image-nav-btn:hover,
.main-image-nav-btn:hover {
background: #ffa200 !important;
border-color: #ffa200 !important;
color: #fff !important;
transform: scale(1.15) !important;
box-shadow: 0 6px 20px rgba(255, 162, 0, 0.5) !important;
}
.main-image-nav .main-image-nav-btn:active,
.main-image-nav-btn:active {
transform: scale(1.05) !important;
}
.main-image-nav .main-image-nav-btn i,
.main-image-nav-btn i {
position: relative !important;
z-index: 1 !important;
font-size: 20px !important;
transition: transform 0.3s ease !important;
margin: 0 !important;
display: block !important;
}
.main-image-nav .main-image-nav-btn:hover i,
.main-image-nav-btn:hover i {
transform: scale(1.2) !important;
}
.main-image-nav .main-image-nav-btn.disabled,
.main-image-nav-btn.disabled {
opacity: 0.4 !important;
cursor: not-allowed !important;
pointer-events: none !important;
}
/* Modal personnalisé pour remplacer les alert */
.custom-alert-modal .modal-content {
border-radius: 12px;
border: none;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
}
.custom-alert-modal .modal-header {
border-bottom: none;
padding: 1.5rem 1.5rem 0.5rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 12px 12px 0 0;
}
.custom-alert-modal .modal-header .btn-close {
filter: brightness(0) invert(1);
opacity: 0.8;
}
.custom-alert-modal .modal-header .btn-close:hover {
opacity: 1;
}
.custom-alert-modal .modal-body {
padding: 1.5rem;
text-align: center;
}
.custom-alert-modal .modal-body .alert-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.custom-alert-modal .modal-body .alert-icon.success {
color: #28a745;
}
.custom-alert-modal .modal-body .alert-icon.error {
color: #dc3545;
}
.custom-alert-modal .modal-body .alert-icon.warning {
color: #ffc107;
}
.custom-alert-modal .modal-body .alert-icon.info {
color: #17a2b8;
}
.custom-alert-modal .modal-body .alert-message {
font-size: 1.1rem;
color: #333;
margin-bottom: 1rem;
line-height: 1.6;
}
.custom-alert-modal .modal-footer {
border-top: none;
padding: 0.5rem 1.5rem 1.5rem;
justify-content: center;
}
.custom-alert-modal .modal-footer .btn {
min-width: 120px;
padding: 0.6rem 1.5rem;
border-radius: 8px;
font-weight: 500;
}
/* Responsive pour single-product */
@media(max-width: 991.98px) {
.product-gallery-modern {
flex-direction: column;
}
.thumbnails-container {
flex-direction: row;
width: 100%;
overflow-x: auto;
padding: 10px 0;
}
.thumbnail-item {
flex-shrink: 0;
}
.main-image-container {
width: 100%;
}
}
@media(max-width: 768px) {
.product-gallery-modern {
flex-direction: column;
}
.thumbnails-container {
width: 100%;
flex-direction: row;
align-items: center;
}
.product-thumbnails {
flex-direction: row;
width: 100%;
max-height: 100px;
overflow-x: auto;
overflow-y: hidden;
}
.thumbnail-item {
flex-shrink: 0;
}
.thumbnail-nav-btn {
margin: 0 5px;
}
.thumbnail-nav-up {
order: 1;
}
.product-thumbnails {
order: 2;
}
.thumbnail-nav-down {
order: 3;
}
.product-main-image-wrapper .image-overlay-icons,
.image-overlay-icons {
opacity: 1 !important;
top: 10px !important;
right: 10px !important;
}
.product-main-image-wrapper .icon-btn,
.image-overlay-icons .icon-btn {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
min-height: 40px !important;
max-width: 40px !important;
max-height: 40px !important;
}
.product-main-image-wrapper .icon-btn i,
.image-overlay-icons .icon-btn i {
font-size: 18px !important;
}
.product-main-image-wrapper .image-indicators,
#image-indicators {
bottom: 15px !important;
padding: 6px 12px !important;
gap: 8px !important;
}
.product-main-image-wrapper .indicator,
#image-indicators .indicator {
width: 10px !important;
height: 10px !important;
min-width: 10px !important;
min-height: 10px !important;
max-width: 10px !important;
max-height: 10px !important;
}
.product-main-image-wrapper .image-counter,
.image-counter {
bottom: 15px !important;
right: 15px !important;
padding: 6px 12px !important;
font-size: 12px !important;
}
.main-image-nav .main-image-nav-btn,
.main-image-nav-btn {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
min-height: 40px !important;
max-width: 40px !important;
max-height: 40px !important;
}
.main-image-nav .main-image-nav-btn i,
.main-image-nav-btn i {
font-size: 18px !important;
}
}
@media(max-width: 576px) {
.product-main-image-wrapper .image-indicators,
#image-indicators {
bottom: 10px !important;
padding: 5px 10px !important;
gap: 6px !important;
}
.product-main-image-wrapper .indicator,
#image-indicators .indicator {
width: 8px !important;
height: 8px !important;
min-width: 8px !important;
min-height: 8px !important;
max-width: 8px !important;
max-height: 8px !important;
}
.product-main-image-wrapper .image-counter,
.image-counter {
bottom: 10px !important;
right: 10px !important;
padding: 5px 10px !important;
font-size: 11px !important;
}
.product-main-image-wrapper .icon-btn,
.image-overlay-icons .icon-btn {
width: 36px !important;
height: 36px !important;
min-width: 36px !important;
min-height: 36px !important;
max-width: 36px !important;
max-height: 36px !important;
}
.product-main-image-wrapper .icon-btn i,
.image-overlay-icons .icon-btn i {
font-size: 16px !important;
}
.main-image-nav .main-image-nav-btn,
.main-image-nav-btn {
width: 36px !important;
height: 36px !important;
min-width: 36px !important;
min-height: 36px !important;
max-width: 36px !important;
max-height: 36px !important;
}
.main-image-nav .main-image-nav-btn i,
.main-image-nav-btn i {
font-size: 16px !important;
}
}
/* Styles finaux pour forcer l'affichage des boutons de quantité */
.product_count .quantity-controls-wrapper button.quantity-btn,
.product_count button.quantity-btn-decrease,
.product_count button.quantity-btn-increase {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
position: relative !important;
top: auto !important;
bottom: auto !important;
right: auto !important;
left: auto !important;
width: 40px !important;
height: 40px !important;
background: #f8f9fa !important;
color: #666 !important;
border: none !important;
box-shadow: none !important;
margin: 0 !important;
padding: 0 !important;
}
.product_count .quantity-controls-wrapper {
display: flex !important;
visibility: visible !important;
opacity: 1 !important;
}
</style>
{% endblock %}
{% block body %}
<!-- Start Banner Area -->
<section class="banner-area organic-breadcrumb">
<div class="container">
<div class="breadcrumb-banner d-flex flex-wrap align-items-center justify-content-end">
<div class="col-first">
<h1>Product Details Page</h1>
<nav class="d-flex align-items-center">
<a href="index.html">Home<span class="lnr lnr-arrow-right"></span>
</a>
<a href="#">Shop<span class="lnr lnr-arrow-right"></span>
</a>
<a href="single-product.html">product-details</a>
</nav>
</div>
</div>
</div>
</section>
<!-- End Banner Area -->
<!--================Single Product Area =================-->
<div class="product_image_area">
<div class="container">
<div class="row s_product_inner">
<div class="col-lg-6">
<div
class="product-gallery-modern">
<!-- Colonne de miniatures à gauche avec navigation -->
<div class="thumbnails-container">
<button class="thumbnail-nav-btn thumbnail-nav-up" id="thumbnail-nav-up" title="Image précédente">
<i class="lnr lnr-chevron-up"></i>
</button>
<div class="product-thumbnails" id="product-thumbnails">
<div class="thumbnails-wrapper" id="thumbnails-wrapper">
{% set allProductImages = product.images|default([]) %}
{% if product.variants is defined %}
{% for variant in product.variants %}
{% if variant.isActive and variant.images is defined %}
{% for variantImg in variant.images %}
{% set allProductImages = allProductImages|merge([variantImg]) %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% if allProductImages|length > 0 %}
{% for img in allProductImages %}
<div class="thumbnail-item {% if loop.first %}active permanently-active{% endif %}" data-image="{{ asset(img) }}" data-index="{{ loop.index0 }}">
<img src="{{ asset(img) }}" alt="{{ product.name }} - Image {{ loop.index }}">
</div>
{% endfor %}
{% else %}
<div class="thumbnail-item active permanently-active" data-image="{{ asset('ui/img/category/s-p1.jpg') }}" data-index="0">
<img src="{{ asset('ui/img/category/s-p1.jpg') }}" alt="{{ product.name }}">
</div>
{% endif %}
</div>
</div>
<button class="thumbnail-nav-btn thumbnail-nav-down" id="thumbnail-nav-down" title="Image suivante">
<i class="lnr lnr-chevron-down"></i>
</button>
</div>
<!-- Grande image principale -->
<div class="product-main-image-wrapper">
<div class="product-main-image">
{% set allProductImages = product.images|default([]) %}
{% if product.variants is defined %}
{% for variant in product.variants %}
{% if variant.isActive and variant.images is defined %}
{% for variantImg in variant.images %}
{% set allProductImages = allProductImages|merge([variantImg]) %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% if allProductImages|length > 0 %}
<img id="main-product-image" src="{{ asset(allProductImages[0]) }}" alt="{{ product.name }}">
{% else %}
<img id="main-product-image" src="{{ asset('ui/img/category/s-p1.jpg') }}" alt="{{ product.name }}">
{% endif %}
<!-- Overlay avec icônes -->
<div class="image-overlay-icons">
<button class="icon-btn zoom-btn" onclick="openImageZoom()" title="Agrandir l'image">
<i class="lnr lnr-magnifier"></i>
</button>
<button class="icon-btn favorite-btn" {% if app.user %} onclick="toggleWishlist({{ product.id }}, this); return false;" {% else %} onclick="showCustomAlert('Vous devez être connecté pour ajouter aux favoris', 'warning'); return false;" {% endif %} title="Ajouter aux favoris" data-product-id="{{ product.id }}">
<i class="lnr lnr-heart"></i>
</button>
</div>
<!-- Navigation sur l'image principale -->
<div class="main-image-nav">
<button type="button" class="main-image-nav-btn" id="main-image-prev" title="Image précédente">
<i class="lnr lnr-chevron-left"></i>
</button>
<button type="button" class="main-image-nav-btn" id="main-image-next" title="Image suivante">
<i class="lnr lnr-chevron-right"></i>
</button>
</div>
</div>
<!-- Indicateur d'image active -->
<div class="image-counter">
<span id="current-image-index">1</span>
/
{% set allProductImages = product.images|default([]) %}
{% if product.variants is defined %}
{% for variant in product.variants %}
{% if variant.isActive and variant.images is defined %}
{% for variantImg in variant.images %}
{% set allProductImages = allProductImages|merge([variantImg]) %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
<span id="total-images">{{ (allProductImages|length) ?: 1 }}</span>
</div>
<!-- Indicateurs d'images (points) -->
<div class="image-indicators" id="image-indicators">
{% set allProductImages = product.images|default([]) %}
{% if product.variants is defined %}
{% for variant in product.variants %}
{% if variant.isActive and variant.images is defined %}
{% for variantImg in variant.images %}
{% set allProductImages = allProductImages|merge([variantImg]) %}
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}
{% for i in 0..((allProductImages|length) ?: 1) - 1 %}
<button class="indicator {{ i == 0 ? 'active' : '' }}" data-index="{{ i }}" title="Image {{ i + 1 }}"></button>
{% endfor %}
</div>
</div>
</div>
</div>
<div class="col-lg-5 offset-lg-1">
<div class="s_product_text">
<h3>{{ product.name }}</h3>
<h2>
<span id="main-unit-price">{{ product.price|number_format(2, '.', ' ') }}</span>
HTG
</h2>
<ul class="list">
<li>
<a class="active" href="#">
<span>Category</span>
:
{{ product.category.name }}</a>
</li>
<li>
<a href="#">
<span>Disponibilité</span>
:
{{ product.stockStatus }}</a>
</li>
</ul>
<div class="product-description-preview">{{ product.description|striptags|length > 150 ? product.description|striptags|slice(0, 150) ~ '...' : product.description|striptags }}</div>
<!-- Sélection de variantes (Couleurs, Tailles, etc.) -->
{% if product.variants is defined and product.variants|length > 0 %}
<div class="product-variants mb-4" id="product-variants" data-product-id="{{ product.id }}">
{% set variantAttributes = {} %}
{% for variant in product.variants %}
{% if variant.isActive %}
{% for attrValue in variant.attributeValues %}
{% set attrName = attrValue.attribute.name|lower %}
{% if variantAttributes[attrName] is not defined %}
{% set variantAttributes = variantAttributes|merge({(attrName): {'attribute': attrValue.attribute, 'values': []}}) %}
{% endif %}
{% if attrValue not in variantAttributes[attrName].values %}
{% set variantAttributes = variantAttributes|merge({(attrName): variantAttributes[attrName]|merge({'values': variantAttributes[attrName].values|merge([attrValue])})}) %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
{% for attrName, attrData in variantAttributes %}
<div class="variant-selector mb-3" data-attribute="{{ attrData.attribute.slug }}">
<label class="variant-label mb-2">
<strong>{{ attrData.attribute.name }}:</strong>
<span class="selected-variant-value" id="selected-{{ attrData.attribute.slug }}"></span>
</label>
<div class="variant-options d-flex flex-wrap gap-2">
{% for attrValue in attrData.values %}
{% if attrValue.isActive %}
{% if attrData.attribute.type == 'color' %}
<button type="button"
class="variant-option variant-color-option {% if loop.first %}selected{% endif %}"
data-value-id="{{ attrValue.id }}"
data-value="{{ attrValue.value }}"
data-attribute="{{ attrData.attribute.slug }}"
title="{{ attrValue.value }}"
style="{% if attrValue.colorCode %}background-color: {{ attrValue.colorCode }};{% endif %} width: 40px; height: 40px; border-radius: 50%; border: 2px solid {% if loop.first %}#007bff{% else %}#ddd{% endif %}; cursor: pointer; position: relative;">
{% if loop.first %}
<span class="check-icon" style="position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: {% if attrValue.colorCode and attrValue.colorCode|is_dark_color %}white{% else %}black{% endif %}; font-size: 18px;">✓</span>
{% endif %}
</button>
{% else %}
<button type="button"
class="variant-option variant-text-option btn btn-outline-secondary {% if loop.first %}active{% endif %}"
data-value-id="{{ attrValue.id }}"
data-value="{{ attrValue.value }}"
data-attribute="{{ attrData.attribute.slug }}">
{{ attrValue.value }}
</button>
{% endif %}
{% endif %}
{% endfor %}
</div>
</div>
{% endfor %}
<!-- Affichage du stock et du prix de la variante sélectionnée -->
<div class="variant-info mt-3 p-3 bg-light rounded" id="variant-info" style="display: none;">
<div class="d-flex justify-content-between align-items-center mb-2">
<span><strong>Prix:</strong></span>
<span class="variant-price text-primary fw-bold" id="variant-price"></span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span><strong>Stock:</strong></span>
<span class="variant-stock" id="variant-stock"></span>
</div>
<input type="hidden" id="selected-variant-id" value="">
<input type="hidden" id="selected-variant-sku" value="">
</div>
</div>
{% endif %}
<!-- Informations sur la boutique -->
<div class="shop-info mb-3">
<small class="text-muted">
<i class="lnr lnr-store"></i>
Vendu par :
{% if product.shop is defined and product.shop %}
<a href="{{ path('ui_shop_show', {'slug': product.shop.slug}) }}" class="shop-link">
{{ product.shop.name }}
</a>
{% else %}
<span class="text-muted">Boutique inconnue</span>
{% endif %}
</small>
</div>
<div class="product_count">
<label for="qty">Quantité :</label>
<div class="quantity-controls-wrapper">
<button onclick="var result = document.getElementById('sst'); var sst = result.value; if( !isNaN( sst ) && sst > 1 ) result.value--;return false;" class="quantity-btn quantity-btn-decrease" type="button" title="Diminuer">
<span class="quantity-icon">−</span>
</button>
<input type="text" name="qty" id="sst" maxlength="12" value="1" title="Quantité:" class="input-text qty quantity-input" max="{{ product.stock ?? 99 }}">
<button onclick="var result = document.getElementById('sst'); var sst = result.value; if( !isNaN( sst ) && sst < {{ product.stock ?? 99 }}) result.value++;return false;" class="quantity-btn quantity-btn-increase" type="button" title="Augmenter">
<span class="quantity-icon">+</span>
</button>
</div>
</div>
<input
type="hidden" id="unit-price-input" value="{{ product.price|number_format(2, '.', '') }}"/>
{# Prix de gros (bulk pricing) - Affiché uniquement si activé par le vendeur #}
{% if product.hasTierPricing and product.tierPrices is defined and product.tierPrices|length > 0 %}
{% set tierPrices = [] %}
{% for tier in product.tierPrices %}
{% set tierPrices = tierPrices|merge([{ 'min': tier.min, 'price': tier.price }]) %}
{% endfor %}
<div id="bulk-pricing" class="mt-4 p-3 bg-light rounded" data-tiers='{{ tierPrices|json_encode|e('html_attr') }}'>
<div class="d-flex align-items-center mb-3">
<i class="lnr lnr-tag text-primary me-2" style="font-size: 1.2rem;"></i>
<strong class="me-2">Prix de gros disponible</strong>
<span id="unit-price-value" class="badge bg-primary ms-2 text-white p-2">{{ product.price|number_format(2, '.', ' ') }} HTG</span>
</div>
<div class="table-responsive">
<table class="table table-sm mb-3" style="border:1px solid #dee2e6; background: white;">
<thead class="table-primary">
<tr>
<th style="font-weight: 600;">Quantité minimale</th>
<th style="font-weight: 600;">Prix unitaire (HTG)</th>
<th style="font-weight: 600;">Économie</th>
</tr>
</thead>
<tbody id="bulk-tier-rows">
{% for tier in tierPrices|sort((a,b)=> a.min <=> b.min) %}
{% set savings = ((product.price - tier.price) / product.price * 100)|round(1) %}
<tr data-min="{{ tier.min }}" class="{% if loop.first %}table-active{% endif %}">
<td><strong>≥ {{ tier.min }}</strong></td>
<td><strong class="text-primary">{{ tier.price|number_format(2, '.', ' ') }}</strong></td>
<td>
{% if savings > 0 %}
<span class="badge bg-success text-white p-2">-{{ savings }}%</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="d-flex flex-wrap gap-3 align-items-baseline mb-2 p-2 bg-white rounded">
<div>
<strong>Total:</strong>
<span id="total-price-value" class="text-primary fs-5">{{ product.price|number_format(2, '.', ' ') }}</span>
HTG
</div>
<div class="text-success">
<strong>Économies:</strong>
<span id="savings-amount">0.00</span>
HTG
(<span id="savings-percent">0</span>%)
</div>
</div>
<small class="text-muted d-block mt-2">
<i class="lnr lnr-info-circle me-1"></i>
Le prix et le total s'adaptent automatiquement selon la quantité sélectionnée.
</small>
</div>
{% endif %}
<!-- Stock disponible -->
{% if product.stock is defined %}
<small class="text-muted">Stock disponible :
{{ product.stock }}
unités</small>
{% endif %}
<!-- Statistiques du produit -->
{% if productStats is defined %}
<div class="product-stats mt-3">
<div class="row text-center">
<div class="col-4">
<div class="stat-item">
<span class="stat-number">{{ productStats.totalViews }}</span>
<small class="stat-label">Vues</small>
</div>
</div>
<div class="col-4">
<div class="stat-item">
<span class="stat-number">{{ productStats.salesCount }}</span>
<small class="stat-label">Ventes</small>
</div>
</div>
<div class="col-4">
<div class="stat-item">
<span class="stat-number">{{ productStats.conversionRate }}%</span>
<small class="stat-label">Conversion</small>
</div>
</div>
</div>
</div>
{% endif %}
<div class="card_area d-flex align-items-center">
<a class="primary-btn btn-add-to-cart" href="javascript:void(0);" id="add-to-cart-btn" data-product-id="{{ product.id }}" data-qty="1">
<i class="lnr lnr-cart"></i>
Ajouter au panier
</a>
<a class="icon_btn wishlist-btn" href="#" title="Ajouter aux favoris" data-product-id="{{ product.id }}" {% if app.user %} onclick="toggleWishlist({{ product.id }}, this); return false;" {% else %} onclick="showCustomAlert('Vous devez être connecté pour ajouter aux favoris', 'warning'); return false;" {% endif %}>
<i class="lnr lnr-heart"></i>
</a>
<a class="icon_btn comparison-btn" href="#" title="Comparer" data-product-id="{{ product.id }}" {% if app.user %} onclick="toggleComparison({{ product.id }}, this); return false;" {% else %} onclick="showCustomAlert('Vous devez être connecté pour comparer des produits', 'warning'); return false;" {% endif %}>
<i class="lnr lnr-sync"></i>
</a>
</div>
<!-- Message de confirmation -->
<div id="cart-message" class="alert alert-success mt-3" style="display: none;">
<i class="lnr lnr-checkmark-circle"></i>
Produit ajouté au panier avec succès !
</div>
<!-- ... existing code ... -->
</div>
</div>
</div>
</div>
</div>
<!--================End Single Product Area =================-->
<!--================Product Description Area =================-->
<section class="product_description_area">
<div class="container">
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-bs-toggle="tab" href="#home" role="tab" aria-controls="home" aria-selected="true">Description</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-tab" data-bs-toggle="tab" href="#profile" role="tab" aria-controls="profile" aria-selected="false">Spécifications</a>
</li>
<li class="nav-item">
<a class="nav-link" id="contact-tab" data-bs-toggle="tab" href="#contact" role="tab" aria-controls="contact" aria-selected="false">Commentaires</a>
</li>
<li class="nav-item">
<a class="nav-link" id="review-tab" data-bs-toggle="tab" href="#review" role="tab" aria-controls="review" aria-selected="false">Avis ({{ product.reviewCount ?? 0 }})</a>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
<div class="description">
{% if product.description %}
<div class="product-description-content">
{{ product.description|raw }}
</div>
{% else %}
<p class="text-muted">Aucune description disponible pour ce produit.</p>
{% endif %}
</div>
</div>
<div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">
<div class="specification-table">
{% if product.weight or product.length or product.width or product.height or product.attributes %}
<div class="table-responsive">
<table class="table">
<tbody>
{% if product.sku %}
<tr>
<td>
<h5>Référence (SKU)</h5>
</td>
<td>
<h5>{{ product.sku }}</h5>
</td>
</tr>
{% endif %}
{% if product.barcode %}
<tr>
<td>
<h5>Code-barres</h5>
</td>
<td>
<h5>{{ product.barcode }}</h5>
</td>
</tr>
{% endif %}
{% if product.brand %}
<tr>
<td>
<h5>Marque</h5>
</td>
<td>
<h5>{{ product.brand.name }}</h5>
</td>
</tr>
{% endif %}
{% if product.category %}
<tr>
<td>
<h5>Catégorie</h5>
</td>
<td>
<h5>{{ product.category.name }}</h5>
</td>
</tr>
{% endif %}
{% if product.weight %}
<tr>
<td>
<h5>Poids</h5>
</td>
<td>
<h5>{{ product.weight }}
kg</h5>
</td>
</tr>
{% endif %}
{% if product.length or product.width or product.height %}
<tr>
<td>
<h5>Dimensions</h5>
</td>
<td>
<h5>
{% if product.length %}
{{ product.length }}cm
{% endif %}
{% if product.width %}
×
{{ product.width }}cm
{% endif %}
{% if product.height %}
×
{{ product.height }}cm
{% endif %}
</h5>
</td>
</tr>
{% endif %}
{% if product.stock is not null %}
<tr>
<td>
<h5>Stock disponible</h5>
</td>
<td>
<h5>{{ product.stock }}
unité(s)</h5>
</td>
</tr>
{% endif %}
{% if product.details is defined and product.details|length > 0 %}
{% for detail in product.details %}
{% if detail.isActive %}
<tr>
<td>
<h5>{{ detail.label }}</h5>
</td>
<td>
<h5>{{ detail.value|raw }}</h5>
</td>
</tr>
{% endif %}
{% endfor %}
{% endif %}
{% if product.stockStatus %}
<tr>
<td>
<h5>Statut</h5>
</td>
<td>
<h5>
{% if product.stockStatus == 'in_stock' %}
<span class="badge bg-success">En stock</span>
{% elseif product.stockStatus == 'out_of_stock' %}
<span class="badge bg-danger">Rupture de stock</span>
{% elseif product.stockStatus == 'backorder' %}
<span class="badge bg-warning">Sur commande</span>
{% else %}
{{ product.stockStatus }}
{% endif %}
</h5>
</td>
</tr>
{% endif %}
{% if product.isDigital %}
<tr>
<td>
<h5>Type</h5>
</td>
<td>
<h5>
<span class="badge bg-info">Produit digital</span>
</h5>
</td>
</tr>
{% endif %}
{% if product.attributes is not empty %}
{% for key, value in product.attributes %}
<tr>
<td>
<h5>{{ key|replace({'_': ' '})|title }}</h5>
</td>
<td>
<h5>{{ value }}</h5>
</td>
</tr>
{% endfor %}
{% endif %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted">Aucune spécification disponible pour ce produit.</p>
{% endif %}
</div>
</div>
<div class="tab-pane fade" id="contact" role="tabpanel" aria-labelledby="contact-tab">
<div class="comment-wrapper">
<div class="alert alert-info">
<i class="lnr lnr-info-circle"></i>
La fonctionnalité de commentaires sera bientôt disponible. Vous pourrez laisser des commentaires et poser des questions sur ce produit.
</div>
{% if app.user %}
<div class="review_box mt-4">
<h4>Poser une question</h4>
<form class="row contact_form" method="post" novalidate="novalidate">
<div class="col-md-12">
<div class="form-group">
<textarea class="form-control" name="comment" id="comment" rows="3" placeholder="Votre question ou commentaire..." required></textarea>
</div>
</div>
<div class="col-md-12 text-right">
<button type="submit" class="btn primary-btn">Envoyer</button>
</div>
</form>
</div>
{% else %}
<p class="text-center">
<a href="{{ path('ui_app_login') }}" class="primary-btn">Connectez-vous</a>
pour poser une question ou laisser un commentaire.
</p>
{% endif %}
</div>
</div>
<div class="tab-pane fade" id="review" role="tabpanel" aria-labelledby="review-tab">
<div class="review-wrapper">
<div class="row">
<div class="col-lg-6">
<div class="row total_rate">
<div class="col-6">
<div class="box_total">
<h5>Note globale</h5>
<h4>{{ product.averageRating ? product.averageRating|number_format(1, '.', ',') : '0.0' }}</h4>
<h6>({{ product.reviewCount ?? 0 }}
Avis)</h6>
</div>
</div>
<div class="col-6">
<div class="rating_list">
<h3>Basé sur
{{ product.reviewCount ?? 0 }}
avis</h3>
<div class="rating-summary">
{% if product.averageRating %}
<div class="rating-display mb-3">
{% for i in 1..5 %}
<i class="fa fa-star{% if i <= product.averageRating %}{% else %}-o{% endif %}" style="color: #ffa200; font-size: 1.2rem;"></i>
{% endfor %}
<span class="ms-2" style="font-size: 1.1rem; font-weight: bold;">{{ product.averageRating|number_format(1, '.', ',') }}
/ 5.0</span>
</div>
{% else %}
<p class="text-muted">Aucune note disponible</p>
{% endif %}
</div>
</div>
</div>
</div>
<div class="review_list">
{% if product.reviewCount and product.reviewCount > 0 %}
<div class="alert alert-info">
<i class="lnr lnr-info-circle"></i>
Le système d'affichage détaillé des avis sera disponible prochainement.
Note actuelle :
{{ product.averageRating|number_format(1, '.', ',') }}
/ 5.0 basée sur
{{ product.reviewCount }}
avis.
</div>
{% else %}
<div class="alert alert-info">
<i class="lnr lnr-info-circle"></i>
Aucun avis pour ce produit pour le moment. Soyez le premier à laisser un avis après votre achat !
</div>
{% endif %}
</div>
</div>
<div class="col-lg-6">
<div class="review_box">
<h4>Laisser un avis</h4>
{% if app.user %}
<p class="text-muted">Vous pouvez laisser un avis après avoir acheté ce produit.</p>
<form class="row contact_form" method="post" novalidate="novalidate">
<div class="col-md-12">
<div class="form-group">
<label>Votre note</label>
<div class="rating-input mb-3" style="display: flex; gap: 5px; flex-direction: row-reverse; justify-content: flex-end;">
{% for i in 5..1 %}
<input type="radio" name="rating" id="rating{{ i }}" value="{{ i }}" required style="display: none;">
<label for="rating{{ i }}" class="star-label" style="cursor: pointer;">
<i class="fa fa-star-o" style="color: #ccc; font-size: 1.5rem;"></i>
</label>
{% endfor %}
</div>
</div>
</div>
<div class="col-md-12">
<div class="form-group">
<textarea class="form-control" name="review" id="review" rows="5" placeholder="Votre avis..." required onfocus="this.placeholder = ''" onblur="this.placeholder = 'Votre avis...'"></textarea>
</div>
</div>
<div class="col-md-12 text-right">
<button type="submit" class="btn primary-btn">Publier l'avis</button>
</div>
</form>
{% else %}
<p class="text-center">
<a href="{{ path('ui_app_login') }}" class="primary-btn">Connectez-vous</a>
pour laisser un avis sur ce produit.
</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!--================End Product Description Area =================-->
<script>
// Données des variantes du produit
const productVariants = {{ product.variants|length > 0 ? product.variants|map(v => {
'id': v.id,
'sku': v.sku,
'price': v.price,
'compareAtPrice': v.compareAtPrice,
'stock': v.stock,
'stockStatus': v.stockStatus,
'isActive': v.isActive,
'images': v.images,
'attributeValues': v.attributeValues|map(av => {
'attributeId': av.attribute.id,
'attributeSlug': av.attribute.slug,
'valueId': av.id,
'value': av.value
})|to_array
})|json_encode|raw : '[]' }};
// Gestion de la sélection de variantes
let selectedAttributes = {};
let currentVariant = null;
// Initialiser les onglets Bootstrap
document.addEventListener('DOMContentLoaded', function () { // Activer les onglets Bootstrap 5
const triggerTabList = document.querySelectorAll('#myTab button[data-bs-toggle="tab"]');
triggerTabList.forEach(triggerEl => {
const tabTrigger = new bootstrap.Tab(triggerEl);
triggerEl.addEventListener('click', event => {
event.preventDefault();
tabTrigger.show();
});
});
// Gestion des étoiles pour les avis
const ratingInputs = document.querySelectorAll('.rating-input input[type="radio"]');
const starLabels = document.querySelectorAll('.rating-input .star-label');
ratingInputs.forEach((input, index) => {
input.addEventListener('change', function () {
const rating = parseInt(this.value);
starLabels.forEach((label) => { // Trouver l'input associé à ce label
const labelInput = label.previousElementSibling;
if (labelInput && labelInput.type === 'radio') {
const starValue = parseInt(labelInput.value);
const star = label.querySelector('i');
if (starValue <= rating) {
star.style.color = '#ffa200';
star.classList.remove('fa-star-o');
star.classList.add('fa-star');
} else {
star.style.color = '#ccc';
star.classList.remove('fa-star');
star.classList.add('fa-star-o');
}
}
});
});
});
// Pré-remplir les étoiles au survol
starLabels.forEach((label) => {
label.addEventListener('mouseenter', function () { // Trouver l'input associé à ce label
const input = label.previousElementSibling;
if (input && input.type === 'radio') {
const rating = parseInt(input.value);
starLabels.forEach((l) => {
const lInput = l.previousElementSibling;
if (lInput && lInput.type === 'radio') {
const starValue = parseInt(lInput.value);
const star = l.querySelector('i');
if (starValue <= rating) {
star.style.color = '#ffa200';
star.classList.remove('fa-star-o');
star.classList.add('fa-star');
} else {
star.style.color = '#ccc';
star.classList.remove('fa-star');
star.classList.add('fa-star-o');
}
}
});
}
});
});
const ratingContainer = document.querySelector('.rating-input');
if (ratingContainer) {
ratingContainer.addEventListener('mouseleave', function () {
const checkedInput = document.querySelector('.rating-input input[type="radio"]:checked');
if (checkedInput) {
checkedInput.dispatchEvent(new Event('change'));
} else {
starLabels.forEach(label => {
const star = label.querySelector('i');
star.style.color = '#ccc';
});
}
});
}
});
// Gestion de la sélection de variantes
if (typeof productVariants !== 'undefined' && productVariants.length > 0) {
// Initialiser avec la première variante active
const firstVariant = productVariants.find(v => v.isActive);
if (firstVariant) {
firstVariant.attributeValues.forEach(av => {
selectedAttributes[av.attributeSlug] = av.valueId;
});
updateVariantDisplay(firstVariant);
} else {
// Si aucune variante active, afficher le prix de base
const priceEl = document.getElementById('main-unit-price');
if (priceEl) {
priceEl.textContent = '{{ product.price|number_format(2, '.', ' ') }}';
}
}
// Écouter les clics sur les options de variantes
document.querySelectorAll('.variant-option').forEach(option => {
option.addEventListener('click', function() {
const attributeSlug = this.getAttribute('data-attribute');
const valueId = parseInt(this.getAttribute('data-value-id'));
const value = this.getAttribute('data-value');
// Mettre à jour la sélection visuelle
document.querySelectorAll(`[data-attribute="${attributeSlug}"]`).forEach(opt => {
opt.classList.remove('selected', 'active');
if (opt.classList.contains('variant-color-option')) {
opt.style.borderColor = '#ddd';
const checkIcon = opt.querySelector('.check-icon');
if (checkIcon) checkIcon.remove();
}
});
this.classList.add('selected', 'active');
if (this.classList.contains('variant-color-option')) {
this.style.borderColor = '#007bff';
const checkIcon = document.createElement('span');
checkIcon.className = 'check-icon';
const bgColor = this.style.backgroundColor || '';
const isDark = bgColor && getContrastColor(bgColor) < 128;
checkIcon.style.cssText = 'position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: ' + (isDark ? 'white' : 'black') + '; font-size: 18px;';
checkIcon.textContent = '✓';
this.appendChild(checkIcon);
}
// Mettre à jour les attributs sélectionnés
selectedAttributes[attributeSlug] = valueId;
const selectedSpan = document.getElementById(`selected-${attributeSlug}`);
if (selectedSpan) {
selectedSpan.textContent = value;
}
// Trouver la variante correspondante
const matchingVariant = findMatchingVariant();
if (matchingVariant) {
updateVariantDisplay(matchingVariant);
} else {
// Aucune variante correspondante trouvée - afficher le produit de base
resetToBaseProduct();
}
});
});
}
function findMatchingVariant() {
if (!productVariants || productVariants.length === 0) return null;
return productVariants.find(variant => {
if (!variant.isActive) return false;
const variantAttributes = {};
variant.attributeValues.forEach(av => {
variantAttributes[av.attributeSlug] = av.valueId;
});
// Vérifier si tous les attributs sélectionnés correspondent
for (const [attrSlug, valueId] of Object.entries(selectedAttributes)) {
if (!variantAttributes[attrSlug] || variantAttributes[attrSlug] !== valueId) {
return false;
}
}
// Vérifier que le nombre d'attributs correspond
return Object.keys(variantAttributes).length === Object.keys(selectedAttributes).length;
});
}
function updateVariantDisplay(variant) {
currentVariant = variant;
// Mettre à jour le prix
const priceElement = document.getElementById('main-unit-price');
const variantPriceElement = document.getElementById('variant-price');
if (priceElement) {
priceElement.textContent = variant.price.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
if (variantPriceElement) {
variantPriceElement.textContent = variant.price.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' HTG';
}
// Mettre à jour le stock
const stockElement = document.getElementById('variant-stock');
if (stockElement) {
if (variant.stock > 0) {
stockElement.textContent = variant.stock + ' disponible(s)';
stockElement.className = 'variant-stock text-success';
} else {
stockElement.textContent = 'Rupture de stock';
stockElement.className = 'variant-stock text-danger';
}
}
// Mettre à jour les champs cachés
const variantIdInput = document.getElementById('selected-variant-id');
const variantSkuInput = document.getElementById('selected-variant-sku');
if (variantIdInput) variantIdInput.value = variant.id;
if (variantSkuInput) variantSkuInput.value = variant.sku;
// Mettre à jour les images si disponibles
if (variant.images && variant.images.length > 0) {
const mainImage = document.getElementById('main-product-image');
if (mainImage && variant.images[0]) {
mainImage.src = variant.images[0];
}
}
// Afficher les informations de la variante
const variantInfo = document.getElementById('variant-info');
if (variantInfo) variantInfo.style.display = 'block';
// Mettre à jour la quantité maximale
const qtyInput = document.getElementById('sst');
if (qtyInput) {
qtyInput.max = variant.stock;
}
}
function getContrastColor(hexColor) {
// Convertir hex en RGB et calculer la luminosité
const rgb = hexColor.match(/\d+/g);
if (rgb && rgb.length >= 3) {
const r = parseInt(rgb[0]);
const g = parseInt(rgb[1]);
const b = parseInt(rgb[2]);
return (r * 299 + g * 587 + b * 114) / 1000;
}
return 128;
}
function resetToBaseProduct() {
currentVariant = null;
// Réinitialiser les attributs sélectionnés
selectedAttributes = {};
// Masquer les informations de variante
const variantInfo = document.getElementById('variant-info');
if (variantInfo) variantInfo.style.display = 'none';
// Réinitialiser le prix au prix de base
const priceEl = document.getElementById('main-unit-price');
if (priceEl) priceEl.textContent = '{{ product.price|number_format(2, '.', ' ') }}';
// Réinitialiser les champs cachés
const variantIdInput = document.getElementById('selected-variant-id');
const variantSkuInput = document.getElementById('selected-variant-sku');
if (variantIdInput) variantIdInput.value = '';
if (variantSkuInput) variantSkuInput.value = '';
// Réinitialiser les sélections visuelles
document.querySelectorAll('.variant-option').forEach(opt => {
opt.classList.remove('selected', 'active');
if (opt.classList.contains('variant-color-option')) {
opt.style.borderColor = '#ddd';
const checkIcon = opt.querySelector('.check-icon');
if (checkIcon) checkIcon.remove();
}
});
// Réinitialiser les labels de sélection
document.querySelectorAll('.selected-variant-value').forEach(span => {
span.textContent = '';
});
// Réinitialiser les images au produit de base
const mainImage = document.getElementById('main-product-image');
if (mainImage && typeof productImages !== 'undefined' && productImages.length > 0) {
mainImage.src = productImages[0];
}
// Réinitialiser la quantité maximale
const qtyInput = document.getElementById('sst');
if (qtyInput) {
qtyInput.max = {{ product.stock }};
}
}
</script>
{% if youMightAlsoLike|length > 0 %}
<section class="related-product-area section_gap_bottom py-5" style="background: linear-gradient(to bottom, #f8f9fa 0%, #ffffff 100%);">
<div class="container">
<div class="row justify-content-center">
<div class="col-lg-8 text-center">
<div class="section-title">
<h2 class="fw-bold mb-3" style="font-size: 2.5rem; color: #2c3e50;">Vous pourriez aussi aimer</h2>
<p class="text-muted" style="font-size: 1.1rem;">Produits sélectionnés pour vous en fonction de vos préférences</p>
</div>
</div>
</div>
<div class="row g-4">
{% for relatedProduct in youMightAlsoLike %}
<div class="col-6 col-md-4 col-lg-3 mb-3">
<div class="modern-product-card">
<div class="product-image-wrapper">
<a href="{{ path('ui_product_show', { slug: relatedProduct.slug }) }}" class="product-image-link">
{% if relatedProduct.images is defined and relatedProduct.images|length > 0 %}
<img src="{{ asset(relatedProduct.images[0]) }}" alt="{{ relatedProduct.name }}" class="product-image">
{% else %}
<img src="{{ asset('ui/img/product/p1.jpg') }}" alt="{{ relatedProduct.name }}" class="product-image">
{% endif %}
<div class="product-overlay">
<span class="view-product-btn">
<i class="ti ti-eye"></i> Voir
</span>
</div>
</a>
<div class="product-badge">
{% if relatedProduct.compareAtPrice and relatedProduct.compareAtPrice > relatedProduct.price %}
<span class="badge-discount">
-{{ ((relatedProduct.compareAtPrice - relatedProduct.price) / relatedProduct.compareAtPrice * 100)|round }}%
</span>
{% endif %}
</div>
</div>
<div class="product-info">
<h6 class="product-title">
<a href="{{ path('ui_product_show', { slug: relatedProduct.slug }) }}">{{ relatedProduct.name }}</a>
</h6>
{% if relatedProduct.shop %}
<div class="product-shop">
<i class="ti ti-store"></i>
<a href="{{ path('ui_shop_show', {'slug': relatedProduct.shop.slug}) }}" class="shop-link">{{ relatedProduct.shop.name }}</a>
</div>
{% endif %}
<div class="product-price-wrapper">
<span class="product-price">{{ relatedProduct.price|number_format(0, '.', ' ') }} HTG</span>
{% if relatedProduct.compareAtPrice and relatedProduct.compareAtPrice > relatedProduct.price %}
<span class="product-compare-price">{{ relatedProduct.compareAtPrice|number_format(0, '.', ' ') }} HTG</span>
{% endif %}
</div>
<div class="product-actions">
<a href="javascript:void(0)" class="btn-add-to-cart" data-product-id="{{ relatedProduct.id }}" data-qty="1" title="Ajouter au panier">
<i class="ti ti-shopping-cart"></i>
<span>Ajouter</span>
</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</section>
<style>
.modern-product-card {
background: #ffffff;
border-radius: 16px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
.modern-product-card:hover {
transform: translateY(-8px);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
}
.product-image-wrapper {
position: relative;
width: 100%;
height: 250px;
overflow: hidden;
background: #f8f9fa;
}
.product-image {
width: 100%;
height: 100%;
object-fit: cover !important;
object-position: center !important;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1);
}
.modern-product-card:hover .product-image {
transform: scale(1.1);
}
.product-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.modern-product-card:hover .product-overlay {
opacity: 1;
}
.view-product-btn {
color: white;
padding: 12px 24px;
background: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
border-radius: 8px;
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.3s ease;
}
.view-product-btn:hover {
background: rgba(255, 255, 255, 0.3);
transform: scale(1.05);
}
.product-badge {
position: absolute;
top: 12px;
right: 12px;
z-index: 2;
}
.badge-discount {
background: linear-gradient(135deg, #ff6b6b 0%, #ee5a6f 100%);
color: white;
padding: 6px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 700;
box-shadow: 0 2px 8px rgba(238, 90, 111, 0.4);
}
.product-info {
padding: 20px;
flex-grow: 1;
display: flex;
flex-direction: column;
}
.product-title {
margin: 0 0 8px 0;
font-size: 1rem;
font-weight: 600;
line-height: 1.4;
height: 2.8em;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.product-title a {
color: #2c3e50;
text-decoration: none;
transition: color 0.3s ease;
}
.product-title a:hover {
color: #ffa200;
}
.product-shop {
font-size: 0.85rem;
color: #6c757d;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.product-shop i {
font-size: 0.9rem;
}
.shop-link {
color: #6c757d;
text-decoration: none;
transition: color 0.3s ease;
}
.shop-link:hover {
color: #ffa200;
}
.product-price-wrapper {
margin-bottom: 16px;
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.product-price {
font-size: 1.25rem;
font-weight: 700;
color: #ffa200;
}
.product-compare-price {
font-size: 0.9rem;
color: #adb5bd;
text-decoration: line-through;
}
.product-actions {
margin-top: auto;
}
/* Styles pour les boutons de la section "Vous pourriez aussi aimer" uniquement */
.modern-product-card .btn-add-to-cart {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #ffa200 0%, #e8910a 100%);
color: white;
border: none;
border-radius: 10px;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
text-decoration: none;
transition: all 0.3s ease;
cursor: pointer;
}
.modern-product-card .btn-add-to-cart:hover {
background: linear-gradient(135deg, #e8910a 0%, #d6820a 100%);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 162, 0, 0.4);
color: white;
}
/* Le bouton principal garde son design original */
.card_area .btn-add-to-cart {
width: auto;
display: inline-flex;
}
.modern-product-card .btn-add-to-cart:active {
transform: translateY(0);
}
.modern-product-card .btn-add-to-cart i {
font-size: 1.1rem;
}
/* Responsive adjustments */
@media (max-width: 768px) {
.product-image-wrapper {
height: 200px;
}
.product-info {
padding: 16px;
}
.product-title {
font-size: 0.95rem;
}
.product-price {
font-size: 1.1rem;
}
.section-title h2 {
font-size: 2rem !important;
}
}
@media (max-width: 576px) {
.product-image-wrapper {
height: 190px;
}
.product-info {
padding: 14px;
}
.btn-add-to-cart {
padding: 10px;
font-size: 0.9rem;
}
}
/* Ensure fixed height for cards */
.col-6.col-md-4.col-lg-3 {
display: flex;
}
.modern-product-card {
width: 100%;
}
/* Loading state for add to cart button */
.btn-add-to-cart.loading {
opacity: 0.7;
pointer-events: none;
}
.btn-add-to-cart.loading i {
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Success state - vert pour tous les boutons */
.btn-add-to-cart.success {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
}
/* Le bouton principal garde son style original même en success */
.card_area .btn-add-to-cart.success {
background: linear-gradient(135deg, #28a745 0%, #20c997 100%) !important;
}
{% endif %}
<script src="/ui/js/vendor/jquery-2.2.4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.11.0/umd/popper.min.js" integrity="sha384-b/U6ypiBEHpOf/4+1nzFpr53nxSS+GLCkfwBdFNTxtclqqenISfwAzpKaMNFNmj4" crossorigin="anonymous"></script>
<script src="/ui/js/vendor/bootstrap.min.js"></script>
<script src="/ui/js/jquery.ajaxchimp.min.js"></script>
<script src="/ui/js/jquery.nice-select.min.js"></script>
<script src="/ui/js/jquery.sticky.js"></script>
<script src="/ui/js/nouislider.min.js"></script>
<script src="/ui/js/jquery.magnific-popup.min.js"></script>
<script src="/ui/js/owl.carousel.min.js"></script>
<!--gmaps Js-->
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCjCGmQ0Uq4exrzdcL6rvxywDDOvfAu6eE"></script>
<script src="/ui/js/gmaps.min.js"></script>
<script src="/ui/js/main.js"></script>
<!-- Product Image Navigation - Chargé en dernier pour éviter les conflits -->
<script src="/ui/js/product-image-navigation.js"></script>
<script>
// Variables globales pour la navigation des images (utilisées par le script externe)
window.productImages = [];
window.currentImageIndex = 0;
let mainImageElement, currentIndexElement, indicatorsElements;
// Test pour vérifier que les fonctions sont définies
window.testImageNavigation = function() {
console.log('Testing image navigation functions...');
if (window.productImageNav) {
console.log('productImageNav object:', window.productImageNav);
console.log('nextImage function:', typeof window.productImageNav.nextImage);
console.log('prevImage function:', typeof window.productImageNav.prevImage);
console.log('changeMainImageByIndex function:', typeof window.productImageNav.changeMainImageByIndex);
console.log('State:', window.productImageNav.getState());
} else {
console.warn('productImageNav not available');
}
};
// Fonction pour nettoyer le cache PWA et désenregistrer le Service Worker
window.clearPWACache = function() {
console.log('🧹 Nettoyage du cache PWA...');
// Désenregistrer tous les Service Workers
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
console.log('Désenregistrement du Service Worker:', registration.scope);
registration.unregister().then(function(boolean) {
console.log('Service Worker désenregistré:', boolean);
});
}
});
}
// Vider tous les caches
if ('caches' in window) {
caches.keys().then(function(names) {
for (let name of names) {
console.log('Suppression du cache:', name);
caches.delete(name);
}
});
}
// Forcer le rechargement de la page après nettoyage
setTimeout(function() {
console.log('🔄 Rechargement de la page...');
window.location.reload(true);
}, 1000);
};
console.log('💡 Pour nettoyer le cache PWA, tapez: clearPWACache() dans la console');
// Fonction pour vérifier l'état du cache PWA
window.checkPWACache = function() {
console.log('🔍 Vérification de l\'état PWA...');
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
console.log('Service Workers enregistrés:', registrations.length);
registrations.forEach(function(registration, index) {
console.log(`SW ${index + 1}:`, registration.scope, registration.active ? 'ACTIF' : 'INACTIF');
});
});
} else {
console.log('Service Worker non supporté');
}
if ('caches' in window) {
caches.keys().then(function(names) {
console.log('Caches disponibles:', names.length);
names.forEach(function(name) {
console.log('- Cache:', name);
});
});
} else {
console.log('Cache API non supporté');
}
};
console.log('💡 Pour vérifier l\'état PWA, tapez: checkPWACache() dans la console');
// Nettoyage automatique du cache PWA pour les tests (à supprimer en production)
if (window.location.search.includes('clearcache')) {
console.log('🧹 Nettoyage automatique du cache PWA demandé via URL');
clearPWACache();
}
// NOTE: La navigation des images principales est gérée par product-image-navigation.js
// Les fonctions sont exposées via window.productImageNav pour le débogage
// NOTE: La navigation des images principales est gérée par product-image-navigation.js
// Le script externe initialise automatiquement les images depuis les thumbnails
// Pas besoin de réinitialiser ici pour éviter les conflits
// Gestion du carrousel de miniatures avec taille fixe carrée
const navUpBtn = document.getElementById('thumbnail-nav-up');
const navDownBtn = document.getElementById('thumbnail-nav-down');
const thumbnailsContainer = document.getElementById('product-thumbnails');
const thumbnailsWrapper = document.getElementById('thumbnails-wrapper');
const thumbnailItems = Array.from(document.querySelectorAll('.thumbnail-item'));
let currentThumbnailOffset = 0;
const thumbnailHeight = 90;
// 80px (hauteur) + 10px (gap)
// Calculer la hauteur disponible pour les miniatures dynamiquement
const thumbnailsEl = document.getElementById('product-thumbnails');
let containerHeight = thumbnailsEl ? thumbnailsEl.clientHeight : 520; // Hauteur par défaut si non disponible
let visibleThumbnails = Math.floor(containerHeight / thumbnailHeight);
// Recalculer après le chargement pour avoir la vraie hauteur
setTimeout(() => {
if (thumbnailsEl) {
containerHeight = thumbnailsEl.clientHeight;
visibleThumbnails = Math.floor(containerHeight / thumbnailHeight);
updateThumbnailsPosition();
}
}, 100);
function getCurrentIndex() {
const current = document.querySelector('.thumbnail-item.permanently-active') || document.querySelector('.thumbnail-item.active');
if (! current)
return 0;
const idxAttr = current.getAttribute('data-index');
if (idxAttr !== null) {
return parseInt(idxAttr, 10);
}
const index = thumbnailItems.indexOf(current);
return index >= 0 ? index : 0;
}
function selectThumbnailByIndex(newIndex) {
if (! thumbnailItems.length)
return;
const total = thumbnailItems.length;
if (newIndex < 0)
newIndex = 0;
if (newIndex > total - 1)
newIndex = total - 1;
// Reset classes
thumbnailItems.forEach(t => t.classList.remove('active', 'permanently-active'));
const target = thumbnailItems[newIndex];
if (! target)
return;
target.classList.add('active', 'permanently-active');
// Utiliser la fonction du script externe si disponible
if (window.productImageNav && typeof window.productImageNav.changeMainImageByIndex === 'function') {
window.productImageNav.changeMainImageByIndex(newIndex);
} else {
// Fallback : changer l'image directement
const imageUrl = target.getAttribute('data-image');
const mainImg = document.getElementById('main-product-image');
if (mainImg && imageUrl) {
mainImg.src = imageUrl;
}
}
// Ajuster le carrousel pour voir la miniature sélectionnée
ensureThumbnailVisible(newIndex);
}
function ensureThumbnailVisible(index) {
if (! thumbnailsWrapper || ! thumbnailItems.length)
return;
const targetTop = index * thumbnailHeight;
const visibleTop = currentThumbnailOffset * thumbnailHeight;
const visibleBottom = visibleTop + containerHeight;
// Si la miniature est au-dessus de la zone visible
if (targetTop < visibleTop) {
currentThumbnailOffset = index;
updateThumbnailsPosition();
}
// Si la miniature est en-dessous de la zone visible else if (targetTop + thumbnailHeight > visibleBottom) {
currentThumbnailOffset = Math.max(0, index - visibleThumbnails + 1);
updateThumbnailsPosition();
}
}
function updateThumbnailsPosition() {
if (!thumbnailsWrapper)
return;
const maxOffset = Math.max(0, thumbnailItems.length - visibleThumbnails);
currentThumbnailOffset = Math.max(0, Math.min(currentThumbnailOffset, maxOffset));
thumbnailsWrapper.style.transform = `translateY(-${
currentThumbnailOffset * thumbnailHeight
}px)`;
updateNavButtons();
}
function updateNavButtons() {
if (!thumbnailItems.length)
return;
const maxOffset = Math.max(0, thumbnailItems.length - visibleThumbnails);
const canScrollUp = currentThumbnailOffset > 0;
const canScrollDown = currentThumbnailOffset < maxOffset;
if (navUpBtn) {
navUpBtn.classList.toggle('disabled', ! canScrollUp);
}
if (navDownBtn) {
navDownBtn.classList.toggle('disabled', ! canScrollDown);
}
}
// Navigation avec les boutons
if(navUpBtn) {
navUpBtn.addEventListener('click', function (e) {
e.preventDefault();
if (currentThumbnailOffset > 0) {
currentThumbnailOffset --;
updateThumbnailsPosition();
}
});
}
if(navDownBtn) {
navDownBtn.addEventListener('click', function (e) {
e.preventDefault();
const maxOffset = Math.max(0, thumbnailItems.length - visibleThumbnails);
if (currentThumbnailOffset < maxOffset) {
currentThumbnailOffset ++;
updateThumbnailsPosition();
}
});
}
// Initialiser le carrousel
if(thumbnailsWrapper && thumbnailItems.length > 0) {
updateThumbnailsPosition();
// S'assurer que la première miniature est visible
ensureThumbnailVisible(0);
// Sélectionner la première image par défaut si aucune n'est sélectionnée
if (!document.querySelector('.thumbnail-item.permanently-active') && !document.querySelector('.thumbnail-item.active')) {
selectThumbnailByIndex(0);
}
// Initialiser l'index actuel via le script externe si disponible
// Le script externe product-image-navigation.js gère déjà l'initialisation
if (window.productImageNav && typeof window.productImageNav.getState === 'function') {
const state = window.productImageNav.getState();
if (state.productImages && state.productImages.length > 0) {
window.productImageNav.changeMainImageByIndex(0);
}
}
}
// NOTE: La navigation des images principales est maintenant gérée par product-image-navigation.js
// Ce fichier externe évite les conflits avec cart-modal.js et autres scripts
}, 100); // Fin du setTimeout
}); // Fin du DOMContentLoaded
// --- Prix de gros : calcul du prix unitaire dynamique ---
(function () {
const bulk = document.getElementById('bulk-pricing');
const qtyInputEl = document.getElementById('sst');
const unitPriceSpan = document.getElementById('unit-price-value');
const mainUnitPriceSpan = document.getElementById('main-unit-price');
const totalPriceSpan = document.getElementById('total-price-value');
const savingsAmountSpan = document.getElementById('savings-amount');
const savingsPercentSpan = document.getElementById('savings-percent');
const unitPriceInput = document.getElementById('unit-price-input');
const tierRowsTbody = document.getElementById('bulk-tier-rows');
if (! bulk || ! qtyInputEl || ! unitPriceSpan || ! mainUnitPriceSpan || ! unitPriceInput)
return;
let tiers = [];
try {
tiers = JSON.parse(bulk.getAttribute('data-tiers') || '[]');
} catch (e) {
tiers = [];
}
if (!Array.isArray(tiers) || tiers.length === 0)
return;
tiers.sort(function (a, b) {
return(a.min || 0) - (b.min || 0);
});
const basePrice = (function () {
const one = tiers.find(t => (t.min || 0) <= 1);
return one ? parseFloat(one.price) : parseFloat(tiers[0].price);
})();
function getUnitPriceForQty(qty) {
let candidate = tiers[0] || null;
for (let i = 0; i < tiers.length; i++) {
if (qty >= (tiers[i].min || 1)) {
candidate = tiers[i];
}
}
return candidate ? parseFloat(candidate.price) : (tiers[0] ? parseFloat(tiers[0].price) : basePrice);
}
function formatPrice(val) {
return(parseFloat(val) || 0).toFixed(2);
}
function highlightActiveTier(qty) {
if (! tierRowsTbody)
return;
const rows = tierRowsTbody.querySelectorAll('tr');
rows.forEach(r => r.classList.remove('table-warning'));
let activeRow = null;
rows.forEach(r => {
const m = parseInt(r.getAttribute('data-min'), 10) || 1;
if (qty >= m)
activeRow = r;
});
if (activeRow)
activeRow.classList.add('table-warning');
}
function refreshPrices() {
const qty = Math.max(1, parseInt(qtyInputEl.value, 10) || 1);
const unit = getUnitPriceForQty(qty);
const total = unit * qty;
const saveAmount = Math.max(0, (basePrice - unit) * qty);
const savePercent = Math.max(0, 100 * (basePrice - unit) / basePrice);
unitPriceSpan.textContent = formatPrice(unit) + ' HTG';
mainUnitPriceSpan.textContent = formatPrice(unit);
if (totalPriceSpan)
totalPriceSpan.textContent = formatPrice(total);
if (savingsAmountSpan)
savingsAmountSpan.textContent = formatPrice(saveAmount);
if (savingsPercentSpan)
savingsPercentSpan.textContent = Math.round(savePercent);
unitPriceInput.value = formatPrice(unit);
highlightActiveTier(qty);
}
qtyInputEl.addEventListener('input', refreshPrices);
qtyInputEl.addEventListener('change', refreshPrices);
setTimeout(refreshPrices, 50);
setInterval(refreshPrices, 350);
})();
// --- /Prix de gros ---
// Améliorer les boutons de quantité avec gestion de l'état disabled
(function() {
const qtyInput = document.getElementById('sst');
const decreaseBtn = document.querySelector('.quantity-btn-decrease');
const increaseBtn = document.querySelector('.quantity-btn-increase');
if (!qtyInput || !decreaseBtn || !increaseBtn) return;
const maxStock = parseInt(qtyInput.getAttribute('max') || '99', 10);
function updateButtonStates() {
const currentValue = parseInt(qtyInput.value, 10) || 1;
// Désactiver le bouton decrease si la valeur est 1
if (currentValue <= 1) {
decreaseBtn.disabled = true;
} else {
decreaseBtn.disabled = false;
}
// Désactiver le bouton increase si la valeur est au maximum
if (currentValue >= maxStock) {
increaseBtn.disabled = true;
} else {
increaseBtn.disabled = false;
}
}
// Mettre à jour l'état des boutons au chargement
updateButtonStates();
// Mettre à jour l'état des boutons lors des changements
qtyInput.addEventListener('input', updateButtonStates);
qtyInput.addEventListener('change', updateButtonStates);
// Mettre à jour l'état après les clics sur les boutons
decreaseBtn.addEventListener('click', function() {
setTimeout(updateButtonStates, 10);
});
increaseBtn.addEventListener('click', function() {
setTimeout(updateButtonStates, 10);
});
})();
// Injection du prix unitaire dans l'ajout au panier
(function () {
const addToCartBtn = document.getElementById('add-to-cart-btn');
if (! addToCartBtn)
return;
addToCartBtn.addEventListener('click', function () {
const unitPriceInput = document.getElementById('unit-price-input');
if (unitPriceInput) {
addToCartBtn.setAttribute('data-unit-price', unitPriceInput.value);
}
});
})();
// Fonction pour ouvrir le modal de zoom avec loader
function openImageZoom () {
const mainImage = document.getElementById('main-product-image');
const zoomModal = document.getElementById('image-zoom-modal');
const zoomImage = document.getElementById('zoom-modal-image');
const zoomLoader = document.getElementById('zoom-loader');
if (mainImage && zoomModal && zoomImage) { // Afficher le loader
if (zoomLoader) {
zoomLoader.style.display = 'flex';
}
// Masquer l'image et réinitialiser
zoomImage.classList.remove('loaded');
zoomImage.style.opacity = '0';
// Ouvrir le modal avec animation
zoomModal.classList.add('active');
document.body.style.overflow = 'hidden';
// Charger l'image
const imageUrl = mainImage.src;
const tempImage = new Image();
tempImage.onload = function () {
zoomImage.src = imageUrl;
// Masquer le loader et afficher l'image avec transition
setTimeout(function () {
if (zoomLoader) {
zoomLoader.style.display = 'none';
}
zoomImage.classList.add('loaded');
zoomImage.style.opacity = '1';
}, 200);
};
tempImage.onerror = function () {
if (zoomLoader) {
zoomLoader.style.display = 'none';
}
zoomImage.src = imageUrl;
zoomImage.classList.add('loaded');
};
tempImage.src = imageUrl;
}
}
// Fonction pour fermer le modal de zoom avec animation
function closeImageZoom () {
const zoomModal = document.getElementById('image-zoom-modal');
const zoomImage = document.getElementById('zoom-modal-image');
const zoomLoader = document.getElementById('zoom-loader');
if (zoomModal) { // Animation de fermeture
if (zoomImage) {
zoomImage.style.opacity = '0';
zoomImage.classList.remove('loaded');
}
setTimeout(function () {
zoomModal.classList.remove('active');
document.body.style.overflow = 'auto';
// Réinitialiser le loader pour la prochaine ouverture
if (zoomLoader) {
zoomLoader.style.display = 'none';
}
}, 200);
}
}
// Fermer le modal en cliquant en dehors
document.addEventListener('DOMContentLoaded', function () {
const zoomModal = document.getElementById('image-zoom-modal');
if (zoomModal) {
zoomModal.addEventListener('click', function (e) {
if (e.target === zoomModal) {
closeImageZoom();
}
});
}
});
</script>
<!-- Modal de zoom d'image -->
<div id="image-zoom-modal" class="image-zoom-modal">
<span class="close-zoom" onclick="closeImageZoom()" title="Fermer">×</span>
<div
class="zoom-modal-content">
<!-- Loader -->
<div class="zoom-loader" id="zoom-loader" style="display: none;">
<div class="zoom-loader-spinner"></div>
<div class="zoom-loader-text">Chargement...</div>
</div>
<!-- Image -->
<img id="zoom-modal-image" src="" alt="{{ product.name }}">
</div>
</div>
{% if dropshipReferral %}
<!-- Modal d'affiliation -->
<div id="dropship-modal" class="dropship-referral-modal" style="display: none;">
<div class="dropship-modal-overlay"></div>
<div class="dropship-modal-content">
<button type="button" class="dropship-modal-close" onclick="closeDropshipModal()" aria-label="Fermer">
×
</button>
<div class="dropship-modal-header">
<div class="dropship-icon">
<i class="fas fa-gift"></i>
</div>
<h3>Produit recommandé par
{{ dropshipReferral.affiliateName }}</h3>
<p class="dropship-subtitle">Vous avez été dirigé vers ce produit par un de nos partenaires</p>
</div>
<div class="dropship-modal-body">
<div class="dropship-product-info">
{% if product.images|length > 0 %}
<img src="{{ asset(product.images[0]) }}" alt="{{ product.name }}" class="dropship-product-image" onerror="this.style.display='none'; this.nextElementSibling.style.display='flex';">
<div class="dropship-product-image-placeholder" style="display: none; width: 100px; height: 100px; background: #f0f0f0; border-radius: 8px; align-items: center; justify-content: center; color: #999;">
<i class="fas fa-image" style="font-size: 32px;"></i>
</div>
{% else %}
<div class="dropship-product-image-placeholder" style="width: 100px; height: 100px; background: #f0f0f0; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: #999;">
<i class="fas fa-image" style="font-size: 32px;"></i>
</div>
{% endif %}
<div class="dropship-product-details">
<h4>{{ product.name }}</h4>
<div class="dropship-product-price">
<span class="price">{{ product.price|number_format(2, ',', ' ') }}
HTG</span>
</div>
</div>
</div>
<div class="dropship-message">
<p>
<strong>{{ dropshipReferral.affiliateName }}</strong>
vous recommande ce produit. Souhaitez-vous l'ajouter à votre panier ?</p>
</div>
</div>
<div class="dropship-modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeDropshipModal()">
Parcourir d'abord
</button>
<button type="button" class="btn btn-primary dropship-add-to-cart" onclick="addToCartFromDropship()">
<i class="fas fa-shopping-cart"></i>
Ajouter au panier
</button>
</div>
</div>
</div>
<style>
.dropship-referral-modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 10000;
display: flex;
align-items: center;
justify-content: center;
}
.dropship-modal-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(4px);
}
.dropship-modal-content {
position: relative;
background: white;
border-radius: 16px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
z-index: 10001;
animation: dropshipModalSlideIn 0.3s ease-out;
}
@keyframes dropshipModalSlideIn {
from {
opacity: 0;
transform: translateY(-30px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
.dropship-modal-close {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
font-size: 28px;
color: #666;
cursor: pointer;
width: 35px;
height: 35px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: all 0.2s;
z-index: 10;
}
.dropship-modal-close:hover {
background: #f0f0f0;
color: #333;
}
.dropship-modal-header {
padding: 30px 30px 20px;
text-align: center;
border-bottom: 1px solid #f0f0f0;
}
.dropship-icon {
width: 70px;
height: 70px;
margin: 0 auto 15px;
background: linear-gradient(135deg, #ffa200 0%, #ff8c00 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
color: white;
box-shadow: 0 4px 15px rgba(255, 162, 0, 0.3);
}
.dropship-modal-header h3 {
margin: 0 0 8px;
font-size: 22px;
font-weight: 600;
color: #333;
}
.dropship-subtitle {
margin: 0;
color: #666;
font-size: 14px;
}
.dropship-modal-body {
padding: 25px 30px;
}
.dropship-product-info {
display: flex;
gap: 15px;
margin-bottom: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
}
.dropship-product-image {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
flex-shrink: 0;
}
.dropship-product-details {
flex: 1;
}
.dropship-product-details h4 {
margin: 0 0 8px;
font-size: 16px;
font-weight: 600;
color: #333;
}
.dropship-product-price {
margin-top: 5px;
}
.dropship-product-price .price {
font-size: 20px;
font-weight: 700;
color: #ffa200;
}
.dropship-message {
padding: 15px;
background: #fff9e6;
border-left: 4px solid #ffa200;
border-radius: 6px;
}
.dropship-message p {
margin: 0;
color: #666;
line-height: 1.6;
}
.dropship-modal-footer {
padding: 20px 30px 30px;
display: flex;
gap: 12px;
justify-content: flex-end;
border-top: 1px solid #f0f0f0;
}
.dropship-modal-footer .btn {
padding: 12px 24px;
border-radius: 8px;
font-weight: 500;
border: none;
cursor: pointer;
transition: all 0.2s;
font-size: 15px;
}
.dropship-modal-footer .btn-secondary {
background: #f0f0f0;
color: #666;
}
.dropship-modal-footer .btn-secondary:hover {
background: #e0e0e0;
}
.dropship-modal-footer .btn-primary {
background: #ffa200;
color: white;
}
.dropship-modal-footer .btn-primary:hover {
background: #e8910a;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(255, 162, 0, 0.3);
}
.dropship-modal-footer .btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
</style>
<script>
// Afficher le modal automatiquement au chargement de la page
document.addEventListener('DOMContentLoaded', function () {
const dropshipModal = document.getElementById('dropship-modal');
if (dropshipModal) {
setTimeout(function () {
dropshipModal.style.display = 'flex';
document.body.style.overflow = 'hidden';
}, 500); // Délai de 500ms pour une meilleure UX
}
});
function closeDropshipModal () {
const dropshipModal = document.getElementById('dropship-modal');
if (dropshipModal) {
dropshipModal.style.display = 'none';
document.body.style.overflow = 'auto';
}
}
function addToCartFromDropship () {
const addBtn = document.querySelector('.dropship-add-to-cart');
if (! addBtn)
return;
// Désactiver le bouton pendant le traitement
addBtn.disabled = true;
addBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Ajout en cours...';
const productId = {{ product.id }};
const qty = parseInt(document.getElementById('sst') ?. value || '1');
// Utiliser fetch pour ajouter au panier
fetch('{{ path("ui_cart_add") }}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams(
{productId: productId, qty: qty}
)
}).then(response => response.json()).then(data => {
if (data.ok) { // Afficher un message de succès
const modalBody = document.querySelector('.dropship-modal-body');
if (modalBody) {
modalBody.innerHTML = `
<div style="text-align: center; padding: 30px 20px;">
<div style="width: 80px; height: 80px; margin: 0 auto 20px; background: #d4edda; border-radius: 50%; display: flex; align-items: center; justify-content: center;">
<i class="fas fa-check" style="font-size: 40px; color: #28a745;"></i>
</div>
<h3 style="color: #28a745; margin-bottom: 10px;">Produit ajouté avec succès !</h3>
<p style="color: #666; margin-bottom: 20px;">Merci d'avoir utilisé le lien de recommandation de <strong>{{ dropshipReferral.affiliateName }}</strong></p>
<p style="color: #666; font-size: 14px;">Vous pouvez continuer vos achats ou aller directement au panier.</p>
</div>
`;
}
const modalFooter = document.querySelector('.dropship-modal-footer');
if (modalFooter) {
modalFooter.innerHTML = `
<button type="button" class="btn btn-secondary" onclick="closeDropshipModal()">
Continuer les achats
</button>
<a href="{{ path('cart') }}" class="btn btn-primary" style="text-decoration: none; display: inline-block;">
<i class="fas fa-shopping-cart"></i> Voir le panier
</a>
`;
}
// Mettre à jour le compteur du panier si présent
const cartCount = document.querySelector('.cart-count, .cart_count, [data-cart-count]');
if (cartCount) {
cartCount.textContent = data.totalQty || 0;
}
} else {
showCustomAlert('Erreur lors de l\'ajout au panier. Veuillez réessayer.', 'error');
addBtn.disabled = false;
addBtn.innerHTML = '<i class="fas fa-shopping-cart"></i> Ajouter au panier';
}
}).catch(error => {
console.error('Erreur:', error);
showCustomAlert('Une erreur est survenue. Veuillez réessayer.', 'error');
addBtn.disabled = false;
addBtn.innerHTML = '<i class="fas fa-shopping-cart"></i> Ajouter au panier';
});
}
// Fermer le modal en cliquant sur l'overlay
document.addEventListener('DOMContentLoaded', function () {
const dropshipModal = document.getElementById('dropship-modal');
if (dropshipModal) {
const overlay = dropshipModal.querySelector('.dropship-modal-overlay');
if (overlay) {
overlay.addEventListener('click', function () {
closeDropshipModal();
});
}
}
});
</script>
{% endif %}
<!-- Modal personnalisé pour remplacer les alert -->
<div class="modal fade custom-alert-modal" id="customAlertModal" tabindex="-1" aria-labelledby="customAlertModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-white" id="customAlertModalLabel">Notification</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="alert-icon" id="alertIcon">
<i class="lnr lnr-info-circle"></i>
</div>
<p class="alert-message" id="alertMessage"></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>
<script>
// Fonction pour remplacer alert()
function showCustomAlert (message, type = 'info') {
const modal = new bootstrap.Modal(document.getElementById('customAlertModal'));
const alertMessage = document.getElementById('alertMessage');
const alertIcon = document.getElementById('alertIcon');
const modalTitle = document.getElementById('customAlertModalLabel');
alertMessage.textContent = message;
// Définir l'icône et le titre selon le type
let iconClass = 'lnr lnr-info-circle';
let title = 'Information';
switch (type) {
case 'success': iconClass = 'lnr lnr-checkmark-circle';
title = 'Succès';
alertIcon.className = 'alert-icon success';
break;
case 'error':
case 'danger': iconClass = 'lnr lnr-warning';
title = 'Erreur';
alertIcon.className = 'alert-icon error';
break;
case 'warning': iconClass = 'lnr lnr-warning';
title = 'Attention';
alertIcon.className = 'alert-icon warning';
break;
default: iconClass = 'lnr lnr-info-circle';
title = 'Information';
alertIcon.className = 'alert-icon info';
}
alertIcon.innerHTML = `<i class="${iconClass}"></i>`;
modalTitle.textContent = title;
modal.show();
}
// Remplacer window.alert par showCustomAlert
window.alert = showCustomAlert;
</script>
{% endblock %}