diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js index ef24083130..a97e33314f 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/NewTrackedEntityRelationship.component.js @@ -14,6 +14,7 @@ import { RetrieverModeSelector } from './RetrieverModeSelector'; import type { ComponentProps, StyledComponentProps } from './NewTrackedEntityRelationship.types'; import { useAddRelationship } from './hooks/useAddRelationship'; import { TARGET_SIDES } from './common'; +import { generateUID } from '../../../../utils/uid/generateUID'; const styles = { container: { @@ -52,7 +53,7 @@ const NewTrackedEntityRelationshipPlain = ({ const [currentStep, setCurrentStep] = useState(NEW_TRACKED_ENTITY_RELATIONSHIP_WIZARD_STEPS.SELECT_LINKED_ENTITY_METADATA); const [selectedLinkedEntityMetadata: LinkedEntityMetadata, setSelectedLinkedEntityMetadata] = useState(undefined); - const { mutate } = useAddRelationship({ + const { addRelationship } = useAddRelationship({ teiId, onMutate: () => onSave && onSave(), }); @@ -61,7 +62,8 @@ const NewTrackedEntityRelationshipPlain = ({ const onLinkToTrackedEntityFromSearch = useCallback( (linkedTrackedEntityId: string, attributes?: { [attributeId: string]: string }) => { if (!selectedLinkedEntityMetadata) return; - const { relationshipId, targetSide } = selectedLinkedEntityMetadata; + const { relationshipId: relationshipTypeId, targetSide } = selectedLinkedEntityMetadata; + const relationshipId = generateUID(); const apiData = targetSide === TARGET_SIDES.TO ? { from: { trackedEntity: { trackedEntity: teiId } }, to: { trackedEntity: { trackedEntity: linkedTrackedEntityId } } } : @@ -84,30 +86,34 @@ const NewTrackedEntityRelationshipPlain = ({ }; } - mutate({ + addRelationship({ apiData: { relationships: [{ - relationshipType: relationshipId, + relationshipType: relationshipTypeId, + relationship: relationshipId, ...apiData, }], }, clientRelationship: { - relationshipType: relationshipId, + relationshipType: relationshipTypeId, + relationship: relationshipId, ...clientData, }, }); - }, [mutate, selectedLinkedEntityMetadata, teiId]); + }, [addRelationship, selectedLinkedEntityMetadata, teiId]); const onLinkToTrackedEntityFromRegistration = useCallback((trackedEntity: Object) => { if (!selectedLinkedEntityMetadata) return; - const { relationshipId, targetSide } = selectedLinkedEntityMetadata; + const { relationshipId: relationshipTypeId, targetSide } = selectedLinkedEntityMetadata; + const relationshipId = generateUID(); const relationshipData = targetSide === TARGET_SIDES.TO ? { from: { trackedEntity: { trackedEntity: teiId } }, to: { trackedEntity: { trackedEntity: trackedEntity.trackedEntity } } } : { from: { trackedEntity: { trackedEntity: trackedEntity.trackedEntity } }, to: { trackedEntity: { trackedEntity: teiId } } }; const clientData = { - relationshipType: relationshipId, + relationship: relationshipId, + relationshipType: relationshipTypeId, createdAt: new Date().toISOString(), pendingApiResponse: true, ...relationshipData, @@ -121,14 +127,18 @@ const NewTrackedEntityRelationshipPlain = ({ }, }; - mutate({ + addRelationship({ apiData: { trackedEntities: [trackedEntity], - relationships: [{ relationshipType: relationshipId, ...relationshipData }], + relationships: [{ + relationship: relationshipId, + relationshipType: relationshipTypeId, + ...relationshipData, + }], }, clientRelationship: clientData, }); - }, [mutate, selectedLinkedEntityMetadata, teiId]); + }, [addRelationship, selectedLinkedEntityMetadata, teiId]); const handleNavigation = useCallback( (destination: $Values) => { diff --git a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js index 045b5f7d4f..9bdfd4b14d 100644 --- a/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js +++ b/src/core_modules/capture-core/components/WidgetsRelationship/WidgetTrackedEntityRelationship/NewTrackedEntityRelationship/hooks/useAddRelationship.js @@ -1,13 +1,12 @@ // @flow import i18n from '@dhis2/d2-i18n'; -// $FlowFixMe - useAlert is exported from app-runtime import { useDataEngine, useAlert } from '@dhis2/app-runtime'; import { useMutation, useQueryClient } from 'react-query'; type Props = { teiId: string; onMutate?: () => void; - onSuccess?: () => void; + onSuccess?: (apiResponse: Object, requestData: Object) => void; } const ReactQueryAppNamespace = 'capture'; @@ -34,9 +33,25 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => { }, }), { - onError: () => { - queryClient.invalidateQueries([ReactQueryAppNamespace, 'relationships']); + onError: (_, requestData) => { showSnackbar(); + const apiRelationshipId = requestData.clientRelationship.relationship; + const currentRelationships = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]); + + if (!currentRelationships?.instances) return; + + const newRelationships = currentRelationships.instances.reduce((acc, relationship) => { + if (relationship.relationship === apiRelationshipId) { + return acc; + } + acc.push(relationship); + return acc; + }, []); + + queryClient.setQueryData( + [ReactQueryAppNamespace, 'relationships', teiId], + { instances: newRelationships }, + ); }, onMutate: (...props) => { onMutate && onMutate(...props); @@ -49,14 +64,31 @@ export const useAddRelationship = ({ teiId, onMutate, onSuccess }: Props) => { return { instances: updatedInstances }; }); }, - onSuccess: (...props) => { - queryClient.invalidateQueries([ReactQueryAppNamespace, 'relationships']); - onSuccess && onSuccess(...props); + onSuccess: async (apiResponse, requestData) => { + const apiRelationshipId = apiResponse.bundleReport.typeReportMap.RELATIONSHIP.objectReports[0].uid; + const currentRelationships = queryClient.getQueryData([ReactQueryAppNamespace, 'relationships', teiId]); + if (!currentRelationships?.instances) return; + + const newRelationships = currentRelationships.instances.map((relationship) => { + if (relationship.relationship === apiRelationshipId) { + return { + ...relationship, + pendingApiResponse: false, + }; + } + return relationship; + }); + + queryClient.setQueryData( + [ReactQueryAppNamespace, 'relationships', teiId], + { instances: newRelationships }, + ); + onSuccess && onSuccess(apiResponse, requestData); }, }, ); return { - mutate, + addRelationship: mutate, }; }; diff --git a/src/core_modules/capture-core/flow/app-runtime_v2.x.x.js b/src/core_modules/capture-core/flow/app-runtime_v2.x.x.js index c6ba900799..f351595ce0 100644 --- a/src/core_modules/capture-core/flow/app-runtime_v2.x.x.js +++ b/src/core_modules/capture-core/flow/app-runtime_v2.x.x.js @@ -116,7 +116,7 @@ declare module '@dhis2/app-runtime' { message: string, details: any, |}; - + declare export type ApiAccessError = {| type: 'access', message: string, @@ -127,15 +127,15 @@ declare module '@dhis2/app-runtime' { status: string, }, |}; - + declare export type ApiUnknownError = {| type: 'unknown', message: string, details: any, |}; - + declare export type ApiError = ApiNetworkError | ApiAccessError | ApiUnknownError; - + declare type QueryRenderInput = {| called: boolean, loading: boolean, @@ -148,4 +148,11 @@ declare module '@dhis2/app-runtime' { declare export function useDataQuery(resourceQueries: ResourceQueries, queryOptions?: QueryOptions): QueryRenderInput; declare export function useDataMutation(mutation: Mutation, mutationOptions?: QueryOptions): MutationRenderInput; + + declare type AlertOptions = { [key: string]: mixed }; + + declare export function useAlert(message: string | ((props: any) => string), options?: AlertOptions | ((props: any) => AlertOptions)): { + show: (props?: any) => void; + hide: () => void; + }; }