fix: improve dashboard data display and clean up ticket management
Dashboard: - Filter out "Non spécifié" from gender chart - Filter empty/unspecified values from age ranges and cities TicketManagement: - Remove debug panel and console logs - Fix date column to show playedAt instead of createdAt - Remove unused imports 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
eded0187a0
commit
51ec802131
|
|
@ -153,20 +153,24 @@ export default function AdminDashboardAdvanced() {
|
|||
percentage: prize.percentage,
|
||||
})) || [];
|
||||
|
||||
// Ne montrer le graphique genre que s'il y a des données autres que "Non spécifié"
|
||||
const genderChartData = stats.demographics?.gender
|
||||
? [
|
||||
{ name: "Hommes", value: stats.demographics.gender.male, color: GENDER_COLORS.male },
|
||||
{ name: "Femmes", value: stats.demographics.gender.female, color: GENDER_COLORS.female },
|
||||
{ name: "Autre", value: stats.demographics.gender.other, color: GENDER_COLORS.other },
|
||||
{
|
||||
name: "Non spécifié",
|
||||
value: stats.demographics.gender.notSpecified,
|
||||
color: GENDER_COLORS.notSpecified,
|
||||
},
|
||||
].filter((item) => item.value > 0)
|
||||
: [];
|
||||
|
||||
const ageChartData = stats.demographics?.ageRanges || [];
|
||||
// Ne montrer le graphique âge que s'il y a des données avec des vraies tranches d'âge
|
||||
const ageChartData = (stats.demographics?.ageRanges || []).filter(
|
||||
(item) => item.range && !item.range.toLowerCase().includes("non spécifié") && item.count > 0
|
||||
);
|
||||
|
||||
// Ne montrer les villes que s'il y a des vraies villes (pas vides ou "non spécifié")
|
||||
const topCitiesData = (stats.demographics?.topCities || []).filter(
|
||||
(city) => city.city && city.city.trim() !== "" && !city.city.toLowerCase().includes("non spécifié") && city.count > 0
|
||||
);
|
||||
|
||||
const ticketDistributedPercent = stats.tickets.total > 0
|
||||
? ((stats.tickets.distributed / stats.tickets.total) * 100).toFixed(1)
|
||||
|
|
@ -420,15 +424,15 @@ export default function AdminDashboardAdvanced() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* Top Villes */}
|
||||
{stats?.demographics?.topCities && stats.demographics.topCities.length > 0 && (
|
||||
{/* Top Villes - affiché uniquement s'il y a des vraies données de villes */}
|
||||
{topCitiesData.length > 0 && (
|
||||
<div className="bg-white rounded-lg shadow-sm border border-gray-200 p-6">
|
||||
<h3 className="text-lg font-semibold text-gray-900 mb-4 flex items-center gap-2">
|
||||
<MapPin className="w-5 h-5" />
|
||||
Top 10 Villes des Participants
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-5 gap-4">
|
||||
{stats.demographics.topCities.slice(0, 10).map((city, idx) => (
|
||||
{topCitiesData.slice(0, 10).map((city, idx) => (
|
||||
<CityCard
|
||||
key={idx}
|
||||
rank={idx + 1}
|
||||
|
|
|
|||
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { adminService } from '@/services/admin.service';
|
||||
import { Ticket, PaginatedResponse } from '@/types';
|
||||
import { API_BASE_URL } from '@/utils/constants';
|
||||
import { Ticket } from '@/types';
|
||||
|
||||
export default function TicketManagement() {
|
||||
const [tickets, setTickets] = useState<Ticket[]>([]);
|
||||
|
|
@ -15,7 +14,6 @@ export default function TicketManagement() {
|
|||
const [filterStatus, setFilterStatus] = useState<string>('');
|
||||
const [filterPrizeType, setFilterPrizeType] = useState<string>('');
|
||||
const [selectedTicket, setSelectedTicket] = useState<Ticket | null>(null);
|
||||
const [showDebug, setShowDebug] = useState(false);
|
||||
|
||||
const loadTickets = useCallback(async () => {
|
||||
try {
|
||||
|
|
@ -39,30 +37,19 @@ export default function TicketManagement() {
|
|||
let totalPagesCount = 1;
|
||||
|
||||
if (Array.isArray(response)) {
|
||||
// Si la réponse est directement un tableau
|
||||
console.log('📦 Réponse est un tableau direct');
|
||||
ticketsData = response;
|
||||
total = response.length;
|
||||
totalPagesCount = 1;
|
||||
} else if (response.data && Array.isArray(response.data)) {
|
||||
// Si la réponse est un objet avec data
|
||||
console.log('📦 Réponse est un objet avec data');
|
||||
ticketsData = response.data;
|
||||
total = response.total || response.data.length;
|
||||
totalPagesCount = response.totalPages || 1;
|
||||
} else {
|
||||
console.warn('⚠️ Format de réponse inattendu:', response);
|
||||
}
|
||||
|
||||
console.log('🎯 Tickets à afficher:', ticketsData);
|
||||
|
||||
setTickets(ticketsData);
|
||||
setTotalPages(totalPagesCount);
|
||||
setTotalTickets(total);
|
||||
} catch (err: any) {
|
||||
console.error('❌ Erreur lors du chargement:', err);
|
||||
|
||||
// Messages d'erreur personnalisés selon le type d'erreur
|
||||
let errorMessage = 'Erreur lors du chargement des tickets';
|
||||
|
||||
if (err.status === 401) {
|
||||
|
|
@ -256,7 +243,7 @@ export default function TicketManagement() {
|
|||
Statut
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Distribué le
|
||||
Joué le
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
Utilisé par
|
||||
|
|
@ -313,13 +300,13 @@ export default function TicketManagement() {
|
|||
</span>
|
||||
</td>
|
||||
|
||||
{/* DISTRIBUÉ LE */}
|
||||
{/* DISTRIBUÉ LE (date d'utilisation du ticket) */}
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{ticket.createdAt ? new Date(ticket.createdAt).toLocaleDateString('fr-FR', {
|
||||
{ticket.playedAt ? new Date(ticket.playedAt).toLocaleDateString('fr-FR', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit'
|
||||
}) : 'N/A'}
|
||||
}) : '-'}
|
||||
</td>
|
||||
|
||||
{/* UTILISÉ PAR */}
|
||||
|
|
@ -371,55 +358,6 @@ export default function TicketManagement() {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
{/* Panneau de Debug */}
|
||||
<div className="mt-8">
|
||||
<button
|
||||
onClick={() => setShowDebug(!showDebug)}
|
||||
className="text-sm text-gray-600 hover:text-gray-900 underline"
|
||||
>
|
||||
{showDebug ? '🔽 Masquer les infos de debug' : '🔍 Afficher les infos de debug'}
|
||||
</button>
|
||||
|
||||
{showDebug && (
|
||||
<div className="mt-4 bg-gray-100 rounded-lg p-4 border border-gray-300">
|
||||
<h3 className="font-bold text-sm mb-2">📊 Informations de Debug</h3>
|
||||
<div className="space-y-2 text-xs font-mono">
|
||||
<div>
|
||||
<strong>État:</strong> {loading ? 'Chargement...' : 'Chargé'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Erreur:</strong> {error || 'Aucune'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Nombre de tickets:</strong> {tickets.length}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Total tickets (API):</strong> {totalTickets}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Page actuelle:</strong> {page} / {totalPages}
|
||||
</div>
|
||||
<div>
|
||||
<strong>Filtre statut:</strong> {filterStatus || 'Aucun'}
|
||||
</div>
|
||||
<div>
|
||||
<strong>URL API:</strong> {API_BASE_URL}
|
||||
</div>
|
||||
<div className="pt-2">
|
||||
<strong>Tickets reçus:</strong>
|
||||
<pre className="mt-2 bg-white p-2 rounded overflow-auto max-h-60">
|
||||
{JSON.stringify(tickets, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<p className="text-xs text-gray-600">
|
||||
💡 Astuce: Ouvrez la console du navigateur (F12) pour voir les logs détaillés
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Modal détails ticket */}
|
||||
{selectedTicket && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user