diff --git a/components/admin/CandidateModalSet.tsx b/components/admin/CandidateModalSet.tsx index 0054317..a416866 100644 --- a/components/admin/CandidateModalSet.tsx +++ b/components/admin/CandidateModalSet.tsx @@ -182,7 +182,6 @@ const CandidateModal = ({isOpen, position, toggle}) => { placeholder={t('admin.candidate-desc-placeholder')} onChange={handleDescription} value={state.description} - maxLength={250} />
diff --git a/components/admin/GradeModalAdd.tsx b/components/admin/GradeModalAdd.tsx index e6849cc..a678c8f 100644 --- a/components/admin/GradeModalAdd.tsx +++ b/components/admin/GradeModalAdd.tsx @@ -1,13 +1,13 @@ -import { useState, useEffect } from 'react'; -import { Col, Label, Input, Modal, ModalBody, Form } from 'reactstrap'; -import { faPlus, faArrowLeft } from '@fortawesome/free-solid-svg-icons'; -import { useTranslation } from 'next-i18next'; -import { ElectionTypes, useElection } from '@services/ElectionContext'; +import {useState, useEffect} from 'react'; +import {Col, Label, Input, Modal, ModalBody, Form} from 'reactstrap'; +import {faPlus, faArrowLeft} from '@fortawesome/free-solid-svg-icons'; +import {useTranslation} from 'next-i18next'; +import {ElectionTypes, useElection} from '@services/ElectionContext'; import Button from '@components/Button'; -import { GradeItem } from '@services/type'; +import {GradeItem} from '@services/type'; -const GradeModal = ({ isOpen, toggle }) => { - const { t } = useTranslation(); +const GradeModal = ({isOpen, toggle}) => { + const {t} = useTranslation(); const [election, dispatch] = useElection(); const [grade, setGrade] = useState({ @@ -19,20 +19,20 @@ const GradeModal = ({ isOpen, toggle }) => { useEffect(() => { const maxValue = Math.max(...election.grades.map((g) => g.value)); - setGrade({ ...grade, value: maxValue + 1 }); + setGrade({...grade, value: maxValue + 1}); }, [election]); const save = () => { dispatch({ type: ElectionTypes.SET, field: 'grades', - value: [...election.grades, grade], + value: [grade, ...election.grades], }); toggle(); }; const handleName = (e) => { - setGrade((s) => ({ ...s, name: e.target.value })); + setGrade((s) => ({...s, name: e.target.value})); }; const names = election.grades.map((g) => g.name); diff --git a/components/admin/Grades.tsx b/components/admin/Grades.tsx index 5373815..0667560 100644 --- a/components/admin/Grades.tsx +++ b/components/admin/Grades.tsx @@ -53,12 +53,12 @@ const Grades = () => { const toggle = () => setVisible((v) => !v); useEffect(() => { - const defaultGrades = DEFAULT_GRADES.map((g, i) => ({ - name: t(g), - value: DEFAULT_GRADES.length - 1 - i, - active: true, - })); if (election.grades.length < 2) { + const defaultGrades = DEFAULT_GRADES.map((g, i) => ({ + name: t(g), + value: DEFAULT_GRADES.length - 1 - i, + active: true, + })); dispatch({ type: ElectionTypes.SET, field: 'grades', diff --git a/pages/admin/[pid]/[tid].tsx b/pages/admin/[pid]/[tid].tsx index e095db8..4d8b8c9 100644 --- a/pages/admin/[pid]/[tid].tsx +++ b/pages/admin/[pid]/[tid].tsx @@ -6,6 +6,9 @@ import {serverSideTranslations} from 'next-i18next/serverSideTranslations'; import {Container, Row, Col} from 'reactstrap'; import { faArrowRight, + faCheck, + faCheckToSlot, + faFloppyDisk, faSquarePollVertical, } from '@fortawesome/free-solid-svg-icons'; import {getElection, updateElection} from '@services/api'; @@ -91,87 +94,83 @@ const Spinner = () => { ); }; -const HeaderRubbon = ({token}) => { +const ManageButtonsMobile = ({handleClosing, waiting}) => { const {t} = useTranslation(); - const [election, dispatch] = useElection(); - const [_, dispatchApp] = useAppContext(); + const [election, _] = useElection(); const router = useRouter(); - const [waiting, setWaiting] = useState(false); - const handleClosing = async () => { - setWaiting(true); - dispatch({ - type: ElectionTypes.SET, - field: 'forceClose', - value: true, - }); + return ( + <> + {!election.restricted && !isClosed(election) && ( + + + + )} - const candidates = election.candidates - .filter((c) => c.active) - .map((c: CandidateItem) => ({ - name: c.name, - description: c.description, - image: c.image, - id: c.id, - })); - const grades = election.grades - .filter((c) => c.active) - .map((g: GradeItem, i: number) => ({name: g.name, value: g.value, id: g.id})); + {canViewResults(election) && ( + + + + )} - const response = await updateElection( - election.ref, - election.name, - candidates, - grades, - election.description, - election.emails.length, - election.hideResults, - true, - election.restricted, - election.randomOrder, - token - ); - if (response.status === 200 && 'ref' in response) { - if (election.restricted && election.emails.length > 0) { - if (election.emails.length !== response.invites.length) { - throw new Error('Unexpected number of invites!'); - } - const urlVotes = response.invites.map((token: string) => - getUrl(RouteTypes.VOTE, router, response.ref, token) - ); - const urlResult = getUrl(RouteTypes.RESULTS, router, response.ref); - await sendInviteMails( - election.emails, - election.name, - urlVotes, - urlResult, - router - ); - } - setWaiting(false); - dispatchApp({ - type: AppTypes.TOAST_ADD, - status: 'success', - message: t('success.election-closed'), - }); - } - }; + {!isClosed(election) && ( + + )} + + ); +}; +const HeaderRubbonDesktop = ({handleClosing, handleSubmit, waiting}) => { + const {t} = useTranslation(); + const [election, _] = useElection(); + const router = useRouter(); return (
{t('admin.admin-title')}
+ + + {!election.restricted && !isClosed(election) && ( )} @@ -185,7 +184,7 @@ const HeaderRubbon = ({token}) => { style={{border: '2px solid rgba(255, 255, 255, 0.4)'}} position="right" > - {t('admin.go-to-result')} + {t('common.results')} )} @@ -205,6 +204,17 @@ const HeaderRubbon = ({token}) => { ); }; + +const HeaderRubbonMobile = () => { + const {t} = useTranslation(); + return ( +
+
{t('admin.admin-title')}
+
+ ) +} + const CreateElection = ({context, token}) => { const {t} = useTranslation(); const [election, dispatch] = useElection(); @@ -313,6 +323,68 @@ const CreateElection = ({context, token}) => { } }; + /** + * Close an election + */ + const handleClosing = async () => { + setWaiting(true); + dispatch({ + type: ElectionTypes.SET, + field: 'forceClose', + value: true, + }); + + const candidates = election.candidates + .filter((c) => c.active) + .map((c: CandidateItem) => ({ + name: c.name, + description: c.description, + image: c.image, + id: c.id, + })); + const grades = election.grades + .filter((c) => c.active) + .map((g: GradeItem, i: number) => ({name: g.name, value: g.value, id: g.id})); + + const response = await updateElection( + election.ref, + election.name, + candidates, + grades, + election.description, + election.emails.length, + election.hideResults, + true, + election.restricted, + election.randomOrder, + token + ); + if (response.status === 200 && 'ref' in response) { + if (election.restricted && election.emails.length > 0) { + if (election.emails.length !== response.invites.length) { + throw new Error('Unexpected number of invites!'); + } + const urlVotes = response.invites.map((token: string) => + getUrl(RouteTypes.VOTE, router, response.ref, token) + ); + const urlResult = getUrl(RouteTypes.RESULTS, router, response.ref); + await sendInviteMails( + election.emails, + election.name, + urlVotes, + urlResult, + router + ); + } + setWaiting(false); + dispatchApp({ + type: AppTypes.TOAST_ADD, + status: 'success', + message: t('success.election-closed'), + }); + } + }; + const numCandidates = election.candidates.filter( (c) => c.active && c.name != '' ).length; @@ -328,7 +400,12 @@ const CreateElection = ({context, token}) => { return ( <> - +
+ +
+
+ +
{ > {waiting ? : t('admin.confirm-edit')} +
+ +
diff --git a/public/locales/en/resource.json b/public/locales/en/resource.json index 959cce6..7eb4a1f 100644 --- a/public/locales/en/resource.json +++ b/public/locales/en/resource.json @@ -48,6 +48,7 @@ "common.days": "days", "common.send": "Send", "common.vote": "Vote", + "common.results": "Results", "common.the-vote": "The vote", "common.the-params": "The parameters", "common.welcome": "Welcome!", diff --git a/public/locales/fr/resource.json b/public/locales/fr/resource.json index c03793b..1446beb 100644 --- a/public/locales/fr/resource.json +++ b/public/locales/fr/resource.json @@ -51,6 +51,7 @@ "common.the-params": "Les paramètres", "common.vote": "Voter", "common.welcome": "Bienvenue !", + "common.results": "Résultats", "error.help": "Besoin d'aide ?", "error.cant-set-ongoing": "Vous ne pouvez pas modifier ce paramètre pour une élection déjà démarrée.", "error.at-least-2-candidates": "Ajoutez au moins deux candidats.",