import { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { Search, Filter, Eye, MapPin, Clock, User, Package, CheckCircle, XCircle, AlertCircle, Truck, ChevronDown, Navigation, RefreshCw, } from "lucide-react"; import AdminLayout from "../../components/AdminLayout"; import "./AdminOrders.css"; import { getAllCommands, getDeliverymanLocationForCommand, isAdminAuthenticated, } from "../../api/api_admin"; import { getCommandItems } from "../../api/api_cabine"; import type { DeliverymanLocationResponse } from "../../api/api_admin_types"; // Interface correspondant au backend interface CommandResponse { id: number; username: string; status: string; adresse: string; total_prix: number; livreur_assign?: string | null; created_at: string; updated_at: string; } interface CommandItem { id: number; command_id: number; produit: string; product_id: number; quantite: number; prix: number; client_username: string; client_nom: string; client_prenom: string; client_telephone: string; delivery_address: string; status: string; created_at: string; updated_at: string; command_status: string; command_address: string; total_prix: number; livreur_assign: string; command_created_at: string; } interface Order { id: number; orderNumber: string; client: { name: string; phone: string; username: string; }; deliveryPerson?: { name: string; username: string; }; items: Array<{ name: string; quantity: number; price: number; }>; total: number; status: | "pending" | "assigned" | "en_route" | "livre" | "approved" | "cancelled"; createdAt: string; deliveryAddress: string; } type FilterStatus = | "all" | "pending" | "assigned" | "en_route" | "livre" | "approved" | "cancelled"; function AdminOrders() { const navigate = useNavigate(); const [orders, setOrders] = useState([]); const [filteredOrders, setFilteredOrders] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(""); const [filterStatus, setFilterStatus] = useState("all"); const [showFilters, setShowFilters] = useState(false); const [selectedOrder, setSelectedOrder] = useState(null); const [showOrderDetails, setShowOrderDetails] = useState(false); const [deliverymanLocation, setDeliverymanLocation] = useState(null); const [loadingLocation, setLoadingLocation] = useState(false); const [showLocationModal, setShowLocationModal] = useState(false); // ✅ AJOUT du compteur cancelled dans les stats const [stats, setStats] = useState({ pending: 0, assigned: 0, en_route: 0, livre: 0, approved: 0, cancelled: 0, // ✅ NOUVEAU total: 0, }); // ✅ VÉRIFICATION INITIALE DE L'AUTHENTIFICATION useEffect(() => { const checkAuth = () => { if (!isAdminAuthenticated()) { console.log( "❌ [AdminOrders] Admin non authentifié, redirection vers /login-admin/admin", ); navigate("/login-admin/admin", { replace: true }); } }; checkAuth(); }, [navigate]); // ✅ VÉRIFICATION CONTINUE (toutes les 5 secondes) useEffect(() => { const authInterval = setInterval(() => { if (!isAdminAuthenticated()) { console.log( "❌ [AdminOrders] Session admin expirée, redirection vers /login-admin/admin", ); navigate("/login-admin/admin", { replace: true }); } }, 5000); return () => clearInterval(authInterval); }, [navigate]); // ✅ Récupération des statistiques depuis le backend - AVEC CANCELLED useEffect(() => { const fetchStats = async () => { if (!isAdminAuthenticated()) { console.log("❌ [fetchStats] Admin non authentifié"); navigate("/login-admin/admin", { replace: true }); return; } try { console.log("📊 [STATS] Récupération des statistiques..."); // ✅ MODIFICATION: Récupérer AUSSI les commandes cancelled explicitement const [completedResponse, cancelledResponse] = await Promise.all([ getAllCommands("approved", ""), getAllCommands("cancelled", ""), // ✅ AJOUT CRITIQUE ]); // Récupérer toutes les commandes actives (sans cancelled ni approved) const allCommandsResponse = await getAllCommands("", ""); let pending = 0; let assigned = 0; let en_route = 0; let livre = 0; if ( allCommandsResponse.success && allCommandsResponse.commands ) { allCommandsResponse.commands.forEach( (cmd: CommandResponse) => { switch (cmd.status) { case "pending": pending++; break; case "assigned": assigned++; break; case "en_route": en_route++; break; case "livre": livre++; break; } }, ); } // ✅ MODIFICATION: Compter les cancelled depuis la requête dédiée const cancelledCount = cancelledResponse.success ? cancelledResponse.count || 0 : 0; const completedCount = completedResponse.success ? completedResponse.count || 0 : 0; setStats({ pending, assigned, en_route, livre, approved: completedCount, cancelled: cancelledCount, // ✅ UTILISÉ ICI total: pending + assigned + en_route + livre + completedCount + cancelledCount, // ✅ INCLUS dans le total }); console.log("✅ [STATS] Statistiques chargées:", { pending, assigned, en_route, livre, approved: completedCount, cancelled: cancelledCount, // ✅ LOG total: pending + assigned + en_route + livre + completedCount + cancelledCount, }); } catch (error) { console.error( "❌ [STATS] Erreur chargement statistiques:", error, ); if (error instanceof Error && error.message.includes("401")) { console.log( "🔓 [AdminOrders] Token invalide - Redirection", ); sessionStorage.removeItem("admin_token"); sessionStorage.removeItem("admin_username"); navigate("/login-admin/admin", { replace: true }); } } }; fetchStats(); }, [navigate]); // Récupération des commandes depuis le backend useEffect(() => { const fetchOrders = async () => { if (!isAdminAuthenticated()) { console.log("❌ [fetchOrders] Admin non authentifié"); navigate("/login-admin/admin", { replace: true }); return; } try { setLoading(true); const statusParam = filterStatus !== "all" ? filterStatus : ""; const response = await getAllCommands(statusParam, ""); if (!response.success || !response.commands) { console.error( "❌ Erreur récupération commandes:", response, ); setOrders([]); setFilteredOrders([]); return; } console.log( "✅ Commandes récupérées:", response.commands.length, ); const transformedOrders: Order[] = await Promise.all( response.commands.map(async (cmd: CommandResponse) => { let items: Array<{ name: string; quantity: number; price: number; }> = []; try { const itemsResponse = await getCommandItems(cmd.id); if (itemsResponse.success && itemsResponse.items) { items = itemsResponse.items.map( (item: CommandItem) => ({ name: item.produit, quantity: item.quantite, price: item.prix, }), ); } } catch (error) { console.warn( `⚠️ Erreur items pour commande ${cmd.id}:`, error, ); } let clientInfo = { name: cmd.username, phone: "", username: cmd.username, }; if (items.length > 0) { try { const itemsResponse = await getCommandItems( cmd.id, ); if ( itemsResponse.success && itemsResponse.items && itemsResponse.items.length > 0 ) { const firstItem = itemsResponse .items[0] as CommandItem; clientInfo = { name: `${firstItem.client_prenom} ${firstItem.client_nom}`.trim() || cmd.username, phone: firstItem.client_telephone || "", username: cmd.username, }; } } catch (error) { console.warn( `⚠️ Erreur infos client pour commande ${cmd.id}:`, error, ); } } return { id: cmd.id, orderNumber: `CMD-${cmd.id.toString().padStart(6, "0")}`, client: clientInfo, deliveryPerson: cmd.livreur_assign ? { name: cmd.livreur_assign, username: cmd.livreur_assign, } : undefined, items: items, total: cmd.total_prix, status: cmd.status as Order["status"], createdAt: cmd.created_at, deliveryAddress: cmd.adresse, }; }), ); setOrders(transformedOrders); setFilteredOrders(transformedOrders); } catch (error) { console.error("❌ Erreur chargement commandes:", error); if (error instanceof Error && error.message.includes("401")) { console.log( "🔓 [AdminOrders] Token invalide - Redirection", ); sessionStorage.removeItem("admin_token"); sessionStorage.removeItem("admin_username"); navigate("/login-admin/admin", { replace: true }); } setOrders([]); setFilteredOrders([]); } finally { setLoading(false); } }; fetchOrders(); }, [filterStatus, navigate]); // Filtrage et recherche useEffect(() => { let filtered = orders; if (searchTerm) { filtered = filtered.filter( (order) => order.orderNumber .toLowerCase() .includes(searchTerm.toLowerCase()) || order.client.name .toLowerCase() .includes(searchTerm.toLowerCase()) || order.deliveryAddress .toLowerCase() .includes(searchTerm.toLowerCase()), ); } setFilteredOrders(filtered); }, [searchTerm, orders]); const handleViewDeliverymanLocation = async (order: Order) => { if (!order.deliveryPerson) { alert("Aucun livreur assigné à cette commande"); return; } setLoadingLocation(true); setShowLocationModal(true); setDeliverymanLocation(null); try { console.log( "📍 [LOCATION] Récupération position pour commande:", order.id, ); const result = await getDeliverymanLocationForCommand(order.id); console.log("✅ [LOCATION] Réponse:", result); if (!result.success) { console.warn( "⚠️ [LOCATION] Position non disponible:", result.message, ); } setDeliverymanLocation(result); } catch (error) { console.error("❌ [LOCATION] Erreur récupération position:", error); setDeliverymanLocation({ success: false, error: "Erreur lors de la récupération de la position", message: error instanceof Error ? error.message : "Erreur inconnue", }); } finally { setLoadingLocation(false); } }; const handleRefreshLocation = async () => { if (!selectedOrder) return; await handleViewDeliverymanLocation(selectedOrder); }; const formatTimeAgo = (seconds: number): string => { if (seconds < 60) return `${seconds}s`; if (seconds < 3600) return `${Math.floor(seconds / 60)}min`; return `${Math.floor(seconds / 3600)}h`; }; const getStatusLabel = (status: string) => { const labels: { [key: string]: string } = { pending: "En attente", assigned: "Assignée", en_route: "En livraison", livre: "Livrée", approved: "Approuvée", cancelled: "Annulée", }; return labels[status] || status; }; const getStatusIcon = (status: string) => { switch (status) { case "pending": return ; case "assigned": return ; case "en_route": return ; case "livre": return ; case "approved": return ; case "cancelled": return ; default: return ; } }; const formatDate = (dateString: string) => { const date = new Date(dateString); return new Intl.DateTimeFormat("fr-FR", { day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit", }).format(date); }; const handleViewOrder = (order: Order) => { setSelectedOrder(order); setShowOrderDetails(true); }; const getStatusCount = (status: FilterStatus) => { if (status === "all") return stats.total; return stats[status as keyof typeof stats] || 0; }; if (loading) { return (

Chargement des commandes...

); } return (
{/* Header */}

Gestion des Commandes

{/* Stats rapides - ✅ AJOUT de la carte Cancelled */}
{getStatusCount("pending")} En attente
{getStatusCount("assigned")} Assignées
{getStatusCount("en_route")} En livraison
{getStatusCount("livre")} Livrées
{/* ✅ NOUVELLE CARTE - Commandes annulées */}
{getStatusCount("cancelled")} Annulées
{getStatusCount("approved")} Approuvées
{/* Barre de recherche et filtres */}
setSearchTerm(e.target.value)} />
{/* Filtres étendus - ✅ AJOUT du bouton Annulées */} {showFilters && (
{/* ✅ NOUVEAU BOUTON - Filtre Annulées */} {/* Bouton Approuvées */}
)} {/* Liste des commandes */}
{filteredOrders.length === 0 ? (

Aucune commande trouvée

Aucune commande ne correspond à vos critères de recherche

) : ( filteredOrders.map((order) => (
{order.orderNumber}
{getStatusIcon(order.status)} {getStatusLabel(order.status)}
Client {order.client.username}
{order.deliveryPerson && (
Livreur { order.deliveryPerson .name }
)}
Adresse {order.deliveryAddress}
Date {formatDate( order.createdAt, )}
Articles: {order.items.map((item, index) => ( {item.quantity}x {item.name} ))}
{order.total.toFixed(2)} €
)) )}
{/* ============================================ MODAL DÉTAILS DE COMMANDE ============================================ */} {showOrderDetails && selectedOrder && ( <>
{ setShowOrderDetails(false); setSelectedOrder(null); }} />

Détails de la commande

{/* Informations générales */}

Informations générales

Numéro de commande {selectedOrder.orderNumber}
Statut
{getStatusIcon( selectedOrder.status, )} {getStatusLabel( selectedOrder.status, )}
Date de création {formatDate( selectedOrder.createdAt, )}
{/* Informations client */}

Client

Nom {selectedOrder.client.name}
Username {selectedOrder.client.username}
{selectedOrder.client.phone && (
Téléphone {selectedOrder.client.phone}
)}
Adresse de livraison {selectedOrder.deliveryAddress}
{/* Informations livreur */} {selectedOrder.deliveryPerson && (

Livreur

Nom { selectedOrder .deliveryPerson.name }
Username { selectedOrder .deliveryPerson .username }
)} {/* Articles */}

Articles commandés

{selectedOrder.items.map( (item, index) => (
{item.name} x{item.quantity} {item.price.toFixed(2)}{" "} €
), )}
Total {selectedOrder.total.toFixed(2)}{" "} €
{selectedOrder.deliveryPerson && (selectedOrder.status === "en_route" || selectedOrder.status === "assigned") && ( )}
)} {/* ============================================ MODAL POSITION LIVREUR ============================================ */} {showLocationModal && ( <>
{ setShowLocationModal(false); setDeliverymanLocation(null); }} />

Position du livreur

{loadingLocation ? (

Récupération de la position...

) : deliverymanLocation && !deliverymanLocation.success ? (

Position non disponible

{deliverymanLocation.message || "Impossible de récupérer la position du livreur"}

{deliverymanLocation.command_info && (

Commande:{" "} { deliverymanLocation .command_info .command_id }

Client:{" "} { deliverymanLocation .command_info.client }

Statut:{" "} { deliverymanLocation .command_info.status }

{deliverymanLocation .command_info .deliveryman ? (

Livreur: {" "} { deliverymanLocation .command_info .deliveryman }

) : (

Livreur: {" "} Aucun livreur assigné

)}
)}
) : ( deliverymanLocation && deliverymanLocation.success && deliverymanLocation.data && (
{/* Informations commande */}

Informations commande

Commande # { deliverymanLocation .data .command_id }
Client { deliverymanLocation .data.client }
Statut
{getStatusIcon( deliverymanLocation .data .command_status, )} {getStatusLabel( deliverymanLocation .data .command_status, )}
{/* Informations livreur */}

Livreur

Username { deliverymanLocation .data .deliveryman .username }
Statut { deliverymanLocation .data .deliveryman .status }
Commande en cours # { deliverymanLocation .data .deliveryman .current_command }
File d'attente { deliverymanLocation .data .deliveryman .queue_size }{" "} commande(s)
{/* Position GPS */}

Position GPS

Latitude {deliverymanLocation.data.deliveryman.location.latitude.toFixed( 6, )}
Longitude {deliverymanLocation.data.deliveryman.location.longitude.toFixed( 6, )}
Dernière mise à jour:{" "} {formatTimeAgo( deliverymanLocation .data .deliveryman .location .last_update_ago, )}

{deliverymanLocation.data.deliveryman.location.latitude.toFixed( 6, )} ,{" "} {deliverymanLocation.data.deliveryman.location.longitude.toFixed( 6, )}

Position actuelle du livreur

{/* ETA */} {deliverymanLocation.data.eta .has_eta && (

Temps estimé d'arrivée

~ { deliverymanLocation .data.eta .minutes }{" "} min

Estimation basée sur la position actuelle

)}
) )}
{!loadingLocation && deliverymanLocation && deliverymanLocation.success && ( )}
)}
); } export default AdminOrders;