-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
polls: refactor PollDetail components
- Loading branch information
Showing
16 changed files
with
467 additions
and
1,038 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
) | ||
} |
Oops, something went wrong.