Skip to content

Commit

Permalink
polls: refactor PollDetail components
Browse files Browse the repository at this point in the history
  • Loading branch information
hom3mad3 committed Dec 10, 2024
1 parent 28cbcf4 commit bb6708b
Show file tree
Hide file tree
Showing 16 changed files with 467 additions and 1,038 deletions.
13 changes: 10 additions & 3 deletions adhocracy4/polls/static/PollDetail/CharCounter.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import React from 'react'
import django from 'django'

export const CharCounter = (props) => {
const current = props.value.length
const translated = {
characters: django.gettext('characters')
}

export const CharCounter = ({ value, max, id }) => {
const current = value.length

return (
<span>{current}/{props.max}</span>
<span className="a4-char-counter" id={id}>
{current}/{max} {translated.characters}
</span>
)
}
97 changes: 97 additions & 0 deletions adhocracy4/polls/static/PollDetail/ChoiceRow.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import React, { useState, useEffect } from 'react'
import django from 'django'
import { TextareaWithCounter } from './TextareaWithCounter'

const translated = {
other: django.gettext('other')
}

const ChoiceInput = ({
type,
choice,
checked,
onInputChange,
disabled
}) => (
<input
className="poll-row__radio"
type={type}
id={'id_choice-' + choice.id + '-' + (type === 'radio' ? 'single' : 'multiple')}
value={choice.id}
checked={checked}
onChange={(event) => onInputChange(event, choice.is_other_choice)}
disabled={disabled}
aria-describedby={'textarea-with-counter-' + choice.id}
/>
)

// eslint-disable-next-line react/display-name
export const ChoiceRow = React.memo(({
choice,
checked,
onInputChange,
type,
disabled,
otherChoiceAnswer,
onOtherChange,
isAuthenticated,
isReadOnly,
errors
}) => {
const [textareaValue, setTextareaValue] = useState(otherChoiceAnswer)
const [showTextarea, setShowTextarea] = useState(false)

// When the choice is selected or changed, update the textarea visibility
useEffect(() => {
if (checked && choice.is_other_choice) {
setShowTextarea(true)
} else {
setShowTextarea(false)
}
}, [checked, choice.is_other_choice])

const handleChange = (event, isOtherChoice) => {
// Update the checkbox/radio button state
onInputChange(event, isOtherChoice)

// If the "Other" option is selected, show the textarea
if (isOtherChoice && event.target.checked) {
setShowTextarea(true)
} else {
setShowTextarea(false)
}
}

const handleTextareaChange = (event) => {
// Preserve the value of the textarea even if options are changed
setTextareaValue(event.target.value)
onOtherChange(event)
}

return (
<label
className={(choice.is_other_choice ? 'poll__choice--other' : 'poll-row radio')}
htmlFor={'id_choice-' + choice.id + '-' + (type === 'radio' ? 'single' : 'multiple')}
>
<ChoiceInput
type={type}
choice={choice}
checked={checked}
onInputChange={handleChange}
disabled={disabled}
/>
<span className={'radio__text' + (type === 'checkbox' ? ' radio__text--checkbox' : '')}>
{choice.is_other_choice ? translated.other : choice.label}
</span>
{showTextarea && (
<TextareaWithCounter
id={choice.id}
value={textareaValue} // Always use the local state value
onChange={handleTextareaChange}
disabled={!isAuthenticated || isReadOnly}
error={errors}
/>
)}
</label>
)
})
96 changes: 96 additions & 0 deletions adhocracy4/polls/static/PollDetail/PollChoice.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import React, { useEffect, useState } from 'react'
import django from 'django'
import { ChoiceRow } from './ChoiceRow'

const translated = {
multiple: django.gettext('Multiple answers are possible.')
}

export const PollChoice = (props) => {
const getUserAnswer = () => {
const userAnswerId = props.question.other_choice_user_answer
const userAnswer = props.question.other_choice_answers.find(oc => oc.vote_id === userAnswerId)
return props.question.other_choice_answer
? props.question.other_choice_answer
: ((userAnswerId && userAnswer)
? userAnswer.answer
: ''
)
}

const [userChoices, setUserChoices] = useState([])
const [otherChoiceAnswer, setOtherChoiceAnswer] = useState(getUserAnswer())
const [errors, setErrors] = useState()

const multiHelpText = props.question.multiple_choice ? <div className="poll__help-text">{translated.multiple}</div> : null
const questionHelpText = props.question.help_text ? <div className="poll__help-text">{props.question.help_text}</div> : null
const userAllowedVote = props.question.authenticated || props.allowUnregisteredUsers

useEffect(() => {
setUserChoices(props.question.userChoices || [])
setErrors(props.errors)
}, [props.question.userChoices, props.errors])

const findOtherChoice = () => {
return props.question.choices.find(c => c.is_other_choice)
}

const handleSingleChange = (event, isOther) => {
const choiceId = parseInt(event.target.value)
setUserChoices([choiceId])
props.onSingleChange(props.question.id, choiceId)
if (!isOther) {
setOtherChoiceAnswer('')
props.onOtherChange(props.question.id, '', findOtherChoice())
}
}

const handleMultiChange = (event, isOther) => {
const choiceId = parseInt(event.target.value)
const newChoices = userChoices.includes(choiceId)
? userChoices.filter(id => id !== choiceId)
: [...userChoices, choiceId]

setUserChoices(newChoices)
props.onMultiChange(props.question.id, choiceId)

if (!newChoices.includes(findOtherChoice()?.id)) {
setOtherChoiceAnswer('')
props.onOtherChange(props.question.id, '', findOtherChoice())
}
}

const handleOtherChange = (event) => {
const otherAnswer = event.target.value
setOtherChoiceAnswer(otherAnswer)
props.onOtherChange(props.question.id, otherAnswer)
}

return (
<div className="poll poll--question">
<h3>{props.question.label}</h3>
{questionHelpText}
{multiHelpText}
<div className="poll__rows">
{props.question.choices.map((choice) => {
const checked = userChoices.indexOf(choice.id) !== -1
return (
<ChoiceRow
key={choice.id}
choice={choice}
checked={checked}
onInputChange={props.question.multiple_choice ? handleMultiChange : handleSingleChange}
type={props.question.multiple_choice ? 'checkbox' : 'radio'}
disabled={!userAllowedVote || props.question.isReadOnly}
otherChoiceAnswer={otherChoiceAnswer}
onOtherChange={handleOtherChange}
isAuthenticated={props.question.authenticated}
isReadOnly={props.question.isReadOnly}
errors={errors}
/>
)
})}
</div>
</div>
)
}
61 changes: 30 additions & 31 deletions adhocracy4/polls/static/PollDetail/PollOpenQuestion.jsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,47 @@
import React, { useState } from 'react'
import { CharCounter } from './CharCounter'

export const PollOpenQuestion = (props) => {
// | Function to define state
import { TextareaWithCounter } from './TextareaWithCounter'

export const PollOpenQuestion = ({
question,
allowUnregisteredUsers,
onOpenChange,
errors
}) => {
const getUserOpenAnswer = () => {
const userAnswerId = props.question.userAnswer
const userAnswer = props.question.answers.find(oa => oa.id === userAnswerId)
return props.question.open_answer
? props.question.open_answer
: ((userAnswerId && userAnswer)
? userAnswer.answer
: ''
)
const userAnswerId = question.userAnswer
const userAnswer = question.answers.find((oa) => oa.id === userAnswerId)
return question.open_answer
? question.open_answer
: userAnswerId && userAnswer
? userAnswer.answer
: ''
}

const [userAnswer, setUserAnswer] = useState(getUserOpenAnswer())
const questionHelpText = props.question.help_text ? <div className="poll__help-text">{props.question.help_text}</div> : null
const maxlength = 750
const userAllowedVote = props.question.authenticated || props.allowUnregisteredUsers
const questionHelpText = question.help_text
? (
<div className="poll__help-text">{question.help_text}</div>
)
: null
const userAllowedVote = question.authenticated || allowUnregisteredUsers

const handleOpenChange = (event) => {
setUserAnswer(event.target.value)
props.onOpenChange(props.question.id, event.target.value)
onOpenChange(question.id, event.target.value)
}

return (
<div className="poll poll--question">
<h2>{props.question.label}</h2>
<h3>{question.label}</h3>
{questionHelpText}
<div className="poll__rows">
<textarea
className="form-control"
name="question"
id={'id_choice-' + props.question.id + '-open'}
value={userAnswer}
disabled={!userAllowedVote || props.question.isReadOnly}
onChange={(event) => { handleOpenChange(event) }}
maxLength={maxlength}
/>
<div className="poll__char-counter">
<CharCounter value={userAnswer} max={maxlength} />
</div>
</div>
<TextareaWithCounter
value={userAnswer}
onChange={handleOpenChange}
disabled={!userAllowedVote || question.isReadOnly}
error={errors}
id={question.id}
questionType="open"
/>
</div>
)
}
Loading

0 comments on commit bb6708b

Please sign in to comment.