chore: add ansible backend docker frontend-prep
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
import { useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCart } from "../context/CartContext";
|
||||
import "./ProductCard.css";
|
||||
|
||||
interface ProductCardProps {
|
||||
id: number;
|
||||
name: string;
|
||||
price: number;
|
||||
unit: string;
|
||||
image: string;
|
||||
stock: number;
|
||||
category: string;
|
||||
prices?: Array<{ quantity: number; price: number }>;
|
||||
hasVideo?: boolean;
|
||||
videoUrl?: string; // ✨ Nouveau prop pour l'URL de la vidéo
|
||||
}
|
||||
|
||||
function ProductCard({
|
||||
id,
|
||||
name,
|
||||
price,
|
||||
image,
|
||||
stock,
|
||||
category = "autre",
|
||||
prices,
|
||||
hasVideo = false,
|
||||
videoUrl,
|
||||
}: ProductCardProps) {
|
||||
const navigate = useNavigate();
|
||||
const { addToCart } = useCart();
|
||||
|
||||
const [showQuantitySelect, setShowQuantitySelect] = useState(false);
|
||||
const [selectedQuantity, setSelectedQuantity] = useState<number | null>(
|
||||
null,
|
||||
);
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const [showVideo, setShowVideo] = useState(false); // ✨ État pour afficher/masquer la vidéo
|
||||
|
||||
const isOutOfStock = stock === 0;
|
||||
const normalizedCategory = (category || "autre").toLowerCase().trim();
|
||||
|
||||
const handleDetailsClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
navigate(`/user/product/${id}`);
|
||||
};
|
||||
|
||||
// ✨ Handler pour afficher/masquer la vidéo
|
||||
const handleVideoToggle = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setShowVideo(!showVideo);
|
||||
};
|
||||
|
||||
// ✨ Handler pour fermer la vidéo
|
||||
const handleCloseVideo = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
setShowVideo(false);
|
||||
};
|
||||
|
||||
const handleQuickAddClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
console.log("🔘 Bouton cliqué", {
|
||||
id,
|
||||
name,
|
||||
category: normalizedCategory,
|
||||
isOutOfStock,
|
||||
prices: prices?.length || 0,
|
||||
showQuantitySelect,
|
||||
});
|
||||
|
||||
if (!isOutOfStock && prices && prices.length > 0) {
|
||||
setShowQuantitySelect(!showQuantitySelect);
|
||||
}
|
||||
};
|
||||
|
||||
const handleQuantitySelect = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const selectedGrams = Number(e.target.value);
|
||||
setSelectedQuantity(selectedGrams);
|
||||
|
||||
const priceOption = prices?.find((p) => p.quantity === selectedGrams);
|
||||
if (priceOption) {
|
||||
console.log("💾 [ProductCard] Ajout au panier:", {
|
||||
id,
|
||||
name,
|
||||
category: normalizedCategory,
|
||||
quantity: selectedGrams,
|
||||
price: priceOption.price,
|
||||
});
|
||||
|
||||
addToCart({
|
||||
product_id: id,
|
||||
name_product: name,
|
||||
price: priceOption.price,
|
||||
quantity: selectedGrams,
|
||||
category: normalizedCategory,
|
||||
image: image,
|
||||
});
|
||||
|
||||
setShowSuccess(true);
|
||||
setTimeout(() => {
|
||||
setShowSuccess(false);
|
||||
setShowQuantitySelect(false);
|
||||
setSelectedQuantity(null);
|
||||
}, 1500);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`product-card ${isOutOfStock ? "out-of-stock" : ""}`}>
|
||||
<div className="product-card-content">
|
||||
<div className="product-image-container">
|
||||
<img
|
||||
src={image}
|
||||
alt={name}
|
||||
className="product-image3"
|
||||
loading="lazy"
|
||||
/>
|
||||
{/* ✨ Icône caméra cliquable */}
|
||||
{hasVideo && !isOutOfStock && videoUrl && (
|
||||
<button
|
||||
className="media-indicator"
|
||||
onClick={handleVideoToggle}
|
||||
aria-label="Voir la vidéo du produit"
|
||||
>
|
||||
<i className="fas fa-camera"></i>
|
||||
</button>
|
||||
)}
|
||||
{/* ✨ Bouton "Détails" */}
|
||||
<button
|
||||
className="details-button"
|
||||
onClick={handleDetailsClick}
|
||||
aria-label="Voir les détails du produit"
|
||||
>
|
||||
<i className="fas fa-info-circle"></i>
|
||||
<span>Détails</span>
|
||||
</button>
|
||||
{isOutOfStock && (
|
||||
<div className="sold-out-overlay">SOLD OUT</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="product-info">
|
||||
<h3 className="product-name">{name}</h3>
|
||||
<p className="product-price">
|
||||
{price > 0
|
||||
? `${price.toFixed(2)} €`
|
||||
: "Prix non disponible"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="quick-add-section"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{showSuccess ? (
|
||||
<div className="success-message">✓ Ajouté !</div>
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className={`quick-add-btn ${isOutOfStock ? "disabled" : ""}`}
|
||||
onClick={handleQuickAddClick}
|
||||
disabled={isOutOfStock}
|
||||
>
|
||||
{isOutOfStock
|
||||
? "Rupture de stock"
|
||||
: "Ajouter rapidement"}
|
||||
</button>
|
||||
|
||||
{showQuantitySelect && prices && prices.length > 0 && (
|
||||
<select
|
||||
className="quantity-select"
|
||||
value={selectedQuantity ?? ""}
|
||||
onChange={handleQuantitySelect}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<option value="">Choisir une quantité</option>
|
||||
{prices.map((priceOption) => (
|
||||
<option
|
||||
key={priceOption.quantity}
|
||||
value={priceOption.quantity}
|
||||
>
|
||||
{priceOption.quantity}g -{" "}
|
||||
{priceOption.price.toFixed(2)} €
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* ✨ Modal vidéo */}
|
||||
{showVideo && videoUrl && (
|
||||
<div className="video-modal" onClick={handleCloseVideo}>
|
||||
<div
|
||||
className="video-modal-content"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<button
|
||||
className="video-close-btn"
|
||||
onClick={handleCloseVideo}
|
||||
aria-label="Fermer la vidéo"
|
||||
>
|
||||
<i className="fas fa-times"></i>
|
||||
</button>
|
||||
<video
|
||||
src={videoUrl}
|
||||
controls
|
||||
autoPlay
|
||||
className="video-player"
|
||||
>
|
||||
Votre navigateur ne supporte pas la lecture de
|
||||
vidéos.
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ProductCard;
|
||||
Reference in New Issue
Block a user