wip: params field

pull/89/head
Pierre-Louis Guhur 1 year ago
parent 2e19bdb964
commit bf22ec4c67

@ -1,8 +1,8 @@
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {Row, Col, Button} from "reactstrap";
const ButtonWithIcon = ({icon, children, ...props}) => {
if (icon) {
const ButtonWithIcon = ({icon, children, position, ...props}) => {
if (icon && position === 'left') {
return <Button {...props}>
<Row className='gx-2 align-items-end'>
<Col className='col-auto'>
@ -14,10 +14,25 @@ const ButtonWithIcon = ({icon, children, ...props}) => {
</Row>
</Button>
}
else if (icon && position === 'right') {
return <Button {...props}>
<Row className='gx-2 align-items-end'>
<Col className='col-auto'>
{children}
</Col>
<Col className='col-auto'>
<FontAwesomeIcon icon={icon} />
</Col>
</Row>
</Button>
}
else {
return (<Button {...props}>{children}</Button>)
}
}
ButtonWithIcon.defaultProps = {
position: 'left'
}
export default ButtonWithIcon

@ -2,38 +2,59 @@
* This component displays a bar releaving the current step
*/
import {useTranslation} from "next-i18next";
const {Row, Col} = require("reactstrap")
import {faArrowLeft, faCheck} from "@fortawesome/free-solid-svg-icons";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
const {Row, Col, Container} = require("reactstrap")
const Step = ({name, position, active}) => {
const Step = ({name, position, active, check}) => {
const {t} = useTranslation();
const disabled = !active && !check
return <Col
className="col-auto">
<Row className={`align-items-center creation-step ${active ? 'active' : ''}`}>
<Row className={`align-items-center creation-step ${active ? 'active' : ''} ${disabled ? 'disabled' : ''}`}>
<Col className='col-auto badge align-items-center justify-content-center d-flex'>
<div>{position}</div>
<div>{check ? <FontAwesomeIcon icon={faCheck} /> : position}</div>
</Col>
<Col className='col-auto name'>
{t(`admin.step-${name}`)}
</Col>
</Row>
</Row >
</Col >
}
const CreationSteps = ({step, ...props}) => {
const steps = ['candidate', 'params', 'confirm'];
export const creationSteps = ['candidate', 'params', 'confirm'];
export const ProgressSteps = ({step, className, ...props}) => {
const {t} = useTranslation();
if (!steps.includes(step)) {
if (!creationSteps.includes(step)) {
throw Error(`Unknown step {step}`);
}
const stepId = creationSteps.indexOf(step);
return <div {...props}>
<Row className='justify-content-between creation-steps'>
{steps.map((name, i) => <Step name={name} active={step === name} key={i} position={i + 1} />
)}
</Row >
</div >
return <Row className={`w-100 m-5 d-flex ${className}`} {...props}>
<Col className='col-3'>
{step === 'candidate' ? null : (
<Row className='gx-2 align-items-end'>
<Col className='col-auto'>
<FontAwesomeIcon icon={faArrowLeft} />
</Col>
<Col className='col-auto'>
{t('admin.candidates-back-step')}
</Col>
</Row>
)
}
</Col>
<Col className='col-6'>
<Row className='w-100 gx-5 justify-content-center'>
{creationSteps.map((name, i) => <Step name={name} active={step === name} check={i < stepId} key={i} position={i + 1} />
)}
</Row >
</Col>
<Col className='col-3'>
</Col>
</Row >
}
export default CreationSteps;

@ -46,7 +46,7 @@ const CandidateField = ({position, className, ...inputProps}) => {
<Image src={image} width={24} height={24} className={image == defaultAvatar ? "default-avatar" : ""} alt={t('common.thumbnail')} />
</Col>
<Col className='col-auto fw-bold'>
{t("admin.add-candidate")}
{candidate.name ? candidate.name : t("admin.add-candidate")}
</Col>
</Row>
</Col>

@ -8,7 +8,6 @@ import {
ModalBody,
Form
} from "reactstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faPlus,
faArrowLeft,
@ -29,7 +28,6 @@ const CandidateModal = ({isOpen, position, toggle}) => {
const dispatch = useElectionDispatch();
const candidate = election.candidates[position];
const [state, setState] = useState(candidate);
console.log('state', state);
const image = state.image && state.image != "" ? state.image : defaultAvatar;
const handleFile = async (event) => {

@ -1,10 +1,12 @@
import {useState, useEffect, createRef} from 'react'
import {useTranslation} from "next-i18next";
import CandidateField from './CandidateField'
import Alert from '@components/Alert'
import {Container} from 'reactstrap';
import {faArrowRight} from "@fortawesome/free-solid-svg-icons";
import {MAX_NUM_CANDIDATES} from '@services/constants';
import {Container, Button} from 'reactstrap';
import Alert from '@components/Alert'
import Button from '@components/Button'
import {useElection, useElectionDispatch} from './ElectionContext';
import CandidateField from './CandidateField'
const CandidatesField = ({onSubmit}) => {
@ -42,8 +44,8 @@ const CandidatesField = ({onSubmit}) => {
})}
</div>
<div className="mb-5 d-flex justify-content-center">
<Button outline={true} color="secondary" onClick={onSubmit} disabled={disabled}>
{t('Valider les candidats')}
<Button outline={true} color="secondary" onClick={onSubmit} disabled={disabled} icon={faArrowRight} position="right">
{t('admin.candidates-submit')}
</Button>
</div>
</Container >

@ -65,8 +65,7 @@ function electionReducer(election, action) {
*/
switch (action.type) {
case 'set': {
election[action.field] = action.value;
return election;
return {...election, [action.field]: action.value};
}
case 'commit': {
throw Error('Not implemented yet')
@ -119,7 +118,7 @@ const initialElection = {
grades: DEFAULT_NUM_GRADES,
isTimeLimited: false,
isRandomOrder: false,
restrictResult: false,
restrictResult: true,
restrictVote: false,
startVote: null,
endVote: null,

@ -0,0 +1,121 @@
import {useState, useEffect} from 'react'
import {useTranslation} from "next-i18next";
import {Container, Row, Col} from 'reactstrap';
import {faArrowRight} from "@fortawesome/free-solid-svg-icons";
import {MAX_NUM_CANDIDATES} from '@services/constants';
import Button from '@components/Button'
import {useElection, useElectionDispatch} from './ElectionContext';
const Switch = ({toggle, state}) => {
return (<div onClick={toggle} className="form-check form-switch">
<input className="form-check-input" type="checkbox" role="switch" checked={state} />
</div>)
}
const AccessResults = () => {
const {t} = useTranslation();
const election = useElection();
const dispatch = useElectionDispatch();
const toggle = () => {
dispatch({
'type': 'set',
'field': 'restrictResult',
'value': !election.restrictResult
})
}
return (<Container className='bg-white container-fluid p-4'>
<Row>
<Col className='col-auto me-auto'>
<h4 className='text-dark'>{t('admin.access-results')}</h4>
<p className='text-dark-50'>{t('admin.access-results-desc')}</p>
</Col>
<Col className='col-auto d-flex align-items-center'>
<Switch toggle={toggle} state={election.restrictResult} />
</Col>
</Row>
</Container>)
}
const LimitDate = () => {
const {t} = useTranslation();
const election = useElection();
const dispatch = useElectionDispatch();
const toggle = () => {
dispatch({
'type': 'set',
'field': 'restrictResult',
'value': !election.restrictResult
})
}
const desc = t('admin.limit-diration-desc');
return (<Container className='bg-white container-fluid p-4'>
<Row>
<Col className='col-auto me-auto'>
<h4 className='text-dark'>{t('admin.limit-duration')}</h4>
{desc === "" ? null :
<p className='text-dark-50'>{desc}</p>
}
</Col>
<Col className='col-auto d-flex align-items-center'>
<Switch toggle={toggle} state={election.restrictResult} />
</Col>
</Row>
</Container>)
}
const Grades = () => {
}
const Private = () => {
}
const ParamsField = ({onSubmit}) => {
const {t} = useTranslation();
const election = useElection();
const dispatch = useElectionDispatch();
const candidates = election.candidates;
const [error, setError] = useState(null)
const disabled = candidates.filter(c => c.name !== "").length < 2;
// What to do when we change the candidates
useEffect(() => {
// Initialize the list with at least two candidates
if (candidates.length < 2) {
dispatch({'type': 'candidate-push', 'value': "default"})
}
if (candidates.length > MAX_NUM_CANDIDATES) {
setError('error.too-many-candidates')
}
}, [candidates])
return (
<Container className="params flex-grow-1 my-5 mt-5 flex-column d-flex justify-content-between">
<div className="d-flex flex-column">
<AccessResults />
<LimitDate />
<Grades />
<Private />
</div>
<div className="mb-5 d-flex justify-content-center">
<Button outline={true} color="secondary" onClick={onSubmit} disabled={disabled} icon={faArrowRight} position="right">
{t('admin.params-submit')}
</Button>
</div>
</Container >
);
}
export default ParamsField

@ -69,14 +69,6 @@ module.exports = {
}
);
if (count > 0) {
/* console.log(
`i18next-scanner: count=${chalk.cyan(count)}, file=${chalk.yellow(
JSON.stringify(file.relative)
)}`
);*/
}
done();
}
};

@ -27,7 +27,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 align-items-start">
<div className="d-flex flex-column w-100 align-items-start">
<Component {...pageProps} />
</div>
</div>

@ -2,30 +2,11 @@ import {useReducer, useState, useEffect} from "react";
import Head from "next/head";
import {useTranslation} from "next-i18next";
import {serverSideTranslations} from "next-i18next/serverSideTranslations";
import {
Container,
Row,
Col,
Input,
Label,
Button,
Modal, ModalHeader, ModalBody
} from "reactstrap";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {
faCheck,
faArrowLeft,
faExclamationCircle,
faChevronRight
} from "@fortawesome/free-solid-svg-icons";
import {createElection} from "@services/api";
import {translateGrades} from "@services/grades";
import {extractTime, extractDay} from "@services/date";
import Loader from "@components/wait";
import CandidatesField from "@components/admin/CandidatesField";
import ParamsField from "@components/admin/ParamsField";
import ConfirmModal from "@components/admin/ConfirmModal";
import {ElectionProvider, useElection} from '@components/admin/ElectionContext';
import CreationSteps from "@components/CreationSteps";
import {ProgressSteps, creationSteps} from "@components/CreationSteps";
// import DatePicker from "react-datepicker";
//
@ -47,23 +28,38 @@ const CreateElectionForm = (props) => {
// load the election
const election = useElection();
const handleSubmit = () => {
if (stepId < 2) {
setStepId(i => i + 1);
}
else { // TODO
}
}
// at which creation step are we?
const [step, setStep] = useState('candidate');
const [stepId, setStepId] = useState(0);
const step = creationSteps[stepId];
let Form;
let Step;
if (step == 'candidate') {
Form = CandidatesField;
Step = <CandidatesField onSubmit={handleSubmit} />
} else if (step == 'params') {
Step = <ParamsField onSubmit={handleSubmit} />;
} else if (step == 'confirm') {
Form = ConfirmModal;
Step = <>
<ParamsField />
<ConfirmModal onSubmit={handleSubmit} />
</>;
} else {
throw Error(`Unknown step ${step}`);
}
return (
<ElectionProvider>
<CreationSteps step={step} className='m-5 justify-content-center d-flex' />
<ProgressSteps step={step} />
{Step}
<Form />
</ElectionProvider>
);

@ -87,11 +87,9 @@ const Result = ({candidates, numGrades, title, pid, err, finish}) => {
typeof window !== "undefined" && window.location.origin
? window.location.origin
: "http://localhost";
console.log("origin", origin);
const urlVote = new URL(`/vote/${pid}`, origin);
const collapsee = (candidates[0].title);
console.log(collapsee);
const [collapseProfiles, setCollapseProfiles] = useState(false);
const [collapseGraphics, setCollapseGraphics] = useState(false);

@ -54,6 +54,13 @@
"admin.add-candidate-desc": "Add a picture, a name, and a description of the candidate.",
"admin.candidate-name-placeholder": "Add the name or the title of the candidate.",
"admin.candidate-desc-placeholder": "Add the description of the candidate.",
"admin.candidates-submit": "Validate the candidates",
"admin.candidates-back-step": "Back to candidates",
"admin.params-submit": "Validate the parameters",
"admin.access-results": "Immediate access to the results",
"admin.access-results-desc": "No one can access to the results as long as the vote is not closed.",
"admin.limit-duration": "Limit the length of the vote",
"admin.limit-duration-desc": "",
"admin.photo": "Picture",
"admin.optional": "optional",
"admin.photo-import": "Import a picture",

@ -54,6 +54,13 @@
"admin.candidate-confirm-del": "Vous souhaitez supprimer un candidat",
"admin.candidate-confirm-back": "Non, je le garde",
"admin.candidate-confirm-ok": "Supprimer",
"admin.candidates-submit": "Valider les candidats",
"admin.candidates-back-step": "Retour aux candidats",
"admin.params-submit": "Valider les paramètres",
"admin.access-results": "Accès immédiat aux résultats",
"admin.access-results-desc": "Personne ne pourra accéder aux résultats tant que le vote n'est pas clôturé.",
"admin.limit-duration": "Limiter la durée du vote",
"admin.limit-duration-desc": "",
"admin.photo": "Photo",
"admin.optional": "facultatif",
"admin.photo-import": "Importer une photo",

@ -1,5 +1,8 @@
.creation-step.disabled {
opacity: 0.5;
}
.creation-step {
color: rgb(255,255,255,0.64);
color: rgb(255,255,255);
.name {
font-weight: 500;
font-size: 16px;
@ -8,11 +11,10 @@
.badge {
width: 24px;
height: 24px;
background-color: rgb(0,0,0, 0.2);
}
background-color: black; // , 0.2);
}
}
.creation-step.active {
.badge {
background-color: white;
@ -23,7 +25,7 @@
}
}
.candidate, .creation-steps {
.candidate {
max-width: 500px;
}
@ -35,10 +37,10 @@
margin-right: 10px;
}
.creation-steps * > input[type="file"] {
.candidate * > input[type="file"] {
display: none;
}
.creation-steps * > .inputfile {
.candidate * > .inputfile {
background: transparent;
color: #0a004c;
border: 2px solid #0a004c;
@ -73,3 +75,7 @@
opacity: 0.4;
}
.params {
max-width: 1070px;
}

@ -388,3 +388,6 @@ ol.result > li {
display: none;
}
.noshadow{ box-shadow: unset!important;}

@ -0,0 +1,12 @@
.form-switch .form-check-input {
height: 24px;
width: 40px;
background-color: #C3BFD8;
border: unset;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3.5' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");
}
.form-switch .form-check-input:checked {
background-color: #0d6efd;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3.5' fill='%23fff'/%3e%3c/svg%3e");
}

@ -35,3 +35,4 @@ $theme-colors: (
// @import "_newVote.scss";
@import "_resultVote.scss";
@import "_admin.scss";
@import "_switch.scss";

Loading…
Cancel
Save