Skip to content
Draft
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
1c23af4
feat: add uncomplete event functionality to view event
henrikmv Jul 20, 2026
ee06506
feat: add icon to changelog meny item
henrikmv Jul 20, 2026
82231d5
fix: complexity error
henrikmv Jul 20, 2026
d43d0d7
feat: implement event completion functionality
henrikmv Jul 20, 2026
9374438
fix: remove added changelog icon
henrikmv Jul 22, 2026
6a87302
feat: add hook to determine event completion status permissions
henrikmv Jul 22, 2026
54e7180
feat: optimize query client usage for event completion updates
henrikmv Jul 22, 2026
dc68891
fix: remove unused query client
henrikmv Jul 22, 2026
240810f
Merge branch 'master' into hv/feat/DHIS2-21655_uncomplete-event-view-…
henrikmv Jul 22, 2026
8f4186f
Merge remote-tracking branch 'origin/master' into hv/feat/DHIS2-21655…
henrikmv Jul 23, 2026
f0f539f
feat: add onMutate and onError callbacks to EventCompletionMenuItem
henrikmv Jul 24, 2026
0e9aa8e
feat: implement event status management in EventCompletionMenuItem
henrikmv Jul 27, 2026
5f6ac26
feat: allign pr with unify menu items pr
henrikmv Jul 28, 2026
4ed2084
feat: add onMutate and onError props to EventCompletionMenuItem
henrikmv Jul 28, 2026
2abdcd2
fix: remove atomicMode from resource query in EventCompletionMenuItem
henrikmv Jul 28, 2026
8ddf18a
feat: remove feature flag for changelog
henrikmv Jul 28, 2026
43ea9b9
fix: status needs refresh to update
henrikmv Jul 28, 2026
46085b6
feat: integrate updateField action for event completion status mutation
henrikmv Jul 28, 2026
2e7cd12
feat: refactor event status handling and update related actions
henrikmv Jul 28, 2026
0f7443e
feat: add event completion status mutation handling in EventRow compo…
henrikmv Jul 28, 2026
73f1e22
feat: replace useEventEditPermissions with useCanChangeCompletionStat…
henrikmv Jul 28, 2026
42e667d
feat: dispatch changeEventFromUrl action on completion status update
henrikmv Jul 28, 2026
206e43c
feat: rename onUpdated to onSuccess
henrikmv Jul 31, 2026
4c34ad1
fix: roll back and devin review
henrikmv Jul 31, 2026
ad09c52
Merge branch 'master' into hv/feat/DHIS2-21655_uncomplete-event-view-…
henrikmv Jul 31, 2026
dfe3c2e
fix: replace EventStatuses with eventStatuses constant in EventRow an…
henrikmv Jul 31, 2026
1c7b39b
feat: add index file and manu item folder
henrikmv Aug 4, 2026
c1d4aad
Merge remote-tracking branch 'origin/master' into hv/feat/DHIS2-21655…
henrikmv Aug 4, 2026
1be294d
Merge remote-tracking branch 'origin/master' into hv/feat/DHIS2-21655…
henrikmv Aug 4, 2026
06daabf
fix: clean up
henrikmv Aug 4, 2026
a702df4
feat: refactor event status handling to use centralised statusTypes
henrikmv Aug 4, 2026
88bae47
feat: centralise event status handling
henrikmv Aug 4, 2026
ae2f15b
feat: enhance event row loading state with circular loader
henrikmv Aug 4, 2026
687ed3b
Merge remote-tracking branch 'origin/master' into hv/feat/DHIS2-21655…
henrikmv Aug 12, 2026
a9d409e
feat: refactor event edit permissions
henrikmv Aug 13, 2026
da15a0c
feat: refactor event edit permissions and authority management
henrikmv Aug 14, 2026
7d27954
feat: consolidate authority imports across components and hooks
henrikmv Aug 14, 2026
25ff87e
feat: refactor event edit permissions to use isEventReadOnly flag
henrikmv Aug 14, 2026
573c6ff
feat: enhance event edit permissions and streamline delete action han…
henrikmv Aug 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 38 additions & 38 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2026-08-04T13:01:31.955Z\n"
"PO-Revision-Date: 2026-08-04T13:01:31.956Z\n"
"POT-Creation-Date: 2026-08-04T14:35:11.571Z\n"
"PO-Revision-Date: 2026-08-04T14:35:11.571Z\n"

msgid "The application could not be loaded."
msgstr "The application could not be loaded."
Expand Down Expand Up @@ -437,6 +437,42 @@ msgstr ""
"This event has unsaved changes. Leaving this page without saving will lose "
"these changes. Are you sure you want to discard unsaved changes?"

msgid "An error occurred when updating event status"
msgstr "An error occurred when updating event status"

msgid "Mark incomplete"
msgstr "Mark incomplete"

msgid "Mark complete"
msgstr "Mark complete"

msgid "An error occurred while deleting the event"
msgstr "An error occurred while deleting the event"

msgid "Delete event"
msgstr "Delete event"

msgid "Deleting an event is permanent and cannot be undone."
msgstr "Deleting an event is permanent and cannot be undone."

msgid "Are you sure you want to delete this event?"
msgstr "Are you sure you want to delete this event?"

msgid "Yes, delete event"
msgstr "Yes, delete event"

msgid "{{occurredAt}} belongs to an expired period. Event cannot be deleted"
msgstr "{{occurredAt}} belongs to an expired period. Event cannot be deleted"

msgid "This event has been completed"
msgstr "This event has been completed"

msgid "This event is outside the edit period"
msgstr "This event is outside the edit period"

msgid "Delete"
msgstr "Delete"

msgid "No events to display"
msgstr "No events to display"

Expand Down Expand Up @@ -572,9 +608,6 @@ msgstr "Uploading file"
msgid "selected"
msgstr "selected"

msgid "Delete"
msgstr "Delete"

msgid "Select file"
msgstr "Select file"

Expand Down Expand Up @@ -1095,9 +1128,6 @@ msgstr "You only have view access to this program stage"
msgid "This event is outside the editing period"
msgstr "This event is outside the editing period"

msgid "This event has been completed"
msgstr "This event has been completed"

msgid "This {{trackedEntityName}} is deactivated"
msgstr "This {{trackedEntityName}} is deactivated"

Expand Down Expand Up @@ -1356,9 +1386,6 @@ msgstr "Reactivate"
msgid "Mark as cancelled"
msgstr "Mark as cancelled"

msgid "Mark incomplete"
msgstr "Mark incomplete"

msgid "You do not have access to delete this enrollment"
msgstr "You do not have access to delete this enrollment"

Expand Down Expand Up @@ -1500,18 +1527,9 @@ msgstr "Warning"
msgid "stage not found in rules execution"
msgstr "stage not found in rules execution"

msgid "Delete event"
msgstr "Delete event"

msgid "Deleting an event is permanent and cannot be undone."
msgstr "Deleting an event is permanent and cannot be undone."

msgid "Are you sure you want to delete this event? "
msgstr "Are you sure you want to delete this event? "

msgid "Yes, delete event"
msgstr "Yes, delete event"

msgid "Go to “Schedule” tab to reschedule this event"
msgstr "Go to “Schedule” tab to reschedule this event"

Expand Down Expand Up @@ -1760,30 +1778,12 @@ msgstr "This stage can only have one event"
msgid "New {{ eventName }} event"
msgstr "New {{ eventName }} event"

msgid "{{occurredAt}} belongs to an expired period. Event cannot be deleted"
msgstr "{{occurredAt}} belongs to an expired period. Event cannot be deleted"

msgid "This event is outside the edit period"
msgstr "This event is outside the edit period"

msgid "An error occurred while deleting the event"
msgstr "An error occurred while deleting the event"

msgid "Are you sure you want to delete this event?"
msgstr "Are you sure you want to delete this event?"

msgid "An error occurred when updating event status"
msgstr "An error occurred when updating event status"

msgid "Unskip"
msgstr "Unskip"

msgid "Skip"
msgstr "Skip"

msgid "To open this event, please wait until saving is complete"
msgstr "To open this event, please wait until saving is complete"

msgid "Show {{ rest }} more"
msgstr "Show {{ rest }} more"

Expand Down
1 change: 1 addition & 0 deletions src/core_modules/capture-core-utils/types/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export type ApiEnrollmentEvent = {
occurredAt: string;
scheduledAt: string;
completedAt?: string;
completedBy?: string;
updatedAt: string;
dataValues: Array<ApiDataValue>;
notes?: Array<any>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState, ComponentType } from 'react';
import i18n from '@dhis2/d2-i18n';
import { withStyles, WithStyles } from 'capture-core-utils/styles';
import { colors } from '@dhis2/ui';
import { statusTypes, type StatusType } from '../../../events/statusTypes';
import { DirectionalChevron } from '../../../utils/rtl';
import { useWorkingListLabel } from './hooks/useWorkingListLabel';
import { BreadcrumbItem } from '../common/BreadcrumbItem';
Expand All @@ -13,24 +14,14 @@ import {

type EnrollmentPageKeyTypes = typeof EnrollmentPageKeys[keyof typeof EnrollmentPageKeys];

export const EventStatuses = {
ACTIVE: 'ACTIVE',
COMPLETED: 'COMPLETED',
SKIPPED: 'SKIPPED',
SCHEDULE: 'SCHEDULE',
OVERDUE: 'OVERDUE',
} as const;

type EventStatus = typeof EventStatuses[keyof typeof EventStatuses];

type OwnProps = {
onBackToMainPage: () => void;
onBackToDashboard?: () => void;
onBackToViewEvent?: () => void;
displayFrontPageList: boolean;
programId: string;
userInteractionInProgress?: boolean;
eventStatus?: EventStatus;
eventStatus?: StatusType;
page: 'mainPage' | EnrollmentPageKeyTypes;
};
type WarningKey = typeof pageKeys[keyof typeof pageKeys];
Expand All @@ -50,9 +41,9 @@ const pageKeys = {
...EnrollmentPageKeys,
} as const;

const eventIsScheduled = (eventStatus?: EventStatus): boolean =>
const eventIsScheduled = (eventStatus?: StatusType): boolean =>
!!eventStatus &&
([EventStatuses.SCHEDULE, EventStatuses.OVERDUE, EventStatuses.SKIPPED] as EventStatus[])
([statusTypes.SCHEDULE, statusTypes.OVERDUE, statusTypes.SKIPPED] as StatusType[])
.includes(eventStatus);


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import log from 'loglevel';
import { MenuItem, IconCheckmark16, IconUndo16 } from '@dhis2/ui';
import { useMutation } from '@tanstack/react-query';
import { useAlert, useDataEngine } from '@dhis2/app-runtime';
import { errorCreator } from 'capture-core-utils';
import { statusTypes as eventStatuses } from 'capture-core/events/statusTypes';

type Props = {
eventId: string;
eventStatus?: string;
onMutate?: (newStatus: string) => void;
onSuccess?: (newStatus: string) => void;
onError?: () => void;
onClose: () => void;
};

export const CompletionMenuItem = ({
eventId,
eventStatus,
onMutate,
onSuccess,
onError,
onClose,
}: Props) => {
const dataEngine = useDataEngine();
const { show: showError } = useAlert(
({ message }) => message,
{ critical: true },
);

const isCompleted = eventStatus === eventStatuses.COMPLETED;
const newStatus = isCompleted ? eventStatuses.ACTIVE : eventStatuses.COMPLETED;

const { mutate: updateCompletionStatus } = useMutation(
async () => {
const { event: apiEvent } = await dataEngine.query({
event: {
resource: 'tracker/events',
id: eventId,
params: {
fields: '*,!completedAt,!completedBy,!dataValues,!relationships',
},
},
}) as any;
return dataEngine.mutate({
resource: 'tracker?async=false&importStrategy=UPDATE',
type: 'create',
data: {
events: [{
...apiEvent,
status: newStatus,
}],
},
});
},
{
onMutate: () => {
onMutate?.(newStatus);
},
onError: (error: unknown) => {
showError({ message: i18n.t('An error occurred when updating event status') });
Comment thread
henrikmv marked this conversation as resolved.
log.error(errorCreator('An error occurred when updating event status')({ error, eventId, newStatus }));
onError?.();
},
onSuccess: () => {
onSuccess?.(newStatus);
},
},
);

return (
<MenuItem
dense
dataTest={isCompleted ? 'uncomplete-event-menu-item' : 'complete-event-menu-item'}
icon={isCompleted ? <IconUndo16 /> : <IconCheckmark16 />}
label={isCompleted ? i18n.t('Mark incomplete') : i18n.t('Mark complete')}
suffix=""
onClick={() => {
onClose();
updateCompletionStatus();
}}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@ export const DeleteActionModal = ({
}),
{
onMutate: () => {
// Capture the event before the optimistic removal so it can be restored
// from the same source of truth (the rendered redux event) if the delete fails.
const eventToRollbackOnFail = eventDetails;

onDeleteEvent(eventId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import {
IconDelete16,
MenuItem,
} from '@dhis2/ui';
import { ConditionalTooltip } from '../../../../../../Tooltips/ConditionalTooltip';
import { convertClientToView, convertServerToClient } from '../../../../../../../converters';
import { dataElementTypes, type ProgramStage } from '../../../../../../../metaData';
import { useEventEditPermissions } from '../../../../../../../hooks';
import { ConditionalTooltip } from '../../Tooltips/ConditionalTooltip';
import { convertClientToView, convertServerToClient } from '../../../converters';
import { dataElementTypes, type ProgramStage } from '../../../metaData';
import { useEventEditPermissions } from '../../../hooks';

type Props = {
setActionsOpen: (open: boolean) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { DeleteActionButton } from './DeleteMenuItem';
export { DeleteActionModal } from './DeleteEventModal';
export { CompletionMenuItem } from './CompletionMenuItem';
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { DeleteActionButton, DeleteActionModal, CompletionMenuItem } from './MenuItems';
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import type {
import {
EnrollmentPageKeys,
} from '../../common/EnrollmentOverviewDomain/EnrollmentPageLayout/DefaultEnrollmentLayout.constants';
import { EventStatuses } from '../../../Breadcrumbs/EnrollmentBreadcrumb/EnrollmentBreadcrumb';
import type { StatusType } from '../../../../events/statusTypes';

type EnrollmentPageKeyTypes = typeof EnrollmentPageKeys[keyof typeof EnrollmentPageKeys];
type EventStatus = typeof EventStatuses[keyof typeof EventStatuses];

export type Props = {
currentPage: EnrollmentPageKeyTypes;
Expand All @@ -29,7 +28,7 @@ export type Props = {
onBackToDashboard?: () => void;
onBackToViewEvent?: () => void;
userInteractionInProgress?: boolean;
eventStatus?: EventStatus;
eventStatus?: StatusType;
onDelete: () => void;
onAddNew: () => void;
onViewAll: (stageId: string) => void;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
});
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -324,7 +323,7 @@ const buildCompleteFieldSettingsFn = () => {
withDisplayMessages()(
withInternalChangeHandler()(
withConditionalTooltip((props: any) => {
const isEventCompleted = props.eventStatus === eventStatuses.COMPLETED;
const isEventCompleted = props.eventStatus === statusTypes.COMPLETED;
const canUncompleteEvent = props.canUncompleteEvent;
const shouldDisable = isEventCompleted && !canUncompleteEvent;
return shouldDisable
Expand All @@ -341,7 +340,7 @@ const buildCompleteFieldSettingsFn = () => {
const completeSettings = {
getComponent: () => completeComponent,
getComponentProps: (props: any) => {
const isEventCompleted = props.eventStatus === eventStatuses.COMPLETED;
const isEventCompleted = props.eventStatus === statusTypes.COMPLETED;
const canUncompleteEvent = props.canUncompleteEvent;
const shouldDisable = isEventCompleted && !canUncompleteEvent;

Expand Down Expand Up @@ -585,7 +584,8 @@ class EditEventDataEntryPlain extends Component<Props & WithStyles<typeof getSty

render() {
const { eventStatus } = this.props;
const isScheduleOrOverdue = eventStatus && [statusTypes.SCHEDULE, statusTypes.OVERDUE].includes(eventStatus);
const isScheduleOrOverdue =
eventStatus === statusTypes.SCHEDULE || eventStatus === statusTypes.OVERDUE;

return isScheduleOrOverdue ? this.renderScheduleView() : this.renderDataEntry();
}
Expand Down
Loading
Loading