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.
272 lines
8.9 KiB
272 lines
8.9 KiB
import { useState } from "react";
|
|
import Head from "next/head";
|
|
import { useRouter } from "next/router";
|
|
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
|
|
import { useTranslation } from "next-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 { getDetails, castBallot, apiErrors } from "@services/api";
|
|
import Error from "@components/Error";
|
|
import { translateGrades } from "@services/grades";
|
|
import config from "../../../next-i18next.config.js";
|
|
|
|
const shuffle = (array) => array.sort(() => Math.random() - 0.5);
|
|
|
|
export async function getServerSideProps({ query: { pid, tid }, locale }) {
|
|
const [details, translations] = await Promise.all([
|
|
getDetails(pid),
|
|
serverSideTranslations(locale, [], config),
|
|
]);
|
|
|
|
if (typeof details === "string" || details instanceof String) {
|
|
return { props: { err: details, ...translations } };
|
|
}
|
|
|
|
if (!details.candidates || !Array.isArray(details.candidates)) {
|
|
return { props: { err: "Unknown error", ...translations } };
|
|
}
|
|
|
|
shuffle(details.candidates);
|
|
|
|
return {
|
|
props: {
|
|
...translations,
|
|
invitationOnly: details.on_invitation_only,
|
|
restrictResults: details.restrict_results,
|
|
candidates: details.candidates.map((name, i) => ({ id: i, label: name })),
|
|
title: details.title,
|
|
numGrades: details.num_grades,
|
|
pid: pid,
|
|
token: tid || null,
|
|
},
|
|
};
|
|
}
|
|
|
|
const VoteBallot = ({ candidates, title, numGrades, pid, err, token }) => {
|
|
const { t } = useTranslation();
|
|
|
|
if (err) {
|
|
return <Error value={apiErrors(err, t)}></Error>;
|
|
}
|
|
|
|
const [judgments, setJudgments] = useState([]);
|
|
const colSizeCandidateLg = 4;
|
|
const colSizeCandidateMd = 6;
|
|
const colSizeCandidateXs = 12;
|
|
const colSizeGradeLg = Math.floor((12 - colSizeCandidateLg) / numGrades);
|
|
const colSizeGradeMd = Math.floor((12 - colSizeCandidateMd) / numGrades);
|
|
const colSizeGradeXs = Math.floor((12 - colSizeCandidateXs) / numGrades);
|
|
|
|
const router = useRouter();
|
|
|
|
const allGrades = translateGrades(t);
|
|
const grades = allGrades.filter(
|
|
(grade) => grade.value >= allGrades.length - numGrades
|
|
);
|
|
|
|
const handleGradeClick = (event) => {
|
|
let data = {
|
|
id: parseInt(event.currentTarget.getAttribute("data-id")),
|
|
value: parseInt(event.currentTarget.value),
|
|
};
|
|
//remove candidate
|
|
const newJudgments = judgments.filter(
|
|
(judgment) => judgment.id !== data.id
|
|
);
|
|
newJudgments.push(data);
|
|
setJudgments(newJudgments);
|
|
};
|
|
|
|
const handleSubmitWithoutAllRate = () => {
|
|
toast.error(t("You have to judge every candidate/proposal!"), {
|
|
position: toast.POSITION.TOP_CENTER,
|
|
});
|
|
};
|
|
|
|
const handleSubmit = (event) => {
|
|
event.preventDefault();
|
|
|
|
const gradesById = {};
|
|
judgments.forEach((c) => {
|
|
gradesById[c.id] = c.value;
|
|
});
|
|
const gradesByCandidate = [];
|
|
Object.keys(gradesById).forEach((id) => {
|
|
gradesByCandidate.push(gradesById[id]);
|
|
});
|
|
|
|
castBallot(gradesByCandidate, pid, token, () => {
|
|
router.push(`/vote/${pid}/confirm`);
|
|
});
|
|
};
|
|
|
|
return (
|
|
<Container>
|
|
<Head>
|
|
<title>{title}</title>
|
|
|
|
<title>{title}</title>
|
|
<meta key="og:title" property="og:title" content={title} />
|
|
<meta
|
|
property="og:description"
|
|
key="og:description"
|
|
content={t("common.application")}
|
|
/>
|
|
</Head>
|
|
<ToastContainer />
|
|
<form onSubmit={handleSubmit} autoComplete="off">
|
|
<Row>
|
|
<Col>
|
|
<h3>{title}</h3>
|
|
</Col>
|
|
</Row>
|
|
<Row className="cardVote d-none d-lg-flex">
|
|
<Col
|
|
xs={colSizeCandidateXs}
|
|
md={colSizeCandidateMd}
|
|
lg={colSizeCandidateLg}
|
|
>
|
|
<h5> </h5>
|
|
</Col>
|
|
{grades.map((grade, gradeId) => {
|
|
return gradeId < numGrades ? (
|
|
<Col
|
|
xs={colSizeGradeXs}
|
|
md={colSizeGradeMd}
|
|
lg={colSizeGradeLg}
|
|
key={gradeId}
|
|
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, candidateId) => {
|
|
return (
|
|
<Row key={candidateId} className="cardVote">
|
|
<Col
|
|
xs={colSizeCandidateXs}
|
|
md={colSizeCandidateMd}
|
|
lg={colSizeCandidateLg}
|
|
>
|
|
<h5 className="m-0">{candidate.label}</h5>
|
|
<hr className="d-lg-none" />
|
|
</Col>
|
|
{grades.map((grade, gradeId) => {
|
|
console.assert(gradeId < numGrades);
|
|
const gradeValue = grade.value;
|
|
return (
|
|
<Col
|
|
xs={colSizeGradeXs}
|
|
md={colSizeGradeMd}
|
|
lg={colSizeGradeLg}
|
|
key={gradeId}
|
|
className="text-lg-center"
|
|
>
|
|
<label
|
|
htmlFor={
|
|
"candidateGrade" + candidateId + "-" + gradeValue
|
|
}
|
|
className="check"
|
|
>
|
|
<small
|
|
className="nowrap d-lg-none ml-2 bold badge"
|
|
style={
|
|
judgments.find((judgment) => {
|
|
return (
|
|
JSON.stringify(judgment) ===
|
|
JSON.stringify({
|
|
id: candidate.id,
|
|
value: gradeValue,
|
|
})
|
|
);
|
|
})
|
|
? { backgroundColor: grade.color, color: "#fff" }
|
|
: {
|
|
backgroundColor: "transparent",
|
|
color: "#000",
|
|
}
|
|
}
|
|
>
|
|
{grade.label}
|
|
</small>
|
|
<input
|
|
type="radio"
|
|
name={"candidate" + candidateId}
|
|
id={"candidateGrade" + candidateId + "-" + gradeValue}
|
|
data-index={candidateId}
|
|
data-id={candidate.id}
|
|
value={grade.value}
|
|
onClick={handleGradeClick}
|
|
defaultChecked={judgments.find((element) => {
|
|
return (
|
|
JSON.stringify(element) ===
|
|
JSON.stringify({
|
|
id: candidate.id,
|
|
value: gradeValue,
|
|
})
|
|
);
|
|
})}
|
|
/>
|
|
<span
|
|
className="checkmark"
|
|
style={
|
|
judgments.find(function (judgment) {
|
|
return (
|
|
JSON.stringify(judgment) ===
|
|
JSON.stringify({
|
|
id: candidate.id,
|
|
value: gradeValue,
|
|
})
|
|
);
|
|
})
|
|
? { backgroundColor: grade.color, color: "#fff" }
|
|
: {
|
|
backgroundColor: "transparent",
|
|
color: "#000",
|
|
}
|
|
}
|
|
/>
|
|
</label>
|
|
</Col>
|
|
);
|
|
})}
|
|
</Row>
|
|
);
|
|
})}
|
|
|
|
<Row>
|
|
<Col className="text-center">
|
|
{judgments.length !== candidates.length ? (
|
|
<Button
|
|
type="button"
|
|
onClick={handleSubmitWithoutAllRate}
|
|
className="btn btn-dark "
|
|
>
|
|
<FontAwesomeIcon icon={faCheck} className="mr-2" />
|
|
{t("Submit my vote")}
|
|
</Button>
|
|
) : (
|
|
<Button type="submit" className="btn btn-success ">
|
|
<FontAwesomeIcon icon={faCheck} className="mr-2" />
|
|
{t("Submit my vote")}
|
|
</Button>
|
|
)}
|
|
</Col>
|
|
</Row>
|
|
</form>
|
|
</Container>
|
|
);
|
|
};
|
|
export default VoteBallot;
|