From 4e8b592feb4e67e1b06f56b90c14050a1d749246 Mon Sep 17 00:00:00 2001 From: Yannick Le Duc Date: Mon, 25 Aug 2025 15:32:15 +0200 Subject: [PATCH] ajout de slider sur la page de vote --- src/app/admin/page.tsx | 3 - .../[id]/vote/[participantId]/page.tsx | 211 +++++++++--------- src/app/globals.css | 65 ++++++ 3 files changed, 175 insertions(+), 104 deletions(-) diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 23d3ea2..acf93d7 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -214,9 +214,6 @@ export default function AdminPage() { -
- Paliers de dépenses: {campaign.spending_tiers} -
{campaign.status === 'deposit' && (
diff --git a/src/app/campaigns/[id]/vote/[participantId]/page.tsx b/src/app/campaigns/[id]/vote/[participantId]/page.tsx index 9340f3b..c7903ae 100644 --- a/src/app/campaigns/[id]/vote/[participantId]/page.tsx +++ b/src/app/campaigns/[id]/vote/[participantId]/page.tsx @@ -21,6 +21,9 @@ export default function PublicVotePage() { const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const [success, setSuccess] = useState(false); + + // Votes temporaires stockés localement + const [localVotes, setLocalVotes] = useState>({}); const [totalVoted, setTotalVoted] = useState(0); useEffect(() => { @@ -29,6 +32,12 @@ export default function PublicVotePage() { } }, [campaignId, participantId]); + // Calculer le total voté à partir des votes locaux + useEffect(() => { + const total = Object.values(localVotes).reduce((sum, amount) => sum + amount, 0); + setTotalVoted(total); + }, [localVotes]); + const loadData = async () => { try { setLoading(true); @@ -70,9 +79,12 @@ export default function PublicVotePage() { setPropositions(propositionsWithVotes); - // Calculer le total voté - const total = votes.reduce((sum, vote) => sum + vote.amount, 0); - setTotalVoted(total); + // Initialiser les votes locaux avec les votes existants + const initialVotes: Record = {}; + votes.forEach(vote => { + initialVotes[vote.proposition_id] = vote.amount; + }); + setLocalVotes(initialVotes); } catch (error) { console.error('Erreur lors du chargement des données:', error); @@ -82,37 +94,18 @@ export default function PublicVotePage() { } }; - const handleVoteChange = async (propositionId: string, amount: number) => { - try { - const existingVote = propositions.find(p => p.id === propositionId)?.vote; - - if (amount === 0) { - // Si on sélectionne "Aucun vote", on supprime le vote existant s'il y en a un - if (existingVote) { - await voteService.delete(existingVote.id); - } - } else { - // Sinon on crée ou met à jour le vote - if (existingVote) { - // Mettre à jour le vote existant - await voteService.update(existingVote.id, { amount }); - } else { - // Créer un nouveau vote - await voteService.create({ - campaign_id: campaignId, - participant_id: participantId, - proposition_id: propositionId, - amount - }); - } - } - - // Recharger les données - await loadData(); - - } catch (error) { - console.error('Erreur lors du vote:', error); - setError('Erreur lors de l\'enregistrement du vote'); + const handleVoteChange = (propositionId: string, amount: number) => { + if (amount === 0) { + // Si on sélectionne "Aucun vote", on supprime le vote local + const newLocalVotes = { ...localVotes }; + delete newLocalVotes[propositionId]; + setLocalVotes(newLocalVotes); + } else { + // Sinon on met à jour le vote local + setLocalVotes(prev => ({ + ...prev, + [propositionId]: amount + })); } }; @@ -126,7 +119,24 @@ export default function PublicVotePage() { setError(''); try { - // Les votes sont déjà sauvegardés, on affiche juste le succès + // Supprimer tous les votes existants pour ce participant + const existingVotes = await voteService.getByParticipant(campaignId, participantId); + for (const vote of existingVotes) { + await voteService.delete(vote.id); + } + + // Créer les nouveaux votes + for (const [propositionId, amount] of Object.entries(localVotes)) { + if (amount > 0) { + await voteService.create({ + campaign_id: campaignId, + participant_id: participantId, + proposition_id: propositionId, + amount + }); + } + } + setSuccess(true); } catch (error) { console.error('Erreur lors de la validation:', error); @@ -217,7 +227,7 @@ export default function PublicVotePage() { return (
- {/* Header fixe avec le total */} + {/* Header fixe avec le total et le bouton de validation */}
@@ -239,17 +249,31 @@ export default function PublicVotePage() {
-
-
- {totalVoted}€ / {campaign?.budget_per_user}€ -
-
- {voteStatus.message} +
+
+
+ {totalVoted}€ / {campaign?.budget_per_user}€ +
+
+ {voteStatus.message} +
+ +
@@ -258,20 +282,11 @@ export default function PublicVotePage() {
{/* Informations de la campagne */}
-

Informations sur la campagne

-
+

Description

{campaign?.description}

-
-

Budget par participant

-

{campaign?.budget_per_user}€

-
-
-

Paliers de dépenses

-

{campaign?.spending_tiers}

-
@@ -297,40 +312,51 @@ export default function PublicVotePage() {

{proposition.description}

-
- Auteur : {proposition.author_first_name} {proposition.author_last_name} -
-
- {spendingTiers.map((tier) => ( - - ))} - +
+ {/* Slider */} +
+ { + const index = parseInt(e.target.value); + const amount = index === 0 ? 0 : spendingTiers[index - 1]; + handleVoteChange(proposition.id, amount); + }} + className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer slider" + /> + + {/* Marqueurs des paliers */} +
+
+
+ 0€ +
+ {spendingTiers.map((tier, index) => ( +
+
+ {tier}€ +
+ ))} +
+
+ + {/* Valeur sélectionnée */} +
+ + Vote sélectionné : {localVotes[proposition.id] || 0}€ + +
@@ -339,23 +365,6 @@ export default function PublicVotePage() {
)} - {/* Bouton de validation */} - {propositions.length > 0 && ( -
- -
- )} - {error && (
{error} diff --git a/src/app/globals.css b/src/app/globals.css index a2dc41e..e8bacd3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -24,3 +24,68 @@ body { color: var(--foreground); font-family: Arial, Helvetica, sans-serif; } + +/* Styles personnalisés pour le slider */ +.slider { + -webkit-appearance: none; + appearance: none; + width: 100%; + height: 8px; + border-radius: 4px; + background: #e5e7eb; + outline: none; + cursor: pointer; +} + +.slider::-webkit-slider-track { + width: 100%; + height: 8px; + border-radius: 4px; + background: #e5e7eb; + border: 1px solid #d1d5db; +} + +.slider::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 24px; + height: 24px; + border-radius: 50%; + background: #4f46e5; + cursor: pointer; + border: 2px solid #ffffff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + transition: all 0.2s ease; +} + +.slider::-webkit-slider-thumb:hover { + background: #3730a3; + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +.slider::-moz-range-track { + width: 100%; + height: 8px; + border-radius: 4px; + background: #e5e7eb; + border: 1px solid #d1d5db; + outline: none; +} + +.slider::-moz-range-thumb { + width: 24px; + height: 24px; + border-radius: 50%; + background: #4f46e5; + cursor: pointer; + border: 2px solid #ffffff; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15); + transition: all 0.2s ease; +} + +.slider::-moz-range-thumb:hover { + background: #3730a3; + transform: scale(1.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +}