'use client'; import { useState, useEffect } from 'react'; import { useParams } from 'next/navigation'; import Link from 'next/link'; import { Campaign, Proposition, Participant, Vote } from '@/types'; import { campaignService, propositionService, participantService, voteService } from '@/lib/services'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Badge } from '@/components/ui/badge'; import { Progress } from '@/components/ui/progress'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import Navigation from '@/components/Navigation'; import AuthGuard from '@/components/AuthGuard'; import { BarChart3, Users, Vote as VoteIcon, TrendingUp, Target, Award, FileText, Calendar, ArrowLeft, SortAsc, TrendingDown, Users2, Target as TargetIcon, Hash } from 'lucide-react'; import { ExportStatsButton } from '@/components/ExportStatsButton'; export const dynamic = 'force-dynamic'; interface PropositionStats { proposition: Proposition; voteCount: number; averageAmount: number; minAmount: number; maxAmount: number; totalAmount: number; participationRate: number; voteDistribution: number; consensusScore: number; } type SortOption = | 'popularity' | 'total_impact' | 'consensus' | 'engagement' | 'distribution' | 'alphabetical'; const sortOptions = [ { value: 'total_impact', label: 'Impact total', icon: Target, description: 'Somme totale investie' }, { value: 'popularity', label: 'Popularité', icon: TrendingUp, description: 'Moyenne puis nombre de votants' }, { value: 'consensus', label: 'Consensus', icon: Users2, description: 'Plus petit écart-type' }, { value: 'engagement', label: 'Engagement', icon: Users, description: 'Taux de participation' }, { value: 'distribution', label: 'Répartition', icon: BarChart3, description: 'Nombre de votes différents' }, { value: 'alphabetical', label: 'Alphabétique', icon: Hash, description: 'Ordre alphabétique' } ]; function CampaignStatsPageContent() { const params = useParams(); const campaignId = params.id as string; const [campaign, setCampaign] = useState(null); const [participants, setParticipants] = useState([]); const [propositions, setPropositions] = useState([]); const [votes, setVotes] = useState([]); const [loading, setLoading] = useState(true); const [propositionStats, setPropositionStats] = useState([]); const [sortBy, setSortBy] = useState('total_impact'); useEffect(() => { // Vérifier la configuration Supabase const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; // Si pas de configuration ou valeurs par défaut, rediriger vers setup if (!supabaseUrl || !supabaseAnonKey || supabaseUrl === 'https://placeholder.supabase.co' || supabaseAnonKey === 'your-anon-key') { console.log('🔧 Configuration Supabase manquante, redirection vers /setup'); window.location.href = '/setup'; return; } if (campaignId) { loadData(); } }, [campaignId]); const loadData = async () => { try { setLoading(true); const [campaignData, participantsData, propositionsData, votesData] = await Promise.all([ campaignService.getById(campaignId), participantService.getByCampaign(campaignId), propositionService.getByCampaign(campaignId), voteService.getByCampaign(campaignId) ]); if (!campaignData) { throw new Error('Campagne non trouvée'); } setCampaign(campaignData); setParticipants(participantsData); setPropositions(propositionsData); setVotes(votesData); // Calculer les statistiques des propositions const stats = propositionsData.map(proposition => { const propositionVotes = votesData.filter(vote => vote.proposition_id === proposition.id && vote.amount > 0); const amounts = propositionVotes.map(vote => vote.amount); const totalAmount = amounts.reduce((sum, amount) => sum + amount, 0); // Calculer l'écart-type pour le consensus const mean = amounts.length > 0 ? totalAmount / amounts.length : 0; const variance = amounts.length > 0 ? amounts.reduce((sum, amount) => sum + Math.pow(amount - mean, 2), 0) / amounts.length : 0; const consensusScore = Math.sqrt(variance); // Calculer le taux de participation pour cette proposition const participationRate = participantsData.length > 0 ? (propositionVotes.length / participantsData.length) * 100 : 0; // Calculer la répartition des votes (nombre de montants différents) const uniqueAmounts = new Set(amounts).size; return { proposition, voteCount: propositionVotes.length, averageAmount: amounts.length > 0 ? Math.round(totalAmount / amounts.length) : 0, minAmount: amounts.length > 0 ? Math.min(...amounts) : 0, maxAmount: amounts.length > 0 ? Math.max(...amounts) : 0, totalAmount, participationRate: Math.round(participationRate * 100) / 100, voteDistribution: uniqueAmounts, consensusScore: Math.round(consensusScore * 100) / 100 }; }); setPropositionStats(stats); } catch (error) { console.error('Erreur lors du chargement des données:', error); } finally { setLoading(false); } }; const getSortedStats = () => { const sorted = [...propositionStats]; switch (sortBy) { case 'popularity': return sorted.sort((a, b) => { if (b.averageAmount !== a.averageAmount) { return b.averageAmount - a.averageAmount; } return b.voteCount - a.voteCount; }); case 'total_impact': return sorted.sort((a, b) => b.totalAmount - a.totalAmount); case 'consensus': return sorted.sort((a, b) => a.consensusScore - b.consensusScore); case 'engagement': return sorted.sort((a, b) => b.participationRate - a.participationRate); case 'distribution': return sorted.sort((a, b) => b.voteDistribution - a.voteDistribution); case 'alphabetical': return sorted.sort((a, b) => a.proposition.title.localeCompare(b.proposition.title)); default: return sorted; } }; const getParticipationRate = () => { if (participants.length === 0) return 0; const votedParticipants = participants.filter(p => { const participantVotes = votes.filter(v => v.participant_id === p.id); return participantVotes.some(v => v.amount > 0); }); return Math.round((votedParticipants.length / participants.length) * 100); }; const getAverageVotesPerProposition = () => { if (propositions.length === 0) return 0; const totalVotes = votes.filter(v => v.amount > 0).length; return Math.round(totalVotes / propositions.length); }; if (loading) { return (

Chargement des statistiques...

); } if (!campaign) { return (

Campagne introuvable

La campagne que vous recherchez n'existe pas ou a été supprimée.

); } const participationRate = getParticipationRate(); const averageVotesPerProposition = getAverageVotesPerProposition(); return (
{/* Header */}
{campaign.status === 'voting' ? 'En cours de vote' : 'Terminée'}

Statistiques

{campaign.title}

{campaign.description}

{/* Overview Stats */}

Taux de participation

{participationRate}%

{participants.filter(p => votes.some(v => v.participant_id === p.id && v.amount > 0)).length} / {participants.length} participants

Propositions

{propositions.length}

{averageVotesPerProposition} votes moy. par proposition

{/* Propositions Stats */}
Préférences par proposition Statistiques des montants exprimés par les participants pour chaque proposition
Trier par :
{propositionStats.length === 0 ? (

Aucune proposition

Aucune proposition n'a été soumise pour cette campagne.

) : (
{getSortedStats().map((stat, index) => (
#{index + 1}

{stat.proposition.title}

{index === 0 && stat.averageAmount > 0 && ( {sortBy === 'popularity' ? 'Préférée' : sortBy === 'total_impact' ? 'Plus d\'impact' : sortBy === 'consensus' ? 'Plus de consensus' : sortBy === 'engagement' ? 'Plus d\'engagement' : sortBy === 'distribution' ? 'Plus de répartition' : 'Première'} )}

{stat.voteCount}

{stat.voteCount === 1 ? 'Votant' : 'Votants'}

{stat.averageAmount}€

Moyenne

{stat.totalAmount}€

Total

{stat.minAmount}€

Minimum

{stat.maxAmount}€

Maximum

{stat.participationRate}%

Participation

{/* Métriques avancées */}
Consensus
Écart-type: {stat.consensusScore}€
Répartition
{stat.voteDistribution} montants différents
{stat.voteCount > 0 && (
Répartition des préférences {stat.voteCount} {stat.voteCount === 1 ? 'votant' : 'votants'}
)}
))}
)}
); } export default function CampaignStatsPage() { return ( ); }