fix: result page

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 9e7e2c690f
commit 170fb372c4

@ -86,7 +86,7 @@ export const toCSV = (data, headers, separator, enclosingCharacter) => {
};
const CSVLink = ({filename, data, children}) => {
const CSVLink = ({filename, data, children, ...rest}) => {
console.log("DATA", data);
const buildURI = ((data, uFEFF, headers, separator, enclosingCharacter) => {
@ -115,6 +115,7 @@ const CSVLink = ({filename, data, children}) => {
<a
download={filename}
target="_blank"
{...rest}
href={href}
>
{children}

@ -68,7 +68,7 @@ const DashedMedian = () => {
const MajorityGrade = ({grade, left}) => {
const spanRef = useRef<HTMLDivElement>();
const [width, setWidth] = useState(0)
const [width, setWidth] = useState(40)
useLayoutEffect(() => {

@ -6,12 +6,12 @@ import '@fortawesome/fontawesome-svg-core/styles.css';
// import nextI18NextConfig from '../next-i18next.config.js'
import { appWithTranslation } from 'next-i18next';
import { AppProvider } from '@services/context';
import {appWithTranslation} from 'next-i18next';
import {AppProvider} from '@services/context';
import Header from '@components/layouts/Header';
import Footer from '@components/layouts/Footer';
function Application({ Component, pageProps }) {
function Application({Component, pageProps}) {
const origin =
typeof window !== 'undefined' && window.location.origin
? window.location.origin
@ -31,7 +31,7 @@ function Application({ Component, pageProps }) {
<main className="d-flex flex-column justify-content-between">
<div className="d-flex flex-grow-1 justify-content-center">
<Header />
<div className="d-flex flex-column w-100 align-items-start">
<div className="d-flex flex-column h-100 w-100 align-items-start">
<Component {...pageProps} />
</div>
</div>

@ -30,7 +30,7 @@ import Logo from '@components/Logo';
import MeritProfile from '@components/MeritProfile';
import {getResults} from '@services/api';
import {GradeResultInterface, ResultInterface, MeritProfileInterface, CandidateResultInterface} from '@services/type';
import {getUrlAdmin} from '@services/routes';
import {getUrlAdmin, RESULTS} from '@services/routes';
import {displayRef} from '@services/utils';
import {getMajorityGrade} from '@services/majorityJudgment';
import avatarBlue from '../../../public/avatarBlue.svg'
@ -145,61 +145,115 @@ const ResultBanner = ({result}) => {
const numVotes = getNumVotes(result)
return (<div className="w-100 bg-white p-5 d-flex justify-content-between align-items-center">
<div className="text-muted">
<div className="d-flex align-items-center">
<Image alt="Calendar" src={calendar} className="me-2" />
<WillClose delay={closedSince} />
</div>
<div className="d-flex align-items-center" >
<Image src={avatarBlue} alt="Avatar" className="me-2" />
<div>{numVotes} {numVotes > 1 ? t('common.participants') : t('common.participant')}</div>
</div>
</div>
const origin = typeof window !== 'undefined' && window.location.origin
? window.location.origin
: 'http://localhost';
// We hide the token!
const url = `${origin}${RESULTS}/${displayRef(result.ref)}`
return (<>
{ // MOBILE
}
<div className="w-100 bg-white p-4 d-flex flex-column d-md-none justify-content-center align-items-start">
<h4 className="text-black">{result.name}</h4>
<h4 className="text-black">{result.name}</h4>
<div className="text-muted w-100 d-flex justify-content-between">
<div className="d-flex align-items-center flex-fill border-end border-end-2">
<Image alt="Calendar" src={calendar} className="me-2" />
<WillClose delay={closedSince} />
</div>
<div className="d-flex align-items-center justify-content-end flex-fill" >
<Image src={avatarBlue} alt="Avatar" className="me-2" />
<div>{numVotes} {numVotes > 1 ? t('common.participants') : t('common.participant')}</div>
</div>
</div>
<div className="text-muted">
<div className="d-flex align-items-center">
<Image alt="Download" src={arrowUpload} className="me-2" />
<div>{t('result.download')}</div>
</div >
{ // DESKTOP
}
<div className="w-100 bg-white p-5 d-md-flex d-none justify-content-between align-items-center">
<div className="text-muted">
<div className="d-flex align-items-center">
<Image alt="Calendar" src={calendar} className="me-2" />
<WillClose delay={closedSince} />
</div>
<div className="d-flex align-items-center" >
<Image src={avatarBlue} alt="Avatar" className="me-2" />
<div>{numVotes} {numVotes > 1 ? t('common.participants') : t('common.participant')}</div>
</div>
</div>
<div className="d-flex align-items-center">
<Image src={arrowLink} alt="Share" className="me-2" />
<div>{t('result.share')}</div>
<h4 className="text-black">{result.name}</h4>
<div className="text-muted">
<Downloader result={result}>
<div role="button" className="d-flex align-items-center">
<Image alt="Download" src={arrowUpload} className="me-2" />
<div className="text-muted">{t('result.download')}</div>
</div>
</Downloader>
<a
href={`https://www.facebook.com/sharer/sharer.php?u=${url}`}
rel="noopener noreferrer"
target="_blank">
<div className="d-flex align-items-center">
<Image src={arrowLink} alt="Share" className="me-2" />
<div className="text-muted">{t('result.share')}</div>
</div>
</a>
</div>
</div>
</div >
</div >
</>
)
}
const BottomButtonsMobile = ({result}) => {
const {t} = useTranslation();
const Downloader = ({result, children, ...rest}) => {
const values = result.grades.map(v => v.value).sort()
// const data = result.candidates.map(c => [c.name]);
const data = result.candidates.map(c => {
const grades = {}
result.grades.forEach(g => grades[g.name] = g.value in c.meritProfile ? c.meritProfile[g.value].toString() : "0")
return {name: c.name, ...grades}
});
console.log(data)
return (
<div className="d-block d-md-none mt-5" role="button">
<CSVLink
filename={`results-${displayRef(result.ref)}.csv`}
data={data}>
<Button className="cursorPointer btn-result btn-validation mb-5 btn btn-secondary">
<CSVLink
filename={`results-${displayRef(result.ref)}.csv`}
{...rest}
data={data}>
{children}
</CSVLink>)
}
const BottomButtonsMobile = ({result}) => {
const {t} = useTranslation();
const origin = typeof window !== 'undefined' && window.location.origin
? window.location.origin
: 'http://localhost';
// We hide the token!
const url = `${origin}${RESULTS}/${displayRef(result.ref)}`
return (
<div className="d-flex flex-column align-items-center d-md-none m-3">
<Downloader result={result}>
<Button className="m-3 d-flex align-items-center justify-content-between" role="button" color="primary" outline={false}>
<Image alt="Download" src={arrowUpload} />
<p>{t('result.download')}</p>
<div className="ms-3" >{t('result.download')}</div>
</Button>
</CSVLink>
<Button className="cursorPointer btn-result btn-validation mb-5 btn btn-secondary">
<Image src={arrowLink} alt="Share" />
<p>{t('result.share')}</p>
</Button>
</Downloader>
<div>
<a
href={`https://www.facebook.com/sharer/sharer.php?u=${url}`}
rel="noopener noreferrer"
target="_blank">
<Button className="m-3 d-flex align-items-center justify-content-between" role="button" color="primary" outline={false}>
<Image src={arrowLink} alt="Share" />
<div className="ms-3" >{t('result.share')}</div>
</Button>
</a>
</div>
</div>
)
}
@ -214,20 +268,39 @@ interface TitleBannerInterface {
const TitleBanner = ({name, electionRef, token}: TitleBannerInterface) => {
const {t} = useTranslation();
return (
<div className="d-none d-md-flex p-3 justify-content-between text-white">
<div className="d-flex">
<Logo title={false} />
<h5>{name}</h5>
<>
{ // MOBILE
}
<div className="d-md-none d-flex p-4 justify-content-between text-white">
<div className="d-flex flex-fill align-items-center pe-5">
<Link href="/"><Logo title={false} /></Link>
<h5 className="m-1 flex-fill text-center">{name}</h5>
</div>
{token ?
<div className="d-flex">
<Link href={getUrlAdmin(electionRef, token)}>
<Button icon={faGear} position="left">{t('result.go-to-admin')}</Button>
</Link>
</div> : null
}
</div>
{token ?
<div className="d-flex">
<Link href={getUrlAdmin(electionRef, token)}>
<Button icon={faGear} position="left">{t('result.go-to-admin')}</Button>
</Link>
</div> : null
{ // DESKTOP
}
<div className="d-none d-md-flex bg-primary p-4 justify-content-between text-white">
<div className="d-flex align-items-center">
<Link href="/"><Logo height={38} title={true} /></Link>
<h5 className="m-1 ms-5">{name}</h5>
</div>
{token ?
<div className="d-flex">
<Link href={getUrlAdmin(electionRef, token)}>
<Button icon={faGear} position="left">{t('result.go-to-admin')}</Button>
</Link>
</div> : null
}
</div>
</div>
</>
)
}
@ -260,10 +333,10 @@ interface CandidateRankedInterface {
const CandidateRanked = ({candidate}: CandidateRankedInterface) => {
const isFirst = candidate.rank == 1;
return <div className="m-3 d-flex flex-column justify-content-end align-items-center candidate_rank fw-bold">
<div className={isFirst ? "text-primary bg-white fs-5 badge" : "text-white bg-secondary fs-6 badge"}>
<div className={isFirst ? "text-primary bg-white fs-4 badge" : "text-white bg-secondary fs-5 badge"}>
{candidate.rank}
</div>
<div className={`text-white my-2 ${isFirst ? "fs-4" : "fs-6"}`}>
<div className={`text-white my-2 ${isFirst ? "fs-4" : "fs-5"}`}>
{candidate.name}
</div>
<ButtonGrade grade={candidate.majorityGrade} />
@ -388,7 +461,7 @@ const ResultPage = ({result, token, err}: ResultPageInterface) => {
result.candidates.filter(c => c.rank < 4).forEach(c => candidateByRank[c.rank] = c)
return (
<Container className="resultContainer resultPage">
<Container className="h-100 resultContainer resultPage d-flex flex-column align-flex-stretch">
<Head>
<title>{result.name}</title>
<link rel="icon" href="/favicon.ico" />
@ -399,23 +472,21 @@ const ResultPage = ({result, token, err}: ResultPageInterface) => {
<Podium candidates={result.candidates} />
<section className="sectionContentResult mb-5">
<Row className="mt-5">
<Col>
<h5 className="text-white">
{t('result.details')}
</h5>
{Object.keys(candidateByRank).sort().map((rank, i) => {
return (
<CandidateCard candidate={candidateByRank[rank]} grades={result.grades} key={i} />
);
})}
</Col>
</Row>
<Container style={{maxWidth: "750px"}} className="mt-5 h-100 d-flex flex-fill flex-column justify-content-between">
<div>
<h5 className="text-white">
{t('result.details')}
</h5>
{Object.keys(candidateByRank).sort().map((rank, i) => {
return (
<CandidateCard candidate={candidateByRank[rank]} grades={result.grades} key={i} />
);
})}
</div>
<BottomButtonsMobile result={result} />
</section>
</Container>
</Container>
</Container >
);
};

@ -5,16 +5,16 @@
"home.writeQuestion": "Write here your question or describe your vote.",
"home.start": "Start a vote",
"home.noAds": "No advertising or ad cookies",
"home.advantage-1-title": "Simple",
"home.advantage-1-name": "Simple",
"home.advantage-1-desc": "Create a vote in less than 1 minute!",
"home.advantage-2-title": "Free",
"home.advantage-2-name": "Free",
"home.advantage-2-desc": "Send invites without any limitations!",
"home.advantage-3-title": "Respecting your privacy",
"home.advantage-3-name": "Respecting your privacy",
"home.advantage-3-desc": "No personal data is recorded",
"home.experience-title": "A democratic and intuitive voting experience",
"home.experience-1-title": "Express your full opinion.",
"home.experience-name": "A democratic and intuitive voting experience",
"home.experience-1-name": "Express your full opinion.",
"home.experience-1-desc": "With majority judgment, each candidate is evaluated on a grid of mentions. Strategic voting has no use anymore.",
"home.experience-2-title": "Get the best possible consensus.",
"home.experience-2-name": "Get the best possible consensus.",
"home.experience-2-desc": "The merit profile provides an accurate picture of the voters' opinions. The winner of the vote is the one with the best majority rating.",
"home.experience-call-to-action": "Find out about the majority judgment",
"home.alt-icon-ballot-box": "icon of a ballot box",

@ -5,16 +5,16 @@
"home.writeQuestion": "Posez la question de votre vote ici.",
"home.start": "C'est parti",
"home.noAds": "Pas de publicités, ni de cookies publicitaires",
"home.advantage-1-title": "Simple",
"home.advantage-1-name": "Simple",
"home.advantage-1-desc": "Créez un vote en moins dune minute.",
"home.advantage-2-title": "Gratuit",
"home.advantage-2-name": "Gratuit",
"home.advantage-2-desc": "Envoyez des invitations par courriel sans limite d'envoi.",
"home.advantage-3-title": "Respect de votre vie privée",
"home.advantage-3-name": "Respect de votre vie privée",
"home.advantage-3-desc": "Aucune donnée personnelle n'est enregistrée",
"home.experience-title": "Une expérience de vote démocratique et intuitive",
"home.experience-1-title": "Exprimez toute votre opinion.",
"home.experience-name": "Une expérience de vote démocratique et intuitive",
"home.experience-1-name": "Exprimez toute votre opinion.",
"home.experience-1-desc": "Au jugement majoritaire, chaque candidat est évalué sur une grille de mention. Vous n'aurez plus besoin de faire un vote stratégique.",
"home.experience-2-title": "Obtenez le meilleur consensus.",
"home.experience-2-name": "Obtenez le meilleur consensus.",
"home.experience-2-desc": "Le profil des mérites dresse un panorama précis de lopinion des électeurs. Le gagnant du vote est celui qui est la meilleure mention majoritaire.",
"home.experience-call-to-action": "Découvrez le jugement majoritaire",
"home.alt-icon-ballot-box": "icone d'urne",

Loading…
Cancel
Save