fix: refactor ballot

pull/89/head
Pierre-Louis Guhur 1 year ago
parent f3873cbb34
commit f29d287294

@ -0,0 +1,77 @@
import {MouseEvent} from 'react'
import {useRouter} from 'next/router';
import {useTranslation} from 'next-i18next';
import Button from '@components/Button';
import {Col, Row, Container} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCalendarDays, faCheck} from '@fortawesome/free-solid-svg-icons';
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import CandidateCard from '@components/ballot/CandidateCard'
import TitleBar from '@components/ballot/TitleBar'
import GradeInput from '@components/ballot/GradeInput'
const BallotDesktop = () => {
const {t} = useTranslation();
const [ballot, dispatch] = useBallot();
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
const router = useRouter();
const handleSubmit = (event: MouseEvent) => {
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, election.id.toString(), token, () => {
router.push(`/confirm/${ballot.election.id}`);
// });
};
return (
<div className="w-100 h-100 display-none display-lg-block">
<TitleBar election={ballot.election} />
<div className="w-100 h-100 d-flex flex-column justify-content-center align-items-center">
<h1 className="mb-5">{ballot.election.name}</h1>
{ballot.election.candidates.map((candidate, candidateId) => {
return (
<div key={candidateId} className="bg-white justify-content-between d-flex my-4 py-2 px-3">
<CandidateCard candidate={candidate} />
<div className="d-flex">
{ballot.election.grades.map((_, gradeId) => {
console.assert(gradeId < numGrades);
return (
<GradeInput key={gradeId} gradeId={gradeId} candidateId={candidateId} />
);
})}
</div>
</div>
);
})}
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
onClick={handleSubmit}
disabled={disabled}
icon={faCheck}
position="left"
>
{t('vote.submit')}
</Button>
</Container>
</div>
</div>
)
}
export default BallotDesktop

@ -0,0 +1,65 @@
import {MouseEvent} from 'react'
import {useRouter} from 'next/router';
import {useTranslation} from 'next-i18next';
import Button from '@components/Button';
import {Col, Row, Container} from 'reactstrap';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCalendarDays, faCheck} from '@fortawesome/free-solid-svg-icons';
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import CandidateCard from '@components/ballot/CandidateCard'
import TitleBar from '@components/ballot/TitleBar'
import GradeInput from '@components/ballot/GradeInput'
const BallotMobile = () => {
const {t} = useTranslation();
const [ballot, dispatch] = useBallot();
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
const router = useRouter();
const handleSubmit = (event: MouseEvent) => {
event.preventDefault();
router.push(`/confirm/${ballot.election.id}`);
};
return (
<div className="w-100 h-100 display-block display-lg-none">
<div className="w-100 h-100 d-flex flex-column justify-content-center align-items-center">
<h1 className="mb-5">{ballot.election.name}</h1>
{ballot.election.candidates.map((candidate, candidateId) => {
return (
<div key={candidateId} className="bg-white justify-content-between d-flex my-4 py-2 px-3">
<CandidateCard candidate={candidate} />
<div className="d-flex">
{ballot.election.grades.map((_, gradeId) => {
console.assert(gradeId < numGrades);
return (
<GradeInput key={gradeId} gradeId={gradeId} candidateId={candidateId} />
);
})}
</div>
</div>
);
})}
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
onClick={handleSubmit}
disabled={disabled}
icon={faCheck}
position="left"
>
{t('vote.submit')}
</Button>
</Container>
</div>
</div>
)
}
export default BallotMobile

@ -0,0 +1,28 @@
import Image from 'next/image';
import {useTranslation} from 'next-i18next';
import defaultAvatar from '../../public/avatarBlue.svg';
import {CandidatePayload} from '@services/api';
interface CandidateCardInterface {
candidate: CandidatePayload;
}
const CandidateCard = ({candidate}: CandidateCardInterface) => {
const {t} = useTranslation();
return (<div className="d-flex align-items-center">
<Image
src={defaultAvatar}
width={32}
height={32}
className="bg-light"
alt={t('common.thumbnail')}
/>
<div className="d-flex lh-sm flex-column justify-content-center ps-3">
<span className="text-black fs-5 m-0 ">{candidate.name}</span>
<br />
<span className="text-muted fs-6 m-0 fw-normal">{t("vote.more-details")}</span>
</div>
</div>)
}
export default CandidateCard;

@ -0,0 +1,34 @@
import {useState, useCallback, useEffect, MouseEvent} from 'react';
import {useBallot, BallotTypes} from '@services/BallotContext';
import {getGradeColor} from '@services/grades';
interface GradeInputInterface {
gradeId: number;
candidateId: number;
}
const GradeInput = ({gradeId, candidateId}: GradeInputInterface) => {
const [ballot, dispatch] = useBallot();
if (!ballot) {throw Error("Ensure the election is loaded")}
const grade = ballot.election.grades[gradeId];
const numGrades = ballot.election.grades.length;
const handleClick = (event: MouseEvent<HTMLInputElement>) => {
dispatch({type: BallotTypes.VOTE, candidateId: candidateId, gradeId: gradeId})
};
const active = ballot.votes.some(b => b.gradeId === gradeId && b.candidateId === candidateId)
const color = active ? getGradeColor(gradeId, numGrades) : '#C3BFD8';
return (
<div
className={`text-lg-center my-1 rounded-1 px-2 py-1 fs-5 text-white ms-3`}
onClick={handleClick}
style={{backgroundColor: color, boxShadow: active ? `0px 2px 0px ${color}` : "0px 2px 0px #8F88BA"}}
>
{grade.name}
</div >
)
}
export default GradeInput

@ -0,0 +1,30 @@
import {useRouter} from 'next/router';
import Button from '@components/Button';
import {useTranslation} from 'next-i18next';
import {getElection, castBallot, apiErrors, ElectionPayload, CandidatePayload, GradePayload} from '@services/api';
import {getLocaleShort} from '@services/utils';
import {faCalendarDays} from '@fortawesome/free-solid-svg-icons';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
interface TitleBarInterface {
election: ElectionPayload;
}
const TitleBar = ({election}: TitleBarInterface) => {
const {t} = useTranslation();
const router = useRouter();
const locale = getLocaleShort(router);
return (
<div className="w-100 bg-light p-2 text-black justify-content-center d-flex ">
<div className="me-2">
<FontAwesomeIcon icon={faCalendarDays} />
</div>
<div>
{` ${t("vote.open-until")} ${new Date(election.date_end).toLocaleDateString(locale, {dateStyle: "long"})}`}
</div>
</div>
)
};
export default TitleBar

@ -1,22 +1,13 @@
import {useState, useCallback, useEffect, MouseEvent} from 'react';
import Image from 'next/image';
import {useEffect} from 'react';
import Head from 'next/head';
import {useRouter} from 'next/router';
import {serverSideTranslations} from 'next-i18next/serverSideTranslations';
import {useTranslation} from 'next-i18next';
import {Col, Row, Container} from 'reactstrap';
// import {toast, ToastContainer} from "react-toastify";
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import {faCalendarDays, faCheck} from '@fortawesome/free-solid-svg-icons';
import {getElection, castBallot, apiErrors, ElectionPayload, CandidatePayload, GradePayload} from '@services/api';
import Button from '@components/Button';
import useEmblaCarousel from 'embla-carousel-react';
import {DotButton} from '@components/admin/EmblaCarouselButtons';
import {getGradeColor} from '@services/grades';
import BallotDesktop from '@components/ballot/BallotDesktop'
import BallotMobile from '@components/ballot/BallotMobile'
import {useBallot, BallotTypes, BallotProvider} from '@services/BallotContext';
import {getLocaleShort} from '@services/utils';
import {ENDED_VOTE} from '@services/routes';
import defaultAvatar from '../../../public/avatarBlue.svg';
const shuffle = (array) => array.sort(() => Math.random() - 0.5);
@ -64,115 +55,8 @@ export async function getServerSideProps({query: {pid, tid}, locale}) {
};
}
interface TitleBarInterface {
election: ElectionPayload;
}
const TitleBar = ({election}: TitleBarInterface) => {
const {t} = useTranslation();
const router = useRouter();
const locale = getLocaleShort(router);
return (
<div className="w-100 bg-light p-2 text-black justify-content-center d-flex ">
<div className="me-2">
<FontAwesomeIcon icon={faCalendarDays} />
</div>
<div>
{` ${t("vote.open-until")} ${new Date(election.date_end).toLocaleDateString(locale, {dateStyle: "long"})}`}
</div>
</div>
)
};
interface CandidateCardInterface {
candidate: CandidatePayload;
}
const CandidateCard = ({candidate}: CandidateCardInterface) => {
const {t} = useTranslation();
return (<div className="d-flex align-items-center">
<Image
src={defaultAvatar}
width={32}
height={32}
className="bg-light"
alt={t('common.thumbnail')}
/>
<div className="d-flex lh-sm flex-column justify-content-center ps-3">
<span className="text-black fs-5 m-0 ">{candidate.name}</span>
<br />
<span className="text-muted fs-6 m-0 fw-normal">{t("vote.more-details")}</span>
</div>
</div>)
}
interface GradeInputInterface {
gradeId: number;
candidateId: number;
}
const GradeInput = ({gradeId, candidateId}: GradeInputInterface) => {
const [ballot, dispatch] = useBallot();
if (!ballot) {throw Error("Ensure the election is loaded")}
const grade = ballot.election.grades[gradeId];
const numGrades = ballot.election.grades.length;
const color = getGradeColor(gradeId, numGrades);
const handleClick = (event: MouseEvent<HTMLInputElement>) => {
dispatch({type: BallotTypes.VOTE, candidateId: candidateId, gradeId: gradeId})
};
const active = ballot.votes.some(b => b.gradeId === gradeId && b.candidateId === candidateId)
return (
<div
className="text-lg-center my-1 voteCheck"
onClick={handleClick}
>
<small
className="nowrap d-lg-none bold badge"
style={
active
? {
backgroundColor: color,
color: '#fff',
}
: {
backgroundColor: 'transparent',
color: '#000',
}
}
>
{grade.name}
</small>
<span
className="checkmark candidateGrade fs-6"
style={
active
? {
backgroundColor: color,
color: '#fff',
}
: {
backgroundColor: '#C3BFD8',
color: '#000',
}
}
>
{ /*<small
className="nowrap bold badge"
style={{
backgroundColor: 'transparent',
color: '#fff',
}}
>*/}
{grade.name}
{ /*</small>*/}
</span>
</div>
)
}
interface VoteInterface {
election: ElectionPayload;
@ -197,7 +81,6 @@ const VoteBallot = ({election, token}: VoteInterface) => {
}
const numGrades = ballot.election.grades.length;
const disabled = ballot.votes.length !== ballot.election.candidates.length;
const colSizeCandidateLg = 4;
const colSizeCandidateMd = 6;
const colSizeCandidateXs = 12;
@ -254,7 +137,7 @@ const VoteBallot = ({election, token}: VoteInterface) => {
// }, [embla, setScrollSnaps, onSelect]);
return (
<>
<form className="w-100 h-100" onSubmit={handleSubmit} autoComplete="off">
<Head>
<title>{election.name}</title>
@ -266,41 +149,9 @@ const VoteBallot = ({election, token}: VoteInterface) => {
/>
</Head>
<TitleBar election={ballot.election} />
<div className="w-100 h-100 d-flex flex-column justify-content-center align-items-center">
<h1 className="mb-5">{election.name}</h1>
<form onSubmit={handleSubmit} autoComplete="off">
{election.candidates.map((candidate, candidateId) => {
return (
<div key={candidateId} className="bg-white justify-content-between d-flex my-4 py-2 px-3">
<CandidateCard candidate={candidate} />
<div className="cardVoteGrades">
{election.grades.map((_, gradeId) => {
console.assert(gradeId < numGrades);
return (
<GradeInput key={gradeId} gradeId={gradeId} candidateId={candidateId} />
);
})}
</div>
</div>
);
})}
</form >
</div >
<Container className="my-5 d-md-flex d-grid justify-content-md-center">
<Button
outline={true}
color="secondary"
className="bg-blue"
onClick={handleSubmit}
disabled={disabled}
icon={faCheck}
position="left"
>
{t('vote.submit')}
</Button>
</Container>
</>
<BallotDesktop />
<BallotMobile />
</form >
);
};

@ -76,3 +76,7 @@
top: 20px;
right: 20px;
}
.btn_shadow{
box-shadow: 0px 2px 0px #8F88BA;
}

@ -46,7 +46,7 @@ $font-sizes: (
2: $h2-font-size,
3: $h3-font-size,
4: $h4-font-size,
5: $font-size-base,
5: $font-size-base * 0.9,
6: $font-size-base * 0.75
);

Loading…
Cancel
Save