From e49ad0bde8f9ddee2109234b4ddbff99d4cd28e6 Mon Sep 17 00:00:00 2001 From: guhur <12297742+guhur@users.noreply.github.com> Date: Sun, 15 Sep 2019 10:46:35 +0200 Subject: [PATCH] Connection to MV API (#1) * change url * fix bug in API * add .env config * rename mentions to grades * connect Result.jsx to API * connect vote --- .gitignore | 3 + package.json | 5 +- public/index.html | 2 +- src/{App.js => App.jsx} | 2 +- src/{App.test.js => App.test.jsx} | 0 src/{Routes.js => Routes.jsx} | 14 +- src/Util.jsx | 5 + ...onWithConfirm.js => ButtonWithConfirm.jsx} | 0 .../form/{HelpButton.js => HelpButton.jsx} | 0 .../{ModalConfirm.js => ModalConfirm.jsx} | 0 .../layouts/{Footer.js => Footer.jsx} | 0 .../layouts/{Header.js => Header.jsx} | 0 .../{CreateElection.js => CreateElection.jsx} | 82 +++---- src/components/views/CreateSuccess.js | 9 +- src/components/views/{Home.js => Home.jsx} | 2 +- .../views/{Result.js => Result.jsx} | 161 +++++++------ .../views/{UnknownView.js => UnknownView.jsx} | 0 src/components/views/Vote.js | 160 ------------- src/components/views/Vote.jsx | 213 ++++++++++++++++++ src/components/views/VoteSuccess.js | 17 +- src/{index.js => index.jsx} | 0 src/{serviceWorker.js => serviceWorker.jsx} | 0 22 files changed, 388 insertions(+), 287 deletions(-) rename src/{App.js => App.jsx} (91%) rename src/{App.test.js => App.test.jsx} (100%) rename src/{Routes.js => Routes.jsx} (61%) create mode 100644 src/Util.jsx rename src/components/form/{ButtonWithConfirm.js => ButtonWithConfirm.jsx} (100%) rename src/components/form/{HelpButton.js => HelpButton.jsx} (100%) rename src/components/form/{ModalConfirm.js => ModalConfirm.jsx} (100%) rename src/components/layouts/{Footer.js => Footer.jsx} (100%) rename src/components/layouts/{Header.js => Header.jsx} (100%) rename src/components/views/{CreateElection.js => CreateElection.jsx} (87%) rename src/components/views/{Home.js => Home.jsx} (99%) rename src/components/views/{Result.js => Result.jsx} (63%) rename src/components/views/{UnknownView.js => UnknownView.jsx} (100%) delete mode 100644 src/components/views/Vote.js create mode 100644 src/components/views/Vote.jsx rename src/{index.js => index.jsx} (100%) rename src/{serviceWorker.js => serviceWorker.jsx} (100%) diff --git a/.gitignore b/.gitignore index a6b1524..ca4d3c6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. +.tern-port +.#* +*.env.local # dependencies /node_modules diff --git a/package.json b/package.json index b76608a..6908f6f 100644 --- a/package.json +++ b/package.json @@ -41,5 +41,8 @@ "last 1 firefox version", "last 1 safari version" ] - } + }, + "jshintConfig": { + "esversion": 6 + } } diff --git a/public/index.html b/public/index.html index 3522caa..03c6431 100644 --- a/public/index.html +++ b/public/index.html @@ -30,7 +30,7 @@ - Platerforme de vote : Jugement Majoritaire + Plateforme de vote : Jugement Majoritaire diff --git a/src/App.js b/src/App.jsx similarity index 91% rename from src/App.js rename to src/App.jsx index cf90f01..580ce64 100644 --- a/src/App.js +++ b/src/App.jsx @@ -1,6 +1,6 @@ import React from 'react'; import { BrowserRouter as Router} from "react-router-dom"; -import Routes from "./Routes.js"; +import Routes from "./Routes"; import Header from "./components/layouts/Header"; import Footer from "./components/layouts/Footer"; diff --git a/src/App.test.js b/src/App.test.jsx similarity index 100% rename from src/App.test.js rename to src/App.test.jsx diff --git a/src/Routes.js b/src/Routes.jsx similarity index 61% rename from src/Routes.js rename to src/Routes.jsx index 81aa54f..6b08fa7 100644 --- a/src/Routes.js +++ b/src/Routes.jsx @@ -1,8 +1,8 @@ import React from 'react'; import {Switch, Route } from "react-router-dom"; -import Home from "./components/views/Home.js"; -import CreateElection from "./components/views/CreateElection.js"; +import Home from "./components/views/Home"; +import CreateElection from "./components/views/CreateElection"; import Vote from "./components/views/Vote"; import Result from "./components/views/Result"; import UnknownView from "./components/views/UnknownView"; @@ -15,11 +15,11 @@ function Routes() { - - - - - + + + + + diff --git a/src/Util.jsx b/src/Util.jsx new file mode 100644 index 0000000..f4a3a17 --- /dev/null +++ b/src/Util.jsx @@ -0,0 +1,5 @@ +const colors = ["#015411", "#019812", "#6bca24", "#ffb200", "#ff5d00", "#b20616", "#6f0214"]; +export const grades = process.env.REACT_APP_GRADES.split(', ').map( + (e, i) => ({label: e, color:colors[i]})); + + diff --git a/src/components/form/ButtonWithConfirm.js b/src/components/form/ButtonWithConfirm.jsx similarity index 100% rename from src/components/form/ButtonWithConfirm.js rename to src/components/form/ButtonWithConfirm.jsx diff --git a/src/components/form/HelpButton.js b/src/components/form/HelpButton.jsx similarity index 100% rename from src/components/form/HelpButton.js rename to src/components/form/HelpButton.jsx diff --git a/src/components/form/ModalConfirm.js b/src/components/form/ModalConfirm.jsx similarity index 100% rename from src/components/form/ModalConfirm.js rename to src/components/form/ModalConfirm.jsx diff --git a/src/components/layouts/Footer.js b/src/components/layouts/Footer.jsx similarity index 100% rename from src/components/layouts/Footer.js rename to src/components/layouts/Footer.jsx diff --git a/src/components/layouts/Header.js b/src/components/layouts/Header.jsx similarity index 100% rename from src/components/layouts/Header.js rename to src/components/layouts/Header.jsx diff --git a/src/components/views/CreateElection.js b/src/components/views/CreateElection.jsx similarity index 87% rename from src/components/views/CreateElection.js rename to src/components/views/CreateElection.jsx index 6f47f67..d935c12 100644 --- a/src/components/views/CreateElection.js +++ b/src/components/views/CreateElection.jsx @@ -1,5 +1,5 @@ import React, {Component} from "react"; - +import { Redirect } from 'react-router-dom'; import { Container, Row, @@ -13,27 +13,16 @@ import { import {toast, ToastContainer} from 'react-toastify'; import 'react-toastify/dist/ReactToastify.css'; +import { resolve } from 'url'; import HelpButton from "../form/HelpButton"; import {arrayMove, sortableContainer, sortableElement, sortableHandle} from 'react-sortable-hoc'; import ButtonWithConfirm from "../form/ButtonWithConfirm"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import {faPlus, faTrashAlt, faCheck } from '@fortawesome/free-solid-svg-icons'; +import { grades } from '../../Util'; -//TODO : variable de config dans un fichier à part (avec les mentions, le min/max de mentions, le nombre max de candidats, les maxlength,l'url api, etc ...) -const mentions = [ - {label:"Excellent", color:"#015411"}, - {label:"Trés Bien", color:"#019812"}, - {label:"Bien", color:"#6bca24"}, - {label:"Assez Bien", color:"#ffb200"}, - {label:"Passable", color:"#ff5d00"}, - {label:"Insuffisant", color:"#b20616"}, - {label:"A Rejeter", color:"#6f0214"}, -]; - -const PATH_API = '/api/'; -const PATH_CREATE_ELECTION = 'create'; const DragHandle = sortableHandle(({children}) => {children}); @@ -85,10 +74,12 @@ class CreateElection extends Component { super(props); this.state = { candidates:[{label:""},{label:""}], - nbCandidatesWithLabel:0, + numCandidatesWithLabel:0, title:null, isVisibleTipsDragAndDropCandidate:true, - nbMentions:7 + numGrades:7, + successCreate: false, + redirectTo: null }; this.candidateInputs = []; this.focusInput= React.createRef(); @@ -108,7 +99,7 @@ class CreateElection extends Component { this.setState({ candidates: candidates}); } if(event.type === 'keypress'){ - setTimeout(()=>{ this.candidateInputs[this.state.candidates.length-1].focus()},250); + setTimeout(()=>{ this.candidateInputs[this.state.candidates.length-1].focus();},250); } }; @@ -125,15 +116,15 @@ class CreateElection extends Component { editCandidateLabel = (event, index) => { let candidates = this.state.candidates; - let nbLabels = 0; + let numLabels = 0; candidates[index].label = event.currentTarget.value; candidates.map((candidate,i)=>{ if(candidate.label!==""){ - nbLabels++; + numLabels++; } return candidate.label; }); - this.setState({candidates: candidates, nbCandidatesWithLabel:nbLabels}); + this.setState({candidates: candidates, numCandidatesWithLabel:numLabels}); }; @@ -156,8 +147,8 @@ class CreateElection extends Component { this.setState({candidates: candidates}); }; - handleChangeNbMentions= (event) => { - this.setState({nbMentions: event.target.value}); + handleChangeNumGrades= (event) => { + this.setState({numGrades: event.target.value}); }; componentWillMount() { @@ -168,26 +159,35 @@ class CreateElection extends Component { handleSubmit () { const { - candidates, + candidates, title, - nbMentions + numGrades } = this.state; - fetch(`${PATH_API}${PATH_CREATE_ELECTION}`, { + + const endpoint = resolve( + process.env.REACT_APP_SERVER_URL, + 'election/' + ); + + fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title: title, - candidates: candidates, + candidates: candidates.map(c => c.label), on_invitation_only: false, - num_grades: nbMentions, + num_grades: numGrades, elector_emails: [] - }) - } - ).then(response => response.json()) - .then(result => alert(result)) - .catch(error => error); + }) + }) + .then(response => response.json()) + .then(result => this.setState(state => ({ + redirectTo: '/create-success/' + result.id, + successCreate: true + }))) + .catch(error => error); }; handleSendWithoutCandidate = () => { @@ -197,7 +197,11 @@ class CreateElection extends Component { }; render(){ + const { successCreate, redirectTo } = this.state; const params = new URLSearchParams(this.props.location.search); + + if (successCreate) return ; + return( @@ -244,7 +248,7 @@ class CreateElection extends Component { - @@ -257,8 +261,8 @@ class CreateElection extends Component { - { mentions.map((mention,i) => { - return {mention.label} + { grades.map((mention,i) => { + return {mention.label} }) } @@ -267,7 +271,7 @@ class CreateElection extends Component {
- {this.state.nbCandidatesWithLabel>=2? + {this.state.numCandidatesWithLabel>=2?
Valider
Confirmez votre vote
@@ -288,8 +292,8 @@ class CreateElection extends Component { }
Mentions :
-
{ mentions.map((mention,i) => { - return (i{mention.label}: +
{ grades.map((mention,i) => { + return (i{mention.label}: }) }
diff --git a/src/components/views/CreateSuccess.js b/src/components/views/CreateSuccess.js index 904a94a..fbe12ca 100644 --- a/src/components/views/CreateSuccess.js +++ b/src/components/views/CreateSuccess.js @@ -1,18 +1,19 @@ import React, {Component} from "react"; import {Button, Col, Container, Row} from "reactstrap"; -import logoLine from "../../logos/logo-line-white.svg"; import {Link} from 'react-router-dom'; import { faCopy, faUsers } from '@fortawesome/free-solid-svg-icons'; import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import logoLine from "../../logos/logo-line-white.svg"; class UnknownView extends Component { constructor(props) { super(props); + const electionSlug = this.props.match.params.handle; this.state = { - urlOfVote:"http://localhost/vote", - urlOfResult:"http://localhost/result" + urlOfVote: "https://" + window.location.hostname + "/vote/" + electionSlug, + urlOfResult: "https://" + window.location.hostname + "/result/" + electionSlug }; this.urlVoteField = React.createRef(); this.urlResultField = React.createRef(); @@ -86,7 +87,7 @@ class UnknownView extends Component { - Participer maintenant ! + Participer maintenant !
diff --git a/src/components/views/Home.js b/src/components/views/Home.jsx similarity index 99% rename from src/components/views/Home.js rename to src/components/views/Home.jsx index 1e3e6e1..1f184e4 100644 --- a/src/components/views/Home.js +++ b/src/components/views/Home.jsx @@ -54,6 +54,6 @@ class Home extends Component { ) - } + }; } export default Home; diff --git a/src/components/views/Result.js b/src/components/views/Result.jsx similarity index 63% rename from src/components/views/Result.js rename to src/components/views/Result.jsx index de2fdad..570ed36 100644 --- a/src/components/views/Result.js +++ b/src/components/views/Result.jsx @@ -1,17 +1,8 @@ import React, {Component} from "react"; +import { Redirect } from 'react-router-dom'; +import { resolve } from 'url'; import {Container, Row, Col, Collapse, Card, CardHeader, CardBody, Table} from "reactstrap"; - -//TODO : variable de config dans un fichier à part (avec les mentions, le min/max de mentions, le nombre max de candidats, les maxlength,l'url api, etc ...) -const mentions = [ - {label: "Excellent", color: "#015411"}, - {label: "Trés Bien", color: "#019812"}, - {label: "Bien", color: "#6bca24"}, - {label: "Assez Bien", color: "#ffb200"}, - {label: "Passable", color: "#ff5d00"}, - {label: "Insuffisant", color: "#b20616"}, - {label: "A Rejeter", color: "#6f0214"}, -]; - +import { grades } from '../../Util'; class Result extends Component { @@ -20,49 +11,84 @@ class Result extends Component { this.state = { candidates: [], title: null, - nbMentions: 0, + numGrades: 0, colSizeCandidateLg: 4, colSizeCandidateMd: 6, colSizeCandidateXs: 12, - colSizeMentionLg: 1, - colSizeMentionMd: 1, - colSizeMentionXs: 1, + colSizeGradeLg: 1, + colSizeGradeMd: 1, + colSizeGradeXs: 1, collapseGraphics: false, - collapseProfiles: false - + collapseProfiles: false, + redirectLost: false, + electionGrades: grades }; + } + handleErrors = (response) => { + if (!response.ok) { + response.json().then( response => { + this.setState(state => ({ + redirectLost: '/unknown-election/' + encodeURIComponent(response)})); + }) + throw Error(response); + } + return response; } - componentDidMount() { - //todo fetch data from API - let fetchedData = { - title: "Merci d'évaluer les candidats suivants", - candidates: [ - {id: 0, label: "Mme ABCD", mention: 2, profile: [20, 20, 20, 10, 10, 20, 0], score: "55.28"}, - {id: 2, label: "M. EFGH", mention: 3, profile: [0, 20, 20, 10, 10, 30, 10], score: "43.10"}, - {id: 3, label: "M. IJKL", mention: 4, profile: [0, 0, 20, 25, 15, 20, 20], score: "22.82"}, - {id: 4, label: "M. MNOP", mention: 4, profile: [0, 0, 15, 15, 30, 10, 30], score: "12.72"} - ],//ordered by result - nbMentions: 7, - }; - let data = { - title: fetchedData.title, - candidates: fetchedData.candidates, - nbMentions: fetchedData.nbMentions, - colSizeCandidateLg: 0, - colSizeCandidateMd: 0, - colSizeCandidateXs: 0, - colSizeMentionLg: Math.floor((12 - this.state.colSizeCandidateLg) / fetchedData.nbMentions), - colSizeMentionMd: Math.floor((12 - this.state.colSizeCandidateMd) / fetchedData.nbMentions), - colSizeMentionXs: Math.floor((12 - this.state.colSizeCandidateXs) / fetchedData.nbMentions), - }; - data.colSizeCandidateLg = ((12 - data.colSizeMentionLg * data.nbMentions) > 0) ? (12 - data.colSizeMentionLg * data.nbMentions) : 12; - data.colSizeCandidateMd = ((12 - data.colSizeMentionMd * data.nbMentions) > 0) ? (12 - data.colSizeMentionMd * data.nbMentions) : 12; - data.colSizeCandidateXs = ((12 - data.colSizeMentionXs * data.nbMentions) > 0) ? (12 - data.colSizeMentionXs * data.nbMentions) : 12; - this.setState(data); + resultsToState = (response) => { + const candidates = response.map(c => ({ + id: c.id, label: c.name, profile: c.profile, grade:c.grade, score: c.score + })); + this.setState(state => ({candidates: candidates})); + return response; } + detailsToState = (response) => { + const numGrades = response.num_grades; + const colSizeGradeLg = Math.floor((12 - this.state.colSizeCandidateLg) / numGrades); + const colSizeGradeMd = Math.floor((12 - this.state.colSizeCandidateMd) / numGrades); + const colSizeGradeXs = Math.floor((12 - this.state.colSizeCandidateXs) / numGrades); + this.setState(state => ({ + title: response.title, + numGrades: numGrades, + colSizeGradeLg: colSizeGradeLg, + colSizeGradeMd: colSizeGradeMd, + colSizeGradeXs: colSizeGradeXs, + colSizeCandidateLg: ((12 - colSizeGradeLg * numGrades) > 0) ? (12 - colSizeGradeLg * numGrades) : 12, + colSizeCandidateMd: ((12 - colSizeGradeMd * numGrades) > 0) ? (12 - colSizeGradeMd * numGrades) : 12, + colSizeCandidateXs: ((12 - colSizeGradeXs * numGrades) > 0) ? (12 - colSizeGradeXs * numGrades) : 12, + electionGrades: grades.slice(0, numGrades) + })); + return response; + } + + componentDidMount() { + // FIXME we should better handling logs + + const electionSlug = this.props.match.params.handle; + + // get details of the election + const detailsEndpoint = resolve(process.env.REACT_APP_SERVER_URL, + 'election/get/'.concat(electionSlug)); + + fetch(detailsEndpoint) + .then(this.handleErrors) + .then(response => response.json()) + .then(this.detailsToState) + .catch(error => console.log(error)); + + // get results of the election + const resultsEndpoint = resolve(process.env.REACT_APP_SERVER_URL, + 'election/results/'.concat(electionSlug)); + + fetch(resultsEndpoint) + .then(this.handleErrors) + .then(response => response.json()) + .then(this.resultsToState) + .catch(error => console.log(error)); + + } toggleGraphics = () => { this.setState(state => ({collapseGraphics: !state.collapseGraphics})); @@ -73,6 +99,16 @@ class Result extends Component { }; render() { + + const { redirectLost, + candidates, + electionGrades + } = this.state; + + if (redirectLost) { + return () + } + return ( @@ -81,13 +117,13 @@ class Result extends Component {

Résultat du vote :

-
    {this.state.candidates.map((candidate, i) => { +
      {candidates.map((candidate, i) => { return (
    1. {candidate.label}{candidate.score}%{mentions[candidate.mention].label}
    2. ); + }}>{grades[candidate.grade].label}); })}
    @@ -102,9 +138,9 @@ class Result extends Component {
    + style={{height: (candidates.length * 28) + 30}}/> - {this.state.candidates.map((candidate, i) => { + {candidates.map((candidate, i) => { return ( {/*candidate.label*/} @@ -120,7 +156,7 @@ class Result extends Component { } return (); }else{ return null @@ -136,7 +172,7 @@ class Result extends Component {
    - {this.state.candidates.map((candidate, i) => { + {candidates.map((candidate, i) => { return ( {(i > 0) ? ", " : ""}{i + 1}: {candidate.label}); })} @@ -144,14 +180,13 @@ class Result extends Component {
    - {mentions.map((mention, i) => { - return (i < this.state.nbMentions) ? + {electionGrades.map((grade, i) => { + return ( {mention.label} : - }) - } + }}>{grade.label} + )})}
    @@ -173,14 +208,14 @@ class Result extends Component { - {mentions.map((mention, i) => { + {electionGrades.map((grade, i) => { return (); + }}>{grade.label} ); })} - {this.state.candidates.map((candidate, i) => { + {candidates.map((candidate, i) => { return ( {/*candidate.label*/} @@ -191,7 +226,7 @@ class Result extends Component { })}
    {i + 1} 
    #{mention.label}
    {i + 1}
    - {this.state.candidates.map((candidate, i) => { + {candidates.map((candidate, i) => { return ({(i > 0) ? ", " : ""}{i + 1}: {candidate.label}); })} diff --git a/src/components/views/UnknownView.js b/src/components/views/UnknownView.jsx similarity index 100% rename from src/components/views/UnknownView.js rename to src/components/views/UnknownView.jsx diff --git a/src/components/views/Vote.js b/src/components/views/Vote.js deleted file mode 100644 index ac84fe6..0000000 --- a/src/components/views/Vote.js +++ /dev/null @@ -1,160 +0,0 @@ -import React, {Component} from "react"; -import {Button, Col, Container, Row} from "reactstrap"; -import {toast, ToastContainer} from "react-toastify"; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; -import {faCheck} from "@fortawesome/free-solid-svg-icons"; - -//TODO : variable de config dans un fichier à part (avec les mentions, le min/max de mentions, le nombre max de candidats, les maxlength,l'url api, etc ...) -const mentions = [ - {label:"Excellent", color:"#015411"}, - {label:"Trés Bien", color:"#019812"}, - {label:"Bien", color:"#6bca24"}, - {label:"Assez Bien", color:"#ffb200"}, - {label:"Passable", color:"#ff5d00"}, - {label:"Insuffisant", color:"#b20616"}, - {label:"A Rejeter", color:"#6f0214"}, - ]; - - - -class Vote extends Component { - - constructor(props) { - super(props); - this.state = { - candidates:[], - title:null, - nbMentions:0, - ratedCandidates:[], - colSizeCandidateLg:4, - colSizeCandidateMd:6, - colSizeCandidateXs:12, - colSizeMentionLg:1, - colSizeMentionMd:1, - colSizeMentionXs:1, - - }; - - } - - componentDidMount() { - //todo fetch data from API - let fetchedData={ - title:"Merci d'évaluer les candidats suivants", - candidates:[ {id:0, label:"Mme ABCD"}, {id:2, label:"M. EFGH"}, {id:3, label:"M. IJKL"}, {id:4, label:"M. MNOP"} ], - nbMentions:7, - }; - let data={ - title:fetchedData.title, - candidates:fetchedData.candidates, - nbMentions:fetchedData.nbMentions, - colSizeCandidateLg:0, - colSizeCandidateMd:0, - colSizeCandidateXs:0, - colSizeMentionLg:Math.floor((12-this.state.colSizeCandidateLg)/fetchedData.nbMentions), - colSizeMentionMd:Math.floor((12-this.state.colSizeCandidateMd)/fetchedData.nbMentions), - colSizeMentionXs:Math.floor((12-this.state.colSizeCandidateXs)/fetchedData.nbMentions), - }; - data.colSizeCandidateLg=((12-data.colSizeMentionLg*data.nbMentions)>0)?(12-data.colSizeMentionLg*data.nbMentions):12; - data.colSizeCandidateMd=((12-data.colSizeMentionMd*data.nbMentions)>0)?(12-data.colSizeMentionMd*data.nbMentions):12; - data.colSizeCandidateXs=((12-data.colSizeMentionXs*data.nbMentions)>0)?(12-data.colSizeMentionXs*data.nbMentions):12; - - //shuffle candidates - let i, - j, - temp; - for (i = data.candidates.length - 1; i > 0; i--) { - j = Math.floor(Math.random() * (i + 1)); - temp = data.candidates[i]; - data.candidates[i] = data.candidates[j]; - data.candidates[j] = temp; - } - this.setState(data); - } - - - handleMentionClick= (event) => { - let data={ - id:parseInt(event.currentTarget.getAttribute("data-id")), - value:parseInt(event.currentTarget.value) - }; - //remove candidate - let ratedCandidates = this.state.ratedCandidates.filter(ratedCandidate => ratedCandidate.id !== data.id); - ratedCandidates.push(data); - this.setState({ratedCandidates:ratedCandidates}); - - }; - - handleSubmitWithoutAllRate = () => { - toast.error("Vous devez évaluer l'ensemble des propositions/candidats !", { - position: toast.POSITION.TOP_CENTER - }); - }; - handleSubmit= (event) => { - event.preventDefault(); - }; - - - render(){ - return( - - -
    - -

    { this.state.title }

    -
    - -
     
    - { mentions.map((mention,j) => { - return (j{mention.label}:null - }) - } -
    - - { - - this.state.candidates.map((candidate,i) => { - return - -
    {candidate.label}

    - { mentions.map((mention,j) => { - return (j - - :null - }) - } -
    - }) - - } - - - - {(this.state.ratedCandidates.length!==this.state.candidates.length)?:} - - - -
    -
    - ) - } -} -export default Vote; diff --git a/src/components/views/Vote.jsx b/src/components/views/Vote.jsx new file mode 100644 index 0000000..690a1ce --- /dev/null +++ b/src/components/views/Vote.jsx @@ -0,0 +1,213 @@ +import React, {Component} from "react"; +import { Redirect } from 'react-router-dom'; +import {Button, Col, Container, Row} from "reactstrap"; +import {toast, ToastContainer} from "react-toastify"; +import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; +import {faCheck} from "@fortawesome/free-solid-svg-icons"; +import { resolve } from 'url'; +import { grades } from '../../Util'; + + +class Vote extends Component { + + constructor(props) { + super(props); + this.state = { + candidates:[], + title:null, + numGrades:0, + ratedCandidates:[], + colSizeCandidateLg:4, + colSizeCandidateMd:6, + colSizeCandidateXs:12, + colSizeGradeLg:1, + colSizeGradeMd:1, + colSizeGradeXs:1, + redirectTo: null, + electionGrades: grades + }; + + } + + handleErrors = (response) => { + if (!response.ok) { + response.json().then( response => { + console.log(response); + this.setState(state => ({ + redirectTo: '/unknown-election/' + encodeURIComponent(response)})); + }) + throw Error(response); + } + return response; + } + + detailsToState = (response) => { + const numGrades = response.num_grades; + const candidates = response.candidates.map((c, i) => ({ + id: i, + label: c + })); + //shuffle candidates + let i, j, temp; + for (i = candidates.length - 1; i > 0; i--) { + j = Math.floor(Math.random() * (i + 1)); + temp = candidates[i]; + candidates[i] = candidates[j]; + candidates[j] = temp; + } + const colSizeGradeLg = Math.floor((12 - this.state.colSizeCandidateLg) / numGrades); + const colSizeGradeMd = Math.floor((12 - this.state.colSizeCandidateMd) / numGrades); + const colSizeGradeXs = Math.floor((12 - this.state.colSizeCandidateXs) / numGrades); + + this.setState(state => ({ + title: response.title, + candidates: candidates, + numGrades: numGrades, + colSizeGradeLg: colSizeGradeLg, + colSizeGradeMd: colSizeGradeMd, + colSizeGradeXs: colSizeGradeXs, + colSizeCandidateLg: ((12 - colSizeGradeLg * numGrades) > 0) ? + (12 - colSizeGradeLg * numGrades) : 12, + colSizeCandidateMd: ((12 - colSizeGradeMd * numGrades) > 0) ? + (12 - colSizeGradeMd * numGrades) : 12, + colSizeCandidateXs: ((12 - colSizeGradeXs * numGrades) > 0) ? + (12 - colSizeGradeXs * numGrades) : 12, + electionGrades: grades.slice(0, numGrades) + })); + return response; + } + + componentDidMount() { + // FIXME we should better handling logs + + const electionSlug = this.props.match.params.handle; + const detailsEndpoint = resolve(process.env.REACT_APP_SERVER_URL, + 'election/get/'.concat(electionSlug)); + + fetch(detailsEndpoint) + .then(this.handleErrors) + .then(response => response.json()) + .then(this.detailsToState) + .catch(error => console.log(error)); + } + + + handleGradeClick = (event) => { + let data={ + id:parseInt(event.currentTarget.getAttribute("data-id")), + value:parseInt(event.currentTarget.value) + }; + //remove candidate + let ratedCandidates = this.state.ratedCandidates.filter(ratedCandidate => ratedCandidate.id !== data.id); + ratedCandidates.push(data); + this.setState({ratedCandidates:ratedCandidates}); + + }; + + handleSubmitWithoutAllRate = () => { + toast.error("Vous devez évaluer l'ensemble des propositions/candidats !", { + position: toast.POSITION.TOP_CENTER + }); + }; + + handleSubmit = (event) => { + event.preventDefault(); + + const { ratedCandidates } = this.state; + const electionSlug = this.props.match.params.handle; + const endpoint = resolve(process.env.REACT_APP_SERVER_URL, + 'election/vote/'); + + const gradesById = {}; + ratedCandidates.forEach(c => { gradesById[c.id] = c.value; }); + const gradesByCandidate = []; + Object.keys(gradesById) + .sort() + .forEach(id => {gradesByCandidate.push(gradesById[id]);}); + + fetch(endpoint, { + method: 'POST', + headers: {'Content-Type': 'application/json'}, + body: JSON.stringify({ + election: electionSlug, + grades_by_candidate: gradesByCandidate, + }) + }) + .then(this.handleErrors) + .then(result => this.setState({redirectTo: '/vote-success/' + electionSlug})) + .catch(error => error); + }; + + + render(){ + + const { redirectTo, + candidates, + electionGrades + } = this.state; + + if (redirectTo) { + return (); + } + + return( + + +
    + +

    { this.state.title }

    +
    + +
     
    + { electionGrades.map((grade,j) => { + return (j{grade.label}:null; + }) + } +
    + + { + candidates.map((candidate,i) => { + return + +
    {candidate.label}

    + { this.state.electionGrades.map((grade,j) => { + return (j + + :null + }) + } +
    + }) + + } + + + + {(this.state.ratedCandidates.length!==this.state.candidates.length)?:} + + + +
    +
    + ) + } +} +export default Vote; diff --git a/src/components/views/VoteSuccess.js b/src/components/views/VoteSuccess.js index 9b07532..2e74211 100644 --- a/src/components/views/VoteSuccess.js +++ b/src/components/views/VoteSuccess.js @@ -1,26 +1,23 @@ import React, {Component} from "react"; -import {Button, Col, Container, Row} from "reactstrap"; +import {Col, Container, Row} from "reactstrap"; import logoLine from "../../logos/logo-line-white.svg"; import {Link} from 'react-router-dom'; -import { faCopy, faUsers } from '@fortawesome/free-solid-svg-icons'; -import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"; class UnknownView extends Component { - constructor(props) { - super(props); - } - render(){ return( - logo + + logo + -

    Participation enregistrée avec succès !

    -

    Merci pour votre participation.

    + +

    Participation enregistrée avec succès !

    +

    Merci pour votre participation.

    diff --git a/src/index.js b/src/index.jsx similarity index 100% rename from src/index.js rename to src/index.jsx diff --git a/src/serviceWorker.js b/src/serviceWorker.jsx similarity index 100% rename from src/serviceWorker.js rename to src/serviceWorker.jsx