Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
6f142ee
feat: unify overflow menus for event
henrikmv Jul 22, 2026
9c22533
feat: refactor EventOverflowMenu to compute visibility for menu items
henrikmv Jul 22, 2026
d3aacb1
fix: pass undefined to deleteEvent for improved error handling
henrikmv Jul 22, 2026
9afd2b9
fix: delete event functionality
henrikmv Jul 22, 2026
2a7b8ee
feat: update menu items icon
henrikmv Jul 22, 2026
6403107
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Jul 24, 2026
5f5a4e8
feat: unify event status update methods and read only badge functiona…
henrikmv Jul 24, 2026
72fbb17
feat: update translation strings and improve delete menu item label
henrikmv Jul 25, 2026
fe45f9e
feat: refactor event edit permissions and unify overflow menu item props
henrikmv Jul 27, 2026
14ed6a3
feat: add handling for SKIPPED event status in enrollment edit page mode
henrikmv Jul 27, 2026
a7f2d5f
feat: unify overflow menu item properties and remove unused features
henrikmv Jul 27, 2026
410438f
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Jul 28, 2026
d53faf6
Merge branch 'hv/feat/DHIS2-21655_uncomplete-event-view-mode' into hv…
henrikmv Jul 28, 2026
5515457
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Jul 28, 2026
0319de3
Merge branch 'hv/feat/DHIS2-21655_uncomplete-event-view-mode' into hv…
henrikmv Jul 28, 2026
b3cbbca
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Jul 28, 2026
c6f8d86
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Jul 28, 2026
b6bde1e
Merge branch 'hv/feat/DHIS2-21655_uncomplete-event-view-mode' into hv…
henrikmv Jul 31, 2026
9182f5a
feat: temp
henrikmv Jul 31, 2026
439be80
Merge branch 'hv/feat/DHIS2-21655_uncomplete-event-view-mode' into hv…
henrikmv Jul 31, 2026
b886dc0
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Jul 31, 2026
fe28ff0
feat: temp clean up
henrikmv Jul 31, 2026
51eedc4
feat: unify overflow menu actions in WidgetHeader component
henrikmv Jul 31, 2026
b4064f9
feat: include completedBy in enrollment event mutation
henrikmv Jul 31, 2026
34e94aa
feat: align with parent branch
henrikmv Jul 31, 2026
511f1bb
feat: implement useOptimisticEventStatus hook for event status manage…
henrikmv Jul 31, 2026
9268bb4
feat: add folder for menu items
henrikmv Aug 4, 2026
10a70bf
Merge remote-tracking branch 'origin/master' into hv/feat/DHIS2-21875…
henrikmv Aug 4, 2026
960ec6e
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Aug 4, 2026
b39ae0f
Merge remote-tracking branch 'origin/master' into hv/feat/DHIS2-21875…
henrikmv Aug 4, 2026
48d10ca
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Aug 4, 2026
457fc0b
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Aug 4, 2026
b358136
Merge remote-tracking branch 'origin/hv/feat/DHIS2-21655_uncomplete-e…
henrikmv Aug 4, 2026
0c08857
Merge branch 'hv/feat/DHIS2-21655_uncomplete-event-view-mode' into hv…
henrikmv Aug 4, 2026
fbec05e
feat: unify event permissions handling
henrikmv Aug 4, 2026
edcb8cc
feat: temp
henrikmv Aug 4, 2026
71d81aa
feat: temp
henrikmv Aug 5, 2026
fb3891e
feat: one menu item per action
henrikmv Aug 5, 2026
68e1b91
feat: update event overflow menu
henrikmv Aug 5, 2026
fd2c74e
Merge remote-tracking branch 'origin/hv/chore/DHIS2-21941_SingleSourc…
henrikmv Aug 5, 2026
7909a3b
Merge remote-tracking branch 'origin/hv/chore/DHIS2-21941_SingleSourc…
henrikmv Aug 6, 2026
a0e8f3b
feat: add event skipped handling read only
henrikmv Aug 6, 2026
e1583ab
feat: stay on view mode when unskip from view mode
henrikmv Aug 6, 2026
994648f
feat: unify event page mode handling by including eventId in useEnrol…
henrikmv Aug 6, 2026
7c9bef0
feat: replace direct query removal with utility function for event ch…
henrikmv Aug 6, 2026
e76df13
feat: read only badge upper right corner for skipped events
henrikmv Aug 6, 2026
2865cfe
Merge remote-tracking branch 'origin/hv/chore/DHIS2-21941_SingleSourc…
henrikmv Aug 12, 2026
432c107
Merge branch 'hv/chore/DHIS2-21941_SingleSourceChangelogValues' into …
henrikmv Aug 12, 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
45 changes: 24 additions & 21 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-12T10:46:09.643Z\n"
"PO-Revision-Date: 2026-08-12T10:46:09.643Z\n"
"POT-Creation-Date: 2026-08-12T10:56:04.908Z\n"
"PO-Revision-Date: 2026-08-12T10:56:04.908Z\n"

msgid "The application could not be loaded."
msgstr "The application could not be loaded."
Expand Down Expand Up @@ -437,6 +437,9 @@ 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 "View changelog"
msgstr "View changelog"

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

Expand All @@ -446,6 +449,18 @@ msgstr "Mark incomplete"
msgid "Mark complete"
msgstr "Mark complete"

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 "An error occurred while deleting the event"
msgstr "An error occurred while deleting the event"

Expand All @@ -461,17 +476,11 @@ 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 "Unskip"
msgstr "Unskip"

msgid "Delete"
msgstr "Delete"
msgid "Skip"
msgstr "Skip"

msgid "No events to display"
msgstr "No events to display"
Expand Down Expand Up @@ -1002,9 +1011,6 @@ msgstr "events"
msgid "event"
msgstr "event"

msgid "View changelog"
msgstr "View changelog"

msgid "Event details"
msgstr "Event details"

Expand Down Expand Up @@ -1128,6 +1134,9 @@ 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 skipped"
msgstr "This event has been skipped"

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

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

msgid "Unskip"
msgstr "Unskip"

msgid "Skip"
msgstr "Skip"

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useQueryClient } from '@tanstack/react-query';
import { CircularLoader, FlyoutMenu, IconMore16 } from '@dhis2/ui';
import type { ApiEnrollmentEvent } from 'capture-core-utils/types/api-types';
import { statusTypes as eventStatuses } from 'capture-core/events/statusTypes';
import { useCanChangeCompletionStatus } from 'capture-core/hooks';
import { OverflowButton } from '../Buttons';
import { removeEventChangelogQueries } from '../WidgetsChangelog';
import { type ProgramStage } from '../../metaData';
import {
updateEnrollmentEvent,
commitEnrollmentEvent,
rollbackEnrollmentEvent,
} from '../Pages/common/EnrollmentOverviewDomain';
import {
CompletionMenuItem,
SkipMenuItem,
ChangelogMenuItem,
DeleteMenuItem,
DeleteEventModal,
} from './MenuItems';

type Props = {
eventId: string;
eventDetails: ApiEnrollmentEvent;
programId: string;
programStage?: ProgramStage | null;
pendingApiResponse?: boolean;
onDeleteEvent: (eventId: string) => void;
onRollbackDeleteEvent: (event: ApiEnrollmentEvent) => void;
onOpenChangelog: () => void;
onStatusUpdated?: (newStatus: string) => void;
dataTest: string;
};

export const EventOverflowMenu = ({
eventId,
eventDetails,
programId,
programStage,
pendingApiResponse,
onDeleteEvent,
onRollbackDeleteEvent,
onOpenChangelog,
onStatusUpdated,
dataTest,
}: Props) => {
const [actionsOpen, setActionsOpen] = useState(false);
const [deleteModalOpen, setDeleteModalOpen] = useState(false);
const dispatch = useDispatch();
const queryClient = useQueryClient();

const canChangeCompletionStatus = useCanChangeCompletionStatus({
programId,
stage: programStage,
eventStatus: eventDetails.status,
});
const canSkip = eventDetails.status === eventStatuses.SCHEDULE ||
eventDetails.status === eventStatuses.SKIPPED;

// Optimistic update shared by both status toggles (complete/incomplete and skip/unskip):
// apply the new status right away, then commit on success or roll back on error.
const onStatusMutate = (newStatus: string) => {
const { completedAt, completedBy, ...eventWithoutCompletion } = eventDetails;
dispatch(updateEnrollmentEvent(eventId, { ...eventWithoutCompletion, status: newStatus }));
};
const onStatusSuccess = (newStatus: string) => {
dispatch(commitEnrollmentEvent(eventId));
// Drop the cached changelog for this event, the way the event pages do on save. Status
// changes are not rendered today (no `status` item definition, so those rows are
// discarded), but this sits with the mutation rather than in each host, so it stays correct
// if they become displayable.
removeEventChangelogQueries(queryClient, eventId);
onStatusUpdated?.(newStatus);
};
const onStatusError = () => {
dispatch(rollbackEnrollmentEvent(eventId));
};

if (pendingApiResponse) {
return <CircularLoader small dataTest={`${dataTest}-loader`} />;
}

return (
<>
<OverflowButton
open={actionsOpen}
onClick={() => setActionsOpen(prev => !prev)}
icon={<IconMore16 />}
small
secondary
dataTest={`${dataTest}-button`}
component={
<FlyoutMenu
dense
maxWidth="250px"
dataTest={`${dataTest}-menu`}
>
{canChangeCompletionStatus && (
<CompletionMenuItem
eventId={eventId}
eventStatus={eventDetails.status}
onMutate={onStatusMutate}
onSuccess={onStatusSuccess}
onError={onStatusError}
onClose={() => setActionsOpen(false)}
/>
)}
{canSkip && (
<SkipMenuItem
eventId={eventId}
eventStatus={eventDetails.status}
onMutate={onStatusMutate}
onSuccess={onStatusSuccess}
onError={onStatusError}
onClose={() => setActionsOpen(false)}
/>
)}
<ChangelogMenuItem
onOpenChangelog={onOpenChangelog}
onClose={() => setActionsOpen(false)}
/>
<DeleteMenuItem
occurredAt={eventDetails.occurredAt}
completedAt={eventDetails.completedAt}
eventStatus={eventDetails.status}
programId={programId}
programStage={programStage}
onRequestDelete={() => setDeleteModalOpen(true)}
onClose={() => setActionsOpen(false)}
/>
</FlyoutMenu>
}
/>

{deleteModalOpen && (
<DeleteEventModal
eventId={eventId}
eventDetails={eventDetails}
onDeleteEvent={onDeleteEvent}
onRollbackDeleteEvent={onRollbackDeleteEvent}
onClose={() => setDeleteModalOpen(false)}
/>
)}
</>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import i18n from '@dhis2/d2-i18n';
import { MenuItem, IconClockHistory16 } from '@dhis2/ui';

type Props = {
onOpenChangelog: () => void;
onClose: () => void;
};

export const ChangelogMenuItem = ({ onOpenChangelog, onClose }: Props) => (
<MenuItem
dense
icon={<IconClockHistory16 />}
label={i18n.t('View changelog')}
suffix={null}
dataTest="event-overflow-changelog"
onClick={() => {
onOpenChangelog();
onClose();
}}
/>
);

This file was deleted.

Loading
Loading