the-tip-top-frontend/services/admin.service.ts
soufiane 3456657ae5 feat: add user details page and getUserById service method
Created comprehensive user details page at /admin/utilisateurs/[id] that
displays contact information, personal data, account status, and ticket
statistics. Added getUserById method to admin service to fetch detailed
user information from the backend API. Fixes the "Détails" button that
was previously navigating to a non-existent page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 15:09:00 +01:00

299 lines
8.6 KiB
TypeScript

import { api } from './api';
import {
AdminStatistics,
Ticket,
User,
Prize,
CreatePrizeData,
UpdatePrizeData,
CreateEmployeeData,
UpdateUserData,
ApiResponse,
PaginatedResponse
} from '@/types';
const API_ENDPOINTS = {
STATISTICS: '/admin/statistics',
TICKETS: '/admin/tickets',
USERS: '/admin/users',
PRIZES: '/admin/prizes',
EMPLOYEES: '/admin/employees',
};
export const adminService = {
// ==================== STATISTIQUES ====================
/**
* Récupérer les statistiques globales
*/
getStatistics: async (): Promise<AdminStatistics> => {
const response = await api.get<ApiResponse<AdminStatistics>>(
API_ENDPOINTS.STATISTICS
);
return response.data!;
},
// ==================== GESTION DES PRIX ====================
/**
* Récupérer tous les prix
*/
getAllPrizes: async (): Promise<Prize[]> => {
const response = await api.get<ApiResponse<any[]>>(
API_ENDPOINTS.PRIZES
);
// Transformer snake_case en camelCase
const prizes = response.data?.map((prize: any) => ({
id: prize.id,
name: prize.name,
type: prize.type,
description: prize.description,
value: prize.value,
probability: prize.probability,
stock: prize.stock,
initialStock: prize.initial_stock || prize.initialStock,
ticketsUsed: prize.tickets_used || prize.ticketsUsed || 0,
isActive: prize.is_active !== undefined ? prize.is_active : prize.isActive,
imageUrl: prize.image_url || prize.imageUrl,
createdAt: prize.created_at || prize.createdAt,
updatedAt: prize.updated_at || prize.updatedAt,
})) || [];
return prizes;
},
/**
* Créer un nouveau prix
*/
createPrize: async (data: CreatePrizeData): Promise<Prize> => {
const response = await api.post<ApiResponse<Prize>>(
API_ENDPOINTS.PRIZES,
data
);
return response.data!;
},
/**
* Modifier un prix
*/
updatePrize: async (prizeId: string, data: UpdatePrizeData): Promise<Prize> => {
const response = await api.put<ApiResponse<Prize>>(
`${API_ENDPOINTS.PRIZES}/${prizeId}`,
data
);
return response.data!;
},
/**
* Supprimer (désactiver) un prix
*/
deletePrize: async (prizeId: string): Promise<void> => {
await api.delete<ApiResponse<void>>(
`${API_ENDPOINTS.PRIZES}/${prizeId}`
);
},
// ==================== GESTION DES UTILISATEURS ====================
/**
* Récupérer un utilisateur par ID
*/
getUserById: async (userId: string): Promise<User> => {
const response = await api.get<ApiResponse<any>>(
`${API_ENDPOINTS.USERS}/${userId}`
);
// Convert snake_case to camelCase
const user = response.data;
return {
id: user.id,
email: user.email,
firstName: user.first_name || user.firstName,
lastName: user.last_name || user.lastName,
phone: user.phone,
address: user.address,
city: user.city,
postalCode: user.postal_code || user.postalCode,
role: user.role,
isVerified: user.is_verified !== undefined ? user.is_verified : user.isVerified,
createdAt: user.created_at || user.createdAt,
ticketsCount: user.tickets_count || user.ticketsCount || 0,
pendingTickets: user.pending_tickets || user.pendingTickets || 0,
claimedTickets: user.claimed_tickets || user.claimedTickets || 0,
dateOfBirth: user.date_of_birth || user.dateOfBirth,
gender: user.gender,
} as User;
},
/**
* Récupérer tous les utilisateurs (paginé)
*/
getAllUsers: async (
page = 1,
limit = 20,
filters?: {
role?: string;
}
): Promise<PaginatedResponse<User>> => {
const params = new URLSearchParams({
page: page.toString(),
limit: limit.toString(),
...(filters?.role && { role: filters.role }),
});
const response = await api.get<any>(
`${API_ENDPOINTS.USERS}?${params}`
);
// Adapter la réponse du backend au format attendu par le frontend
// Backend: { success: true, data: { users: [...], pagination: {...} } }
// Frontend attend: { data: [...], total, page, limit, totalPages }
if (response.data && response.data.users) {
// Convertir snake_case en camelCase pour chaque utilisateur
const users = response.data.users.map((user: any) => ({
id: user.id,
email: user.email,
firstName: user.first_name || user.firstName,
lastName: user.last_name || user.lastName,
phone: user.phone,
address: user.address,
city: user.city,
postalCode: user.postal_code || user.postalCode,
role: user.role,
isVerified: user.is_verified !== undefined ? user.is_verified : user.isVerified,
createdAt: user.created_at || user.createdAt,
ticketsCount: user.tickets_count || user.ticketsCount || 0,
}));
return {
data: users,
total: response.data.pagination.total,
page: response.data.pagination.page,
limit: response.data.pagination.limit,
totalPages: response.data.pagination.totalPages,
};
}
// Fallback si la structure est différente
return response.data || { data: [], total: 0, page: 1, limit: 20, totalPages: 0 };
},
/**
* Créer un nouvel employé
*/
createEmployee: async (data: CreateEmployeeData): Promise<User> => {
const response = await api.post<ApiResponse<User>>(
API_ENDPOINTS.EMPLOYEES,
data
);
return response.data!;
},
/**
* Modifier un utilisateur
*/
updateUser: async (userId: string, data: UpdateUserData): Promise<User> => {
const response = await api.put<ApiResponse<User>>(
`${API_ENDPOINTS.USERS}/${userId}`,
data
);
return response.data!;
},
/**
* Supprimer un utilisateur
*/
deleteUser: async (userId: string): Promise<void> => {
await api.delete<ApiResponse<void>>(
`${API_ENDPOINTS.USERS}/${userId}`
);
},
// ==================== GESTION DES TICKETS ====================
/**
* Récupérer tous les tickets (paginé avec filtres)
*/
getAllTickets: async (
page = 1,
limit = 20,
filters?: {
status?: string;
userId?: string;
prizeType?: string;
}
): Promise<PaginatedResponse<Ticket>> => {
const params = new URLSearchParams();
params.append('page', page.toString());
params.append('limit', limit.toString());
if (filters?.status) {
params.append('status', filters.status);
}
if (filters?.userId) {
params.append('userId', filters.userId);
}
if (filters?.prizeType) {
params.append('prizeType', filters.prizeType);
}
const url = `${API_ENDPOINTS.TICKETS}?${params}`;
console.log('🔍 Frontend - URL appelée:', url);
console.log('🔍 Frontend - Filters:', filters);
const response = await api.get<any>(url);
// Transformer les données du backend (format plat) en format attendu par le frontend
const transformTicket = (ticket: any): Ticket => ({
id: ticket.id,
code: ticket.code,
status: ticket.status,
playedAt: ticket.played_at || ticket.playedAt,
claimedAt: ticket.claimed_at || ticket.claimedAt,
validatedAt: ticket.validated_at || ticket.validatedAt,
createdAt: ticket.created_at || ticket.createdAt,
// Transformer les données utilisateur
user: ticket.user_email ? {
email: ticket.user_email,
firstName: ticket.user_name?.split(' ')[0] || '',
lastName: ticket.user_name?.split(' ').slice(1).join(' ') || '',
} as any : undefined,
// Transformer les données prize
prize: ticket.prize_name ? {
name: ticket.prize_name,
type: ticket.prize_type,
value: ticket.prize_value || '0',
} as any : undefined,
});
// Gérer différents formats de réponse de l'API
if (response.data && response.data.data) {
return {
data: response.data.data.map(transformTicket),
total: response.data.total,
page: response.data.page,
limit: response.data.limit,
totalPages: response.data.totalPages,
};
} else if (response.data && Array.isArray(response.data)) {
return {
data: response.data.map(transformTicket),
total: response.total || response.data.length,
page: response.page || page,
limit: response.limit || limit,
totalPages: response.totalPages || 1,
};
} else if (Array.isArray(response)) {
return {
data: response.map(transformTicket),
total: response.length,
page: page,
limit: limit,
totalPages: 1,
};
}
return response as PaginatedResponse<Ticket>;
},
};