Files
projet_gestion_commande/frontend-prep/src/api/api_admin.ts
T

2653 lines
75 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================
// api/api_admin.ts - ADMIN API
// ============================================
// ✅ Gestion complète de l'authentification admin
// ✅ Utilise /api/v2/admin/* endpoints
// ✅ sessionStorage pour la persistance
import type { AdminResponse } from "./api_admin_types";
import type {
ProductListResponse,
ProductResponse,
Product,
DeleteResponse,
CreateProductData,
DeliveryPersonDetails,
DeliveryPersonStats,
} from "./api_admin_types";
const API_URL = "/api/v2";
// ============================================
// 🔐 TYPES - ADMIN
// ============================================
/**
* ✅ Admin User Interface
*/
export interface AdminUser {
id: number;
username: string;
role: string;
}
/**
* ✅ Admin Auth Response
*/
export interface AdminAuthResponse {
success: boolean;
message?: string;
access_token?: string;
token_type?: string;
expires_in?: number;
user?: AdminUser;
}
/**
* ✅ Client Response - Mise à jour avec champs optionnels
*/
export interface ClientResponse {
id: number;
username: string;
nom: string;
prenom: string;
telephone: string;
adresse?: string;
role?: string;
created_at?: string;
updated_at?: string;
is_active?: boolean;
command: number;
point: number; // Points weed/hash
points_zipette: number; // ✅ NOUVEAU: Points zipette
amende: number;
cancellations_count: number;
last_penalty_reason?: string;
total_orders?: number;
completed_deliveries?: number;
total_spent?: number;
}
export interface CommandResponse {
id: number;
username: string;
status: string;
adresse: string;
total_prix: number;
livreur_assign?: string | null; // peut être null
created_at: string; // ISO date string
updated_at: string; // ISO date string
}
export interface AllCommandResponse {
success: boolean;
commands: CommandResponse[];
count: number;
}
export interface Alert {
id: number;
username: string;
status: string;
created_at: string;
updated_at: string;
}
// ============================================
// 🔐 GESTION JWT ADMIN
// ============================================
/**
* ✅ Extraire username du JWT Admin
*/
export const extractAdminUsernameFromToken = (): string | null => {
try {
const token = sessionStorage.getItem("admin_token");
if (!token) {
console.log("❌ [ADMIN_JWT] Aucun token dans sessionStorage");
return null;
}
console.log("🔐 [ADMIN_JWT] Token trouvé, décodage...");
// JWT format: header.payload.signature
const parts = token.split(".");
if (parts.length !== 3) {
console.error("❌ [ADMIN_JWT] Format invalide");
return null;
}
// Décoder le payload (base64 -> JSON)
const base64Url = parts[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map(
(c) =>
"%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2),
)
.join(""),
);
const payload = JSON.parse(jsonPayload);
console.log("📋 [ADMIN_JWT] Payload:", payload);
// Extraire le username
const username = payload.username;
if (!username) {
console.error("❌ [ADMIN_JWT] Username non trouvé dans payload");
return null;
}
console.log("✅ [ADMIN_JWT] Username extrait:", username);
return username;
} catch (error) {
console.error("❌ [ADMIN_JWT] Erreur décodage:", error);
return null;
}
};
/**
* ✅ Extraire le rôle du JWT Admin
*/
export const extractAdminRoleFromToken = (): string | null => {
try {
const token = sessionStorage.getItem("admin_token");
if (!token) {
return null;
}
const parts = token.split(".");
if (parts.length !== 3) {
return null;
}
const base64Url = parts[1];
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
const jsonPayload = decodeURIComponent(
atob(base64)
.split("")
.map(
(c) =>
"%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2),
)
.join(""),
);
const payload = JSON.parse(jsonPayload);
const role = payload.role;
if (!role) {
console.error("❌ [ADMIN_JWT] Role non trouvé dans payload");
return null;
}
console.log("✅ [ADMIN_JWT] Role extrait:", role);
return role;
} catch (error) {
console.error("❌ [ADMIN_JWT] Erreur extraction role:", error);
return null;
}
};
/**
* ✅ Synchroniser sessionStorage avec JWT Admin
*/
export const syncAdminUsernameFromJWT = (): string | null => {
const jwtUsername = extractAdminUsernameFromToken();
if (!jwtUsername) {
console.log("❌ [ADMIN_SYNC] Impossible d'extraire username du JWT");
return null;
}
const storedUsername = sessionStorage.getItem("admin_username");
// Mismatch détecté
if (storedUsername && storedUsername !== jwtUsername) {
console.warn(`⚠️ [ADMIN_SYNC] MISMATCH!`);
console.warn(` Ancien: ${storedUsername}`);
console.warn(` JWT: ${jwtUsername}`);
sessionStorage.removeItem("admin_username");
}
// Toujours stocker le JWT username (source de vérité)
sessionStorage.setItem("admin_username", jwtUsername);
console.log(`✅ [ADMIN_SYNC] Username synchronisé: ${jwtUsername}`);
return jwtUsername;
};
/**
* ✅ Récupérer le username admin authentifié
*/
export const getAuthenticatedAdminUsername = (): string | null => {
return extractAdminUsernameFromToken();
};
/**
* ✅ Vérifier si admin est authentifié
*/
export const isAdminAuthenticated = (): boolean => {
const token = sessionStorage.getItem("admin_token");
const username = extractAdminUsernameFromToken();
const role = extractAdminRoleFromToken();
// Vérifier que c'est bien un admin
return !!(token && username && role === "admin");
};
/**
* ✅ Récupérer le token admin
*/
export const getAdminAuthToken = (): string | null => {
return sessionStorage.getItem("admin_token");
};
// ============================================
// 🔐 AUTHENTIFICATION ADMIN
// ============================================
/**
* ✅ REGISTER ADMIN
* POST /api/v2/admin/auth/register
*/
export const registerAdmin = async (
username: string,
password: string,
): Promise<AdminAuthResponse> => {
try {
console.log("📝 [ADMIN_REGISTER] Appel API...");
const response = await fetch(`${API_URL}/admin/auth/register`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (!response.ok) {
const errorData = await response.json();
console.error("❌ [ADMIN_REGISTER] Erreur API:", errorData);
return {
success: false,
message: errorData.error || "Erreur d'inscription admin",
};
}
const data = await response.json();
console.log("📋 [ADMIN_REGISTER] Réponse:", data);
// ✅ Vérifier access_token
if (!data.access_token) {
console.error("❌ [ADMIN_REGISTER] Pas de access_token");
return {
success: false,
message: "Token non reçu du serveur",
};
}
// ✅ Stocker en sessionStorage avec préfixe admin_
sessionStorage.setItem("admin_token", data.access_token);
console.log("✅ [ADMIN_REGISTER] Token admin stocké");
// ✅ Synchroniser username
const jwtUsername = syncAdminUsernameFromJWT();
if (!jwtUsername) {
console.warn(
"⚠️ [ADMIN_REGISTER] Impossible de synchroniser username",
);
return {
success: false,
message: "Erreur synchronisation JWT",
};
}
// ✅ Vérifier le rôle
const role = extractAdminRoleFromToken();
if (role !== "admin") {
console.error("❌ [ADMIN_REGISTER] Rôle incorrect:", role);
sessionStorage.removeItem("admin_token");
sessionStorage.removeItem("admin_username");
return {
success: false,
message: "Accès admin requis",
};
}
console.log(
`✅ [ADMIN_REGISTER] Admin créé et connecté: ${jwtUsername}`,
);
return {
success: true,
message: "Inscription admin réussie",
access_token: data.access_token,
token_type: data.token_type,
expires_in: data.expires_in,
user: data.user,
};
} catch (error) {
console.error("❌ [ADMIN_REGISTER] Erreur:", error);
return {
success: false,
message:
error instanceof Error ? error.message : "Erreur d'inscription",
};
}
};
/**
* ✅ LOGIN ADMIN
* POST /api/v2/admin/auth/login
*/
export const loginAdmin = async (
username: string,
password: string,
): Promise<AdminAuthResponse> => {
try {
console.log("🔐 [ADMIN_LOGIN] Appel API...");
const response = await fetch(`${API_URL}/admin/auth/login`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (!response.ok) {
const errorData = await response.json();
console.error("❌ [ADMIN_LOGIN] Erreur API:", errorData);
return {
success: false,
message: errorData.error || "Erreur de connexion admin",
};
}
const data = await response.json();
console.log("📋 [ADMIN_LOGIN] Réponse:", data);
// ✅ Vérifier access_token
if (!data.access_token) {
console.error("❌ [ADMIN_LOGIN] Pas de access_token");
return {
success: false,
message: "Token non reçu du serveur",
};
}
// ✅ Stocker en sessionStorage avec préfixe admin_
sessionStorage.setItem("admin_token", data.access_token);
console.log("✅ [ADMIN_LOGIN] Token admin stocké");
// ✅ Synchroniser username
const jwtUsername = syncAdminUsernameFromJWT();
if (!jwtUsername) {
console.warn(
"⚠️ [ADMIN_LOGIN] Impossible de synchroniser username",
);
return {
success: false,
message: "Erreur synchronisation JWT",
};
}
// ✅ Vérifier le rôle
const role = extractAdminRoleFromToken();
if (role !== "admin" && role !== "livreur" && role !== "cabine") {
console.error("❌ [ADMIN_LOGIN] Rôle incorrect:", role);
sessionStorage.removeItem("admin_token");
sessionStorage.removeItem("admin_username");
return {
success: false,
message: "Accès admin requis",
};
}
console.log(`✅ [ADMIN_LOGIN] Admin connecté: ${jwtUsername}`);
return {
success: true,
message: "Connexion admin réussie",
access_token: data.access_token,
token_type: data.token_type,
expires_in: data.expires_in,
user: data.user,
};
} catch (error) {
console.error("❌ [ADMIN_LOGIN] Erreur:", error);
return {
success: false,
message:
error instanceof Error ? error.message : "Erreur de connexion",
};
}
};
/**
* ✅ LOGOUT ADMIN
* POST /api/v2/admin/auth/logout
*/
export const logoutAdmin = async (): Promise<void> => {
const token = sessionStorage.getItem("admin_token");
if (token) {
try {
console.log("🚪 [ADMIN_LOGOUT] Appel API...");
await fetch(`${API_URL}/admin/auth/logout`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
});
console.log("✅ [ADMIN_LOGOUT] Déconnexion backend réussie");
} catch (error) {
console.warn("⚠️ [ADMIN_LOGOUT] Erreur backend:", error);
}
}
// Nettoyer sessionStorage
sessionStorage.removeItem("admin_token");
sessionStorage.removeItem("admin_username");
console.log("✅ [ADMIN_LOGOUT] sessionStorage nettoyé");
};
// ============================================
// 📊 FONCTIONS UTILITAIRES ADMIN
// ============================================
/**
* ✅ Vérifier si l'utilisateur actuel est admin
*/
export const checkAdminRole = (): boolean => {
const role = extractAdminRoleFromToken();
return role === "admin";
};
/**
* ✅ Récupérer les informations admin depuis le JWT
*/
export const getAdminInfo = (): { username: string; role: string } | null => {
const username = extractAdminUsernameFromToken();
const role = extractAdminRoleFromToken();
if (!username || !role) {
return null;
}
return { username, role };
};
/**
* ✅ Faire une requête authentifiée admin
*/
export const adminAuthenticatedFetch = async (
endpoint: string,
options: RequestInit = {},
): Promise<Response> => {
const token = getAdminAuthToken();
if (!token) {
throw new Error("Token admin non trouvé");
}
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
...options.headers,
};
return fetch(`${API_URL}${endpoint}`, {
...options,
headers,
});
};
/**
* ✅ Récupérer tous les utilisateurs (Admin)
*/
export const getAllUsers = async (): Promise<AdminResponse[]> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🔍 [GET_ALL_USERS] Appel API...");
const response = await fetch(`${API_URL}/admin/protected/all/users`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_ALL_USERS] Erreur API:", data);
throw new Error(data.error || "Erreur récupération utilisateurs");
}
console.log("✅ [GET_ALL_USERS] Réponse:", data);
// Retourner le tableau d'utilisateurs
return data.users || []; // ⬅️ Ajustez selon la structure de votre réponse API
} catch (error) {
console.error("❌ [GET_ALL_USERS] Erreur fetch:", error);
throw error;
}
};
export const getAllClients = async (): Promise<ClientResponse[]> => {
const token = sessionStorage.getItem("admin_token");
const response = await fetch(`${API_URL}/admin/protected/all/clients`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [CLIENTS] Erreur API:", data);
throw new Error(data.error || "Erreur récupération clients");
}
console.log("📋 [CLIENTS] Réponse:", data);
return data.clients; // ⬅️ retourne un tableau
};
export const getCommandCount = async (): Promise<number> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
const response = await fetch(`${API_URL}/admin/protected/orders`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [COMMANDS] Erreur API:", data);
throw new Error(data.error || "Erreur récupération commandes");
}
// Retourne juste le count
return data.count ?? 0;
} catch (error) {
console.error("❌ [COMMANDS] Erreur fetch:", error);
return 0;
}
};
export const getCommandCountCompleted = async (): Promise<number> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
const status = "approved";
const seenIds = new Set<number>();
let totalCount = 0;
try {
const response = await fetch(
`${API_URL}/admin/protected/orders?status=${status}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error(`❌ [COMMANDS] Erreur pour status=${status}:`, data);
}
if (data.commands && Array.isArray(data.commands)) {
data.commands.forEach((cmd: CommandResponse) => {
if (!seenIds.has(cmd.id)) {
seenIds.add(cmd.id);
totalCount++;
}
});
}
return totalCount;
} catch (error) {
console.error("❌ [COMMANDS] Erreur fetch:", error);
return 0;
}
};
export const getCommandCountInRoute = async (): Promise<number> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
const status = "en_route";
const seenIds = new Set<number>();
let totalCount = 0;
try {
const response = await fetch(
`${API_URL}/admin/protected/orders?status=${status}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error(`❌ [COMMANDS] Erreur pour status=${status}:`, data);
}
if (data.commands && Array.isArray(data.commands)) {
data.commands.forEach((cmd: CommandResponse) => {
if (!seenIds.has(cmd.id)) {
seenIds.add(cmd.id);
totalCount++;
}
});
}
return totalCount;
} catch (error) {
console.error("❌ [COMMANDS] Erreur fetch:", error);
return 0;
}
};
export const getCommandCountByStatus = async (): Promise<number> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
const status = ["pending"];
let totalCount = 0;
const seenIds = new Set<number>();
try {
for (const s of status) {
const response = await fetch(
`${API_URL}/admin/protected/orders?status=${s}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error(`❌ [COMMANDS] Erreur pour status=${s}:`, data);
continue;
}
if (data.commands && Array.isArray(data.commands)) {
data.commands.forEach((cmd: CommandResponse) => {
if (!seenIds.has(cmd.id)) {
seenIds.add(cmd.id);
totalCount++;
}
});
}
}
console.log(`✅ [COMMANDS] Total commandes en attente: ${totalCount}`);
return totalCount;
} catch (error) {
console.error("❌ [COMMANDS] Erreur fetch:", error);
return 0;
}
};
export const getAllCommands = async (status?: string, username?: string) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
let url = `${API_URL}/admin/protected/orders`;
const params = new URLSearchParams();
if (status) params.append("status", status);
if (username) params.append("username", username);
if (params.toString()) {
url += `?${params.toString()}`;
}
console.log("🔍 [GET_ALL_COMMANDS] Appel:", url);
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_ALL_COMMANDS] Erreur API:", data);
throw new Error(data.error || "Erreur récupération commandes");
}
console.log("✅ [GET_ALL_COMMANDS] Réponse:", data);
return {
success: true,
commands: data.commands || [],
count: data.count || 0,
};
} catch (error) {
console.error("❌ [GET_ALL_COMMANDS] Erreur fetch:", error);
return {
success: false,
commands: [],
count: 0,
};
}
};
/**
* ✅ Récupérer une commande par ID
*/
export const getCommandByID = async (commandId: number) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🔍 [GET_COMMAND_BY_ID] Appel:", commandId);
const response = await fetch(
`${API_URL}/admin/protected/orders/${commandId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_COMMAND_BY_ID] Erreur API:", data);
throw new Error(data.error || "Erreur récupération commande");
}
console.log("✅ [GET_COMMAND_BY_ID] Réponse:", data);
return {
success: true,
command: data.command,
};
} catch (error) {
console.error("❌ [GET_COMMAND_BY_ID] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Mettre à jour le statut d'une commande
*/
export const updateCommandStatus = async (
commandId: number,
newStatus: string,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📝 [UPDATE_STATUS] Appel:", commandId, newStatus);
const response = await fetch(
`${API_URL}/admin/protected/orders/${commandId}/status`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ status: newStatus }),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_STATUS] Erreur API:", data);
throw new Error(data.error || "Erreur mise à jour statut");
}
console.log("✅ [UPDATE_STATUS] Réponse:", data);
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [UPDATE_STATUS] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Assigner un livreur à une commande
*/
export const assignDeliveryPerson = async (
commandId: number,
deliveryPersonUsername: string,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log(
"👤 [ASSIGN_DELIVERY] Appel:",
commandId,
deliveryPersonUsername,
);
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${deliveryPersonUsername}/assign/${commandId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [ASSIGN_DELIVERY] Erreur API:", data);
throw new Error(data.error || "Erreur assignation livreur");
}
console.log("✅ [ASSIGN_DELIVERY] Réponse:", data);
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [ASSIGN_DELIVERY] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Récupérer les livreurs disponibles
*/
export const getAvailableDeliveryPersons = async () => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🔍 [GET_DELIVERY_PERSONS] Appel");
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_DELIVERY_PERSONS] Erreur API:", data);
throw new Error(data.error || "Erreur récupération livreurs");
}
console.log("✅ [GET_DELIVERY_PERSONS] Réponse:", data);
return {
success: true,
livreurs: data.livreurs || [],
count: data.count || 0,
};
} catch (error) {
console.error("❌ [GET_DELIVERY_PERSONS] Erreur fetch:", error);
return {
success: false,
livreurs: [],
count: 0,
};
}
};
/**
* ✅ Récupérer tous les livreurs avec détails complets et stats
* GET /api/v2/admin/protected/delivery-persons
* Enrichit automatiquement avec les détails et stats de chaque livreur
*/
export const getAllDeliveryPersonsWithDetails = async () => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
/**
* ✅ Helper: Parser le statut qui peut être une chaîne JSON
*/
const parseStatus = (status: any): "available" | "busy" | "offline" => {
if (!status) return "offline";
// Si c'est déjà une valeur valide
if (
status === "available" ||
status === "busy" ||
status === "offline"
) {
return status;
}
// Si c'est une chaîne JSON
if (typeof status === "string" && status.startsWith("{")) {
try {
const statusObj = JSON.parse(status);
const parsedStatus = statusObj.status;
if (
parsedStatus === "available" ||
parsedStatus === "busy" ||
parsedStatus === "offline"
) {
console.log(
`🔄 [PARSE_STATUS] Statut parsé: "${parsedStatus}"`,
);
return parsedStatus;
}
} catch (e) {
console.warn(`⚠️ [PARSE_STATUS] Impossible de parser:`, status);
}
}
// Par défaut
return "offline";
};
try {
console.log("🔍 [GET_ALL_DELIVERY_PERSONS] Appel");
// Récupérer la liste de base
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_ALL_DELIVERY_PERSONS] Erreur API:", data);
return {
success: false,
deliveryPersons: [],
count: 0,
stats: {
total: 0,
available: 0,
busy: 0,
offline: 0,
active_deliveries: 0,
},
};
}
console.log("✅ [GET_ALL_DELIVERY_PERSONS] Réponse de base:", data);
const livreurs = data.livreurs || [];
// Enrichir chaque livreur avec ses détails et stats
const enrichedDeliveryPersons = await Promise.all(
livreurs.map(async (livreur: any) => {
try {
// Récupérer les détails du livreur
const details = await getDeliveryPersonDetails(
livreur.username,
);
// Récupérer les stats du livreur
const stats = await getDeliveryPersonStats(
livreur.username,
);
// ✅ Parser le statut (gère les cas où c'est du JSON)
const parsedStatus = parseStatus(details.status);
return {
id: livreur.id,
username: livreur.username,
nom: livreur.nom || "",
prenom: livreur.prenom || "",
telephone: livreur.telephone || "",
status: parsedStatus,
location: {
latitude: details.location?.latitude || 0,
longitude: details.location?.longitude || 0,
last_update: details.location?.last_update
? new Date(
details.location.last_update * 1000,
).toISOString()
: new Date().toISOString(),
is_recent: details.location?.is_recent || false,
},
stats: {
total_deliveries: stats.total_deliveries || 0,
completed_today: stats.completed_deliveries || 0,
queue_size: details.queue_size || 0,
current_command: details.current_command || null,
},
};
} catch (error) {
console.warn(
`⚠️ [GET_ALL_DELIVERY_PERSONS] Erreur enrichissement ${livreur.username}:`,
error,
);
// Retourner des données de base en cas d'erreur
return {
id: livreur.id,
username: livreur.username,
nom: livreur.nom || "",
prenom: livreur.prenom || "",
telephone: livreur.telephone || "",
status: "offline" as const,
location: {
latitude: 0,
longitude: 0,
last_update: new Date().toISOString(),
is_recent: false,
},
stats: {
total_deliveries: 0,
completed_today: 0,
queue_size: 0,
current_command: null,
},
};
}
}),
);
// Calculer les stats globales
const globalStats = {
total: enrichedDeliveryPersons.length,
available: enrichedDeliveryPersons.filter(
(d) => d.status === "available",
).length,
busy: enrichedDeliveryPersons.filter((d) => d.status === "busy")
.length,
offline: enrichedDeliveryPersons.filter(
(d) => d.status === "offline",
).length,
active_deliveries: enrichedDeliveryPersons.filter(
(d) => d.stats.current_command !== null,
).length,
};
console.log(
"✅ [GET_ALL_DELIVERY_PERSONS] Données enrichies:",
enrichedDeliveryPersons.length,
"livreurs",
);
console.log(
"📊 [GET_ALL_DELIVERY_PERSONS] Stats globales:",
globalStats,
);
return {
success: true,
deliveryPersons: enrichedDeliveryPersons,
count: enrichedDeliveryPersons.length,
stats: globalStats,
};
} catch (error) {
console.error("❌ [GET_ALL_DELIVERY_PERSONS] Erreur fetch:", error);
return {
success: false,
deliveryPersons: [],
count: 0,
stats: {
total: 0,
available: 0,
busy: 0,
offline: 0,
active_deliveries: 0,
},
};
}
};
/**
* ✅ Valider/Approuver une commande (Admin)
*/
export const validateCommand = async (commandId: number) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("✅ [VALIDATE_COMMAND] Appel:", commandId);
const response = await fetch(
`${API_URL}/admin/protected/orders/${commandId}/force-validate`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [VALIDATE_COMMAND] Erreur API:", data);
throw new Error(data.error || "Erreur validation commande");
}
console.log("✅ [VALIDATE_COMMAND] Réponse:", data);
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [VALIDATE_COMMAND] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Mettre à jour l'adresse de livraison
*/
export const updateCommandAddress = async (
commandId: number,
newAddress: string,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📝 [UPDATE_ADDRESS] Appel:", commandId, newAddress);
const response = await fetch(
`${API_URL}/admin/protected/orders/${commandId}/address`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ delivery_address: newAddress }),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_ADDRESS] Erreur API:", data);
throw new Error(data.error || "Erreur mise à jour adresse");
}
console.log("✅ [UPDATE_ADDRESS] Réponse:", data);
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [UPDATE_ADDRESS] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Récupérer la position GPS du livreur assigné à une commande (Admin)
* GET /api/v2/admin/protected/commands/:id/deliveryman/location
*/
export const getDeliverymanLocationForCommand = async (commandId: number) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log(
"📍 [GET_DELIVERYMAN_LOCATION] Appel pour commande:",
commandId,
);
const response = await fetch(
`${API_URL}/admin/protected/commands/${commandId}/deliveryman/location`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_DELIVERYMAN_LOCATION] Erreur API:", data);
// Si pas de livreur assigné ou pas de position, retourner info utile
if (response.status === 404) {
return {
success: false,
error: data.error,
message: data.message,
command_info: data.command_info || null,
};
}
throw new Error(
data.error || "Erreur récupération position livreur",
);
}
console.log("✅ [GET_DELIVERYMAN_LOCATION] Réponse:", data);
return {
success: true,
data: {
command_id: data.data.command_id,
client: data.data.client,
command_status: data.data.command_status,
deliveryman: {
username: data.data.deliveryman.username,
status: data.data.deliveryman.status,
current_command: data.data.deliveryman.current_command,
queue_size: data.data.deliveryman.queue_size,
location: {
latitude: data.data.deliveryman.location.latitude,
longitude: data.data.deliveryman.location.longitude,
last_update: data.data.deliveryman.location.last_update,
last_update_ago:
data.data.deliveryman.location.last_update_ago,
is_recent: data.data.deliveryman.location.is_recent,
},
},
eta: {
minutes: data.data.eta.minutes,
has_eta: data.data.eta.has_eta,
set_at: data.data.eta.set_at,
},
},
requested_by: data.requested_by,
};
} catch (error) {
console.error("❌ [GET_DELIVERYMAN_LOCATION] Erreur fetch:", error);
throw error;
}
};
export const updateClientByAdmin = async (
clientId: number,
updates: {
username?: string;
password?: string;
nom?: string;
prenom?: string;
telephone?: string;
command?: number;
point?: number;
amende?: number;
},
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📝 [UPDATE_CLIENT_ADMIN] Appel:", clientId, updates);
const response = await fetch(
`${API_URL}/admin/protected/clients/${clientId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(updates),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_CLIENT_ADMIN] Erreur API:", data);
throw new Error(data.error || "Erreur mise à jour client");
}
console.log("✅ [UPDATE_CLIENT_ADMIN] Réponse:", data);
return {
success: true,
message: data.message,
client: data.client,
};
} catch (error) {
console.error("❌ [UPDATE_CLIENT_ADMIN] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Mettre à jour un profil USER (Admin/Cabine/Livreur) par Admin
* PUT /api/v2/admin/protected/users/:id
*
* @param userId - ID de l'utilisateur à modifier
* @param updates - Champs à mettre à jour
* @returns Réponse avec l'utilisateur mis à jour
*
* @example
* // Modifier le username
* await updateUserByAdmin(1, { username: "nouveau_username" });
*
* // Modifier le mot de passe
* await updateUserByAdmin(1, { password: "nouveau_password_secure" });
*
* // Changer le rôle
* await updateUserByAdmin(1, { role: "livreur" });
*/
export const updateUserByAdmin = async (
userId: number,
updates: {
username?: string;
password?: string;
role?: "admin" | "cabine" | "livreur";
},
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📝 [UPDATE_USER_ADMIN] Appel:", userId, updates);
const response = await fetch(
`${API_URL}/admin/protected/users/${userId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(updates),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_USER_ADMIN] Erreur API:", data);
throw new Error(data.error || "Erreur mise à jour utilisateur");
}
console.log("✅ [UPDATE_USER_ADMIN] Réponse:", data);
return {
success: true,
message: data.message,
user: data.user,
};
} catch (error) {
console.error("❌ [UPDATE_USER_ADMIN] Erreur fetch:", error);
throw error;
}
};
export const getAllProductsAdmin = async (): Promise<ProductListResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📦 [GET_ALL_PRODUCTS_ADMIN] Appel API...");
const response = await fetch(`${API_URL}/admin/protected/products`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_ALL_PRODUCTS_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur récupération produits",
};
}
console.log("✅ [GET_ALL_PRODUCTS_ADMIN] Réponse:", data);
return {
success: true,
data: data.data || [],
count: data.count || 0,
message: data.message,
};
} catch (error) {
console.error("❌ [GET_ALL_PRODUCTS_ADMIN] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
/**
* ✅ Récupérer un produit par ID (Admin)
* GET /api/v2/admin/protected/products/:id
*/
export const getProductByIdAdmin = async (
productId: number,
): Promise<ProductResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📦 [GET_PRODUCT_BY_ID_ADMIN] Appel API:", productId);
const response = await fetch(
`${API_URL}/admin/protected/products/${productId}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_PRODUCT_BY_ID_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur récupération produit",
};
}
console.log("✅ [GET_PRODUCT_BY_ID_ADMIN] Réponse:", data);
return {
success: true,
data: data.data,
message: data.message,
};
} catch (error) {
console.error("❌ [GET_PRODUCT_BY_ID_ADMIN] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
// ============================================
// CRÉATION DE PRODUIT
// ============================================
/**
* ✅ Créer un nouveau produit avec médias (Admin)
* POST /api/v2/admin/protected/products
*
* @param productData - Données du produit
* @param mediaFiles - Fichiers médias (images/vidéos)
*
* IMPORTANT: Envoie en multipart/form-data
*/
export const createProductAdmin = async (
productData: CreateProductData,
mediaFiles?: File[],
): Promise<ProductResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log(" [CREATE_PRODUCT_ADMIN] Préparation FormData...");
console.log("📋 [CREATE_PRODUCT_ADMIN] Données:", productData);
// ✅ Créer FormData pour multipart/form-data
const formData = new FormData();
// ✅ Ajouter les champs basiques
formData.append("name", productData.name);
formData.append("category", productData.category);
formData.append("description", productData.description);
formData.append("stock", productData.stock.toString());
console.log("📝 [CREATE_PRODUCT_ADMIN] Champs basiques ajoutés");
// ✅ Ajouter les prix
productData.prices.forEach((price, index) => {
formData.append(
`prices[${index}][quantity]`,
price.quantity.toString(),
);
formData.append(`prices[${index}][price]`, price.price.toString());
console.log(
`💰 [CREATE_PRODUCT_ADMIN] Prix[${index}]: ${price.quantity}g = ${price.price}€`,
);
});
// ✅ Ajouter les fichiers médias
if (mediaFiles && mediaFiles.length > 0) {
console.log(
`📁 [CREATE_PRODUCT_ADMIN] Ajout de ${mediaFiles.length} fichiers médias`,
);
mediaFiles.forEach((file, index) => {
formData.append("media", file);
console.log(
` 📄 [${index + 1}] ${file.name} (${file.type}, ${file.size} bytes)`,
);
});
} else {
console.log("⚠️ [CREATE_PRODUCT_ADMIN] Aucun média à ajouter");
}
// ✅ Log du contenu FormData (debug)
console.log("📦 [CREATE_PRODUCT_ADMIN] Contenu FormData:");
for (const [key, value] of formData.entries()) {
if (value instanceof File) {
console.log(` ${key}: File(${value.name})`);
} else {
console.log(` ${key}: ${value}`);
}
}
console.log("🚀 [CREATE_PRODUCT_ADMIN] Envoi requête...");
// ✅ Envoyer la requête (sans Content-Type - le navigateur le gère automatiquement)
const response = await fetch(`${API_URL}/admin/protected/products`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [CREATE_PRODUCT_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur création produit",
};
}
console.log("✅ [CREATE_PRODUCT_ADMIN] Produit créé:", data);
return {
success: true,
product: data.product,
message: data.message,
};
} catch (error) {
console.error("❌ [CREATE_PRODUCT_ADMIN] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
// ============================================
// ✏️ MODIFICATION DE PRODUIT
// ============================================
/**
* ✅ Mettre à jour un produit (Admin)
* PUT /api/v2/admin/protected/products/:id
*/
export const updateProductAdmin = async (
productId: number,
updates: Partial<Product>,
): Promise<ProductResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("✏️ [UPDATE_PRODUCT_ADMIN] Appel API:", productId, updates);
const response = await fetch(
`${API_URL}/admin/protected/products/update/${productId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(updates),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_PRODUCT_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur mise à jour produit",
};
}
console.log("✅ [UPDATE_PRODUCT_ADMIN] Produit mis à jour:", data);
return {
success: true,
product: data.product,
message: data.message,
};
} catch (error) {
console.error("❌ [UPDATE_PRODUCT_ADMIN] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
// ============================================
// 🗑️ SUPPRESSION DE PRODUIT
// ============================================
/**
* ✅ Supprimer un produit (Admin)
* DELETE /api/v2/admin/protected/products/:id
*/
export const deleteProductAdmin = async (
productId: number,
): Promise<ProductResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🗑️ [DELETE_PRODUCT_ADMIN] Appel API:", productId);
const response = await fetch(
`${API_URL}/admin/protected/products/${productId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [DELETE_PRODUCT_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur suppression produit",
};
}
console.log("✅ [DELETE_PRODUCT_ADMIN] Produit supprimé:", data);
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [DELETE_PRODUCT_ADMIN] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
// ============================================
// 📊 GESTION DES PRIX
// ============================================
/**
* ✅ Récupérer les prix d'un produit
* GET /api/v2/admin/protected/products/:id/prices
*/
export const getProductPricesAdmin = async (productId: number) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
const response = await fetch(
`${API_URL}/admin/protected/products/${productId}/prices`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_PRICES] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur récupération prix",
};
}
return {
success: true,
data: data.data || [],
count: data.count || 0,
};
} catch (error) {
console.error("❌ [GET_PRICES] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
/**
* ✅ Ajouter un prix à un produit
* POST /api/v2/admin/protected/products/:id/prices
*/
export const addProductPriceAdmin = async (
productId: number,
quantity: number,
price: number,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
const response = await fetch(
`${API_URL}/admin/protected/products/${productId}/prices`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ quantity, price }),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [ADD_PRICE] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur ajout prix",
};
}
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [ADD_PRICE] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
// ============================================
// 🎬 GESTION DES MÉDIAS
// ============================================
/**
* ✅ Uploader un média pour un produit existant
* POST /api/v2/admin/protected/products/:id/media
*/
export const uploadProductMediaAdmin = async (
productId: number,
file: File,
type: "image" | "video",
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📤 [UPLOAD_MEDIA] Upload fichier:", file.name);
const formData = new FormData();
formData.append("file", file);
formData.append("type", type);
const response = await fetch(
`${API_URL}/admin/protected/products/${productId}/media`,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
},
body: formData,
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPLOAD_MEDIA] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur upload média",
};
}
console.log("✅ [UPLOAD_MEDIA] Média uploadé:", data);
return {
success: true,
media: data.media,
message: data.message,
};
} catch (error) {
console.error("❌ [UPLOAD_MEDIA] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
/**
* ✅ Supprimer un média
* DELETE /api/v2/admin/protected/products/:productId/media/:mediaId
*/
export const deleteProductMediaAdmin = async (
productId: number,
mediaId: number,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🗑️ [DELETE_MEDIA] Suppression média:", mediaId);
const response = await fetch(
`${API_URL}/admin/protected/products/${productId}/media/${mediaId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [DELETE_MEDIA] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur suppression média",
};
}
console.log("✅ [DELETE_MEDIA] Média supprimé");
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [DELETE_MEDIA] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
export const getDeliveryPersonDetails = async (
username: string,
): Promise<DeliveryPersonDetails> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("👤 [GET_DELIVERY_DETAILS] Appel pour:", username);
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${username}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_DELIVERY_DETAILS] Erreur API:", data);
throw new Error(
data.error || "Erreur récupération détails livreur",
);
}
console.log("✅ [GET_DELIVERY_DETAILS] Réponse:", data);
return data.deliveryman;
} catch (error) {
console.error("❌ [GET_DELIVERY_DETAILS] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Modifier le statut d'un livreur (Admin)
* PUT /api/v2/admin/protected/delivery-persons/:username/status
*/
export const updateDeliveryPersonStatusAdmin = async (
username: string,
status: "available" | "busy" | "offline",
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📝 [UPDATE_DELIVERY_STATUS] Appel:", username, status);
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${username}/status`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ status }),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_DELIVERY_STATUS] Erreur API:", data);
throw new Error(data.error || "Erreur mise à jour statut livreur");
}
console.log("✅ [UPDATE_DELIVERY_STATUS] Réponse:", data);
return {
success: true,
message: data.message,
deliveryman: data.deliveryman,
};
} catch (error) {
console.error("❌ [UPDATE_DELIVERY_STATUS] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Récupérer les statistiques d'un livreur
* GET /api/v2/admin/protected/delivery-persons/:username/stats
*/
export const getDeliveryPersonStats = async (
username: string,
): Promise<DeliveryPersonStats> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📊 [GET_DELIVERY_STATS] Appel pour:", username);
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${username}/stats`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_DELIVERY_STATS] Erreur API:", data);
throw new Error(data.error || "Erreur récupération stats livreur");
}
console.log("✅ [GET_DELIVERY_STATS] Réponse:", data);
return data.stats;
} catch (error) {
console.error("❌ [GET_DELIVERY_STATS] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Récupérer l'historique des livraisons d'un livreur
* GET /api/v2/admin/protected/delivery-persons/:username/history
*/
export const getDeliveryPersonHistory = async (
username: string,
limit?: number,
offset?: number,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
let url = `${API_URL}/admin/protected/delivery-persons/${username}/history`;
const params = new URLSearchParams();
if (limit) params.append("limit", limit.toString());
if (offset) params.append("offset", offset.toString());
if (params.toString()) {
url += `?${params.toString()}`;
}
console.log("📜 [GET_DELIVERY_HISTORY] Appel:", url);
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_DELIVERY_HISTORY] Erreur API:", data);
throw new Error(data.error || "Erreur récupération historique");
}
console.log("✅ [GET_DELIVERY_HISTORY] Réponse:", data);
return {
success: true,
history: data.history || [],
count: data.count || 0,
total: data.total || 0,
};
} catch (error) {
console.error("❌ [GET_DELIVERY_HISTORY] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Modifier la position GPS d'un livreur (Admin)
* PUT /api/v2/admin/protected/delivery-persons/:username/location
*/
export const updateDeliveryPersonLocationAdmin = async (
username: string,
latitude: number,
longitude: number,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("📍 [UPDATE_DELIVERY_LOCATION] Appel:", username, {
latitude,
longitude,
});
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${username}/location`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ latitude, longitude }),
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [UPDATE_DELIVERY_LOCATION] Erreur API:", data);
throw new Error(data.error || "Erreur mise à jour position");
}
console.log("✅ [UPDATE_DELIVERY_LOCATION] Réponse:", data);
return {
success: true,
message: data.message,
location: data.location,
};
} catch (error) {
console.error("❌ [UPDATE_DELIVERY_LOCATION] Erreur fetch:", error);
throw error;
}
};
/**
* ✅ Retirer une commande de la queue d'un livreur
* DELETE /api/v2/admin/protected/delivery-persons/:username/queue/:command_id
*/
export const removeCommandFromQueue = async (
username: string,
commandId: number,
) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🗑️ [REMOVE_FROM_QUEUE] Appel:", username, commandId);
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${username}/queue/${commandId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [REMOVE_FROM_QUEUE] Erreur API:", data);
throw new Error(data.error || "Erreur suppression de la queue");
}
console.log("✅ [REMOVE_FROM_QUEUE] Réponse:", data);
return {
success: true,
message: data.message,
};
} catch (error) {
console.error("❌ [REMOVE_FROM_QUEUE] Erreur fetch:", error);
throw error;
}
};
export const getDeliveryPersonMapLinks = async (username: string) => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🗺️ [GET_MAP_LINKS] Appel pour:", username);
const response = await fetch(
`${API_URL}/admin/protected/delivery-persons/${username}/map-links`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [GET_MAP_LINKS] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur récupération liens GPS",
};
}
console.log("✅ [GET_MAP_LINKS] Réponse:", data);
return {
success: true,
deliveryman: data.deliveryman,
location: data.location,
map_links: data.map_links,
};
} catch (error) {
console.error("❌ [GET_MAP_LINKS] Erreur fetch:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Erreur réseau",
};
}
};
export const deleteUserAdmin = async (
userId: number,
): Promise<DeleteResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log(
"🗑️ [DELETE_USER_ADMIN] Tentative de suppression de l'utilisateur ID:",
userId,
);
const response = await fetch(
`${API_URL}/admin/protected/users/${userId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [DELETE_USER_ADMIN] Erreur API:", data);
return {
success: false,
error:
data.error ||
"Erreur lors de la suppression de l'utilisateur",
message: data.message,
};
}
console.log(
"✅ [DELETE_USER_ADMIN] Utilisateur supprimé avec succès:",
data,
);
return {
success: true,
message: data.message || "Utilisateur supprimé avec succès",
};
} catch (error) {
console.error("❌ [DELETE_USER_ADMIN] Erreur fetch:", error);
return {
success: false,
error:
error instanceof Error
? error.message
: "Erreur réseau inconnue",
};
}
};
export const deleteClientAdmin = async (
clientId: number,
): Promise<DeleteResponse> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log(
"🗑️ [DELETE_CLIENT_ADMIN] Tentative de suppression du client ID:",
clientId,
);
const response = await fetch(
`${API_URL}/admin/protected/clients/${clientId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [DELETE_CLIENT_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur lors de la suppression du client",
message: data.message,
};
}
console.log(
"✅ [DELETE_CLIENT_ADMIN] Client supprimé avec succès:",
data,
);
return {
success: true,
message: data.message || "Client supprimé avec succès",
};
} catch (error) {
console.error("❌ [DELETE_CLIENT_ADMIN] Erreur fetch:", error);
return {
success: false,
error:
error instanceof Error
? error.message
: "Erreur réseau inconnue",
};
}
};
export const deleteAlertAdmin = async (
alertId: number,
): Promise<{
success: boolean;
message?: string;
error?: string;
}> => {
const token = sessionStorage.getItem("admin_token");
if (!token) {
throw new Error("Token admin non trouvé");
}
try {
console.log("🗑️ [DELETE_ALERT_ADMIN] Suppression alerte:", alertId);
const response = await fetch(
`${API_URL}/admin/protected/delete/alerts/${alertId}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
},
);
const data = await response.json();
if (!response.ok) {
console.error("❌ [DELETE_ALERT_ADMIN] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur suppression alerte",
};
}
console.log("✅ [DELETE_ALERT_ADMIN] Alerte supprimée avec succès");
return {
success: true,
message: data.message || "Alerte supprimée avec succès",
};
} catch (error) {
console.error("❌ [DELETE_ALERT_ADMIN] Erreur fetch:", error);
return {
success: false,
error:
error instanceof Error
? error.message
: "Erreur réseau inconnue",
};
}
};
export const CreateUser = async (
username: string,
password: string,
role: string,
) => {
try {
const token = sessionStorage.getItem("admin_token");
const response = await fetch(`${API_URL}/admin/protected/users`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({ username, password, role }),
});
const data = await response.json();
if (!response.ok) {
console.error("❌ [CREATE_USER] Erreur API:", data);
return {
success: false,
error: data.error || "Erreur création utilisateur",
};
}
console.log("✅ [CREATE_USER] Utilisateur créé avec succès");
return {
success: true,
message: data.message || "Utilisateur créé avec succès",
};
} catch (error) {
console.error("❌ [CREATE_USER] Erreur fetch:", error);
return {
success: false,
error:
error instanceof Error
? error.message
: "Erreur réseau inconnue",
};
}
};
// ============================================
// 🔄 EXPORT PAR DÉFAUT
// ============================================
export default {
// Auth
registerAdmin,
loginAdmin,
logoutAdmin,
CreateUser,
// JWT Utils
extractAdminUsernameFromToken,
extractAdminRoleFromToken,
syncAdminUsernameFromJWT,
getAuthenticatedAdminUsername,
isAdminAuthenticated,
getAdminAuthToken,
// Users
getAllUsers,
getAllClients,
// Commands
getAllCommands,
getCommandByID,
getCommandCount,
getCommandCountCompleted,
getCommandCountInRoute,
getCommandCountByStatus,
updateCommandStatus,
assignDeliveryPerson,
getAvailableDeliveryPersons,
validateCommand,
updateCommandAddress,
getDeliverymanLocationForCommand, // ⭐ NOUVEAU
// Utils
checkAdminRole,
getAdminInfo,
adminAuthenticatedFetch,
// Update profile
updateClientByAdmin,
updateUserByAdmin,
// Produits
getAllProductsAdmin,
getProductByIdAdmin,
createProductAdmin,
updateProductAdmin,
deleteProductAdmin,
// Prix
getProductPricesAdmin,
addProductPriceAdmin,
// Médias
uploadProductMediaAdmin,
deleteProductMediaAdmin,
// Livreurs - ⭐ NOUVEAU
getAllDeliveryPersonsWithDetails,
getDeliveryPersonDetails,
getDeliveryPersonStats,
getDeliveryPersonHistory,
updateDeliveryPersonStatusAdmin,
updateDeliveryPersonLocationAdmin,
removeCommandFromQueue,
getDeliveryPersonMapLinks,
deleteClientAdmin,
deleteUserAdmin,
deleteAlertAdmin,
};