fix: clean up code

pull/84/head
Pierre-Louis Guhur 2 years ago
parent 4c2759d8c9
commit f11ee7b535

@ -0,0 +1,166 @@
import React from 'react';
import plotly from 'plotly.js/dist/plotly';
import createPlotComponent from 'react-plotly.js/factory';
import LoadingScreen from "./LoadingScreen";
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();
// déclaration et initialisation d'une instance de graphique en bulles
// const Plot = createPlotComponent(plotly);
const Plot = require('react-plotly.js').default;
//---------------------------------------------//
//----------- Affichage des données -----------//
const [loading, setLoading] = React.useState(true);
React.useEffect(() =>{
setTimeout(() => setLoading(false), 3000);
})
return (
// <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;

@ -0,0 +1,41 @@
import * as React from "react";
import * as d3 from "d3";
function drawChart(svgRef: React.RefObject<SVGSVGElement>) {
const data = [12, 5, 6, 6, 9, 10];
const h = 120;
const w = 250;
const svg = d3.select(svgRef.current);
svg
.attr("width", w)
.attr("height", h)
.style("margin-top", 50)
.style("margin-left", 50);
svg
.selectAll("rect")
.data(data)
.enter()
.append("rect")
.attr("x", (d, i) => i * 40)
.attr("y", (d, i) => h - 10 * d)
.attr("width", 20)
.attr("height", (d, i) => d * 10)
.attr("fill", "steelblue");
}
const Chart: React.FunctionComponent = () => {
const svg = React.useRef<SVGSVGElement>(null);
React.useEffect(() => {
drawChart(svg);
}, [svg]);
return (
<div id="chart">
<svg ref={svg} />
</div>
);
};
export default Chart;

@ -0,0 +1,21 @@
import React, { useRef, useState, useEffect } from 'react';
import D3Chart from './D3Chart';
const ChartWrapper = () => {
const chartArea = useRef(null);
const [chart, setChart] = useState(null);
useEffect(() => {
if (!chart) {
setChart(new D3Chart(chartArea.current));
}
}, [chart]);
return (
<div ref={chartArea}></div>
);
}
export default ChartWrapper;

@ -3,7 +3,7 @@ import React from "react";
import { Button } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
faCopy,
faClone,
faVoteYea,
faExclamationTriangle,
faExternalLinkAlt,
@ -25,9 +25,10 @@ const CopyField = (props) => {
const { t, value, iconCopy, iconOpen } = props;
return (
<div className="input-group ">
<div className="input-group my-4 ">
<input
type="text"
style={{display:"none"}}
className="form-control"
ref={ref}
value={value}
@ -35,7 +36,7 @@ const CopyField = (props) => {
onClick={handleClickOnField}
/>
<div className="input-group-append">
<div className="input-group-append copy">
{/*
<Button
href={value}
@ -49,12 +50,12 @@ const CopyField = (props) => {
</Button>
*/}
<Button
className="btn btn-secondary"
className="btn btn-copy"
onClick={handleClickOnButton}
type="button"
>
<FontAwesomeIcon icon={iconCopy} className="mr-2" />
{t("Copy")}
<FontAwesomeIcon icon={iconCopy} className="ml-2" />
</Button>
</div>
</div>
@ -62,7 +63,7 @@ const CopyField = (props) => {
};
CopyField.defaultProps = {
iconCopy: faCopy,
iconCopy: faClone,
iconOpen: faExternalLinkAlt,
};

@ -0,0 +1,38 @@
import * as d3 from 'd3';
const url = "https://udemy-react-d3.firebaseio.com/tallest_men.json";
const WIDTH = 800;
const HEIGHT = 500;
export default class D3Chart {
constructor(element) {
const svg = d3.select(element)
.append("svg")
.attr("width", 800)
.attr("height", 500)
d3.json(url).then(data => {
const max = d3.max(data, d => d.height)
const y = d3.scaleLinear()
.domain([0, max])
.range([0, HEIGHT])
const x = d3.scaleBand()
.domain(data.map(d => d.name))
.range([0, WIDTH])
.padding(0.4)
const rects = svg.selectAll("rect")
.data(data)
rects.enter()
.append("rect")
.attr("x", d => x(d.name))
.attr("y", d => HEIGHT - y(d.height))
.attr("width", x.bandwidth)
.attr("height", d => y(d.height))
.attr("fill", "grey")
})
}
}

@ -0,0 +1,75 @@
import React from "react"
import styled from "styled-components"
const Screen = styled.div`
position: relative;
opacity: 0;
animation: fade 0.4s ease-in forwards;
background: black;
@keyframes fade {
0% {
opacity: 0.4;
}
50% {
opacity: 0.8;
}
100% {
opacity: 1;
}
}
`;
const Balls = styled.div`
display: flex;
.ball {
height: 20px;
width: 20px;
border-radius: 50%;
background: red;
margin: 0 6px 0 0;
animation: oscillate 0.7s ease-in forwards infinite;
}
.one {
animation-delay: 0.5s;
}
.two {
animation-delay: 1s;
}
.three {
animation-delay: 2s;
}
@keyframes oscillate {
0% {
transform: translateY(0);
}
50% {
transform: translateY(20px);
}
100% {
transform: translateY(0);
}
}
`;
const LoadingScreen = () => {
return (
<Screen>
<Balls>
<div className="ball one"></div>
<div className="ball two"></div>
<div className="ball three"></div>
</Balls>
</Screen>
);
};
export default LoadingScreen;

@ -0,0 +1,62 @@
import React, { useEffect, useRef, useState } from "react";
import ReactDOM from "react-dom";
import styled from "styled-components";
const Modal = ({ show, onClose, children, title }) => {
const handleCloseClick = (e) => {
e.preventDefault();
onClose();
};
const modalContent = show ? (
<StyledModalOverlay>
<StyledModal>
<StyledModalHeader>
<a href="#" onClick={handleCloseClick}>
x
</a>
</StyledModalHeader>
{title && <StyledModalTitle>{title}</StyledModalTitle>}
<StyledModalBody>{children}</StyledModalBody>
</StyledModal>
</StyledModalOverlay>
) : null;
return (
modalContent
);
};
const StyledModalBody = styled.div`
padding-top: 10px;
`;
const StyledModalHeader = styled.div`
display: flex;
justify-content: flex-end;
font-size: 25px;
`;
const StyledModal = styled.div`
background: white;
width: 500px;
height: 600px;
border-radius: 15px;
padding: 15px;
`;
const StyledModalOverlay = styled.div`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.5);
`;
export default Modal;

@ -0,0 +1,37 @@
import React, {Fragment} from 'react';
import Head from 'next/head';
import dynamic from 'next/dynamic';
const Bulles = dynamic(import('./Bulles'), {
ssr: false
})
const nbVotesPassables = 15;
const nbVotesAssezBien = 200;
const nbVotesBien = 389;
const nbVotesTresBien = 12;
const nbVotesExcellent = 2;
const resultats = [nbVotesPassables, nbVotesAssezBien, nbVotesBien, nbVotesTresBien, nbVotesExcellent];
var totalVotes = 0;
for(var i = 0; i < resultats.length; i++) {
totalVotes += resultats[i];
}
function SystemeVote() {
return (
<Fragment>
<Bulles donnees={resultats} />
<p style={{color: '#000000'}}>Le total des votes est de {totalVotes}.</p>
</Fragment>
);
}
export default SystemeVote;

@ -6,7 +6,7 @@ import { faFacebookSquare } from "@fortawesome/free-brands-svg-icons";
const Facebook = props => {
const handleClick = () => {
const url =
"https://www.facebook.com/sharer.php?u=" +
"https://www.facebook.com/sharer/sharer.php?u=" +
props.url +
"&t=" +
props.title;
@ -17,10 +17,9 @@ const Facebook = props => {
);
};
return (
<button className={props.className} onClick={handleClick} type="button">
<FontAwesomeIcon icon={faFacebookSquare} className="mr-2" />
{props.text}
</button>
<img src="/facebook.svg" onClick={handleClick} className="cursorPointer mr-2" />
);
};

@ -12,7 +12,6 @@ const Gform = (props) => {
target="_blank"
rel="noopener noreferrer"
>
<FontAwesomeIcon icon={faCommentAlt} className="mr-2" />
Votre avis nous intéresse !
</a>
);

@ -0,0 +1,43 @@
import {useTranslation} from "next-i18next";
import {useRouter} from "next/router"
import {faPaypal} from "@fortawesome/free-brands-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
const PaypalNoLogo = () => {
const {t} = useTranslation();
// FIXME generate a xx_XX string for locale version
const {locale} = useRouter();
let localeShort = locale.substring(0, 2);
let localeComplete =
localeShort.toLowerCase() + "_" + localeShort.toUpperCase();
if (localeComplete === "en_EN") {
localeComplete = "en_US";
}
const pixelLink =
`https://www.paypal.com/${localeComplete}/i/scr/pixel.gif`;
return (
<div className="d-inline-block m-auto">
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_top"
>
<button
type="submit"
className="addButton"
title={t("PayPal - The safer, easier way to pay online!")}
>
{t("Support us !")}
<img src="/arrow-blue.svg" className="mr-2" />
</button>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="KB2Z7L9KARS7C" />
<img alt="" border="0" src={pixelLink} width="1" height="1" />
</form>
</div>
);
};
export default PaypalNoLogo;

@ -0,0 +1,29 @@
/* eslint react/prop-types: 0 */
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faFacebookSquare } from "@fortawesome/free-brands-svg-icons";
const Twitter = props => {
const handleClick = () => {
const url =
"https://twitter.com/intent/tweet?text=" +
props.title +
"&t=" +
props.url;
window.open(
url,
"",
"menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=500,width=700"
);
};
return (
<img src="/twitter.svg" onClick={handleClick} className="cursorPointer mr-2" />
);
};
export default Twitter;
//i

@ -0,0 +1,37 @@
import { useState } from "react";
export default function AddPicture(props) {
const [image, setImage] = useState(null);
const [createObjectURL, setCreateObjectURL] = useState(null);
const uploadToClient = (event) => {
if (event.target.files && event.target.files[0]) {
const i = event.target.files[0];
setImage(i);
setCreateObjectURL(URL.createObjectURL(i));
}
};
return (
<div className="ajout-avatar">
<div>
<div className="avatar-placeholer">
<img src={createObjectURL} />
</div>
</div>
<div className="avatar-text">
<h4>Photo <span> (facultatif)</span></h4>
<p>Importer une photo.<br />format : jpg, png, pdf</p>
<div className="btn-ajout-avatar">
<input type="file" name="myImage" id="myImage" onChange={uploadToClient} />
<label className="inputfile" for="myImage">Importer une photo</label>
</div>
</div>
</div>
);
}

@ -0,0 +1,24 @@
import { useState } from 'react'
import { Alert, Button } from 'react-bootstrap';
import { faTimes, faExclamationCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export default function AlertDismissibleExample() {
const [show, setShow] = useState(true);
if (show) {
return (
<Alert className="preventWarning">
<Alert.Heading>
<div>
<FontAwesomeIcon icon={faExclamationCircle} className="mr-2" />
<span>2 candidats minimum</span>
</div>
<FontAwesomeIcon onClick={() => setShow(false)} icon={faTimes} className="mr-2" />
</Alert.Heading>
</Alert>
);
}
return null;
}

@ -13,39 +13,41 @@ const ButtonWithConfirm = ({className, label, onDelete}) => {
const toggle = () => setVisibility(!visibled)
return (
<div className="input-group-append">
<div className="input-group-append cancelButton">
<button
type="button"
className={className}
className={"btn " + className}
onClick={toggle}
>
<FontAwesomeIcon icon={faTrashAlt} />
<div className="annuler"><img className="ml-0" src="/arrow-dark-left.svg" /><p className="ml-0" >Annuler</p></div>
</button>
<Modal
isOpen={visibled}
toggle={toggle}
className="modal-dialog-centered"
className="modal-dialog-centered cancelForm"
>
<ModalHeader toggle={toggle}>{t("Delete?")}</ModalHeader>
<ModalHeader><FontAwesomeIcon icon={faTrashAlt} /></ModalHeader>
<ModalBody>
{t("Are you sure to delete")}{" "}
{t("Are you sure to delete")}{<br />}
{label && label !== "" ? (
<b>&quot;{label}&quot;</b>
<b>{label}</b>
) : (
<>{t("the row")}</>
)}{" "}
?
)}
</ModalBody>
<ModalFooter>
<Button
color="primary-outline"
className="text-primary border-primary"
className={className}
onClick={toggle}>
{t("No")}
<div className="annuler"><img src="/arrow-dark-left.svg" /> {t("No")}</div>
</Button>
<Button color="primary"
<Button
className="new-btn-confirm"
onClick={() => {toggle(); onDelete();}}
>
<FontAwesomeIcon icon={faTrashAlt} className="mr-2"/>
{t("Yes")}
</Button>
</ModalFooter>

@ -1,53 +1,175 @@
import {useState} from 'react'
import { useState, useEffect } from 'react'
import ButtonWithConfirm from "./ButtonWithConfirm";
import TrashButton from "./TrashButton";
import {
Row,
Col,
Label,
Input,
InputGroup,
InputGroupAddon,
Button, Modal, ModalHeader, ModalBody, ModalFooter
} from "reactstrap";
import {useTranslation} from "react-i18next";
import { useTranslation } from "react-i18next";
import {
sortableHandle
} from "react-sortable-hoc";
import HelpButton from "@components/form/HelpButton";
const DragHandle = sortableHandle(({children}) => (
import AddPicture from "@components/form/AddPicture";
import {
faPlus, faCogs, faCheck, faTrash
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const DragHandle = sortableHandle(({ children }) => (
<span className="input-group-text indexNumber">{children}</span>
));
const CandidateField = ({label, candIndex, onDelete, ...inputProps}) => {
const {t} = useTranslation();
const CandidateField = ({ avatar, label, description, candIndex, onDelete, onAdd, ...inputProps }) => {
const { t } = useTranslation();
const [visibled, setVisibility] = useState(false);
const toggle = () => setVisibility(!visibled)
const [selected, setSelectedState] = useState(false);
const [className , setClassName] = useState("none");
const [trashIcon , setTrashIcon] = useState("none");
const [plusIcon , setPlusIcon] = useState("none");
const addCandidate = () => {
toggle();
onAdd();
}
useEffect(() => {
setClassName("candidateButton " + (selected ? "candidateAdded" : ""))
}, [selected] );
useEffect(() => {
setPlusIcon("mr-2 cursorPointer " + (selected ? "trashIcon" : ""))
}, [selected] );
useEffect(() => {
setTrashIcon("trashIcon " + (selected ? "displayTrash" : ""))
}, [selected] );
const addFunction = () => {
addCandidate();
setSelectedState(!selected);
}
const [image, setImage] = useState(null);
const [createObjectURL, setCreateObjectURL] = useState(null);
const uploadToClient = (event) => {
if (event.target.files && event.target.files[0]) {
const i = event.target.files[0];
setImage(i);
setCreateObjectURL(URL.createObjectURL(i));
}
};
return (
<Row>
<Col>
<InputGroup>
<InputGroupAddon addonType="prepend">
<DragHandle>
<span>{candIndex + 1}</span>
</DragHandle>
</InputGroupAddon>
<Input
type="text"
value={label}
{...inputProps}
placeholder={t("resource.candidatePlaceholder")}
tabIndex={candIndex + 1}
maxLength="250"
autoFocus
/>
<ButtonWithConfirm className="btn btn-primary border-light" label={label} onDelete={onDelete}>
</ButtonWithConfirm>
</InputGroup>
</Col>
<Col xs="auto" className="align-self-center pl-0">
<Row className="rowNoMargin">
<div className={className}>
<div className="avatarThumb">
<img src={createObjectURL} alt="" />
<input placeholder="Ajouter un candidat" className="candidate-placeholder ml-2" value={label}/>
</div>
<FontAwesomeIcon onClick={toggle} icon={faPlus} className={plusIcon} />
<div className={trashIcon}><TrashButton label={label} onDelete={onDelete}/></div>
</div>
<Modal
isOpen={visibled}
toggle={toggle}
className="modal-dialog-centered"
>
<ModalHeader className='closeModalAddCandidate' toggle={toggle}>
</ModalHeader>
<ModalBody>
<Col className="addCandidateCard">
<InputGroup className="addCandidateForm">
<InputGroupAddon addonType="prepend" className="addCandidateHeader">
<DragHandle>
<h6>Ajouter un participant</h6>
<p>Ajoutez une photo, le nom et une description au candidat.</p>
<div className="ajout-avatar">
<div>
<div className="avatar-placeholer">
<img src={createObjectURL} />
</div>
</div>
<div className="avatar-text">
<h4>Photo <span> (facultatif)</span></h4>
<p>Importer une photo.<br />format : jpg, png, pdf</p>
<div className="btn-ajout-avatar">
<input type="file" name="myImage" id="myImage" value={avatar} onChange={uploadToClient} />
<label className="inputfile" for="myImage">Importer une photo</label>
</div>
</div>
</div>
<img src="/avatar.svg" />
</DragHandle>
</InputGroupAddon>
<Label className="addCandidateText">Nom et prenom</Label>
<Input
type="text"
value={label}
{...inputProps}
placeholder={t("resource.candidatePlaceholder")}
tabIndex={candIndex + 1}
maxLength="250"
autoFocus
className="addCandidateText"
required
/>
<Label>Description (Facultatif)</Label>
<Input
type="text"
value={description}
placeholder="Texte"
tabIndex={candIndex + 1}
maxLength="250"
autoFocus
/>
<Row className="removeAddButtons">
<ButtonWithConfirm className="removeButton" label={label} onDelete={onDelete, toggle}/>
<Button className="addButton" label={label} onClick={addFunction}>
<FontAwesomeIcon icon={faPlus} />
<span>Ajouter</span>
</Button>
</Row>
</InputGroup>
</Col>
</ModalBody></Modal>
{/* <Col xs="auto" className="align-self-center pl-0">
<HelpButton>
{t(
"Enter the name of your candidate or proposal here (250 characters max.)"
)}
</HelpButton>
</Col>
</Col> */}
</Row>
);
}

@ -16,7 +16,7 @@ import {
} from "react-sortable-hoc";
import arrayMove from "array-move"
import CandidateField from './CandidateField'
import AlertDismissibleExample from './AlertButton'
// const SortableItem = sortableElement(({className, ...childProps}) => <li className={className}><CandidateField {...childProps} /></li>);
//
// const SortableContainer = sortableContainer(({children}) => {
@ -36,7 +36,7 @@ const CandidatesField = ({onChange}) => {
const addCandidate = () => {
if (candidates.length < 1000) {
candidates.push({label: "", fieldRef: createRef()});
candidates.push({label: "", description: "", fieldRef: createRef()});
setCandidates([...candidates]);
onChange(candidates)
} else {
@ -53,8 +53,8 @@ const CandidatesField = ({onChange}) => {
const removeCandidate = index => {
if (candidates.length === 1) {
const newCandidates = []
newCandidates.push({label: "", fieldRef: createRef()});
newCandidates.push({label: "", fieldRef: createRef()});
newCandidates.push({label: "", description: "", fieldRef: createRef()});
newCandidates.push({label: "", description: "", fieldRef: createRef()});
setCandidates(newCandidates);
onChange(newCandidates)
}
@ -65,8 +65,9 @@ const CandidatesField = ({onChange}) => {
}
};
const editCandidate = (index, label) => {
const editCandidate = (index, label, description) => {
candidates[index].label = label
candidates[index].description = description
setCandidates([...candidates]);
onChange(candidates);
};
@ -88,7 +89,10 @@ const CandidatesField = ({onChange}) => {
};
return (
<>
<div className="sectionAjouterCandidat">
<div className="ajouterCandidat">
<h4>Saisissez ici le nom de vos candidats.</h4>
<AlertDismissibleExample />
<SortableContainer onSortEnd={onSortEnd}>
{candidates.map((candidate, index) => {
const className = "sortable"
@ -99,26 +103,18 @@ const CandidatesField = ({onChange}) => {
index={index}
candIndex={index}
label={candidate.label}
description={candidate.description}
onDelete={() => removeCandidate(index)}
onChange={(e) => editCandidate(index, e.target.value)}
onKeyPress={(e) => handleKeyPress(e, index)}
onAdd={addCandidate}
innerRef={candidate.fieldRef}
/>
)
})}
</SortableContainer>
<Button
color="secondary"
className="btn-block mt-2"
tabIndex={candidates.length + 2}
type="button"
onClick={addCandidate}
>
<FontAwesomeIcon icon={faPlus} className="mr-2" />
{t("Add a proposal")}
</Button>
</>
</div>
</div>
);
}

@ -1,160 +1,195 @@
import {useTranslation} from "next-i18next";
import {useState} from "react";
import { useTranslation } from "next-i18next";
import { useState } from "react";
import TrashButton from "./TrashButton";
import {
faExclamationTriangle,
faCheck,
faArrowLeft,
faTrashAlt
} from "@fortawesome/free-solid-svg-icons";
import {Button, Modal, ModalHeader, ModalBody, ModalFooter} from "reactstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import { Button, Modal, ModalHeader, ModalBody, ModalFooter, Row, Col } from "reactstrap";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
const ConfirmModal = ({tabIndex, title, candidates, grades, isTimeLimited, start, finish, emails, restrictResult, className, confirmCallback}) => {
const ConfirmModal = ({ tabIndex, title, candidates, grades, isTimeLimited, start, finish, emails, restrictResult, className, confirmCallback }) => {
const [visibled, setVisibility] = useState(false);
const {t} = useTranslation();
const { t } = useTranslation();
const toggle = () => setVisibility(!visibled)
return (
<div className="input-group-append">
<button
type="button"
className={className}
onClick={toggle}
tabIndex={tabIndex}
>
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Validate")}
</button>
<Button onClick={toggle}
tabIndex={tabIndex} className={"mt-5 componentDesktop btn-transparent cursorPointer btn-validation mb-5 mx-auto" + className} >{t("Confirm")}<img src="/arrow-white.svg" /></Button>
<Button
className={"componentMobile btn-confirm-mobile mb-5" + className}
onClick={toggle}
tabIndex={tabIndex}>
<FontAwesomeIcon className="mr-2" icon={faCheck} />
{t("Valider")}
</Button>
<Modal
isOpen={visibled}
isOpen={!visibled}
toggle={toggle}
className="modal-dialog-centered"
className="modal-dialog-centered settings-modal"
>
<ModalHeader toggle={toggle}>
{t("Confirm your vote")}
<ModalHeader className="modal-header-settings">
<div onClick={toggle} className="btn-return-candidates"><FontAwesomeIcon icon={faArrowLeft} className="mr-2" />Retour aux paramètres</div>
<Row>
<Row className="stepForm">
<Col className="stepFormCol">
<img src="/icone-check-dark.svg" />
<h4>Les candidats</h4>
</Col>
<Col className="stepFormCol">
<img src="/icone-check-dark.svg" />
<h4>Paramètres du vote</h4>
</Col>
<Col className="stepFormCol">
<img src="/icone-three-white.svg" />
<h4>Confirmation</h4>
</Col>
</Row>
</Row>
</ModalHeader>
<ModalBody>
<div className="mt-1 mb-1">
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Question of the election")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">{title}</div>
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Candidates/Proposals")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
<ul className="m-0 pl-4">
{candidates.map((candidate, i) => {
if (candidate.label !== "") {
return (
<li key={i} className="m-0">
{candidate.label}
</li>
);
} else {
return <li key={i} className="d-none" />;
}
})}
</ul>
</div>
<div className={(isTimeLimited ? "d-block " : "d-none")} >
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Dates")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
{t("The election will take place from")}{" "}
<b>
{start.toLocaleDateString()}, {t("at")}{" "}
{start.toLocaleTimeString()}
</b>{" "}
{t("to")}{" "}
<b>
{finish.toLocaleDateString()}, {t("at")}{" "}
{finish.toLocaleTimeString()}
</b>
<ModalBody className="confirm-modal-body">
<Row>
<Col md="4" className="p-0">
<div className="text-light">{t("Le vote")}</div>
<div className="mt-1 mb-1 recap-vote">
<Row className="m-0">
<div className="recap-vote-label">
{t("Question of the election")}
</div>
</Row>
<Row>
<div className="p-2 pl-3 pr-3 mb-3 recap-vote-question">{title}</div>
</Row>
<hr className="confirmation-divider"/>
<Row>
<div className="recap-vote-label p-2 pl-3 pr-3 ">
{t("Candidates/Proposals")}
</div>
</Row>
<Row>
<div className="p-2 pl-3 pr-3 mb-3 recap-vote-content">
<ul className="m-0 pl-4">
{candidates.map((candidate, i) => {
if (candidate.label !== "") {
return (
<li key={i} className="m-0">
{candidate.label}
</li>
);
} else {
return <li key={i} className="d-none" />;
}
})}
</ul>
</div>
</Row>
</div>
</div>
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Grades")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
{grades.map((mention, i) => {
return i < grades.length ? (
<span
key={i}
className="badge badge-light mr-2 mt-2"
style={{
backgroundColor: mention.color,
color: "#fff"
}}
>
{mention.label}
</span>
) : (
<span key={i} />
);
})}
</div>
<div className="text-white bg-primary p-2 pl-3 pr-3 rounded">
{t("Voters' list")}
</div>
<div className="p-2 pl-3 pr-3 bg-light mb-3">
{emails.length > 0 ? (
emails.join(", ")
) : (
<p>
{t("The form contains no address.")}
<br />
<em>
{t(
"The election will be opened to anyone with the link"
)}
</em>
</p>
)}
</div>
{restrictResult ? (
<div>
<div className="small bg-primary text-white p-3 mt-2 rounded">
<h6 className="m-0 p-0">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="mr-2"
/>
<u>{t("Results available at the close of the vote")}</u>
</h6>
<p className="m-2 p-0">
<span>
{t(
"The results page will not be accessible until the end date is reached."
)}{" "}
({finish.toLocaleDateString()} {t("at")}{" "}
{finish.toLocaleTimeString()})
</Col>
<Col md="8">
<div className="text-light">{t("Les paramètres")}</div>
<div className="recap-vote">
<div className={(isTimeLimited ? "d-block " : "d-none")} >
<div className="p-2 pl-3 pr-3 recap-vote-label ">
{t("Dates")}
</div>
<div className="p-2 pl-3 pr-3 recap-vote-content mb-3">
{t("The election will take place from")}{" "}
<b>
{start.toLocaleDateString()}, {t("at")}{" "}
{start.toLocaleTimeString()}
</b>{" "}
{t("to")}{" "}
<b>
{finish.toLocaleDateString()}, {t("at")}{" "}
{finish.toLocaleTimeString()}
</b>
</div>
</div>
<hr className="confirmation-divider"/>
<div className="recap-vote-label p-2 pl-3 pr-3">
{t("Grades")}
</div>
<div className="p-2 pl-3 pr-3 recap-vote-content mb-3">
{grades.map((mention, i) => {
return i < grades.length ? (
<span
key={i}
className="badge badge-light mr-2 mt-2"
style={{
backgroundColor: mention.color,
color: "#fff"
}}
>
{mention.label}
</span>
</p>
) : (
<span key={i} />
);
})}
</div>
</div>
) : (
<div>
<div className="small bg-primary text-white p-3 mt-2 rounded">
<h6 className="m-0 p-0">
{t("Results available at any time")}
</h6>
<hr className="confirmation-divider"/>
<div className="recap-vote-label p-2 pl-3 pr-3 rounded">
{t("Voters' list")}
</div>
<div className="p-2 pl-3 pr-3 recap-vote-content mb-3">
{emails.length > 0 ? (
emails.join(", ")
) : (
<p>
{t("The form contains no address.")}
<br />
<em>
{t(
"The election will be opened to anyone with the link"
)}
</em>
</p>
)}
</div>
<hr className="confirmation-divider"/>
{restrictResult ? (
<div>
<div className="small recap-vote-label p-3 mt-2">
<h6 className="m-0 p-0 recap-vote-content">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="mr-2"
/>
<u>{t("Results available at the close of the vote")}</u>
</h6>
<p className="m-2 p-0 ">
<span>
{t(
"The results page will not be accessible until the end date is reached."
)}{" "}
({finish.toLocaleDateString()} {t("at")}{" "}
{finish.toLocaleTimeString()})
</span>
</p>
</div>
</div>
) : (
<div>
<div className="small recap-vote-label p-3 mt-2 ">
<h6 className="m-0 p-0 recap-vote-content">
{t("Results available at any time")}
</h6>
</div>
</div>
)}
</div>
)}
</div>
</Col>