CIA/e-voting-system/.backups/frontend-old/src/pages/ElectionDetailsPage.jsx
Alexis Bruneteau 14eff8d0da feat: Rebuild frontend with Next.js and shadcn/ui components
- Migrate from React CRA to Next.js 15 with modern architecture
- Implement comprehensive shadcn/ui component library
- Create complete dashboard system with layouts and navigation
- Build authentication pages (login, register) with proper forms
- Implement vote management pages (active, upcoming, history, archives)
- Add user profile management with security settings
- Configure Tailwind CSS with custom dark theme (accent: #e8704b)
- Setup TypeScript with strict type checking
- Backup old React-based frontend to .backups/frontend-old
- All pages compile successfully and build passes linting

Pages created:
- Home page with hero section and features
- Authentication (login/register)
- Dashboard with stats and vote cards
- Vote management (active, upcoming, history, archives)
- User profile with form validation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 17:02:14 +01:00

254 lines
8.6 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { useParams, useNavigate } from 'react-router-dom';
import { ArrowLeft, Calendar, Users, BarChart3, CheckCircle } from 'lucide-react';
import LoadingSpinner from '../components/LoadingSpinner';
import Alert from '../components/Alert';
import './ElectionDetailsPage.css';
export default function ElectionDetailsPage({ voter = null, type = 'archives' }) {
const { id } = useParams();
const navigate = useNavigate();
const [election, setElection] = useState(null);
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [candidates, setCandidates] = useState([]);
const [userVote, setUserVote] = useState(null);
useEffect(() => {
fetchElectionDetails();
}, [id]);
const fetchElectionDetails = async () => {
try {
setLoading(true);
setError('');
// Récupérer les détails de l'élection
const electionResponse = await fetch(`http://localhost:8000/api/elections/${id}`);
if (!electionResponse.ok) {
throw new Error('Élection non trouvée');
}
const electionData = await electionResponse.json();
setElection(electionData);
setCandidates(electionData.candidates || []);
// Récupérer le vote de l'utilisateur si connecté et type = historique
if (voter && type === 'historique') {
try {
const token = localStorage.getItem('token');
const userVoteResponse = await fetch(`http://localhost:8000/api/votes/election/${id}`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (userVoteResponse.ok) {
const userVoteData = await userVoteResponse.json();
setUserVote(userVoteData);
}
} catch (err) {
console.warn('Impossible de récupérer le vote utilisateur');
}
}
// Récupérer les résultats si l'élection est terminée
if (electionData.results_published) {
try {
const resultsResponse = await fetch(`http://localhost:8000/api/elections/${id}/results`);
if (resultsResponse.ok) {
const resultsData = await resultsResponse.json();
setResults(resultsData);
}
} catch (err) {
console.warn('Résultats non disponibles');
}
}
} catch (err) {
setError(err.message || 'Erreur de chargement');
console.error('Erreur:', err);
} finally {
setLoading(false);
}
};
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'long',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
const getElectionStatus = () => {
if (!election) return '';
const now = new Date();
const start = new Date(election.start_date);
const end = new Date(election.end_date);
if (now < start) return 'À venir';
if (now > end) return 'Terminée';
return 'En cours';
};
if (loading) return <LoadingSpinner fullscreen />;
if (error) {
return (
<div className="election-details-page">
<div className="container">
<button className="btn btn-ghost" onClick={() => navigate(-1)}>
<ArrowLeft size={20} />
Retour
</button>
<Alert type="error" title="Erreur" message={error} />
</div>
</div>
);
}
if (!election) {
return (
<div className="election-details-page">
<div className="container">
<button className="btn btn-ghost" onClick={() => navigate(-1)}>
<ArrowLeft size={20} />
Retour
</button>
<Alert type="error" title="Erreur" message="Élection non trouvée" />
</div>
</div>
);
}
const status = getElectionStatus();
const statusColor = status === 'Terminée' ? 'closed' : status === 'En cours' ? 'active' : 'future';
return (
<div className="election-details-page">
<div className="container">
<button
className="btn btn-ghost btn-back"
onClick={() => {
if (type === 'historique') {
navigate('/dashboard/historique');
} else if (type === 'futur') {
navigate('/dashboard/futurs');
} else {
navigate('/archives');
}
}}
>
<ArrowLeft size={20} />
Retour
</button>
<div className="details-header">
<div>
<h1>{election.name}</h1>
<span className={`status-badge status-${statusColor}`}>{status}</span>
</div>
</div>
<div className="details-grid">
{/* Section Informations */}
<div className="details-card">
<h2>📋 Informations</h2>
<p className="description">{election.description}</p>
<div className="info-section">
<div className="info-item">
<Calendar size={20} />
<div>
<label>Ouverture</label>
<p>{formatDate(election.start_date)}</p>
</div>
</div>
<div className="info-item">
<Calendar size={20} />
<div>
<label>Fermeture</label>
<p>{formatDate(election.end_date)}</p>
</div>
</div>
</div>
</div>
{/* Section Candidats */}
<div className="details-card">
<h2>👥 Candidats ({candidates.length})</h2>
<div className="candidates-list">
{candidates.map((candidate, index) => (
<div key={candidate.id} className="candidate-item">
<div className="candidate-number">{candidate.order || index + 1}</div>
<div className="candidate-info">
<h3>{candidate.name}</h3>
{candidate.description && <p>{candidate.description}</p>}
</div>
</div>
))}
</div>
</div>
{/* Section Résultats */}
{results && election.results_published && (
<div className="details-card results-card">
<h2>📊 Résultats</h2>
<div className="results-section">
{results.results && results.results.length > 0 ? (
<>
<p className="total-votes">
<Users size={18} />
Total: <strong>{results.total_votes} vote(s)</strong>
</p>
<div className="results-list">
{results.results.map((result, index) => (
<div key={index} className="result-item">
<div className="result-header">
<span className="result-name">
{userVote && userVote.candidate_name === result.candidate_name && (
<CheckCircle size={16} style={{ display: 'inline', marginRight: '8px', color: '#4CAF50' }} />
)}
{result.candidate_name}
</span>
<span className="result-percentage">{result.percentage.toFixed(1)}%</span>
</div>
<div className="result-bar">
<div
className="result-bar-fill"
style={{ width: `${result.percentage}%` }}
></div>
</div>
<span className="result-count">{result.vote_count} vote(s)</span>
</div>
))}
</div>
</>
) : (
<p className="no-results">Aucun résultat disponible</p>
)}
</div>
</div>
)}
{!election.results_published && status === 'Terminée' && (
<div className="details-card">
<p className="info-message">
📊 Les résultats de cette élection n'ont pas encore été publiés.
</p>
</div>
)}
{status !== 'Terminée' && (
<div className="details-card">
<p className="info-message">
⏳ Les résultats seront disponibles une fois l'élection terminée.
</p>
</div>
)}
</div>
</div>
</div>
);
}