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

feat(interview): apply to all [2] #1125

Merged
merged 11 commits into from
Jan 10, 2025
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { maxBy } from 'lodash'

import { gatherOptions } from '../../../utils/options'

import QuestionCopyValues from '../question/QuestionCopyValues'
import QuestionError from '../question/QuestionError'
import QuestionSuccess from '../question/QuestionSuccess'

import CheckboxInput from './CheckboxInput'

const CheckboxWidget = ({ question, values, currentSet, disabled, createValue, updateValue, deleteValue }) => {
const CheckboxWidget = ({ question, sets, values, siblings, currentSet, disabled,
createValue, updateValue, deleteValue, copyValue }) => {

const handleCreateValue = (option, additionalInput) => {
const lastValue = maxBy(values, (v) => v.collection_index)
Expand Down Expand Up @@ -69,6 +71,14 @@ const CheckboxWidget = ({ question, values, currentSet, disabled, createValue, u
</div>
<div className="buttons">
<QuestionSuccess value={{ success }} />
<QuestionCopyValues
question={question}
sets={sets}
values={values}
siblings={siblings}
currentSet={currentSet}
copyValue={copyValue}
/>
</div>
</div>
</div>
Expand All @@ -79,12 +89,15 @@ const CheckboxWidget = ({ question, values, currentSet, disabled, createValue, u

CheckboxWidget.propTypes = {
question: PropTypes.object.isRequired,
sets: PropTypes.array.isRequired,
values: PropTypes.array.isRequired,
siblings: PropTypes.array,
disabled: PropTypes.bool,
currentSet: PropTypes.object.isRequired,
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired
deleteValue: PropTypes.func.isRequired,
copyValue: PropTypes.func.isRequired
}

export default CheckboxWidget
Loading
Loading