diff --git a/rdmo/projects/assets/js/interview/actions/actionTypes.js b/rdmo/projects/assets/js/interview/actions/actionTypes.js
index d8025bf3ad..b42995cdbf 100644
--- a/rdmo/projects/assets/js/interview/actions/actionTypes.js
+++ b/rdmo/projects/assets/js/interview/actions/actionTypes.js
@@ -50,3 +50,7 @@ export const CREATE_SET = 'CREATE_SET'
export const DELETE_SET_INIT = 'DELETE_SET_INIT'
export const DELETE_SET_SUCCESS = 'DELETE_SET_SUCCESS'
export const DELETE_SET_ERROR = 'DELETE_SET_ERROR'
+
+export const COPY_SET_INIT = 'COPY_SET_INIT'
+export const COPY_SET_SUCCESS = 'COPY_SET_SUCCESS'
+export const COPY_SET_ERROR = 'COPY_SET_ERROR'
diff --git a/rdmo/projects/assets/js/interview/actions/interviewActions.js b/rdmo/projects/assets/js/interview/actions/interviewActions.js
index d9ba9cf468..3053b803b7 100644
--- a/rdmo/projects/assets/js/interview/actions/interviewActions.js
+++ b/rdmo/projects/assets/js/interview/actions/interviewActions.js
@@ -47,7 +47,10 @@ import {
CREATE_SET,
DELETE_SET_INIT,
DELETE_SET_SUCCESS,
- DELETE_SET_ERROR
+ DELETE_SET_ERROR,
+ COPY_SET_INIT,
+ COPY_SET_SUCCESS,
+ COPY_SET_ERROR
} from './actionTypes'
import { updateConfig } from 'rdmo/core/assets/js/actions/configActions'
@@ -459,8 +462,8 @@ export function createSet(attrs) {
// create a value for the text if the page has an attribute
const value = isNil(attrs.attribute) ? null : ValueFactory.create(attrs)
- // create an action to be called immediately or after saving the value
- const createSetSuccess = (value) => {
+ // create a callback function to be called immediately or after saving the value
+ const createSetCallback = (value) => {
dispatch(activateSet(set))
const state = getState().interview
@@ -476,12 +479,12 @@ export function createSet(attrs) {
}
if (isNil(value)) {
- return createSetSuccess()
+ return createSetCallback()
} else {
return dispatch(storeValue(value)).then(() => {
const storedValue = getState().interview.values.find((v) => compareValues(v, value))
if (!isNil(storedValue)) {
- createSetSuccess(storedValue)
+ createSetCallback(storedValue)
}
})
}
@@ -559,3 +562,78 @@ export function deleteSetSuccess(set) {
export function deleteSetError(errors) {
return {type: DELETE_SET_ERROR, errors}
}
+
+export function copySet(currentSet, attrs) {
+ const pendingId = `copySet/${currentSet.set_prefix}/${currentSet.set_index}`
+
+ return (dispatch, getState) => {
+ dispatch(addToPending(pendingId))
+ dispatch(copySetInit())
+
+ // create a new set
+ const set = SetFactory.create(attrs)
+
+ // create a value for the text if the page has an attribute
+ const value = isNil(attrs.attribute) ? null : ValueFactory.create(attrs)
+
+ // create a callback function to be called immediately or after saving the value
+ const copySetCallback = (setValues) => {
+ dispatch(activateSet(set))
+
+ const state = getState().interview
+
+ const page = state.page
+ const values = [...state.values, ...setValues]
+ const sets = gatherSets(values)
+
+ initSets(sets, page)
+ initValues(sets, values, page)
+
+ return dispatch({type: COPY_SET_SUCCESS, values, sets})
+ }
+
+ if (isNil(value)) {
+ // gather all values for the currentSet and it's descendants
+ const currentValues = getDescendants(getState().interview.values, currentSet)
+
+ // store each value in currentSet with the new set_index
+ return Promise.all(
+ currentValues.filter((currentValue) => !isEmptyValue(currentValue)).map((currentValue) => {
+ const value = {...currentValue}
+ const setPrefixLength = set.set_prefix.split('|').length
+
+ if (value.set_prefix == set.set_prefix) {
+ value.set_index == set.set_index
+ } else {
+ value.set_prefix = value.set_prefix.split('|').reduce((acc, cur, idx) => {
+ return [...acc, (idx == setPrefixLength - 1) ? set.set_index : cur]
+ }, []).join('|')
+ }
+
+ delete value.id
+ return ValueApi.storeValue(projectId, value)
+ })
+ ).then((values) => {
+ dispatch(removeFromPending(pendingId))
+ dispatch(copySetCallback(values))
+ }).catch((errors) => {
+ dispatch(removeFromPending(pendingId))
+ dispatch(copySetError(errors))
+ })
+ } else {
+ console.log(value)
+ }
+ }
+}
+
+export function copySetInit() {
+ return {type: COPY_SET_INIT}
+}
+
+export function copySetSuccess(values, sets) {
+ return {type: COPY_SET_SUCCESS, values, sets}
+}
+
+export function copySetError(errors) {
+ return {type: COPY_SET_ERROR, errors}
+}
diff --git a/rdmo/projects/assets/js/interview/components/main/page/Page.js b/rdmo/projects/assets/js/interview/components/main/page/Page.js
index dcab494514..f55861894b 100644
--- a/rdmo/projects/assets/js/interview/components/main/page/Page.js
+++ b/rdmo/projects/assets/js/interview/components/main/page/Page.js
@@ -13,12 +13,17 @@ import PageHead from './PageHead'
const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createValue, updateValue, deleteValue, copyValue,
- activateSet, createSet, updateSet, deleteSet }) => {
+ activateSet, createSet, updateSet, deleteSet, copySet }) => {
const currentSetPrefix = ''
- const currentSetIndex = page.is_collection ? get(config, 'page.currentSetIndex', 0) : 0
- const currentSet = sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == currentSetIndex)) ||
- sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == 0)) // sanity check
+ let currentSetIndex = page.is_collection ? get(config, 'page.currentSetIndex', 0) : 0
+ let currentSet = sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == currentSetIndex))
+
+ // sanity check
+ if (isNil(currentSet)) {
+ currentSetIndex = 0
+ currentSet = sets.find((set) => (set.set_prefix == currentSetPrefix && set.set_index == 0))
+ }
const isManager = (overview.is_superuser || overview.is_editor || overview.is_reviewer)
@@ -36,6 +41,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createSet={createSet}
updateSet={updateSet}
deleteSet={deleteSet}
+ copySet={copySet}
/>
{
@@ -108,11 +114,12 @@ Page.propTypes = {
createValue: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
deleteValue: PropTypes.func.isRequired,
+ copyValue: PropTypes.func.isRequired,
activateSet: PropTypes.func.isRequired,
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired,
- copyValue: PropTypes.func.isRequired
+ copySet: PropTypes.func.isRequired
}
export default Page
diff --git a/rdmo/projects/assets/js/interview/components/main/page/PageHead.js b/rdmo/projects/assets/js/interview/components/main/page/PageHead.js
index 87371c5ecf..3087be1fc1 100644
--- a/rdmo/projects/assets/js/interview/components/main/page/PageHead.js
+++ b/rdmo/projects/assets/js/interview/components/main/page/PageHead.js
@@ -9,7 +9,8 @@ import useModal from 'rdmo/core/assets/js/hooks/useModal'
import PageHeadDeleteModal from './PageHeadDeleteModal'
import PageHeadFormModal from './PageHeadFormModal'
-const PageHead = ({ templates, page, sets, values, currentSet, activateSet, createSet, updateSet, deleteSet }) => {
+const PageHead = ({ templates, page, sets, values, currentSet,
+ activateSet, createSet, updateSet, deleteSet, copySet }) => {
const currentSetValue = isNil(currentSet) ? null : (
values.find((value) => (
@@ -20,6 +21,7 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
const [showCreateModal, openCreateModal, closeCreateModal] = useModal()
const [showUpdateModal, openUpdateModal, closeUpdateModal] = useModal()
const [showDeleteModal, openDeleteModal, closeDeleteModal] = useModal()
+ const [showCopyModal, openCopyModal, closeCopyModal] = useModal()
const handleActivateSet = (event, set) => {
event.preventDefault()
@@ -53,6 +55,16 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
closeDeleteModal()
}
+ const handleCopySet = (text) => {
+ copySet(currentSet, {
+ attribute: page.attribute,
+ set_index: last(sets) ? last(sets).set_index + 1 : 0,
+ set_collection: page.is_collection,
+ text
+ })
+ closeCopyModal()
+ }
+
return page.is_collection && (
@@ -82,12 +94,13 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
- {
- page.attribute && (
-
- )
- }
-
+ {
+ page.attribute && (
+
+ )
+ }
+
+
>
) : (
@@ -99,15 +112,28 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
+
{
currentSetValue && (
{
+const PageHeadFormModal = ({ title, submitText, submitColor, show, initial, onClose, onSubmit }) => {
const ref = useRef(null)
const [inputValue, setInputValue] = useState('')
const [hasError, setHasError] = useState(false)
- const submitText = isEmpty(initial) ? gettext('Create') : gettext('Update')
- const submitColor = isEmpty(initial) ? 'success' : 'primary'
const handleSubmit = () => {
if (isEmpty(inputValue) && !isNil(initial)) {
@@ -77,6 +75,8 @@ const PageHeadFormModal = ({ title, show, initial, onClose, onSubmit }) => {
PageHeadFormModal.propTypes = {
title: PropTypes.string.isRequired,
+ submitText: PropTypes.string.isRequired,
+ submitColor: PropTypes.string.isRequired,
show: PropTypes.bool.isRequired,
initial: PropTypes.string,
onClose: PropTypes.func.isRequired,
diff --git a/rdmo/projects/assets/js/interview/containers/Main.js b/rdmo/projects/assets/js/interview/containers/Main.js
index a8f6189fe2..be0b98f316 100644
--- a/rdmo/projects/assets/js/interview/containers/Main.js
+++ b/rdmo/projects/assets/js/interview/containers/Main.js
@@ -54,6 +54,7 @@ const Main = ({ config, settings, templates, user, project, interview, configAct
updateSet={interviewActions.updateSet}
deleteSet={interviewActions.deleteSet}
copyValue={interviewActions.copyValue}
+ copySet={interviewActions.copySet}
/>
)
}
diff --git a/rdmo/projects/assets/js/interview/reducers/interviewReducer.js b/rdmo/projects/assets/js/interview/reducers/interviewReducer.js
index 39ac156bc2..d62bcd60cf 100644
--- a/rdmo/projects/assets/js/interview/reducers/interviewReducer.js
+++ b/rdmo/projects/assets/js/interview/reducers/interviewReducer.js
@@ -25,7 +25,10 @@ import {
CREATE_SET,
DELETE_SET_INIT,
DELETE_SET_SUCCESS,
- DELETE_SET_ERROR
+ DELETE_SET_ERROR,
+ COPY_SET_INIT,
+ COPY_SET_SUCCESS,
+ COPY_SET_ERROR
} from '../actions/actionTypes'
const initialState = {
@@ -76,6 +79,8 @@ export default function interviewReducer(state = initialState, action) {
values: state.values.filter((value) => !action.values.includes(value)),
sets: state.sets.filter((set) => !action.sets.includes(set))
}
+ case COPY_SET_SUCCESS:
+ return { ...state, values: action.values, sets: action.sets }
case FETCH_PAGE_INIT:
case FETCH_NAVIGATION_INIT:
case FETCH_OPTIONS_INIT:
@@ -97,6 +102,7 @@ export default function interviewReducer(state = initialState, action) {
))
}
case DELETE_SET_INIT:
+ case COPY_SET_INIT:
return { ...state, errors: [] }
case FETCH_PAGE_ERROR:
case FETCH_NAVIGATION_ERROR:
@@ -117,6 +123,8 @@ export default function interviewReducer(state = initialState, action) {
case DELETE_VALUE_ERROR:
case DELETE_SET_ERROR:
return { ...state, errors: [...state.errors, { actionType: action.type, ...action.error }] }
+ case COPY_SET_ERROR:
+ return { ...state, errors: [...state.errors, { actionType: action.type, ...action.error }] }
default:
return state
}
diff --git a/rdmo/projects/assets/js/interview/utils/set.js b/rdmo/projects/assets/js/interview/utils/set.js
index 282fa90b21..9a576875f9 100644
--- a/rdmo/projects/assets/js/interview/utils/set.js
+++ b/rdmo/projects/assets/js/interview/utils/set.js
@@ -1,4 +1,4 @@
-import { isEmpty, isNil, toNumber, toString, last } from 'lodash'
+import { isEmpty, isNil, toNumber, toString, last, sortBy } from 'lodash'
import SetFactory from '../factories/SetFactory'
@@ -27,7 +27,7 @@ const getDescendants = (items, set) => {
}
const gatherSets = (values) => {
- return values.reduce((sets, value) => {
+ const sets = values.reduce((sets, value) => {
if (sets.find((set) => (
(set.set_prefix === value.set_prefix) &&
(set.set_index === value.set_index)
@@ -40,6 +40,8 @@ const gatherSets = (values) => {
})]
}
}, [])
+
+ return sortBy(sets, ['set_prefix', 'set_index'])
}
const initSets = (sets, element, setPrefix) => {