Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[#8443] polls: refactor PollQuestion.jsx and add TextareaWithCounter.jsx #1687

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
hom3mad3 marked this conversation as resolved.
Show resolved Hide resolved
{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