refactor: Remove all mock data and use real API data for elections and voting
This commit is contained in:
parent
a73c713b9c
commit
c367dbaf43
@ -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 été enregistré et chiffré de manière sécurisée.
|
||||
Votre vote a été 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"
|
||||
|
||||
@ -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" />
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user