792 lines
34 KiB
TypeScript
792 lines
34 KiB
TypeScript
import { useState, useEffect } from "react";
|
|
import { useParams, useNavigate, useLocation } from "react-router-dom";
|
|
import Navbar from "../../components/Navbar";
|
|
import "./OrderDetails.css";
|
|
import {
|
|
formatPrice,
|
|
getOrderAge,
|
|
getCommandItemsWithDetails,
|
|
isUserAuthenticated,
|
|
getProductById,
|
|
getMediaUrl,
|
|
} from "../../api/api";
|
|
import type { CompletedOrder, Product } from "../../api/api_types";
|
|
import {
|
|
Package,
|
|
MapPin,
|
|
User,
|
|
Calendar,
|
|
Clock,
|
|
ArrowLeft,
|
|
CheckCircle,
|
|
Truck,
|
|
Phone,
|
|
Mail,
|
|
CreditCard,
|
|
ShoppingBag,
|
|
Camera,
|
|
X,
|
|
} from "lucide-react";
|
|
|
|
interface OrderProduct {
|
|
id: number;
|
|
product_id: number;
|
|
name_product: string;
|
|
category: string;
|
|
price: number;
|
|
quantity: number;
|
|
image?: string;
|
|
}
|
|
|
|
interface OrderProductWithMedia extends OrderProduct {
|
|
hasVideo: boolean;
|
|
videoUrl?: string;
|
|
}
|
|
|
|
interface OrderDetailsData extends CompletedOrder {
|
|
products?: OrderProduct[];
|
|
phone?: string;
|
|
email?: string;
|
|
nom?: string;
|
|
prenom?: string;
|
|
payment_method?: string;
|
|
delivery_time?: string;
|
|
delivery_notes?: string;
|
|
}
|
|
|
|
function OrderDetails() {
|
|
const { orderId } = useParams<{ orderId: string }>();
|
|
const navigate = useNavigate();
|
|
const location = useLocation();
|
|
// commandId = ID global pour l'API (passé en state depuis l'historique)
|
|
// fallback sur orderId si navigation directe via URL
|
|
const commandId = (location.state as { commandId?: number } | null)?.commandId ?? parseInt(orderId ?? "0");
|
|
|
|
const [order, setOrder] = useState<OrderDetailsData | null>(null);
|
|
const [enrichedProducts, setEnrichedProducts] = useState<
|
|
OrderProductWithMedia[]
|
|
>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [loadingMedia, setLoadingMedia] = useState(false);
|
|
const [error, setError] = useState<string>("");
|
|
const [showVideo, setShowVideo] = useState(false);
|
|
const [currentVideoUrl, setCurrentVideoUrl] = useState<string>("");
|
|
|
|
// ✅ VÉRIFICATION INITIALE DE L'AUTHENTIFICATION
|
|
useEffect(() => {
|
|
const checkAuth = () => {
|
|
if (!isUserAuthenticated()) {
|
|
console.log(
|
|
"❌ [OrderDetails] Utilisateur non authentifié, redirection vers /login/client",
|
|
);
|
|
navigate("/login/client", { replace: true });
|
|
}
|
|
};
|
|
|
|
checkAuth();
|
|
}, [navigate]);
|
|
|
|
// ✅ VÉRIFICATION CONTINUE (toutes les 5 secondes)
|
|
useEffect(() => {
|
|
const authInterval = setInterval(() => {
|
|
if (!isUserAuthenticated()) {
|
|
console.log(
|
|
"❌ [OrderDetails] Session expirée, redirection vers /login/client",
|
|
);
|
|
navigate("/login/client", { replace: true });
|
|
}
|
|
}, 5000);
|
|
|
|
return () => clearInterval(authInterval);
|
|
}, [navigate]);
|
|
|
|
useEffect(() => {
|
|
if (commandId) {
|
|
fetchOrderDetails(commandId);
|
|
}
|
|
}, [orderId]);
|
|
|
|
// ============================================
|
|
// FONCTIONS POUR PHOTOS ET VIDÉOS (COMME PRODUCTCARD)
|
|
// ============================================
|
|
|
|
const getProductImage = (product: Product): string => {
|
|
if (!product.media || product.media.length === 0) {
|
|
return "https://via.placeholder.com/400x400/1a1a1a/ffffff?text=No+Image";
|
|
}
|
|
|
|
for (let i = 0; i < product.media.length; i++) {
|
|
const mediaItem = product.media[i];
|
|
if (mediaItem && mediaItem.type === "image" && mediaItem.url) {
|
|
return getMediaUrl(mediaItem.url);
|
|
}
|
|
}
|
|
|
|
return "https://via.placeholder.com/400x400/1a1a1a/ffffff?text=No+Image";
|
|
};
|
|
|
|
const hasProductVideo = (product: Product): boolean => {
|
|
if (!product.media || product.media.length === 0) {
|
|
return false;
|
|
}
|
|
return product.media.some(
|
|
(mediaItem) => mediaItem && mediaItem.type === "video",
|
|
);
|
|
};
|
|
|
|
const getProductVideoUrl = (product: Product): string | undefined => {
|
|
if (!product.media || product.media.length === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const videoMedia = product.media.find(
|
|
(mediaItem) => mediaItem && mediaItem.type === "video",
|
|
);
|
|
|
|
return videoMedia?.url ? getMediaUrl(videoMedia.url) : undefined;
|
|
};
|
|
|
|
// HANDLERS VIDÉO
|
|
const handleVideoToggle = (videoUrl: string) => {
|
|
setCurrentVideoUrl(videoUrl);
|
|
setShowVideo(true);
|
|
};
|
|
|
|
const handleCloseVideo = () => {
|
|
setShowVideo(false);
|
|
setCurrentVideoUrl("");
|
|
};
|
|
|
|
// ENRICHIR LES PRODUITS AVEC MÉDIAS
|
|
useEffect(() => {
|
|
const enrichOrderProducts = async () => {
|
|
if (!order?.products || order.products.length === 0) {
|
|
setEnrichedProducts([]);
|
|
setLoadingMedia(false);
|
|
return;
|
|
}
|
|
|
|
setLoadingMedia(true);
|
|
console.log(
|
|
"🔄 [ORDER DETAILS] Enrichissement de",
|
|
order.products.length,
|
|
"produits...",
|
|
);
|
|
|
|
try {
|
|
const enrichedPromises = order.products.map(async (product) => {
|
|
try {
|
|
console.log(
|
|
`📦 [ORDER DETAILS] Récupération médias pour produit ${product.product_id}...`,
|
|
);
|
|
const productResponse = await getProductById(
|
|
product.product_id,
|
|
);
|
|
|
|
if (productResponse.success && productResponse.data) {
|
|
const fullProduct = productResponse.data;
|
|
const enriched: OrderProductWithMedia = {
|
|
...product,
|
|
image: getProductImage(fullProduct),
|
|
hasVideo: hasProductVideo(fullProduct),
|
|
videoUrl: getProductVideoUrl(fullProduct),
|
|
};
|
|
console.log(`✅ [ORDER DETAILS] Produit enrichi:`, {
|
|
name: product.name_product,
|
|
image: enriched.image,
|
|
hasVideo: enriched.hasVideo,
|
|
});
|
|
return enriched;
|
|
} else {
|
|
console.warn(
|
|
`⚠️ [ORDER DETAILS] Produit ${product.product_id} non trouvé`,
|
|
);
|
|
return {
|
|
...product,
|
|
image: "https://via.placeholder.com/400x400/1a1a1a/ffffff?text=No+Image",
|
|
hasVideo: false,
|
|
videoUrl: undefined,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.error(
|
|
`❌ [ORDER DETAILS] Erreur pour produit ${product.product_id}:`,
|
|
error,
|
|
);
|
|
return {
|
|
...product,
|
|
image: "https://via.placeholder.com/400x400/1a1a1a/ffffff?text=No+Image",
|
|
hasVideo: false,
|
|
videoUrl: undefined,
|
|
};
|
|
}
|
|
});
|
|
|
|
const enriched = await Promise.all(enrichedPromises);
|
|
setEnrichedProducts(enriched);
|
|
console.log(
|
|
"✅ [ORDER DETAILS] Enrichissement terminé:",
|
|
enriched.length,
|
|
"produits",
|
|
);
|
|
} catch (error) {
|
|
console.error(
|
|
"❌ [ORDER DETAILS] Erreur enrichissement:",
|
|
error,
|
|
);
|
|
setEnrichedProducts(
|
|
order.products.map((product) => ({
|
|
...product,
|
|
image: "https://via.placeholder.com/400x400/1a1a1a/ffffff?text=No+Image",
|
|
hasVideo: false,
|
|
videoUrl: undefined,
|
|
})),
|
|
);
|
|
} finally {
|
|
setLoadingMedia(false);
|
|
}
|
|
};
|
|
|
|
enrichOrderProducts();
|
|
}, [order?.products]);
|
|
|
|
const fetchOrderDetails = async (id: number) => {
|
|
if (!isUserAuthenticated()) {
|
|
console.log("❌ [fetchOrderDetails] Non authentifié");
|
|
navigate("/login/client", { replace: true });
|
|
return;
|
|
}
|
|
|
|
setIsLoading(true);
|
|
setError("");
|
|
|
|
try {
|
|
console.log("📦 [ORDER DETAILS] Chargement commande:", id);
|
|
|
|
// ✅ Appeler getCommandItemsWithDetails pour récupérer les items
|
|
const result = await getCommandItemsWithDetails(id);
|
|
|
|
if (result.success && result.data) {
|
|
console.log("✅ [ORDER DETAILS] Données reçues:", result.data);
|
|
|
|
const apiData = result.data;
|
|
|
|
// ✅ Mapper les données depuis la structure de GetCommandItemsWithDetails
|
|
const orderData: OrderDetailsData = {
|
|
// Infos de command_info
|
|
id: apiData.command_info?.id || id,
|
|
client_order_number:
|
|
apiData.command_info?.client_order_number || 0,
|
|
username: apiData.client_info?.username || "",
|
|
status: apiData.command_info?.command_status || "unknown",
|
|
adresse: apiData.command_info?.command_address || "",
|
|
total_prix:
|
|
apiData.command_info?.total_prix ||
|
|
apiData.total_price ||
|
|
0,
|
|
livreur_assign: apiData.command_info?.livreur_assign || "",
|
|
created_at:
|
|
apiData.command_info?.command_created_at ||
|
|
new Date().toISOString(),
|
|
updated_at:
|
|
apiData.command_info?.command_created_at ||
|
|
new Date().toISOString(),
|
|
|
|
// ✅ Infos client depuis client_info
|
|
nom: apiData.client_info?.nom || "",
|
|
prenom: apiData.client_info?.prenom || "",
|
|
phone: apiData.client_info?.telephone || "",
|
|
|
|
// ✅ Produits depuis items (command_items)
|
|
products:
|
|
apiData.items?.map((item: any) => ({
|
|
id: item.id,
|
|
product_id: item.product_id,
|
|
name_product: item.produit,
|
|
category: "Non spécifié",
|
|
price: item.prix,
|
|
quantity: item.quantite,
|
|
status: item.status,
|
|
image: item.image,
|
|
})) || [],
|
|
};
|
|
|
|
console.log("✅ [ORDER DETAILS] Données mappées:", orderData);
|
|
setOrder(orderData);
|
|
} else {
|
|
console.error("❌ [ORDER DETAILS] Erreur:", result.message);
|
|
setError(result.message || "Erreur lors du chargement");
|
|
}
|
|
} catch (err) {
|
|
console.error("❌ [ORDER DETAILS] Erreur catch:", err);
|
|
setError("Erreur de connexion");
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const getProductCount = (totalPrix: number): number => {
|
|
return Math.max(1, Math.round(totalPrix / 25));
|
|
};
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<>
|
|
<Navbar />
|
|
<div className="order-details-container">
|
|
<div className="loading-container">
|
|
<div className="loading-spinner">
|
|
<div className="spinner"></div>
|
|
</div>
|
|
<p className="loading-text">
|
|
Chargement des détails...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
if (error || !order) {
|
|
return (
|
|
<>
|
|
<Navbar />
|
|
<div className="order-details-container">
|
|
<div className="error-container">
|
|
<Package size={64} className="error-icon" />
|
|
<h2>Commande introuvable</h2>
|
|
<p>
|
|
{error ||
|
|
"Cette commande n'existe pas ou vous n'y avez pas accès"}
|
|
</p>
|
|
<button
|
|
className="back-button"
|
|
onClick={() =>
|
|
navigate("/user/consultation-historique")
|
|
}
|
|
>
|
|
<ArrowLeft size={20} />
|
|
Retour à l'historique
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Navbar />
|
|
<div className="order-details-container">
|
|
{/* Header avec bouton retour */}
|
|
<div className="details-header">
|
|
<button
|
|
className="back-button"
|
|
onClick={() =>
|
|
navigate("/user/consultation-historique")
|
|
}
|
|
>
|
|
<ArrowLeft size={20} />
|
|
Retour
|
|
</button>
|
|
<div className="header-info">
|
|
<h1 className="order-number">
|
|
Commande #
|
|
{(order.client_order_number ?? 0)
|
|
.toString()
|
|
.padStart(4, "0")}
|
|
</h1>
|
|
<div className="status-container">
|
|
<span className="status-badge delivered">
|
|
<CheckCircle size={16} />
|
|
Livrée
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Grille principale */}
|
|
<div className="details-grid">
|
|
{/* Section Informations de livraison */}
|
|
<div className="details-card">
|
|
<div className="card-header">
|
|
<Truck size={24} />
|
|
<h2>Informations de livraison</h2>
|
|
</div>
|
|
<div className="card-content">
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<MapPin size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Adresse de livraison
|
|
</span>
|
|
<span className="info-value">
|
|
{order.adresse}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{order.livreur_assign && (
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<User size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Livreur
|
|
</span>
|
|
<span className="info-value">
|
|
{order.livreur_assign}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<Calendar size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Date de commande
|
|
</span>
|
|
<span className="info-value">
|
|
{new Date(
|
|
order.created_at,
|
|
).toLocaleDateString("fr-FR", {
|
|
weekday: "long",
|
|
day: "numeric",
|
|
month: "long",
|
|
year: "numeric",
|
|
})}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<Clock size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">Heure</span>
|
|
<span className="info-value">
|
|
{new Date(
|
|
order.created_at,
|
|
).toLocaleTimeString("fr-FR", {
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})}
|
|
</span>
|
|
<span className="info-age">
|
|
{getOrderAge(order.created_at)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{order.delivery_time && (
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<CheckCircle size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Livrée le
|
|
</span>
|
|
<span className="info-value">
|
|
{order.delivery_time}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section Contact Client */}
|
|
<div className="details-card">
|
|
<div className="card-header">
|
|
<Phone size={24} />
|
|
<h2>Informations de contact</h2>
|
|
</div>
|
|
<div className="card-content">
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<User size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">Client</span>
|
|
<span className="info-value">
|
|
{order.prenom && order.nom
|
|
? `${order.prenom} ${order.nom}`
|
|
: order.username}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
|
|
{order.phone && (
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<Phone size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Téléphone
|
|
</span>
|
|
<span className="info-value">
|
|
{order.phone}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
{order.email && (
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<Mail size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Email
|
|
</span>
|
|
<span className="info-value">
|
|
{order.email}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="info-row">
|
|
<div className="info-icon">
|
|
<User size={18} />
|
|
</div>
|
|
<div className="info-content">
|
|
<span className="info-label">
|
|
Nom d'utilisateur
|
|
</span>
|
|
<span className="info-value">
|
|
{order.username}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section Produits */}
|
|
<div className="details-card products-card">
|
|
<div className="card-header">
|
|
<ShoppingBag size={24} />
|
|
<h2>Produits commandés</h2>
|
|
</div>
|
|
<div className="card-content">
|
|
{loadingMedia && enrichedProducts.length === 0 ? (
|
|
<div className="loading-container">
|
|
<p>Chargement des médias...</p>
|
|
</div>
|
|
) : enrichedProducts.length > 0 ? (
|
|
<div className="products-list">
|
|
{enrichedProducts.map((product, index) => (
|
|
<div
|
|
key={`${product.id}-${index}`}
|
|
className="product-item"
|
|
>
|
|
<div className="product-media-container">
|
|
<img
|
|
src={product.image}
|
|
alt={product.name_product}
|
|
className="product-image2"
|
|
loading="lazy"
|
|
onError={(e) => {
|
|
console.warn(
|
|
`❌ Erreur chargement image pour ${product.name_product}:`,
|
|
product.image,
|
|
);
|
|
e.currentTarget.src =
|
|
"https://via.placeholder.com/400x400/7c3aed/ffffff?text=" +
|
|
encodeURIComponent(
|
|
product.name_product.substring(
|
|
0,
|
|
10,
|
|
),
|
|
);
|
|
}}
|
|
/>
|
|
{/* Badge vidéo si disponible */}
|
|
{product.hasVideo &&
|
|
product.videoUrl && (
|
|
<button
|
|
className="product-video-badge"
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
handleVideoToggle(
|
|
product.videoUrl!,
|
|
);
|
|
}}
|
|
aria-label="Voir la vidéo du produit"
|
|
>
|
|
<Camera size={14} />
|
|
<span>Vidéo</span>
|
|
</button>
|
|
)}
|
|
</div>
|
|
<div className="product-info">
|
|
<h3 className="product-name">
|
|
{product.name_product}
|
|
</h3>
|
|
<p className="product-category">
|
|
{product.category}
|
|
</p>
|
|
<div className="product-details">
|
|
<span className="product-quantity">
|
|
Qté: {product.quantity}g
|
|
</span>
|
|
<span className="product-price">
|
|
{formatPrice(
|
|
product.price,
|
|
)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{/* ✅ FIX: Ne pas multiplier, price contient déjà le total */}
|
|
<div className="product-total">
|
|
{formatPrice(product.price)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
) : (
|
|
<div className="products-placeholder">
|
|
<Package
|
|
size={32}
|
|
className="placeholder-icon"
|
|
/>
|
|
<p>
|
|
~{getProductCount(order.total_prix)}{" "}
|
|
produit
|
|
{getProductCount(order.total_prix) > 1
|
|
? "s"
|
|
: ""}
|
|
</p>
|
|
<span className="placeholder-note">
|
|
Détails non disponibles
|
|
</span>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Section Récapitulatif */}
|
|
<div className="details-card summary-card">
|
|
<div className="card-header">
|
|
<CreditCard size={24} />
|
|
<h2>Récapitulatif</h2>
|
|
</div>
|
|
<div className="card-content">
|
|
<div className="summary-row">
|
|
<span>Sous-total</span>
|
|
<span>{formatPrice(order.total_prix)}</span>
|
|
</div>
|
|
<div className="summary-row total-row">
|
|
<span>Total</span>
|
|
<span className="total-amount">
|
|
{formatPrice(order.total_prix)}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Notes de livraison */}
|
|
{order.delivery_notes && (
|
|
<div className="details-card notes-card">
|
|
<div className="card-header">
|
|
<Package size={24} />
|
|
<h2>Notes de livraison</h2>
|
|
</div>
|
|
<div className="card-content">
|
|
<p className="delivery-notes">
|
|
{order.delivery_notes}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Timeline de livraison */}
|
|
<div className="delivery-timeline">
|
|
<h2 className="timeline-title">Suivi de la commande</h2>
|
|
<div className="timeline">
|
|
<div className="timeline-item completed">
|
|
<div className="timeline-marker"></div>
|
|
<div className="timeline-content">
|
|
<h3>Commande confirmée</h3>
|
|
<p>
|
|
{new Date(order.created_at).toLocaleString(
|
|
"fr-FR",
|
|
)}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="timeline-item completed">
|
|
<div className="timeline-marker"></div>
|
|
<div className="timeline-content">
|
|
<h3>En préparation</h3>
|
|
<p>Votre commande a été préparée</p>
|
|
</div>
|
|
</div>
|
|
<div className="timeline-item completed">
|
|
<div className="timeline-marker"></div>
|
|
<div className="timeline-content">
|
|
<h3>En cours de livraison</h3>
|
|
<p>
|
|
{order.livreur_assign
|
|
? `Livrée par ${order.livreur_assign}`
|
|
: "En route"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="timeline-item completed">
|
|
<div className="timeline-marker"></div>
|
|
<div className="timeline-content">
|
|
<h3>Livrée</h3>
|
|
<p>
|
|
{order.delivery_time ||
|
|
"Commande livrée avec succès"}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Modal vidéo */}
|
|
{showVideo && currentVideoUrl && (
|
|
<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"
|
|
>
|
|
<X size={18} />
|
|
</button>
|
|
<video
|
|
src={currentVideoUrl}
|
|
controls
|
|
autoPlay
|
|
className="video-player"
|
|
>
|
|
Votre navigateur ne supporte pas la lecture de
|
|
vidéos.
|
|
</video>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default OrderDetails;
|