diff --git a/package.json b/package.json index 288d6ea..fcb9018 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "react-dom": "^16.8.6", "react-router-dom": "^5.0.0", "react-scripts": "3.0.1", + "react-sortable-hoc": "^1.9.1", + "react-toastify": "^5.2.1", "reactstrap": "^8.0.0" }, "scripts": { diff --git a/src/components/form/ButtonWithConfirm.js b/src/components/form/ButtonWithConfirm.js new file mode 100644 index 0000000..dab0412 --- /dev/null +++ b/src/components/form/ButtonWithConfirm.js @@ -0,0 +1,53 @@ +import React, {Component} from "react"; +import ModalConfirm from "./ModalConfirm"; + + +class ButtonWithConfirm extends Component { + constructor(props) { + super(props); + this._modalConfirm=React.createRef(); + this.state={ + focused:false + } + } + + getComponent= (key) => { + return this.props.children.filter( (comp) => { + return comp.key === key; + }); + }; + + render() { + const classNames=this.props.className.split(" "); + + let classNameForDiv=""; + let classNameForButton=""; + classNames.forEach(function(className){ + if(className==="input-group-prepend" || className==="input-group-append" ){ + classNameForDiv+=" "+className; + }else{ + classNameForButton+=" "+className; + } + }); + + + + return ( +
+ + +
{this.getComponent("modal-title")}
+
{this.getComponent("modal-body")}
+
{this.getComponent("modal-confirm")}
+
{this.getComponent("modal-cancel")}
+
+
+ ); + } +} + +export default ButtonWithConfirm; \ No newline at end of file diff --git a/src/components/form/ModalConfirm.js b/src/components/form/ModalConfirm.js new file mode 100644 index 0000000..cd69311 --- /dev/null +++ b/src/components/form/ModalConfirm.js @@ -0,0 +1,40 @@ +import React, {Component} from "react"; +import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap'; + +class ModalConfirm extends Component { + constructor(props) { + super(props); + this.state = { + modal: false + }; + } + + toggle = () => { + this.setState({ + modal: !this.state.modal + }); + }; + + getComponent= (key) => { + return this.props.children.filter( (comp) => { + return comp.key === key; + }); + }; + + render() { + return ( + + {this.getComponent("title")} + + {this.getComponent("body")} + + + {' '} + + + + ); + } +} + +export default ModalConfirm; \ No newline at end of file diff --git a/src/components/views/CreateBallot.js b/src/components/views/CreateBallot.js index 6e1f764..362696a 100644 --- a/src/components/views/CreateBallot.js +++ b/src/components/views/CreateBallot.js @@ -1,24 +1,108 @@ import React, {Component} from "react"; -import { Container, Row, Col, Input, Label } from 'reactstrap'; +import { Container, Row, Col, Input, Label, Card, CardHeader, CardBody, Collapse } from 'reactstrap'; +import {toast, ToastContainer} from 'react-toastify'; import HelpButton from "../form/HelpButton"; +import {arrayMove, sortableContainer, sortableElement, sortableHandle} from 'react-sortable-hoc'; +import ButtonWithConfirm from "../form/ButtonWithConfirm"; +const DragHandle = sortableHandle(({children}) => {children}); + +const SortableCandidate = sortableElement(({candidate, sortIndex, form}) =>
  • +
    +
    +
    +
    +
    + + {sortIndex + 1} + +
    + form.editCandidateLabel(event, sortIndex)} + maxLength="250"/> + +
    +
    Suppression ?
    +
    Êtes-vous sûr de vouloir supprimer la + proposition "{candidate.label}" ? +
    +
    form.removeCandidate(sortIndex)}>Oui
    +
    Non
    +
    +
    +
    +
    +
    +
  • ); + +const SortableCandidatesContainer = sortableContainer(({items, form}) => { + return ; +}); class CreateBallot extends Component { constructor(props) { super(props); this.state = { - } + candidates:[] + }; + this._candidateLabelInput = React.createRef(); + this._addCandidateButton = React.createRef(); + this.focusInput= React.createRef(); } + addCandidate = (evt) => { + if (evt.type === "click" || (evt.type === "keydown" && evt.keyCode === 13)) { + const candidateFieldLabel = this._candidateLabelInput.current.value; + let candidates = this.state.candidates; + if (candidates.length < 100) { + candidates.push({label: candidateFieldLabel}); + this._candidateLabelInput.current.value = ''; + this.setState({isAddCandidateOpen: false, candidates: candidates}); + } + + } + + }; + + removeCandidate = (index) => { + let candidates = this.state.candidates; + candidates.splice(index, 1); + this.setState({candidates: candidates}); + }; + + editCandidateLabel = (event, index) => { + let candidates = this.state.candidates; + candidates[index].label = event.currentTarget.value; + this.setState({candidates: candidates}); + }; + + toggleAddCandidate = () => { + if (this.state.candidates.length >= 100) { + toast.error("Vous ne pouvez plus ajouter de proposition ! (100 max.)", { + position: toast.POSITION.TOP_CENTER + }); + } else { + this._candidateLabelInput.current.value = ""; + this.setState({ + isAddCandidateOpen: !this.state.isAddCandidateOpen + }); + } + + + }; + render(){ const params = new URLSearchParams(this.props.location.search); return( +

    Démarrer un vote

    @@ -26,20 +110,107 @@ class CreateBallot extends Component {
    - + - + - Posez ici votre question ou introduisez simplement votre vote. + Posez ici votre question ou introduisez simplement votre vote (250 caractères max.)
    Par exemple : Pour être mon représentant, je juge ce candidat ...
    + + +
    +
    + {this.state.candidates.length} + {(this.state.candidates.length < 2) ? Proposition soumise : + Propositions soumises } + au vote +
    +
    + + +
    +
    + +
    +
    + {(this.state.candidates.length > 2 && this.state.isVisibleTipsDragndropCandidate === true) ? +
    +
    + + Astuce : Vous pouvez changer l'ordre des + propositions par glisser-déposer du numéro ! +
    +
    + +
    +
    :
    } +
    +
    + { + this._candidateLabelInput.current.focus() + }} + onExited={() => { + this._addCandidateButton.current.focus() + }}> + + + + Ajout d'une proposition + (100 max.) + +
    +
    + + this.addCandidate(evt)} + ref={this._candidateLabelInput} + placeholder="Nom de la proposition, nom du candidat, etc..." + maxLength="250"/> +
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + +
    + {this.state.isAddCandidateOpen ? null : + } + +
    + +
    + + ) diff --git a/yarn.lock b/yarn.lock index a8c4731..c361fe0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2556,7 +2556,7 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@^2.2.3: +classnames@^2.2.3, classnames@^2.2.6: version "2.2.6" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== @@ -8443,7 +8443,7 @@ prop-types-exact@^1.2.0: object.assign "^4.1.0" reflect.ownkeys "^0.2.0" -prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -8788,6 +8788,15 @@ react-scripts@3.0.1: optionalDependencies: fsevents "2.0.6" +react-sortable-hoc@^1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-1.9.1.tgz#ae3d28c3cff87fb862be3ddcde9c76b5b5bd2152" + integrity sha512-2VeofjRav8+eZeE5Nm/+b8mrA94rQ+gBsqhXi8pRBSjOWNqslU3ZEm+0XhSlfoXJY2lkgHipfYAUuJbDtCixRg== + dependencies: + "@babel/runtime" "^7.2.0" + invariant "^2.2.4" + prop-types "^15.5.7" + react-test-renderer@^16.0.0-0: version "16.8.6" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1" @@ -8798,7 +8807,17 @@ react-test-renderer@^16.0.0-0: react-is "^16.8.6" scheduler "^0.13.6" -react-transition-group@^2.3.1: +react-toastify@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/react-toastify/-/react-toastify-5.2.1.tgz#faa05bc4cd5066ee50bf56c7f8b8fd1492e71aca" + integrity sha512-OEZQld/jvjFCQnmXShb73dxVgslEuVz6Jb9/K22x+OcpQH5abtb278tO+Z9FwWsnu8aOvKiPuEYRrSfXC0HF8w== + dependencies: + "@babel/runtime" "^7.4.2" + classnames "^2.2.6" + prop-types "^15.7.2" + react-transition-group "^2.6.1" + +react-transition-group@^2.3.1, react-transition-group@^2.6.1: version "2.9.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d" integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==