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

162 lines
5.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import { X, Users, BarChart3, CheckCircle } from 'lucide-react';
import './ElectionDetailsModal.css';
export default function ElectionDetailsModal({ electionId, isOpen, onClose, voter = null, type = 'historique' }) {
const [election, setElection] = useState(null);
const [results, setResults] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [userVote, setUserVote] = useState(null);
useEffect(() => {
if (isOpen && electionId) {
fetchElectionDetails();
}
}, [isOpen, electionId]);
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/${electionId}`);
if (!electionResponse.ok) {
throw new Error('Élection non trouvée');
}
const electionData = await electionResponse.json();
setElection(electionData);
// 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/${electionId}`, {
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/${electionId}/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',
});
};
if (!isOpen) return null;
return (
<div className="modal-overlay" onClick={onClose}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<div className="modal-header">
<h2>{election?.name || 'Détails de l\'élection'}</h2>
<button className="modal-close" onClick={onClose}>
<X size={24} />
</button>
</div>
{loading && <div className="modal-loading">Chargement...</div>}
{error && <div className="modal-error">{error}</div>}
{election && !loading && (
<div className="modal-body">
<div className="modal-section">
<h3>📋 Informations</h3>
<p className="modal-description">{election.description}</p>
<div className="modal-dates">
<div>
<label>Ouverture</label>
<p>{formatDate(election.start_date)}</p>
</div>
<div>
<label>Fermeture</label>
<p>{formatDate(election.end_date)}</p>
</div>
</div>
</div>
<div className="modal-section">
<h3>👥 Candidats ({election.candidates?.length || 0})</h3>
<div className="modal-candidates">
{election.candidates?.map((candidate, index) => (
<div key={candidate.id} className="modal-candidate">
<span className="candidate-number">{candidate.order || index + 1}</span>
<div>
<h4>{candidate.name}</h4>
{candidate.description && <p>{candidate.description}</p>}
</div>
</div>
))}
</div>
</div>
{results && election.results_published && (
<div className="modal-section">
<h3>📊 Résultats</h3>
<p className="modal-total-votes">
<Users size={18} />
Total: <strong>{results.total_votes} vote(s)</strong>
</p>
<div className="modal-results">
{results.results?.map((result, index) => (
<div key={index} className="modal-result-item">
<div className="modal-result-header">
<span className="modal-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="modal-result-percentage">{result.percentage.toFixed(1)}%</span>
</div>
<div className="modal-result-bar">
<div
className="modal-result-bar-fill"
style={{ width: `${result.percentage}%` }}
></div>
</div>
<span className="modal-result-count">{result.vote_count} vote(s)</span>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
</div>
);
}