the-tip-top-frontend/services/admin.service.ts
soufiane f20cf40fff feat: redesign admin panel with blanc cassé theme
- 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>
2025-12-03 19:43:14 +01:00

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>;
},
};