- Update sidebar and header with off-white (#faf8f5) background - Add ticket stats endpoint integration for global counts - Redesign tirages page with animation and improved layout - Add red accent color for admin avatar - Update various button styles and remove unnecessary elements 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
295 lines
8.6 KiB
TypeScript
295 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;
|
|
search?: string;
|
|
isActive?: boolean;
|
|
}
|
|
): Promise<PaginatedResponse<User>> => {
|
|
const params = new URLSearchParams({
|
|
page: page.toString(),
|
|
limit: limit.toString(),
|
|
...(filters?.role && { role: filters.role }),
|
|
...(filters?.search && { search: filters.search }),
|
|
...(filters?.isActive !== undefined && { isActive: filters.isActive.toString() }),
|
|
});
|
|
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,
|
|
isActive: user.is_active !== undefined ? user.is_active : (user.isActive !== undefined ? user.isActive : true),
|
|
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}`;
|
|
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
|
|
// Backend renvoie: { success, data, total, page, limit, totalPages, stats }
|
|
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,
|
|
stats: response.stats,
|
|
};
|
|
} else if (Array.isArray(response)) {
|
|
return {
|
|
data: response.map(transformTicket),
|
|
total: response.length,
|
|
page: page,
|
|
limit: limit,
|
|
totalPages: 1,
|
|
};
|
|
}
|
|
|
|
return response as PaginatedResponse<Ticket>;
|
|
},
|
|
};
|