[CREATE] fix timestamp bug

pull/73/head
Pierre-Louis Guhur 4 years ago
parent 3b8ca70ca8
commit 5c6512b8ea

@ -51,6 +51,7 @@
"postcss-normalize": "7.0.1", "postcss-normalize": "7.0.1",
"postcss-preset-env": "6.6.0", "postcss-preset-env": "6.6.0",
"postcss-safe-parser": "4.0.1", "postcss-safe-parser": "4.0.1",
"query-string": "^6.12.0",
"querystringify": "^2.0.0", "querystringify": "^2.0.0",
"react": "^16.8.6", "react": "^16.8.6",
"react-app-polyfill": "^1.0.1", "react-app-polyfill": "^1.0.1",

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, {Component} from 'react';
import { Redirect } from "react-router-dom"; import {Redirect, withRouter} from 'react-router-dom';
import { import {
Collapse, Collapse,
Container, Container,
@ -11,41 +11,58 @@ import {
InputGroupAddon, InputGroupAddon,
Button, Button,
Card, Card,
CardBody CardBody,
} from "reactstrap"; } from 'reactstrap';
import { toast, ToastContainer } from "react-toastify"; import {toast, ToastContainer} from 'react-toastify';
import "react-toastify/dist/ReactToastify.css"; import 'react-toastify/dist/ReactToastify.css';
import { resolve } from "url"; import {resolve} from 'url';
import HelpButton from "../form/HelpButton"; import queryString from 'query-string';
import HelpButton from '../form/HelpButton';
import { import {
arrayMove, arrayMove,
sortableContainer, sortableContainer,
sortableElement, sortableElement,
sortableHandle sortableHandle,
} from "react-sortable-hoc"; } from 'react-sortable-hoc';
import ButtonWithConfirm from "../form/ButtonWithConfirm"; import ButtonWithConfirm from '../form/ButtonWithConfirm';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import {FontAwesomeIcon} from '@fortawesome/react-fontawesome';
import { import {
faPlus, faPlus,
faTrashAlt, faTrashAlt,
faCheck, faCheck,
faCogs faCogs,
} from "@fortawesome/free-solid-svg-icons"; } from '@fortawesome/free-solid-svg-icons';
import { grades } from "../../Util"; import {grades} from '../../Util';
import { ReactMultiEmail, isEmail } from "react-multi-email"; import {ReactMultiEmail, isEmail} from 'react-multi-email';
import "react-multi-email/style.css"; import 'react-multi-email/style.css';
import { AppContext } from "../../AppContext"; import {AppContext} from '../../AppContext';
const DragHandle = sortableHandle(({ children }) => ( // Convert a Date object into YYYY-MM-DD
const dateToISO = date => date.toISOString().substring(0, 10);
// Retrieve the current hour, minute, sec, ms, time into a timestamp
const hours = date => date.getHours() * 3600 * 1000;
const minutes = date => date.getMinutes() * 60 * 1000;
const seconds = date => date.getSeconds() * 1000;
const ms = date => date.getMilliseconds() * 1000;
const time = date => hours(date) + minutes(date) + seconds(date) + ms(date);
// Retrieve the time part from a timestamp and remove the day. Return a int.
const timeMinusDate = date => time(date);
// Retrieve the day and remove the time. Return a Date
const dateMinusTime = date => new Date(date.getTime() - time(date));
const DragHandle = sortableHandle(({children}) => (
<span className="input-group-text indexNumber">{children}</span> <span className="input-group-text indexNumber">{children}</span>
)); ));
const SortableCandidate = sortableElement(({ candidate, sortIndex, form }) => ( const SortableCandidate = sortableElement(({candidate, sortIndex, form}) => (
<li className="sortable"> <li className="sortable">
<Row key={"rowCandidate" + sortIndex}> <Row key={'rowCandidate' + sortIndex}>
<Col> <Col>
<InputGroup> <InputGroup>
<InputGroupAddon addonType="prepend"> <InputGroupAddon addonType="prepend">
@ -71,18 +88,17 @@ const SortableCandidate = sortableElement(({ candidate, sortIndex, form }) => (
</div> </div>
<div key="modal-title">Suppression ?</div> <div key="modal-title">Suppression ?</div>
<div key="modal-body"> <div key="modal-body">
Êtes-vous sûr de vouloir supprimer{" "} Êtes-vous sûr de vouloir supprimer{' '}
{candidate.label !== "" ? ( {candidate.label !== '' ? (
<b>"{candidate.label}"</b> <b>"{candidate.label}"</b>
) : ( ) : (
<span>la ligne {sortIndex + 1}</span> <span>la ligne {sortIndex + 1}</span>
)}{" "} )}{' '}
? ?
</div> </div>
<div <div
key="modal-confirm" key="modal-confirm"
onClick={() => form.removeCandidate(sortIndex)} onClick={() => form.removeCandidate(sortIndex)}>
>
Oui Oui
</div> </div>
<div key="modal-cancel">Non</div> <div key="modal-cancel">Non</div>
@ -99,7 +115,7 @@ const SortableCandidate = sortableElement(({ candidate, sortIndex, form }) => (
</li> </li>
)); ));
const SortableCandidatesContainer = sortableContainer(({ items, form }) => { const SortableCandidatesContainer = sortableContainer(({items, form}) => {
return ( return (
<ul className="sortable"> <ul className="sortable">
{items.map((candidate, index) => ( {items.map((candidate, index) => (
@ -119,26 +135,24 @@ class CreateElection extends Component {
static contextType = AppContext; static contextType = AppContext;
constructor(props) { constructor(props) {
super(props); super(props);
//default value : start now // default value : start at the last hour
const startedAt = new Date(); const now = new Date();
const startedHour = startedAt.getHours(); const start = new Date(dateMinusTime(now).getTime() + hours(now));
//default value : finish in one week const { title } = queryString.parse(this.props.location.search);
const finishedAt = new Date(startedAt.getTime() + 7 * 24 * 60 * 60 * 1000);
const params = new URLSearchParams(window.location.search);
this.state = { this.state = {
candidates: [{ label: "" }, { label: "" }], candidates: [{label: ''}, {label: ''}],
numCandidatesWithLabel: 0, numCandidatesWithLabel: 0,
title: params.get("title") ? params.get("title") : "", title: title || '',
isVisibleTipsDragAndDropCandidate: true, isVisibleTipsDragAndDropCandidate: true,
numGrades: 7, numGrades: 7,
successCreate: false, successCreate: false,
redirectTo: null, redirectTo: null,
isAdvancedOptionsOpen: false, isAdvancedOptionsOpen: false,
startedDayAt: startedAt.toISOString().substring(0, 10), start,
finishedDayAt: finishedAt.toISOString().substring(0, 10), // by default, the election ends in a week
startedTimeAt: Math.floor(startedHour / 2) * 2 + ":00:00", finish: new Date(start.getTime() + 7 * 24 * 3600 * 1000),
finishedTimeAt: "23:59:59", electorEmails: [],
electorEmails: []
}; };
this.candidateInputs = []; this.candidateInputs = [];
this.focusInput = React.createRef(); this.focusInput = React.createRef();
@ -146,16 +160,16 @@ class CreateElection extends Component {
} }
handleChangeTitle = event => { handleChangeTitle = event => {
this.setState({ title: event.target.value }); this.setState({title: event.target.value});
}; };
addCandidate = event => { addCandidate = event => {
let candidates = this.state.candidates; let candidates = this.state.candidates;
if (candidates.length < 100) { if (candidates.length < 100) {
candidates.push({ label: "" }); candidates.push({label: ''});
this.setState({ candidates: candidates }); this.setState({candidates: candidates});
} }
if (event.type === "keypress") { if (event.type === 'keypress') {
setTimeout(() => { setTimeout(() => {
this.candidateInputs[this.state.candidates.length - 1].focus(); this.candidateInputs[this.state.candidates.length - 1].focus();
}, 250); }, 250);
@ -166,9 +180,9 @@ class CreateElection extends Component {
let candidates = this.state.candidates; let candidates = this.state.candidates;
candidates.splice(index, 1); candidates.splice(index, 1);
if (candidates.length === 0) { if (candidates.length === 0) {
candidates = [{ label: "" }]; candidates = [{label: ''}];
} }
this.setState({ candidates: candidates }); this.setState({candidates: candidates});
}; };
editCandidateLabel = (event, index) => { editCandidateLabel = (event, index) => {
@ -176,19 +190,19 @@ class CreateElection extends Component {
let numLabels = 0; let numLabels = 0;
candidates[index].label = event.currentTarget.value; candidates[index].label = event.currentTarget.value;
candidates.map((candidate, i) => { candidates.map((candidate, i) => {
if (candidate.label !== "") { if (candidate.label !== '') {
numLabels++; numLabels++;
} }
return candidate.label; return candidate.label;
}); });
this.setState({ this.setState({
candidates: candidates, candidates: candidates,
numCandidatesWithLabel: numLabels numCandidatesWithLabel: numLabels,
}); });
}; };
handleKeypressOnCandidateLabel = (event, index) => { handleKeypressOnCandidateLabel = (event, index) => {
if (event.key === "Enter") { if (event.key === 'Enter') {
event.preventDefault(); event.preventDefault();
if (index + 1 === this.state.candidates.length) { if (index + 1 === this.state.candidates.length) {
this.addCandidate(event); this.addCandidate(event);
@ -198,36 +212,33 @@ class CreateElection extends Component {
} }
}; };
onCandidatesSortEnd = ({ oldIndex, newIndex }) => { onCandidatesSortEnd = ({oldIndex, newIndex}) => {
let candidates = this.state.candidates; let candidates = this.state.candidates;
candidates = arrayMove(candidates, oldIndex, newIndex); candidates = arrayMove(candidates, oldIndex, newIndex);
this.setState({ candidates: candidates }); this.setState({candidates: candidates});
}; };
handleChangeNumGrades = event => { handleChangeNumGrades = event => {
this.setState({ numGrades: event.target.value }); this.setState({numGrades: event.target.value});
}; };
toggleAdvancedOptions = () => { toggleAdvancedOptions = () => {
this.setState({ isAdvancedOptionsOpen: !this.state.isAdvancedOptionsOpen }); this.setState({isAdvancedOptionsOpen: !this.state.isAdvancedOptionsOpen});
}; };
handleSubmit() { handleSubmit() {
const { candidates, title, numGrades } = this.state; const {candidates, title, numGrades, start, finish} = this.state;
console.log("dates", start, finish);
const endpoint = resolve( const endpoint = resolve(
this.context.urlServer, this.context.urlServer,
this.context.routesServer.setElection this.context.routesServer.setElection,
); );
const startedAtAsArray=this.state.startedDayAt.split("-").concat(this.state.startedTimeAt.split(":"));
const startedAtAsDate = new Date(startedAtAsArray[0], startedAtAsArray[1], startedAtAsArray[2], startedAtAsArray[3], startedAtAsArray[4], startedAtAsArray[5]);
const finishedAtAsArray=this.state.finishedDayAt.split("-").concat(this.state.finishedTimeAt.split(":"));
const finishedAtAsDate = new Date(finishedAtAsArray[0], finishedAtAsArray[1], finishedAtAsArray[2], finishedAtAsArray[3], finishedAtAsArray[4], finishedAtAsArray[5]);
fetch(endpoint, { fetch(endpoint, {
method: "POST", method: 'POST',
headers: { headers: {
"Content-Type": "application/json" 'Content-Type': 'application/json',
}, },
body: JSON.stringify({ body: JSON.stringify({
title: title, title: title,
@ -235,57 +246,29 @@ class CreateElection extends Component {
on_invitation_only: this.state.electorEmails.length > 0, on_invitation_only: this.state.electorEmails.length > 0,
num_grades: numGrades, num_grades: numGrades,
elector_emails: this.state.electorEmails, elector_emails: this.state.electorEmails,
started_at: startedAtAsDate.getTime()/1000, start_at: start.getTime() / 1000,
finished_at: finishedAtAsDate.getTime()/1000 finish_at: finish.getTime() / 1000,
}) }),
}) })
.then(response => response.json()) .then(response => response.json())
.then(result => .then(result =>
this.setState(state => ({ this.setState(state => ({
redirectTo: "/create-success/" + result.id, redirectTo: '/create-success/' + result.id,
successCreate: true successCreate: true,
})) })),
) )
.catch(error => error); .catch(error => error);
} }
handleSendWithoutCandidate = () => { handleSendWithoutCandidate = () => {
toast.error("Vous devez saisir au moins deux candidats !", { toast.error('Vous devez saisir au moins deux candidats !', {
position: toast.POSITION.TOP_CENTER position: toast.POSITION.TOP_CENTER,
}); });
}; };
formatDate = (dateIsoStr, timeIsoStr) => {
let date = new Date(dateIsoStr + "T" + timeIsoStr);
let day = date.getDate();
let month = date.getMonth() + 1; //Months are zero based
let year = date.getFullYear();
let hours = date.getHours();
let minutes = date.getMinutes();
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
if (hours < 10) {
hours = "0" + hours;
}
if (minutes < 10) {
minutes = "0" + minutes;
}
let hoursString = hours + "h" + minutes;
if (hoursString === "23h59") {
hoursString = "minuit";
}
return day + "/" + month + "/" + year + " à " + hoursString;
};
render() { render() {
const { successCreate, redirectTo } = this.state; const {successCreate, redirectTo} = this.state;
const { electorEmails } = this.state; const {electorEmails} = this.state;
if (successCreate) return <Redirect to={redirectTo} />; if (successCreate) return <Redirect to={redirectTo} />;
@ -321,7 +304,7 @@ class CreateElection extends Component {
Posez ici votre question ou introduisez simplement votre vote Posez ici votre question ou introduisez simplement votre vote
(250 caractères max.) (250 caractères max.)
<br /> <br />
<u>Par exemple :</u>{" "} <u>Par exemple :</u>{' '}
<em>Pour être mon représentant, je juge ce candidat ...</em> <em>Pour être mon représentant, je juge ce candidat ...</em>
</HelpButton> </HelpButton>
</Col> </Col>
@ -346,8 +329,7 @@ class CreateElection extends Component {
className="btn-block mt-2" className="btn-block mt-2"
tabIndex={this.state.candidates.length + 2} tabIndex={this.state.candidates.length + 2}
type="button" type="button"
onClick={event => this.addCandidate(event)} onClick={event => this.addCandidate(event)}>
>
<FontAwesomeIcon icon={faPlus} className="mr-2" /> <FontAwesomeIcon icon={faPlus} className="mr-2" />
Ajouter une proposition Ajouter une proposition
</Button> </Button>
@ -356,13 +338,11 @@ class CreateElection extends Component {
xs="12" xs="12"
sm="6" sm="6"
md="12" md="12"
className="text-center text-sm-right text-md-left" className="text-center text-sm-right text-md-left">
>
<Button <Button
color="link" color="link"
className="text-white mt-3 mb-1" className="text-white mt-3 mb-1"
onClick={this.toggleAdvancedOptions} onClick={this.toggleAdvancedOptions}>
>
<FontAwesomeIcon icon={faCogs} className="mr-2" /> <FontAwesomeIcon icon={faCogs} className="mr-2" />
Options avancées Options avancées
</Button> </Button>
@ -379,32 +359,34 @@ class CreateElection extends Component {
<input <input
className="form-control" className="form-control"
type="date" type="date"
value={this.state.startedDayAt} value={dateToISO(this.state.start)}
onChange={e => onChange={e => {
this.setState({ startedDayAt: e.target.value }) this.setState({
} start:
timeMinusDate(this.state.start) +
new Date(e.target.valueAsNumber),
});
}}
/> />
</Col> </Col>
<Col xs="6" md="5" lg="3"> <Col xs="6" md="5" lg="3">
<select <select
className="form-control" className="form-control"
value={this.state.startedTimeAt} value={this.state.start.getHours()}
onChange={e => onChange={e =>
this.setState({ startedTimeAt: e.target.value }) this.setState({
} start:
> dateMinusTime(this.state.start) +
<option value="2:00:00">02h00</option> new Date(e.target.value) * 3600000,
<option value="3:00:00">03h00</option> })
<option value="4:00:00">04h00</option> }>
<option value="6:00:00">06h00</option> {Array(22)
<option value="8:00:00">08h00</option> .fill(1)
<option value="10:00:00">10h00</option> .map((x, i) => (
<option value="12:00:00">12h00</option> <option value={i} key={i}>
<option value="16:00:00">16h00</option> {i}h00
<option value="18:00:00">18h00</option> </option>
<option value="20:00:00">20h00</option> ))}
<option value="22:00:00">22h00</option>
<option value="23:59:59">Minuit</option>
</select> </select>
</Col> </Col>
</Row> </Row>
@ -417,33 +399,35 @@ class CreateElection extends Component {
<input <input
className="form-control" className="form-control"
type="date" type="date"
value={this.state.finishedDayAt} value={dateToISO(this.state.finish)}
min={this.state.startedDayAt} min={dateToISO(this.state.start)}
onChange={e => onChange={e => {
this.setState({ finishedDayAt: e.target.value }) this.setState({
} start:
timeMinusDate(this.state.start) +
new Date(e.target.valueAsNumber),
});
}}
/> />
</Col> </Col>
<Col xs="6" md="5" lg="3"> <Col xs="6" md="5" lg="3">
<select <select
className="form-control" className="form-control"
value={this.state.finishedTimeAt} value={this.state.finish.getHours()}
onChange={e => onChange={e =>
this.setState({ finishedTimeAt: e.target.value }) this.setState({
} finish:
> dateMinusTime(this.state.finish) +
<option value="2:00:00">02h00</option> new Date(e.target.value) * 3600000,
<option value="3:00:00">03h00</option> })
<option value="4:00:00">04h00</option> }>
<option value="6:00:00">06h00</option> {Array(22)
<option value="8:00:00">08h00</option> .fill(1)
<option value="10:00:00">10h00</option> .map((x, i) => (
<option value="12:00:00">12h00</option> <option value={i} key={i}>
<option value="16:00:00">16h00</option> {i}h00
<option value="18:00:00">18h00</option> </option>
<option value="20:00:00">20h00</option> ))}
<option value="22:00:00">22h00</option>
<option value="23:59:59">Minuit</option>
</select> </select>
</Col> </Col>
</Row> </Row>
@ -457,8 +441,7 @@ class CreateElection extends Component {
className="form-control" className="form-control"
tabIndex={this.state.candidates.length + 3} tabIndex={this.state.candidates.length + 3}
onChange={this.handleChangeNumGrades} onChange={this.handleChangeNumGrades}
defaultValue="7" defaultValue="7">
>
<option value="5">5</option> <option value="5">5</option>
<option value="6">6</option> <option value="6">6</option>
<option value="7">7</option> <option value="7">7</option>
@ -469,9 +452,9 @@ class CreateElection extends Component {
Vous pouvez choisir ici le nombre de mentions pour votre Vous pouvez choisir ici le nombre de mentions pour votre
vote vote
<br /> <br />
<u>Par exemple : </u>{" "} <u>Par exemple : </u>{' '}
<em> <em>
{" "} {' '}
5 = Excellent, Très bien, bien, assez bien, passable 5 = Excellent, Très bien, bien, assez bien, passable
</em> </em>
</HelpButton> </HelpButton>
@ -480,8 +463,7 @@ class CreateElection extends Component {
xs="12" xs="12"
md="9" md="9"
lg="10" lg="10"
className="offset-xs-0 offset-md-3 offset-lg-2" className="offset-xs-0 offset-md-3 offset-lg-2">
>
{grades.map((mention, i) => { {grades.map((mention, i) => {
return ( return (
<span <span
@ -489,10 +471,9 @@ class CreateElection extends Component {
className="badge badge-light mr-2 mt-2 " className="badge badge-light mr-2 mt-2 "
style={{ style={{
backgroundColor: mention.color, backgroundColor: mention.color,
color: "#fff", color: '#fff',
opacity: i < this.state.numGrades ? 1 : 0.3 opacity: i < this.state.numGrades ? 1 : 0.3,
}} }}>
>
{mention.label} {mention.label}
</span> </span>
); );
@ -509,7 +490,7 @@ class CreateElection extends Component {
placeholder="Saisissez ici les e-mails des participants" placeholder="Saisissez ici les e-mails des participants"
emails={electorEmails} emails={electorEmails}
onChange={(_emails: string[]) => { onChange={(_emails: string[]) => {
this.setState({ electorEmails: _emails }); this.setState({electorEmails: _emails});
}} }}
validateEmail={email => { validateEmail={email => {
return isEmail(email); // return boolean return isEmail(email); // return boolean
@ -517,15 +498,14 @@ class CreateElection extends Component {
getLabel={( getLabel={(
email: string, email: string,
index: number, index: number,
removeEmail: (index: number) => void removeEmail: (index: number) => void,
) => { ) => {
return ( return (
<div data-tag key={index}> <div data-tag key={index}>
{email} {email}
<span <span
data-tag-handle data-tag-handle
onClick={() => removeEmail(index)} onClick={() => removeEmail(index)}>
>
× ×
</span> </span>
</div> </div>
@ -549,8 +529,7 @@ class CreateElection extends Component {
{this.state.numCandidatesWithLabel >= 2 ? ( {this.state.numCandidatesWithLabel >= 2 ? (
<ButtonWithConfirm <ButtonWithConfirm
className="btn btn-success float-right btn-block" className="btn btn-success float-right btn-block"
tabIndex={this.state.candidates.length + 4} tabIndex={this.state.candidates.length + 4}>
>
<div key="button"> <div key="button">
<FontAwesomeIcon icon={faCheck} className="mr-2" /> <FontAwesomeIcon icon={faCheck} className="mr-2" />
Valider Valider
@ -570,7 +549,7 @@ class CreateElection extends Component {
<div className="p-1 pl-0"> <div className="p-1 pl-0">
<ul className="m-0 pl-4"> <ul className="m-0 pl-4">
{this.state.candidates.map((candidate, i) => { {this.state.candidates.map((candidate, i) => {
if (candidate.label !== "") { if (candidate.label !== '') {
return ( return (
<li key={i} className="m-0"> <li key={i} className="m-0">
{candidate.label} {candidate.label}
@ -586,19 +565,15 @@ class CreateElection extends Component {
Dates Dates
</div> </div>
<p className="p-1 pl-3"> <p className="p-1 pl-3">
Le vote se déroulera du{" "} Le vote se déroulera du{' '}
<b> <b>
{this.formatDate( {this.state.start.toLocaleDateString()}, à{' '}
this.state.startedDayAt, {this.state.start.toLocaleTimeString()}
this.state.startedTimeAt </b>{' '}
)} au{' '}
</b>{" "}
au{" "}
<b> <b>
{this.formatDate( {this.state.finish.toLocaleDateString()}, à{' '}
this.state.finishedDayAt, {this.state.finish.toLocaleTimeString()}
this.state.finishedTimeAt
)}
</b> </b>
</p> </p>
<div className="text-white bg-primary p-1">Mentions</div> <div className="text-white bg-primary p-1">Mentions</div>
@ -610,9 +585,8 @@ class CreateElection extends Component {
className="badge badge-light mr-2 mt-2" className="badge badge-light mr-2 mt-2"
style={{ style={{
backgroundColor: mention.color, backgroundColor: mention.color,
color: "#fff" color: '#fff',
}} }}>
>
{mention.label} {mention.label}
</span> </span>
) : ( ) : (
@ -625,7 +599,7 @@ class CreateElection extends Component {
</div> </div>
<div className="p-1 pl-3"> <div className="p-1 pl-3">
{electorEmails.length > 0 ? ( {electorEmails.length > 0 ? (
electorEmails.join(", ") electorEmails.join(', ')
) : ( ) : (
<p> <p>
Aucune adresse e-mail précisée. Aucune adresse e-mail précisée.
@ -648,8 +622,7 @@ class CreateElection extends Component {
<Button <Button
type="button" type="button"
className="btn btn-dark float-right btn-block" className="btn btn-dark float-right btn-block"
onClick={this.handleSendWithoutCandidate} onClick={this.handleSendWithoutCandidate}>
>
<FontAwesomeIcon icon={faCheck} className="mr-2" /> <FontAwesomeIcon icon={faCheck} className="mr-2" />
Valider Valider
</Button> </Button>
@ -661,4 +634,4 @@ class CreateElection extends Component {
); );
} }
} }
export default CreateElection; export default withRouter(CreateElection);

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save