@@ -138,7 +136,7 @@ export const ViewEventPlain = (props: Props & WithStyles
) => {
/>
(
);
diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext/EnrollmentAccessContext.tsx b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext/EnrollmentAccessContext.tsx
index d7b31c649e..2b5792ad9b 100644
--- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext/EnrollmentAccessContext.tsx
+++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentAccessContext/EnrollmentAccessContext.tsx
@@ -18,9 +18,8 @@ export type EnrollmentAccessContextValue = {
showWidgetBadge: boolean;
trackedEntityInactive: boolean;
canToggleTrackedEntityStatus: boolean;
- isEventWithinValidPeriod?: boolean;
- canEditCompletedEvent?: boolean;
- isWithinCompleteEventsExpiry?: boolean;
+ isEventBlockedByExpiry?: boolean;
+ isEventBlockedByCompletion?: boolean;
};
const fallback: EnrollmentAccessContextValue = {
@@ -37,6 +36,8 @@ const fallback: EnrollmentAccessContextValue = {
showWidgetBadge: true,
trackedEntityInactive: false,
canToggleTrackedEntityStatus: false,
+ isEventBlockedByExpiry: false,
+ isEventBlockedByCompletion: false,
};
const Context = createContext(fallback);
@@ -45,9 +46,8 @@ type ProviderProps = {
program?: TrackerProgram;
currentStageId?: string;
trackedEntityInactive?: boolean;
- isEventWithinValidPeriod?: boolean;
- canEditCompletedEvent?: boolean;
- isWithinCompleteEventsExpiry?: boolean;
+ isEventBlockedByExpiry?: boolean;
+ isEventBlockedByCompletion?: boolean;
children: React.ReactNode;
};
@@ -66,9 +66,8 @@ const computeContextValue = (
program: TrackerProgram,
currentStageId: string | undefined,
trackedEntityInactive: boolean,
- isEventWithinValidPeriod?: boolean,
- canEditCompletedEvent?: boolean,
- isWithinCompleteEventsExpiry?: boolean,
+ isEventBlockedByExpiry?: boolean,
+ isEventBlockedByCompletion?: boolean,
): EnrollmentAccessContextValue => {
const { rawStageWriteAccessById, stageReadAccessById } = buildStageAccessMaps(program);
const rawProgramWriteAccess = Boolean(program.access?.data?.write);
@@ -101,9 +100,8 @@ const computeContextValue = (
showWidgetBadge: !isEventPage && !allWriteAccessMissing,
trackedEntityInactive,
canToggleTrackedEntityStatus: rawTrackedEntityTypeWriteAccess,
- isEventWithinValidPeriod,
- canEditCompletedEvent,
- isWithinCompleteEventsExpiry,
+ isEventBlockedByExpiry,
+ isEventBlockedByCompletion,
};
};
@@ -111,9 +109,8 @@ export const EnrollmentAccessProvider = ({
program,
currentStageId,
trackedEntityInactive = false,
- isEventWithinValidPeriod,
- canEditCompletedEvent,
- isWithinCompleteEventsExpiry,
+ isEventBlockedByExpiry,
+ isEventBlockedByCompletion,
children,
}: ProviderProps) => {
const value = useMemo(
@@ -122,9 +119,8 @@ export const EnrollmentAccessProvider = ({
program,
currentStageId,
trackedEntityInactive,
- isEventWithinValidPeriod,
- canEditCompletedEvent,
- isWithinCompleteEventsExpiry,
+ isEventBlockedByExpiry,
+ isEventBlockedByCompletion,
)
: {
...fallback,
@@ -142,9 +138,8 @@ export const EnrollmentAccessProvider = ({
program,
currentStageId,
trackedEntityInactive,
- isEventWithinValidPeriod,
- canEditCompletedEvent,
- isWithinCompleteEventsExpiry,
+ isEventBlockedByExpiry,
+ isEventBlockedByCompletion,
],
);
diff --git a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentReadOnlyBadge/EnrollmentReadOnlyBadge.component.tsx b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentReadOnlyBadge/EnrollmentReadOnlyBadge.component.tsx
index bb2e96cedb..86685cfdae 100644
--- a/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentReadOnlyBadge/EnrollmentReadOnlyBadge.component.tsx
+++ b/src/core_modules/capture-core/components/Pages/common/EnrollmentOverviewDomain/EnrollmentPageLayout/EnrollmentReadOnlyBadge/EnrollmentReadOnlyBadge.component.tsx
@@ -12,18 +12,16 @@ export const EnrollmentReadOnlyBadge = () => {
anyStageReadAccess,
trackedEntityTypeName,
trackedEntityInactive,
- isEventWithinValidPeriod,
- canEditCompletedEvent,
- isWithinCompleteEventsExpiry,
+ isEventBlockedByExpiry,
+ isEventBlockedByCompletion,
} = useEnrollmentAccessContext();
if (isEventPage) {
return (
{
if (trackedEntityInactive) return getDeactivatedMessage(trackedEntityName);
@@ -46,9 +45,8 @@ const getReadOnlyMessage = ({
if (!access.program) return getProgramMessage();
if (!access.trackedEntityType) return getTrackedEntityMessage(trackedEntityName);
if (!access.programStage) return getProgramStageMessage(multipleStages);
- if (!eventWithinValidPeriod) return getExpiredMessage();
- if (!canEditCompletedEvent) return getCompletedEventMessage();
- if (!withinCompleteEventsExpiry) return getExpiredMessage();
+ if (isEventBlockedByExpiry) return getExpiredMessage();
+ if (isEventBlockedByCompletion) return getCompletedEventMessage();
return '';
};
@@ -56,9 +54,8 @@ const ReadOnlyBadgePlain = ({
programWriteAccess = true,
trackedEntityTypeWriteAccess = true,
programStageWriteAccess = true,
- eventWithinValidPeriod = true,
- canEditCompletedEvent = true,
- withinCompleteEventsExpiry = true,
+ isEventBlockedByExpiry = false,
+ isEventBlockedByCompletion = false,
multipleStages = false,
trackedEntityName,
trackedEntityInactive = false,
@@ -74,9 +71,8 @@ const ReadOnlyBadgePlain = ({
access,
trackedEntityName,
multipleStages,
- eventWithinValidPeriod,
- canEditCompletedEvent,
- withinCompleteEventsExpiry,
+ isEventBlockedByExpiry,
+ isEventBlockedByCompletion,
trackedEntityInactive,
});
if (!message) return null;
diff --git a/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts b/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts
index a7ada9cb08..9c36730417 100644
--- a/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts
+++ b/src/core_modules/capture-core/components/ReadOnlyBadge/ReadOnlyBadge.types.ts
@@ -2,9 +2,8 @@ export type Props = {
programWriteAccess?: boolean;
trackedEntityTypeWriteAccess?: boolean;
programStageWriteAccess?: boolean;
- eventWithinValidPeriod?: boolean;
- canEditCompletedEvent?: boolean;
- withinCompleteEventsExpiry?: boolean;
+ isEventBlockedByExpiry?: boolean;
+ isEventBlockedByCompletion?: boolean;
multipleStages?: boolean;
trackedEntityName?: string;
trackedEntityInactive?: boolean;
@@ -21,8 +20,7 @@ export type ReadOnlyMessageInput = {
access: Access;
trackedEntityName: string | undefined;
multipleStages: boolean;
- eventWithinValidPeriod: boolean;
- canEditCompletedEvent: boolean;
- withinCompleteEventsExpiry: boolean;
+ isEventBlockedByExpiry: boolean;
+ isEventBlockedByCompletion: boolean;
trackedEntityInactive: boolean;
};
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.tsx
index 833d422dd1..bd8a45f4a4 100644
--- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.tsx
+++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Actions.container.tsx
@@ -3,7 +3,7 @@ import { ActionsComponent } from './Actions.component';
import type { Props } from './actions.types';
import { useUpdateEnrollment, useDeleteEnrollment } from '../dataMutation/dataMutation';
import { useUpdateOwnership } from './Transfer/hooks';
-import { useAuthorities } from '../../../utils/authority/useAuthorities';
+import { useAuthority, Authorities } from '../../../utils/authority';
export const Actions = ({
enrollment = {},
@@ -21,7 +21,7 @@ export const Actions = ({
}: Props) => {
const { updateMutation, updateLoading } = useUpdateEnrollment(refetchEnrollment, refetchTEI, onError, onSuccess);
const { deleteMutation, deleteLoading } = useDeleteEnrollment(onDelete, onError, onSuccess);
- const { hasAuthority } = useAuthorities({ authorities: ['F_ENROLLMENT_CASCADE_DELETE'] });
+ const { hasAuthority } = useAuthority(Authorities.ENROLLMENT_CASCADE_DELETE);
const { updateEnrollmentOwnership, isTransferLoading } = useUpdateOwnership({
teiId: enrollment.trackedEntity,
programId: enrollment.program,
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/Complete.component.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/Complete.component.tsx
index fa4786aedd..5446d1d6eb 100644
--- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/Complete.component.tsx
+++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/Complete.component.tsx
@@ -1,8 +1,9 @@
import React from 'react';
import { IconCheckmark16, MenuItem } from '@dhis2/ui';
import i18n from '@dhis2/d2-i18n';
+import { statusTypes as eventStatuses } from 'capture-core/events/statusTypes';
import type { Props } from './complete.types';
-import { plainStatus, eventStatuses } from '../../constants/status.const';
+import { plainStatus } from '../../constants/status.const';
export const Complete = ({ enrollment, events, onUpdate, setOpenCompleteModal }: Props) => {
const hasActiveEvents = events?.some(event => event.status === eventStatuses.ACTIVE);
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/CompleteModal/CompleteModal.container.tsx b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/CompleteModal/CompleteModal.container.tsx
index b1353e9d3f..14cc472609 100644
--- a/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/CompleteModal/CompleteModal.container.tsx
+++ b/src/core_modules/capture-core/components/WidgetEnrollment/Actions/Complete/CompleteModal/CompleteModal.container.tsx
@@ -1,8 +1,9 @@
import React, { useCallback, useMemo } from 'react';
import { useTimeZoneConversion } from '@dhis2/app-runtime';
import { formatMomentEn } from 'capture-core-utils/date';
+import { statusTypes as eventStatuses } from 'capture-core/events/statusTypes';
import { CompleteModalComponent } from './CompleteModal.component';
-import { eventStatuses, plainStatus } from '../../../constants/status.const';
+import { plainStatus } from '../../../constants/status.const';
import type { Props } from './completeModal.types';
export const CompleteModal = ({ enrollment, events, programStages, setOpenCompleteModal, onUpdateStatus }: Props) => {
diff --git a/src/core_modules/capture-core/components/WidgetEnrollment/constants/status.const.ts b/src/core_modules/capture-core/components/WidgetEnrollment/constants/status.const.ts
index 2b0bb8939f..7b0854dce2 100644
--- a/src/core_modules/capture-core/components/WidgetEnrollment/constants/status.const.ts
+++ b/src/core_modules/capture-core/components/WidgetEnrollment/constants/status.const.ts
@@ -11,12 +11,3 @@ export const translatedStatus = Object.freeze({
[plainStatus.COMPLETED]: i18n.t('Completed'),
[plainStatus.CANCELLED]: i18n.t('Cancelled'),
});
-
-export const eventStatuses = Object.freeze({
- ACTIVE: 'ACTIVE',
- COMPLETED: 'COMPLETED',
- VISITED: 'VISITED',
- SCHEDULE: 'SCHEDULE',
- OVERDUE: 'OVERDUE',
- SKIPPED: 'SKIPPED',
-});
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.tsx b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.tsx
index a14687d4aa..51720d4f92 100644
--- a/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.tsx
+++ b/src/core_modules/capture-core/components/WidgetEventEdit/EditEventDataEntry/EditEventDataEntry.component.tsx
@@ -36,7 +36,6 @@ import {
withConditionalTooltip,
} from '../../FormFields/New';
import { statusTypes, translatedStatusTypes } from '../../../events/statusTypes';
-import { eventStatuses } from '../constants/status.const';
import labelTypeClasses from '../DataEntry/dataEntryFieldLabels.module.css';
import { withDeleteButton } from '../DataEntry/withDeleteButton';
import { withAskToCreateNew } from '../../DataEntry/withAskToCreateNew';
@@ -324,9 +323,7 @@ const buildCompleteFieldSettingsFn = () => {
withDisplayMessages()(
withInternalChangeHandler()(
withConditionalTooltip((props: any) => {
- const isEventCompleted = props.eventStatus === eventStatuses.COMPLETED;
- const canUncompleteEvent = props.canUncompleteEvent;
- const shouldDisable = isEventCompleted && !canUncompleteEvent;
+ const shouldDisable = !props.canUncompleteEvent;
return shouldDisable
? i18n.t('You do not have access to uncomplete this event')
: undefined;
@@ -341,9 +338,7 @@ const buildCompleteFieldSettingsFn = () => {
const completeSettings = {
getComponent: () => completeComponent,
getComponentProps: (props: any) => {
- const isEventCompleted = props.eventStatus === eventStatuses.COMPLETED;
- const canUncompleteEvent = props.canUncompleteEvent;
- const shouldDisable = isEventCompleted && !canUncompleteEvent;
+ const shouldDisable = !props.canUncompleteEvent;
return createComponentProps(props, {
label: i18n.t('Complete event'),
@@ -585,7 +580,8 @@ class EditEventDataEntryPlain extends Component
@@ -133,12 +136,14 @@ const WidgetEventEditPlain = ({
}
noncollapsible
@@ -175,7 +180,7 @@ const WidgetEventEditPlain = ({
eventStatus={eventStatus}
canUncompleteEvent={canUncompleteEvent}
onCancelEditEvent={onCancelEditEvent}
- hasDeleteButton={!readOnly}
+ hasDeleteButton={!isEventReadOnly}
onHandleScheduleSave={onHandleScheduleSave}
onSaveExternal={onSaveExternal}
initialScheduleDate={initialScheduleDate}
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetHeader/WidgetHeader.container.tsx b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetHeader/WidgetHeader.container.tsx
index 086660bb88..3ed32293ed 100644
--- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetHeader/WidgetHeader.container.tsx
+++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetHeader/WidgetHeader.container.tsx
@@ -1,6 +1,6 @@
-import React, { useState, useEffect } from 'react';
+import React, { useState, useEffect, useCallback } from 'react';
import { dataEntryKeys } from 'capture-core/constants';
-import { useDispatch } from 'react-redux';
+import { useDispatch, useSelector } from 'react-redux';
import { spacersNum, Button, IconEdit24, IconMore16, FlyoutMenu, MenuItem, spacers } from '@dhis2/ui';
import { withStyles, type WithStyles } from 'capture-core-utils/styles';
import i18n from '@dhis2/d2-i18n';
@@ -10,6 +10,14 @@ import { NonBundledDhis2Icon } from '../../NonBundledDhis2Icon';
import { useCategoryCombinations } from '../../DataEntryDhis2Helpers/AOC/useCategoryCombinations';
import { OverflowButton } from '../../Buttons';
import { inMemoryFileStore } from '../../DataEntry/file/inMemoryFileStore';
+import {
+ updateEnrollmentEvent,
+ commitEnrollmentEvent,
+ rollbackEnrollmentEvent,
+} from '../../Pages/common/EnrollmentOverviewDomain';
+import { CompletionMenuItem } from '../../EventOverflowMenu';
+import { changeEventFromUrl } from '../../Pages/ViewEvent/ViewEventComponent/viewEvent.actions';
+import { pageKeys } from '../../App/withAppUrlSync';
import type { PlainProps } from './WidgetHeader.types';
const styles: Readonly = {
@@ -29,6 +37,7 @@ const styles: Readonly = {
type Props = PlainProps & WithStyles;
const WidgetHeaderPlain = ({
+ eventId,
eventStatus,
stage,
programId,
@@ -36,6 +45,7 @@ const WidgetHeaderPlain = ({
setChangeLogIsOpen,
classes,
readOnly,
+ canUncompleteEvent,
}: Props) => {
useEffect(() => inMemoryFileStore.clear, []);
const dispatch = useDispatch();
@@ -46,6 +56,25 @@ const WidgetHeaderPlain = ({
const showEditButton = !readOnly;
const { programCategory } = useCategoryCombinations(programId);
+ const storedEvent = useSelector((state: any) =>
+ state.enrollmentDomain?.enrollment?.events?.find((event: any) => event.event === eventId));
+
+ const onCompletionStatusMutate = useCallback((newStatus: string) => {
+ if (storedEvent) {
+ const { completedAt, ...eventWithoutCompletion } = storedEvent;
+ dispatch(updateEnrollmentEvent(eventId, { ...eventWithoutCompletion, status: newStatus }));
+ }
+ }, [dispatch, storedEvent, eventId]);
+
+ const onCompletionStatusSuccess = useCallback(() => {
+ dispatch(commitEnrollmentEvent(eventId));
+ dispatch(changeEventFromUrl(eventId, pageKeys.ENROLLMENT_EVENT));
+ }, [dispatch, eventId]);
+
+ const onCompletionStatusError = useCallback(() => {
+ dispatch(rollbackEnrollmentEvent(eventId));
+ }, [dispatch, eventId]);
+
const { icon, name } = stage;
return (
@@ -90,6 +119,16 @@ const WidgetHeaderPlain = ({
maxWidth="250px"
dataTest={'tracker-program-event-overflow-menu'}
>
+ {canUncompleteEvent && (
+ setActionsIsOpen(false)}
+ />
+ )}