diff --git a/components/admin/GradeField.tsx b/components/admin/GradeField.tsx index f9e3ede..0c52207 100644 --- a/components/admin/GradeField.tsx +++ b/components/admin/GradeField.tsx @@ -9,7 +9,7 @@ import { faRotateLeft, } from '@fortawesome/free-solid-svg-icons'; import {useElection, useElectionDispatch} from '@services/ElectionContext'; -import {gradeColors} from '@services/grades'; +import {getGradeColor, gradeColors} from '@services/grades'; import {useSortable} from '@dnd-kit/sortable'; @@ -79,15 +79,3 @@ export default ({value}: GradeInterface) => { }; -const getGradeColor = (gradeIdx: number, numGrades: number): string => { - const extraColors = gradeColors.length - numGrades; - if (extraColors < 0) { - throw Error("More grades than available colors"); - } - const startIndex = Math.floor(extraColors / 2); - const colors = gradeColors.slice(startIndex, gradeColors.length - (extraColors - startIndex)); - if (colors.length < numGrades) { - throw Error("Issue with the number of colors"); - } - return colors[colors.length - gradeIdx - 1] -} diff --git a/pages/admin/confirm/[pid].tsx b/pages/admin/confirm/[pid].tsx index e2f4841..62f3091 100644 --- a/pages/admin/confirm/[pid].tsx +++ b/pages/admin/confirm/[pid].tsx @@ -4,7 +4,7 @@ import { useRouter } from 'next/router'; import { useTranslation } from 'next-i18next'; import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; import { - getDetails, + getElection, apiErrors, ELECTION_NOT_STARTED_ERROR, } from '@services/api'; @@ -26,7 +26,7 @@ import config from '../../../next-i18next.config.js'; import { AnimatePresence, motion } from 'framer-motion'; export async function getServerSideProps({ query: { pid }, locale }) { let [details, translations] = await Promise.all([ - getDetails(pid, console.log, console.log), + getElection(pid, console.log, console.log), serverSideTranslations(locale, [], config), ]); diff --git a/pages/result/[pid]/[[...tid]].tsx b/pages/result/[pid]/[[...tid]].tsx index 2dd6fad..36dde17 100644 --- a/pages/result/[pid]/[[...tid]].tsx +++ b/pages/result/[pid]/[[...tid]].tsx @@ -1,8 +1,8 @@ -import { useState } from 'react'; +import {useState} from 'react'; import Head from 'next/head'; -import { useTranslation } from 'next-i18next'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import { useRouter } from 'next/router'; +import {useTranslation} from 'next-i18next'; +import {serverSideTranslations} from 'next-i18next/serverSideTranslations'; +import {useRouter} from 'next/router'; import Link from 'next/link'; import { Container, @@ -15,37 +15,36 @@ import { Table, Button, } from 'reactstrap'; -import { getResults, getDetails, apiErrors } from '@services/api'; -import { translateGrades } from '@services/grades'; -import Error from '@components/Error'; +import {getResults, getElection, apiErrors, ResultsPayload} from '@services/api'; +import ErrorMessage from '@components/Error'; import config from '../../../next-i18next.config.js'; import Footer from '@components/layouts/Footer'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; import { faChevronDown, faChevronRight, faChevronUp, } from '@fortawesome/free-solid-svg-icons'; -export async function getServerSideProps({ query, locale }) { - const { pid, tid } = query; +export async function getServerSideProps({query, locale}) { + const {pid, tid} = query; const [res, details, translations] = await Promise.all([ getResults(pid), - getDetails(pid), + getElection(pid), serverSideTranslations(locale, [], config), ]); if (typeof res === 'string' || res instanceof String) { - return { props: { err: res.slice(1, -1), ...translations } }; + return {props: {err: res.slice(1, -1), ...translations}}; } if (typeof details === 'string' || details instanceof String) { - return { props: { err: res.slice(1, -1), ...translations } }; + return {props: {err: res.slice(1, -1), ...translations}}; } if (!details.candidates || !Array.isArray(details.candidates)) { - return { props: { err: 'Unknown error', ...translations } }; + return {props: {err: 'Unknown error', ...translations}}; } return { @@ -60,23 +59,23 @@ export async function getServerSideProps({ query, locale }) { }; } -const Result = ({ candidates, numGrades, title, pid, err, finish }) => { - const { t } = useTranslation(); +interface ResultsInterface { + results: ResultsPayload; + err: string; +} + + +const Results = ({results, err}: ResultsInterface) => { + const {t} = useTranslation(); - const newstart = new Date(finish * 1000).toLocaleDateString('fr-FR'); + const newstart = new Date(results.date_end).toLocaleDateString(); if (err && err !== '') { - return ; + return ; } const router = useRouter(); - const allGrades = translateGrades(t); - const grades = allGrades.filter( - (grade) => grade.value >= allGrades.length - numGrades - ); - const offsetGrade = grades.length - numGrades; - const colSizeCandidateLg = 4; const colSizeCandidateMd = 6; const colSizeCandidateXs = 12; @@ -88,27 +87,36 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => { typeof window !== 'undefined' && window.location.origin ? window.location.origin : 'http://localhost'; - const urlVote = new URL(`/vote/${pid}`, origin); + const urlVote = new URL(`/vote/${results.id}`, origin); - const collapsee = candidates[0].title; - const [collapseProfiles, setCollapseProfiles] = useState(false); + if (typeof results.candidates === "undefined" || results.candidates.length === 0) { + throw Error("No candidates were loaded in this election") + } + + // const collapsee = results.candidates[0].name; + // const [collapseProfiles, setCollapseProfiles] = useState(false); const [collapseGraphics, setCollapseGraphics] = useState(false); const sum = (seq: Array) => Object.values(seq).reduce((a, b) => a + b, 0); - const numVotes = - candidates && candidates.length > 0 ? sum(candidates[0].profile) : 1; - const gradeIds = - candidates && candidates.length > 0 - ? Object.keys(candidates[0].profile) - : []; + const anyCandidateName = results.candidates[0].name; + const numVotes = sum(results.votes[anyCandidateName]); + + // check each vote contains the same number of votes + // TODO move it to a more appropriate location + Object.values(results.votes).forEach(v => { + if (sum(v) !== numVotes) { + throw Error("The election does not contain the same numberof votes for each candidate") + } + }) + const gradeIds = results.grades.map(g => g.value); return ( - {title} + {results.name} - + @@ -127,7 +135,7 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => { -

{title}

+

{results.name}

@@ -148,7 +156,7 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => { -

{title}

+

{results.name}

@@ -166,7 +174,7 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => {
    - {candidates.map((candidate, i) => { + { /* {results.candidates.map((candidate, i) => { const gradeValue = candidate.grade + offsetGrade; return (
  1. @@ -186,13 +194,14 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => {
  2. ); })} + */}
-
+ {/*
{t('Détails des résultats')}
{candidates.map((candidate, i) => { @@ -249,13 +258,12 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => {
-
+
- {/*candidate.label*/} -
+
{gradeIds .slice(0) .reverse() @@ -305,6 +313,7 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => { ); })} + */}
@@ -326,4 +335,5 @@ const Result = ({ candidates, numGrades, title, pid, err, finish }) => { ); }; -export default Result; + +export default Results; diff --git a/pages/vote/[pid]/[[...tid]].tsx b/pages/vote/[pid]/[[...tid]].tsx index 6b40e30..fc0b32d 100644 --- a/pages/vote/[pid]/[[...tid]].tsx +++ b/pages/vote/[pid]/[[...tid]].tsx @@ -1,8 +1,8 @@ -import { useState, useCallback, useEffect } from 'react'; +import {useState, useCallback, useEffect} from 'react'; import Head from 'next/head'; -import { useRouter } from 'next/router'; -import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; -import { useTranslation } from 'next-i18next'; +import {useRouter} from 'next/router'; +import {serverSideTranslations} from 'next-i18next/serverSideTranslations'; +import {useTranslation} from 'next-i18next'; import { Button, Col, @@ -14,11 +14,10 @@ import { } from 'reactstrap'; import Link from 'next/link'; // import {toast, ToastContainer} from "react-toastify"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck } from '@fortawesome/free-solid-svg-icons'; -import { getDetails, castBallot, apiErrors } from '@services/api'; -import Error from '@components/Error'; -import { translateGrades } from '@services/grades'; +import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'; +import {faCheck} from '@fortawesome/free-solid-svg-icons'; +import {getElection, castBallot, apiErrors, ElectionPayload} from '@services/api'; +import ErrorMessage from '@components/Error'; import Footer from '@components/layouts/Footer'; import useEmblaCarousel from 'embla-carousel-react'; import { @@ -27,21 +26,22 @@ import { NextButton, } from '@components/admin/EmblaCarouselButtons'; import VoteButtonWithConfirm from '@components/admin/VoteButtonWithConfirm'; +import {getGradeColor} from '@services/grades'; const shuffle = (array) => array.sort(() => Math.random() - 0.5); -export async function getServerSideProps({ query: { pid, tid }, locale }) { +export async function getServerSideProps({query: {pid, tid}, locale}) { const [details, translations] = await Promise.all([ - getDetails(pid), + getElection(pid), serverSideTranslations(locale, ['resource']), ]); if (typeof details === 'string' || details instanceof String) { - return { props: { err: details, ...translations } }; + return {props: {err: details, ...translations}}; } if (!details.candidates || !Array.isArray(details.candidates)) { - return { props: { err: 'Unknown error', ...translations } }; + return {props: {err: 'Unknown error', ...translations}}; } shuffle(details.candidates); @@ -53,7 +53,7 @@ export async function getServerSideProps({ query: { pid, tid }, locale }) { restrictResults: details.restrict_results, candidates: details.candidates.map((name, i, infos) => ({ id: i, - label: name, + name: name, description: infos, })), title: details.title, @@ -64,13 +64,20 @@ export async function getServerSideProps({ query: { pid, tid }, locale }) { }; } -const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { - const { t } = useTranslation(); +interface VoteInterface { + election: ElectionPayload; + err: string; + token?: string; +} + +const VoteBallot = ({election, err, token}: VoteInterface) => { + const {t} = useTranslation(); if (err) { - return ; + return ; } + const numGrades = election.grades.length; const [judgments, setJudgments] = useState([]); const colSizeCandidateLg = 4; const colSizeCandidateMd = 6; @@ -81,11 +88,6 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { const router = useRouter(); - const allGrades = translateGrades(t); - const grades = allGrades.filter( - (grade) => grade.value >= allGrades.length - numGrades - ); - const handleGradeClick = (event) => { let data = { id: parseInt(event.currentTarget.getAttribute('data-id')), @@ -115,8 +117,8 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { gradesByCandidate.push(gradesById[id]); }); - castBallot(gradesByCandidate, pid, token, () => { - router.push(`/vote/${pid}/confirm`); + castBallot(gradesByCandidate, election.id.toString(), token, () => { + router.push(`/vote/${election.id}/confirm`); }); }; const toggle = () => setVisibility(!visibled); @@ -126,7 +128,7 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { const toggleDesktop = () => setVisibilityDesktop(!visibledDesktop); const [visibledDesktop, setVisibilityDesktop] = useState(false); - const [viewportRef, embla] = useEmblaCarousel({ skipSnaps: false }); + const [viewportRef, embla] = useEmblaCarousel({skipSnaps: false}); const [prevBtnEnabled, setPrevBtnEnabled] = useState(false); const [nextBtnEnabled, setNextBtnEnabled] = useState(false); const [selectedIndex, setSelectedIndex] = useState(0); @@ -156,10 +158,9 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { return ( - {title} + {election.name} - {title} - + { className="modalVote voteDesktop" >
- {title} + {election.name}
- {candidates.map((candidate, candidateId) => { + {election.candidates.map((candidate, candidateId) => { return ( -
{candidate.label}
-
{candidate.infos}
+
{candidate.name}
+
{candidate.description}
- {grades.map((grade, gradeId) => { + {election.grades.map((grade, gradeId) => { console.assert(gradeId < numGrades); const gradeValue = grade.value; + const color = getGradeColor(gradeId, numGrades); return ( { ); }) ? { - backgroundColor: grade.color, - color: '#fff', - } + backgroundColor: color, + color: '#fff', + } : { - backgroundColor: 'transparent', - color: '#000', - } + backgroundColor: 'transparent', + color: '#000', + } } > - {grade.label} + {grade.name} { ); }) ? { - backgroundColor: grade.color, - color: '#fff', - } + backgroundColor: color, + color: '#fff', + } : { - backgroundColor: '#C3BFD8', - color: '#000', - } + backgroundColor: '#C3BFD8', + color: '#000', + } } > { color: '#fff', }} > - {grade.label} + {grade.name} @@ -430,7 +432,7 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { - {judgments.length !== candidates.length ? ( + {judgments.length !== election.candidates.length ? ( @@ -454,23 +456,24 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => { className="modalVote voteMobile" >
- {title} + {election.name}
- {candidates.map((candidate, candidateId) => { + {election.candidates.map((candidate, candidateId) => { return (
-
{candidate.label}
+
{candidate.name}
{candidate.id + 1}
- {grades.map((grade, gradeId) => { + {election.grades.map((grade, gradeId) => { console.assert(gradeId < numGrades); const gradeValue = grade.value; + const color = getGradeColor(gradeId, numGrades); return ( { ); }) ? { - backgroundColor: grade.color, - color: '#fff', - } + backgroundColor: color, + color: '#fff', + } : { - backgroundColor: 'transparent', - color: '#000', - } + backgroundColor: 'transparent', + color: '#000', + } } > - {grade.label} + {grade.name} { ); }) ? { - backgroundColor: grade.color, - color: '#fff', - } + backgroundColor: color, + color: '#fff', + } : { - backgroundColor: '#C3BFD8', - color: '#000', - } + backgroundColor: '#C3BFD8', + color: '#000', + } } > { color: '#fff', }} > - {grade.label} + {grade.name} @@ -606,7 +609,7 @@ const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
- {judgments.length !== candidates.length ? ( + {judgments.length !== election.candidates.length ? ( ) : (