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>
299 lines
8.6 KiB
TypeScript
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>;
|
|
},
|
|
};
|