import apiClient from "./client"; import { API_BASE_URL } from "./client"; import type { AuthResponse, ClientResponse, CommandResponse, Product, DeliveryPerson, DeliveryPersonsStats, Alert, } from "./types"; const V2 = `${API_BASE_URL}/api/v2`; const CABINE_URL = `${API_BASE_URL}/api/v1/cabine`; const V1_PUBLIC = `${API_BASE_URL}/api/v1`; export const loginAdmin = async ( username: string, password: string, ): Promise => { try { const { data } = await apiClient.post(`${V2}/admin/auth/login`, { username, password, }); if (!data.access_token) { return { success: false, message: "Token non reçu du serveur" }; } return { success: true, message: "Connexion réussie", access_token: data.access_token, token_type: data.token_type, expires_in: data.expires_in, user: data.user, }; } catch (error: any) { return { success: false, message: error.response?.data?.error || error.message || "Erreur de connexion", }; } }; export const logoutAdmin = async (): Promise => { try { await apiClient.post(`${V2}/admin/auth/logout`); } catch { /* ignore */ } }; export interface StatsSummary { total_orders: number; total_revenue: number; peak_weekday: string; top_product: string; avg_per_day: number; } export interface WeekdayStat { weekday: string; count: number; } export interface DayStat { day: string; label: string; count: number; } export interface DayRevenueStat { day: string; label: string; revenue: number; } export interface HourStat { hour: number; label: string; count: number; revenue: number; } export interface ProductStat { product_id: number; name: string; quantity: number; order_count: number; revenue: number; category: string; category_color: string; } export interface QuantityStat { quantity: number; order_count: number; total_sold: number; revenue: number; } export interface ProductQuantityBreakdown { product_id: number; name: string; category_color: string; total_orders: number; quantities: QuantityStat[]; } export interface AdminStats { summary: StatsSummary; by_weekday: WeekdayStat[]; by_day_30: DayStat[]; by_day_revenue: DayRevenueStat[]; by_hour: HourStat[]; top_products: ProductStat[]; by_quantity: ProductQuantityBreakdown[]; } export const getAdminStats = async (): Promise => { const { data } = await apiClient.get(`${V2}/admin/protected/stats`); return data; }; export const getAllClients = async (): Promise => { const { data } = await apiClient.get(`${V2}/admin/protected/all/clients`); return data.clients || []; }; export const getAllUsers = async () => { const { data } = await apiClient.get(`${V2}/admin/protected/all/users`); return data.users || []; }; export const updateClientByAdmin = async ( clientId: number, updates: Record, ) => { const { data } = await apiClient.put( `${V2}/admin/protected/clients/${clientId}`, updates, ); return { success: true, message: data.message, client: data.client }; }; export const updateUserByAdmin = async ( userId: number, updates: Record, ) => { const { data } = await apiClient.put( `${V2}/admin/protected/users/${userId}`, updates, ); return { success: true, message: data.message, user: data.user }; }; export const getAllCommands = async (status?: string, username?: string) => { let url = `${V2}/admin/protected/orders`; const params: string[] = []; if (status) params.push(`status=${status}`); if (username) params.push(`username=${username}`); if (params.length) url += `?${params.join("&")}`; const { data } = await apiClient.get(url); return { success: true, commands: data.commands || [], count: data.count || 0, }; }; export const getCommandByID = async (commandId: number) => { const { data } = await apiClient.get( `${V2}/admin/protected/orders/${commandId}`, ); return { success: true, command: data.command }; }; export const getCommandItems = async (commandId: number) => { const { data } = await apiClient.get( `${V2}/admin/protected/orders/${commandId}/items`, ); return { success: true, items: data.items || [], count: data.count || 0, command_info: data.command_info, client_info: data.client_info, }; }; export const exportApprovedCommandsCSV = async (): Promise => { const { data } = await apiClient.get( `${V2}/admin/protected/orders/export/csv`, { responseType: "text", }, ); return data as string; }; export const deleteCommand = async (commandId: number) => { const { data } = await apiClient.delete( `${V2}/admin/protected/orders/${commandId}`, ); return { success: true, message: data.message }; }; export const deleteCommandItem = async (commandId: number, itemId: number) => { const { data } = await apiClient.delete( `${V2}/admin/protected/orders/${commandId}/items/${itemId}`, ); return { success: true, message: data.message }; }; export const updateCommandStatus = async ( commandId: number, newStatus: string, ) => { const { data } = await apiClient.put( `${V2}/admin/protected/orders/${commandId}/status`, { status: newStatus }, ); return { success: true, message: data.message }; }; export const updateCommandAddress = async ( commandId: number, newAddress: string, ) => { const { data } = await apiClient.put( `${V2}/admin/protected/orders/${commandId}/address`, { delivery_address: newAddress }, ); return { success: true, message: data.message }; }; export const validateCommand = async (commandId: number) => { const { data } = await apiClient.post( `${V2}/admin/protected/orders/${commandId}/force-validate`, ); return { success: true, message: data.message }; }; export const proposeAddressChangeAdmin = async ( commandId: number, proposedAddress: string, ) => { const { data } = await apiClient.post( `${V2}/admin/protected/orders/${commandId}/propose-address`, { proposed_address: proposedAddress }, ); return { success: true, message: data.message }; }; export const notifyClientToDescend = async (commandId: number) => { const { data } = await apiClient.post( `${V2}/admin/protected/orders/${commandId}/notify-client`, ); return { success: true, message: data.message, client_username: data.client_username, }; }; export const confirmReceptionAdmin = async (commandId: number) => { const { data } = await apiClient.post( `${V2}/admin/protected/orders/${commandId}/confirm-reception`, ); return { success: true, message: data.message, points_earned: data.points_earned, client_username: data.client_username, }; }; // ============================================ export const getAvailableDeliveryPersons = async () => { try { const { data } = await apiClient.get( `${V2}/admin/protected/delivery-persons`, ); return { success: true, livreurs: data.livreurs || [], count: data.count || 0, }; } catch { return { success: false, livreurs: [], count: 0 }; } }; export const assignDeliveryPerson = async ( commandId: number, deliveryUsername: string, ) => { const { data } = await apiClient.post( `${V2}/admin/protected/delivery-persons/${deliveryUsername}/assign/${commandId}`, ); return { success: true, message: data.message }; }; export const getDeliveryPersonDetails = async (username: string) => { const { data } = await apiClient.get( `${V2}/admin/protected/delivery-persons/${username}`, ); return data.deliveryman || data; }; const parseStatus = (status: any): "available" | "busy" | "offline" => { if (!status) return "offline"; if (status === "available" || status === "busy" || status === "offline") return status; if (typeof status === "string" && status.startsWith("{")) { try { const obj = JSON.parse(status); if ( obj.status === "available" || obj.status === "busy" || obj.status === "offline" ) return obj.status; } catch { /* ignore */ } } return "offline"; }; export const getAllDeliveryPersonsWithDetails = async (): Promise<{ success: boolean; deliveryPersons: DeliveryPerson[]; count: number; stats: DeliveryPersonsStats; }> => { try { const { data } = await apiClient.get( `${V2}/admin/protected/delivery-persons`, ); const livreurs = data.livreurs || []; const enriched = await Promise.all( livreurs.map(async (l: any): Promise => { try { const details = await getDeliveryPersonDetails(l.username); const parsedStatus = parseStatus(details.status); const hasLoc = details.location?.latitude && details.location?.longitude; return { id: l.id, username: l.username, status: parsedStatus, location: { latitude: hasLoc ? details.location.latitude : 0, longitude: hasLoc ? 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: details.total_deliveries || 0, completed_today: details.completed_deliveries || 0, queue_size: details.queue_size || 0, current_command: details.current_command || null, }, }; } catch { return { id: l.id, username: l.username, status: "offline", 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, }, }; } }), ); const stats: DeliveryPersonsStats = { total: enriched.length, available: enriched.filter((d) => d.status === "available").length, busy: enriched.filter((d) => d.status === "busy").length, offline: enriched.filter((d) => d.status === "offline").length, active_deliveries: enriched.filter( (d) => d.stats.current_command !== null, ).length, }; return { success: true, deliveryPersons: enriched, count: enriched.length, stats, }; } catch { return { success: false, deliveryPersons: [], count: 0, stats: { total: 0, available: 0, busy: 0, offline: 0, active_deliveries: 0, }, }; } }; export const getDeliverymanLocationForCommand = async (commandId: number) => { try { const { data } = await apiClient.get( `${V2}/admin/protected/commands/${commandId}/deliveryman/location`, ); return { success: true, data: data.data }; } catch (error: any) { return { success: false, error: error.response?.data?.error || "Erreur", }; } }; export const getAllProductsAdmin = async (): Promise<{ success: boolean; data: Product[]; count: number; }> => { try { const { data } = await apiClient.get(`${V2}/admin/protected/products`); return { success: true, data: data.data || [], count: data.count || 0 }; } catch { return { success: false, data: [], count: 0 }; } }; export const createProductAdmin = async (formData: FormData) => { const { data } = await apiClient.post( `${V2}/admin/protected/products`, formData, { headers: { "Content-Type": "multipart/form-data" }, timeout: 120000, }, ); return { success: true, data: data.data, message: data.message }; }; export const updateProductAdmin = async ( productId: number, updates: Record, ) => { const { data } = await apiClient.put( `${V2}/admin/protected/products/${productId}`, updates, ); return { success: true, data: data.data, message: data.message }; }; export const deleteProductAdmin = async (productId: number) => { const { data } = await apiClient.delete( `${V2}/admin/protected/products/${productId}`, ); return { success: true, message: data.message }; }; // ============================================ // CREATION UTILISATEURS / CLIENTS // ============================================ export const createClientByAdmin = async (data: { username: string; password: string; nom: string; prenom: string; telephone: string; }) => { const { data: res } = await apiClient.post( `${V2}/admin/protected/clients`, data, ); return { success: true, message: res.message || "Client créé", client: res.client, }; }; export const createUserByAdmin = async (data: { username: string; password: string; role: string; }) => { const { data: res } = await apiClient.post( `${V2}/admin/protected/users`, data, ); return { success: true, message: res.message || "Utilisateur créé", user: res.user, }; }; // ============================================ // SUPPRESSION UTILISATEURS / CLIENTS // ============================================ export const deleteUserAdmin = async (userId: number) => { const { data } = await apiClient.delete( `${V2}/admin/protected/users/${userId}`, ); return { success: true, message: data.message }; }; export const deleteClientAdmin = async (clientId: number) => { const { data } = await apiClient.delete( `${V2}/admin/protected/clients/${clientId}`, ); return { success: true, message: data.message }; }; // ============================================ // PARRAINAGE ADMIN // ============================================ export const setClientParrain = async ( username: string, parrain: string, ): Promise<{ success: boolean; error?: string }> => { try { await apiClient.post( `${V2}/admin/protected/client/${username}/parrain/set`, { parrain }, ); return { success: true }; } catch (e: any) { return { success: false, error: e.response?.data?.error || "Erreur" }; } }; export const creditClientReferral = async ( username: string, amount: number, ): Promise<{ success: boolean; balance?: number; message?: string }> => { try { const { data } = await apiClient.post( `${V2}/admin/protected/client/${username}/referral/credit`, { amount }, ); return { success: true, balance: data.balance, message: data.message }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur crédit parrainage", }; } }; export const getClientReferralAdmin = async ( username: string, ): Promise<{ success: boolean; balance?: number; message?: string }> => { try { const { data } = await apiClient.get( `${V2}/admin/protected/client/${username}/referral`, ); return { success: true, balance: data.balance }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur récupération", }; } }; export const resetClientReferral = async ( username: string, ): Promise<{ success: boolean; message?: string }> => { try { const { data } = await apiClient.delete( `${V2}/admin/protected/client/${username}/referral/reset`, ); return { success: true, message: data.message }; } catch (error: any) { return { success: false, message: error.response?.data?.error || "Erreur reset parrainage", }; } }; export interface CancelledOrder { id: number; username: string; total_prix: number; created_at: string; updated_at: string; items_count: number; cancel_reason: string; cancellation?: { cancelled_at: string; cancelled_by: string; reason: string; }; } export const getClientCancelledOrders = async ( username: string, ): Promise<{ success: boolean; orders: CancelledOrder[] }> => { try { const { data } = await apiClient.get( `${V2}/admin/protected/orders/cancelled`, { params: { username } }, ); return { success: true, orders: data.data?.cancelled_orders ?? [], }; } catch { return { success: false, orders: [] }; } }; // ============================================ // AMENDES & POINTS CLIENT (Admin) // ============================================ export const applyClientPenalty = async ( username: string, points: number, reason: string, ): Promise<{ success: boolean; compensated?: boolean; error?: string }> => { try { const { data } = await apiClient.post(`${V2}/admin/protected/penalty`, { username, points, reason, }); return { success: true, compensated: data?.compensated === true }; } catch (e: any) { return { success: false, error: e.response?.data?.error || "Erreur" }; } }; export const resetClientPenalties = async ( username: string, resetCancellationsCount = false, ): Promise<{ success: boolean; error?: string }> => { try { await apiClient.post( `${V2}/admin/protected/client/${username}/penalties/reset`, { reset_cancellations_count: resetCancellationsCount }, ); return { success: true }; } catch (e: any) { return { success: false, error: e.response?.data?.error || "Erreur" }; } }; export const resetClientPoints = async ( username: string, pool = -1, ): Promise<{ success: boolean; error?: string }> => { try { await apiClient.post( `${V2}/admin/protected/client/${username}/point/reset`, { pool }, ); return { success: true }; } catch (e: any) { return { success: false, error: e.response?.data?.error || "Erreur" }; } }; export const addClientPoints = async ( username: string, poolKey: string, points: number, ): Promise<{ success: boolean; error?: string }> => { try { await apiClient.post( `${V2}/admin/protected/client/${username}/points/add`, { pool_key: poolKey, points }, ); return { success: true }; } catch (e: any) { return { success: false, error: e.response?.data?.error || "Erreur" }; } }; export const subtractClientPoints = async ( username: string, poolKey: string, points: number, ): Promise<{ success: boolean; error?: string }> => { try { await apiClient.post( `${V2}/admin/protected/client/${username}/points/subtract`, { pool_key: poolKey, points }, ); return { success: true }; } catch (e: any) { return { success: false, error: e.response?.data?.error || "Erreur" }; } }; // ============================================ // ALERTES // ============================================ export const getAdminAlerts = async (): Promise<{ success: boolean; alerts: Alert[]; count: number; }> => { try { const { data } = await apiClient.get(`${CABINE_URL}/alerts`); return { success: true, alerts: data.alerts || [], count: data.count || 0, }; } catch { return { success: false, alerts: [], count: 0 }; } }; // ============================================ // MEDIAS PRODUITS // ============================================ export const uploadProductMediaAdmin = async ( productId: number, fileUri: string, fileName: string, fileType: string, mediaType: "image" | "video", ) => { const fd = new FormData(); fd.append("file", { uri: fileUri, name: fileName, type: fileType } as any); fd.append("type", mediaType); const { data } = await apiClient.post( `${V2}/admin/protected/products/${productId}/media`, fd, { headers: { "Content-Type": "multipart/form-data" }, timeout: 120000 }, ); return { success: true, media: data.media, message: data.message }; }; export const deleteProductMediaAdmin = async ( productId: number, mediaId: number, ) => { const { data } = await apiClient.delete( `${V2}/admin/protected/products/${productId}/media/${mediaId}`, ); return { success: true, message: data.message }; }; export const activateProductPrice = async (priceId: number) => { const { data } = await apiClient.post( `${V2}/admin/protected/active/product/price/${priceId}`, ); return { success: true, message: data.message }; }; export const deactivateProductPrice = async (priceId: number) => { const { data } = await apiClient.post( `${V2}/admin/protected/desactive/product/price/${priceId}`, ); return { success: true, message: data.message }; }; // ============================================ // ADDRESSES // ============================================ export const addAddress = async ( invalidAddress: string, correctAddress: string, ): Promise<{ success: boolean; message: string }> => { const { data } = await apiClient.post(`${V2}/admin/protected/add/address`, { invalid_address: invalidAddress, correct_address: correctAddress, }); return { success: true, message: data.message }; }; export const deleteAddress = async ( invalidAddress: string, correctAddress: string, ): Promise<{ success: boolean; message: string }> => { const { data } = await apiClient.delete( `${V2}/admin/protected/delete/address`, { data: { invalid_address: invalidAddress, correct_address: correctAddress, }, }, ); return { success: true, message: data.message }; }; export const getAllAddresses = async (): Promise< { invalid_address: string; correct_address: string }[] > => { const { data } = await apiClient.get(`${V2}/admin/protected/addresses`); return (data.addresses ?? []).map((a: any) => ({ invalid_address: a.invalid_address ?? a.InvalidAddress ?? "", correct_address: a.correct_address ?? a.CorrectAddress ?? "", })); }; // ============================================ // STATS HELPERS // ============================================ export const getCommandCountByStatus = async ( status: string, ): Promise => { try { const result = await getAllCommands(status); return result.commands.length; } catch { return 0; } }; // ============================================ // CATÉGORIES // ============================================ export interface Category { id: number; name: string; color: string; is_coming_soon: boolean; created_at: string; } export const getCategories = async (): Promise => { try { const { data } = await apiClient.get(`${V1_PUBLIC}/categories`); return data.categories || []; } catch { return []; } }; export const createCategoryAdmin = async ( name: string, color: string, isComingSoon: boolean = false, ): Promise<{ success: boolean; category?: Category; error?: string }> => { try { const { data } = await apiClient.post( `${V2}/admin/protected/categories`, { name, color, is_coming_soon: isComingSoon }, ); return { success: true, category: data.category }; } catch (error: any) { return { success: false, error: error.response?.data?.error || "Erreur", }; } }; export const updateCategoryAdmin = async ( id: number, name: string, color: string, isComingSoon: boolean = false, ): Promise<{ success: boolean; category?: Category; error?: string }> => { try { const { data } = await apiClient.put( `${V2}/admin/protected/categories/${id}`, { name, color, is_coming_soon: isComingSoon }, ); return { success: true, category: data.category }; } catch (error: any) { return { success: false, error: error.response?.data?.error || "Erreur", }; } }; export const deleteCategoryAdmin = async ( id: number, ): Promise<{ success: boolean; error?: string }> => { try { await apiClient.delete(`${V2}/admin/protected/categories/${id}`); return { success: true }; } catch (error: any) { return { success: false, error: error.response?.data?.error || "Erreur", }; } }; // ============================================ // PARAMÈTRES GLOBAUX // ============================================ export interface PointsTier { min: number; max: number; // 0 = illimité points: number; } export interface RewardCategoryConfig { category: string; all_products: boolean; product_ids: number[]; amount: number; } export interface PointsReward { threshold: number; type: "free_product" | "half_price_product" | "custom"; description: string; category_configs: RewardCategoryConfig[]; } export interface PointsPool { key: string; name: string; categories: string[]; tiers: PointsTier[]; } export interface PostalZone { name: string; min_amount: number; codes: string[]; } export interface DaySchedule { enabled: boolean; open_time: string; // "09:00" close_time: string; // "20:00" } export interface DeliverySchedule { monday: DaySchedule; tuesday: DaySchedule; wednesday: DaySchedule; thursday: DaySchedule; friday: DaySchedule; saturday: DaySchedule; sunday: DaySchedule; } export const DEFAULT_POSTAL_ZONES: PostalZone[] = [ { name: "Zone 30€", min_amount: 30, codes: ["44000", "44100", "44200", "44300"], }, { name: "Zone 50€", min_amount: 50, codes: [ "44400", "44880", "44120", "44230", "44115", "44980", "44470", "44240", "44700", "44800", "44340", "44620", "44830", ], }, { name: "Zone 100€", min_amount: 100, codes: ["44860", "44220", "44118", "44710", "44690", "44119"], }, ]; export const DEFAULT_DAY: DaySchedule = { enabled: true, open_time: "09:00", close_time: "20:00", }; export const DEFAULT_DELIVERY_SCHEDULE: DeliverySchedule = { monday: { ...DEFAULT_DAY }, tuesday: { ...DEFAULT_DAY }, wednesday: { ...DEFAULT_DAY }, thursday: { ...DEFAULT_DAY }, friday: { ...DEFAULT_DAY }, saturday: { ...DEFAULT_DAY }, sunday: { ...DEFAULT_DAY }, }; export interface CategoryRoute { deliveryman_username: string; categories: string[]; } export interface DeliveryModeConfig { mode: "single" | "category_based"; category_routes: CategoryRoute[]; } export interface PenaltyTier { min_cancel: number; amount: number; } export interface AppSettings { penalties_enabled: boolean; show_amende_score: boolean; penalty_tiers: PenaltyTier[]; points_enabled: boolean; points_pools: PointsPool[]; points_reward?: PointsReward | null; referral_enabled: boolean; delivery_schedule: DeliverySchedule; postal_zones: PostalZone[]; crypto_payment_enabled: boolean; crypto_only: boolean; nowpayments_api_key: string; nowpayments_ipn_secret: string; nowpayments_currencies: string[]; telegram_bot_token: string; telegram_bot_username: string; telegram_notifications_enabled: boolean; telegram_2fa_enabled: boolean; telegram_notif_bots: TelegramBotConfig[]; delivery_mode: DeliveryModeConfig; shop_name: string; contact_telegram: string; } export type TelegramBotConfig = { token: string; username: string; }; export const getSettings = async (): Promise<{ success: boolean; settings?: AppSettings; error?: string; }> => { try { const { data } = await apiClient.get(`${V2}/admin/protected/settings`); return { success: true, settings: data.settings }; } catch (error: any) { return { success: false, error: error.response?.data?.error || "Erreur", }; } }; export interface AppNotification { command_id: number; type: string; message: string; created_at: string; read: boolean; } export const getAdminNotifications = async (): Promise<{ notifications: AppNotification[]; unread_count: number; }> => { const { data } = await apiClient.get(`${V2}/admin/protected/notifications`); return data; }; export const markAdminNotificationsRead = async (): Promise => { await apiClient.post(`${V2}/admin/protected/notifications/read`); }; // ============================================ // TELEGRAM — ADMIN // ============================================ export const getAdminTelegramStatus = async (): Promise<{ linked: boolean; enabled: boolean; }> => { try { const { data } = await apiClient.get( `${V2}/admin/protected/telegram/status`, ); return data; } catch { return { linked: false, enabled: false }; } }; export const generateAdminLinkToken = async (): Promise<{ link_url?: string; error?: string; }> => { try { const { data } = await apiClient.post( `${V2}/admin/protected/telegram/link-token`, ); return data; } catch (e: any) { return { error: e?.response?.data?.error || "Erreur" }; } }; export const unlinkAdminTelegram = async (): Promise => { try { await apiClient.delete(`${V2}/admin/protected/telegram/unlink`); } catch { /* ignore */ } }; export const updateSettings = async ( settings: AppSettings, ): Promise<{ success: boolean; settings?: AppSettings; error?: string }> => { try { const { data } = await apiClient.put( `${V2}/admin/protected/settings`, settings, ); return { success: true, settings: data.settings }; } catch (error: any) { return { success: false, error: error.response?.data?.error || "Erreur", }; } };