Skip to content

Commit

Permalink
Merge pull request #421 from mikefranze/sandbox-updates
Browse files Browse the repository at this point in the history
Sandbox updates
  • Loading branch information
mikefranze authored Dec 17, 2023
2 parents 7571d4a + 0e547c1 commit 3bd2d39
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 83 deletions.
4 changes: 3 additions & 1 deletion backend/src/Controllers/sandboxController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ const getSandboxResults = async (req: Request, res: Response, next: NextFunction

res.json(
{
results: results
results: results,
nWinners: num_winners,
candidates: candidateNames,
}
);
}
Expand Down
164 changes: 83 additions & 81 deletions frontend/src/components/Sandbox.tsx
Original file line number Diff line number Diff line change
@@ -1,108 +1,109 @@
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import Results from './Election/Results/Results';
import { Grid } from "@mui/material";
import { FormHelperText, Grid, Typography } from "@mui/material";
import TextField from "@mui/material/TextField";
import FormControl from "@mui/material/FormControl";
import Select from "@mui/material/Select";
import MenuItem from "@mui/material/MenuItem";
import Button from "@mui/material/Button";
import { Box, InputLabel } from "@mui/material";
import { useGetSandboxResults } from '../hooks/useAPI';
import { VotingMethod } from '@domain_model/Race';

const Sandbox = () => {

const { data, error, isPending, makeRequest } = useGetSandboxResults()

const [candidates, setCandidates] = useState('A,B,C,D,E')
const [cvr, setCvr] = useState('10:2,1,3,4,5\n10:5,4,3,1,2\n3,2,5,4,1')
const [nWinners, setNWinners] = useState(1)
const [votingMethod, setVotingMethod] = useState('STAR')
const [isPending, setIsPending] = useState(true)
const [error, setError] = useState(null)
const [data, setData] = useState(null)

const getCvr = () => {

const cvrRows = cvr.split("\n")
const cvrSplit = cvrRows.map((row) => (row.split(',').map((score) => parseInt(score))))
console.log(cvrSplit)
}
const [votingMethod, setVotingMethod] = useState<VotingMethod>('STAR')
const [errorText, setErrorText] = useState('')

const getResults = async () => {
const cvrRows = cvr.split("\n")
const cvrSplit = [];
const parsedCandidates = candidates.split(",").filter(d => (d !== ' ' && d !== ''))
const nCandidates = parsedCandidates.length

if (nCandidates < nWinners) {
setErrorText('Cannot have more winners than candidates')
return
}
let valid = true
cvrRows.forEach((row) => {
const data = row.split(':')
if (data.length == 2) {
const nBallots = parseInt(data[0]);
const vote = data[1].split(/[\s,]+/).map((score) => parseInt(score))
const vote = data[1].split(/[\s,]+/).filter(d => d !== ' ').map((score) => parseInt(score)).filter(d => !isNaN(d))
console.log(vote)
if (vote.length !== nCandidates) {
setErrorText('Each ballot must have the same length as the number of candidates')
valid = false
console.log('Ping1')
}
cvrSplit.push(...Array(nBallots).fill(vote))
} else {
const vote = data[0].split(/[\s,]+/).map((score) => parseInt(score))
const vote = data[0].split(/[\s,]+/).filter(d => d !== ' ').map((score) => parseInt(score)).filter(d => !isNaN(d))
console.log(vote)
if (vote.length !== nCandidates) {
setErrorText('Each ballot must have the same length as the number of candidates')
valid = false
console.log('Ping2')
}
cvrSplit.push(vote)
}
})
const res = await fetch('/API/Sandbox', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
cvr: cvrSplit,
candidates: candidates.split(","),
num_winners: nWinners,
votingMethod: votingMethod,
})
}).then(res => {
if (!res.ok) {
throw Error('Could not fetch data')
}
return res.json();
if (!valid) return
setErrorText('')
await makeRequest({
cvr: cvrSplit,
candidates: candidates.split(","),
num_winners: nWinners,
votingMethod: votingMethod,
})
.then(data => {
setData(data);
setIsPending(false);
setError(null);
})
.catch(err => {
setIsPending(false);
setError(err.message);
})

}

useEffect(() => {
getResults()
}, [nWinners, cvr, votingMethod, candidates])

return (
//Using grid to force results into the center and fill screen on smaller screens.
//Using theme settings and css can probably replace the grids
<Grid container spacing={0}>
<Grid container spacing={0} sx={{ p: 3 }}>
<Grid item xs={12}>
<Box sx={{ minWidth: 120 }}>
<FormControl fullWidth>
<InputLabel variant="standard" htmlFor="uncontrolled-native">
Voting Method
</InputLabel>
<Select
name="Voting Method"
label="Voting Method"
value={votingMethod}
onChange={(e) => setVotingMethod(e.target.value as string)}
>
<MenuItem key="STAR" value="STAR">
STAR
</MenuItem>
<MenuItem key="STAR_PR" value="STAR_PR">
STAR-PR
</MenuItem>
<MenuItem key="RankedRobin" value="RankedRobin">
Ranked Robin
</MenuItem>
<MenuItem key="Approval" value="Approval">
Approval
</MenuItem>
<MenuItem key="Plurality" value="Plurality">
Plurality
</MenuItem>
<MenuItem key="IRV" value="IRV">
Ranked Choice Voting (IRV)
</MenuItem>
</Select>
</FormControl>
</Box>
<FormControl fullWidth>
<InputLabel variant="standard" htmlFor="uncontrolled-native">
Voting Method
</InputLabel>
<Select
name="Voting Method"
label="Voting Method"
value={votingMethod}
onChange={(e) => setVotingMethod(e.target.value as VotingMethod)}
>
<MenuItem key="STAR" value="STAR">
STAR
</MenuItem>
<MenuItem key="STAR_PR" value="STAR_PR">
STAR-PR
</MenuItem>
<MenuItem key="RankedRobin" value="RankedRobin">
Ranked Robin
</MenuItem>
<MenuItem key="Approval" value="Approval">
Approval
</MenuItem>
<MenuItem key="Plurality" value="Plurality">
Plurality
</MenuItem>
<MenuItem key="IRV" value="IRV">
Ranked Choice Voting (IRV)
</MenuItem>
</Select>
</FormControl>
</Grid>
<Grid item xs={12}>

Expand Down Expand Up @@ -145,24 +146,25 @@ const Sandbox = () => {
helperText="Comma seperated scores, one ballot per line, optional 'x:' in front of ballot to indicate x number of that ballot"
onChange={(e) => setCvr(e.target.value)}
/>
<FormHelperText error>
{errorText}
</FormHelperText>
</Grid>
<Button variant='outlined' onClick={() => getResults()} > Get Results </Button>
<Grid item xs={12}>
</Grid>
<Grid item xs={12}>
<Box border={2} sx={{ mt: 5, width: '100%', p: 2 }}>
{isPending && <div> Loading Results... </div>}
{data && (
{/* {isPending && <div> Loading Results... </div>} */}
{data && !error && (
<Results
title=''
raceIndex={0}
result={data.results}
race={{
race_id: '',
title: '',
candidates: candidates.split(',').map((candidate,index) => { return { candidate_id: index.toString(), candidate_name: candidate }}),
voting_method: data.results.voting_method,
num_winners: nWinners,
candidates: data.candidates.map((candidate, index) => { return { candidate_id: index.toString(), candidate_name: candidate } }),
voting_method: data.results.votingMethod,
num_winners: data.nWinners,
}}
/>)}
</Box>
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/hooks/useAPI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Election } from "@domain_model/Election";
import { VoterAuth } from '@domain_model/VoterAuth';
import { ElectionRoll } from "@domain_model/ElectionRoll";
import useFetch from "./useFetch";
import { VotingMethod } from "@domain_model/Race";
import { ElectionResults } from "@domain_model/ITabulators";
import { Ballot } from "@domain_model/Ballot";

Expand Down Expand Up @@ -96,4 +97,11 @@ export const usePostBallot = (election_id: string | undefined) => {
return useFetch<{ ballot: Ballot }, {ballot: Ballot}>(`/API/Election/${election_id}/vote`, 'post')
}


export const useGetSandboxResults = () => {
return useFetch<{
cvr: number[][],
candidates: string[],
num_winners: number,
votingMethod: VotingMethod}, { results: ElectionResults, nWinners: number, candidates: string[]}>
(`/API/Sandbox`, 'post')
}

0 comments on commit 3bd2d39

Please sign in to comment.