From 1e9b3f9cd479e4a3db49494ee540d2b9d44fc073 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Thu, 19 Dec 2024 15:07:00 +0100 Subject: [PATCH] feat: show related stages widget in view event page --- i18n/en.pot | 16 ++- ...llmentWithFirstStageDataEntry.container.js | 4 +- .../getConvertedRelatedStageEvent.types.js | 4 +- .../EnrollmentEditEventPage.component.js | 2 + .../EnrollmentEditEventPage.container.js | 12 ++ .../EnrollmentEditEventPage.types.js | 1 + .../PageLayout/DefaultPageLayout.constants.js | 6 + .../DefaultEnrollmentLayout.types.js | 1 + .../LayoutComponentConfig.js | 30 +++++ .../Validated/Validated.component.js | 4 +- .../RelatedStagesActions.component.js | 76 +++++++---- .../RelatedStagesActions.container.js} | 26 ++-- .../RelatedStagesActions.types.js | 18 ++- .../RelatedStagesActions/index.js | 2 +- .../WidgetRelatedStages.container.js | 126 ++++++++++++++++++ .../WidgetRelatedStages.types.js | 11 +- .../WidgetRelatedStages/hooks/index.js | 7 + .../hooks/useAddEventWithRelationship.js | 61 +++++++++ .../hooks/useBuildRelatedStageEventPayload.js | 95 +++++++++++++ .../{ => hooks}/useRelatedStages.js | 10 +- .../components/WidgetRelatedStages/index.js | 3 +- .../WidgetTwoEventWorkspace/hooks/index.js | 2 + .../WidgetTwoEventWorkspace/index.js | 1 + 23 files changed, 458 insertions(+), 60 deletions(-) rename src/core_modules/capture-core/components/WidgetRelatedStages/{WidgetRelatedStages.component.js => RelatedStagesActions/RelatedStagesActions.container.js} (82%) create mode 100644 src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.container.js create mode 100644 src/core_modules/capture-core/components/WidgetRelatedStages/hooks/index.js create mode 100644 src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useAddEventWithRelationship.js create mode 100644 src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useBuildRelatedStageEventPayload.js rename src/core_modules/capture-core/components/WidgetRelatedStages/{ => hooks}/useRelatedStages.js (90%) diff --git a/i18n/en.pot b/i18n/en.pot index d4d4fb3813..88458264ab 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-12-05T11:39:04.447Z\n" -"PO-Revision-Date: 2024-12-05T11:39:04.447Z\n" +"POT-Creation-Date: 2024-12-11T08:18:31.184Z\n" +"PO-Revision-Date: 2024-12-11T08:18:31.184Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1397,12 +1397,21 @@ msgstr "Actions - {{relationshipName}}" msgid "Ambiguous relationships, contact system administrator" msgstr "Ambiguous relationships, contact system administrator" +msgid "Enter details" +msgstr "Enter details" + +msgid "Linked event" +msgstr "Linked event" + msgid "Enter details now" msgstr "Enter details now" msgid "Link to an existing event" msgstr "Link to an existing event" +msgid "An error occurred while linking the event" +msgstr "An error occurred while linking the event" + msgid "Scheduled date" msgstr "Scheduled date" @@ -1513,9 +1522,6 @@ msgstr "You do not have access to remove the link and delete the linked event" msgid "An error occurred while loading the widget." msgstr "An error occurred while loading the widget." -msgid "Linked event" -msgstr "Linked event" - msgid "" "This {{stageName}} event is linked to a {{linkedStageName}} event. Review " "the linked event details before entering data below" diff --git a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js index 420c663669..dcce7692a4 100644 --- a/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js +++ b/src/core_modules/capture-core/components/DataEntries/Enrollment/EnrollmentWithFirstStageDataEntry/EnrollmentWithFirstStageDataEntry.container.js @@ -4,7 +4,7 @@ import type { Props } from './EnrollmentWithFirstStageDataEntry.types'; import { FirstStageDataEntry } from './EnrollmentWithFirstStageDataEntry.component'; import { useDataEntrySections } from './hooks'; import { Section } from '../../../../metaData'; -import { WidgetRelatedStages } from '../../../WidgetRelatedStages'; +import { RelatedStagesActions } from '../../../WidgetRelatedStages'; const getSectionId = sectionId => (sectionId === Section.MAIN_SECTION_ID ? `${Section.MAIN_SECTION_ID}-stage` : sectionId); @@ -26,7 +26,7 @@ export const EnrollmentWithFirstStageDataEntry = (props: Props) => { firstStageMetaData={firstStageMetaData} dataEntrySections={dataEntrySections} /> - , - status?: string, + status: 'ACTIVE' | 'VISITED' | 'COMPLETED' | 'SCHEDULE' | 'OVERDUE' | 'SKIPPED', } export type RequestEvent = { ...CommonEventDetails, occurredAt: string, notes?: Array<{ value: string }>, - completedAt?: string, } export type LinkedRequestEvent = { ...CommonEventDetails, occurredAt?: string, - completedAt?: string, } export type ConvertedRelatedStageEventProps = {| 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 679bb616c4..4dd2ff5341 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 @@ -57,6 +57,7 @@ export const EnrollmentEditEventPageComponent = ({ onSaveAssigneeError, onDeleteTrackedEntitySuccess, onAccessLostFromTransfer, + onNavigateToEvent, }: 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 f459cff54d..3d81fcba59 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 @@ -187,6 +187,17 @@ const EnrollmentEditEventPageWithContextPlain = ({ const onGoBack = () => history.push(`/enrollment?${buildUrlQueryString({ enrollmentId })}`); + const onNavigateToEvent = (eventIdToRedirectTo: string) => { + history.push( + `/enrollmentEventEdit?${buildUrlQueryString({ + eventId: eventIdToRedirectTo, + orgUnitId, + programId, + enrollmentId, + })}`, + ); + }; + const onHandleScheduleSave = (eventData: Object) => { dispatch(updateEnrollmentEvent(eventId, eventData)); history.push(`enrollment?${buildUrlQueryString({ enrollmentId })}`); @@ -291,6 +302,7 @@ const EnrollmentEditEventPageWithContextPlain = ({ onSaveAssigneeError={onSaveAssigneeError} events={enrollmentSite?.events} onAccessLostFromTransfer={onAccessLostFromTransfer} + onNavigateToEvent={onNavigateToEvent} /> ); }; 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 56e2d5c5de..a32173f2c2 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 @@ -29,6 +29,7 @@ export type PlainProps = {| onDelete: () => void, onAddNew: () => void, onGoBack: () => void, + onNavigateToEvent: (eventId: string) => void, onBackToMainPage: () => void, onBackToDashboard: () => void, onBackToViewEvent: () => void, diff --git a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js index 0a8065a405..9e1486eae8 100644 --- a/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js +++ b/src/core_modules/capture-core/components/Pages/EnrollmentEditEvent/PageLayout/DefaultPageLayout.constants.js @@ -10,11 +10,13 @@ import { AssigneeWidget, WidgetTypes, TwoEventWorkspace, + WidgetRelatedStagesWorkspace, } from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout'; export const WidgetsForEnrollmentEventEdit: $ReadOnly<{ [key: string]: WidgetConfig }> = Object.freeze({ EditEventWorkspace, TwoEventWorkspace, + WidgetRelatedStagesWorkspace, EventNote, AssigneeWidget, ...DefaultWidgetsForEnrollmentOverview, @@ -26,6 +28,10 @@ export const DefaultPageLayout: PageLayoutConfig = { type: WidgetTypes.COMPONENT, name: 'EditEventWorkspace', }, + { + type: WidgetTypes.COMPONENT, + name: 'WidgetRelatedStagesWorkspace', + }, ], rightColumn: [ { diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js index cbb1bea87a..b37abd6507 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.types.js @@ -8,6 +8,7 @@ type DefaultComponents = 'QuickActions' | 'AssigneeWidget' | 'NewEventWorkspace' | 'EditEventWorkspace' + | 'WidgetRelatedStagesWorkspace' | 'EnrollmentNote' | 'EventNote' | 'TrackedEntityRelationship' diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js index 2baee53485..1bf2275e72 100644 --- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js +++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/LayoutComponentConfig/LayoutComponentConfig.js @@ -29,6 +29,10 @@ import type { InputIndicatorProps, } from '../../../../../WidgetFeedback/WidgetFeedback.types'; import { WidgetTwoEventWorkspace } from '../../../../../WidgetTwoEventWorkspace'; +import { WidgetRelatedStages } from '../../../../../WidgetRelatedStages'; +import { + EnrollmentPageKeys, +} from '../DefaultEnrollmentLayout.constants'; export const QuickActions: WidgetConfig = { Component: EnrollmentQuickActions, @@ -286,3 +290,29 @@ export const EventNote: WidgetConfig = { dataEntryId, }), }; + +export const WidgetRelatedStagesWorkspace: WidgetConfig = { + Component: WidgetRelatedStages, + shouldHideWidget: ({ currentPage }) => currentPage === EnrollmentPageKeys.EDIT_EVENT, + getProps: ({ + program, + stageId, + enrollmentId, + eventId, + teiId, + onUpdateEnrollmentStatus, + onUpdateEnrollmentStatusSuccess, + onUpdateEnrollmentStatusError, + onNavigateToEvent, + }) => ({ + programId: program.id, + programStageId: stageId, + enrollmentId, + eventId, + teiId, + onUpdateEnrollment: onUpdateEnrollmentStatus, + onUpdateEnrollmentSuccess: onUpdateEnrollmentStatusSuccess, + onUpdateEnrollmentError: onUpdateEnrollmentStatusError, + onNavigateToEvent, + }), +}; diff --git a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js index 663348da1b..7f34363548 100644 --- a/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js +++ b/src/core_modules/capture-core/components/WidgetEnrollmentEventNew/Validated/Validated.component.js @@ -6,7 +6,7 @@ import { Widget } from '../../Widget'; import { DataEntry } from '../DataEntry'; import { FinishButtons } from '../FinishButtons'; import { SavingText } from '../SavingText'; -import { WidgetRelatedStages } from '../../WidgetRelatedStages'; +import { RelatedStagesActions } from '../../WidgetRelatedStages'; import type { Props } from './validated.types'; const styles = () => ({ @@ -47,7 +47,7 @@ const ValidatedPlain = ({ id={id} orgUnit={orgUnit} /> - ({ clearSelections: { padding: spacers.dp8, }, + link: { + padding: spacers.dp8, + }, }); const Schedule = ({ @@ -171,6 +174,16 @@ const LinkExistingResponse = ({ ); }; +const LinkButton = withStyles(styles)(({ onLink, label, loading, classes }) => ( + onLink ? ( +
+ +
+ ) : null +)); + const RelatedStagesActionsPlain = ({ classes, type, @@ -184,7 +197,8 @@ const RelatedStagesActionsPlain = ({ errorMessages, saveAttempted, actionsOptions, -}: Props) => { + onLink, +}: PlainProps) => { const { programStage } = useProgramStageInfo(constraint?.programStage?.id); const selectedAction = useMemo(() => relatedStagesDataValues.linkMode, [relatedStagesDataValues.linkMode]); @@ -253,37 +267,47 @@ const RelatedStagesActionsPlain = ({ )} {selectedAction === relatedStageActions.SCHEDULE_IN_ORG && ( - + <> + + + )} {selectedAction === relatedStageActions.ENTER_DATA && ( - + <> + + + )} {selectedAction === relatedStageActions.LINK_EXISTING_RESPONSE && ( - + <> + + + )} + ); }; -export const RelatedStagesActions: ComponentType<$Diff> = withStyles(styles)(RelatedStagesActionsPlain); +export const RelatedStagesActions: ComponentType<$Diff> = withStyles(styles)(RelatedStagesActionsPlain); diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.container.js similarity index 82% rename from src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js rename to src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.container.js index 9ce0628481..e50803fb6f 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.component.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.container.js @@ -1,19 +1,18 @@ // @flow import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react'; -import { useRelatedStages } from './useRelatedStages'; -import { useOrgUnitAutoSelect } from '../../dataQueries'; -import type { Props, RelatedStageDataValueStates } from './WidgetRelatedStages.types'; -import type { ErrorMessagesForRelatedStages } from './RelatedStagesActions'; -import { RelatedStagesActions } from './RelatedStagesActions'; -import { relatedStageStatus } from './constants'; -import { useStageLabels } from './hooks/useStageLabels'; -import { relatedStageWidgetIsValid } from './relatedStageEventIsValid/relatedStageEventIsValid'; -import { useRelatedStageEvents } from './hooks/useRelatedStageEvents'; +import { useOrgUnitAutoSelect } from '../../../dataQueries'; +import type { RelatedStageDataValueStates } from '../WidgetRelatedStages.types'; +import type { Props, ErrorMessagesForRelatedStages } from './RelatedStagesActions.types'; +import { RelatedStagesActions as RelatedStagesActionsComponent } from './RelatedStagesActions.component'; +import { relatedStageStatus } from '../constants'; +import { useStageLabels, useRelatedStageEvents, useRelatedStages } from '../hooks'; +import { relatedStageWidgetIsValid } from '../relatedStageEventIsValid/relatedStageEventIsValid'; -const WidgetRelatedStagesPlain = ({ +const RelatedStagesActionsPlain = ({ programId, enrollmentId, programStageId, + onLink, ...passOnProps }: Props, ref) => { const { currentRelatedStagesStatus, selectedRelationshipType, constraint } = useRelatedStages({ @@ -97,7 +96,7 @@ const WidgetRelatedStagesPlain = ({ } return ( - ); }; -export const WidgetRelatedStages = forwardRef < Props, {| +export const RelatedStagesActions = forwardRef < Props, {| eventHasLinkableStageRelationship: Function, formIsValidOnSave: Function, getLinkedStageValues: Function - |}>(WidgetRelatedStagesPlain); + |}>(RelatedStagesActionsPlain); diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.types.js b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.types.js index 18aedce6ac..3dfe97c009 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.types.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/RelatedStagesActions.types.js @@ -25,7 +25,7 @@ export type RelatedStagesEvents = { status: string, } -export type Props = {| +export type PlainProps = {| type: string, relationshipName: string, relatedStagesDataValues: RelatedStageDataValueStates, @@ -37,6 +37,7 @@ export type Props = {| constraint: ?Constraint, addErrorMessage: (ErrorMessagesForRelatedStages) => void, setRelatedStagesDataValues: (() => Object) => void, + onLink?: () => void, actionsOptions?: { [key: $Keys]: { hidden?: boolean, @@ -46,3 +47,18 @@ export type Props = {| }, ...CssClasses |} + +export type Props = {| + programId: string, + enrollmentId?: string, + programStageId: string, + onLink?: () => void, + actionsOptions?: { + [key: $Keys]: { + hidden?: boolean, + disabled?: boolean, + disabledMessage?: string + }, + }, +|} + diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/index.js b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/index.js index 9655a91302..7d46218f32 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/index.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/RelatedStagesActions/index.js @@ -1,3 +1,3 @@ // @flow -export { RelatedStagesActions } from './RelatedStagesActions.component'; +export { RelatedStagesActions } from './RelatedStagesActions.container'; export type { ErrorMessagesForRelatedStages } from './RelatedStagesActions.types'; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.container.js b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.container.js new file mode 100644 index 0000000000..16144ab6db --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.container.js @@ -0,0 +1,126 @@ +// @flow +import React, { type ComponentType, useRef, useCallback } from 'react'; +import { IconLink24, spacers } from '@dhis2/ui'; +import { withStyles } from '@material-ui/core'; +import i18n from '@dhis2/d2-i18n'; +import { Widget } from '../Widget'; +import { type RelatedStageRefPayload } from './index'; +import { RelatedStagesActions } from './RelatedStagesActions'; +import { useLinkedEventByOriginId } from '../WidgetTwoEventWorkspace/hooks'; +import type { Props } from './WidgetRelatedStages.types'; +import { + useRelatedStages, + useBuildRelatedStageEventPayload, + useAddEventWithRelationship, + createServerData, +} from './hooks'; +import { relatedStageStatus } from './constants'; +import { useCommonEnrollmentDomainData } from '../Pages/common/EnrollmentOverviewDomain'; +import { type RequestEvent } from '../DataEntries'; + +const styles = { + header: { + display: 'flex', + alignItems: 'center', + }, + icon: { + paddingRight: spacers.dp8, + }, + actions: { + margin: `0 ${spacers.dp16} ${spacers.dp16} ${spacers.dp16}`, + }, +}; + +export const WidgetRelatedStagesPlain = ({ + programId, + eventId, + enrollmentId, + programStageId, + teiId, + actionsOptions, + onUpdateEnrollment, + onUpdateEnrollmentSuccess, + onUpdateEnrollmentError, + onNavigateToEvent, + classes, +}: Props) => { + const { enrollment } = useCommonEnrollmentDomainData(teiId, enrollmentId, programId); + const { currentRelatedStagesStatus } = useRelatedStages({ programStageId, programId }); + const { + linkedEvent, + isLoading: isLinkedEventLoading, + } = useLinkedEventByOriginId({ originEventId: eventId }); + const relatedStageRef = useRef(null); + const { buildRelatedStageEventPayload } = useBuildRelatedStageEventPayload(); + const { addEventWithRelationship } = useAddEventWithRelationship({ + eventId, + onUpdateEnrollment, + onUpdateEnrollmentSuccess, + onUpdateEnrollmentError, + onNavigateToEvent, + }); + + const onLink = useCallback(() => { + // $FlowFixMe[incompatible-type] + const serverRequestEvent: ?RequestEvent = enrollment?.events.find(e => e.event === eventId); + + const { + formHasError, + linkedEvent: relatedStageLinkedEvent, + relationship, + linkMode, + } = buildRelatedStageEventPayload({ + serverRequestEvent, + relatedStageRef, + programStageId, + programId, + teiId, + enrollmentId, + }); + + if (!formHasError && relationship && linkMode) { + const serverData = createServerData({ enrollment, linkedEvent: relatedStageLinkedEvent, relationship }); + addEventWithRelationship({ serverData, linkMode, eventIdToRedirectTo: relatedStageLinkedEvent?.event }); + } + }, [ + programStageId, + programId, + teiId, + eventId, + enrollmentId, + enrollment, + buildRelatedStageEventPayload, + addEventWithRelationship, + ]); + + if (isLinkedEventLoading || linkedEvent || currentRelatedStagesStatus !== relatedStageStatus.LINKABLE) { + return null; + } + + return ( + + + + + {i18n.t('Linked event')} + + } + > +
+ +
+
+ ); +}; + +export const WidgetRelatedStages: ComponentType = withStyles(styles)(WidgetRelatedStagesPlain); diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js index 7876e528c1..9288f6f008 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/WidgetRelatedStages.types.js @@ -17,8 +17,14 @@ export type RelationshipType = {| export type Props = {| programId: string, - enrollmentId?: string, + eventId: string, + teiId: string, + enrollmentId: string, programStageId: string, + onUpdateEnrollment: (enrollment: Object) => void, + onUpdateEnrollmentSuccess: ({ redirect?: boolean }) => void, + onUpdateEnrollmentError: (message: string) => void, + onNavigateToEvent: (eventId: string) => void, actionsOptions?: { [key: $Keys]: { hidden?: boolean, @@ -26,7 +32,9 @@ export type Props = {| disabledMessage?: string }, }, + ...CssClasses, |} + export type RelatedStageDataValueStates = {| linkMode: ?$Keys, scheduledAt: string, @@ -41,6 +49,7 @@ export type RelatedStageDataValueStates = {| export type RelatedStageRelationshipType = {| id: string, + displayName: string, fromConstraint: {| programStage: { id: string, diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/index.js b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/index.js new file mode 100644 index 0000000000..54d9aa0537 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/index.js @@ -0,0 +1,7 @@ +// @flow +export { useRelatedStages } from './useRelatedStages'; +export { useStageLabels } from './useStageLabels'; +export { useRelatedStageEvents } from './useRelatedStageEvents'; +export { useCanAddNewEventToStage } from './useCanAddNewEventToStage'; +export { useBuildRelatedStageEventPayload, createServerData } from './useBuildRelatedStageEventPayload'; +export { useAddEventWithRelationship } from './useAddEventWithRelationship'; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useAddEventWithRelationship.js b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useAddEventWithRelationship.js new file mode 100644 index 0000000000..39230785ff --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useAddEventWithRelationship.js @@ -0,0 +1,61 @@ +// @flow +import i18n from '@dhis2/d2-i18n'; +import { useDataEngine } from '@dhis2/app-runtime'; +import { useMutation, useQueryClient } from 'react-query'; +import { relatedStageActions } from '../constants'; + +const ReactQueryAppNamespace = 'capture'; + +const addEventWithRelationshipMutation = { + resource: '/tracker?async=false&importStrategy=CREATE_AND_UPDATE', + type: 'create', + data: ({ serverData }) => serverData, +}; + +export const useAddEventWithRelationship = ({ + eventId, + onUpdateEnrollment, + onUpdateEnrollmentSuccess, + onUpdateEnrollmentError, + onNavigateToEvent, +}: { + eventId: string, + onUpdateEnrollment: (enrollment: Object) => void, + onUpdateEnrollmentSuccess: ({ redirect?: boolean }) => void, + onUpdateEnrollmentError: (message: string) => void, + onNavigateToEvent: (eventId: string) => void, +}) => { + const dataEngine = useDataEngine(); + const queryClient = useQueryClient(); + + const { mutate } = useMutation( + ({ serverData }: Object) => + dataEngine.mutate(addEventWithRelationshipMutation, { + variables: { + serverData, + }, + }), + { + onMutate: (payload: { serverData: Object }) => { + const enrollmentToUpdate = payload.serverData.enrollments?.[0]; + enrollmentToUpdate && onUpdateEnrollment(enrollmentToUpdate); + }, + onSuccess: (_, payload: { linkMode: string, eventIdToRedirectTo?: string }) => { + const queryKey = [ReactQueryAppNamespace, 'linkedEventByOriginEvent', eventId]; + queryClient.refetchQueries(queryKey); + onUpdateEnrollmentSuccess({}); + + if (payload.linkMode === relatedStageActions.ENTER_DATA && payload.eventIdToRedirectTo) { + onNavigateToEvent(payload.eventIdToRedirectTo); + } + }, + onError: () => { + onUpdateEnrollmentError(i18n.t('An error occurred while linking the event')); + }, + }, + ); + + return { + addEventWithRelationship: mutate, + }; +}; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useBuildRelatedStageEventPayload.js b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useBuildRelatedStageEventPayload.js new file mode 100644 index 0000000000..f235cd07fb --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useBuildRelatedStageEventPayload.js @@ -0,0 +1,95 @@ +// @flow +import { getConvertedRelatedStageEvent, type RequestEvent, type LinkedRequestEvent } from '../../DataEntries'; +import type { RelatedStageRefPayload } from '../index'; + +export const createServerData = ({ + linkedEvent, + relationship, + enrollment, +}: { + linkedEvent: ?LinkedRequestEvent, + relationship: ?Object, + enrollment: Object, +}) => { + const updatedEnrollment = { ...enrollment, events: [...enrollment.events, linkedEvent] }; + if (linkedEvent) { + return { + enrollments: [updatedEnrollment], + relationships: [relationship], + }; + } + return { + relationships: [relationship], + }; +}; + +export const useBuildRelatedStageEventPayload = () => { + const buildRelatedStageEventPayload = ({ + serverRequestEvent, + relatedStageRef, + programStageId, + programId, + teiId, + enrollmentId, + }: { + serverRequestEvent: ?RequestEvent, + relatedStageRef?: { current: ?RelatedStageRefPayload }, + programStageId: string, + programId: string, + teiId: string, + enrollmentId: string, + }) => { + if (relatedStageRef?.current && relatedStageRef.current.eventHasLinkableStageRelationship()) { + const isValid = relatedStageRef.current.formIsValidOnSave(); + + if (!isValid || !relatedStageRef.current?.getLinkedStageValues || !programStageId || !serverRequestEvent) { + return { + formHasError: true, + linkedEvent: null, + relationship: null, + linkMode: null, + }; + } + + const { selectedRelationshipType, relatedStageDataValues, linkMode } = + relatedStageRef.current.getLinkedStageValues(); + + if (!linkMode) { + return { + formHasError: false, + linkedEvent: null, + relationship: null, + linkMode: null, + }; + } + + const { linkedEvent, relationship } = getConvertedRelatedStageEvent({ + linkMode, + relatedStageDataValues, + serverRequestEvent, + relatedStageType: selectedRelationshipType, + programId, + currentProgramStageId: programStageId, + teiId, + enrollmentId, + }); + + return { + formHasError: false, + linkedEvent, + relationship, + linkMode, + }; + } + return { + formHasError: false, + linkedEvent: null, + relationship: null, + linkMode: null, + }; + }; + + return { + buildRelatedStageEventPayload, + }; +}; diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/useRelatedStages.js b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStages.js similarity index 90% rename from src/core_modules/capture-core/components/WidgetRelatedStages/useRelatedStages.js rename to src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStages.js index 6472c0b801..6622c8df31 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/useRelatedStages.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/hooks/useRelatedStages.js @@ -1,10 +1,10 @@ // @flow import { useMemo } from 'react'; -import { relatedStageStatus } from './constants'; -import { getUserStorageController, userStores } from '../../storageControllers'; -import { useIndexedDBQuery } from '../../utils/reactQueryHelpers'; -import { RELATIONSHIP_ENTITIES } from './WidgetRelatedStages.constants'; -import type { RelationshipType } from './WidgetRelatedStages.types'; +import { relatedStageStatus } from '../constants'; +import { getUserStorageController, userStores } from '../../../storageControllers'; +import { useIndexedDBQuery } from '../../../utils/reactQueryHelpers'; +import { RELATIONSHIP_ENTITIES } from '../WidgetRelatedStages.constants'; +import type { RelationshipType } from '../WidgetRelatedStages.types'; const getRelationshipTypeFromIndexedDB = () => { const storageController = getUserStorageController(); diff --git a/src/core_modules/capture-core/components/WidgetRelatedStages/index.js b/src/core_modules/capture-core/components/WidgetRelatedStages/index.js index e75b93c8cc..b23f2475ec 100644 --- a/src/core_modules/capture-core/components/WidgetRelatedStages/index.js +++ b/src/core_modules/capture-core/components/WidgetRelatedStages/index.js @@ -1,5 +1,6 @@ // @flow -export { WidgetRelatedStages } from './WidgetRelatedStages.component'; +export { RelatedStagesActions } from './RelatedStagesActions'; +export { WidgetRelatedStages } from './WidgetRelatedStages.container'; export type { RelatedStageDataValueStates, RelatedStageRefPayload, diff --git a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/index.js b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/index.js index 195e176eef..0596291a5b 100644 --- a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/index.js +++ b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/hooks/index.js @@ -1,3 +1,5 @@ +// @flow + export { useClientDataValues } from './useClientDataValues'; export { useLinkedEventByOriginId } from './useLinkedEventByOriginId'; export { useRelationshipTypeAccess } from './useRelationshipTypeAccess'; diff --git a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/index.js b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/index.js index b057d60b99..69e57db145 100644 --- a/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/index.js +++ b/src/core_modules/capture-core/components/WidgetTwoEventWorkspace/index.js @@ -2,3 +2,4 @@ export { WidgetTwoEventWorkspace } from './WidgetTwoEventWorkspace.container'; export { WidgetTwoEventWorkspaceWrapperTypes } from './WidgetTwoEventWorkspaceWrapper.const'; +export { useLinkedEventByOriginId } from './hooks';