fix: refactor app

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 99c91d55e3
commit 0c38cddbfe

@ -1,175 +0,0 @@
import React from 'react';
// import Plot from 'react-plotly.js';
function Bulles(props) {
// récupération des résultats de l'élection et stockage en tableau
const votesBrut = Object.values(props)[0];
// déclaration et initialisation des mentions et couleurs
const mentionsBrut = [
'Passable',
'Assez bien',
'Bien',
'Très bien',
'Excellent',
];
const couleursBrut = ['#BB9C42', '#AABA44', '#DCDF44', '#B3D849', '#61AD45'];
//----------- Traitement des données -----------//
// fonction d'inversement des éléments de tableau
function inverse(obj) {
var retobj = {};
for (var key in obj) {
retobj[obj[key]] = key;
}
return retobj;
}
// fonction de réduction d'amplitude permettant de conserver une représentation ordinale du nombre de votes sans décalage visuel trop important
/*
Pattern de calcul :
Soient Ai, Bi, Ci, Di, Ei les nombres de votes initiaux fournis dans le tableau classé par ordre mélioratif de mention (de Passable à Excellent). Il vient :
A = 1
B = <{[1 + (Bi/Ai)] / 40} * A>
C = <{[1 + (Ci/Bi)] / 40} * B>
D = <{[1 + (Di/Ci)] / 40} * C>
E = <{[1 + (Ei/Di)] / 40} * D>
*/
function redAmpli(tab) {
var nvTab = [];
nvTab[0] = 100;
for (i = 1; i < tab.length; i++) {
nvTab[i] = (1 + tab[i] / tab[i - 1] / 40) * nvTab[i - 1];
}
return nvTab;
}
// déclaration de l'objet votes-mention et votes-couleur
var votesMentionNonOrdonnes = {};
var votesCouleurNonOrdonnes = {};
// initialisation votes-mention ordonnés croissants
for (var i = 0; i < mentionsBrut.length; i++) {
votesMentionNonOrdonnes[votesBrut[i]] = mentionsBrut[i];
votesCouleurNonOrdonnes[votesBrut[i]] = couleursBrut[i];
}
// déclaration des mentions-votes par ordre croissant
var votesMentionOrdonnes = inverse(votesMentionNonOrdonnes);
var votesCouleurOrdonnes = inverse(votesCouleurNonOrdonnes);
// vérification du nombre de votes classés par ordre croissant et passés initialement en propriétés au composant
console.log(
'Les données transmises au composant concernant le nombre de votes par mention sont : '
);
console.log(votesBrut);
// vérification des mentions destinées à être associées aux votes et ordonnées initialement par ordre mélioratif
console.log(
'Les mentions des votes sont classées initialement par ordre mélioratif de la façon suivante :'
);
console.log(mentionsBrut);
// vérification du nombre de votes classés par ordre croissant
console.log(
'Les mentions-votes classées par ordre croissant de votes sont : '
);
console.log(votesMentionOrdonnes);
// séparation des mentions et des votes
const mentions = Object.keys(votesMentionOrdonnes);
const votes = Object.values(votesMentionOrdonnes);
const couleurs = Object.keys(votesCouleurOrdonnes);
// vérification des mentions et des votes prêts à être traités pour la représentation graphique
console.log(
'La liste des mentions issue du classement par ordre croissant de votes est :'
);
console.log(mentions);
console.log(
'La liste du nombre de votes correspondant, classée par ordre croissant, est :'
);
console.log(votes);
// déclaration et initialisation des rayons de bulle pour la représentation graphique
var rayons = [];
rayons = redAmpli(votes);
// vérification des rayons
console.log(
'La liste des rayons à représenter graphiquement est la suivante :'
);
console.log(rayons);
// déclaration et initialisation des textes des bulles
const texteBulle1 = (mentions[0] + '<br>' + votes[0] + ' votes').toString();
const texteBulle2 = (mentions[1] + '<br>' + votes[1] + ' votes').toString();
const texteBulle3 = (mentions[2] + '<br>' + votes[2] + ' votes').toString();
const texteBulle4 = (mentions[3] + '<br>' + votes[3] + ' votes').toString();
const texteBulle5 = (mentions[4] + '<br>' + votes[4] + ' votes').toString();
//---------------------------------------------//
//----------- Affichage des données -----------//
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
setTimeout(() => setLoading(false), 3000);
});
return (
<div>TBD</div>
// <div>
// {!loading ? (
// <React.Fragment>
// <Plot
// data={[
// {
// x: [0.7, 0.6, 0.5, 0.6, 0.7],
// y: [0.3, 0.4, 0.5, 0.6, 0.5],
// hovertemplate:
// '<b>%{text}</b>' +
// '<extra></extra>',
// text: [texteBulle1, texteBulle2, texteBulle3, texteBulle4, texteBulle5],
// showlegend: false,
// mode: 'markers',
// marker: {
// color: [couleurs[0], couleurs[1], couleurs[2], couleurs[3], couleurs[4]],
// size: rayons
// }
// }
// ]}
// layout={{
// width: 600,
// height: 600,
// title: 'Nombre de voix par candidat',
// xaxis: {
// showgrid: false,
// showticklabels: false,
// showline: false,
// zeroline: false,
// range: [0, 1]
// },
// yaxis: {
// showgrid: false,
// showticklabels: false,
// showline: false,
// zeroline: false,
// range: [0, 1]
// }
// }}
// config={{
// displayModeBar: false // this is the line that hides the bar.
// }}
// />
// </React.Fragment>
// ) : (
// <LoadingScreen />
// )}
// </div>
);
}
export default Bulles;

@ -1,12 +1,13 @@
import { useState, forwardRef, ReactNode } from 'react';
import { Button, Row, Col } from 'reactstrap';
import { useTranslation } from 'next-i18next';
import {useState, forwardRef, ReactNode} from 'react';
import {Button, Row, Col} from 'reactstrap';
import {useTranslation} from 'next-i18next';
import {
faCalendarDays,
faChevronDown,
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import DatePicker from 'react-datepicker';
import {AppTypes, useAppContext} from '@services/context';
interface InputProps {
children?: ReactNode;
@ -15,11 +16,27 @@ interface InputProps {
}
export type ButtonRef = HTMLButtonElement;
const CustomDatePicker = ({ date, setDate, className = '', ...props }) => {
const { t } = useTranslation();
const CustomDatePicker = ({date, setDate, className = '', ...props}) => {
const {t} = useTranslation();
const [_, dispatchApp] = useAppContext();
const handleChange = (date) => {
const now = new Date();
if (+date < +now) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.date-past")
})
} else {
setDate(date)
}
}
const ExampleCustomInput = forwardRef<ButtonRef, InputProps>(
({ value, onClick }, ref) => (
({value, onClick}, ref) => (
<div className="d-grid">
<button onClick={onClick} ref={ref}>
<Row className="p-2 align-items-end">
@ -47,7 +64,7 @@ const CustomDatePicker = ({ date, setDate, className = '', ...props }) => {
selected={date}
className={className}
customInput={<ExampleCustomInput value={null} onClick={null} />}
onChange={(date) => setDate(date)}
onChange={handleChange}
/>
);
// {/*<Button className="example-custom-input"

@ -1,5 +1,5 @@
export default ({children}) => {
return <div className="waiting flex-grow-1 w-100">{children}</div>
return <div className="waiting position-absolute w-100">{children}</div>
}

@ -10,30 +10,24 @@ const AccessResults = () => {
const toggle = () => {
dispatch({
type: ElectionTypes.SET,
field: 'restrictResult',
field: 'hideResults',
value: !election.hideResults,
});
};
return (
<>
<Container className="bg-white p-3 p-md-4">
<div className="d-flex">
<div className="me-auto">
<h4 className="text-dark mb-0">{t('admin.access-results')}</h4>
<Container className="bg-white p-3 p-md-4">
<div className="d-flex">
<div className="me-auto">
<h4 className="text-dark mb-0">{t('admin.access-results')}</h4>
{election.hideResults ?
<p className="text-muted d-none d-md-block">
{t('admin.access-results-desc')}
</p>
</div>
<Switch toggle={toggle} state={election.hideResults} />
</p> : null}
</div>
</Container>
{election.hideResults ? (
<Container className="text-white d-md-none p-3">
{t('admin.access-results-desc')}
</Container>
) : null}
</>
<Switch toggle={toggle} state={!election.hideResults} />
</div>
</Container>
);
};

@ -7,6 +7,7 @@ import {ElectionTypes, useElection} from '@services/ElectionContext';
import Button from '@components/Button';
import {upload} from '@services/imgpush';
import {IMGPUSH_URL} from '@services/constants';
import {AppTypes, useAppContext} from '@services/context';
import defaultAvatar from '../../public/default-avatar.svg';
const CandidateModal = ({isOpen, position, toggle}) => {
@ -21,10 +22,11 @@ const CandidateModal = ({isOpen, position, toggle}) => {
setState((s) => ({...s, image: `${IMGPUSH_URL}/${payload['filename']}`}));
};
const [app, dispatchApp] = useAppContext();
// to manage the hidden ugly file input
const hiddenFileInput = useRef(null);
const names = election.candidates.filter((_, i) => i != position).map(c => c.name)
const disabled = state.name === "" || names.includes(state.name);
@ -32,7 +34,26 @@ const CandidateModal = ({isOpen, position, toggle}) => {
setState(election.candidates[position]);
}, [election]);
const save = () => {
const save = (e) => {
e.preventDefault()
if (state.name === "") {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.empty-name")
})
return
}
if (names.includes(state.name)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.twice-same-names")
})
return
}
dispatch({
type: ElectionTypes.CANDIDATE_SET,
position: position,
@ -153,14 +174,15 @@ const CandidateModal = ({isOpen, position, toggle}) => {
>
{t('common.cancel')}
</Button>
<Button
color={disabled ? "light" : "primary"}
disabled={disabled}
onClick={save}
icon={faPlus}
>
{t('common.save')}
</Button>
<div onClick={save}>
<Button
color={disabled ? "light" : "primary"}
disabled={disabled}
icon={faPlus}
>
{t('common.save')}
</Button>
</div>
</div>
</Form>
</Col>

@ -14,13 +14,12 @@ import LimitDate from './LimitDate';
import Grades from './Grades';
import Private from './Private';
import Order from './Order';
import {useElection, ElectionContextInterface} from '@services/ElectionContext';
import {useElection, ElectionContextInterface, hasEnoughCandidates, hasEnoughGrades, checkName, canBeFinished} from '@services/ElectionContext';
import {createElection, ElectionCreatedPayload} from '@services/api';
import {getUrlVote, getUrlResults} from '@services/routes';
import {GradeItem, CandidateItem} from '@services/type';
import {sendInviteMails} from '@services/mail';
import {gradeColors} from '@services/grades';
import {AppTypes, useAppContext} from '@services/context';
const submitElection = (
@ -68,20 +67,55 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure}) => {
const {t} = useTranslation();
const router = useRouter();
const [election, _] = useElection();
const [app, dispatchApp] = useAppContext();
const handleSubmit = () => {
if (!checkName(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.uncorrect-name")
})
return
}
if (!hasEnoughGrades(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.not-enough-grades")
})
return
}
if (!hasEnoughCandidates(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.not-enough-candidates")
})
return
}
if (!canBeFinished(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.cant-be-finished")
})
return
}
onSubmit();
submitElection(election, onSuccess, onFailure, router);
}
const numCandidates = election.candidates.filter(c => c.active && c.name != "").length;
const numGrades = election.grades.filter(g => g.active && g.name != "").length;
const disabled = (
!election.name || election.name == "" ||
numCandidates < 2 ||
numGrades < 2 || numGrades > gradeColors.length
!checkName(election) ||
!hasEnoughCandidates(election) ||
!hasEnoughGrades(election) ||
!canBeFinished(election)
)
return (
@ -112,17 +146,19 @@ const ConfirmField = ({onSubmit, onSuccess, onFailure}) => {
</Col>
</Row>
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
disabled={disabled}
onClick={handleSubmit}
icon={faArrowRight}
position="right"
>
{t('admin.confirm-submit')}
</Button>
<div
onClick={handleSubmit}>
<Button
outline={true}
color="secondary"
className="bg-blue"
disabled={disabled}
icon={faArrowRight}
position="right"
>
{t('admin.confirm-submit')}
</Button>
</div>
</Container>
</Container>
);

@ -9,17 +9,23 @@ import Switch from '@components/Switch';
import ListInput from '@components/ListInput';
import {ElectionTypes, useElection} from '@services/ElectionContext';
import {validateMail} from '@services/mail';
import {AppTypes, useAppContext} from '@services/context';
const Private = () => {
const {t} = useTranslation();
const [_, dispatchApp] = useAppContext();
const [election, dispatch] = useElection();
const isEditable = !election.ref || election.ref === "";
const toggle = () => {
if (!isEditable) {
return;
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.cant-set-ongoing"),
})
}
dispatch({
type: ElectionTypes.SET,

@ -1,21 +1,28 @@
/**
* This component manages the title of the election
*/
import {faPen} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {useElection} from '@services/ElectionContext';
import {useTranslation} from 'next-i18next';
import {useState} from 'react';
import {Col, Container, Row} from 'reactstrap';
import TitleModal from './TitleModal';
const TitleField = () => {
const {t} = useTranslation();
const [election, _] = useElection();
const [modal, setModal] = useState(false);
const toggle = () => setModal((m) => !m);
return (
<Container className="bg-white p-4">
<Row>
<Col className="col-auto me-auto">
<h5 className="text-dark">{t('admin.confirm-question')}</h5>
</Col>
</Row>
<Container onClick={toggle} className="bg-white p-4">
<div className="w-100 text-dark d-flex justify-content-between">
<h5 className="me-2">{t('admin.confirm-question')}</h5>
<FontAwesomeIcon icon={faPen} />
</div>
<h4 className="text-primary">{election.name}</h4>
<TitleModal isOpen={modal} toggle={toggle} />
</Container>
);
};

@ -0,0 +1,105 @@
import {useState, useEffect} from 'react';
import {Label, Input, Modal, ModalBody, Form} from 'reactstrap';
import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons';
import {useTranslation} from 'next-i18next';
import {ElectionTypes, useElection} from '@services/ElectionContext';
import Button from '@components/Button';
import {checkName} from '@services/ElectionContext';
import {AppTypes, useAppContext} from '@services/context';
import {useRouter} from 'next/router';
const TitleModal = ({isOpen, toggle}) => {
const {t} = useTranslation();
const router = useRouter();
const [election, dispatch] = useElection();
const [_, dispatchApp] = useAppContext();
const [name, setName] = useState(election.name);
const disabled = name === "";
useEffect(() => {
setName(election.name);
}, [election.name])
const save = (e) => {
e.preventDefault()
if (name === "") {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.empty-name")
})
return
}
dispatch({
type: ElectionTypes.SET,
field: 'name',
value: name,
});
toggle();
};
const handleName = (e) => {
setName(e.target.value);
};
return (
<Modal
isOpen={isOpen}
toggle={toggle}
keyboard={true}
className="modal_candidate"
>
<div className="modal-header p-4">
<h4 className="modal-title">{t('admin.set-title')}</h4>
<button
type="button"
onClick={toggle}
className="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
></button>
</div>
<ModalBody className="p-4">
<Form className="container container-fluid">
<div className="mb-3">
<Label className="fw-bold">{t('common.name')} </Label>
<Input
type="text"
placeholder={t('home.writeQuestion')}
value={name}
onChange={handleName}
autoFocus
required
/>
</div>
<div className="mt-5 gap-2 d-grid mb-3 d-md-flex">
<Button
onClick={toggle}
color="dark"
className="me-md-auto"
outline={true}
icon={faArrowLeft}
>
{t('common.cancel')}
</Button>
<div onClick={save}>
<Button
color={disabled ? "light" : "primary"}
disabled={disabled}
icon={faPlus}
>
{t('common.save')}
</Button>
</div>
</div>
</Form>
</ModalBody>
</Modal>
);
};
export default TitleModal;

@ -6,7 +6,7 @@ import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {Container, Row, Col} from 'reactstrap';
import {faArrowRight} from '@fortawesome/free-solid-svg-icons';
import {getElection, updateElection} from '@services/api';
import {ElectionContextInterface, ElectionProvider, ElectionTypes, useElection, isClosed, canViewResults} from '@services/ElectionContext';
import {ElectionContextInterface, ElectionProvider, ElectionTypes, useElection, isClosed, canViewResults, checkName, hasEnoughGrades, hasEnoughCandidates, canBeFinished} from '@services/ElectionContext';
import {CandidateItem, GradeItem} from '@services/type';
import {gradeColors} from '@services/grades';
import TitleField from '@components/admin/Title';
@ -20,6 +20,7 @@ import Private from '@components/admin/Private';
import Blur from '@components/Blur';
import {getUrlResults, getUrlVote, RESULTS, VOTE} from '@services/routes';
import {sendInviteMails} from '@services/mail';
import {AppTypes, useAppContext} from '@services/context';
export async function getServerSideProps({query, locale}) {
@ -78,6 +79,7 @@ const Spinner = () => {
const HeaderRubbon = () => {
const {t} = useTranslation();
const [election, dispatch] = useElection();
const [_, dispatchApp] = useAppContext();
const router = useRouter();
const [waiting, setWaiting] = useState(false);
@ -119,6 +121,11 @@ const HeaderRubbon = () => {
);
}
setWaiting(false)
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "success",
message: t("success.election-closed")
})
}
}
@ -172,6 +179,7 @@ const HeaderRubbon = () => {
const CreateElection = ({context, token}) => {
const {t} = useTranslation();
const [election, dispatch] = useElection();
const [_, dispatchApp] = useAppContext();
const router = useRouter();
const [waiting, setWaiting] = useState(false);
@ -180,6 +188,42 @@ const CreateElection = ({context, token}) => {
}, [])
const handleSubmit = async () => {
if (!checkName(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.uncorrect-name")
})
return
}
if (!hasEnoughGrades(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.not-enough-grades")
})
return
}
if (!hasEnoughCandidates(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.not-enough-candidates")
})
return
}
if (!canBeFinished(election)) {
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "error",
message: t("error.cant-be-finished")
})
return
}
const candidates = election.candidates.filter(c => c.active).map((c: CandidateItem) => ({name: c.name, description: c.description, image: c.image}))
const grades = election.grades.filter(c => c.active).map((g: GradeItem, i: number) => ({name: g.name, value: i}))
setWaiting(true)
@ -212,6 +256,12 @@ const CreateElection = ({context, token}) => {
);
}
setWaiting(false)
dispatchApp({
type: AppTypes.TOAST_ADD,
status: "success",
message: t("success.election-updated")
})
}
}

@ -51,17 +51,27 @@
"common.the-params": "The parameters",
"common.welcome": "Welcome!",
"error.at-least-2-candidates": "At least two candidates are required.",
"error.cant-set-ongoing": "You can not set this parameter for an ongoing election",
"error.ended-election": "The election has ended",
"error.catch22": "Unknown error...",
"error.help": "Ask for our help",
"error.no-title": "Please add a title to your election.",
"error.twice-same-names": "Two items have the same name.",
"error.empty-name": "The name is empty",
"error.date-past": "The date is in the past",
"error.cant-be-finished": "The election will never be closed. Add invites, access to results, or a ending date.",
"error.not-enough-grades": "Not enough grades",
"error.not-enough-candidates": "Not enough candidates",
"error.uncorrect-name": "The title is incorrect",
"grades.very-good": "Very good",
"grades.good": "Good",
"grades.passable": "Passable",
"grades.inadequate": "Inadequate",
"grades.mediocre": "Mediocre",
"admin.admin-title": "Voting management panel",
"admin.close-election": "Close the election",
"admin.date-limit": "Set a deadline for voting",
"admin.set-title": "Set the question of the vote",
"admin.step-candidate": "Candidates",
"admin.step-params": "Parameters",
"admin.step-confirm": "Confirm",
@ -89,7 +99,7 @@
"admin.params-title": "Your parameters",
"admin.access-results": "Immediate access to the results",
"admin.access-results-desc": "No one can access to the results as long as the vote is not closed.",
"admin.limit-duration": "Limit the length of the vote",
"admin.limit-duration": "Limit the duration of the vote",
"admin.limit-duration-desc": "",
"admin.photo": "Picture",
"admin.optional": "optional",
@ -107,7 +117,6 @@
"admin.confirm-submit": "Start the vote",
"admin.confirm-title": "Confirmer votre vote",
"admin.confirm-edit": "Save your modifications",
"admin.success-election": "The vote has been created with success!",
"admin.success-emails": "The voting link has been sent by emails to the participants.",
"admin.success-copy-vote": "Copy the voting link",
"admin.success-copy-result": "Copy the result link",
@ -126,6 +135,8 @@
"result.how-to-interpret": "How to understand the results",
"result.will-close": "Will close in",
"result.share": "Share results",
"success.election-closed": "The vote has been created with success!",
"success.election-updated": "The vote has been updated with success!",
"vote.discover-mj": "Discover majority judgment",
"vote.discover-mj-desc": "Developed by French researchers, majority judgment is a voting system that improves voter expressiveness and provides the best consensus.",
"vote.form": "Your opinion is important to us",

@ -51,17 +51,28 @@
"common.vote": "Voter",
"common.welcome": "Bienvenue !",
"error.help": "Besoin d'aide ?",
"error.cant-set-ongoing": "Vous ne pouvez pas modifier ce paramètre pour une élection déjà démarrée.",
"error.at-least-2-candidates": "Ajoutez au moins deux candidats.",
"error.no-title": "Ajoutez un titre à l'élection.",
"error.ended-election": "L'élection est terminée",
"error.twice-same-names": "Deux éléments ont le même nom",
"error.empty-name": "Le nom est vite",
"error.wrong-name": "Le titre de l'élection est incorrecte",
"error.cant-be-finished": "L'élection ne se terminera jamais. Ajoutez une date limite, un accès aux résultats, ou des invitations.",
"error.catch22": "Erreur inconnue...",
"error.date-past": "La date est dans le passé",
"error.uncorrect-name": "The title is incorrect",
"error.not-enough-grades": "Il manque des mentions",
"error.not-enough-candidates": "Il manque des candidats",
"grades.very-good": "Très bien",
"grades.good": "Bien",
"grades.passable": "Passable",
"grades.inadequate": "Insuffisant",
"grades.mediocre": "Médiocre",
"admin.admin-title": "Administration du vote",
"admin.close-election": "Clôturer l'élection",
"admin.date-limit": "Fixer une date limite pour le vote",
"admin.set-title": "Changer la question de l'élection",
"admin.step-candidate": "Les candidats",
"admin.step-params": "Paramètres du vote",
"admin.step-confirm": "Confirmation",
@ -126,6 +137,8 @@
"result.share": "Partager les résultats",
"result.will-close": "Se termine dans",
"result.how-to-interpret": "Comment interpréter les résultats",
"success.election-closed": "L'élection est désormais clôturée.",
"success.election-updated": "L'élection a été mise à jour.",
"vote.discover-mj": "Découvrez le jugement majoritaire",
"vote.discover-mj-desc": "Créé par des chercheurs français, le jugement majoritaire est un mode de scrutin qui améliore l'expressivité des électeurs et fournit le meilleur consensus.",
"vote.go-to-results": "Voir les résultats",

@ -4,6 +4,7 @@
import {createContext, useContext, useReducer, useEffect, Dispatch, SetStateAction} from 'react';
import {useRouter} from 'next/router';
import {CandidateItem, GradeItem} from './type';
import {gradeColors} from '@services/grades';
export interface ElectionContextInterface {
name: string;
@ -33,7 +34,7 @@ const defaultElection: ElectionContextInterface = {
candidates: [{...defaultCandidate}, {...defaultCandidate}],
grades: [],
randomOrder: true,
hideResults: true,
hideResults: false,
forceClose: false,
restricted: false,
dateEnd: null,
@ -105,11 +106,14 @@ export function ElectionProvider({children}) {
useEffect(() => {
if (!router.isReady) return;
dispatch({
type: ElectionTypes.SET,
field: 'name',
value: router.query.name || '',
});
if (election.name === "" && router.query.name !== "") {
dispatch({
type: ElectionTypes.SET,
field: 'name',
value: router.query.name,
});
}
}, [router.isReady]);
return (
@ -220,3 +224,23 @@ export const canViewResults = (election: ElectionContextInterface) => {
const isOver = +dateEnd < (+now);
return election.forceClose || !election.hideResults || isOver;
}
export const hasEnoughCandidates = (election: ElectionContextInterface) => {
const numCandidates = election.candidates.filter(c => c.active && c.name != "").length;
return numCandidates > 1;
}
export const hasEnoughGrades = (election: ElectionContextInterface) => {
const numGrades = election.grades.filter(g => g.active && g.name != "").length;
return numGrades > 1 && numGrades <= gradeColors.length
}
export const checkName = (election: ElectionContextInterface) => {
return election.name && election.name !== ""
}
export const canBeFinished = (election: ElectionContextInterface) => {
return election.restricted || election.forceClose || election.dateEnd || !election.hideResults;
}

@ -82,7 +82,6 @@ export function AppProvider({children}) {
const [app, dispatch] = useReducer(reducer, defaultApp);
const removeToast = (i: number) => {
console.log(i)
dispatch({
type: AppTypes.TOAST_RM,
position: i
@ -100,7 +99,6 @@ export function AppProvider({children}) {
}
})
console.log("TOASTS", app.toasts)
}, [app.toasts]);
@ -110,7 +108,7 @@ export function AppProvider({children}) {
{app.toasts.filter(t => t.display).map((toast, i) => (
<div
key={i}
className={`toast text-bg-${toast.status === "error" ? "danger" : "success"} fade show align-items-center`}
className={`toast text-bg-${toast.status === "error" ? "danger" : "success"} fade show align-items-center text-light`}
role={toast.status === "error" ? "alert" : "status"}
aria-live={toast.status === "error" ? "assertive" : "polite"}
aria-atomic="true">

@ -91,6 +91,10 @@
background-image: url('../../public/back.svg');
background-size: auto 100%;
background-repeat: no-repeat;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
@include media-breakpoint-up('xxl') {
.waiting {

Loading…
Cancel
Save