Skip to content

Commit

Permalink
Add copySet action to interview
Browse files Browse the repository at this point in the history
  • Loading branch information
jochenklar committed Aug 22, 2024
1 parent 5c65410 commit 1e97f24
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 24 deletions.
4 changes: 4 additions & 0 deletions rdmo/projects/assets/js/interview/actions/actionTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
88 changes: 83 additions & 5 deletions rdmo/projects/assets/js/interview/actions/interviewActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand All @@ -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)
}
})
}
Expand Down Expand Up @@ -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}
}
17 changes: 12 additions & 5 deletions rdmo/projects/assets/js/interview/components/main/page/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -36,6 +41,7 @@ const Page = ({ config, templates, overview, page, sets, values, fetchPage,
createSet={createSet}
updateSet={updateSet}
deleteSet={deleteSet}
copySet={copySet}
/>
<div className="row">
{
Expand Down Expand Up @@ -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
43 changes: 35 additions & 8 deletions rdmo/projects/assets/js/interview/components/main/page/PageHead.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => (
Expand All @@ -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()
Expand Down Expand Up @@ -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 && (
<div className="interview-page-tabs">
<Html html={templates.project_interview_page_help} />
Expand Down Expand Up @@ -82,12 +94,13 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea
</li>
</ul>
<div className="interview-page-tabs-buttons">
{
page.attribute && (
<button className="btn-link fa fa-pencil" title={gettext('Edit tab')} onClick={openUpdateModal} />
)
}
<button className="btn-link fa fa-trash" title={gettext('Remove tab')} onClick={openDeleteModal} />
{
page.attribute && (
<button className="btn-link fa fa-pencil" title={gettext('Edit tab')} onClick={openUpdateModal} />
)
}
<button className="btn-link fa fa-copy" title={gettext('Copy tab')} onClick={openCopyModal} />
<button className="btn-link fa fa-trash" title={gettext('Remove tab')} onClick={openDeleteModal} />
</div>
</>
) : (
Expand All @@ -99,15 +112,28 @@ const PageHead = ({ templates, page, sets, values, currentSet, activateSet, crea

<PageHeadFormModal
title={capitalize(page.verbose_name)}
submitText={gettext('Create')}
submitColor="success"
show={showCreateModal}
initial={isNil(page.attribute) ? null : ''}
onClose={closeCreateModal}
onSubmit={handleCreateSet}
/>
<PageHeadFormModal
title={capitalize(page.verbose_name)}
submitText={gettext('Copy')}
submitColor="info"
show={showCopyModal}
initial={isNil(page.attribute) ? null : ''}
onClose={closeCopyModal}
onSubmit={handleCopySet}
/>
{
currentSetValue && (
<PageHeadFormModal
title={capitalize(page.verbose_name)}
submitText={gettext('update')}
submitColor="primary"
show={showUpdateModal}
initial={currentSetValue.text}
onClose={closeUpdateModal}
Expand All @@ -134,7 +160,8 @@ PageHead.propTypes = {
activateSet: PropTypes.func.isRequired,
createSet: PropTypes.func.isRequired,
updateSet: PropTypes.func.isRequired,
deleteSet: PropTypes.func.isRequired
deleteSet: PropTypes.func.isRequired,
copySet: PropTypes.func.isRequired
}

export default PageHead
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,11 @@ import Modal from 'rdmo/core/assets/js/components/Modal'
import useFocusEffect from '../../../hooks/useFocusEffect'


const PageHeadFormModal = ({ title, show, initial, onClose, onSubmit }) => {
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)) {
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions rdmo/projects/assets/js/interview/containers/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}
/>
)
}
Expand Down
10 changes: 9 additions & 1 deletion rdmo/projects/assets/js/interview/reducers/interviewReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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
}
Expand Down
6 changes: 4 additions & 2 deletions rdmo/projects/assets/js/interview/utils/set.js
Original file line number Diff line number Diff line change
@@ -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'

Expand Down Expand Up @@ -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)
Expand All @@ -40,6 +40,8 @@ const gatherSets = (values) => {
})]
}
}, [])

return sortBy(sets, ['set_prefix', 'set_index'])
}

const initSets = (sets, element, setPrefix) => {
Expand Down

0 comments on commit 1e97f24

Please sign in to comment.