From 2b8adc1e30e32a730243a1ea9c548eedc3a0848b Mon Sep 17 00:00:00 2001 From: Alexis Bruneteau Date: Fri, 7 Nov 2025 02:47:06 +0100 Subject: [PATCH] feat: Add vote detail page for individual elections (/dashboard/votes/active/[id]) --- .../app/dashboard/votes/active/[id]/page.tsx | 288 ++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 e-voting-system/frontend/app/dashboard/votes/active/[id]/page.tsx diff --git a/e-voting-system/frontend/app/dashboard/votes/active/[id]/page.tsx b/e-voting-system/frontend/app/dashboard/votes/active/[id]/page.tsx new file mode 100644 index 0000000..6ef1b63 --- /dev/null +++ b/e-voting-system/frontend/app/dashboard/votes/active/[id]/page.tsx @@ -0,0 +1,288 @@ +"use client" + +import { useState, useEffect } from "react" +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 { VotingInterface } from "@/components/voting-interface" + +export default function VoteDetailPage() { + const params = useParams() + const voteId = params.id as string + const [election, setElection] = useState(null) + const [isLoading, setIsLoading] = useState(true) + const [hasVoted, setHasVoted] = useState(false) + const [error, setError] = useState(null) + + // Mock elections data - matches the active/page.tsx + const mockElections: Record = { + "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 A" }, + { id: 2, name: "Bob Martin", description: "Candidate B" }, + { id: 3, name: "Claire Laurent", description: "Candidate C" }, + ], + votes: 4521, + }, + "2": { + id: 2, + name: "Référendum : Réforme Constitutionnelle", + description: "Consultez la population sur la nouvelle constitution", + category: "Nationale", + status: "En cours", + progress: 45, + endDate: "2025-11-08T18:00:00", + candidates: [ + { id: 1, name: "Oui", description: "Pour la réforme" }, + { id: 2, name: "Non", description: "Contre la réforme" }, + ], + votes: 2341, + }, + "3": { + id: 3, + name: "Election Municipale - Île-de-France", + description: "Élection locale régionale pour les positions municipales", + category: "Locale", + status: "En cours", + progress: 78, + endDate: "2025-11-10T17:00:00", + candidates: [ + { id: 1, name: "Liste A", description: "Équipe A" }, + { id: 2, name: "Liste B", description: "Équipe B" }, + { id: 3, name: "Liste C", description: "Équipe C" }, + ], + votes: 1234, + }, + "4": { + id: 4, + name: "Conseil Départemental", + description: "Élection des conseillers départementaux", + category: "Régionale", + status: "En cours", + progress: 52, + endDate: "2025-11-12T19:00:00", + candidates: [ + { id: 1, name: "Conseiller A", description: "Candidat 1" }, + { id: 2, name: "Conseiller B", description: "Candidat 2" }, + { id: 3, name: "Conseiller C", description: "Candidat 3" }, + ], + votes: 987, + }, + } + + 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}`, + }, + }) + + if (response.ok) { + const data = await response.json() + setElection(data) + return + } + } catch (err) { + // Fall back to mock data + } + + // Use mock data if API fails + const mockData = mockElections[voteId] + if (mockData) { + setElection(mockData) + } else { + setError("Élection non trouvée") + } + } catch (err) { + setError("Erreur lors du chargement de l'élection") + } finally { + setIsLoading(false) + } + } + + fetchElection() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [voteId]) + + if (isLoading) { + return ( +
+
+ + + +
+ + +

Chargement...

+
+
+
+ ) + } + + if (error || !election) { + return ( +
+
+ + + +
+ + + +
+

Erreur

+

+ {error || "Élection non trouvée"} +

+
+
+
+
+ ) + } + + return ( +
+ {/* Header */} +
+
+ + + +
+
+

{election.name}

+

{election.description}

+
+
+ + {/* Election Info */} +
+ + + + + Candidats + + + +

{election.candidates?.length || 0}

+
+
+ + + + + + Votes + + + +

{election.votes?.toLocaleString() || 0}

+
+
+ + + + + + Statut + + + +

{election.status}

+
+
+
+ + {/* Voting Interface */} + {!hasVoted ? ( + + + Voter + + Sélectionnez votre choix et confirmez votre vote + + + + { + setHasVoted(true) + }} + /> + + + ) : ( + + + +
+

Vote enregistré

+

+ Votre vote a été enregistré et chiffré de manière sécurisée. +

+ + Voir l'historique de vos votes → + +
+
+
+ )} + + {/* Candidates List */} + {election.candidates && election.candidates.length > 0 && ( + + + Candidats + + +
+ {election.candidates.map((candidate: any) => ( +
+

{candidate.name}

+ {candidate.description && ( +

{candidate.description}

+ )} +
+ ))} +
+
+
+ )} +
+ ) +}