refactor: Remove all mock data and use real API data for elections and voting

This commit is contained in:
Alexis Bruneteau 2025-11-07 02:49:50 +01:00
parent a73c713b9c
commit c367dbaf43
2 changed files with 194 additions and 108 deletions

View File

@ -5,69 +5,58 @@ import Link from "next/link"
import { useParams } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ArrowLeft, Clock, Users, CheckCircle2, AlertCircle } from "lucide-react"
import { ArrowLeft, Clock, Users, CheckCircle2, AlertCircle, Loader2 } from "lucide-react"
import { VotingInterface } from "@/components/voting-interface"
interface Candidate {
id: number
name: string
description?: string
order: number
}
interface Election {
id: number
name: string
description?: string
start_date: string
end_date: string
is_active: boolean
results_published: boolean
candidates: Candidate[]
}
export default function VoteDetailPage() {
const params = useParams()
const voteId = params.id as string
const [election, setElection] = useState<any>(null)
const [election, setElection] = useState<Election | null>(null)
const [isLoading, setIsLoading] = useState(true)
const [hasVoted, setHasVoted] = useState(false)
const [error, setError] = useState<string | null>(null)
// Mock elections data - matches the active/page.tsx
const mockElections: Record<string, any> = {
"1": {
id: 1,
name: "Election Présidentielle 2025",
description: "Première manche - Scrutin dimanche",
category: "Nationale",
status: "En cours",
progress: 65,
endDate: "2025-11-06T20:00:00",
candidates: [
{ id: 1, name: "Alice Dupont", description: "Candidate progressive" },
{ id: 2, name: "Bob Martin", description: "Candidate centriste" },
{ id: 3, name: "Claire Laurent", description: "Candidate conservatrice" },
],
votes: 4521,
},
}
useEffect(() => {
const fetchElection = async () => {
try {
setIsLoading(true)
setError(null)
// First try to fetch from API
const token = localStorage.getItem("access_token")
try {
const response = await fetch(`/api/elections/${voteId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
const response = await fetch(`/api/elections/${voteId}`, {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (response.ok) {
const data = await response.json()
setElection(data)
return
}
} catch (err) {
// Fall back to mock data
if (!response.ok) {
throw new Error("Élection non trouvée")
}
// Use mock data if API fails
const mockData = mockElections[voteId]
if (mockData) {
setElection(mockData)
} else {
setError("Élection non trouvée")
}
const data = await response.json()
setElection(data)
} catch (err) {
setError("Erreur lors du chargement de l'élection")
const message = err instanceof Error ? err.message : "Erreur lors du chargement"
setError(message)
setElection(null)
} finally {
setIsLoading(false)
}
@ -89,8 +78,9 @@ export default function VoteDetailPage() {
</Link>
</div>
<Card>
<CardContent className="pt-6">
<p className="text-muted-foreground">Chargement...</p>
<CardContent className="pt-6 flex gap-4 items-center justify-center py-12">
<Loader2 className="w-5 h-5 animate-spin text-accent" />
<p className="text-muted-foreground">Chargement de l'élection...</p>
</CardContent>
</Card>
</div>
@ -110,7 +100,7 @@ export default function VoteDetailPage() {
</div>
<Card className="border-red-500 bg-red-50 dark:bg-red-950">
<CardContent className="pt-6 flex gap-4">
<AlertCircle className="w-5 h-5 text-red-500 flex-shrink-0" />
<AlertCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
<div>
<h3 className="font-semibold text-red-900 dark:text-red-100">Erreur</h3>
<p className="text-sm text-red-800 dark:text-red-200 mt-1">
@ -137,7 +127,9 @@ export default function VoteDetailPage() {
</div>
<div className="space-y-2">
<h1 className="text-3xl font-bold">{election.name}</h1>
<p className="text-muted-foreground">{election.description}</p>
{election.description && (
<p className="text-muted-foreground">{election.description}</p>
)}
</div>
</div>
@ -158,30 +150,34 @@ export default function VoteDetailPage() {
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">
<CheckCircle2 className="w-4 h-4" />
Votes
<Clock className="w-4 h-4" />
Date de fin
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-3xl font-bold">{election.votes?.toLocaleString() || 0}</p>
<p className="text-sm font-bold">
{new Date(election.end_date).toLocaleDateString("fr-FR")}
</p>
</CardContent>
</Card>
<Card>
<CardHeader className="pb-2">
<CardTitle className="text-sm font-medium text-muted-foreground flex items-center gap-2">
<Clock className="w-4 h-4" />
<CheckCircle2 className="w-4 h-4" />
Statut
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-lg font-bold text-accent">{election.status}</p>
<p className="text-lg font-bold text-accent">
{election.is_active ? "En cours" : "Terminée"}
</p>
</CardContent>
</Card>
</div>
{/* Voting Interface */}
{!hasVoted ? (
{!hasVoted && election.is_active ? (
<Card>
<CardHeader>
<CardTitle>Voter</CardTitle>
@ -199,21 +195,33 @@ export default function VoteDetailPage() {
/>
</CardContent>
</Card>
) : (
) : hasVoted ? (
<Card className="border-green-500 bg-green-50 dark:bg-green-950">
<CardContent className="pt-6 flex gap-4">
<CheckCircle2 className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" />
<div>
<h3 className="font-semibold text-green-900 dark:text-green-100">Vote enregistré</h3>
<p className="text-sm text-green-800 dark:text-green-200 mt-1">
Votre vote a é enregistré et chiffré de manière sécurisée.
Votre vote a é enregistré dans la blockchain et chiffré de manière sécurisée.
</p>
<Link href="/dashboard/votes/history" className="text-sm font-medium text-green-700 dark:text-green-300 hover:underline mt-2 block">
Voir l'historique de vos votes
<Link href="/dashboard/blockchain" className="text-sm font-medium text-green-700 dark:text-green-300 hover:underline mt-2 block">
Voir la blockchain
</Link>
</div>
</CardContent>
</Card>
) : (
<Card className="border-yellow-500 bg-yellow-50 dark:bg-yellow-950">
<CardContent className="pt-6 flex gap-4">
<AlertCircle className="w-5 h-5 text-yellow-500 flex-shrink-0 mt-0.5" />
<div>
<h3 className="font-semibold text-yellow-900 dark:text-yellow-100">Élection terminée</h3>
<p className="text-sm text-yellow-800 dark:text-yellow-200 mt-1">
Cette élection est terminée. Les résultats sont disponibles.
</p>
</div>
</CardContent>
</Card>
)}
{/* Candidates List */}
@ -224,7 +232,7 @@ export default function VoteDetailPage() {
</CardHeader>
<CardContent>
<div className="space-y-2">
{election.candidates.map((candidate: any) => (
{election.candidates.map((candidate) => (
<div
key={candidate.id}
className="p-3 rounded-lg border border-border hover:border-accent/50 transition-colors"

View File

@ -1,24 +1,119 @@
"use client"
import { useState, useEffect } from "react"
import Link from "next/link"
import { Button } from "@/components/ui/button"
import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ChevronRight } from "lucide-react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
import { ChevronRight, AlertCircle, Loader2 } from "lucide-react"
interface Election {
id: number
name: string
description: string
start_date: string
end_date: string
is_active: boolean
candidates: Array<{ id: number; name: string }>
}
export default function ActiveVotesPage() {
const activeVotes = [
{
id: 1,
title: "Election Présidentielle 2025",
description: "Première manche - Scrutin dimanche",
category: "Nationale",
status: "En cours",
progress: 65,
endDate: "6 Nov 2025 à 20:00",
candidates: 12,
votes: 4521,
},
]
const [elections, setElections] = useState<Election[]>([])
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
useEffect(() => {
const fetchElections = async () => {
try {
setIsLoading(true)
setError(null)
const token = localStorage.getItem("access_token")
const response = await fetch("/api/elections/active", {
headers: {
Authorization: `Bearer ${token}`,
},
})
if (!response.ok) {
throw new Error("Impossible de charger les élections actives")
}
const data = await response.json()
setElections(data || [])
} catch (err) {
const message = err instanceof Error ? err.message : "Erreur lors du chargement"
setError(message)
setElections([])
} finally {
setIsLoading(false)
}
}
fetchElections()
}, [])
if (isLoading) {
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-bold">Votes Actifs</h1>
<p className="text-muted-foreground mt-2">
Participez aux élections et scrutins en cours
</p>
</div>
<Card>
<CardContent className="pt-6 flex gap-4 items-center justify-center py-12">
<Loader2 className="w-5 h-5 animate-spin text-accent" />
<p className="text-muted-foreground">Chargement des élections actives...</p>
</CardContent>
</Card>
</div>
)
}
if (error) {
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-bold">Votes Actifs</h1>
<p className="text-muted-foreground mt-2">
Participez aux élections et scrutins en cours
</p>
</div>
<Card className="border-red-500 bg-red-50 dark:bg-red-950">
<CardContent className="pt-6 flex gap-4">
<AlertCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" />
<div>
<h3 className="font-semibold text-red-900 dark:text-red-100">Erreur</h3>
<p className="text-sm text-red-800 dark:text-red-200 mt-1">{error}</p>
</div>
</CardContent>
</Card>
</div>
)
}
if (elections.length === 0) {
return (
<div className="space-y-8">
<div>
<h1 className="text-3xl font-bold">Votes Actifs</h1>
<p className="text-muted-foreground mt-2">
Participez aux élections et scrutins en cours
</p>
</div>
<Card>
<CardContent className="pt-6 text-center py-12">
<div className="text-5xl mb-4">📭</div>
<h3 className="font-semibold text-lg">Aucune élection active</h3>
<p className="text-sm text-muted-foreground mt-2">
Il n'y a actuellement aucune élection en cours. Revenez bientôt.
</p>
</CardContent>
</Card>
</div>
)
}
return (
<div className="space-y-8">
@ -33,66 +128,49 @@ export default function ActiveVotesPage() {
{/* Filters */}
<div className="flex gap-2 flex-wrap">
<Button variant="default" size="sm">
Tous ({activeVotes.length})
</Button>
<Button variant="outline" size="sm">
Nationales (1)
Tous ({elections.length})
</Button>
</div>
{/* Votes List */}
{/* Elections List */}
<div className="grid gap-6">
{activeVotes.map((vote) => (
<Card key={vote.id} className="overflow-hidden hover:border-accent transition-colors">
{elections.map((election) => (
<Card key={election.id} className="overflow-hidden hover:border-accent transition-colors">
<CardHeader className="pb-0">
<div className="space-y-4">
{/* Title and Category */}
{/* Title */}
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<CardTitle className="text-xl">{vote.title}</CardTitle>
<CardDescription className="mt-2">{vote.description}</CardDescription>
<CardTitle className="text-xl">{election.name}</CardTitle>
{election.description && (
<CardDescription className="mt-2">{election.description}</CardDescription>
)}
</div>
<span className="px-3 py-1 rounded-full bg-accent/10 text-accent text-xs font-medium whitespace-nowrap">
{vote.category}
En cours
</span>
</div>
{/* Stats Row */}
<div className="grid grid-cols-3 gap-4 py-4 border-y border-border">
<div className="grid grid-cols-2 gap-4 py-4 border-y border-border">
<div>
<p className="text-xs text-muted-foreground">Candidats</p>
<p className="text-lg font-bold">{vote.candidates}</p>
<p className="text-lg font-bold">{election.candidates?.length || 0}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Votes</p>
<p className="text-lg font-bold">{vote.votes.toLocaleString()}</p>
</div>
<div>
<p className="text-xs text-muted-foreground">Participation</p>
<p className="text-lg font-bold">{vote.progress}%</p>
</div>
</div>
{/* Progress Bar */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Participation</span>
<span className="font-medium">{vote.progress}%</span>
</div>
<div className="w-full h-2 bg-muted rounded-full overflow-hidden">
<div
className="h-full bg-gradient-to-r from-accent to-accent/60 transition-all"
style={{ width: `${vote.progress}%` }}
/>
<p className="text-xs text-muted-foreground">Date de fin</p>
<p className="text-sm font-bold">
{new Date(election.end_date).toLocaleDateString("fr-FR")}
</p>
</div>
</div>
{/* Footer */}
<div className="flex items-center justify-between pt-2">
<span className="text-sm text-muted-foreground">
Ferme le {vote.endDate}
Ferme le {new Date(election.end_date).toLocaleString("fr-FR")}
</span>
<Link href={`/dashboard/votes/active/${vote.id}`}>
<Link href={`/dashboard/votes/active/${election.id}`}>
<Button>
Participer
<ChevronRight className="w-4 h-4 ml-2" />