Connection to MV API (#1)
* change url * fix bug in API * add .env config * rename mentions to grades * connect Result.jsx to API * connect votepull/73/head
parent
ea2d783f3c
commit
e49ad0bde8
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { BrowserRouter as Router} from "react-router-dom";
|
import { BrowserRouter as Router} from "react-router-dom";
|
||||||
import Routes from "./Routes.js";
|
import Routes from "./Routes";
|
||||||
|
|
||||||
import Header from "./components/layouts/Header";
|
import Header from "./components/layouts/Header";
|
||||||
import Footer from "./components/layouts/Footer";
|
import Footer from "./components/layouts/Footer";
|
@ -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]}));
|
||||||
|
|
||||||
|
|
@ -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(
|
|
||||||
<Container>
|
|
||||||
<ToastContainer/>
|
|
||||||
<form onSubmit={this.handleSubmit} autoComplete="off" >
|
|
||||||
<Row>
|
|
||||||
<Col ><h3>{ this.state.title }</h3></Col>
|
|
||||||
</Row>
|
|
||||||
<Row className="cardVote d-none d-lg-flex" >
|
|
||||||
<Col xs={this.state.colSizeCandidateXs} md={this.state.colSizeCandidateMd} lg={this.state.colSizeCandidateLg} ><h5 > </h5></Col>
|
|
||||||
{ mentions.map((mention,j) => {
|
|
||||||
return (j<this.state.nbMentions)?<Col xs={this.state.colSizeMentionXs} md={this.state.colSizeMentionMd} lg={this.state.colSizeMentionLg} key={j} className="text-center p-0" style={{lineHeight:2}}><small className="nowrap bold badge" style={{backgroundColor:mention.color,color:"#fff"}}>{mention.label}</small></Col>:null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
{
|
|
||||||
|
|
||||||
this.state.candidates.map((candidate,i) => {
|
|
||||||
return <Row key={i} className="cardVote">
|
|
||||||
<Col xs={this.state.colSizeCandidateXs} md={this.state.colSizeCandidateMd} lg={this.state.colSizeCandidateLg} >
|
|
||||||
<h5 className="m-0">{candidate.label}</h5><hr className="d-lg-none" /></Col>
|
|
||||||
{ mentions.map((mention,j) => {
|
|
||||||
return (j<this.state.nbMentions)?<Col
|
|
||||||
xs={this.state.colSizeMentionXs} md={this.state.colSizeMentionMd} lg={this.state.colSizeMentionLg} key={j}
|
|
||||||
className="text-lg-center"
|
|
||||||
>
|
|
||||||
|
|
||||||
<label htmlFor={"candidateMention"+i+"-"+j} className="check"
|
|
||||||
|
|
||||||
>
|
|
||||||
<small className="nowrap d-lg-none ml-2 bold badge"
|
|
||||||
style={
|
|
||||||
this.state.ratedCandidates.find(function(ratedCandidat){return JSON.stringify(ratedCandidat) === JSON.stringify({id:candidate.id,value:j})})?
|
|
||||||
{backgroundColor:mention.color,color:"#fff"}:{backgroundColor:'transparent',color:"#000"}
|
|
||||||
}
|
|
||||||
|
|
||||||
>{mention.label}</small>
|
|
||||||
<input type="radio" name={"candidate"+i} id={"candidateMention"+i+"-"+j} data-index={i} data-id={candidate.id} value={j} onClick={this.handleMentionClick} defaultChecked={this.state.ratedCandidates.find(function(element) { return JSON.stringify(element) === JSON.stringify({id:candidate.id,value:j})})} />
|
|
||||||
<span className="checkmark" style={
|
|
||||||
this.state.ratedCandidates.find(function(ratedCandidat){return JSON.stringify(ratedCandidat) === JSON.stringify({id:candidate.id,value:j})})?
|
|
||||||
{backgroundColor:mention.color,color:"#fff"}:{backgroundColor:'transparent',color:"#000"}
|
|
||||||
}/>
|
|
||||||
</label></Col>:null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</Row>
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
<Row>
|
|
||||||
<Col className="text-center" >
|
|
||||||
{(this.state.ratedCandidates.length!==this.state.candidates.length)?<Button type="button" onClick={this.handleSubmitWithoutAllRate} className="btn btn-dark "><FontAwesomeIcon icon={faCheck} className="mr-2" />Valider</Button>:<Button type="submit" className="btn btn-success "><FontAwesomeIcon icon={faCheck} className="mr-2" />Valider</Button>}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</Container>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default Vote;
|
|
@ -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 (<Redirect to={redirectTo}/>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return(
|
||||||
|
<Container>
|
||||||
|
<ToastContainer/>
|
||||||
|
<form onSubmit={this.handleSubmit} autoComplete="off" >
|
||||||
|
<Row>
|
||||||
|
<Col ><h3>{ this.state.title }</h3></Col>
|
||||||
|
</Row>
|
||||||
|
<Row className="cardVote d-none d-lg-flex" >
|
||||||
|
<Col xs={this.state.colSizeCandidateXs} md={this.state.colSizeCandidateMd} lg={this.state.colSizeCandidateLg} ><h5 > </h5></Col>
|
||||||
|
{ electionGrades.map((grade,j) => {
|
||||||
|
return (j<this.state.numGrades)?<Col xs={this.state.colSizeGradeXs} md={this.state.colSizeGradeMd} lg={this.state.colSizeGradeLg} key={j} className="text-center p-0" style={{lineHeight:2}}><small className="nowrap bold badge" style={{backgroundColor:grade.color,color:"#fff"}}>{grade.label}</small></Col>:null;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{
|
||||||
|
candidates.map((candidate,i) => {
|
||||||
|
return <Row key={i} className="cardVote">
|
||||||
|
<Col xs={this.state.colSizeCandidateXs} md={this.state.colSizeCandidateMd} lg={this.state.colSizeCandidateLg} >
|
||||||
|
<h5 className="m-0">{candidate.label}</h5><hr className="d-lg-none" /></Col>
|
||||||
|
{ this.state.electionGrades.map((grade,j) => {
|
||||||
|
return (j<this.state.numGrades)?<Col
|
||||||
|
xs={this.state.colSizeGradeXs} md={this.state.colSizeGradeMd} lg={this.state.colSizeGradeLg} key={j}
|
||||||
|
className="text-lg-center"
|
||||||
|
>
|
||||||
|
|
||||||
|
<label htmlFor={"candidateGrade"+i+"-"+j} className="check"
|
||||||
|
|
||||||
|
>
|
||||||
|
<small className="nowrap d-lg-none ml-2 bold badge"
|
||||||
|
style={
|
||||||
|
this.state.ratedCandidates.find(function(ratedCandidat){return JSON.stringify(ratedCandidat) === JSON.stringify({id:candidate.id,value:j})})?
|
||||||
|
{backgroundColor:grade.color,color:"#fff"}:{backgroundColor:'transparent',color:"#000"}
|
||||||
|
}
|
||||||
|
|
||||||
|
>{grade.label}</small>
|
||||||
|
<input type="radio" name={"candidate"+i} id={"candidateGrade"+i+"-"+j} data-index={i} data-id={candidate.id} value={j} onClick={this.handleGradeClick} defaultChecked={this.state.ratedCandidates.find(function(element) { return JSON.stringify(element) === JSON.stringify({id:candidate.id,value:j})})} />
|
||||||
|
<span className="checkmark" style={
|
||||||
|
this.state.ratedCandidates.find(function(ratedCandidat){return JSON.stringify(ratedCandidat) === JSON.stringify({id:candidate.id,value:j})})?
|
||||||
|
{backgroundColor:grade.color,color:"#fff"}:{backgroundColor:'transparent',color:"#000"}
|
||||||
|
}/>
|
||||||
|
</label></Col>:null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Row>
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
<Row>
|
||||||
|
<Col className="text-center" >
|
||||||
|
{(this.state.ratedCandidates.length!==this.state.candidates.length)?<Button type="button" onClick={this.handleSubmitWithoutAllRate} className="btn btn-dark "><FontAwesomeIcon icon={faCheck} className="mr-2" />Valider</Button>:<Button type="submit" className="btn btn-success "><FontAwesomeIcon icon={faCheck} className="mr-2" />Valider</Button>}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export default Vote;
|
Loading…
Reference in new issue