You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mvfront-react/src/components/views/Vote.jsx

356 lines
11 KiB

/* eslint react/prop-types: 0 */
import React, { Component } from "react";
import { Redirect } from "react-router-dom";
import { withTranslation } from "react-i18next";
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 { i18nGrades } from "../../Util";
import { AppContext } from "../../AppContext";
import { errorMessage } from "../../Errors";
const shuffle = array => array.sort(() => Math.random() - 0.5);
class Vote extends Component {
static contextType = AppContext;
4 years ago
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: i18nGrades(),
errorMsg: ""
4 years ago
};
}
4 years ago
handleErrors = response => {
if (!response.ok) {
response.json().then(response => {
console.log(response);
const { t } = this.props;
this.setState(() => ({
errorMsg: errorMessage(response, t)
}));
4 years ago
});
throw Error(response);
}
4 years ago
return response;
};
4 years ago
detailsToState = response => {
const numGrades = response.num_grades;
const candidates = response.candidates.map((c, i) => ({
id: i,
label: c
4 years ago
}));
shuffle(candidates);
4 years ago
const colSizeGradeLg = Math.floor(
(12 - this.state.colSizeCandidateLg) / numGrades
4 years ago
);
const colSizeGradeMd = Math.floor(
(12 - this.state.colSizeCandidateMd) / numGrades
4 years ago
);
const colSizeGradeXs = Math.floor(
(12 - this.state.colSizeCandidateXs) / numGrades
4 years ago
);
this.setState(() => ({
4 years ago
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
4 years ago
}));
return response;
};
4 years ago
componentDidMount() {
// FIXME we should better handling logs
const electionSlug = this.props.match.params.slug;
4 years ago
const detailsEndpoint = resolve(
this.context.urlServer,
this.context.routesServer.getElection.replace(
new RegExp(":slug", "g"),
electionSlug
)
4 years ago
);
fetch(detailsEndpoint)
.then(this.handleErrors)
.then(response => response.json())
.then(this.detailsToState)
.catch(error => console.log(error));
}
4 years ago
handleGradeClick = event => {
let data = {
id: parseInt(event.currentTarget.getAttribute("data-id")),
value: parseInt(event.currentTarget.value)
4 years ago
};
//remove candidate
let ratedCandidates = this.state.ratedCandidates.filter(
ratedCandidate => ratedCandidate.id !== data.id
4 years ago
);
ratedCandidates.push(data);
this.setState({ ratedCandidates });
4 years ago
};
4 years ago
handleSubmitWithoutAllRate = () => {
const { t } = this.props;
toast.error(t("You have to judge every candidate/proposal!"), {
position: toast.POSITION.TOP_CENTER
4 years ago
});
};
4 years ago
handleSubmit = event => {
event.preventDefault();
const { ratedCandidates } = this.state;
const electionSlug = this.props.match.params.slug;
const token = this.props.location.search.substr(7);
4 years ago
const endpoint = resolve(
this.context.urlServer,
this.context.routesServer.voteElection
4 years ago
);
4 years ago
const gradesById = {};
ratedCandidates.forEach(c => {
gradesById[c.id] = c.value;
});
const gradesByCandidate = [];
Object.keys(gradesById).forEach(id => {
gradesByCandidate.push(gradesById[id]);
});
const payload = {
election: electionSlug,
grades_by_candidate: gradesByCandidate
};
if (token !== "") {
payload["token"] = token;
}
4 years ago
fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
4 years ago
})
.then(this.handleErrors)
.then(() =>
this.setState({ redirectTo: "/vote-success/" + electionSlug })
4 years ago
)
.catch(error => error);
};
4 years ago
render() {
const { t } = this.props;
const { candidates, errorMsg, redirectTo } = this.state;
4 years ago
const grades = i18nGrades();
const offsetGrade = grades.length - this.state.numGrades;
4 years ago
const electionGrades = grades.slice(0, this.state.numGrades);
4 years ago
if (redirectTo) {
return <Redirect to={redirectTo} />;
}
4 years ago
if (errorMsg !== "") {
return (
<Container>
<Row>
<Col>
<h3>{errorMsg}</h3>
</Col>
</Row>
</Container>
);
}
4 years ago
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}
>
4 years ago
<h5>&nbsp;</h5>
</Col>
4 years ago
{electionGrades.map((grade, gradeId) => {
return gradeId < this.state.numGrades ? (
4 years ago
<Col
xs={this.state.colSizeGradeXs}
md={this.state.colSizeGradeMd}
lg={this.state.colSizeGradeLg}
4 years ago
key={gradeId}
4 years ago
className="text-center p-0"
style={{ lineHeight: 2 }}
>
4 years ago
<small
className="nowrap bold badge"
style={{ backgroundColor: grade.color, color: "#fff" }}
>
4 years ago
{grade.label}
</small>
</Col>
) : null;
})}
</Row>
4 years ago
{candidates.map((candidate, candidateId) => {
4 years ago
return (
4 years ago
<Row key={candidateId} className="cardVote">
4 years ago
<Col
xs={this.state.colSizeCandidateXs}
md={this.state.colSizeCandidateMd}
lg={this.state.colSizeCandidateLg}
>
4 years ago
<h5 className="m-0">{candidate.label}</h5>
<hr className="d-lg-none" />
</Col>
4 years ago
{electionGrades.map((grade, gradeId) => {
console.assert(gradeId < this.state.numGrades);
const gradeValue = grade.value - offsetGrade;
4 years ago
return (
4 years ago
<Col
xs={this.state.colSizeGradeXs}
md={this.state.colSizeGradeMd}
lg={this.state.colSizeGradeLg}
4 years ago
key={gradeId}
className="text-lg-center"
>
4 years ago
<label
htmlFor={
"candidateGrade" + candidateId + "-" + gradeValue
}
className="check"
>
4 years ago
<small
className="nowrap d-lg-none ml-2 bold badge"
style={
this.state.ratedCandidates.find(function(
ratedCandidat
4 years ago
) {
return (
JSON.stringify(ratedCandidat) ===
JSON.stringify({
id: candidate.id,
value: gradeValue
})
4 years ago
);
})
? { backgroundColor: grade.color, color: "#fff" }
4 years ago
: {
backgroundColor: "transparent",
color: "#000"
4 years ago
}
}
>
4 years ago
{grade.label}
</small>
<input
type="radio"
name={"candidate" + candidateId}
id={"candidateGrade" + candidateId + "-" + gradeValue}
4 years ago
data-index={candidateId}
4 years ago
data-id={candidate.id}
value={grade.value - offsetGrade}
4 years ago
onClick={this.handleGradeClick}
defaultChecked={this.state.ratedCandidates.find(
function(element) {
return (
JSON.stringify(element) ===
JSON.stringify({
id: candidate.id,
value: gradeValue
})
4 years ago
);
}
4 years ago
)}
/>
<span
className="checkmark"
style={
this.state.ratedCandidates.find(function(
ratedCandidat
4 years ago
) {
return (
JSON.stringify(ratedCandidat) ===
JSON.stringify({
id: candidate.id,
value: gradeValue
})
4 years ago
);
})
? { backgroundColor: grade.color, color: "#fff" }
4 years ago
: {
backgroundColor: "transparent",
color: "#000"
4 years ago
}
}
/>
</label>
</Col>
);
4 years ago
})}
</Row>
);
})}
<Row>
<Col className="text-center">
{this.state.ratedCandidates.length !==
this.state.candidates.length ? (
<Button
type="button"
onClick={this.handleSubmitWithoutAllRate}
className="btn btn-dark "
>
4 years ago
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Submit my vote")}
4 years ago
</Button>
) : (
<Button type="submit" className="btn btn-success ">
<FontAwesomeIcon icon={faCheck} className="mr-2" />
{t("Submit my vote")}
4 years ago
</Button>
)}
</Col>
</Row>
</form>
</Container>
);
}
}
4 years ago
export default withTranslation()(Vote);