From 58310afba4e89db6966fbf8f32cd4a44a52a6ca6 Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 11 Jul 2024 02:23:51 +0100 Subject: Add new vote component and voting redux store --- src/components/InputTypes/Vote.tsx | 226 +++++++++++++++++++++++++++++++++++++ 1 file changed, 226 insertions(+) create mode 100644 src/components/InputTypes/Vote.tsx (limited to 'src/components') diff --git a/src/components/InputTypes/Vote.tsx b/src/components/InputTypes/Vote.tsx new file mode 100644 index 0000000..67a7cac --- /dev/null +++ b/src/components/InputTypes/Vote.tsx @@ -0,0 +1,226 @@ +/** @jsx jsx */ +import { jsx, css } from "@emotion/react"; + +import React, { useEffect, useState } from "react"; +import RenderedQuestion from "../Question"; +import { slugify, humanize } from "../../utils"; +import colors from "../../colors"; +import { useDispatch, useSelector } from "react-redux"; +import { registerVote, changeVote, VoteSliceState } from "../../slices/votes"; +import FlipMove from "react-flip-move"; + +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faCaretUp, faCaretDown } from "@fortawesome/free-solid-svg-icons"; + +interface VoteProps { + options: Array; + valid: boolean; + question: React.RefObject; + questionId: string; + handler: (event: Record) => void; +} + +const baseCardStyles = css` + display: flex; + align-items: center; + background-color: ${colors.darkerGreyple}; + padding: 10px; + border-radius: 5px; + margin-bottom: 10px; +`; + +const buttonStyles = css` + border: none; + background: ${colors.greyple}; + cursor: pointer; + margin-right: 20px; + margin-left: 20px; + border-radius: 5px; + transition: transform 0.1s ease; + transform: none; + + :hover { + transform: scale(1.1); + } +`; + +const iconStyles = css` + font-size: 2em; + color: white; +`; + +const markerStyles = css` + border-radius: 5px; + padding: 5px; + margin-right: 10px; +`; + +const votingStyles = css` + button { + background-color: ${colors.darkerBlurple}; + } +`; + +const Buttons = (base: { questionId: string; optionId: string }) => { + const dispatch = useDispatch(); + + return ( +
+ + +
+ ); +}; + +const Card = ({ + id, + content, + questionId, +}: { + id: string; + content: string; + questionId: string; +}) => { + const foundIndex = useSelector<{ vote: VoteSliceState }, number | null>( + (state) => { + const votes = state.vote?.votes; + const questionVotes = votes?.[questionId]; + return questionVotes ? questionVotes[id] : null; + }, + ); + + const indexMarker = + foundIndex === null ? ( +
+ No preference +
+ ) : ( +
+ {humanize(foundIndex)} +
+ ); + + return ( +
+ {indexMarker} +

{content}

+
+ +
+ ); +}; + +const CardList = React.memo(function CardList({ + cards, + questionId, + handler, +}: { + cards: string[]; + questionId: string; + handler: VoteProps["handler"]; +}) { + const votes = useSelector< + { vote: VoteSliceState }, + Record + >((state) => { + const votes = state.vote?.votes; + const questionVotes = votes?.[questionId]; + return questionVotes; + }); + + useEffect(() => { + handler(votes); + }, [votes]); + + if (votes) { + cards = cards.sort((a, b) => { + if (!votes[slugify(a)]) { + return 1; + } + + if (!votes[slugify(b)]) { + return -1; + } + return votes[slugify(a)] - votes[slugify(b)]; + }); + } + + return ( + + {cards.map((cardContent: string) => ( +
+ +
+ ))} +
+ ); +}); + +export default function Vote(props: VoteProps): JSX.Element { + const [state,] = useState({ + cards: props.options + .map((value) => ({ value, sort: Math.random() })) + .sort((a, b) => a.sort - b.sort) + .map(({ value }) => value), + }); + + const dispatch = useDispatch(); + + useEffect(() => { + dispatch( + registerVote({ + questionId: props.questionId, + questionSlugs: state.cards.map((value) => slugify(value)), + }), + ); + }, [props.questionId]); + + const COPY = "Use the buttons to organise options into your preferred order. You can have multiple options with the same ranking. Additionally, you can leave some or all options as \"No preference\" if you do not wish to order them."; + + return ( +
+

{COPY}

+ +
+ ); +} -- cgit v1.2.3 From 572454f01749b4e8b43bfd9c1fcc47210d86fa0f Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Thu, 11 Jul 2024 02:24:08 +0100 Subject: Handle voting component callbacks --- src/api/question.ts | 1 + src/components/InputTypes/index.tsx | 5 +++++ src/components/Question.tsx | 18 +++++++++++++++--- 3 files changed, 21 insertions(+), 3 deletions(-) (limited to 'src/components') diff --git a/src/api/question.ts b/src/api/question.ts index 56706e2..0fcea03 100644 --- a/src/api/question.ts +++ b/src/api/question.ts @@ -8,6 +8,7 @@ export enum QuestionType { Range = "range", Section = "section", TimeZone = "timezone", + Vote = "vote" } export interface Question { diff --git a/src/components/InputTypes/index.tsx b/src/components/InputTypes/index.tsx index ee92ef1..1880bcd 100644 --- a/src/components/InputTypes/index.tsx +++ b/src/components/InputTypes/index.tsx @@ -11,6 +11,7 @@ import {QuestionType} from "../../api/question"; import RenderedQuestion from "../Question"; import Code from "./Code"; import TimeZone from "./TimeZone"; +import Vote from "./Vote"; export default function create_input( {props: renderedQuestionProps, realState}: RenderedQuestion, @@ -67,6 +68,10 @@ export default function create_input( result = ; break; + case QuestionType.Vote: + result = ; + break; + default: result =