From 03170abf7ac1bd2e4d120a77a27365ac53eab7f7 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 13 Sep 2023 15:37:55 +0300 Subject: [PATCH] feat: widget assignee --- .../WidgetAssignee/index.js | 33 +++++++ .../WidgetsForEnrollmentEditEvent.feature | 9 +- .../WidgetsForEnrollmentEditEvent/index.js | 2 + i18n/en.pot | 25 +++--- .../components/FormFields/UserField/index.js | 2 + .../EnrollmentEditEventPage.component.js | 10 +++ .../EnrollmentEditEventPage.container.js | 9 +- .../EnrollmentEditEventPage.types.js | 11 +++ .../Pages/EnrollmentEditEvent/hooks/index.js | 1 + .../hooks/useAssignedUserSaveContext.js | 27 ++++++ .../AssigneeSection.component.js | 52 ----------- .../AssigneeSection.container.js | 25 ------ .../AssigneeSection/Contents.component.js | 40 --------- .../AssigneeSection/DisplayMode.component.js | 90 ------------------- .../assigneeSection.actions.js | 27 ------ .../RightColumn/AssigneeSection/index.js | 4 - .../AssigneeSection/saveAssignee.epic.js | 37 -------- .../RightColumnWrapper.component.js | 4 +- .../ViewEventComponent/ViewEvent.component.js | 8 +- .../ViewEventComponent/ViewEvent.container.js | 10 ++- .../ViewEventComponent/viewEvent.selectors.js | 27 +++++- .../components/Pages/ViewEvent/index.js | 2 - .../useCommonEnrollmentDomainData.types.js | 1 + .../Widget/WidgetCollapsible.component.js | 1 - .../WidgetAssignee/DisplayMode.component.js | 47 ++++++++++ .../EditMode.component.js | 33 +++---- .../WidgetAssignee.component.js | 60 +++++++++++++ .../WidgetAssignee.container.js | 31 +++++++ .../WidgetAssignee/WidgetAssignee.types.js | 27 ++++++ .../WidgetAssignee/assignee.actions.js | 50 +++++++++++ .../components/WidgetAssignee/converter.js | 19 ++++ .../components/WidgetAssignee/index.js | 4 + .../types/common.types.js | 1 + .../capture-core/flow/apiTypes.js | 7 ++ .../enrollmentDomain.reducerDescription.js | 24 +++++ .../feedback.reducerDescriptionGetter.js | 3 + .../viewEvent.reducerDescription.js | 21 +++-- src/epics/trackerCapture.epics.js | 2 - 38 files changed, 460 insertions(+), 326 deletions(-) create mode 100644 cypress/integration/WidgetsForEnrollmentPages/WidgetAssignee/index.js create mode 100644 src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.component.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.container.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/Contents.component.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/DisplayMode.component.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/assigneeSection.actions.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/index.js delete mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/saveAssignee.epic.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js rename src/core_modules/capture-core/components/{Pages/ViewEvent/RightColumn/AssigneeSection => WidgetAssignee}/EditMode.component.js (54%) create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/converter.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/index.js diff --git a/cypress/integration/WidgetsForEnrollmentPages/WidgetAssignee/index.js b/cypress/integration/WidgetsForEnrollmentPages/WidgetAssignee/index.js new file mode 100644 index 0000000000..d33ce12adc --- /dev/null +++ b/cypress/integration/WidgetsForEnrollmentPages/WidgetAssignee/index.js @@ -0,0 +1,33 @@ +When('you assign the user Geetha in the view mode', () => { + cy.get('[data-test="widget-assignee"]').within(() => { + cy.get('[data-test="widget-assignee-edit"]').click(); + cy.get('[data-test="capture-ui-input"]').type('Geetha'); + cy.contains('Geetha Alwan').click(); + }); +}); + +When('you assign the user Tracker demo User in the edit mode', () => { + cy + .get('[data-test="widget-enrollment-event"]') + .find('[data-test="dhis2-uicore-button"]') + .eq(1) + .click(); + + cy.get('[data-test="widget-assignee"]').within(() => { + cy.get('[data-test="widget-assignee-edit"]').click(); + cy.get('[data-test="capture-ui-input"]').type('Tracker demo'); + cy.contains('Tracker demo User').click(); + }); +}); + +Then('the event has the user Geetha Alwan assigned', () => { + cy.get('[data-test="widget-assignee"]').within(() => { + cy.get('[data-test="widget-contents"]').contains('Geetha Alwan').should('exist'); + }); +}); + +Then('the event has the user Tracker demo User assigned', () => { + cy.get('[data-test="widget-assignee"]').within(() => { + cy.get('[data-test="widget-contents"]').contains('Tracker demo User').should('exist'); + }); +}); diff --git a/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature b/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature index 1f551735a3..ee3c746d05 100644 --- a/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature +++ b/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature @@ -99,4 +99,11 @@ Feature: The user interacts with the widgets on the enrollment edit event Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=XGLkLlOXgmE&orgUnitId=DiszpKrYNg8 Then the enrollment widget should be loaded When you click edit mode - Then list should contain the new comment: new test comment \ No newline at end of file + Then list should contain the new comment: new test comment + + Scenario: You can assign a user to a event + Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=SObENdEf76z&orgUnitId=g8upMTyEZGZ + When you assign the user Geetha in the view mode + Then the event has the user Geetha Alwan assigned + When you assign the user Tracker demo User in the edit mode + Then the event has the user Tracker demo User assigned diff --git a/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js b/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js index 656f67c564..ff80131398 100644 --- a/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js +++ b/cypress/integration/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent/index.js @@ -2,3 +2,5 @@ import '../sharedSteps'; import '../WidgetEnrollment'; import '../WidgetProfile'; import '../WidgetEventComment'; +import '../WidgetAssignee'; + diff --git a/i18n/en.pot b/i18n/en.pot index 52dcc1b13e..b3c7223c6d 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2023-08-22T12:04:52.436Z\n" -"PO-Revision-Date: 2023-08-22T12:04:52.436Z\n" +"POT-Creation-Date: 2023-09-08T10:24:43.998Z\n" +"PO-Revision-Date: 2023-09-08T10:24:43.998Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -911,15 +911,6 @@ msgstr "" "Leaving this page will discard any selections you made for a new " "relationship" -msgid "No one is assigned to this event" -msgstr "No one is assigned to this event" - -msgid "Assign" -msgstr "Assign" - -msgid "Event assigned to {{name}}" -msgstr "Event assigned to {{name}}" - msgid "Feedbacks" msgstr "Feedbacks" @@ -1092,6 +1083,15 @@ msgstr "To work with the selected program," msgid "open the Tracker Capture app" msgstr "open the Tracker Capture app" +msgid "Assign" +msgstr "Assign" + +msgid "Event assigned to {{name}}" +msgstr "Event assigned to {{name}}" + +msgid "No one is assigned to this event" +msgstr "No one is assigned to this event" + msgid "This program is protected" msgstr "This program is protected" @@ -1497,6 +1497,9 @@ msgstr "Error deleting the enrollment event" msgid "Error editing the event, the changes made were not saved" msgstr "Error editing the event, the changes made were not saved" +msgid "Error updating the Assignee" +msgstr "Error updating the Assignee" + msgid "Set coordinate" msgstr "Set coordinate" diff --git a/src/core_modules/capture-core/components/FormFields/UserField/index.js b/src/core_modules/capture-core/components/FormFields/UserField/index.js index 3e88ffe311..e9cb31b656 100644 --- a/src/core_modules/capture-core/components/FormFields/UserField/index.js +++ b/src/core_modules/capture-core/components/FormFields/UserField/index.js @@ -1,2 +1,4 @@ // @flow export { UserField } from './UserField.component'; +export { UserSearch } from './UserSearch.component'; +export type { User as UserFormField } from './types'; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js index b5efd930b9..82b06c5bfc 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.component.js @@ -14,6 +14,7 @@ import { WidgetFeedback } from '../../WidgetFeedback'; import { WidgetIndicator } from '../../WidgetIndicator'; import { WidgetProfile } from '../../WidgetProfile'; import { WidgetEnrollment } from '../../WidgetEnrollment'; +import { WidgetAssignee } from '../../WidgetAssignee'; import { IncompleteSelectionsMessage } from '../../IncompleteSelectionsMessage'; import { WidgetEventComment } from '../../WidgetEventComment'; import { OrgUnitFetcher } from '../../OrgUnitFetcher'; @@ -66,11 +67,14 @@ const EnrollmentEditEventPagePain = ({ eventDate, scheduleDate, eventStatus, + eventAccess, + assignee, pageStatus, onEnrollmentError, onEnrollmentSuccess, onCancelEditEvent, onHandleScheduleSave, + onGetAssignedUserSaveContext, }: PlainProps) => (
+ diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js index c46d3dbdab..0b80031199 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.container.js @@ -17,12 +17,13 @@ import { changeEventFromUrl } from '../ViewEvent/ViewEventComponent/viewEvent.ac import { buildEnrollmentsAsOptions } from '../../ScopeSelector'; import { convertDateWithTimeForView, convertValue } from '../../../converters/clientToView'; import { dataElementTypes } from '../../../metaData/DataElement'; -import { useEvent } from './hooks'; +import { useEvent, useAssignee, useAssignedUserSaveContext } from './hooks'; import type { Props } from './EnrollmentEditEventPage.types'; import { LoadingMaskForPage } from '../../LoadingMasks'; import { cleanUpDataEntry } from '../../DataEntry'; import { pageKeys } from '../../App/withAppUrlSync'; import { withErrorMessageHandler } from '../../../HOC'; +import { getProgramEventAccess } from '../../../metaData'; const getEventDate = (event) => { const eventDataConvertValue = convertDateWithTimeForView(event?.occurredAt || event?.scheduledAt); @@ -120,6 +121,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ programId, stageId, teiId, en const { currentPageMode } = useEnrollmentEditEventPageMode(event?.status); const dataEntryKey = `${dataEntryIds.ENROLLMENT_EVENT}-${currentPageMode}`; const outputEffects = useWidgetDataFromStore(dataEntryKey); + const eventAccess = getProgramEventAccess(programId, programStage?.id); const pageStatus = getPageStatus({ orgUnitId, @@ -129,6 +131,8 @@ const EnrollmentEditEventPageWithContextPlain = ({ programId, stageId, teiId, en programStage, event, }); + const assignee = useAssignee(event); + const onGetAssignedUserSaveContext = useAssignedUserSaveContext(enrollmentSite, event, eventId); return ( ); }; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js index 59ff58c190..1afe61fbec 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.types.js @@ -1,6 +1,7 @@ // @flow import type { ProgramStage } from '../../../metaData'; import type { WidgetEffects, HideWidgets } from '../common/EnrollmentOverviewDomain'; +import type { UserFormField } from '../../FormFields/UserField'; export type PlainProps = {| programStage: ?ProgramStage, @@ -25,6 +26,16 @@ export type PlainProps = {| onHandleScheduleSave: (eventData: Object) => void, pageStatus: string, eventStatus?: string, + eventAccess: {| + read: boolean, + write: boolean, + |} | null, + onGetAssignedUserSaveContext: (assignee: UserFormField) => { + eventId: string, + events: Array, + assignedUser?: ApiAssignedUser, + }, + assignee: UserFormField | null, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/index.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/index.js index 6ed8386853..c3906733fb 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/index.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/index.js @@ -1,2 +1,3 @@ // @flow export { useEvent } from './useEvent'; +export { useAssignee, useAssignedUserSaveContext } from './useAssignedUserSaveContext'; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js new file mode 100644 index 0000000000..ad654dd070 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js @@ -0,0 +1,27 @@ +// @flow +import { useCallback, useMemo } from 'react'; +import { convertServerToClient, convertClientToServer } from '../../../WidgetAssignee'; +import type { EnrollmentData } from '../../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; +import type { UserFormField } from '../../../FormFields/UserField'; + +export const useAssignee = (event?: ApiEnrollmentEvent) => + useMemo(() => convertServerToClient(event?.assignedUser), [event?.assignedUser]); + +export const useAssignedUserSaveContext = ( + enrollmentSite?: EnrollmentData, + event?: ApiEnrollmentEvent, + eventId: string, +) => + useCallback( + (newAssignee: UserFormField) => ({ + eventId, + assignedUser: event?.assignedUser, + events: enrollmentSite?.events + // $FlowFixMe[missing-annot] + ? enrollmentSite.events.map(e => ( + e.event === eventId ? { ...e, assignedUser: convertClientToServer(newAssignee) } : e + )) + : [], + }), + [enrollmentSite?.events, event?.assignedUser, eventId], + ); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.component.js deleted file mode 100644 index dde335035b..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.component.js +++ /dev/null @@ -1,52 +0,0 @@ -// @flow - -import * as React from 'react'; -import i18n from '@dhis2/d2-i18n'; -import { IconUser24 } from '@dhis2/ui'; -import { ViewEventSection } from '../../Section/ViewEventSection.component'; -import { ViewEventSectionHeader } from '../../Section/ViewEventSectionHeader.component'; -import { Contents } from './Contents.component'; -import { withLoadingIndicator } from '../../../../../HOC/withLoadingIndicator'; -import { type ProgramStage } from '../../../../../metaData'; - -const LoadingContents = withLoadingIndicator(null, props => ({ style: props.loadingIndicatorStyle }))(Contents); - -type Props = { - programStage: ProgramStage, - classes: Object, -} - -const loadingIndicatorStyle = { - height: 36, - width: 36, -}; - -export class AssigneeSectionComponent extends React.Component { - renderHeader = () => ( - - ) - - render() { - const { programStage, ...passOnProps } = this.props; - - if (!programStage.enableUserAssignment) { - return null; - } - - return ( - - {/* $FlowFixMe[cannot-spread-inexact] automated comment */} - - - ); - } -} diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.container.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.container.js deleted file mode 100644 index b22b90aa22..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.container.js +++ /dev/null @@ -1,25 +0,0 @@ -// @flow -import { connect } from 'react-redux'; -import { AssigneeSectionComponent } from './AssigneeSection.component'; -import { setAssignee } from './assigneeSection.actions'; - -const mapStateToProps = (state: ReduxState) => { - const assigneeSection = state.viewEventPage.assigneeSection || {}; - - return { - assignee: (!assigneeSection.isLoading) ? - state.viewEventPage.loadedValues.eventContainer.event.assignee : - undefined, - ready: !assigneeSection.isLoading, - }; -}; - -const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ - onSet: (user: Object) => { - dispatch(setAssignee(user)); - }, -}); - -// $FlowSuppress -// $FlowFixMe[missing-annot] automated comment -export const AssigneeSection = connect(mapStateToProps, mapDispatchToProps)(AssigneeSectionComponent); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/Contents.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/Contents.component.js deleted file mode 100644 index 5d5daf12aa..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/Contents.component.js +++ /dev/null @@ -1,40 +0,0 @@ -// @flow -import * as React from 'react'; -import { DisplayMode } from './DisplayMode.component'; -import { EditMode } from './EditMode.component'; - -type Props = { - onSet: (user: Object) => void, -}; - -export const Contents = (props: Props) => { - const { onSet, ...passOnProps } = props; - const [editMode, setEditMode] = React.useState(false); - - const handleSet = React.useCallback((user) => { - setEditMode(false); - onSet(user); - }, [onSet]); - - const handleCancelSearch = React.useCallback(() => { - setEditMode(false); - }, []); - - if (editMode) { - return ( - - ); - } - - return ( - // $FlowFixMe[cannot-spread-inexact] automated comment - { setEditMode(true); }} - {...passOnProps} - /> - ); -}; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/DisplayMode.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/DisplayMode.component.js deleted file mode 100644 index 394275ea37..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/DisplayMode.component.js +++ /dev/null @@ -1,90 +0,0 @@ -// @flow -import * as React from 'react'; -import i18n from '@dhis2/d2-i18n'; -import { withStyles, IconButton } from '@material-ui/core'; -import { IconEdit24, Button } from '@dhis2/ui'; - -const getStyles = () => ({ - container: { - display: 'flex', - alignItems: 'center', - }, - nameContainer: { - paddingRight: 5, - overflow: 'hidden', - textOverflow: 'ellipsis', - }, - iconContainer: { - width: 24, - }, - editButton: { - color: 'inherit', - }, - addIcon: { - paddingRight: 5, - }, -}); - -type User = { - id: string, - username: string, - name: string, -}; - -type Props = { - assignee: ?User, - onEdit: () => void, - classes: Object, - eventAccess: { read: boolean, write: boolean }, -}; - -const DisplayModePlain = (props: Props) => { - const { eventAccess, assignee, onEdit, classes } = props; - - if (!assignee) { - if (!eventAccess.write) { - return ( -
- {i18n.t('No one is assigned to this event')} -
- ); - } - return ( -
- -
- ); - } - - return ( -
-
- {i18n.t('Event assigned to {{name}}', { name: assignee.name })} -
-
- {eventAccess.write ? - ( - - - - ) : null} -
-
- ); -}; - -export const DisplayMode = withStyles(getStyles)(DisplayModePlain); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/assigneeSection.actions.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/assigneeSection.actions.js deleted file mode 100644 index 0bfd4a1db2..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/assigneeSection.actions.js +++ /dev/null @@ -1,27 +0,0 @@ -// @flow - -import { actionCreator } from '../../../../../actions/actions.utils'; -import { effectMethods } from '../../../../../trackerOffline'; - -export const actionTypes = { - VIEW_EVENT_ASSIGNEE_SET: 'ViewEventAssigneeSet', - VIEW_EVENT_ASSIGNEE_SAVE: 'ViewEventAssigneeSave', - VIEW_EVENT_ASSIGNEE_SAVE_COMPLETED: 'ViewEventAssigneeSaveCompleted', - VIEW_EVENT_ASSIGNEE_SAVE_FAILED: 'ViewEventAssigneeSaveFailed', -}; - -export const setAssignee = (assignee: Object) => - actionCreator(actionTypes.VIEW_EVENT_ASSIGNEE_SET)({ assignee }); - -export const saveAssignee = (eventId: string, serverData: Object, selections: Object) => - actionCreator(actionTypes.VIEW_EVENT_ASSIGNEE_SAVE)({}, { - offline: { - effect: { - url: 'tracker?async=false&importStrategy=UPDATE', - method: effectMethods.POST, - data: serverData, - }, - commit: { type: actionTypes.VIEW_EVENT_ASSIGNEE_SAVE_COMPLETED, meta: { eventId, selections } }, - rollback: { type: actionTypes.VIEW_EVENT_ASSIGNEE_SAVE_FAILED, meta: { eventId, selections } }, - }, - }); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/index.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/index.js deleted file mode 100644 index 5abb01a6c0..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/index.js +++ /dev/null @@ -1,4 +0,0 @@ -// @flow -export { actionTypes as assigneeSectionActionTypes } from './assigneeSection.actions'; -export { AssigneeSection } from './AssigneeSection.container'; -export { saveAssigneeEpic } from './saveAssignee.epic'; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/saveAssignee.epic.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/saveAssignee.epic.js deleted file mode 100644 index 99f4a3348b..0000000000 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/saveAssignee.epic.js +++ /dev/null @@ -1,37 +0,0 @@ -// @flow -import { ofType } from 'redux-observable'; -import { map } from 'rxjs/operators'; -import { actionTypes, saveAssignee } from './assigneeSection.actions'; -import { getEventProgramThrowIfNotFound } from '../../../../../metaData'; -import { convertValue as convertToServerValue } from '../../../../../converters/clientToServer'; -import { convertMainEventClientToServer } from '../../../../../events/mainConverters'; - -export const saveAssigneeEpic = (action$: InputObservable, store: ReduxStore) => - action$.pipe( - ofType(actionTypes.VIEW_EVENT_ASSIGNEE_SET), - map(() => { - const state = store.value; - const eventId = state.viewEventPage.eventId; - const eventContainer = state.viewEventPage.loadedValues.eventContainer; - const { event: clientMainValues, values: clientValues } = eventContainer; - const program = getEventProgramThrowIfNotFound(clientMainValues.programId); - const formFoundation = program.stage.stageForm; - const formServerValues = formFoundation.convertValues(clientValues, convertToServerValue); - const mainDataServerValues: Object = convertMainEventClientToServer(clientMainValues); - - const serverData = { - events: [{ - ...mainDataServerValues, - dataValues: Object - .keys(formServerValues) - .map(key => ({ - dataElement: key, - value: formServerValues[key], - })), - }], - }; - - const currentSelectionSet = state.currentSelections; - - return saveAssignee(eventId, serverData, currentSelectionSet); - })); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/RightColumnWrapper.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/RightColumnWrapper.component.js index 344c6b4698..a99536d77a 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/RightColumnWrapper.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/RightColumnWrapper.component.js @@ -8,7 +8,7 @@ import { FeedbacksSection } from './FeedbacksSection/FeedbacksSection.container' import { IndicatorsSection } from './IndicatorsSection/IndicatorsSection.container'; import { RelationshipsSection } from './RelationshipsSection/RelationshipsSection.container'; import { NotesSection } from './NotesSection/NotesSection.container'; -import { AssigneeSection } from './AssigneeSection/AssigneeSection.container'; +import { WidgetAssignee } from '../../../WidgetAssignee'; type Props = { classes: { @@ -29,7 +29,7 @@ const componentContainers = [ { id: 'WarningsSection', Component: WarningsSection }, { id: 'FeedbacksSection', Component: FeedbacksSection }, { id: 'IndicatorsSection', Component: IndicatorsSection }, - { id: 'AssigneeSection', Component: AssigneeSection }, + { id: 'WidgetAssignee', Component: WidgetAssignee }, { id: 'RelationshipsSection', Component: RelationshipsSection }, { id: 'NotesSection', Component: NotesSection }, ]; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.component.js index dbb2406dff..d24c511dbd 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.component.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.component.js @@ -8,7 +8,7 @@ import { RightColumnWrapper } from '../RightColumn/RightColumnWrapper.component' import type { ProgramStage } from '../../../../metaData'; import { DiscardDialog } from '../../../Dialogs/DiscardDialog.component'; import { defaultDialogProps } from '../../../Dialogs/DiscardDialog.constants'; - +import type { UserFormField } from '../../../FormFields/UserField'; const getStyles = (theme: Theme) => ({ container: { @@ -48,6 +48,8 @@ type Props = { header: string, showAllEvents: string, }, + assignee: UserFormField, + onGetAssignedUserSaveContext: (assignee: UserFormField) => { eventId: string, events: Array }, }; type State = { @@ -70,7 +72,7 @@ class ViewEventPlain extends Component { } render() { - const { classes, programStage, currentDataEntryKey, eventAccess } = this.props; + const { classes, programStage, currentDataEntryKey, eventAccess, assignee, onGetAssignedUserSaveContext } = this.props; return (
{ const programStageSelector = makeProgramStageSelector(); const eventAccessSelector = makeEventAccessSelector(); + const assignedUserContextSelector = makeAssignedUserContextSelector(); // $FlowFixMe[not-an-object] automated comment return (state: ReduxState) => { @@ -29,6 +33,8 @@ const makeMapStateToProps = () => { error: state.viewEventPage.loadError, currentDataEntryKey, isUserInteractionInProgress, + assignee: state.viewEventPage.loadedValues?.eventContainer.event.assignee, + onGetAssignedUserSaveContext: assignee => assignedUserContextSelector(state)(assignee), }; }; }; diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.selectors.js b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.selectors.js index 69cd29f1f6..99513c9601 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.selectors.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.selectors.js @@ -2,10 +2,14 @@ import { createSelector } from 'reselect'; import { getEventProgramEventAccess, getEventProgramThrowIfNotFound } from '../../../../metaData'; - +import { convertValue as convertToServerValue } from '../../../../converters/clientToServer'; +import { convertMainEventClientToServer } from '../../../../events/mainConverters'; +import { convertClientToServer } from '../../../WidgetAssignee'; const programIdSelector = state => state.currentSelections.programId; const categoriesMetaSelector = state => state.currentSelections.categoriesMeta; +const eventContainerSelector = state => state.viewEventPage.loadedValues?.eventContainer; +const eventIdSelector = state => state.viewEventPage.eventId; // $FlowFixMe[missing-annot] automated comment export const makeProgramStageSelector = () => createSelector( @@ -18,3 +22,24 @@ export const makeEventAccessSelector = () => createSelector( categoriesMetaSelector, (programId: string, categoriesMeta: ?Object) => getEventProgramEventAccess(programId, categoriesMeta)); +export const makeAssignedUserContextSelector = () => + // $FlowFixMe[missing-annot] + createSelector(eventContainerSelector, eventIdSelector, (eventContainer, eventId) => (assignee) => { + const { event: clientMainValues, values: clientValues } = eventContainer; + const program = getEventProgramThrowIfNotFound(clientMainValues.programId); + const formFoundation = program.stage.stageForm; + const formServerValues = formFoundation.convertValues(clientValues, convertToServerValue); + const mainDataServerValues: Object = convertMainEventClientToServer(clientMainValues); + + const events = [ + { + ...mainDataServerValues, + dataValues: Object.keys(formServerValues).map(key => ({ + dataElement: key, + value: formServerValues[key], + })), + assignedUser: convertClientToServer(assignee), + }, + ]; + return { eventId, events }; + }); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/index.js b/src/core_modules/capture-core/components/Pages/ViewEvent/index.js index 681239203a..082ff0bd70 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/index.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/index.js @@ -1,5 +1,3 @@ // @flow export { actionTypes as editEventDataEntryActionTypes } from '../../WidgetEventEdit/EditEventDataEntry'; -export { assigneeSectionActionTypes } from '../ViewEvent/RightColumn/AssigneeSection'; - export { ViewEventPage } from './ViewEventPage.container'; diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js index fb48b2a555..6d2b632bfa 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData/useCommonEnrollmentDomainData.types.js @@ -22,6 +22,7 @@ export type Event = {| trackedEntityInstance: string, notes?: Array, pendingApiResponse?: ?boolean, + assignedUser?: ApiAssignedUser, |}; export type EnrollmentData = {| diff --git a/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js b/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js index c097dc89c2..6d794477aa 100644 --- a/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js +++ b/src/core_modules/capture-core/components/Widget/WidgetCollapsible.component.js @@ -39,7 +39,6 @@ const styles = { borderColor: colors.grey400, borderWidth: 1, borderTopWidth: 0, - overflow: 'hidden', '&.open': { animation: 'slidein 200ms normal forwards ease-in-out', transformOrigin: '50% 0%', diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js new file mode 100644 index 0000000000..3bb4164769 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -0,0 +1,47 @@ +// @flow +import React from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { IconEdit24, Button, spacers } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core/styles'; +import type { UserFormField } from '../FormFields/UserField'; + +const styles = () => ({ + wrapper: { + display: 'flex', + alignItems: 'center', + }, + editButton: { + border: 'none !important', + borderRadius: '50% !important', + padding: '0px 6px !important', + margin: spacers.dp4, + }, +}); + +type Props = { + assignee: UserFormField | null, + onEdit: () => {}, + ...CssClasses, +}; + +const DisplayModePlain = ({ assignee, onEdit, classes }: Props) => { + const renderNoAssigned = () => ( + <> + + + ); + const renderAssigned = () => ( +
+ {i18n.t('Event assigned to {{name}}', { name: assignee?.name })} + +
+ ); + + return assignee ? renderAssigned() : renderNoAssigned(); +}; + +export const DisplayMode = withStyles(styles)(DisplayModePlain); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/EditMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js similarity index 54% rename from src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/EditMode.component.js rename to src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js index 41de1ddd9a..b3d68df239 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/EditMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js @@ -3,9 +3,9 @@ import * as React from 'react'; import i18n from '@dhis2/d2-i18n'; import { Button } from '@dhis2/ui'; import { withStyles } from '@material-ui/core/styles'; -import { UserSearch } from '../../../../FormFields/UserField/UserSearch.component'; +import { UserSearch, type UserFormField } from '../FormFields/UserField'; -const getStyles = () => ({ +const styles = () => ({ container: { display: 'flex', alignItems: 'center', @@ -22,35 +22,26 @@ const getStyles = () => ({ }); type Props = { - onCancel: Function, - classes: Object, + onCancel: () => {}, + onSet: (user: UserFormField) => void, + ...CssClasses, }; const EditModePlain = (props: Props) => { - const { onCancel, classes, ...passOnProps } = props; + const { onCancel, onSet, classes } = props; return ( -
-
- {/* $FlowFixMe[cannot-spread-inexact] automated comment */} +
+
-
-
@@ -58,4 +49,4 @@ const EditModePlain = (props: Props) => { ); }; -export const EditMode = withStyles(getStyles)(EditModePlain); +export const EditMode = withStyles(styles)(EditModePlain); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js new file mode 100644 index 0000000000..03363d1375 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js @@ -0,0 +1,60 @@ +// @flow +import React, { useState, useCallback } from 'react'; +import i18n from '@dhis2/d2-i18n'; +import { IconUser24, spacers } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core/styles'; +import type { PlainProps } from './WidgetAssignee.types'; +import { Widget } from '../Widget'; +import { DisplayMode } from './DisplayMode.component'; +import { EditMode } from './EditMode.component'; + +const styles = () => ({ + header: { + display: 'flex', + alignItems: 'center', + }, + wrapper: { + padding: `0 ${spacers.dp16} ${spacers.dp16} ${spacers.dp16}`, + }, +}); + +const WidgetAssigneePlain = ({ assignee, eventAccess, onSet, classes }: PlainProps) => { + const [open, setOpenStatus] = useState(true); + const [editMode, setEditMode] = useState(false); + + const handleSet = useCallback( + (user) => { + setEditMode(false); + onSet(user); + }, + [onSet], + ); + + const renderNoAccess = () => i18n.t('No one is assigned to this event'); + + const renderContent = () => + (editMode ? ( + setEditMode(false)} onSet={handleSet} /> + ) : ( + setEditMode(true)} /> + )); + + return ( +
+ + {i18n.t('Assignee')} + + } + onOpen={useCallback(() => setOpenStatus(true), [setOpenStatus])} + onClose={useCallback(() => setOpenStatus(false), [setOpenStatus])} + open={open} + > +
{eventAccess?.write ? renderContent() : renderNoAccess()}
+
+
+ ); +}; + +export const WidgetAssigneeComponent = withStyles(styles)(WidgetAssigneePlain); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js new file mode 100644 index 0000000000..a341f6b045 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js @@ -0,0 +1,31 @@ +// @flow +import React from 'react'; +import { useDispatch } from 'react-redux'; +import { batchActions } from 'redux-batched-actions'; +import type { Props } from './WidgetAssignee.types'; +import { WidgetAssigneeComponent } from './WidgetAssignee.component'; +import { saveAssignee, setAssignee } from './assignee.actions'; +import type { UserFormField } from '../FormFields/UserField'; + +export const WidgetAssignee = (props: Props) => { + const dispatch = useDispatch(); + const { programStage, assignee, eventAccess, onGetSaveContext } = props; + + if (!programStage?.enableUserAssignment) { + return null; + } + + const onSet = (newAssignee: UserFormField) => { + const { eventId, events, assignedUser } = onGetSaveContext(newAssignee); + const serverData = { events }; + + dispatch( + batchActions([ + setAssignee(eventId, serverData, newAssignee), + saveAssignee({ eventId, serverData, assignee, assignedUser }), + ]), + ); + }; + + return ; +}; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js new file mode 100644 index 0000000000..49f9b5abde --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js @@ -0,0 +1,27 @@ +// @flow +import type { ProgramStage } from '../../metaData'; +import type { UserFormField } from '../FormFields/UserField'; + +export type Props = {| + assignee: UserFormField | null, + programStage: ?ProgramStage, + eventAccess: {| + read: boolean, + write: boolean, + |} | null, + onGetSaveContext: (assignee: UserFormField) => { + eventId: string, + events: Array, + assignedUser?: ApiAssignedUser, + }, +|}; + +export type PlainProps = {| + assignee: UserFormField | null, + eventAccess: {| + read: boolean, + write: boolean, + |} | null, + onSet: (user: UserFormField) => void, + ...CssClasses, +|}; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js b/src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js new file mode 100644 index 0000000000..4a39006f81 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js @@ -0,0 +1,50 @@ +// @flow + +import { actionCreator } from '../../actions/actions.utils'; +import { effectMethods } from '../../trackerOffline'; +import type { UserFormField } from '../FormFields/UserField'; + +export const actionTypes = { + WIDGET_ASSIGNEE_SET: 'ViewEventAssigneeSet', + WIDGET_ASSIGNEE_SAVE: 'ViewEventAssigneeSave', + WIDGET_ASSIGNEE_SAVE_COMPLETED: 'ViewEventAssigneeSaveCompleted', + WIDGET_ASSIGNEE_SAVE_FAILED: 'ViewEventAssigneeSaveFailed', +}; + +export const setAssignee = ( + eventId: string, + serverData: { events: Array }, + assignee: UserFormField, +) => actionCreator(actionTypes.WIDGET_ASSIGNEE_SET)({ eventId, serverData, assignee }); + +export const saveAssignee = ({ + eventId, + serverData, + assignee, + assignedUser, +}: { + eventId: string, + serverData: { events: Array }, + assignee: UserFormField | null, + assignedUser?: ApiAssignedUser, +}) => + actionCreator(actionTypes.WIDGET_ASSIGNEE_SAVE)( + {}, + { + offline: { + effect: { + url: 'tracker?async=false&importStrategy=UPDATE', + method: effectMethods.POST, + data: serverData, + }, + commit: { + type: actionTypes.WIDGET_ASSIGNEE_SAVE_COMPLETED, + meta: { eventId }, + }, + rollback: { + type: actionTypes.WIDGET_ASSIGNEE_SAVE_FAILED, + meta: { eventId, assignee, assignedUser }, + }, + }, + }, + ); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/converter.js b/src/core_modules/capture-core/components/WidgetAssignee/converter.js new file mode 100644 index 0000000000..5ec29be37d --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/converter.js @@ -0,0 +1,19 @@ +// @flow +import type { UserFormField } from '../FormFields/UserField'; + +export const convertClientToServer = (assignee: UserFormField): ApiAssignedUser => ({ + uid: assignee.id, + displayName: assignee.name, + username: assignee.username, +}); + +export const convertServerToClient = (assignedUser?: ApiAssignedUser): UserFormField | null => { + if (!assignedUser) { + return null; + } + return { + id: assignedUser.uid, + name: assignedUser.displayName, + username: assignedUser.username, + }; +}; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/index.js b/src/core_modules/capture-core/components/WidgetAssignee/index.js new file mode 100644 index 0000000000..516b85aaab --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/index.js @@ -0,0 +1,4 @@ +// @flow +export { WidgetAssignee } from './WidgetAssignee.container'; +export { actionTypes as assigneeSectionActionTypes } from './assignee.actions'; +export { convertClientToServer, convertServerToClient } from './converter'; diff --git a/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.js b/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.js index 9ec7b002ac..fdd9db8a0e 100644 --- a/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.js +++ b/src/core_modules/capture-core/components/WidgetStagesAndEvents/types/common.types.js @@ -54,4 +54,5 @@ export type Event = {| trackedEntityInstance: string, notes?: Array, pendingApiResponse?: ?boolean, + assignedUser?: ApiAssignedUser, |}; diff --git a/src/core_modules/capture-core/flow/apiTypes.js b/src/core_modules/capture-core/flow/apiTypes.js index b11decd46e..d087aff8cf 100644 --- a/src/core_modules/capture-core/flow/apiTypes.js +++ b/src/core_modules/capture-core/flow/apiTypes.js @@ -1,5 +1,11 @@ // @flow +declare type ApiAssignedUser = {| + uid: string, + username: string, + displayName: string, +|}; + declare type ApiDataValue = { dataElement: string, value: string, @@ -22,6 +28,7 @@ declare type ApiEnrollmentEvent = {| notes?: Array, deleted?: boolean, pendingApiResponse?: ?boolean, + assignedUser?: ApiAssignedUser, |}; type ApiAttributeValues = { diff --git a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.js index b3470cbe24..ee276e1be6 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.js @@ -4,6 +4,7 @@ import { enrollmentSiteActionTypes } from '../../components/Pages/common/Enrollm import { actionTypes as enrollmentNoteActionTypes } from '../../components/WidgetEnrollmentComment/WidgetEnrollmentComment.actions'; import { actionTypes as editEventActionTypes } from '../../components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.actions'; +import { assigneeSectionActionTypes } from '../../components/WidgetAssignee'; const initialReducerValue = {}; const { @@ -127,6 +128,29 @@ export const enrollmentDomainDesc = createReducerDescription( }); return { ...state, enrollment: { ...state.enrollment, events } }; }, + [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SET]: (state, action) => { + const { serverData, eventId } = action.payload; + const event = state.enrollment?.events?.find(e => e.event === eventId); + if (!event) { + return state; + } + + return { ...state, enrollment: { ...state.enrollment, events: serverData.events } }; + }, + [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SAVE_FAILED]: (state, action) => { + const { assignedUser, eventId } = action.meta; + const events = state.enrollment?.events; + if (!events) { + return state; + } + return { + ...state, + enrollment: { + ...state.enrollment, + events: events.map(e => (e.event === eventId ? { ...e, assignedUser } : e)), + }, + }; + }, }, 'enrollmentDomain', initialReducerValue, diff --git a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js index a131dc4e35..6a61c08426 100644 --- a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js +++ b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js @@ -25,6 +25,7 @@ import { workingListsCommonActionTypes } from '../../components/WorkingLists/Wor import type { Updaters } from '../../trackerRedux/trackerReducer'; import { registrationFormActionTypes } from '../../components/Pages/New/RegistrationDataEntry/RegistrationDataEntry.actions'; import { enrollmentSiteActionTypes } from '../../components/Pages/common/EnrollmentOverviewDomain'; +import { assigneeSectionActionTypes } from '../../components/WidgetAssignee'; function addErrorFeedback(state: ReduxState, message: string, action?: ?Node) { const newState = [...state]; @@ -119,5 +120,7 @@ export const getFeedbackDesc = (appUpdaters: Updaters) => createReducerDescripti addErrorFeedback(state, i18n.t('Error editing the event, the changes made were not saved')), [enrollmentSiteActionTypes.ERROR_ENROLLMENT]: (state, action) => addErrorFeedback(state, i18n.t(action.payload.message)), + [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SAVE_FAILED]: state => + addErrorFeedback(state, i18n.t('Error updating the Assignee')), }, 'feedbacks', []); diff --git a/src/core_modules/capture-core/reducers/descriptions/viewEvent.reducerDescription.js b/src/core_modules/capture-core/reducers/descriptions/viewEvent.reducerDescription.js index ec4f51fe2b..94fdc53284 100644 --- a/src/core_modules/capture-core/reducers/descriptions/viewEvent.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/viewEvent.reducerDescription.js @@ -12,7 +12,7 @@ import { actionTypes as editEventDataEntryActionTypes, } from '../../components/WidgetEventEdit/EditEventDataEntry/editEventDataEntry.actions'; import { actionTypes as viewEventNotesActionTypes } from '../../components/Pages/ViewEvent/Notes/viewEventNotes.actions'; -import { assigneeSectionActionTypes } from '../../components/Pages/ViewEvent/RightColumn/AssigneeSection'; +import { assigneeSectionActionTypes } from '../../components/WidgetAssignee'; import { eventWorkingListsActionTypes } from '../../components/WorkingLists/EventWorkingLists'; import { actionTypes as widgetEventEditActionTypes, @@ -144,7 +144,7 @@ export const viewEventPageDesc = createReducerDescription({ eventHasChanged: true, }; }, - [assigneeSectionActionTypes.VIEW_EVENT_ASSIGNEE_SET]: (state, action) => { + [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SET]: (state, action) => { const { assignee } = action.payload; const newState = { @@ -164,7 +164,7 @@ export const viewEventPageDesc = createReducerDescription({ return newState; }, - [assigneeSectionActionTypes.VIEW_EVENT_ASSIGNEE_SAVE_COMPLETED]: (state, action) => { + [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SAVE_COMPLETED]: (state, action) => { if (action.meta.eventId !== state.eventId) { return state; } @@ -175,14 +175,25 @@ export const viewEventPageDesc = createReducerDescription({ eventHasChanged: true, }; }, - [assigneeSectionActionTypes.VIEW_EVENT_ASSIGNEE_SAVE_FAILED]: (state, action) => { - if (action.meta.eventId !== state.eventId) { + [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SAVE_FAILED]: (state, action) => { + const { assignee, eventId } = action.meta; + if (eventId !== state.eventId) { return state; } return { ...state, saveInProgress: false, + loadedValues: { + ...state.loadedValues, + eventContainer: { + ...state.loadedValues.eventContainer, + event: { + ...state.loadedValues.eventContainer.event, + assignee, + }, + }, + }, }; }, }, 'viewEventPage'); diff --git a/src/epics/trackerCapture.epics.js b/src/epics/trackerCapture.epics.js index 6cce6ae2eb..98e8c0e6f5 100644 --- a/src/epics/trackerCapture.epics.js +++ b/src/epics/trackerCapture.epics.js @@ -135,7 +135,6 @@ import { runRulesOnEnrollmentFieldUpdateEpic, runRulesOnEnrollmentDataEntryFieldUpdateEpic, } from 'capture-core/components/DataEntries'; -import { saveAssigneeEpic } from 'capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection'; import { triggerLoadCoreEpic, loadAppEpic } from '../components/AppStart'; @@ -300,7 +299,6 @@ export const epics = combineEpics( openNewRelationshipRegisterTeiEpic, loadSearchGroupDuplicatesForReviewEpic, teiForNewEventRelationshipSavedEpic, - saveAssigneeEpic, validateSelectionsBasedOnUrlUpdateEpic, getOrgUnitDataBasedOnUrlUpdateEpic, setOrgUnitDataEmptyBasedOnUrlUpdateEpic,