Files
projet_gestion_commande/frontend-prep/src/pages/AdminOrders/AdminOrders.tsx
T

1386 lines
66 KiB
TypeScript

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<Order[]>([]);
const [filteredOrders, setFilteredOrders] = useState<Order[]>([]);
const [loading, setLoading] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const [filterStatus, setFilterStatus] = useState<FilterStatus>("all");
const [showFilters, setShowFilters] = useState(false);
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
const [showOrderDetails, setShowOrderDetails] = useState(false);
const [deliverymanLocation, setDeliverymanLocation] =
useState<DeliverymanLocationResponse | null>(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 <Clock size={16} />;
case "assigned":
return <User size={16} />;
case "en_route":
return <Truck size={16} />;
case "livre":
return <Package size={16} />;
case "approved":
return <CheckCircle size={16} />;
case "cancelled":
return <XCircle size={16} />;
default:
return <AlertCircle size={16} />;
}
};
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 (
<AdminLayout>
<div className="admin-container">
<div className="loading-orders">
<p>Chargement des commandes...</p>
</div>
</div>
</AdminLayout>
);
}
return (
<AdminLayout>
<div className="admin-container">
{/* Header */}
<div className="orders-header">
<div className="header-content">
<h1>Gestion des Commandes</h1>
</div>
</div>
{/* Stats rapides - ✅ AJOUT de la carte Cancelled */}
<div className="orders-stats">
<div className="stat-item">
<div className="stat-icon orange">
<Clock size={20} />
</div>
<div className="stat-info">
<span className="stat-value">
{getStatusCount("pending")}
</span>
<span className="stat-label">En attente</span>
</div>
</div>
<div className="stat-item">
<div className="stat-icon blue">
<User size={20} />
</div>
<div className="stat-info">
<span className="stat-value">
{getStatusCount("assigned")}
</span>
<span className="stat-label">Assignées</span>
</div>
</div>
<div className="stat-item">
<div className="stat-icon purple">
<Truck size={20} />
</div>
<div className="stat-info">
<span className="stat-value">
{getStatusCount("en_route")}
</span>
<span className="stat-label">En livraison</span>
</div>
</div>
<div className="stat-item">
<div className="stat-icon green">
<CheckCircle size={20} />
</div>
<div className="stat-info">
<span className="stat-value">
{getStatusCount("livre")}
</span>
<span className="stat-label">Livrées</span>
</div>
</div>
{/* ✅ NOUVELLE CARTE - Commandes annulées */}
<div className="stat-item">
<div className="stat-icon red">
<XCircle size={20} />
</div>
<div className="stat-info">
<span className="stat-value">
{getStatusCount("cancelled")}
</span>
<span className="stat-label">Annulées</span>
</div>
</div>
<div className="stat-item">
<div className="stat-icon success">
<CheckCircle size={20} />
</div>
<div className="stat-info">
<span className="stat-value">
{getStatusCount("approved")}
</span>
<span className="stat-label">Approuvées</span>
</div>
</div>
</div>
{/* Barre de recherche et filtres */}
<div className="orders-controls">
<div className="search-bar">
<Search size={20} />
<input
type="text"
placeholder="Rechercher par numéro, client, adresse..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
</div>
<button
className={`filter-toggle ${showFilters ? "active" : ""}`}
onClick={() => setShowFilters(!showFilters)}
>
<Filter size={20} />
<span>Filtres</span>
<ChevronDown
size={16}
className={showFilters ? "rotated" : ""}
/>
</button>
</div>
{/* Filtres étendus - ✅ AJOUT du bouton Annulées */}
{showFilters && (
<div className="filters-panel">
<div className="filter-group">
<label>Statut</label>
<div className="filter-buttons">
<button
className={
filterStatus === "all" ? "active" : ""
}
onClick={() => setFilterStatus("all")}
>
Tous ({getStatusCount("all")})
</button>
<button
className={
filterStatus === "pending"
? "active"
: ""
}
onClick={() => setFilterStatus("pending")}
>
En attente ({getStatusCount("pending")})
</button>
<button
className={
filterStatus === "assigned"
? "active"
: ""
}
onClick={() => setFilterStatus("assigned")}
>
Assignées ({getStatusCount("assigned")})
</button>
<button
className={
filterStatus === "en_route"
? "active"
: ""
}
onClick={() => setFilterStatus("en_route")}
>
En livraison ({getStatusCount("en_route")})
</button>
<button
className={
filterStatus === "livre" ? "active" : ""
}
onClick={() => setFilterStatus("livre")}
>
Livrées ({getStatusCount("livre")})
</button>
{/* ✅ NOUVEAU BOUTON - Filtre Annulées */}
<button
className={
filterStatus === "cancelled"
? "active"
: ""
}
onClick={() => setFilterStatus("cancelled")}
>
Annulées ({getStatusCount("cancelled")})
</button>
{/* Bouton Approuvées */}
<button
className={
filterStatus === "approved"
? "active"
: ""
}
onClick={() => setFilterStatus("approved")}
>
Approuvées ({getStatusCount("approved")})
</button>
</div>
</div>
</div>
)}
{/* Liste des commandes */}
<div className="orders-list">
{filteredOrders.length === 0 ? (
<div className="empty-state">
<Package size={64} />
<h3>Aucune commande trouvée</h3>
<p>
Aucune commande ne correspond à vos critères de
recherche
</p>
</div>
) : (
filteredOrders.map((order) => (
<div key={order.id} className="order-card">
<div className="order-header">
<div className="order-number">
<Package size={20} />
<span>{order.orderNumber}</span>
</div>
<div
className={`order-status status-${order.status}`}
>
{getStatusIcon(order.status)}
<span>
{getStatusLabel(order.status)}
</span>
</div>
</div>
<div className="order-content">
<div className="order-info-grid">
<div className="info-item">
<User size={16} />
<div className="info-details">
<span className="info-label">
Client
</span>
<span className="info-value">
{order.client.username}
</span>
</div>
</div>
{order.deliveryPerson && (
<div className="info-item">
<Truck size={16} />
<div className="info-details">
<span className="info-label">
Livreur
</span>
<span className="info-value">
{
order.deliveryPerson
.name
}
</span>
</div>
</div>
)}
<div className="info-item">
<MapPin size={16} />
<div className="info-details">
<span className="info-label">
Adresse
</span>
<span className="info-value">
{order.deliveryAddress}
</span>
</div>
</div>
<div className="info-item">
<Clock size={16} />
<div className="info-details">
<span className="info-label">
Date
</span>
<span className="info-value">
{formatDate(
order.createdAt,
)}
</span>
</div>
</div>
</div>
<div className="order-items">
<span className="items-label">
Articles:
</span>
{order.items.map((item, index) => (
<span
key={index}
className="item-tag"
>
{item.quantity}x {item.name}
</span>
))}
</div>
</div>
<div className="order-footer">
<div className="order-total">
<span>{order.total.toFixed(2)} </span>
</div>
<button
className="view-details-button"
onClick={() => handleViewOrder(order)}
>
<Eye size={18} />
<span>Détails</span>
</button>
</div>
</div>
))
)}
</div>
{/* ============================================
MODAL DÉTAILS DE COMMANDE
============================================ */}
{showOrderDetails && selectedOrder && (
<>
<div
className="modal-overlay"
onClick={() => {
setShowOrderDetails(false);
setSelectedOrder(null);
}}
/>
<div className="order-details-modal">
<div className="modal-header">
<h2>Détails de la commande</h2>
<button
className="close-modal"
onClick={() => {
setShowOrderDetails(false);
setSelectedOrder(null);
}}
>
<XCircle size={24} />
</button>
</div>
<div className="modal-content">
{/* Informations générales */}
<div className="detail-section">
<h3>Informations générales</h3>
<div className="detail-grid">
<div className="detail-item">
<span className="detail-label">
Numéro de commande
</span>
<span className="detail-value">
{selectedOrder.orderNumber}
</span>
</div>
<div className="detail-item">
<span className="detail-label">
Statut
</span>
<span className="detail-value">
<div
className={`order-status status-${selectedOrder.status}`}
>
{getStatusIcon(
selectedOrder.status,
)}
<span>
{getStatusLabel(
selectedOrder.status,
)}
</span>
</div>
</span>
</div>
<div className="detail-item">
<span className="detail-label">
Date de création
</span>
<span className="detail-value">
{formatDate(
selectedOrder.createdAt,
)}
</span>
</div>
</div>
</div>
{/* Informations client */}
<div className="detail-section">
<h3>Client</h3>
<div className="detail-grid">
<div className="detail-item">
<span className="detail-label">
Nom
</span>
<span className="detail-value">
{selectedOrder.client.name}
</span>
</div>
<div className="detail-item">
<span className="detail-label">
Username
</span>
<span className="detail-value">
{selectedOrder.client.username}
</span>
</div>
{selectedOrder.client.phone && (
<div className="detail-item">
<span className="detail-label">
Téléphone
</span>
<span className="detail-value">
{selectedOrder.client.phone}
</span>
</div>
)}
<div className="detail-item full-width">
<span className="detail-label">
Adresse de livraison
</span>
<span className="detail-value">
{selectedOrder.deliveryAddress}
</span>
</div>
</div>
</div>
{/* Informations livreur */}
{selectedOrder.deliveryPerson && (
<div className="detail-section">
<h3>Livreur</h3>
<div className="detail-grid">
<div className="detail-item">
<span className="detail-label">
Nom
</span>
<span className="detail-value">
{
selectedOrder
.deliveryPerson.name
}
</span>
</div>
<div className="detail-item">
<span className="detail-label">
Username
</span>
<span className="detail-value">
{
selectedOrder
.deliveryPerson
.username
}
</span>
</div>
</div>
</div>
)}
{/* Articles */}
<div className="detail-section">
<h3>Articles commandés</h3>
<div className="items-list">
{selectedOrder.items.map(
(item, index) => (
<div
key={index}
className="item-row"
>
<span className="item-name">
{item.name}
</span>
<span className="item-quantity">
x{item.quantity}
</span>
<span className="item-price">
{item.price.toFixed(2)}{" "}
</span>
</div>
),
)}
<div className="items-total">
<span>Total</span>
<span className="total-amount">
{selectedOrder.total.toFixed(2)}{" "}
</span>
</div>
</div>
</div>
</div>
<div className="modal-actions">
<button
className="action-button secondary"
onClick={() => {
setShowOrderDetails(false);
setSelectedOrder(null);
}}
>
<XCircle size={18} />
<span>Fermer</span>
</button>
{selectedOrder.deliveryPerson &&
(selectedOrder.status === "en_route" ||
selectedOrder.status ===
"assigned") && (
<button
className="action-button primary"
onClick={() => {
handleViewDeliverymanLocation(
selectedOrder,
);
}}
>
<Navigation size={18} />
<span>Voir position livreur</span>
</button>
)}
</div>
</div>
</>
)}
{/* ============================================
MODAL POSITION LIVREUR
============================================ */}
{showLocationModal && (
<>
<div
className="location-modal-overlay"
onClick={() => {
setShowLocationModal(false);
setDeliverymanLocation(null);
}}
/>
<div className="location-modal">
<div className="modal-header">
<h2>Position du livreur</h2>
<button
className="close-modal"
onClick={() => {
setShowLocationModal(false);
setDeliverymanLocation(null);
}}
>
<XCircle size={24} />
</button>
</div>
<div className="modal-content">
{loadingLocation ? (
<div className="loading-location">
<RefreshCw
size={48}
className="spin-animation"
/>
<p>Récupération de la position...</p>
</div>
) : deliverymanLocation &&
!deliverymanLocation.success ? (
<div className="location-error">
<AlertCircle
size={64}
color="#ef4444"
/>
<h3>Position non disponible</h3>
<p>
{deliverymanLocation.message ||
"Impossible de récupérer la position du livreur"}
</p>
{deliverymanLocation.command_info && (
<div className="error-details">
<p>
<strong>Commande:</strong>{" "}
{
deliverymanLocation
.command_info
.command_id
}
</p>
<p>
<strong>Client:</strong>{" "}
{
deliverymanLocation
.command_info.client
}
</p>
<p>
<strong>Statut:</strong>{" "}
{
deliverymanLocation
.command_info.status
}
</p>
{deliverymanLocation
.command_info
.deliveryman ? (
<p>
<strong>
Livreur:
</strong>{" "}
{
deliverymanLocation
.command_info
.deliveryman
}
</p>
) : (
<p>
<strong>
Livreur:
</strong>{" "}
Aucun livreur assigné
</p>
)}
</div>
)}
</div>
) : (
deliverymanLocation &&
deliverymanLocation.success &&
deliverymanLocation.data && (
<div className="location-content">
{/* Informations commande */}
<div className="location-section">
<h3>
<Package size={20} />
Informations commande
</h3>
<div className="info-grid">
<div className="info-row">
<span className="label">
Commande
</span>
<span className="value">
#
{
deliverymanLocation
.data
.command_id
}
</span>
</div>
<div className="info-row">
<span className="label">
Client
</span>
<span className="value">
{
deliverymanLocation
.data.client
}
</span>
</div>
<div className="info-row">
<span className="label">
Statut
</span>
<span className="value">
<div
className={`order-status status-${deliverymanLocation.data.command_status}`}
>
{getStatusIcon(
deliverymanLocation
.data
.command_status,
)}
<span>
{getStatusLabel(
deliverymanLocation
.data
.command_status,
)}
</span>
</div>
</span>
</div>
</div>
</div>
{/* Informations livreur */}
<div className="location-section">
<h3>
<Truck size={20} />
Livreur
</h3>
<div className="info-grid">
<div className="info-row">
<span className="label">
Username
</span>
<span className="value">
{
deliverymanLocation
.data
.deliveryman
.username
}
</span>
</div>
<div className="info-row">
<span className="label">
Statut
</span>
<span className="value">
{
deliverymanLocation
.data
.deliveryman
.status
}
</span>
</div>
<div className="info-row">
<span className="label">
Commande en cours
</span>
<span className="value">
#
{
deliverymanLocation
.data
.deliveryman
.current_command
}
</span>
</div>
<div className="info-row">
<span className="label">
File d'attente
</span>
<span className="value">
{
deliverymanLocation
.data
.deliveryman
.queue_size
}{" "}
commande(s)
</span>
</div>
</div>
</div>
{/* Position GPS */}
<div className="location-section">
<h3>
<MapPin size={20} />
Position GPS
</h3>
<div className="gps-info">
<div className="gps-coordinates">
<div className="coordinate">
<span className="coord-label">
Latitude
</span>
<span className="coord-value">
{deliverymanLocation.data.deliveryman.location.latitude.toFixed(
6,
)}
</span>
</div>
<div className="coordinate">
<span className="coord-label">
Longitude
</span>
<span className="coord-value">
{deliverymanLocation.data.deliveryman.location.longitude.toFixed(
6,
)}
</span>
</div>
</div>
<div
className={`update-status ${deliverymanLocation.data.deliveryman.location.is_recent ? "recent" : "old"}`}
>
<Clock size={20} />
<span>
Dernière mise à
jour:{" "}
{formatTimeAgo(
deliverymanLocation
.data
.deliveryman
.location
.last_update_ago,
)}
</span>
</div>
<div className="map-placeholder">
<MapPin size={48} />
<p className="map-coords">
{deliverymanLocation.data.deliveryman.location.latitude.toFixed(
6,
)}
,{" "}
{deliverymanLocation.data.deliveryman.location.longitude.toFixed(
6,
)}
</p>
<p className="map-hint">
Position actuelle du
livreur
</p>
</div>
</div>
</div>
{/* ETA */}
{deliverymanLocation.data.eta
.has_eta && (
<div className="location-section eta-section">
<h3>
<Clock size={20} />
Temps estimé d'arrivée
</h3>
<div className="eta-value">
~
{
deliverymanLocation
.data.eta
.minutes
}{" "}
min
</div>
<p className="eta-note">
Estimation basée sur la
position actuelle
</p>
</div>
)}
</div>
)
)}
</div>
{!loadingLocation &&
deliverymanLocation &&
deliverymanLocation.success && (
<button
className="refresh-location-btn"
onClick={handleRefreshLocation}
disabled={loadingLocation}
>
<RefreshCw size={20} />
<span>Actualiser la position</span>
</button>
)}
</div>
</>
)}
</div>
</AdminLayout>
);
}
export default AdminOrders;