Skip to content

Commit

Permalink
Merge pull request #1125 from rdmorganiser/interview-apply-to-all
Browse files Browse the repository at this point in the history
feat(interview): apply to all [2]
  • Loading branch information
jochenklar authored Jan 10, 2025
2 parents 5d3bc73 + fbe188a commit 0ddc19d
Show file tree
Hide file tree
Showing 23 changed files with 376 additions and 63 deletions.
36 changes: 31 additions & 5 deletions rdmo/projects/assets/js/interview/actions/interviewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { updateLocation } from '../utils/location'
import { updateOptions } from '../utils/options'
import { initPage } from '../utils/page'
import { gatherSets, getDescendants, initSets } from '../utils/set'
import { activateFirstValue, gatherDefaultValues, initValues } from '../utils/value'
import { activateFirstValue, gatherDefaultValues, initValues, compareValues, isEmptyValue } from '../utils/value'
import { projectId } from '../utils/meta'

import ValueFactory from '../factories/ValueFactory'
Expand Down Expand Up @@ -272,7 +272,7 @@ export function storeValue(value) {
return {type: NOOP}
} else {
return (dispatch, getState) => {
const valueIndex = getState().interview.values.map((v) => v.id).indexOf(value.id)
const valueIndex = getState().interview.values.findIndex((v) => compareValues(v, value))
const valueFile = value.file
const valueSuccess = value.success

Expand Down Expand Up @@ -363,6 +363,31 @@ export function updateValue(value, attrs, store = true) {
}
}

export function copyValue(value) {
return (dispatch, getState) => {
const sets = getState().interview.sets
const values = getState().interview.values

sets.filter((set) => (
(set.set_prefix == value.set_prefix) &&
(set.set_index != value.set_index)
)).forEach((set) => {
const sibling = values.find((v) => (
(v.attribute == value.attribute) &&
(v.set_prefix == set.set_prefix) &&
(v.set_index == set.set_index) &&
(v.collection_index == value.collection_index)
))

if (isNil(sibling)) {
dispatch(storeValue(ValueFactory.create({ ...value, set_index: set.set_index })))
} else if (isEmptyValue(sibling)) {
dispatch(storeValue(ValueFactory.update(sibling, value)))
}
})
}
}

export function deleteValue(value) {
const pendingId = `deleteValue/${value.id}`

Expand Down Expand Up @@ -453,9 +478,10 @@ export function createSet(attrs) {
if (isNil(value)) {
return createSetSuccess()
} else {
return dispatch(storeValue(value)).then((action) => {
if (action.type === STORE_VALUE_SUCCESS) {
createSetSuccess(action.value)
return dispatch(storeValue(value)).then(() => {
const storedValue = getState().interview.values.find((v) => compareValues(v, value))
if (!isNil(storedValue)) {
createSetSuccess(storedValue)
}
})
}
Expand Down
15 changes: 13 additions & 2 deletions rdmo/projects/assets/js/interview/components/main/page/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import PageButtons from './PageButtons'
import PageHead from './PageHead'

const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createValue, updateValue, deleteValue,
createValue, updateValue, deleteValue, copyValue,
activateSet, createSet, updateSet, deleteSet }) => {

const currentSetPrefix = ''
Expand Down Expand Up @@ -58,6 +58,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
/>
)
} else {
Expand All @@ -66,17 +67,26 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
key={elementIndex}
templates={templates}
question={element}
sets={sets.filter((set) => (
set.set_prefix == currentSetPrefix
))}
values={values.filter((value) => (
value.attribute == element.attribute &&
value.set_prefix == currentSetPrefix &&
value.set_index == currentSetIndex
))}
siblings={values.filter((value) => (
value.attribute == element.attribute &&
value.set_prefix == currentSetPrefix &&
value.set_index != currentSetIndex
))}
disabled={overview.read_only}
isManager={isManager}
currentSet={currentSet}
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
/>
)
}
Expand Down Expand Up @@ -104,7 +114,8 @@ Page.propTypes = {
activateSet: PropTypes.func.isRequired,
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired
deleteSet: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired
}

export default Page
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
}
}

const handleOpenCreateModal = (event) => {
event.preventDefault()
openCreateModal()
}

const handleCreateSet = (text) => {
createSet({
attribute: page.attribute,
Expand Down Expand Up @@ -71,7 +76,7 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
})
}
<li>
<a href="#" title={gettext('Add tab')} className="add-set" onClick={openCreateModal}>
<a href="" title={gettext('Add tab')} className="add-set" onClick={handleOpenCreateModal}>
<i className="fa fa-plus fa-btn"></i> {capitalize(page.verbose_name)}
</a>
</li>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import QuestionText from './QuestionText'
import QuestionWarning from './QuestionWarning'
import QuestionWidget from './QuestionWidget'

const Question = ({ templates, question, values, disabled, isManager,
currentSet, createValue, updateValue, deleteValue }) => {
const Question = ({ templates, question, sets, values, siblings, disabled, isManager,
currentSet, createValue, updateValue, deleteValue, copyValue }) => {
return checkQuestion(question, currentSet) && (
<div className={`interview-question col-md-${question.width || '12'}`}>
<QuestionOptional question={question} />
Expand All @@ -25,12 +25,15 @@ const Question = ({ templates, question, values, disabled, isManager,
<QuestionManagement question={question} isManager={isManager} />
<QuestionWidget
question={question}
sets={sets}
values={values}
siblings={siblings}
disabled={disabled}
currentSet={currentSet}
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
/>
</div>
)
Expand All @@ -39,13 +42,16 @@ const Question = ({ templates, question, values, disabled, isManager,
Question.propTypes = {
templates: PropTypes.object.isRequired,
question: PropTypes.object.isRequired,
sets: PropTypes.array.isRequired,
values: PropTypes.array.isRequired,
siblings: PropTypes.array,
disabled: PropTypes.bool.isRequired,
isManager: PropTypes.bool.isRequired,
currentSet: PropTypes.object.isRequired,
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired
}

export default Question
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const AddValue = ({ question, values, currentSet, disabled, createValue }) => {
}

return !disabled && question.is_collection && (
<button type="button" className="btn btn-success add-value-button" onClick={handleClick}>
<button type="button" className="btn btn-success btn-xs add-value-button" onClick={handleClick}>
<i className="fa fa-plus fa-btn"></i> {capitalize(question.verbose_name)}
</button>
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react'
import PropTypes from 'prop-types'

import { isEmptyValue } from '../../../utils/value'

const QuestionCopyValue = ({ question, value, siblings, copyValue }) => {
return (
question.set_collection &&
!question.is_collection &&
!isEmptyValue(value) &&
siblings.some((value) => isEmptyValue(value)) && (
<button className="btn btn-link btn-apply-to-all" onClick={() => copyValue(value, siblings)}
title={gettext('Apply this answer to all tabs where this question is empty')}>
<i className="fa fa-arrow-circle-right fa-btn"></i>
</button>
)
)
}

QuestionCopyValue.propTypes = {
question: PropTypes.object.isRequired,
value: PropTypes.object.isRequired,
siblings: PropTypes.array.isRequired,
copyValue: PropTypes.func.isRequired
}

export default QuestionCopyValue
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react'
import PropTypes from 'prop-types'
import { isEmpty } from 'lodash'

import { isEmptyValue } from '../../../utils/value'

const QuestionCopyValues = ({ question, sets, values, siblings, currentSet, copyValue }) => {
const handleCopyValues = () => {
values.forEach((value) => copyValue(value))
}

const button = question.widget_type == 'checkbox' ? (
<button className="btn btn-link btn-apply-to-all" onClick={handleCopyValues}
title={gettext('Apply this answer to all tabs where this question is empty')}>
<i className="fa fa-arrow-circle-right fa-btn"></i>
</button>
) : (
<button type="button" className="btn btn-primary btn-xs copy-value-button ml-10" onClick={handleCopyValues}>
<i className="fa fa-arrow-circle-right fa-btn"></i> {gettext('Apply to all')}
</button>
)

const hasValues = values.some((value) => !isEmptyValue(value))

const hasEmptySiblings = sets.filter((set) => (
(set.set_prefix == currentSet.set_prefix) &&
(set.set_index != currentSet.set_index)
)).some((set) => {
// loop over all other sets and filter siblings accordingly
const setSiblings = siblings.filter((value) => (
(value.set_prefix == set.set_prefix) &&
(value.set_index == set.set_index)
))

// check if this set has no sibling at all (for checkboxes) or if they are empty
return isEmpty(setSiblings) || setSiblings.some((value) => isEmptyValue(value))
})

return (
question.is_collection &&
question.set_collection &&
hasValues &&
hasEmptySiblings &&
button
)
}

QuestionCopyValues.propTypes = {
question: PropTypes.object.isRequired,
sets: PropTypes.array.isRequired,
values: PropTypes.array.isRequired,
siblings: PropTypes.array,
currentSet: PropTypes.object.isRequired,
copyValue: PropTypes.func.isRequired
}

export default QuestionCopyValues
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react'
import PropTypes from 'prop-types'

const QuestionEraseValues = ({ values, disabled, deleteValue }) => {
const handleEraseValue = () => {
values.forEach((value) => deleteValue(value))
}

return !disabled && (
<button type="button" className="btn btn-link btn-erase-value" onClick={handleEraseValue}
title={gettext('Erase input')}>
<i className="fa fa-eraser fa-btn"></i>
</button>
)
}

QuestionEraseValues.propTypes = {
values: PropTypes.array.isRequired,
disabled: PropTypes.bool.isRequired,
deleteValue: PropTypes.func.isRequired
}

export default QuestionEraseValues
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import QuestionSetRemoveSet from './QuestionSetRemoveSet'

const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager,
parentSet, createSet, updateSet, deleteSet,
createValue, updateValue, deleteValue }) => {
createValue, updateValue, deleteValue, copyValue }) => {

const setPrefix = getChildPrefix(parentSet)

Expand Down Expand Up @@ -58,6 +58,7 @@ const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
/>
)
} else {
Expand All @@ -66,17 +67,26 @@ const QuestionSet = ({ templates, questionset, sets, values, disabled, isManager
key={elementIndex}
templates={templates}
question={element}
sets={sets.filter((set) => (
set.set_prefix == setPrefix
))}
values={values.filter((value) => (
value.attribute == element.attribute &&
value.set_prefix == set.set_prefix &&
value.set_index == set.set_index
))}
siblings={values.filter((value) => (
value.attribute == element.attribute &&
value.set_prefix == set.set_prefix &&
value.set_index != set.set_index
))}
disabled={disabled}
isManager={isManager}
currentSet={set}
createValue={createValue}
updateValue={updateValue}
deleteValue={deleteValue}
copyValue={copyValue}
/>
)
}
Expand Down Expand Up @@ -107,7 +117,8 @@ QuestionSet.propTypes = {
deleteSet: PropTypes.func.isRequired,
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired
deleteValue: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired
}

export default QuestionSet
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const CheckboxInput = ({ question, value, option, disabled, onCreate, onUpdate,
})
} else {
onUpdate(value, {
text: additionalInput,
option: option.id,
unit: question.unit,
value_type: question.value_type
Expand Down
Loading

0 comments on commit 0ddc19d

Please sign in to comment.