2653 lines
75 KiB
TypeScript
2653 lines
75 KiB
TypeScript
// ============================================
|
||
// 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,
|
||
};
|