diff --git a/components/admin/TicketManagement.tsx b/components/admin/TicketManagement.tsx index 13b82f9..196895b 100644 --- a/components/admin/TicketManagement.tsx +++ b/components/admin/TicketManagement.tsx @@ -1,9 +1,10 @@ 'use client'; -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useMemo } from 'react'; import { adminService } from '@/services/admin.service'; import { Ticket } from '@/types'; import { StatusBadge, Pagination } from '@/components/ui'; +import { Search, RefreshCw, X, Ticket as TicketIcon, Clock, CheckCircle, XCircle, Gift, Filter } from 'lucide-react'; export default function TicketManagement() { const [tickets, setTickets] = useState([]); @@ -75,166 +76,217 @@ export default function TicketManagement() { loadTickets(); }, [loadTickets]); + // Calculer les stats des tickets + const ticketStats = useMemo(() => { + const stats = { pending: 0, claimed: 0, rejected: 0 }; + tickets.forEach(ticket => { + if (ticket.status === 'PENDING') stats.pending++; + else if (ticket.status === 'CLAIMED') stats.claimed++; + else if (ticket.status === 'REJECTED') stats.rejected++; + }); + return stats; + }, [tickets]); + + // Fonction pour obtenir les initiales + const getInitials = (firstName?: string, lastName?: string) => { + const f = firstName?.charAt(0) || ''; + const l = lastName?.charAt(0) || ''; + return (f + l).toUpperCase() || '?'; + }; if (loading && tickets.length === 0) { - return
Chargement des tickets...
; + return ( +
+
+
+

Chargement des tickets...

+
+
+ ); } return (
-
-

Gestion des Tickets

- + {/* Section filtres avec gradient */} +
+
+
+ + + + + {(filterStatus || filterPrizeType) && ( + + )} +
+ +
{error && ( -
-

Erreur de chargement:

-

{error}

- - {error.includes('401') && ( -
-

✅ Le header Authorization est bien envoyé

-

❌ Mais votre token est invalide ou a expiré

- +
+
+
+
- )} +
+

Erreur de chargement:

+

{error}

- {error.includes('403') && ( -
-

Vous n'avez pas le rôle ADMIN requis.

- - 🩺 Voir le diagnostic complet - -
- )} + {error.includes('401') && ( +
+

Votre session a expiré ou votre token est invalide.

+ +
+ )} - {!error.includes('401') && !error.includes('403') && ( -
-

Vérifiez que le backend est démarré sur http://localhost:4000

- - 🩺 Lancer le diagnostic - + {error.includes('403') && ( +
+

Vous n'avez pas le rôle ADMIN requis.

+
+ )} + + {!error.includes('401') && !error.includes('403') && ( +
+

Vérifiez que le backend est démarré.

+
+ )}
- )} +
)} - {/* Info et Filtres */} -
-
- {totalTickets > 0 && ( - - {totalTickets} ticket{totalTickets > 1 ? 's' : ''} au total - {(filterStatus || filterPrizeType) && ' (filtré)'} - - )} + {/* Stats rapides */} +
+
+
+
+ +
+
+

{totalTickets}

+

Total

+
+
-
- {/* Filtre par type de lot */} - - - {/* Filtre par statut */} - - - {/* Bouton pour réinitialiser les filtres */} - {(filterStatus || filterPrizeType) && ( - - )} +
+
+
+ +
+
+

{ticketStats.pending}

+

En attente

+
+
+
+
+
+
+ +
+
+

{ticketStats.claimed}

+

Réclamés

+
+
+
+
+
+
+ +
+
+

{ticketStats.rejected}

+

Rejetés

+
+
{/* Table des tickets */} -
- - +
+
+ - - - - - - - + {tickets.length === 0 ? ( ) : ( tickets.map((ticket) => ( - - {/* CODE TICKET */} - - - {/* LOT GAGNÉ */} - - - {/* STATUT */} - - - {/* DISTRIBUÉ LE (date d'utilisation du ticket) */} - - - {/* UTILISÉ PAR */} - + {/* CODE TICKET */} + + - {/* ACTIONS */} - - - )) + {/* LOT GAGNÉ */} + + + {/* STATUT */} + + + {/* DISTRIBUÉ LE */} + + + {/* UTILISÉ PAR */} + + + {/* ACTIONS */} + + + )) )}
+ Code Ticket + Lot Gagné + Statut + Joué le + Utilisé par + Actions
-
🎫
+
+ +

{!error ? 'Aucun ticket trouvé' : 'Impossible de charger les tickets'}

@@ -244,68 +296,80 @@ export default function TicketManagement() { : 'Aucun ticket n\'a été créé pour le moment' }

- {!error && ( -

- Les tickets apparaîtront ici une fois que des utilisateurs auront joué au jeu -

- )}
-
- {ticket.code} -
-
-
- {ticket.prize?.name || 'N/A'} -
-
- - - {ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString('fr-FR', { - year: 'numeric', - month: '2-digit', - day: '2-digit' - }) : '-'} - -
- {ticket.user ? `${ticket.user.firstName} ${ticket.user.lastName}` : '-'} -
- {ticket.user && ( -
- {ticket.user.email} +
+
+
+ +
+ + {ticket.code} +
- )} -
- -
+
+ + + {ticket.prize?.name || 'N/A'} + +
+
+ + + {ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString('fr-FR', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }) : '-'} + + {ticket.user ? ( +
+
+ {getInitials(ticket.user.firstName, ticket.user.lastName)} +
+
+
+ {ticket.user.firstName} {ticket.user.lastName} +
+
+ {ticket.user.email} +
+
+
+ ) : ( + - + )} +
+ +
@@ -322,68 +386,98 @@ export default function TicketManagement() { {/* Modal détails ticket */} {selectedTicket && ( -
-
-

Détails du ticket

+
+
+
+
+

Détails du ticket

+ +
+
-
-
-
-

Code

-

{selectedTicket.code}

+
+ {/* Code et Statut */} +
+
+
+ +
+
+

Code ticket

+

{selectedTicket.code}

+
-
-

Statut

-

- -

+ +
+ + {/* Utilisateur */} + {selectedTicket.user && ( +
+

Utilisateur

+
+
+ {getInitials(selectedTicket.user.firstName, selectedTicket.user.lastName)} +
+
+

+ {selectedTicket.user.firstName} {selectedTicket.user.lastName} +

+

{selectedTicket.user.email}

+
+
+
+ )} + + {/* Prix gagné */} +
+

Prix gagné

+
+
+ +
+
+

{selectedTicket.prize?.name}

+

{selectedTicket.prize?.description}

+
-
-

Utilisateur

-

- {selectedTicket.user ? `${selectedTicket.user.firstName} ${selectedTicket.user.lastName}` : 'N/A'} -

-

{selectedTicket.user?.email}

-
- -
-

Prix gagné

-

{selectedTicket.prize?.name}

-

{selectedTicket.prize?.description}

-

{selectedTicket.prize?.value}€

-
- + {/* Dates */}
-
-

Date de jeu

-

+

+

Date de jeu

+

{selectedTicket.playedAt ? new Date(selectedTicket.playedAt).toLocaleString('fr-FR') : 'N/A'}

{selectedTicket.validatedAt && ( -
-

Date de validation

-

+

+

Date de validation

+

{new Date(selectedTicket.validatedAt).toLocaleString('fr-FR')}

)}
+ {/* Raison du rejet */} {selectedTicket.rejectionReason && ( -
-

Raison du rejet

-

{selectedTicket.rejectionReason}

+
+

Raison du rejet

+

{selectedTicket.rejectionReason}

)}
-
+
diff --git a/components/admin/UserManagement.tsx b/components/admin/UserManagement.tsx index e8520f1..313fd6b 100644 --- a/components/admin/UserManagement.tsx +++ b/components/admin/UserManagement.tsx @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'; import { adminService } from '@/services/admin.service'; import { User, CreateEmployeeData, UpdateUserData, PaginatedResponse } from '@/types'; import { Pagination } from '@/components/ui'; +import { Search, UserPlus, X, Filter, RefreshCw, Users, Shield, Briefcase, UserCheck } from 'lucide-react'; export default function UserManagement() { const router = useRouter(); @@ -132,132 +133,238 @@ export default function UserManagement() { } }; + // Fonction pour obtenir les initiales + const getInitials = (firstName?: string, lastName?: string) => { + const f = firstName?.charAt(0) || ''; + const l = lastName?.charAt(0) || ''; + return (f + l).toUpperCase() || '?'; + }; + + // Fonction pour obtenir la couleur de l'avatar selon le rôle + const getAvatarColor = (role: string) => { + switch (role) { + case 'ADMIN': + return 'bg-gradient-to-br from-red-500 to-red-600'; + case 'EMPLOYEE': + return 'bg-gradient-to-br from-blue-500 to-blue-600'; + case 'CLIENT': + return 'bg-gradient-to-br from-green-500 to-green-600'; + default: + return 'bg-gradient-to-br from-gray-500 to-gray-600'; + } + }; + + // Compter les utilisateurs par rôle + const roleStats = useMemo(() => { + const stats = { ADMIN: 0, EMPLOYEE: 0, CLIENT: 0 }; + filteredUsers.forEach(user => { + if (user.role in stats) { + stats[user.role as keyof typeof stats]++; + } + }); + return stats; + }, [filteredUsers]); + if (loading && users.length === 0) { - return
Chargement des utilisateurs...
; + return ( +
+
+
+

Chargement des utilisateurs...

+
+
+ ); } return (
-
-

Gestion des Utilisateurs

- + {/* Section recherche avec gradient */} +
+
+
+
+ + setSearchQuery(e.target.value)} + className="w-full pl-12 pr-4 py-3 rounded-xl bg-white/10 backdrop-blur-sm text-white placeholder-blue-200 border border-white/20 focus:outline-none focus:ring-2 focus:ring-white/40" + /> +
+
+
+ + + +
+
{error && ( -
- {error} +
+
+ +
+ {error}
)} - {/* Filtres */} -
-
- setSearchQuery(e.target.value)} - className="w-full border rounded px-3 py-2" - /> + {/* Stats rapides */} +
+
+
+
+ +
+
+

{filteredUsers.length}

+

Total

+
+
+
+
+
+
+ +
+
+

{roleStats.CLIENT}

+

Clients

+
+
+
+
+
+
+ +
+
+

{roleStats.EMPLOYEE}

+

Employés

+
+
+
+
+
+
+ +
+
+

{roleStats.ADMIN}

+

Admins

+
+
- -
{/* Table des utilisateurs */} -
- - +
+
+ - - - - - - - + {filteredUsers.length === 0 ? ( - ) : ( filteredUsers.map((user) => ( - - - - - - - - - )) + + + + + + + + )) )}
+ Utilisateur - Email - + Rôle + Statut + Date création + Actions
- {searchQuery ? 'Aucun résultat pour cette recherche' : 'Aucun utilisateur trouvé'} + +
+
+ +
+

+ {searchQuery ? 'Aucun résultat pour cette recherche' : 'Aucun utilisateur trouvé'} +

+
-
- {user.firstName} {user.lastName} -
-
-
{user.email}
-
- - {user.role} - - - - {user.isActive !== false ? 'Actif' : 'Inactif'} - - - {new Date(user.createdAt).toLocaleDateString('fr-FR')} - - -
+
+
+ {getInitials(user.firstName, user.lastName)} +
+
+
+ {user.firstName} {user.lastName} +
+
{user.email}
+
+
+
+ + {user.role === 'ADMIN' ? 'Admin' : user.role === 'EMPLOYEE' ? 'Employé' : 'Client'} + + +
+
+ + {user.isActive !== false ? 'Actif' : 'Inactif'} + +
+
+ {new Date(user.createdAt).toLocaleDateString('fr-FR')} + + +
@@ -273,64 +380,80 @@ export default function UserManagement() { {/* Modal créer employé */} {isCreateEmployeeModalOpen && ( -
-
-

Créer un employé

-
+
+
+
+
+

Créer un employé

+ +
+
+
- + setEmployeeFormData({ ...employeeFormData, email: e.target.value })} - className="w-full border rounded px-3 py-2" + className="w-full border border-gray-200 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="email@exemple.com" required />
- + setEmployeeFormData({ ...employeeFormData, password: e.target.value })} - className="w-full border rounded px-3 py-2" + className="w-full border border-gray-200 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="••••••••" required />
-
- - setEmployeeFormData({ ...employeeFormData, firstName: e.target.value })} - className="w-full border rounded px-3 py-2" - required - /> +
+
+ + setEmployeeFormData({ ...employeeFormData, firstName: e.target.value })} + className="w-full border border-gray-200 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Jean" + required + /> +
+
+ + setEmployeeFormData({ ...employeeFormData, lastName: e.target.value })} + className="w-full border border-gray-200 rounded-xl px-4 py-3 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="Dupont" + required + /> +
-
- - setEmployeeFormData({ ...employeeFormData, lastName: e.target.value })} - className="w-full border rounded px-3 py-2" - required - /> -
-
- +
+
@@ -339,36 +462,55 @@ export default function UserManagement() { {/* Modal modifier utilisateur */} {isEditUserModalOpen && editingUser && ( -
-
-

Modifier l'utilisateur

-
+
+
+
+
+

Modifier l'utilisateur

+ +
+
+ +
+
+ {getInitials(editingUser.firstName, editingUser.lastName)} +
+
+

{editingUser.firstName} {editingUser.lastName}

+

{editingUser.email}

+
+
- +
-
- +
+