From 4027bdd628e5aadf9299c8b34fd2562045fe819d Mon Sep 17 00:00:00 2001 From: paschalidi Date: Tue, 20 Apr 2021 13:40:22 +0300 Subject: [PATCH] fix: DHIS2-10206 displays dialog when data has entered and user is exiting the page (#1592) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: Update README * refactor: exports shared steps & fixes all tests (#1483) * chore: only the fail true is on * chore: fixes existing tests * chore: reduces time * chore: drops time more * chore: adds 1000 ms * chore: bring time to 20000 * chore: fixes new page * chore: bring time down to 7000 * chore: time is 12000 * chore: timer is 14000 * chore: time equals 12000 * feat: extracts the functions * chore: drops time to 6000 * chore: up to 12000 * chore: 15000 * chore: rebase and splits the shared steps * chore: addmission date and missing steps * chore: random and * chore: fixes the tests * chore: typo * fix: program error bug (#1532) * chore: solves the bug * chore: covers with tests * chore: adds missing test cases * chore: removes the dhis2-capture- prefix (#1410) * fix: the bug (#1536) * chore(release): cut 1.15.1 [skip ci] * the bug ([#1536](https://github.com/dhis2/capture-app/issues/1536)) ([fd2616f](https://github.com/dhis2/capture-app/commit/fd2616f92b768249601be6e020527e9f89cd19ef)) * chore: forward-ports the last two PRs (#1588) * fix: DHIS2-10738 pressing the back button after a fallback crashes app (#1581) * chore: sets to false * chore: covers bug case * fix: the bug (#1580) * chore: fix the rerun (#1589) * chore: reverts * chore: saves * chore: fixes the bug * chore: removes unused onCleanup * chore: improves * chore: cleans up and lints * chore: fixes * chore: imports from correct action * chore: cleans up also on unmount * chore: adds curr state * chore: cleans up * fix: the crash by removing only the correct entries on cleanUp * chore: fixes the problem with the window Co-authored-by: Joakim Storløkken Melseth Co-authored-by: @dhis2-bot Co-authored-by: Joakim Storløkken Melseth --- .../D2Form/asyncHandlerHOC/actions.js | 5 -- .../asyncHandlerHOC/withAsyncHandler.js | 17 +---- .../NewEventNewRelationshipWrapper.actions.js | 4 +- ...ewEventNewRelationshipWrapper.component.js | 5 +- ...ewEventNewRelationshipWrapper.container.js | 4 +- .../SingleEventRegistrationEntry.container.js | 2 +- .../DataEntry/CancelButton.container.js | 2 +- .../DataEntry/common/dataEntryHasChanges.js | 10 ++- .../DataEntry/withBrowserBackWarning.js | 2 +- .../Dialogs/ConfirmDialog.component.js | 14 ++-- .../LockedSelector.component.js | 53 ++++++++------ .../LockedSelector.container.js | 2 + .../components/Pages/New/NewPage.actions.js | 9 ++- .../components/Pages/New/NewPage.component.js | 19 ++++- .../components/Pages/New/NewPage.container.js | 12 ++++ .../components/Pages/New/NewPage.types.js | 1 + .../RegistrationDataEntry.container.js | 10 ++- .../ViewEvent/ViewEventPage.container.js | 10 +-- .../dataEntry.reducerDescription.js | 72 ++++--------------- .../descriptions/form.reducerDescription.js | 14 ++++ .../FormBuilder/FormBuilder.component.js | 9 --- 21 files changed, 143 insertions(+), 133 deletions(-) diff --git a/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/actions.js b/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/actions.js index 50351782f4..ce47d6b56b 100644 --- a/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/actions.js +++ b/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/actions.js @@ -5,7 +5,6 @@ import { actionCreator } from '../../../actions/actions.utils'; export const actionTypes = { FIELDS_VALIDATED: 'FieldsValidated', FIELD_IS_VALIDATING: 'FieldIsValidating', - CLEAN_UP_FORM_BUILDER: 'CleanUpFormBuilder', START_UPDATE_FIELD_ASYNC: 'StartUpdateFieldAsync', UPDATE_FIELD_FROM_ASYNC: 'UpdateFieldFromAsync', ASYNC_UPDATE_FIELD_FAILED: 'AsyncUpdateFieldFailed', @@ -39,10 +38,6 @@ export const fieldsValidated = ( actionCreator(actionTypes.FIELDS_VALIDATED)( { fieldsUI, formBuilderId, formId, validatingUids }); -export const cleanUpFormBuilder = (remainingUids: Array, formId: string) => - actionCreator(actionTypes.CLEAN_UP_FORM_BUILDER)( - { remainingUids, formId }); - export const startUpdateFieldAsync = ( elementId: string, fieldLabel: string, diff --git a/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/withAsyncHandler.js b/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/withAsyncHandler.js index 987ee192ed..03563b226d 100644 --- a/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/withAsyncHandler.js +++ b/src/core_modules/capture-core/components/D2Form/asyncHandlerHOC/withAsyncHandler.js @@ -2,13 +2,12 @@ import * as React from 'react'; import { connect } from 'react-redux'; import uuid from 'uuid/v4'; -import { fieldIsValidating, fieldsValidated, cleanUpFormBuilder, startUpdateFieldAsync } from './actions'; +import { fieldIsValidating, fieldsValidated, startUpdateFieldAsync } from './actions'; type Props = { id: string, onIsValidating: Function, onFieldsValidated: Function, - onCleanUp: Function, onUpdateFieldAsyncInner: Function, onUpdateFieldAsync: ?Function, }; @@ -28,12 +27,6 @@ const getAsyncHandler = (InnerComponent: React.ComponentType) => this.props.onFieldsValidated(...args, id); } - // $FlowFixMe[missing-annot] automated comment - handleCleanUp = (...args) => { - const { id } = this.props; - this.props.onCleanUp(...args, id); - } - // $FlowFixMe[missing-annot] automated comment handleUpdateFieldAsyncInner = (...args) => { const { onUpdateFieldAsyncInner, onUpdateFieldAsync } = this.props; @@ -44,7 +37,6 @@ const getAsyncHandler = (InnerComponent: React.ComponentType) => const { onIsValidating, onFieldsValidated, - onCleanUp, onUpdateFieldAsyncInner, onUpdateFieldAsync, ...passOnProps } = this.props; @@ -54,7 +46,6 @@ const getAsyncHandler = (InnerComponent: React.ComponentType) => onIsValidating={this.handleIsValidating} onFieldsValidated={this.handleFieldsValidated} onUpdateFieldAsync={this.handleUpdateFieldAsyncInner} - onCleanUp={this.handleCleanUp} {...passOnProps} /> ); @@ -84,12 +75,6 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ const action = fieldsValidated(fieldsUI, formBuilderId, formId, validatingUids); dispatch(action); }, - onCleanUp: ( - remainingUids: Array, - formId: string, - ) => { - dispatch(cleanUpFormBuilder(remainingUids, formId)); - }, onUpdateFieldAsyncInner: ( fieldId: string, fieldLabel: string, diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.actions.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.actions.js index a972386f14..19fd4eeb3d 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.actions.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.actions.js @@ -11,8 +11,8 @@ export const batchActionTypes = { ADD_RELATIONSHIP_BATCH: 'AddNewEventRelationshipBatch', }; -export const newEventCancelNewRelationship = () => - actionCreator(actionTypes.NEW_EVENT_CANCEL_NEW_RELATIONSHIP)({}); +export const newEventCancelNewRelationship = (dataEntryId: string) => + actionCreator(actionTypes.NEW_EVENT_CANCEL_NEW_RELATIONSHIP)({ dataEntryId }); export const addNewEventRelationship = (relationshipType: { id: string, name: string }, entity: Object, entityType: string) => actionCreator(actionTypes.ADD_NEW_EVENT_RELATIONSHIP)({ relationshipType, entity, entityType }); diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js index baf3773336..2cc08107db 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.component.js @@ -42,7 +42,8 @@ const getStyles = theme => ({ }); type Props = { - onCancel: () => void, + onCancel: (dataEntryid: string) => void, + dataEntryKey: string, classes: { headerContainer: string, header: string, @@ -122,7 +123,7 @@ class NewEventNewRelationshipWrapper extends React.Component { text={i18n.t('Leaving this page will discard the selections you made for a new relationship')} confirmText={i18n.t('Yes, discard')} cancelText={i18n.t('No, stay here')} - onConfirm={this.props.onCancel} + onConfirm={() => this.props.onCancel('relationship')} open={!!this.state.discardDialogOpen} onCancel={this.handleCancelDiscard} /> diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.container.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.container.js index e573fa1aa1..4d4870f1ec 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.container.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/NewRelationshipWrapper/NewEventNewRelationshipWrapper.container.js @@ -26,8 +26,8 @@ const makeMapStateToProps = () => { }; const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ - onCancel: () => { - dispatch(newEventCancelNewRelationship()); + onCancel: (dataEntryId: string) => { + dispatch(newEventCancelNewRelationship(dataEntryId)); }, onAddRelationship: (relationshipType: { id: string, name: string}, entity: Object, entityType: string) => { dispatch(addNewEventRelationship(relationshipType, entity, entityType)); diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js index 12c3a60048..28ce0ac4dc 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/SingleEventRegistrationEntry.container.js @@ -5,7 +5,7 @@ import i18n from '@dhis2/d2-i18n'; import { compose } from 'redux'; import { SingleEventRegistrationEntryComponent } from './SingleEventRegistrationEntry.component'; import withBrowserBackWarning from '../../../HOC/withBrowserBackWarning'; -import dataEntryHasChanges from '../../DataEntry/common/dataEntryHasChanges'; +import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges'; import { makeEventAccessSelector } from './SingleEventRegistrationEntry.selectors'; import { withLoadingIndicator } from '../../../HOC'; diff --git a/src/core_modules/capture-core/components/DataEntry/CancelButton.container.js b/src/core_modules/capture-core/components/DataEntry/CancelButton.container.js index 6f2bb964a8..bb8b6f084d 100644 --- a/src/core_modules/capture-core/components/DataEntry/CancelButton.container.js +++ b/src/core_modules/capture-core/components/DataEntry/CancelButton.container.js @@ -2,7 +2,7 @@ import { connect } from 'react-redux'; import CancelButton from './CancelButton.component'; import getDataEntryKey from './common/getDataEntryKey'; -import dataEntryHasChanges from './common/dataEntryHasChanges'; +import { dataEntryHasChanges } from './common/dataEntryHasChanges'; const mapStateToProps = (state: ReduxState, props: {id: string}) => { const itemId = state.dataEntries && state.dataEntries[props.id] && state.dataEntries[props.id].itemId; diff --git a/src/core_modules/capture-core/components/DataEntry/common/dataEntryHasChanges.js b/src/core_modules/capture-core/components/DataEntry/common/dataEntryHasChanges.js index 3aa1fc16ca..a2ff4205db 100644 --- a/src/core_modules/capture-core/components/DataEntry/common/dataEntryHasChanges.js +++ b/src/core_modules/capture-core/components/DataEntry/common/dataEntryHasChanges.js @@ -1,9 +1,15 @@ // @flow -export default (state: ReduxState, key: string) => { +export const dataEntryHasChanges = (state: ReduxState, key: string): boolean => { const reduced = Object.keys(state.formsSectionsFieldsUI) .filter(formSectionUI => formSectionUI.startsWith(key)) - .reduce((accElementsUI, sectionKey) => [...accElementsUI, ...Object.keys(state.formsSectionsFieldsUI[sectionKey]).map(elementKey => state.formsSectionsFieldsUI[sectionKey][elementKey])], []); + .reduce((accElementsUI, sectionKey) => + [ + ...accElementsUI, + ...Object.keys(state.formsSectionsFieldsUI[sectionKey]) + .map(elementKey => state.formsSectionsFieldsUI[sectionKey][elementKey]), + ] + , []); const formIsModified = reduced.some(element => element.modified); diff --git a/src/core_modules/capture-core/components/DataEntry/withBrowserBackWarning.js b/src/core_modules/capture-core/components/DataEntry/withBrowserBackWarning.js index 33af1821df..e35633dfa7 100644 --- a/src/core_modules/capture-core/components/DataEntry/withBrowserBackWarning.js +++ b/src/core_modules/capture-core/components/DataEntry/withBrowserBackWarning.js @@ -6,7 +6,7 @@ import { withRouter } from 'react-router'; import ConfirmDialog from '../Dialogs/ConfirmDialog.component'; import getDataEntryKey from './common/getDataEntryKey'; -import getDataEntryHasChanges from './common/dataEntryHasChanges'; +import { dataEntryHasChanges as getDataEntryHasChanges } from './common/dataEntryHasChanges'; type Props = { dataEntryHasChanges: boolean, diff --git a/src/core_modules/capture-core/components/Dialogs/ConfirmDialog.component.js b/src/core_modules/capture-core/components/Dialogs/ConfirmDialog.component.js index 65534ceb30..032c3908f4 100644 --- a/src/core_modules/capture-core/components/Dialogs/ConfirmDialog.component.js +++ b/src/core_modules/capture-core/components/Dialogs/ConfirmDialog.component.js @@ -1,5 +1,6 @@ // @flow import React, { Component } from 'react'; +import { withStyles } from '@material-ui/core'; import Dialog from '@material-ui/core/Dialog'; import DialogActions from '@material-ui/core/DialogActions'; import DialogContent from '@material-ui/core/DialogContent'; @@ -17,6 +18,9 @@ type Props = { onConfirm: () => void, }; +const StyledDialog = withStyles({ root: { zIndex: 3000 } })(Dialog); +const StyledDialogActions = withStyles({ root: { margin: '8px 8px 12px 0' } })(DialogActions); + class ConfirmDialog extends Component { render() { const { @@ -30,7 +34,7 @@ class ConfirmDialog extends Component { } = this.props; return ( - @@ -40,17 +44,17 @@ class ConfirmDialog extends Component { {text} - + - - + + ); } } -export default (ConfirmDialog); +export default ConfirmDialog; diff --git a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.component.js b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.component.js index b5d93d5a57..ebe94ba518 100644 --- a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.component.js +++ b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.component.js @@ -39,6 +39,38 @@ class LockedSelectorClass extends Component { this.setState({ openStartAgainWarning: true }); } + openNewRegistrationPage = () => { + if (this.props.isUserInteractionInProgress) { + this.setState({ openStartAgainWarning: true }); + return; + } + this.props.onOpenNewEventPage(); + } + + handleOpenNewRegistrationPageWithoutProgramId = () => { + if (this.dontShowWarning()) { + this.props.onOpenNewRegistrationPageWithoutProgramId(); + return; + } + this.setState({ openStartAgainWarning: true }); + } + + handleOpenSearchPage = () => { + if (this.dontShowWarning()) { + this.props.onOpenSearchPage(); + return; + } + this.setState({ openStartAgainWarning: true }); + } + + handleOpenSearchPageWithoutProgramId = () => { + if (this.dontShowWarning()) { + this.props.onOpenSearchPageWithoutProgramId(); + return; + } + this.setState({ openStartAgainWarning: true }); + } + handleOpenOrgUnitWarning = () => { if (this.dontShowWarning()) { this.props.onResetOrgUnitId(); @@ -95,32 +127,11 @@ class LockedSelectorClass extends Component { this.handleClose(); } - openNewRegistrationPage = () => { - if (this.props.isUserInteractionInProgress) { - this.setState({ openNewEventWarning: true }); - return; - } - this.props.onOpenNewEventPage(); - } - - handleOpenNewRegistrationPageWithoutProgramId = () => { - this.props.onOpenNewRegistrationPageWithoutProgramId(); - } - handleAcceptNew = () => { this.props.onOpenNewEventPage(); this.handleClose(); } - handleOpenSearchPage = () => { - this.props.onOpenSearchPage(); - } - - handleOpenSearchPageWithoutProgramId = () => { - this.props.onOpenSearchPageWithoutProgramId(); - } - - render() { const { onSetOrgUnit, onSetProgramId, onSetCategoryOption, onResetAllCategoryOptions } = this.props; return ( diff --git a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.container.js b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.container.js index 5ad4aee23b..a90ce499ff 100644 --- a/src/core_modules/capture-core/components/LockedSelector/LockedSelector.container.js +++ b/src/core_modules/capture-core/components/LockedSelector/LockedSelector.container.js @@ -70,6 +70,7 @@ export const LockedSelector: ComponentType = customActionsOnProgramIdReset = [], customActionsOnOrgUnitIdReset = [], pageToPush = '', + isUserInteractionInProgress = false, }) => { const dispatch = useDispatch(); @@ -187,6 +188,7 @@ export const LockedSelector: ComponentType = onSetOrgUnit={dispatchOnSetOrgUnit} selectedOrgUnitId={selectedOrgUnitId} selectedProgramId={selectedProgramId} + isUserInteractionInProgress={isUserInteractionInProgress} ready={ready} /> ); diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.actions.js b/src/core_modules/capture-core/components/Pages/New/NewPage.actions.js index 92bc5c5e94..6e5d5c1102 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.actions.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.actions.js @@ -1,9 +1,10 @@ import { actionCreator } from '../../../actions/actions.utils'; export const newPageActionTypes = { - NEW_PAGE_WITHOUT_ORG_UNIT_SELECTED_VIEW: 'NewPageWithoutOrgUnitSelectedView', - NEW_PAGE_WITHOUT_PROGRAM_CATEGORY_SELECTED_VIEW: 'NewPageWithoutProgramComboSelectedView', - NEW_PAGE_DEFAULT_VIEW: 'NewPageDefaultView', + NEW_PAGE_WITHOUT_ORG_UNIT_SELECTED_VIEW: 'NewPage.WithoutOrgUnitSelectedView', + NEW_PAGE_WITHOUT_PROGRAM_CATEGORY_SELECTED_VIEW: 'NewPage.WithoutProgramComboSelectedView', + NEW_PAGE_DEFAULT_VIEW: 'NewPage.DefaultView', + CLEAN_UP_DATA_ENTRY: 'NewPage.DataEntryCleanUp', }; @@ -15,3 +16,5 @@ export const showMessageToSelectProgramCategoryOnNewPage = () => export const showDefaultViewOnNewPage = () => actionCreator(newPageActionTypes.NEW_PAGE_DEFAULT_VIEW)(); + +export const cleanUpDataEntry = dataEntryId => actionCreator(newPageActionTypes.CLEAN_UP_DATA_ENTRY)({ dataEntryId }); diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.component.js b/src/core_modules/capture-core/components/Pages/New/NewPage.component.js index be182ebf2c..c11c7af283 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.component.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.component.js @@ -13,6 +13,7 @@ import { useScopeInfo } from '../../../hooks/useScopeInfo'; import { RegistrationDataEntry } from './RegistrationDataEntry'; import { NoWriteAccessMessage } from '../../NoWriteAccessMessage'; import { IncompleteSelectionsMessage } from '../../IncompleteSelectionsMessage'; +import { cleanUpDataEntry } from './NewPage.actions'; const getStyles = () => ({ container: { @@ -21,6 +22,8 @@ const getStyles = () => ({ }); export const NEW_TEI_DATA_ENTRY_ID = 'newPageDataEntryId'; +export const NEW_SINGLE_EVENT_DATA_ENTRY_ID = 'singleEvent'; +export const NEW_RELATIONSHIP_EVENT_DATA_ENTRY_ID = 'relationship'; const NewPagePlain = ({ showMessageToSelectOrgUnitOnNewPage, @@ -34,6 +37,7 @@ const NewPagePlain = ({ programCategorySelectionIncomplete, missingCategoriesInProgramSelection, orgUnitSelectionIncomplete, + isUserInteractionInProgress, }: Props) => { const { scopeType } = useScopeInfo(currentScopeId); const [selectedScopeId, setScopeId] = useState(currentScopeId); @@ -60,7 +64,20 @@ const NewPagePlain = ({ ]); return (<> - +
{ !writeAccess ? diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.container.js b/src/core_modules/capture-core/components/Pages/New/NewPage.container.js index 219a49ae8a..6393e70cd5 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.container.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.container.js @@ -15,6 +15,7 @@ import { useCurrentOrgUnitInfo } from '../../../hooks/useCurrentOrgUnitInfo'; import { useCurrentProgramInfo } from '../../../hooks/useCurrentProgramInfo'; import { getScopeFromScopeId, TrackerProgram, TrackedEntityType } from '../../../metaData'; import { useMissingCategoriesInProgramSelection } from '../../../hooks/useMissingCategoriesInProgramSelection'; +import { dataEntryHasChanges } from '../../DataEntry/common/dataEntryHasChanges'; const useUserWriteAccess = (scopeId) => { const scope = getScopeFromScopeId(scopeId); @@ -80,6 +81,16 @@ export const NewPage: ComponentType<{||}> = () => { }; const writeAccess = useUserWriteAccess(currentScopeId); + + const isUserInteractionInProgress: boolean = useSelector( + state => + dataEntryHasChanges(state, 'singleEvent-newEvent') + || dataEntryHasChanges(state, 'relationship-newTei') + || dataEntryHasChanges(state, 'relationship-newEvent') + || dataEntryHasChanges(state, 'newPageDataEntryId-newEnrollment') + || dataEntryHasChanges(state, 'newPageDataEntryId-newTei'), + ); + return ( = () => { newPageStatus={newPageStatus} error={error} ready={ready} + isUserInteractionInProgress={isUserInteractionInProgress} />); }; diff --git a/src/core_modules/capture-core/components/Pages/New/NewPage.types.js b/src/core_modules/capture-core/components/Pages/New/NewPage.types.js index dd976270ca..f0580d2fce 100644 --- a/src/core_modules/capture-core/components/Pages/New/NewPage.types.js +++ b/src/core_modules/capture-core/components/Pages/New/NewPage.types.js @@ -16,6 +16,7 @@ export type ContainerProps = $ReadOnly<{| writeAccess: boolean, error: boolean, ready: boolean, + isUserInteractionInProgress: boolean |} > diff --git a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js index feaffd3886..bfd859014f 100644 --- a/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js +++ b/src/core_modules/capture-core/components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.container.js @@ -1,12 +1,14 @@ // @flow import { useDispatch, useSelector } from 'react-redux'; -import React, { useCallback, type ComponentType } from 'react'; +import React, { useCallback, type ComponentType, useEffect } from 'react'; import { RegistrationDataEntryComponent } from './RegistrationDataEntry.component'; import type { OwnProps } from './RegistrationDataEntry.types'; import { startSavingNewTrackedEntityInstance, startSavingNewTrackedEntityInstanceWithEnrollment, } from './RegistrationDataEntry.actions'; +import { cleanUpDataEntry } from '../NewPage.actions'; +import { NEW_RELATIONSHIP_EVENT_DATA_ENTRY_ID, NEW_SINGLE_EVENT_DATA_ENTRY_ID, NEW_TEI_DATA_ENTRY_ID } from '../NewPage.component'; export const RegistrationDataEntry: ComponentType = ({ selectedScopeId, dataEntryId, setScopeId }) => { @@ -22,6 +24,12 @@ export const RegistrationDataEntry: ComponentType const dataEntryIsReady = useSelector(({ dataEntries }) => (!!dataEntries[dataEntryId])); + useEffect(() => () => { + dispatch(cleanUpDataEntry(NEW_TEI_DATA_ENTRY_ID)); + dispatch(cleanUpDataEntry(NEW_SINGLE_EVENT_DATA_ENTRY_ID)); + dispatch(cleanUpDataEntry(NEW_RELATIONSHIP_EVENT_DATA_ENTRY_ID)); + }, [dispatch]); + return ( { const eventDetailsSection = state.viewEventPage.eventDetailsSection || {}; const isUserInteractionInProgress = - state.currentSelections.complete && - eventDetailsSection.showEditEvent && - dataEntryHasChanges(state, 'singleEvent-editEvent'); + (state.currentSelections.complete && eventDetailsSection.showEditEvent) + ? + dataEntryHasChanges(state, 'singleEvent-editEvent') + : + false; return { error: state.activePage.viewEventLoadError && state.activePage.viewEventLoadError.error, ready: !state.activePage.lockedSelectorLoads, diff --git a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js index 2ef9ed477f..5785712894 100644 --- a/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/dataEntry.reducerDescription.js @@ -7,6 +7,17 @@ import { loadEditActionTypes, } from '../../components/DataEntry'; import getDataEntryKey from '../../components/DataEntry/common/getDataEntryKey'; +import { newPageActionTypes } from '../../components/Pages/New/NewPage.actions'; +import { newRelationshipActionTypes } from '../../components/DataEntries/SingleEventRegistrationEntry'; + +// cleans up data entries that start with dataEntryId +const cleanUp = (state, { payload: { dataEntryId } }) => { + const newState = Object.keys(state).reduce((acc, curr) => + (curr.startsWith(dataEntryId) ? { ...acc, [curr]: {} } : { ...acc, [curr]: state[curr] }), + {}); + + return newState; +}; export const dataEntriesDesc = createReducerDescription({ [loadNewActionTypes.LOAD_NEW_DATA_ENTRY]: (state, action) => { @@ -26,18 +37,6 @@ export const dataEntriesDesc = createReducerDescription({ }; return newState; }, - /* - [loadViewActionTypes.LOAD_VIEW_DATA_ENTRY]: (state, action) => { - const newState = { ...state }; - const payload = action.payload; - newState[payload.dataEntryId] = { - ...newState[payload.dataEntryId], - itemId: payload.itemId, - ...payload.extraProps, - }; - return newState; - }, - */ [actionTypes.SET_CURRENT_DATA_ENTRY]: (state, action) => { const newState = { ...state }; const payload = action.payload; @@ -69,17 +68,6 @@ export const dataEntriesUIDesc = createReducerDescription({ }; return newState; }, - /* - [loadViewActionTypes.LOAD_VIEW_DATA_ENTRY]: (state, action) => { - const newState = { ...state }; - const payload = action.payload; - const key = payload.key; - newState[key] = { - loaded: true, - }; - return newState; - }, - */ [actionTypes.SAVE_VALIDATION_FALED]: (state, action) => { const newState = { ...state }; const payload = action.payload; @@ -129,15 +117,6 @@ export const dataEntriesFieldsValueDesc = createReducerDescription({ newState[key] = payload.dataEntryValues; return newState; }, - /* - [loadViewActionTypes.LOAD_VIEW_DATA_ENTRY]: (state, action) => { - const newState = { ...state }; - const payload = action.payload; - const key = payload.key; - newState[key] = payload.dataEntryValues; - return newState; - }, - */ [actionTypes.UPDATE_FIELD]: (state, action) => { const newState = { ...state }; const payload = action.payload; @@ -148,6 +127,8 @@ export const dataEntriesFieldsValueDesc = createReducerDescription({ dataEntryValues[payload.fieldId] = payload.value; return newState; }, + [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, + [newRelationshipActionTypes.NEW_EVENT_CANCEL_NEW_RELATIONSHIP]: cleanUp, }, 'dataEntriesFieldsValue'); export const dataEntriesNotesDesc = createReducerDescription({ @@ -199,15 +180,6 @@ export const dataEntriesFieldsMetaDesc = createReducerDescription({ newState[key] = payload.dataEntryMeta; return newState; }, - /* - [loadViewActionTypes.LOAD_VIEW_DATA_ENTRY]: (state, action) => { - const newState = { ...state }; - const payload = action.payload; - const key = payload.key; - newState[key] = payload.dataEntryMeta; - return newState; - }, - */ }, 'dataEntriesFieldsMeta'); export const dataEntriesFieldsUIDesc = createReducerDescription({ @@ -239,22 +211,6 @@ export const dataEntriesFieldsUIDesc = createReducerDescription({ return newState; }, - /* - [loadViewActionTypes.LOAD_VIEW_DATA_ENTRY]: (state, action) => { - const newState = { ...state }; - const payload = action.payload; - const key = payload.key; - newState[key] = Object.keys(payload.dataEntryUI).reduce((accValuesUI, elementKey) => { - accValuesUI[elementKey] = { - ...payload.dataEntryUI[elementKey], - touched: false, - }; - return accValuesUI; - }, {}); - - return newState; - }, - */ [actionTypes.UPDATE_FIELD]: (state, action) => { const newState = { ...state }; const payload = action.payload; @@ -265,6 +221,8 @@ export const dataEntriesFieldsUIDesc = createReducerDescription({ dataEntryValuesUI[payload.fieldId] = { ...dataEntryValuesUI[payload.fieldId], ...payload.valueMeta, modified: true }; return newState; }, + [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, + [newRelationshipActionTypes.NEW_EVENT_CANCEL_NEW_RELATIONSHIP]: cleanUp, }, 'dataEntriesFieldsUI'); export const dataEntriesRelationshipsDesc = createReducerDescription({ diff --git a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js index ef5ccd8560..b9c855cc59 100644 --- a/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/form.reducerDescription.js @@ -10,16 +10,27 @@ import { actionTypes as formBuilderActionTypes } from '../../components/D2Form/f import { actionTypes as dataEntryActionTypes } from '../../components/DataEntry/actions/dataEntry.actions'; import { actionTypes as rulesEffectsActionTypes } from '../../rules/actionsCreator'; import { actionTypes as orgUnitFormFieldActionTypes } from '../../components/D2Form/field/Components/OrgUnitField/orgUnitFieldForForms.actions'; +import { newRelationshipActionTypes } from '../../components/DataEntries/SingleEventRegistrationEntry'; import getOrgUnitRootsKey from '../../components/D2Form/field/Components/OrgUnitField/getOrgUnitRootsKey'; import { set as setStoreRoots, } from '../../components/FormFields/New/Fields/OrgUnitField/orgUnitRoots.store'; +import { newPageActionTypes } from '../../components/Pages/New/NewPage.actions'; const removeFormData = (state, { payload: { formId } }) => { const remainingKeys = Object.keys(state).filter(key => !key.includes(formId)); return remainingKeys.reduce((acc, key) => ({ ...acc, [key]: state[key] }), {}); }; +// cleans up data entries that _start with_ dataEntryId +const cleanUp = (state, { payload: { dataEntryId } }) => { + const newState = Object.keys(state).reduce((acc, curr) => + (curr.startsWith(dataEntryId) ? { ...acc, [curr]: {} } : { ...acc, [curr]: state[curr] }), + {}); + + return newState; +}; + export const formsValuesDesc = createReducerDescription({ [loaderActionTypes.FORM_DATA_ADD]: (state, action) => { const newState = { ...state }; @@ -81,6 +92,8 @@ export const formsValuesDesc = createReducerDescription({ return newState; }, [loaderActionTypes.FORM_DATA_REMOVE]: removeFormData, + [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, + [newRelationshipActionTypes.NEW_EVENT_CANCEL_NEW_RELATIONSHIP]: cleanUp, }, 'formsValues'); export const formsSectionsFieldsUIDesc = createReducerDescription({ @@ -202,6 +215,7 @@ export const formsSectionsFieldsUIDesc = createReducerDescription({ }; }, [loaderActionTypes.FORM_DATA_REMOVE]: removeFormData, + [newPageActionTypes.CLEAN_UP_DATA_ENTRY]: cleanUp, }, 'formsSectionsFieldsUI'); export const formsDesc = createReducerDescription({ diff --git a/src/core_modules/capture-ui/FormBuilder/FormBuilder.component.js b/src/core_modules/capture-ui/FormBuilder/FormBuilder.component.js index 828567b7d2..8098bf0944 100644 --- a/src/core_modules/capture-ui/FormBuilder/FormBuilder.component.js +++ b/src/core_modules/capture-ui/FormBuilder/FormBuilder.component.js @@ -71,7 +71,6 @@ type Props = { onGetContainerProps?: ?GetContainerPropsFn, onGetValidationContext: ?() => ?Object, onIsValidating: ?IsValidatingFn, - onCleanUp?: ?(remainingUids: Array) => void, loadNr: number, onPostProcessErrorMessage: (message: string, type: ?string, messageData: ?string, id: string, fieldId: string) => React.Node, }; @@ -261,8 +260,6 @@ class FormBuilder extends React.Component { if (newProps.id !== this.props.id || newProps.loadNr !== this.props.loadNr) { this.asyncUIState = FormBuilder.getAsyncUIState(this.props.fieldsUI); this.commitUpdateTriggeredForFields = {}; - const onCleanUp = newProps.onCleanUp; - onCleanUp && onCleanUp(this.getCleanUpData()); if (this.props.validateIfNoUIData) { this.validateAllFields(newProps); } @@ -272,11 +269,6 @@ class FormBuilder extends React.Component { } } - componentWillUnmount() { - const onCleanUp = this.props.onCleanUp; - onCleanUp && onCleanUp(this.getCleanUpData()); - } - getCleanUpData() { const remainingCompleteUids: Array = Object .keys(this.fieldsValidatingPromiseContainer) @@ -529,7 +521,6 @@ class FormBuilder extends React.Component { onGetContainerProps: extractOnGetContainerProps, onIsValidating, onGetValidationContext, - onCleanUp, loadNr, onPostProcessErrorMessage, ...passOnProps } = this.props;