From 3456657ae55a26c8d101b1d1ca4e7774311b6891 Mon Sep 17 00:00:00 2001 From: soufiane Date: Wed, 19 Nov 2025 15:09:00 +0100 Subject: [PATCH] feat: add user details page and getUserById service method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- app/admin/utilisateurs/[id]/page.tsx | 262 +++++++++++++++++++++++++++ services/admin.service.ts | 30 +++ 2 files changed, 292 insertions(+) create mode 100644 app/admin/utilisateurs/[id]/page.tsx diff --git a/app/admin/utilisateurs/[id]/page.tsx b/app/admin/utilisateurs/[id]/page.tsx new file mode 100644 index 0000000..b2bd093 --- /dev/null +++ b/app/admin/utilisateurs/[id]/page.tsx @@ -0,0 +1,262 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { useRouter, useParams } from 'next/navigation'; +import { adminService } from '@/services/admin.service'; +import { User } from '@/types'; +import { ArrowLeft, Mail, Phone, MapPin, Calendar, Award, Ticket, CheckCircle, Clock } from 'lucide-react'; +import toast from 'react-hot-toast'; + +export default function UserDetailsPage() { + const router = useRouter(); + const params = useParams(); + const userId = params.id as string; + + const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + loadUserDetails(); + }, [userId]); + + const loadUserDetails = async () => { + try { + setLoading(true); + setError(null); + const userData = await adminService.getUserById(userId); + setUser(userData); + } catch (err: any) { + setError(err.message || 'Erreur lors du chargement des détails'); + toast.error('Erreur lors du chargement des détails de l\'utilisateur'); + } finally { + setLoading(false); + } + }; + + const getRoleBadgeColor = (role: string) => { + switch (role) { + case 'ADMIN': + return 'bg-red-100 text-red-800'; + case 'EMPLOYEE': + return 'bg-blue-100 text-blue-800'; + case 'CLIENT': + return 'bg-green-100 text-green-800'; + default: + return 'bg-gray-100 text-gray-800'; + } + }; + + if (loading) { + return ( +
+
+
+
+
+
+ ); + } + + if (error || !user) { + return ( +
+
+ {error || 'Utilisateur non trouvé'} +
+ +
+ ); + } + + return ( +
+ {/* Header */} +
+ +
+
+

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

+

Détails de l'utilisateur

+
+ + {user.role} + +
+
+ +
+ {/* Informations principales */} +
+ {/* Informations de contact */} +
+

Informations de contact

+
+
+ +
+

Email

+

{user.email}

+
+
+ {user.phone && ( +
+ +
+

Téléphone

+

{user.phone}

+
+
+ )} + {(user.address || user.city || user.postalCode) && ( +
+ +
+

Adresse

+

+ {user.address && <>{user.address}
} + {user.postalCode && user.city && `${user.postalCode} ${user.city}`} +

+
+
+ )} +
+
+ + {/* Informations personnelles */} + {(user.dateOfBirth || user.gender) && ( +
+

Informations personnelles

+
+ {user.dateOfBirth && ( +
+

Date de naissance

+

+ {new Date(user.dateOfBirth).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric' + })} +

+
+ )} + {user.gender && ( +
+

Genre

+

+ {user.gender === 'MALE' ? 'Homme' : + user.gender === 'FEMALE' ? 'Femme' : + user.gender === 'OTHER' ? 'Autre' : 'Non spécifié'} +

+
+ )} +
+
+ )} + + {/* Statistiques de tickets */} + {user.role === 'CLIENT' && ( +
+

Activité des tickets

+
+
+ +

{user.ticketsCount || 0}

+

Total tickets

+
+
+ +

{user.pendingTickets || 0}

+

En attente

+
+
+ +

{user.claimedTickets || 0}

+

Réclamés

+
+
+
+ )} +
+ + {/* Sidebar */} +
+ {/* Statut du compte */} +
+

Statut du compte

+
+
+

Vérification email

+ + {user.isVerified ? ( + <> + + Vérifié + + ) : ( + <> + + Non vérifié + + )} + +
+
+

Membre depuis

+
+ + + {new Date(user.createdAt).toLocaleDateString('fr-FR', { + day: 'numeric', + month: 'long', + year: 'numeric' + })} + +
+
+
+
+ + {/* Actions rapides */} +
+

Actions

+
+ + {user.role === 'CLIENT' && ( + + )} +
+
+
+
+
+ ); +} diff --git a/services/admin.service.ts b/services/admin.service.ts index 014cdfb..a0b95bf 100644 --- a/services/admin.service.ts +++ b/services/admin.service.ts @@ -96,6 +96,36 @@ export const adminService = { // ==================== GESTION DES UTILISATEURS ==================== + /** + * Récupérer un utilisateur par ID + */ + getUserById: async (userId: string): Promise => { + const response = await api.get>( + `${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é) */