From 03170abf7ac1bd2e4d120a77a27365ac53eab7f7 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 13 Sep 2023 15:37:55 +0300 Subject: [PATCH 01/20] 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, From 02820088c8b7255e7046a5ae0672690e68833ba6 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 11 Oct 2023 09:29:41 +0200 Subject: [PATCH 02/20] chore: improve displaymodel types --- i18n/en.pot | 10 ++++---- .../WidgetAssignee/DisplayMode.component.js | 23 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 3f6b48ef4b..efa207aa45 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-09-04T07:07:59.195Z\n" -"PO-Revision-Date: 2023-09-04T07:07:59.195Z\n" +"POT-Creation-Date: 2023-10-11T07:28:55.943Z\n" +"PO-Revision-Date: 2023-10-11T07:28:55.943Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1083,12 +1083,12 @@ 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 "Assign" +msgstr "Assign" + msgid "No one is assigned to this event" msgstr "No one is assigned to this event" diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index 3bb4164769..872ac22014 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -24,24 +24,19 @@ type Props = { ...CssClasses, }; -const DisplayModePlain = ({ assignee, onEdit, classes }: Props) => { - const renderNoAssigned = () => ( - <> - - - ); - const renderAssigned = () => ( +const DisplayModePlain = ({ assignee, onEdit, classes }: Props) => ( + assignee ? (
- {i18n.t('Event assigned to {{name}}', { name: assignee?.name })} + {i18n.t('Event assigned to {{name}}', { name: assignee.name })}
- ); - - return assignee ? renderAssigned() : renderNoAssigned(); -}; + ) : ( + + ) +); export const DisplayMode = withStyles(styles)(DisplayModePlain); From 4cf93f5315e5558cb97d332f79f5eda0c3ee2165 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 1 Nov 2023 08:42:29 +0100 Subject: [PATCH 03/20] feat: remove redux and use app runtime hooks and callbacks --- .../UserField/UserSearch.component.js | 2 + .../components/FormFields/UserField/types.js | 2 + .../EnrollmentEditEventPage.actions.js | 15 ++++ .../EnrollmentEditEventPage.component.js | 4 + .../EnrollmentEditEventPage.container.js | 27 ++++-- .../EnrollmentEditEventPage.types.js | 11 ++- .../hooks/useAssignedUserSaveContext.js | 29 ++----- .../Pages/EnrollmentEditEvent/index.js | 3 + .../ViewEventComponent/ViewEvent.component.js | 18 +++- .../ViewEventComponent/ViewEvent.container.js | 29 +++++-- .../ViewEventComponent/viewEvent.actions.js | 10 +++ .../ViewEventComponent/viewEvent.selectors.js | 12 ++- .../WidgetAssignee/DisplayMode.component.js | 4 +- .../WidgetAssignee/EditMode.component.js | 5 +- .../WidgetAssignee.container.js | 59 +++++++------ .../WidgetAssignee/WidgetAssignee.types.js | 16 ++-- .../WidgetAssignee/assignee.actions.js | 50 ----------- .../components/WidgetAssignee/converter.js | 16 +--- .../components/WidgetAssignee/index.js | 3 +- .../capture-core/converters/clientToServer.js | 17 ++++ .../capture-core/converters/index.js | 3 +- .../capture-core/converters/serverToClient.js | 13 +++ .../mainEventClientToServerConverter.js | 4 +- .../capture-core/flow/apiTypes.js | 2 + .../enrollmentDomain.reducerDescription.js | 38 ++++----- .../feedback.reducerDescriptionGetter.js | 7 +- .../viewEvent.reducerDescription.js | 82 +++++++------------ 27 files changed, 251 insertions(+), 230 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.actions.js delete mode 100644 src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js diff --git a/src/core_modules/capture-core/components/FormFields/UserField/UserSearch.component.js b/src/core_modules/capture-core/components/FormFields/UserField/UserSearch.component.js index ed3bf21c9e..cb44cf74a1 100644 --- a/src/core_modules/capture-core/components/FormFields/UserField/UserSearch.component.js +++ b/src/core_modules/capture-core/components/FormFields/UserField/UserSearch.component.js @@ -133,6 +133,8 @@ class UserSearchPlain extends React.Component { id: au.id, name: au.displayName, username: au.username, + firstName: au.firstName, + surname: au.surname, })); }); diff --git a/src/core_modules/capture-core/components/FormFields/UserField/types.js b/src/core_modules/capture-core/components/FormFields/UserField/types.js index 0695b283f1..c8d34e711a 100644 --- a/src/core_modules/capture-core/components/FormFields/UserField/types.js +++ b/src/core_modules/capture-core/components/FormFields/UserField/types.js @@ -3,4 +3,6 @@ export type User = { id: string, username: string, name: string, + firstName: string, + surname: string, }; diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.actions.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.actions.js new file mode 100644 index 0000000000..ed817f7708 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/EnrollmentEditEventPage.actions.js @@ -0,0 +1,15 @@ +// @flow + +import { actionCreator } from '../../../actions/actions.utils'; +import type { UserFormField } from '../../FormFields/UserField'; + +export const actionTypes = { + ASSIGNEE_SET: 'EnrollmentDomain.AssigneeSet', + ASSIGNEE_SAVE_FAILED: 'EnrollmentDomain.AssigneeSaveFailed', +}; + +export const setAssignee = (assignedUser?: ApiAssignedUser, assignee: UserFormField | null, eventId: string) => + actionCreator(actionTypes.ASSIGNEE_SET)({ assignedUser, assignee, eventId }); + +export const rollbackAssignee = (assignedUser?: ApiAssignedUser, assignee: UserFormField | null, eventId: string) => + actionCreator(actionTypes.ASSIGNEE_SAVE_FAILED)({ assignedUser, assignee, eventId }); 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 c251de808f..adeb0c2d7b 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 @@ -75,6 +75,8 @@ const EnrollmentEditEventPagePain = ({ onCancelEditEvent, onHandleScheduleSave, onGetAssignedUserSaveContext, + onSaveAssignee, + onSaveAssigneeError, }: 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 80225e9dfb..b5cdca8aec 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 @@ -24,6 +24,8 @@ import { cleanUpDataEntry } from '../../DataEntry'; import { pageKeys } from '../../App/withAppUrlSync'; import { withErrorMessageHandler } from '../../../HOC'; import { getProgramEventAccess } from '../../../metaData'; +import { setAssignee, rollbackAssignee } from './EnrollmentEditEventPage.actions'; +import { convertAssigneeToServer } from '../../../converters'; const getEventDate = (event) => { const eventDataConvertValue = convertDateWithTimeForView(event?.occurredAt || event?.scheduledAt); @@ -55,6 +57,8 @@ export const EnrollmentEditEventPage = () => { const { loading, event } = useEvent(eventId); const { program: programId, programStage: stageId, trackedEntity: teiId, enrollment: enrollmentId } = event; const { orgUnitId, eventId: urlEventId, initMode } = useLocationQuery(); + const enrollmentSite = useCommonEnrollmentDomainData(teiId, enrollmentId, programId).enrollment; + const storedEvent = enrollmentSite?.events?.find(item => item.event === eventId); useEffect(() => { if (!urlEventId) { @@ -65,16 +69,17 @@ export const EnrollmentEditEventPage = () => { } }, [dispatch, history, eventId, urlEventId, orgUnitId]); - return (!loading && eventId === urlEventId) || error ? ( + return ((!loading && eventId === urlEventId) || error) && storedEvent ? ( ) : ; }; @@ -85,11 +90,13 @@ const EnrollmentEditEventPageWithContextPlain = ({ teiId, enrollmentId, orgUnitId, - eventId, initMode, + enrollmentSite, + event, }: Props) => { const history = useHistory(); const dispatch = useDispatch(); + const { event: eventId } = event; useEffect(() => () => { dispatch(cleanUpDataEntry(dataEntryIds.ENROLLMENT_EVENT)); @@ -124,12 +131,10 @@ const EnrollmentEditEventPageWithContextPlain = ({ dispatch(updateEnrollmentEvents(eventId, eventData)); history.push(`enrollment?${buildUrlQueryString({ enrollmentId })}`); }; - const enrollmentSite = useCommonEnrollmentDomainData(teiId, enrollmentId, programId).enrollment; const { teiDisplayName } = useTeiDisplayName(teiId, programId); // $FlowFixMe const trackedEntityName = program?.trackedEntityType?.name; const enrollmentsAsOptions = buildEnrollmentsAsOptions([enrollmentSite || {}], programId); - const event = enrollmentSite?.events?.find(item => item.event === eventId); const eventDate = getEventDate(event); const scheduleDate = getEventScheduleDate(event); const { currentPageMode } = useEnrollmentEditEventPageMode(event?.status); @@ -146,7 +151,15 @@ const EnrollmentEditEventPageWithContextPlain = ({ event, }); const assignee = useAssignee(event); - const onGetAssignedUserSaveContext = useAssignedUserSaveContext(enrollmentSite, event, eventId); + const onGetAssignedUserSaveContext = useAssignedUserSaveContext(event); + const onSaveAssignee = (newAssignee) => { + const assignedUser = convertAssigneeToServer(newAssignee); + dispatch(setAssignee(assignedUser, newAssignee, eventId)); + }; + const onSaveAssigneeError = (prevAssignee) => { + const assignedUser = prevAssignee ? convertAssigneeToServer(prevAssignee) : undefined; + dispatch(rollbackAssignee(assignedUser, prevAssignee, 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 96bed2de86..fc6e45f1de 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 @@ -30,12 +30,10 @@ export type PlainProps = {| read: boolean, write: boolean, |} | null, - onGetAssignedUserSaveContext: (assignee: UserFormField) => { - eventId: string, - events: Array, - assignedUser?: ApiAssignedUser, - }, + onGetAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, assignee: UserFormField | null, + onSaveAssignee: (newAssignee: UserFormField) => void, + onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, ...CssClasses, |}; @@ -45,6 +43,7 @@ export type Props = {| teiId: string, enrollmentId: string, orgUnitId: string, - eventId: string, + event: ApiEnrollmentEvent, + enrollmentSite: ApiEnrollment, initMode?: string, |}; 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 index ad654dd070..7ba3e577ea 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js @@ -1,27 +1,8 @@ // @flow -import { useCallback, useMemo } from 'react'; -import { convertServerToClient, convertClientToServer } from '../../../WidgetAssignee'; -import type { EnrollmentData } from '../../common/EnrollmentOverviewDomain/useCommonEnrollmentDomainData'; -import type { UserFormField } from '../../../FormFields/UserField'; +import { useMemo, useCallback } from 'react'; +import { convertAssignedUserToClient } from '../../../../converters'; -export const useAssignee = (event?: ApiEnrollmentEvent) => - useMemo(() => convertServerToClient(event?.assignedUser), [event?.assignedUser]); +export const useAssignee = (event: ApiEnrollmentEvent) => + useMemo(() => convertAssignedUserToClient(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], - ); +export const useAssignedUserSaveContext = (event: ApiEnrollmentEvent) => useCallback(() => ({ event }), [event]); diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/index.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/index.js index 3ce5e6a217..5a8c768ab0 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/index.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/index.js @@ -1,3 +1,6 @@ // @flow export { EnrollmentEditEventPage } from './EnrollmentEditEventPage.container'; export { updateEventSucceededEpic, updateEventFailedEpic } from './EnrollmentEditEventPage.epics'; +export { + actionTypes as enrollmentEditEventActionTypes, +} from './EnrollmentEditEventPage.actions'; 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 d24c511dbd..900e437cec 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 @@ -49,7 +49,9 @@ type Props = { showAllEvents: string, }, assignee: UserFormField, - onGetAssignedUserSaveContext: (assignee: UserFormField) => { eventId: string, events: Array }, + onGetAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, + onSaveAssignee: (newAssignee: UserFormField) => void, + onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, }; type State = { @@ -72,7 +74,17 @@ class ViewEventPlain extends Component { } render() { - const { classes, programStage, currentDataEntryKey, eventAccess, assignee, onGetAssignedUserSaveContext } = this.props; + const { + classes, + programStage, + currentDataEntryKey, + eventAccess, + assignee, + onGetAssignedUserSaveContext, + onSaveAssignee, + onSaveAssigneeError, + } = this.props; + return (
{ currentDataEntryKey, isUserInteractionInProgress, assignee: state.viewEventPage.loadedValues?.eventContainer.event.assignee, - onGetAssignedUserSaveContext: assignee => assignedUserContextSelector(state)(assignee), + onGetAssignedUserSaveContext: () => assignedUserContextSelector(state), + eventId: state.viewEventPage.eventId, }; }; }; @@ -43,10 +42,26 @@ const mapDispatchToProps = (dispatch: ReduxDispatch) => ({ onBackToAllEvents: () => { dispatch(startGoBackToMainPage()); }, + dispatch, }); +const mergeProps = (stateProps, dispatchProps, ownProps) => { + const mergedProps = { + onSaveAssignee: (newAssignee) => { + dispatchProps.dispatch(setAssignee(newAssignee, stateProps.eventId)); + }, + onSaveAssigneeError: (prevAssignee) => { + dispatchProps.dispatch(rollbackAssignee(prevAssignee, stateProps.eventId)); + }, + }; + + return Object.assign({}, ownProps, stateProps, dispatchProps, mergedProps); +}; + // $FlowSuppress // $FlowFixMe[missing-annot] automated comment -export const ViewEvent = connect(makeMapStateToProps, mapDispatchToProps)( - withErrorMessageHandler()(ViewEventComponent), -); +export const ViewEvent = connect( + makeMapStateToProps, + mapDispatchToProps, + mergeProps, +)(withErrorMessageHandler()(ViewEventComponent)); diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.actions.js b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.actions.js index 2c4c627067..14413d97bc 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.actions.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/viewEvent.actions.js @@ -1,6 +1,7 @@ // @flow import { actionCreator } from 'capture-core/actions/actions.utils'; import type { OrgUnit } from '@dhis2/rules-engine-javascript'; +import type { UserFormField } from '../../../FormFields/UserField'; export const actionTypes = { VIEW_EVENT_FROM_URL: 'ViewEventFromUrl', @@ -19,6 +20,8 @@ export const actionTypes = { UPDATE_WORKING_LIST_PENDING_ON_BACK_TO_MAIN_PAGE: 'UpdateWorkingListPendingOnBackToMainPageForViewEvent', OPEN_VIEW_EVENT_PAGE_FAILED: 'OpenViewEventPageFailed', INITIALIZE_WORKING_LISTS_ON_BACK_TO_MAIN_PAGE: 'InitializeWorkingListsOnBackToMainPage', + ASSIGNEE_SET: 'SingleEvent.AssigneeSet', + ASSIGNEE_SAVE_FAILED: 'SingleEvent.AssigneeSaveFailed', }; export const viewEventFromUrl = (data: Object) => @@ -74,3 +77,10 @@ export const updateEventContainer = (eventContainer: Object, orgUnit: OrgUnit) = export const openViewEventPageFailed = (error: string) => actionCreator(actionTypes.OPEN_VIEW_EVENT_PAGE_FAILED)({ error }); + +export const setAssignee = (assignee: UserFormField, eventId: string) => + actionCreator(actionTypes.ASSIGNEE_SET)({ assignee, eventId }); + +export const rollbackAssignee = (assignee: UserFormField, eventId: string) => + actionCreator(actionTypes.ASSIGNEE_SAVE_FAILED)({ assignee, eventId }); + 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 99513c9601..dc62894624 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 @@ -4,7 +4,6 @@ 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; @@ -24,22 +23,21 @@ export const makeEventAccessSelector = () => createSelector( export const makeAssignedUserContextSelector = () => // $FlowFixMe[missing-annot] - createSelector(eventContainerSelector, eventIdSelector, (eventContainer, eventId) => (assignee) => { + createSelector(eventContainerSelector, eventIdSelector, (eventContainer, eventId) => { 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 = [ + const event = { ...mainDataServerValues, dataValues: Object.keys(formServerValues).map(key => ({ dataElement: key, value: formServerValues[key], })), - assignedUser: convertClientToServer(assignee), - }, - ]; - return { eventId, events }; + }; + + return { eventId, event }; }); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index 872ac22014..e2c00d32f6 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -3,7 +3,7 @@ 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'; +import type { Assignee } from './WidgetAssignee.types'; const styles = () => ({ wrapper: { @@ -19,7 +19,7 @@ const styles = () => ({ }); type Props = { - assignee: UserFormField | null, + assignee: Assignee | null, onEdit: () => {}, ...CssClasses, }; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js index b3d68df239..26c81425ad 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/EditMode.component.js @@ -3,7 +3,8 @@ 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, type UserFormField } from '../FormFields/UserField'; +import type { Assignee } from './WidgetAssignee.types'; +import { UserSearch } from '../FormFields/UserField'; const styles = () => ({ container: { @@ -23,7 +24,7 @@ const styles = () => ({ type Props = { onCancel: () => {}, - onSet: (user: UserFormField) => void, + onSet: (user: Assignee) => void, ...CssClasses, }; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js index a341f6b045..64e456211f 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js @@ -1,31 +1,44 @@ // @flow -import React from 'react'; -import { useDispatch } from 'react-redux'; -import { batchActions } from 'redux-batched-actions'; -import type { Props } from './WidgetAssignee.types'; +import React, { useCallback, useRef } from 'react'; +import { useDataMutation } from '@dhis2/app-runtime'; +import type { Props, Assignee } from './WidgetAssignee.types'; import { WidgetAssigneeComponent } from './WidgetAssignee.component'; -import { saveAssignee, setAssignee } from './assignee.actions'; -import type { UserFormField } from '../FormFields/UserField'; +import { convertClientToServer } from './converter'; -export const WidgetAssignee = (props: Props) => { - const dispatch = useDispatch(); - const { programStage, assignee, eventAccess, onGetSaveContext } = props; +const WidgetAssigneeWithHooks = (props: Props) => { + const { assignee, eventAccess, onGetSaveContext, onSave, onSaveError } = props; + const prevAssignee = useRef(assignee); - if (!programStage?.enableUserAssignment) { - return null; - } + const [updateMutation] = useDataMutation( + { + resource: 'tracker?async=false&importStrategy=UPDATE', + type: 'create', + data: event => ({ events: [event] }), + }, + { + onError: () => { + onSaveError(prevAssignee.current); + }, + }, + ); - const onSet = (newAssignee: UserFormField) => { - const { eventId, events, assignedUser } = onGetSaveContext(newAssignee); - const serverData = { events }; - - dispatch( - batchActions([ - setAssignee(eventId, serverData, newAssignee), - saveAssignee({ eventId, serverData, assignee, assignedUser }), - ]), - ); - }; + const onSet = useCallback( + async (newAssignee: Assignee) => { + const { event } = onGetSaveContext(); + prevAssignee.current = assignee; + onSave(newAssignee); + await updateMutation({ ...event, assignedUser: convertClientToServer(newAssignee) }); + }, + [updateMutation, onGetSaveContext, onSave, assignee], + ); return ; }; + +export const WidgetAssignee = (props: Props) => { + if (!props.programStage?.enableUserAssignment) { + return null; + } + + 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 index 49f9b5abde..5823e0ac1e 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js @@ -2,6 +2,14 @@ import type { ProgramStage } from '../../metaData'; import type { UserFormField } from '../FormFields/UserField'; +export type Assignee = { + id: string, + username: string, + name: string, + firstName: string, + surname: string, +} + export type Props = {| assignee: UserFormField | null, programStage: ?ProgramStage, @@ -9,11 +17,9 @@ export type Props = {| read: boolean, write: boolean, |} | null, - onGetSaveContext: (assignee: UserFormField) => { - eventId: string, - events: Array, - assignedUser?: ApiAssignedUser, - }, + onGetSaveContext: () => { event: ApiEnrollmentEvent }, + onSave: (newAssignee: UserFormField) => void, + onSaveError: (prevAssignee: UserFormField | null) => void, |}; export type PlainProps = {| diff --git a/src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js b/src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js deleted file mode 100644 index 4a39006f81..0000000000 --- a/src/core_modules/capture-core/components/WidgetAssignee/assignee.actions.js +++ /dev/null @@ -1,50 +0,0 @@ -// @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 index 5ec29be37d..049e2becd6 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/converter.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/converter.js @@ -1,19 +1,11 @@ // @flow -import type { UserFormField } from '../FormFields/UserField'; +import type { Assignee } from './WidgetAssignee.types'; -export const convertClientToServer = (assignee: UserFormField): ApiAssignedUser => ({ +export const convertClientToServer = (assignee: Assignee): ApiAssignedUser => ({ uid: assignee.id, displayName: assignee.name, username: assignee.username, + firstName: assignee.firstName, + surname: assignee.surname, }); -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 index 516b85aaab..3118669d83 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/index.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/index.js @@ -1,4 +1,3 @@ // @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/converters/clientToServer.js b/src/core_modules/capture-core/converters/clientToServer.js index 7477352af6..49b4af1021 100644 --- a/src/core_modules/capture-core/converters/clientToServer.js +++ b/src/core_modules/capture-core/converters/clientToServer.js @@ -8,6 +8,13 @@ type RangeValue = { to: number, } +type Assignee = { + id: string, + username: string, + name: string, + firstName: string, + surname: string, +} function convertDate(rawValue: string): string { const editedDate = rawValue; @@ -66,3 +73,13 @@ export function convertCategoryOptionsToServer(value: {[categoryId: string]: str } return value; } + +export function convertAssigneeToServer(assignee: Assignee): ApiAssignedUser { + return { + uid: assignee.id, + displayName: assignee.name, + username: assignee.username, + firstName: assignee.firstName, + surname: assignee.surname, + }; +} diff --git a/src/core_modules/capture-core/converters/index.js b/src/core_modules/capture-core/converters/index.js index 74b4d0e019..dc9c9dae3e 100644 --- a/src/core_modules/capture-core/converters/index.js +++ b/src/core_modules/capture-core/converters/index.js @@ -2,9 +2,10 @@ export { convertValue as convertClientToForm } from './clientToForm'; export { convertValue as convertClientToList } from './clientToList'; export { convertValue as convertClientToView, convertDateWithTimeForView } from './clientToView'; -export { convertValue as convertClientToServer } from './clientToServer'; +export { convertValue as convertClientToServer, convertAssigneeToServer } from './clientToServer'; export { convertValue as convertFormToClient } from './formToClient'; export { convertValue as convertServerToClient, convertOptionSetValue as convertOptionSetValueServerToClient, + convertAssignedUserToClient, } from './serverToClient'; diff --git a/src/core_modules/capture-core/converters/serverToClient.js b/src/core_modules/capture-core/converters/serverToClient.js index 897a4cc13e..4c08cf4ba6 100644 --- a/src/core_modules/capture-core/converters/serverToClient.js +++ b/src/core_modules/capture-core/converters/serverToClient.js @@ -61,3 +61,16 @@ export function convertValue(value: any, type: $Keys) { // $FlowFixMe dataElementTypes flow error return valueConvertersForType[type] ? valueConvertersForType[type](value) : value; } + +export const convertAssignedUserToClient = (assignedUser?: ApiAssignedUser) => { + if (!assignedUser) { + return null; + } + return { + id: assignedUser.uid, + name: assignedUser.displayName, + username: assignedUser.username, + firstName: assignedUser.firstName, + surname: assignedUser.surname, + }; +}; diff --git a/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js b/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js index 1e3f62f036..207918b8cb 100644 --- a/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js +++ b/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js @@ -1,5 +1,5 @@ // @flow -import { convertClientToServer } from '../../converters'; +import { convertClientToServer, convertAssigneeToServer } from '../../converters'; import { convertMainEvent } from './mainEventConverter'; import { dataElementTypes } from '../../metaData'; import { convertEventAttributeOptions } from '../convertEventAttributeOptions'; @@ -26,7 +26,7 @@ export function convertMainEventClientToServer(event: Object) { convertedValue = convertClientToServer(value, dataElementTypes.DATE); break; case 'assignee': - convertedValue = value && ({ uid: value.id }); + convertedValue = value && convertAssigneeToServer(value); break; default: convertedValue = value; diff --git a/src/core_modules/capture-core/flow/apiTypes.js b/src/core_modules/capture-core/flow/apiTypes.js index d087aff8cf..dfe843a65e 100644 --- a/src/core_modules/capture-core/flow/apiTypes.js +++ b/src/core_modules/capture-core/flow/apiTypes.js @@ -4,6 +4,8 @@ declare type ApiAssignedUser = {| uid: string, username: string, displayName: string, + firstName: string, + surname: string, |}; declare type ApiDataValue = { 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 9c24892223..247ac7a1e6 100644 --- a/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/enrollmentDomain.reducerDescription.js @@ -4,7 +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'; +import { enrollmentEditEventActionTypes } from '../../components/Pages/EnrollmentEditEvent'; const initialReducerValue = {}; const { @@ -20,6 +20,17 @@ const { COMMIT_ENROLLMENT_EVENT_WITHOUT_ID, } = enrollmentSiteActionTypes; +const setAssignee = (state, action) => { + const { assignedUser, eventId } = action.payload; + + const events = state.enrollment.events.reduce( + (acc, e) => (e.event === eventId ? [...acc, { ...e, assignedUser }] : [...acc, e]), + [], + ); + + return { ...state, enrollment: { ...state.enrollment, events } }; +}; + export const enrollmentDomainDesc = createReducerDescription( { [COMMON_ENROLLMENT_SITE_DATA_SET]: (state, { payload: { enrollment, attributeValues } }) => ({ @@ -144,29 +155,8 @@ 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)), - }, - }; - }, + [enrollmentEditEventActionTypes.ASSIGNEE_SET]: setAssignee, + [enrollmentEditEventActionTypes.ASSIGNEE_SAVE_FAILED]: setAssignee, }, '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 6a61c08426..1fd8c503e8 100644 --- a/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js +++ b/src/core_modules/capture-core/reducers/descriptions/feedback.reducerDescriptionGetter.js @@ -25,7 +25,8 @@ 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'; +import { enrollmentEditEventActionTypes } from '../../components/Pages/EnrollmentEditEvent'; +import { actionTypes as viewEventActionTypes } from '../../components/Pages/ViewEvent/ViewEventComponent/viewEvent.actions'; function addErrorFeedback(state: ReduxState, message: string, action?: ?Node) { const newState = [...state]; @@ -120,7 +121,9 @@ 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 => + [viewEventActionTypes.ASSIGNEE_SAVE_FAILED]: state => + addErrorFeedback(state, i18n.t('Error updating the Assignee')), + [enrollmentEditEventActionTypes.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 94fdc53284..f82ba16563 100644 --- a/src/core_modules/capture-core/reducers/descriptions/viewEvent.reducerDescription.js +++ b/src/core_modules/capture-core/reducers/descriptions/viewEvent.reducerDescription.js @@ -12,11 +12,35 @@ 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/WidgetAssignee'; import { eventWorkingListsActionTypes } from '../../components/WorkingLists/EventWorkingLists'; import { actionTypes as widgetEventEditActionTypes, } from '../../components/WidgetEventEdit/WidgetEventEdit.actions'; +import { enrollmentEditEventActionTypes } from '../../components/Pages/EnrollmentEditEvent'; + +const setAssignee = (state, action) => { + const { assignee, eventId } = action.payload; + if (eventId !== state.eventId) { + return state; + } + + const newState = { + ...state, + saveInProgress: true, + loadedValues: { + ...state.loadedValues, + eventContainer: { + ...state.loadedValues.eventContainer, + event: { + ...state.loadedValues.eventContainer.event, + assignee, + }, + }, + }, + }; + + return newState; +}; export const viewEventPageDesc = createReducerDescription({ [viewEventActionTypes.VIEW_EVENT_FROM_URL]: (state, action) => { @@ -144,56 +168,8 @@ export const viewEventPageDesc = createReducerDescription({ eventHasChanged: true, }; }, - [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SET]: (state, action) => { - const { assignee } = action.payload; - - const newState = { - ...state, - saveInProgress: true, - loadedValues: { - ...state.loadedValues, - eventContainer: { - ...state.loadedValues.eventContainer, - event: { - ...state.loadedValues.eventContainer.event, - assignee, - }, - }, - }, - }; - - return newState; - }, - [assigneeSectionActionTypes.WIDGET_ASSIGNEE_SAVE_COMPLETED]: (state, action) => { - if (action.meta.eventId !== state.eventId) { - return state; - } - - return { - ...state, - saveInProgress: false, - eventHasChanged: true, - }; - }, - [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, - }, - }, - }, - }; - }, + [viewEventActionTypes.ASSIGNEE_SET]: setAssignee, + [viewEventActionTypes.ASSIGNEE_SAVE_FAILED]: setAssignee, + [enrollmentEditEventActionTypes.ASSIGNEE_SET]: setAssignee, + [enrollmentEditEventActionTypes.ASSIGNEE_SAVE_FAILED]: setAssignee, }, 'viewEventPage'); From e0d91ad06d05428c0537daec6dcd70698ced6c51 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 1 Nov 2023 09:29:36 +0100 Subject: [PATCH 04/20] chore: split enrollment event page --- .../EnrollmentEditEventPage.component.js | 210 ++++++++++++------ 1 file changed, 144 insertions(+), 66 deletions(-) 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 6d2349c533..2b16b04f96 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 @@ -55,6 +55,119 @@ const styles = ({ typography }) => ({ }, }); +const EnrollmentEditEventPageLeft = ({ + programStage, + teiId, + enrollmentId, + programId, + onGoBack, + orgUnitId, + scheduleDate, + eventStatus, + pageStatus, + onCancelEditEvent, + onHandleScheduleSave, +}) => ( + <> + {pageStatus === pageStatuses.DEFAULT && programStage && ( + + )} + {pageStatus === pageStatuses.MISSING_DATA && ( + {i18n.t('The enrollment event data could not be found')} + )} + {pageStatus === pageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( + + {i18n.t('Choose a registering unit to start reporting')} + + )} + +); + +const EnrollmentEditEventPageRight = ({ + mode, + programStage, + teiId, + enrollmentId, + trackedEntityTypeId, + programId, + widgetEffects, + hideWidgets, + onDelete, + onAddNew, + onLinkedRecordClick, + orgUnitId, + eventAccess, + assignee, + onEnrollmentError, + onEnrollmentSuccess, + onGetAssignedUserSaveContext, + onSaveAssignee, + onSaveAssigneeError, + addRelationShipContainerElement, + toggleVisibility, +}) => ( + <> + + + + + {!hideWidgets.feedback && ( + + )} + {!hideWidgets.indicator && ( + + )} + {addRelationShipContainerElement && ( + {}} + onLinkedRecordClick={onLinkedRecordClick} + /> + )} + + + +); + const EnrollmentEditEventPagePain = ({ mode, programStage, @@ -110,10 +223,7 @@ const EnrollmentEditEventPagePain = ({
-
+
{mode === dataEntryKeys.VIEW ? i18n.t('Enrollment{{escape}} View Event', { escape: ':' }) @@ -121,76 +231,44 @@ const EnrollmentEditEventPagePain = ({
- {pageStatus === pageStatuses.DEFAULT && programStage && ( - - )} - {pageStatus === pageStatuses.MISSING_DATA && ( - {i18n.t('The enrollment event data could not be found')} - )} - {pageStatus === pageStatuses.WITHOUT_ORG_UNIT_SELECTED && ( - - {i18n.t('Choose a registering unit to start reporting')} - - )} +
- - - - - {!hideWidgets.feedback && ( - - )} - {!hideWidgets.indicator && ( - - )} - {addRelationShipContainerElement && - {}} - onLinkedRecordClick={onLinkedRecordClick} - /> - } - -
From af609d0abd266f25758af81a6f0bdd499e0e08d7 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Fri, 3 Nov 2023 11:05:53 +0100 Subject: [PATCH 05/20] chore: improve converters --- .../EnrollmentEditEventPage.container.js | 10 ++++--- .../hooks/useAssignedUserSaveContext.js | 9 ++++--- .../Assignee/Assignee.types.js | 1 + .../WidgetEventSchedule.actions.js | 2 +- .../WidgetEventSchedule.container.js | 6 ++--- .../widgetEventSchedule.types.js | 2 +- .../capture-core/converters/clientToServer.js | 21 ++++++++------- .../capture-core/converters/index.js | 3 +-- .../capture-core/converters/serverToClient.js | 27 ++++++++++--------- .../mainEventClientToServerConverter.js | 4 +-- 10 files changed, 47 insertions(+), 38 deletions(-) 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 291aee9267..690e83141e 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 @@ -26,7 +26,7 @@ import { pageKeys } from '../../App/withAppUrlSync'; import { withErrorMessageHandler } from '../../../HOC'; import { getProgramEventAccess } from '../../../metaData'; import { setAssignee, rollbackAssignee } from './EnrollmentEditEventPage.actions'; -import { convertAssigneeToServer } from '../../../converters'; +import { convertClientToServer } from '../../../converters'; const getEventDate = (event) => { const eventDataConvertValue = convertDateWithTimeForView(event?.occurredAt || event?.scheduledAt); @@ -157,11 +157,15 @@ const EnrollmentEditEventPageWithContextPlain = ({ const assignee = useAssignee(event); const onGetAssignedUserSaveContext = useAssignedUserSaveContext(event); const onSaveAssignee = (newAssignee) => { - const assignedUser = convertAssigneeToServer(newAssignee); + // $FlowFixMe dataElementTypes flow error + const assignedUser: ApiAssignedUser = convertClientToServer(newAssignee, dataElementTypes.ASSIGNEE); dispatch(setAssignee(assignedUser, newAssignee, eventId)); }; const onSaveAssigneeError = (prevAssignee) => { - const assignedUser = prevAssignee ? convertAssigneeToServer(prevAssignee) : undefined; + const assignedUser: ApiAssignedUser | typeof undefined = prevAssignee + // $FlowFixMe dataElementTypes flow error + ? convertClientToServer(prevAssignee, dataElementTypes.ASSIGNEE) + : undefined; dispatch(rollbackAssignee(assignedUser, prevAssignee, eventId)); }; 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 index 7ba3e577ea..3819ec28a6 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/hooks/useAssignedUserSaveContext.js @@ -1,8 +1,11 @@ // @flow import { useMemo, useCallback } from 'react'; -import { convertAssignedUserToClient } from '../../../../converters'; +import { dataElementTypes } from '../../../../metaData'; +import { convertServerToClient } from '../../../../converters'; +import type { UserFormField } from '../../../FormFields/UserField'; -export const useAssignee = (event: ApiEnrollmentEvent) => - useMemo(() => convertAssignedUserToClient(event?.assignedUser), [event?.assignedUser]); +export const useAssignee = (event: ApiEnrollmentEvent): UserFormField | null => + // $FlowFixMe dataElementTypes flow error + useMemo(() => convertServerToClient(event?.assignedUser, dataElementTypes.ASSIGNEE), [event?.assignedUser]); export const useAssignedUserSaveContext = (event: ApiEnrollmentEvent) => useCallback(() => ({ event }), [event]); diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js index fdbbccd9f2..e9a1111920 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/Assignee/Assignee.types.js @@ -4,4 +4,5 @@ import type { UserFormField } from '../../FormFields/UserField'; export type Props = { ...CssClasses, assignee?: UserFormField, + onSetAssignee: (user: UserFormField) => void, }; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js index 6dc9c0a5bc..cccaec60a2 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js @@ -35,7 +35,7 @@ export const requestScheduleEvent = ({ onSaveExternal: (eventServerValues: Object, uid: string) => void, onSaveSuccessActionType?: string, onSaveErrorActionType?: string, - assignedUser?: {uid: string}, + assignedUser?: ApiAssignedUser, }) => actionCreator(scheduleEventWidgetActionTypes.EVENT_SCHEDULE_REQUEST)({ scheduleDate, diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js index 367ab395bb..2f29e06e44 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js @@ -3,7 +3,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import i18n from '@dhis2/d2-i18n'; import { useDispatch } from 'react-redux'; import moment from 'moment'; -import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess } from '../../metaData'; +import { getProgramAndStageForProgram, TrackerProgram, getProgramEventAccess, dataElementTypes } from '../../metaData'; import { useOrganisationUnit } from '../../dataQueries'; import { useLocationQuery } from '../../utils/routing'; import type { ContainerProps } from './widgetEventSchedule.types'; @@ -18,7 +18,7 @@ import { import { requestScheduleEvent } from './WidgetEventSchedule.actions'; import { NoAccess } from './AccessVerification'; import { useCategoryCombinations } from '../DataEntryDhis2Helpers/AOC/useCategoryCombinations'; -import { convertAssigneeToServer } from '../../converters'; +import { convertClientToServer } from '../../converters'; export const WidgetEventSchedule = ({ enrollmentId, @@ -82,7 +82,7 @@ export const WidgetEventSchedule = ({ onSaveExternal: onSave, onSaveSuccessActionType, onSaveErrorActionType, - ...(assignee && { assignedUser: convertAssigneeToServer(assignee) }), + ...(assignee && { assignedUser: convertClientToServer(assignee, dataElementTypes.ASSIGNEE) }), })); }, [ dispatch, diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js index f42a7e8f87..cfae040a82 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js @@ -41,7 +41,7 @@ export type Props = {| categoryOptionsError?: ?{[categoryId: string]: { touched: boolean, valid: boolean} }, enableUserAssignment?: boolean, onSchedule: () => void, - onSetAssignee: (user: UserFormField) => void, + onSetAssignee: () => void, assignee?: UserFormField, onCancel: () => void, setScheduleDate: (date: string) => void, diff --git a/src/core_modules/capture-core/converters/clientToServer.js b/src/core_modules/capture-core/converters/clientToServer.js index 49b4af1021..e085c53a74 100644 --- a/src/core_modules/capture-core/converters/clientToServer.js +++ b/src/core_modules/capture-core/converters/clientToServer.js @@ -30,6 +30,16 @@ function convertRange(parser: (value: any) => any, rangeValue: RangeValue) { }; } +function convertAssigneeToServer(assignee: Assignee): ApiAssignedUser { + return { + uid: assignee.id, + displayName: assignee.name, + username: assignee.username, + firstName: assignee.firstName, + surname: assignee.surname, + }; +} + const valueConvertersForType = { [dataElementTypes.NUMBER]: stringifyNumber, [dataElementTypes.NUMBER_RANGE]: (value: RangeValue) => convertRange(stringifyNumber, value), @@ -51,6 +61,7 @@ const valueConvertersForType = { [dataElementTypes.COORDINATE]: (rawValue: Object) => `[${rawValue.longitude},${rawValue.latitude}]`, [dataElementTypes.ORGANISATION_UNIT]: (rawValue: Object) => rawValue.id, [dataElementTypes.AGE]: (rawValue: Object) => convertDate(rawValue), + [dataElementTypes.ASSIGNEE]: convertAssigneeToServer, }; export function convertValue(value: any, type: $Keys) { @@ -73,13 +84,3 @@ export function convertCategoryOptionsToServer(value: {[categoryId: string]: str } return value; } - -export function convertAssigneeToServer(assignee: Assignee): ApiAssignedUser { - return { - uid: assignee.id, - displayName: assignee.name, - username: assignee.username, - firstName: assignee.firstName, - surname: assignee.surname, - }; -} diff --git a/src/core_modules/capture-core/converters/index.js b/src/core_modules/capture-core/converters/index.js index dc9c9dae3e..74b4d0e019 100644 --- a/src/core_modules/capture-core/converters/index.js +++ b/src/core_modules/capture-core/converters/index.js @@ -2,10 +2,9 @@ export { convertValue as convertClientToForm } from './clientToForm'; export { convertValue as convertClientToList } from './clientToList'; export { convertValue as convertClientToView, convertDateWithTimeForView } from './clientToView'; -export { convertValue as convertClientToServer, convertAssigneeToServer } from './clientToServer'; +export { convertValue as convertClientToServer } from './clientToServer'; export { convertValue as convertFormToClient } from './formToClient'; export { convertValue as convertServerToClient, convertOptionSetValue as convertOptionSetValueServerToClient, - convertAssignedUserToClient, } from './serverToClient'; diff --git a/src/core_modules/capture-core/converters/serverToClient.js b/src/core_modules/capture-core/converters/serverToClient.js index 4c08cf4ba6..0115b1d7d6 100644 --- a/src/core_modules/capture-core/converters/serverToClient.js +++ b/src/core_modules/capture-core/converters/serverToClient.js @@ -11,6 +11,19 @@ function convertTime(d2Value: string) { return parseData.momentTime; } +const convertAssignedUserToClient = (assignedUser?: ApiAssignedUser) => { + if (!assignedUser) { + return null; + } + return { + id: assignedUser.uid, + name: assignedUser.displayName, + username: assignedUser.username, + firstName: assignedUser.firstName, + surname: assignedUser.surname, + }; +}; + const optionSetConvertersForType = { [dataElementTypes.NUMBER]: parseNumber, [dataElementTypes.INTEGER]: parseNumber, @@ -51,6 +64,7 @@ const valueConvertersForType = { return { latitude: arr[1], longitude: arr[0] }; }, [dataElementTypes.POLYGON]: () => 'Polygon', + [dataElementTypes.ASSIGNEE]: convertAssignedUserToClient, }; export function convertValue(value: any, type: $Keys) { @@ -61,16 +75,3 @@ export function convertValue(value: any, type: $Keys) { // $FlowFixMe dataElementTypes flow error return valueConvertersForType[type] ? valueConvertersForType[type](value) : value; } - -export const convertAssignedUserToClient = (assignedUser?: ApiAssignedUser) => { - if (!assignedUser) { - return null; - } - return { - id: assignedUser.uid, - name: assignedUser.displayName, - username: assignedUser.username, - firstName: assignedUser.firstName, - surname: assignedUser.surname, - }; -}; diff --git a/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js b/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js index 207918b8cb..37f3dc635c 100644 --- a/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js +++ b/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js @@ -1,5 +1,5 @@ // @flow -import { convertClientToServer, convertAssigneeToServer } from '../../converters'; +import { convertClientToServer } from '../../converters'; import { convertMainEvent } from './mainEventConverter'; import { dataElementTypes } from '../../metaData'; import { convertEventAttributeOptions } from '../convertEventAttributeOptions'; @@ -26,7 +26,7 @@ export function convertMainEventClientToServer(event: Object) { convertedValue = convertClientToServer(value, dataElementTypes.DATE); break; case 'assignee': - convertedValue = value && convertAssigneeToServer(value); + convertedValue = value && convertClientToServer(value, dataElementTypes.ASSIGNEE); break; default: convertedValue = value; From fac65a1a11e017c29a4de7889f618057c816c1e0 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 14 Nov 2023 08:16:07 +0100 Subject: [PATCH 06/20] chore: simplify widget assignee props --- .../EnrollmentEditEventPage.component.js | 4 +-- .../AssigneeSection.container.js | 35 +++++++++++++++++++ .../RightColumn/AssigneeSection/index.js | 2 ++ .../RightColumnWrapper.component.js | 4 +-- .../WidgetAssignee.component.js | 4 +-- .../WidgetAssignee.container.js | 6 ++-- .../WidgetAssignee/WidgetAssignee.types.js | 13 ++----- 7 files changed, 49 insertions(+), 19 deletions(-) create mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.container.js create mode 100644 src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/index.js 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 2b16b04f96..97360e8fa3 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 @@ -119,10 +119,10 @@ const EnrollmentEditEventPageRight = ({ }) => ( <> 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 new file mode 100644 index 0000000000..a28c47d541 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/AssigneeSection.container.js @@ -0,0 +1,35 @@ +// @flow +import React from 'react'; +import { WidgetAssignee } from '../../../../WidgetAssignee'; +import type { ProgramStage } from '../../../../../metaData'; +import type { UserFormField } from '../../../../FormFields/UserField'; + +type Props = {| + assignee: UserFormField | null, + programStage: ?ProgramStage, + eventAccess: {| + read: boolean, + write: boolean, + |} | null, + onGetAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, + onSaveAssignee: (newAssignee: UserFormField) => void, + onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, +|}; + +export const AssigneeSection = ({ + assignee, + programStage, + onGetAssignedUserSaveContext, + eventAccess, + onSaveAssignee, + onSaveAssigneeError, +}: Props) => ( + +); 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 new file mode 100644 index 0000000000..9646d16bb6 --- /dev/null +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/RightColumn/AssigneeSection/index.js @@ -0,0 +1,2 @@ +// @flow +export { AssigneeSection } from './AssigneeSection.container'; 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 a99536d77a..195dbbbb04 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 { WidgetAssignee } from '../../../WidgetAssignee'; +import { AssigneeSection } from './AssigneeSection'; type Props = { classes: { @@ -29,7 +29,7 @@ const componentContainers = [ { id: 'WarningsSection', Component: WarningsSection }, { id: 'FeedbacksSection', Component: FeedbacksSection }, { id: 'IndicatorsSection', Component: IndicatorsSection }, - { id: 'WidgetAssignee', Component: WidgetAssignee }, + { id: 'AssigneeSection', Component: AssigneeSection }, { id: 'RelationshipsSection', Component: RelationshipsSection }, { id: 'NotesSection', Component: NotesSection }, ]; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js index 03363d1375..688c1a4852 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js @@ -18,7 +18,7 @@ const styles = () => ({ }, }); -const WidgetAssigneePlain = ({ assignee, eventAccess, onSet, classes }: PlainProps) => { +const WidgetAssigneePlain = ({ assignee, writeAccess, onSet, classes }: PlainProps) => { const [open, setOpenStatus] = useState(true); const [editMode, setEditMode] = useState(false); @@ -51,7 +51,7 @@ const WidgetAssigneePlain = ({ assignee, eventAccess, onSet, classes }: PlainPro onClose={useCallback(() => setOpenStatus(false), [setOpenStatus])} open={open} > -
{eventAccess?.write ? renderContent() : renderNoAccess()}
+
{writeAccess ? renderContent() : renderNoAccess()}
); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js index 64e456211f..6d60bd81af 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js @@ -6,7 +6,7 @@ import { WidgetAssigneeComponent } from './WidgetAssignee.component'; import { convertClientToServer } from './converter'; const WidgetAssigneeWithHooks = (props: Props) => { - const { assignee, eventAccess, onGetSaveContext, onSave, onSaveError } = props; + const { assignee, writeAccess, onGetSaveContext, onSave, onSaveError } = props; const prevAssignee = useRef(assignee); const [updateMutation] = useDataMutation( @@ -32,11 +32,11 @@ const WidgetAssigneeWithHooks = (props: Props) => { [updateMutation, onGetSaveContext, onSave, assignee], ); - return ; + return ; }; export const WidgetAssignee = (props: Props) => { - if (!props.programStage?.enableUserAssignment) { + if (!props.enabled) { return null; } diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js index 5823e0ac1e..22d6e27a2e 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js @@ -1,5 +1,4 @@ // @flow -import type { ProgramStage } from '../../metaData'; import type { UserFormField } from '../FormFields/UserField'; export type Assignee = { @@ -12,11 +11,8 @@ export type Assignee = { export type Props = {| assignee: UserFormField | null, - programStage: ?ProgramStage, - eventAccess: {| - read: boolean, - write: boolean, - |} | null, + enabled: boolean, + writeAccess: boolean, onGetSaveContext: () => { event: ApiEnrollmentEvent }, onSave: (newAssignee: UserFormField) => void, onSaveError: (prevAssignee: UserFormField | null) => void, @@ -24,10 +20,7 @@ export type Props = {| export type PlainProps = {| assignee: UserFormField | null, - eventAccess: {| - read: boolean, - write: boolean, - |} | null, + writeAccess: boolean, onSet: (user: UserFormField) => void, ...CssClasses, |}; From fd3974efa8de9e37cf64513147dd9db860715627 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 14 Nov 2023 08:52:44 +0100 Subject: [PATCH 07/20] chore: disable action buttons in the read-only mode --- i18n/en.pot | 14 ++++-- .../WidgetAssignee/DisplayMode.component.js | 43 ++++++++++++++++--- .../WidgetAssignee.component.js | 17 +++----- 3 files changed, 53 insertions(+), 21 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index c90fcf6c07..bcd7d8dafd 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-11-01T08:10:43.456Z\n" -"PO-Revision-Date: 2023-11-01T08:10:43.456Z\n" +"POT-Creation-Date: 2023-11-14T07:52:45.978Z\n" +"PO-Revision-Date: 2023-11-14T07:52:45.978Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1101,12 +1101,18 @@ msgstr "open the Tracker Capture app" msgid "Event assigned to {{name}}" msgstr "Event assigned to {{name}}" -msgid "Assign" -msgstr "Assign" +msgid "You don't have access to edit this assignee" +msgstr "You don't have access to edit this assignee" msgid "No one is assigned to this event" msgstr "No one is assigned to this event" +msgid "You don't have access to assign an assignee" +msgstr "You don't have access to assign an assignee" + +msgid "Assign" +msgstr "Assign" + msgid "This program is protected" msgstr "This program is protected" diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index e2c00d32f6..2db92c35a2 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -3,6 +3,7 @@ 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 { ConditionalTooltip } from 'capture-core/components/ConditionalTooltip'; import type { Assignee } from './WidgetAssignee.types'; const styles = () => ({ @@ -16,26 +17,54 @@ const styles = () => ({ padding: '0px 6px !important', margin: spacers.dp4, }, + assignButton: { + margin: spacers.dp4, + }, }); type Props = { assignee: Assignee | null, onEdit: () => {}, + writeAccess: boolean, ...CssClasses, }; -const DisplayModePlain = ({ assignee, onEdit, classes }: Props) => ( +const DisplayModePlain = ({ assignee, onEdit, writeAccess, classes }: Props) => ( assignee ? (
{i18n.t('Event assigned to {{name}}', { name: assignee.name })} - + + +
) : ( - +
+ {i18n.t('No one is assigned to this event')} + + + +
) ); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js index 688c1a4852..6c492cd752 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js @@ -30,15 +30,6 @@ const WidgetAssigneePlain = ({ assignee, writeAccess, onSet, classes }: PlainPro [onSet], ); - const renderNoAccess = () => i18n.t('No one is assigned to this event'); - - const renderContent = () => - (editMode ? ( - setEditMode(false)} onSet={handleSet} /> - ) : ( - setEditMode(true)} /> - )); - return (
setOpenStatus(false), [setOpenStatus])} open={open} > -
{writeAccess ? renderContent() : renderNoAccess()}
+
+ {editMode ? ( + setEditMode(false)} onSet={handleSet} /> + ) : ( + setEditMode(true)} writeAccess={writeAccess} /> + )} +
); From 52ae166e624e040dbc04efc7cf40ed7c4753ba92 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Wed, 15 Nov 2023 09:06:05 +0100 Subject: [PATCH 08/20] feat: add delete assigned user functionality --- .../WidgetAssignee/index.js | 4 ++ i18n/en.pot | 4 +- .../ViewEventComponent/ViewEvent.component.js | 6 +-- .../WidgetAssignee/EditMode.component.js | 40 ++++++++++++------- .../WidgetAssignee.component.js | 2 +- .../WidgetAssignee/WidgetAssignee.types.js | 11 +++-- .../components/WidgetAssignee/converter.js | 19 +++++---- .../capture-core/converters/clientToServer.js | 19 ++++----- .../capture-core/converters/serverToClient.js | 10 ++--- 9 files changed, 64 insertions(+), 51 deletions(-) diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js index d8640ff681..9a1ac6dace 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js @@ -3,8 +3,10 @@ import { When, Then } from '@badeball/cypress-cucumber-preprocessor'; 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="dhis2-uicore-chip-remove"]').click(); cy.get('[data-test="capture-ui-input"]').type('Geetha'); cy.contains('Geetha Alwan').click(); + cy.get('[data-test="widget-assignee-save"]').click(); }); }); @@ -17,8 +19,10 @@ When('you assign the user Tracker demo User in the edit mode', () => { cy.get('[data-test="widget-assignee"]').within(() => { cy.get('[data-test="widget-assignee-edit"]').click(); + cy.get('[data-test="dhis2-uicore-chip-remove"]').click(); cy.get('[data-test="capture-ui-input"]').type('Tracker demo'); cy.contains('Tracker demo User').click(); + cy.get('[data-test="widget-assignee-save"]').click(); }); }); diff --git a/i18n/en.pot b/i18n/en.pot index bcd7d8dafd..1a56d19480 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-11-14T07:52:45.978Z\n" -"PO-Revision-Date: 2023-11-14T07:52:45.978Z\n" +"POT-Creation-Date: 2023-11-15T07:36:48.781Z\n" +"PO-Revision-Date: 2023-11-15T07:36:48.781Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." 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 900e437cec..b046751948 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 @@ -104,9 +104,9 @@ class ViewEventPlain extends Component { programStage={programStage} dataEntryKey={currentDataEntryKey} assignee={assignee} - onGetSaveContext={onGetAssignedUserSaveContext} - onSave={onSaveAssignee} - onSaveError={onSaveAssigneeError} + onGetAssignedUserSaveContext={onGetAssignedUserSaveContext} + onSaveAssignee={onSaveAssignee} + onSaveAssigneeError={onSaveAssigneeError} />
({ container: { @@ -17,34 +17,44 @@ const styles = () => ({ paddingRight: 5, }, buttonContainer: { - flexGrow: 0, - flexShrink: 0, + marginTop: spacers.dp8, }, }); type Props = { + assignee: Assignee | null, onCancel: () => {}, - onSet: (user: Assignee) => void, + onSet: (user: Assignee | null) => void, ...CssClasses, }; const EditModePlain = (props: Props) => { - const { onCancel, onSet, classes } = props; + const { onCancel, onSet, assignee, classes } = props; + const [tempUser, setTempUser] = useState(assignee); + + const onHandleSet = (user) => { + setTempUser(user); + }; + return (
- -
-
- + + + +
); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js index 6c492cd752..1f3d785481 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.component.js @@ -44,7 +44,7 @@ const WidgetAssigneePlain = ({ assignee, writeAccess, onSet, classes }: PlainPro >
{editMode ? ( - setEditMode(false)} onSet={handleSet} /> + setEditMode(false)} onSet={handleSet} assignee={assignee} /> ) : ( setEditMode(true)} writeAccess={writeAccess} /> )} diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js index 22d6e27a2e..96a22ca6f1 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js @@ -1,5 +1,4 @@ // @flow -import type { UserFormField } from '../FormFields/UserField'; export type Assignee = { id: string, @@ -10,17 +9,17 @@ export type Assignee = { } export type Props = {| - assignee: UserFormField | null, + assignee: Assignee | null, enabled: boolean, writeAccess: boolean, onGetSaveContext: () => { event: ApiEnrollmentEvent }, - onSave: (newAssignee: UserFormField) => void, - onSaveError: (prevAssignee: UserFormField | null) => void, + onSave: (newAssignee: Assignee) => void, + onSaveError: (prevAssignee: Assignee | null) => void, |}; export type PlainProps = {| - assignee: UserFormField | null, + assignee: Assignee | null, writeAccess: boolean, - onSet: (user: UserFormField) => void, + onSet: (user: Assignee | null) => void, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/converter.js b/src/core_modules/capture-core/components/WidgetAssignee/converter.js index 049e2becd6..735b8ddfb2 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/converter.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/converter.js @@ -1,11 +1,14 @@ // @flow import type { Assignee } from './WidgetAssignee.types'; -export const convertClientToServer = (assignee: Assignee): ApiAssignedUser => ({ - uid: assignee.id, - displayName: assignee.name, - username: assignee.username, - firstName: assignee.firstName, - surname: assignee.surname, -}); - +export const convertClientToServer = (assignee?: Assignee): ApiAssignedUser | null => ( + assignee + ? { + uid: assignee.id, + displayName: assignee.name, + username: assignee.username, + firstName: assignee.firstName, + surname: assignee.surname, + } + : null +); diff --git a/src/core_modules/capture-core/converters/clientToServer.js b/src/core_modules/capture-core/converters/clientToServer.js index e085c53a74..75dda2a646 100644 --- a/src/core_modules/capture-core/converters/clientToServer.js +++ b/src/core_modules/capture-core/converters/clientToServer.js @@ -30,15 +30,16 @@ function convertRange(parser: (value: any) => any, rangeValue: RangeValue) { }; } -function convertAssigneeToServer(assignee: Assignee): ApiAssignedUser { - return { - uid: assignee.id, - displayName: assignee.name, - username: assignee.username, - firstName: assignee.firstName, - surname: assignee.surname, - }; -} +const convertAssigneeToServer = (assignee?: Assignee): ApiAssignedUser | null => + (assignee + ? { + uid: assignee.id, + displayName: assignee.name, + username: assignee.username, + firstName: assignee.firstName, + surname: assignee.surname, + } + : null); const valueConvertersForType = { [dataElementTypes.NUMBER]: stringifyNumber, diff --git a/src/core_modules/capture-core/converters/serverToClient.js b/src/core_modules/capture-core/converters/serverToClient.js index 0115b1d7d6..288948461e 100644 --- a/src/core_modules/capture-core/converters/serverToClient.js +++ b/src/core_modules/capture-core/converters/serverToClient.js @@ -11,18 +11,14 @@ function convertTime(d2Value: string) { return parseData.momentTime; } -const convertAssignedUserToClient = (assignedUser?: ApiAssignedUser) => { - if (!assignedUser) { - return null; - } - return { +const convertAssignedUserToClient = (assignedUser?: ApiAssignedUser) => + ((assignedUser && assignedUser.uid) ? { id: assignedUser.uid, name: assignedUser.displayName, username: assignedUser.username, firstName: assignedUser.firstName, surname: assignedUser.surname, - }; -}; + } : null); const optionSetConvertersForType = { [dataElementTypes.NUMBER]: parseNumber, From 95453dd8d5e175849d8b6f45e3244a8a58021da1 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 4 Dec 2023 09:57:22 +0100 Subject: [PATCH 09/20] chore: clean trackedEntityInstance --- .../useCommonEnrollmentDomainData.types.js | 3 ++- .../EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js | 2 +- .../components/WidgetProfile/DataEntry/hooks/useEvents.js | 2 +- .../components/WidgetStagesAndEvents/types/common.types.js | 3 ++- src/core_modules/capture-core/flow/apiTypes.js | 3 ++- 5 files changed, 8 insertions(+), 5 deletions(-) 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 9b7273fdc1..1919cc56c6 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 @@ -17,10 +17,11 @@ export type Event = {| program: string, programStage: string, status: 'ACTIVE' | 'VISITED' | 'COMPLETED' | 'SCHEDULE' | 'OVERDUE' | 'SKIPPED', - trackedEntityInstance: string, + trackedEntity: string, notes?: Array, pendingApiResponse?: ?boolean, assignedUser?: ApiAssignedUser, + followUp?: boolean, |}; export type EnrollmentData = {| diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js index d8ff56e1b7..f1d24d1b6f 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/useRuleEffects/useRuleEffects.js @@ -33,7 +33,7 @@ const useEventsData = (enrollment, program) => { programId: event.program, programStageId: event.programStage, orgUnitId: event.orgUnit, - trackedEntityInstanceId: event.trackedEntityInstance, + trackedEntityInstanceId: event.trackedEntity, enrollmentId: event.enrollment, enrollmentStatus: event.enrollmentStatus, status: event.status, diff --git a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js index 0c900af9a4..222a1e33d6 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js +++ b/src/core_modules/capture-core/components/WidgetProfile/DataEntry/hooks/useEvents.js @@ -35,7 +35,7 @@ export const useEvents = (enrollment: any, elementsById: Array) => { programStageId: event.programStage, orgUnitId: event.orgUnit, orgUnitName: orgUnitNames[event.orgUnit], - trackedEntityInstanceId: event.trackedEntityInstance, + trackedEntityInstanceId: event.trackedEntity, enrollmentId: event.enrollment, enrollmentStatus: event.enrollmentStatus, status: event.status, 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 6b1ba76807..5989d738fd 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 @@ -52,8 +52,9 @@ export type Event = {| program: string, programStage: string, status: 'ACTIVE' | 'VISITED' | 'COMPLETED' | 'SCHEDULE' | 'OVERDUE' | 'SKIPPED', - trackedEntityInstance: string, + trackedEntity: string, notes?: Array, pendingApiResponse?: ?boolean, assignedUser?: ApiAssignedUser, + followUp?: boolean, |}; diff --git a/src/core_modules/capture-core/flow/apiTypes.js b/src/core_modules/capture-core/flow/apiTypes.js index fd501f45e4..5b8d819dc6 100644 --- a/src/core_modules/capture-core/flow/apiTypes.js +++ b/src/core_modules/capture-core/flow/apiTypes.js @@ -18,7 +18,7 @@ declare type ApiEnrollmentEvent = {| program: string, programStage: string, orgUnit: string, - trackedEntityInstance: string, + trackedEntity: string, enrollment: string, enrollmentStatus: string, status: 'ACTIVE' | 'VISITED' | 'COMPLETED' | 'SCHEDULE' | 'OVERDUE' | 'SKIPPED', @@ -30,6 +30,7 @@ declare type ApiEnrollmentEvent = {| deleted?: boolean, pendingApiResponse?: ?boolean, assignedUser?: ApiAssignedUser, + followUp?: boolean, |}; type ApiAttributeValues = { From 7a5ffe30cb06804f788effa0e5d6221f84a6c86f Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 4 Dec 2023 15:08:04 +0100 Subject: [PATCH 10/20] chore: clean trackedEntityInstance from payload --- .../DataEntry/epics/getConvertedNewSingleEvent.js | 2 +- .../WidgetEnrollmentEventNew/Validated/getConvertedAddEvent.js | 2 +- src/core_modules/capture-core/events/eventRequests.js | 2 +- .../events/mainConverters/mainEventClientToServerConverter.js | 2 +- src/core_modules/capture-core/events/prepareEnrollmentEvents.js | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/getConvertedNewSingleEvent.js b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/getConvertedNewSingleEvent.js index d862669ae5..338d00e3ae 100644 --- a/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/getConvertedNewSingleEvent.js +++ b/src/core_modules/capture-core/components/DataEntries/SingleEventRegistrationEntry/DataEntryWrapper/DataEntry/epics/getConvertedNewSingleEvent.js @@ -72,7 +72,7 @@ export const getAddEventEnrollmentServerData = (state: ReduxState, program: programId, programStage: formFoundation.id, orgUnit: orgUnitId, - trackedEntityInstance: teiId, + trackedEntity: teiId, enrollment: enrollmentId, ...getApiCategoriesArgument(state.currentSelections.categories), dataValues: Object diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedAddEvent.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedAddEvent.js index f1eeab8b32..db0b0e9af7 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedAddEvent.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/getConvertedAddEvent.js @@ -44,7 +44,7 @@ export const getAddEventEnrollmentServerData = ({ program: programId, programStage: formFoundation.id, orgUnit: orgUnitId, - trackedEntityInstance: teiId, + trackedEntity: teiId, enrollment: enrollmentId, scheduledAt: mainDataServerValues.occurredAt, orgUnitName, diff --git a/src/core_modules/capture-core/events/eventRequests.js b/src/core_modules/capture-core/events/eventRequests.js index e598b93a16..4039fc3895 100644 --- a/src/core_modules/capture-core/events/eventRequests.js +++ b/src/core_modules/capture-core/events/eventRequests.js @@ -60,7 +60,7 @@ const mapEventInputKeyToOutputKey = { program: 'programId', programStage: 'programStageId', orgUnit: 'orgUnitId', - trackedEntityInstance: 'trackedEntityInstanceId', + trackedEntity: 'trackedEntityId', enrollment: 'enrollmentId', }; diff --git a/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js b/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js index 37f3dc635c..4b7344eac9 100644 --- a/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js +++ b/src/core_modules/capture-core/events/mainConverters/mainEventClientToServerConverter.js @@ -10,7 +10,7 @@ export function convertMainEventClientToServer(event: Object) { programId: 'program', programStageId: 'programStage', orgUnitId: 'orgUnit', - trackedEntityInstanceId: 'trackedEntityInstance', + trackedEntityId: 'trackedEntity', enrollmentId: 'enrollment', assignee: 'assignedUser', }; diff --git a/src/core_modules/capture-core/events/prepareEnrollmentEvents.js b/src/core_modules/capture-core/events/prepareEnrollmentEvents.js index c059182f99..31e19524e1 100644 --- a/src/core_modules/capture-core/events/prepareEnrollmentEvents.js +++ b/src/core_modules/capture-core/events/prepareEnrollmentEvents.js @@ -24,7 +24,7 @@ const mapEventInputKeyToOutputKey = { program: 'programId', programStage: 'programStageId', orgUnit: 'orgUnitId', - trackedEntityInstance: 'trackedEntityInstanceId', + trackedEntity: 'trackedEntityId', enrollment: 'enrollmentId', }; From 667b1cb07e0c4e993f1e4e5d7ee1563e7ede67fa Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 4 Dec 2023 15:18:40 +0100 Subject: [PATCH 11/20] fix: hide assignee in form when rescheaduling an event --- .../NewEventWorkspace/NewEventWorkspace.component.js | 1 + .../EnrollmentEditEventPage.component.js | 3 +++ .../EditEventDataEntry.component.js | 4 ++++ .../WidgetEventEdit/WidgetEventEdit.container.js | 2 ++ .../WidgetEventEdit/widgetEventEdit.types.js | 2 ++ .../WidgetEventSchedule.actions.js | 2 +- .../WidgetEventSchedule.container.js | 11 +++++++++-- .../WidgetEventSchedule/widgetEventSchedule.types.js | 4 +++- .../capture-core/converters/clientToServer.js | 2 +- 9 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js index 4fdb52e652..e9f2dedb08 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentAddEvent/NewEventWorkspace/NewEventWorkspace.component.js @@ -105,6 +105,7 @@ const NewEventWorkspacePlain = ({ onSave={onSave} onCancel={onCancel} hideDueDate={stage?.hideDueDate} + enableUserAssignment />} 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 97360e8fa3..1a0620e262 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 @@ -67,6 +67,7 @@ const EnrollmentEditEventPageLeft = ({ pageStatus, onCancelEditEvent, onHandleScheduleSave, + assignee, }) => ( <> {pageStatus === pageStatuses.DEFAULT && programStage && ( @@ -81,6 +82,7 @@ const EnrollmentEditEventPageLeft = ({ initialScheduleDate={scheduleDate} onCancelEditEvent={onCancelEditEvent} onHandleScheduleSave={onHandleScheduleSave} + assignee={assignee} /> )} {pageStatus === pageStatuses.MISSING_DATA && ( @@ -244,6 +246,7 @@ const EnrollmentEditEventPagePain = ({ pageStatus={pageStatus} onCancelEditEvent={onCancelEditEvent} onHandleScheduleSave={onHandleScheduleSave} + assignee={assignee} />
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js index 5542c3e8d4..3f39dd6b3f 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.js @@ -48,6 +48,7 @@ import { withDataEntryFields, } from '../../DataEntryDhis2Helpers/'; import { getProgramThrowIfNotFound, EventProgram } from '../../../metaData'; +import type { UserFormField } from '../../FormFields/UserField'; const tabMode = Object.freeze({ REPORT: 'REPORT', @@ -398,6 +399,7 @@ type Props = { eventStatus?: string, enrollmentId?: string, isCompleted?: boolean, + assignee?: UserFormField | null, }; @@ -458,6 +460,7 @@ class EditEventDataEntryPlain extends Component { classes, dataEntryId, onCancelEditEvent, + assignee, ...passOnProps } = this.props; @@ -485,6 +488,7 @@ class EditEventDataEntryPlain extends Component { orgUnitId={orgUnit.id} onSaveSuccessActionType={actionTypes.EVENT_SCHEDULE_SUCCESS} onSaveErrorActionType={actionTypes.EVENT_SCHEDULE_ERROR} + assignee={assignee} {...passOnProps} />}
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js index e2096656ab..26f0b34890 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -58,6 +58,7 @@ export const WidgetEventEditPlain = ({ orgUnitId, enrollmentId, teiId, + assignee, }: Props) => { const dispatch = useDispatch(); const { currentPageMode } = useEnrollmentEditEventPageMode(eventStatus); @@ -144,6 +145,7 @@ export const WidgetEventEditPlain = ({ allowGenerateNextVisit={programStage.allowGenerateNextVisit} availableProgramStages={availableProgramStages} hideDueDate={programStage.hideDueDate} + assignee={assignee} /> )} diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js b/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js index 8dbc06e0c6..01e4caade5 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/widgetEventEdit.types.js @@ -1,6 +1,7 @@ // @flow import type { ProgramStage } from '../../metaData'; +import type { UserFormField } from '../FormFields/UserField'; export type Props = {| programStage: ProgramStage, @@ -13,5 +14,6 @@ export type Props = {| enrollmentId: string, teiId: string, initialScheduleDate?: string, + assignee?: UserFormField | null, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js index cccaec60a2..cf7b02e10d 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.actions.js @@ -35,7 +35,7 @@ export const requestScheduleEvent = ({ onSaveExternal: (eventServerValues: Object, uid: string) => void, onSaveSuccessActionType?: string, onSaveErrorActionType?: string, - assignedUser?: ApiAssignedUser, + assignedUser?: ApiAssignedUser | null, }) => actionCreator(scheduleEventWidgetActionTypes.EVENT_SCHEDULE_REQUEST)({ scheduleDate, diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js index 7d66e9aea8..27cf5bc8b2 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/WidgetEventSchedule.container.js @@ -31,6 +31,8 @@ export const WidgetEventSchedule = ({ onSaveErrorActionType, onCancel, initialScheduleDate, + enableUserAssignment, + assignee: storedAssignee, ...passOnProps }: ContainerProps) => { const { program, stage } = useMemo(() => getProgramAndStageForProgram(programId, stageId), [programId, stageId]); @@ -44,7 +46,7 @@ export const WidgetEventSchedule = ({ const { currentUser, noteId } = useCommentDetails(); const [scheduleDate, setScheduleDate] = useState(''); const [comments, setComments] = useState([]); - const [assignee, setAssignee] = useState(); + const [assignee, setAssignee] = useState(storedAssignee); const { events } = useEventsInOrgUnit(orgUnitId, scheduleDate); const { eventId } = useLocationQuery(); const eventCountInOrgUnit = events @@ -57,6 +59,10 @@ export const WidgetEventSchedule = ({ if (!scheduleDate && suggestedScheduleDate) { setScheduleDate(suggestedScheduleDate); } }, [suggestedScheduleDate, scheduleDate]); + useEffect(() => { + setAssignee(storedAssignee); + }, [storedAssignee]); + const onHandleSchedule = useCallback(() => { if (programCategory?.categories && Object.keys(selectedCategories).length !== programCategory?.categories?.length) { @@ -82,6 +88,7 @@ export const WidgetEventSchedule = ({ onSaveExternal: onSave, onSaveSuccessActionType, onSaveErrorActionType, + // $FlowFixMe[incompatible-call] ...(assignee && { assignedUser: convertClientToServer(assignee, dataElementTypes.ASSIGNEE) }), })); }, [ @@ -170,7 +177,7 @@ export const WidgetEventSchedule = ({ programId={programId} programCategory={programCategory} programName={program.name} - enableUserAssignment={stage?.enableUserAssignment} + enableUserAssignment={enableUserAssignment && stage?.enableUserAssignment} scheduleDate={scheduleDate} displayDueDateLabel={programStageScheduleConfig.displayDueDateLabel} suggestedScheduleDate={suggestedScheduleDate} diff --git a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js index cfae040a82..39b5789226 100644 --- a/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js +++ b/src/core_modules/capture-core/components/WidgetEventSchedule/widgetEventSchedule.types.js @@ -19,6 +19,8 @@ export type ContainerProps = {| onSaveSuccessActionType: string, onSaveErrorActionType: string, onCancel: () => void, + assignee?: UserFormField | null, + enableUserAssignment?: boolean, |}; export type Props = {| @@ -42,7 +44,7 @@ export type Props = {| enableUserAssignment?: boolean, onSchedule: () => void, onSetAssignee: () => void, - assignee?: UserFormField, + assignee?: UserFormField | null, onCancel: () => void, setScheduleDate: (date: string) => void, onAddComment: (comment: string) => void, diff --git a/src/core_modules/capture-core/converters/clientToServer.js b/src/core_modules/capture-core/converters/clientToServer.js index 75dda2a646..c59c2580ec 100644 --- a/src/core_modules/capture-core/converters/clientToServer.js +++ b/src/core_modules/capture-core/converters/clientToServer.js @@ -30,7 +30,7 @@ function convertRange(parser: (value: any) => any, rangeValue: RangeValue) { }; } -const convertAssigneeToServer = (assignee?: Assignee): ApiAssignedUser | null => +const convertAssigneeToServer = (assignee?: Assignee | null): ApiAssignedUser | null => (assignee ? { uid: assignee.id, From 6aac3b82c11ad59b17de3c2654ebe950755f1b3a Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 22 Jan 2024 14:30:12 +0100 Subject: [PATCH 12/20] chore: remove on prefix --- .../EnrollmentEditEventPage.component.js | 8 ++++---- .../EnrollmentEditEventPage.container.js | 4 ++-- .../EnrollmentEditEvent/EnrollmentEditEventPage.types.js | 2 +- .../AssigneeSection/AssigneeSection.container.js | 6 +++--- .../ViewEvent/ViewEventComponent/ViewEvent.component.js | 6 +++--- .../ViewEvent/ViewEventComponent/ViewEvent.container.js | 2 +- .../ViewEvent/ViewEventComponent/viewEvent.selectors.js | 4 ++-- .../components/WidgetAssignee/WidgetAssignee.container.js | 6 +++--- .../components/WidgetAssignee/WidgetAssignee.types.js | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) 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 1a0620e262..16d527e887 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 @@ -113,7 +113,7 @@ const EnrollmentEditEventPageRight = ({ assignee, onEnrollmentError, onEnrollmentSuccess, - onGetAssignedUserSaveContext, + getAssignedUserSaveContext, onSaveAssignee, onSaveAssigneeError, addRelationShipContainerElement, @@ -123,7 +123,7 @@ const EnrollmentEditEventPageRight = ({ { @@ -267,7 +267,7 @@ const EnrollmentEditEventPagePain = ({ assignee={assignee} onEnrollmentError={onEnrollmentError} onEnrollmentSuccess={onEnrollmentSuccess} - onGetAssignedUserSaveContext={onGetAssignedUserSaveContext} + getAssignedUserSaveContext={getAssignedUserSaveContext} onSaveAssignee={onSaveAssignee} onSaveAssigneeError={onSaveAssigneeError} addRelationShipContainerElement={addRelationShipContainerElement} 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 690e83141e..7c3de5ae8b 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 @@ -155,7 +155,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ event, }); const assignee = useAssignee(event); - const onGetAssignedUserSaveContext = useAssignedUserSaveContext(event); + const getAssignedUserSaveContext = useAssignedUserSaveContext(event); const onSaveAssignee = (newAssignee) => { // $FlowFixMe dataElementTypes flow error const assignedUser: ApiAssignedUser = convertClientToServer(newAssignee, dataElementTypes.ASSIGNEE); @@ -197,7 +197,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ scheduleDate={scheduleDate} onCancelEditEvent={onCancelEditEvent} onHandleScheduleSave={onHandleScheduleSave} - onGetAssignedUserSaveContext={onGetAssignedUserSaveContext} + getAssignedUserSaveContext={getAssignedUserSaveContext} onSaveAssignee={onSaveAssignee} onSaveAssigneeError={onSaveAssigneeError} /> 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 d73fc3abc9..ed95cad492 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 @@ -33,7 +33,7 @@ export type PlainProps = {| read: boolean, write: boolean, |} | null, - onGetAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, + getAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, assignee: UserFormField | null, onSaveAssignee: (newAssignee: UserFormField) => void, onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, 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 index a28c47d541..4698089e83 100644 --- 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 @@ -11,7 +11,7 @@ type Props = {| read: boolean, write: boolean, |} | null, - onGetAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, + getAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, onSaveAssignee: (newAssignee: UserFormField) => void, onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, |}; @@ -19,7 +19,7 @@ type Props = {| export const AssigneeSection = ({ assignee, programStage, - onGetAssignedUserSaveContext, + getAssignedUserSaveContext, eventAccess, onSaveAssignee, onSaveAssigneeError, @@ -27,7 +27,7 @@ export const AssigneeSection = ({ { event: ApiEnrollmentEvent }, + getAssignedUserSaveContext: () => { event: ApiEnrollmentEvent }, onSaveAssignee: (newAssignee: UserFormField) => void, onSaveAssigneeError: (prevAssignee: UserFormField | null) => void, }; @@ -80,7 +80,7 @@ class ViewEventPlain extends Component { currentDataEntryKey, eventAccess, assignee, - onGetAssignedUserSaveContext, + getAssignedUserSaveContext, onSaveAssignee, onSaveAssigneeError, } = this.props; @@ -104,7 +104,7 @@ class ViewEventPlain extends Component { programStage={programStage} dataEntryKey={currentDataEntryKey} assignee={assignee} - onGetAssignedUserSaveContext={onGetAssignedUserSaveContext} + getAssignedUserSaveContext={getAssignedUserSaveContext} onSaveAssignee={onSaveAssignee} onSaveAssigneeError={onSaveAssigneeError} /> diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.container.js b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.container.js index e15d6d1dba..35186a838e 100644 --- a/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.container.js +++ b/src/core_modules/capture-core/components/Pages/ViewEvent/ViewEventComponent/ViewEvent.container.js @@ -32,7 +32,7 @@ const makeMapStateToProps = () => { currentDataEntryKey, isUserInteractionInProgress, assignee: state.viewEventPage.loadedValues?.eventContainer.event.assignee, - onGetAssignedUserSaveContext: () => assignedUserContextSelector(state), + getAssignedUserSaveContext: () => assignedUserContextSelector(state), eventId: state.viewEventPage.eventId, }; }; 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 dc62894624..1643d9854d 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 @@ -23,7 +23,7 @@ export const makeEventAccessSelector = () => createSelector( export const makeAssignedUserContextSelector = () => // $FlowFixMe[missing-annot] - createSelector(eventContainerSelector, eventIdSelector, (eventContainer, eventId) => { + createSelector(eventContainerSelector, eventIdSelector, (eventContainer) => { const { event: clientMainValues, values: clientValues } = eventContainer; const program = getEventProgramThrowIfNotFound(clientMainValues.programId); const formFoundation = program.stage.stageForm; @@ -39,5 +39,5 @@ export const makeAssignedUserContextSelector = () => })), }; - return { eventId, event }; + return { event }; }); diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js index 6d60bd81af..99769c8656 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js @@ -6,7 +6,7 @@ import { WidgetAssigneeComponent } from './WidgetAssignee.component'; import { convertClientToServer } from './converter'; const WidgetAssigneeWithHooks = (props: Props) => { - const { assignee, writeAccess, onGetSaveContext, onSave, onSaveError } = props; + const { assignee, writeAccess, getSaveContext, onSave, onSaveError } = props; const prevAssignee = useRef(assignee); const [updateMutation] = useDataMutation( @@ -24,12 +24,12 @@ const WidgetAssigneeWithHooks = (props: Props) => { const onSet = useCallback( async (newAssignee: Assignee) => { - const { event } = onGetSaveContext(); + const { event } = getSaveContext(); prevAssignee.current = assignee; onSave(newAssignee); await updateMutation({ ...event, assignedUser: convertClientToServer(newAssignee) }); }, - [updateMutation, onGetSaveContext, onSave, assignee], + [updateMutation, getSaveContext, onSave, assignee], ); 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 index 96a22ca6f1..03cffa05ad 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js @@ -12,7 +12,7 @@ export type Props = {| assignee: Assignee | null, enabled: boolean, writeAccess: boolean, - onGetSaveContext: () => { event: ApiEnrollmentEvent }, + getSaveContext: () => { event: ApiEnrollmentEvent }, onSave: (newAssignee: Assignee) => void, onSaveError: (prevAssignee: Assignee | null) => void, |}; From 2bcd8e3e3dd43e17634086d2bed28a59f043e646 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 22 Jan 2024 15:05:21 +0100 Subject: [PATCH 13/20] feat: use UserAvatar component --- i18n/en.pot | 17 +++++++---------- .../WidgetAssignee/DisplayMode.component.js | 17 +++++++++++------ 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index f0122cd94e..cfe2c464c0 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: 2024-01-05T14:09:35.742Z\n" -"PO-Revision-Date: 2024-01-05T14:09:35.742Z\n" +"POT-Creation-Date: 2024-01-22T13:52:51.172Z\n" +"PO-Revision-Date: 2024-01-22T13:52:51.172Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1098,12 +1098,15 @@ msgstr "To work with the selected program," msgid "open the Tracker Capture app" msgstr "open the Tracker Capture app" -msgid "Event assigned to {{name}}" -msgstr "Event assigned to {{name}}" +msgid "Assigned to" +msgstr "Assigned to" msgid "You don't have access to edit this assignee" msgstr "You don't have access to edit this assignee" +msgid "Edit" +msgstr "Edit" + msgid "No one is assigned to this event" msgstr "No one is assigned to this event" @@ -1188,9 +1191,6 @@ msgstr "Latitude" msgid "Longitude" msgstr "Longitude" -msgid "Edit" -msgstr "Edit" - msgid "Set coordinates" msgstr "Set coordinates" @@ -1386,9 +1386,6 @@ msgstr "This stage can only have one event" msgid "Events could not be retrieved. Please try again later." msgstr "Events could not be retrieved. Please try again later." -msgid "Assigned to" -msgstr "Assigned to" - msgid "{{ totalEvents }} events" msgstr "{{ totalEvents }} events" diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index e0ff6861da..41216aa6ad 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -1,7 +1,7 @@ // @flow import React from 'react'; import i18n from '@dhis2/d2-i18n'; -import { IconEdit24, Button, spacers } from '@dhis2/ui'; +import { Button, spacers, UserAvatar } from '@dhis2/ui'; import { withStyles } from '@material-ui/core/styles'; import { ConditionalTooltip } from 'capture-core/components/Tooltips/ConditionalTooltip'; import type { Assignee } from './WidgetAssignee.types'; @@ -12,14 +12,14 @@ const styles = () => ({ alignItems: 'center', }, editButton: { - border: 'none !important', - borderRadius: '50% !important', - padding: '0px 6px !important', margin: spacers.dp4, }, assignButton: { margin: spacers.dp4, }, + avatar: { + margin: spacers.dp4, + }, }); type Props = { @@ -32,7 +32,11 @@ type Props = { const DisplayModePlain = ({ assignee, onEdit, writeAccess, classes }: Props) => ( assignee ? (
- {i18n.t('Event assigned to {{name}}', { name: assignee.name })} +
+ {i18n.t('Assigned to')} + + {assignee.name} +
className={classes.editButton} dataTest="widget-assignee-edit" disabled={!writeAccess} + secondary > - + {i18n.t('Edit')}
From 3bd6cd0b64c5098f582198a855d548a03f10dc80 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 22 Jan 2024 16:13:34 +0100 Subject: [PATCH 14/20] chore: remove eventIdSelector --- .../Pages/ViewEvent/ViewEventComponent/viewEvent.selectors.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 1643d9854d..afb7761610 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 @@ -8,7 +8,6 @@ import { convertMainEventClientToServer } from '../../../../events/mainConverter 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( @@ -23,7 +22,7 @@ export const makeEventAccessSelector = () => createSelector( export const makeAssignedUserContextSelector = () => // $FlowFixMe[missing-annot] - createSelector(eventContainerSelector, eventIdSelector, (eventContainer) => { + createSelector(eventContainerSelector, (eventContainer) => { const { event: clientMainValues, values: clientValues } = eventContainer; const program = getEventProgramThrowIfNotFound(clientMainValues.programId); const formFoundation = program.stage.stageForm; From da212bcf6d74c2e22e8b8b66b269058819b4498e Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 23 Jan 2024 11:26:41 +0100 Subject: [PATCH 15/20] feat: get the user avatar id with react query --- .../WidgetAssignee/DisplayMode.component.js | 11 ++++++++--- .../WidgetAssignee.component.js | 9 +++++++-- .../WidgetAssignee.container.js | 4 +++- .../WidgetAssignee/WidgetAssignee.types.js | 1 + .../components/WidgetAssignee/hooks/index.js | 2 ++ .../WidgetAssignee/hooks/useUserAvatar.js | 19 +++++++++++++++++++ 6 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/hooks/index.js create mode 100644 src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index 41216aa6ad..621a1d489d 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -17,6 +17,10 @@ const styles = () => ({ assignButton: { margin: spacers.dp4, }, + avatarWrapper: { + display: 'flex', + alignItems: 'center', + }, avatar: { margin: spacers.dp4, }, @@ -26,15 +30,16 @@ type Props = { assignee: Assignee | null, onEdit: () => {}, writeAccess: boolean, + avatarId?: string, ...CssClasses, }; -const DisplayModePlain = ({ assignee, onEdit, writeAccess, classes }: Props) => ( +const DisplayModePlain = ({ assignee, onEdit, writeAccess, avatarId, classes }: Props) => ( assignee ? (
-
+
{i18n.t('Assigned to')} - + {assignee.name}
({ }, }); -const WidgetAssigneePlain = ({ assignee, writeAccess, onSet, classes }: PlainProps) => { +const WidgetAssigneePlain = ({ assignee, writeAccess, onSet, avatarId, classes }: PlainProps) => { const [open, setOpenStatus] = useState(true); const [editMode, setEditMode] = useState(false); @@ -46,7 +46,12 @@ const WidgetAssigneePlain = ({ assignee, writeAccess, onSet, classes }: PlainPro {editMode ? ( setEditMode(false)} onSet={handleSet} assignee={assignee} /> ) : ( - setEditMode(true)} writeAccess={writeAccess} /> + setEditMode(true)} + writeAccess={writeAccess} + avatarId={avatarId} + /> )}
diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js index 99769c8656..e6ea6dfd0b 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js @@ -4,10 +4,12 @@ import { useDataMutation } from '@dhis2/app-runtime'; import type { Props, Assignee } from './WidgetAssignee.types'; import { WidgetAssigneeComponent } from './WidgetAssignee.component'; import { convertClientToServer } from './converter'; +import { useUserAvatar } from './hooks'; const WidgetAssigneeWithHooks = (props: Props) => { const { assignee, writeAccess, getSaveContext, onSave, onSaveError } = props; const prevAssignee = useRef(assignee); + const { avatarId } = useUserAvatar(assignee?.id); const [updateMutation] = useDataMutation( { @@ -32,7 +34,7 @@ const WidgetAssigneeWithHooks = (props: Props) => { [updateMutation, getSaveContext, onSave, assignee], ); - return ; + return ; }; export const WidgetAssignee = (props: Props) => { diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js index 03cffa05ad..33eb25a570 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.types.js @@ -21,5 +21,6 @@ export type PlainProps = {| assignee: Assignee | null, writeAccess: boolean, onSet: (user: Assignee | null) => void, + avatarId?: string, ...CssClasses, |}; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/hooks/index.js b/src/core_modules/capture-core/components/WidgetAssignee/hooks/index.js new file mode 100644 index 0000000000..d6927c9a30 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/hooks/index.js @@ -0,0 +1,2 @@ +// @flow +export { useUserAvatar } from './useUserAvatar'; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js b/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js new file mode 100644 index 0000000000..fd6d12d375 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js @@ -0,0 +1,19 @@ +// @flow +import { useApiMetadataQuery } from 'capture-core/utils/reactQueryHelpers'; + +export const useUserAvatar = (userId?: string) => { + const queryKey = ['users', ...(userId ? [userId] : [])]; + const queryFn = { + resource: 'users', + id: userId, + params: { + fields: 'avatar', + }, + }; + const queryOptions = { enabled: Boolean(userId) }; + const { data } = useApiMetadataQuery(queryKey, queryFn, queryOptions); + + return { + avatarId: data?.avatar?.id, + }; +}; From c96bac8df25a77335e0c87bfa4a4ae35897ba642 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 23 Jan 2024 12:45:05 +0100 Subject: [PATCH 16/20] fix: wait for the user avatar to load --- .../components/WidgetAssignee/WidgetAssignee.container.js | 6 +++++- .../components/WidgetAssignee/hooks/useUserAvatar.js | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js index e6ea6dfd0b..53910eac42 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/WidgetAssignee.container.js @@ -9,7 +9,7 @@ import { useUserAvatar } from './hooks'; const WidgetAssigneeWithHooks = (props: Props) => { const { assignee, writeAccess, getSaveContext, onSave, onSaveError } = props; const prevAssignee = useRef(assignee); - const { avatarId } = useUserAvatar(assignee?.id); + const { avatarId, isLoading } = useUserAvatar(assignee?.id); const [updateMutation] = useDataMutation( { @@ -34,6 +34,10 @@ const WidgetAssigneeWithHooks = (props: Props) => { [updateMutation, getSaveContext, onSave, assignee], ); + if (isLoading) { + return null; + } + return ; }; diff --git a/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js b/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js index fd6d12d375..2e66e93561 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/hooks/useUserAvatar.js @@ -11,9 +11,10 @@ export const useUserAvatar = (userId?: string) => { }, }; const queryOptions = { enabled: Boolean(userId) }; - const { data } = useApiMetadataQuery(queryKey, queryFn, queryOptions); + const { data, isLoading } = useApiMetadataQuery(queryKey, queryFn, queryOptions); return { avatarId: data?.avatar?.id, + isLoading, }; }; From 27d4ce207cbf48c8d84cd1c0e777b8f82b8f30f3 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 23 Jan 2024 13:30:56 +0100 Subject: [PATCH 17/20] chore: apply mockup css properties --- .../components/WidgetAssignee/DisplayMode.component.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index 621a1d489d..507d096762 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -12,7 +12,7 @@ const styles = () => ({ alignItems: 'center', }, editButton: { - margin: spacers.dp4, + marginLeft: spacers.dp12, }, assignButton: { margin: spacers.dp4, @@ -20,6 +20,7 @@ const styles = () => ({ avatarWrapper: { display: 'flex', alignItems: 'center', + fontSize: 14, }, avatar: { margin: spacers.dp4, @@ -39,7 +40,7 @@ const DisplayModePlain = ({ assignee, onEdit, writeAccess, avatarId, classes }:
{i18n.t('Assigned to')} - + {assignee.name}
{i18n.t('Edit')} From 8529d353d13a27302a6bc2f89472e29691dd5fe8 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 23 Jan 2024 15:18:55 +0100 Subject: [PATCH 18/20] test: fix 2.38 cypress failures --- .../components/WidgetAssignee/DisplayMode.component.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index 507d096762..5140a4b02c 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -10,17 +10,17 @@ const styles = () => ({ wrapper: { display: 'flex', alignItems: 'center', + fontSize: 14, }, editButton: { marginLeft: spacers.dp12, }, assignButton: { - margin: spacers.dp4, + marginLeft: spacers.dp12, }, avatarWrapper: { display: 'flex', alignItems: 'center', - fontSize: 14, }, avatar: { margin: spacers.dp4, @@ -36,7 +36,7 @@ type Props = { }; const DisplayModePlain = ({ assignee, onEdit, writeAccess, avatarId, classes }: Props) => ( - assignee ? ( + assignee?.name ? (
{i18n.t('Assigned to')} @@ -71,6 +71,7 @@ const DisplayModePlain = ({ assignee, onEdit, writeAccess, avatarId, classes }: className={classes.assignButton} dataTest="widget-assignee-assign" small + secondary disabled={!writeAccess} > {i18n.t('Assign')} From c272d46574195368af60a3d07810fa35e427262c Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 23 Jan 2024 15:58:37 +0100 Subject: [PATCH 19/20] test: scenario for removing the assigned user --- .../WidgetAssignee/index.js | 17 +++++++++++++++-- .../WidgetsForEnrollmentEditEvent.feature | 4 +++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js b/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js index 9a1ac6dace..9f45c31e2f 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetAssignee/index.js @@ -2,8 +2,7 @@ import { When, Then } from '@badeball/cypress-cucumber-preprocessor'; 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="dhis2-uicore-chip-remove"]').click(); + cy.get('[data-test="widget-assignee-assign"]').click(); cy.get('[data-test="capture-ui-input"]').type('Geetha'); cy.contains('Geetha Alwan').click(); cy.get('[data-test="widget-assignee-save"]').click(); @@ -26,6 +25,14 @@ When('you assign the user Tracker demo User in the edit mode', () => { }); }); +When('you remove the assigned user', () => { + cy.get('[data-test="widget-assignee"]').within(() => { + cy.get('[data-test="widget-assignee-edit"]').click(); + cy.get('[data-test="dhis2-uicore-chip-remove"]').click(); + cy.get('[data-test="widget-assignee-save"]').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'); @@ -37,3 +44,9 @@ Then('the event has the user Tracker demo User assigned', () => { cy.get('[data-test="widget-contents"]').contains('Tracker demo User').should('exist'); }); }); + +Then('the event has no assignd user', () => { + cy.get('[data-test="widget-assignee"]').within(() => { + cy.get('[data-test="widget-contents"]').contains('No one is assigned to this event').should('exist'); + }); +}); diff --git a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature index c333e2b547..27dae1fd6a 100644 --- a/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature +++ b/cypress/e2e/WidgetsForEnrollmentPages/WidgetsForEnrollmentEditEvent.feature @@ -105,8 +105,10 @@ Feature: The user interacts with the widgets on the enrollment edit event 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 + Given you land on the enrollment edit event page by having typed /#/enrollmentEventEdit?eventId=veuwiLC2x0e&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 + When you remove the assigned user + Then the event has no assignd user From 094b32e527a2dbb3ee6e57b06e99f40117fb05a0 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 25 Jan 2024 09:27:14 +0100 Subject: [PATCH 20/20] fix: fallback to firstname and surname --- .../components/WidgetAssignee/DisplayMode.component.js | 2 +- src/core_modules/capture-core/converters/serverToClient.js | 2 +- src/core_modules/capture-core/flow/apiTypes.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js index 5140a4b02c..e0b2ebb2dd 100644 --- a/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js +++ b/src/core_modules/capture-core/components/WidgetAssignee/DisplayMode.component.js @@ -36,7 +36,7 @@ type Props = { }; const DisplayModePlain = ({ assignee, onEdit, writeAccess, avatarId, classes }: Props) => ( - assignee?.name ? ( + assignee ? (
{i18n.t('Assigned to')} diff --git a/src/core_modules/capture-core/converters/serverToClient.js b/src/core_modules/capture-core/converters/serverToClient.js index 288948461e..696feca4b7 100644 --- a/src/core_modules/capture-core/converters/serverToClient.js +++ b/src/core_modules/capture-core/converters/serverToClient.js @@ -14,7 +14,7 @@ function convertTime(d2Value: string) { const convertAssignedUserToClient = (assignedUser?: ApiAssignedUser) => ((assignedUser && assignedUser.uid) ? { id: assignedUser.uid, - name: assignedUser.displayName, + name: assignedUser.displayName || `${assignedUser.firstName} ${assignedUser.surname}`, username: assignedUser.username, firstName: assignedUser.firstName, surname: assignedUser.surname, diff --git a/src/core_modules/capture-core/flow/apiTypes.js b/src/core_modules/capture-core/flow/apiTypes.js index 5b8d819dc6..491870d8c2 100644 --- a/src/core_modules/capture-core/flow/apiTypes.js +++ b/src/core_modules/capture-core/flow/apiTypes.js @@ -3,7 +3,7 @@ declare type ApiAssignedUser = {| uid: string, username: string, - displayName: string, + displayName?: string, firstName: string, surname: string, |};