diff --git a/i18n/en.pot b/i18n/en.pot
index 9abb22fa99..11c24b0478 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-11-04T18:45:47.626Z\n"
-"PO-Revision-Date: 2024-11-04T18:45:47.626Z\n"
+"POT-Creation-Date: 2024-11-07T11:57:59.094Z\n"
+"PO-Revision-Date: 2024-11-07T11:57:59.094Z\n"
msgid "Choose one or more dates..."
msgstr "Choose one or more dates..."
@@ -1547,6 +1547,12 @@ msgstr "Change"
msgid "Value"
msgstr "Value"
+msgid "File"
+msgstr "File"
+
+msgid "Image"
+msgstr "Image"
+
msgid "New {{trackedEntityTypeName}} relationship"
msgstr "New {{trackedEntityTypeName}} relationship"
diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js
index 70ee627719..30af8fca67 100644
--- a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js
+++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.component.js
@@ -56,6 +56,7 @@ const getStyles = () => ({
type Props = {
showEditEvent: ?boolean,
eventId: string,
+ eventData: Object,
onOpenEditEvent: (orgUnit: Object, programCategory: ?ProgramCategory) => void,
programStage: ProgramStage,
eventAccess: { read: boolean, write: boolean },
@@ -76,6 +77,7 @@ const EventDetailsSectionPlain = (props: Props) => {
const {
classes,
eventId,
+ eventData,
onOpenEditEvent,
showEditEvent,
programStage,
@@ -200,6 +202,7 @@ const EventDetailsSectionPlain = (props: Props) => {
diff --git a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js
index e57972d6bc..46c97da75d 100644
--- a/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js
+++ b/src/core_modules/capture-core/components/Pages/ViewEvent/EventDetailsSection/EventDetailsSection.container.js
@@ -10,6 +10,7 @@ import type { ProgramCategory } from '../../../WidgetEventSchedule/CategoryOptio
const mapStateToProps = (state: ReduxState) => ({
showEditEvent: state.viewEventPage.eventDetailsSection && state.viewEventPage.eventDetailsSection.showEditEvent,
eventId: state.viewEventPage.eventId,
+ eventData: state.viewEventPage.loadedValues?.eventContainer?.values || {},
programId: state.currentSelections.programId,
});
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js
index d9f20c2e66..e7041bdd74 100644
--- a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js
+++ b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.component.js
@@ -5,7 +5,7 @@ import { dataElementTypes } from '../../../metaData';
import type { Props } from './EventChangelogWrapper.types';
import { WidgetEventChangelog } from '../../WidgetsChangelog';
-export const EventChangelogWrapper = ({ formFoundation, eventId, ...passOnProps }: Props) => {
+export const EventChangelogWrapper = ({ formFoundation, eventId, eventData, ...passOnProps }: Props) => {
const dataItemDefinitions = useMemo(() => {
const elements = formFoundation.getElements();
const contextLabels = formFoundation.getLabels();
@@ -52,6 +52,7 @@ export const EventChangelogWrapper = ({ formFoundation, eventId, ...passOnProps
);
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js
index 274891017e..58cb1d981f 100644
--- a/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js
+++ b/src/core_modules/capture-core/components/WidgetEventEdit/EventChangelogWrapper/EventChangelogWrapper.types.js
@@ -10,4 +10,5 @@ type PassOnProps = {|
export type Props = {
...PassOnProps,
formFoundation: RenderFoundation,
+ eventData: Object,
};
diff --git a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js
index 075855e6cd..2a6cb8be80 100644
--- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js
+++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js
@@ -236,6 +236,7 @@ export const WidgetEventEditPlain = ({
isOpen
setIsOpen={setChangeLogIsOpen}
eventId={loadedValues.eventContainer.id}
+ eventData={loadedValues.eventContainer.values}
formFoundation={formFoundation}
/>
)}
diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js
index e711f1a1f7..faf83dfe91 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js
+++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.component.js
@@ -9,6 +9,7 @@ import { TrackedEntityChangelogWrapper } from './TrackedEntityChangelogWrapper';
export const OverflowMenuComponent = ({
trackedEntity,
+ trackedEntityData,
trackedEntityTypeName,
canWriteData,
canCascadeDeleteTei,
@@ -68,6 +69,7 @@ export const OverflowMenuComponent = ({
programAPI={programAPI}
isOpen={changelogIsOpen}
setIsOpen={setChangelogIsOpen}
+ trackedEntityData={trackedEntityData}
/>
)}
>
diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js
index 031f7fd462..38d17ad0c0 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js
+++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.container.js
@@ -8,6 +8,7 @@ export const OverflowMenu = ({
trackedEntityTypeName,
canWriteData,
trackedEntity,
+ trackedEntityData,
onDeleteSuccess,
displayChangelog,
teiId,
@@ -21,6 +22,7 @@ export const OverflowMenu = ({
canWriteData={canWriteData}
canCascadeDeleteTei={hasAuthority}
trackedEntity={trackedEntity}
+ trackedEntityData={trackedEntityData}
onDeleteSuccess={onDeleteSuccess}
displayChangelog={displayChangelog}
teiId={teiId}
diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js
index ad3cc687d4..84ea57e86a 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js
+++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/OverflowMenu.types.js
@@ -3,6 +3,7 @@
export type Props = {|
trackedEntity: { trackedEntity: string },
trackedEntityTypeName: string,
+ trackedEntityData: Object,
canWriteData: boolean,
onDeleteSuccess?: () => void,
displayChangelog: boolean,
@@ -13,6 +14,7 @@ export type Props = {|
export type PlainProps = {|
trackedEntity: { trackedEntity: string },
trackedEntityTypeName: string,
+ trackedEntityData: Object,
canWriteData: boolean,
canCascadeDeleteTei: boolean,
onDeleteSuccess?: () => void,
diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js
index 1dc3172af6..27db89634d 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js
+++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.component.js
@@ -5,9 +5,14 @@ import { useFormFoundation } from '../../DataEntry/hooks';
import { WidgetTrackedEntityChangelog } from '../../../WidgetsChangelog';
import type { Props } from './TrackedEntityChangelogWrapper.types';
-export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, ...passOnProps }: Props) => {
+export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, trackedEntityData, ...passOnProps }: Props) => {
const formFoundation: RenderFoundation = useFormFoundation(programAPI);
+ const transformedTrackedEntityData = trackedEntityData.reduce((acc, item) => {
+ acc[item.attribute] = item.value;
+ return acc;
+ }, {});
+
const dataItemDefinitions = useMemo(() => {
if (!Object.keys(formFoundation)?.length) return {};
const elements = formFoundation.getElements();
@@ -58,6 +63,7 @@ export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, ..
close={() => setIsOpen(false)}
programId={programAPI.id}
dataItemDefinitions={dataItemDefinitions}
+ trackedEntityData={transformedTrackedEntityData}
/>
);
};
diff --git a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js
index e0bb62461f..c2c5d726f4 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js
+++ b/src/core_modules/capture-core/components/WidgetProfile/OverflowMenu/TrackedEntityChangelogWrapper/TrackedEntityChangelogWrapper.types.js
@@ -5,6 +5,7 @@ type PassOnProps = {|
teiId: string,
isOpen: boolean,
setIsOpen: (boolean | boolean => boolean) => void,
+ trackedEntityData: Object,
|}
export type Props = {
diff --git a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js
index 0204ac19a7..f63800dd43 100644
--- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js
+++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js
@@ -162,6 +162,7 @@ const WidgetProfilePlain = ({
trackedEntity={trackedEntity}
onDeleteSuccess={onDeleteSuccess}
displayChangelog={displayChangelog}
+ trackedEntityData={clientAttributesWithSubvalues}
teiId={teiId}
programAPI={program}
/>
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js
index 86137d295e..aabb240e4a 100644
--- a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js
@@ -5,6 +5,7 @@ import { Changelog, CHANGELOG_ENTITY_TYPES } from '../common/Changelog';
type Props = {
eventId: string,
+ eventData: Object,
dataItemDefinitions: ItemDefinitions,
isOpen: boolean,
setIsOpen: (boolean | boolean => boolean) => void,
@@ -12,6 +13,7 @@ type Props = {
export const WidgetEventChangelog = ({
eventId,
+ eventData,
setIsOpen,
...passOnProps
}: Props) => (
@@ -19,6 +21,7 @@ export const WidgetEventChangelog = ({
{...passOnProps}
close={() => setIsOpen(false)}
entityId={eventId}
+ entityData={eventData}
entityType={CHANGELOG_ENTITY_TYPES.EVENT}
/>
);
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js
index 86e941142c..a28370ea35 100644
--- a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js
@@ -9,17 +9,20 @@ type Props = {
dataItemDefinitions: ItemDefinitions,
isOpen: boolean,
close: () => void,
+ trackedEntityData: Object,
}
export const WidgetTrackedEntityChangelog = ({
teiId,
programId,
close,
+ trackedEntityData,
...passOnProps
}: Props) => (
,
isOpen: boolean,
close: () => void,
dataItemDefinitions: ItemDefinitions,
programId?: string,
-}
+};
export const Changelog = ({
entityId,
+ entityData,
entityType,
programId,
isOpen,
@@ -25,9 +27,11 @@ export const Changelog = ({
dataItemDefinitions,
}: Props) => {
const {
- records,
+ rawRecords,
pager,
- isLoading,
+ isLoading: isChangelogLoading,
+ page,
+ pageSize,
setPage,
setPageSize,
sortDirection,
@@ -36,10 +40,24 @@ export const Changelog = ({
entityId,
entityType,
programId,
+ });
+
+ const {
+ processedRecords,
+ isLoading: isProcessingLoading,
+ } = useListDataValues({
+ rawRecords,
dataItemDefinitions,
+ entityId,
+ entityData,
+ entityType,
+ programId,
+ sortDirection,
+ page,
+ pageSize,
});
- if (isLoading) {
+ if (isChangelogLoading || isProcessingLoading) {
return (
@@ -51,7 +69,7 @@ export const Changelog = ({
(
-
{previousValue}
-
-
{currentValue}
+
{previousValue}
+
+
{currentValue}
);
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js
index e93571cbf2..3169194fc1 100644
--- a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogTableRow.js
@@ -13,13 +13,11 @@ type Props = {
},
classes: {
dataItemColumn: string,
- valueColumn: string,
},
};
const styles = {
dataItemColumn: { wordWrap: 'break-word', hyphens: 'auto' },
- valueColumn: { wordWrap: 'break-word' },
};
const ChangelogTableRowPlain = ({ record, classes }: Props) => (
@@ -28,7 +26,7 @@ const ChangelogTableRowPlain = ({ record, classes }: Props) => (
{record.user}
{record.dataItemLabel}
-
+
);
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js
index 1cc9b74cc8..7d2911cd6d 100644
--- a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/index.js
@@ -1,3 +1,4 @@
// @flow
export { useChangelogData } from './useChangelogData';
+export { useListDataValues } from './useListDataValues';
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js
index bcf1c82a54..f9562cf4ac 100644
--- a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useChangelogData.js
@@ -1,48 +1,31 @@
// @flow
-import moment from 'moment';
-import { v4 as uuid } from 'uuid';
-import log from 'loglevel';
-import { errorCreator } from 'capture-core-utils';
-import { useMemo, useState } from 'react';
-import { useTimeZoneConversion } from '@dhis2/app-runtime';
+import { useState } from 'react';
import { useApiDataQuery } from '../../../../utils/reactQueryHelpers';
-import { CHANGELOG_ENTITY_TYPES, QUERY_KEYS_BY_ENTITY_TYPE } from '../Changelog/Changelog.constants';
-import type { Change, ChangelogRecord, ItemDefinitions, SortDirection } from '../Changelog/Changelog.types';
-import { convertServerToClient } from '../../../../converters';
-import { convert } from '../../../../converters/clientToList';
+import {
+ CHANGELOG_ENTITY_TYPES,
+ QUERY_KEYS_BY_ENTITY_TYPE,
+} from '../Changelog/Changelog.constants';
+import type {
+ SortDirection,
+} from '../Changelog/Changelog.types';
type Props = {
entityId: string,
programId?: string,
entityType: $Values,
- dataItemDefinitions: ItemDefinitions,
-}
+};
const DEFAULT_PAGE_SIZE = 10;
const DEFAULT_SORT_DIRECTION = 'default';
-const getMetadataItemDefinition = (
- elementKey: string,
- change: Change,
- dataItemDefinitions: ItemDefinitions,
-) => {
- const { dataElement, attribute } = change;
- const fieldId = dataElement ?? attribute;
- const metadataElement = fieldId ? dataItemDefinitions[fieldId] : dataItemDefinitions[elementKey];
-
- return { metadataElement, fieldId };
-};
-
export const useChangelogData = ({
entityId,
entityType,
programId,
- dataItemDefinitions,
}: Props) => {
+ const [sortDirection, setSortDirection] = useState(DEFAULT_SORT_DIRECTION);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
- const [sortDirection, setSortDirection] = useState(DEFAULT_SORT_DIRECTION);
- const { fromServerDate } = useTimeZoneConversion();
const handleChangePageSize = (newPageSize: number) => {
setPage(1);
@@ -50,7 +33,7 @@ export const useChangelogData = ({
};
const { data, isLoading, isError } = useApiDataQuery(
- ['changelog', entityType, entityId, { sortDirection, page, pageSize, programId }],
+ ['changelog', entityType, entityId, 'rawData', { sortDirection, page, pageSize, programId }],
{
resource: `tracker/${QUERY_KEYS_BY_ENTITY_TYPE[entityType]}/${entityId}/changeLogs`,
params: {
@@ -67,62 +50,15 @@ export const useChangelogData = ({
},
);
- const records: ?Array = useMemo(() => {
- if (!data) return undefined;
-
- return data.changeLogs.map((changelog) => {
- const { change: apiChange, createdAt, createdBy } = changelog;
- const elementKey = Object.keys(apiChange)[0];
- const change = apiChange[elementKey];
-
- const { metadataElement, fieldId } = getMetadataItemDefinition(
- elementKey,
- change,
- dataItemDefinitions,
- );
-
- if (!metadataElement) {
- log.error(errorCreator('Could not find metadata for element')({
- ...changelog,
- }));
- return null;
- }
-
- const { firstName, surname, username } = createdBy;
- const { options } = metadataElement;
-
- const previousValue = convert(
- convertServerToClient(change.previousValue, metadataElement.type),
- metadataElement.type,
- options,
- );
-
- const currentValue = convert(
- convertServerToClient(change.currentValue, metadataElement.type),
- metadataElement.type,
- options,
- );
-
- return {
- reactKey: uuid(),
- date: moment(fromServerDate(createdAt)).format('YYYY-MM-DD HH:mm'),
- user: `${firstName} ${surname} (${username})`,
- dataItemId: fieldId,
- changeType: changelog.type,
- dataItemLabel: metadataElement.name,
- previousValue,
- currentValue,
- };
- }).filter(Boolean);
- }, [data, dataItemDefinitions, fromServerDate]);
-
return {
- records,
+ rawRecords: data,
pager: data?.pager,
setPage,
setPageSize: handleChangePageSize,
sortDirection,
setSortDirection,
+ page,
+ pageSize,
isLoading,
isError,
};
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useListDataValues.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useListDataValues.js
new file mode 100644
index 0000000000..e94bf94a88
--- /dev/null
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useListDataValues.js
@@ -0,0 +1,177 @@
+// @flow
+import { useMemo } from 'react';
+import log from 'loglevel';
+import { useTimeZoneConversion, useConfig, useDataEngine } from '@dhis2/app-runtime';
+import { useQuery } from 'react-query';
+import { errorCreator, buildUrl, pipe } from 'capture-core-utils';
+import { ReactQueryAppNamespace } from 'capture-core/utils/reactQueryHelpers';
+import { dataElementTypes } from '../../../../metaData';
+import { CHANGELOG_ENTITY_TYPES } from '../Changelog/Changelog.constants';
+import type { Change, ItemDefinitions, SortDirection } from '../Changelog/Changelog.types';
+import { convertServerToClient } from '../../../../converters';
+import { convert as convertClientToList } from '../../../../converters/clientToList';
+import { RECORD_TYPE, subValueGetterByElementType } from '../utils/getSubValueForChangelogData';
+import { makeQuerySingleResource } from '../../../../utils/api';
+import { attributeOptionsKey } from '../../../DataEntryDhis2Helpers';
+
+
+type Props = {
+ rawRecords: Object,
+ dataItemDefinitions: ItemDefinitions,
+ entityId: string,
+ entityData: Object,
+ entityType: $Values,
+ programId?: string,
+ sortDirection: SortDirection,
+ page: number,
+ pageSize: number,
+};
+
+const fetchFormattedValues = async ({
+ rawRecords,
+ dataItemDefinitions,
+ entityId,
+ entityData,
+ entityType,
+ programId,
+ absoluteApiPath,
+ querySingleResource,
+ fromServerDate,
+}) => {
+ if (!rawRecords) return [];
+
+ const getMetadataItemDefinition = (
+ elementKey: string,
+ change: Change,
+ ) => {
+ const fieldId = change.dataElement || change.attribute;
+ if (!fieldId) {
+ log.error('Could not find fieldId in change:', change);
+ return { metadataElement: null, fieldId: null };
+ }
+ const metadataElement = dataItemDefinitions[fieldId];
+ return { metadataElement, fieldId };
+ };
+
+
+ const fetchedRecords = await Promise.all(
+ rawRecords.changeLogs.map(async (changelog) => {
+ const { change: apiChange, createdAt, createdBy, type } = changelog;
+ const elementKey = Object.keys(apiChange)[0];
+ const change = apiChange[elementKey];
+
+ const { metadataElement, fieldId } = getMetadataItemDefinition(
+ elementKey,
+ change,
+ );
+
+ if (!metadataElement) {
+ log.error(
+ errorCreator('Could not find metadata for element')({ ...changelog }),
+ );
+ return null;
+ }
+
+ const getSubValue = subValueGetterByElementType[RECORD_TYPE[entityType]]?.[metadataElement.type];
+
+ const getValue = async (value, latestValue) => {
+ if (!getSubValue) {
+ return convertServerToClient(value, metadataElement.type);
+ }
+ if (entityType === RECORD_TYPE.trackedEntity) {
+ return getSubValue({
+ trackedEntity: { teiId: entityId, value },
+ programId,
+ attributeId: fieldId,
+ absoluteApiPath,
+ querySingleResource,
+ latestValue,
+ });
+ }
+ if (entityType === RECORD_TYPE.event) {
+ return getSubValue({
+ dataElement: { id: fieldId, value },
+ eventId: entityId,
+ absoluteApiPath,
+ querySingleResource,
+ latestValue,
+ });
+ }
+ return null;
+ };
+
+ const [previousValueClient, currentValueClient] = await Promise.all([
+ change.previousValue ? getValue(change.previousValue, false) : null,
+ getValue(change.currentValue, entityData?.[change.attribute ?? change.dataElement]?.value === change.currentValue),
+ ]);
+
+ const { firstName, surname, username } = createdBy;
+ const { options } = metadataElement;
+
+ const previousValue = convertClientToList(previousValueClient, metadataElement.type, options);
+ const currentValue = convertClientToList(currentValueClient, metadataElement.type, options);
+
+ return {
+ reactKey: fieldId ? `${createdAt}-${fieldId}` : attributeOptionsKey,
+ date: pipe(convertServerToClient, convertClientToList)(fromServerDate(createdAt), dataElementTypes.DATETIME),
+ user: `${firstName} ${surname} (${username})`,
+ changeType: type,
+ dataItemLabel: metadataElement.name,
+ previousValue,
+ currentValue,
+ };
+ }),
+ );
+
+ return fetchedRecords.filter(Boolean);
+};
+
+export const useListDataValues = ({
+ rawRecords,
+ dataItemDefinitions,
+ entityId,
+ entityData,
+ entityType,
+ programId,
+ sortDirection,
+ page,
+ pageSize,
+}: Props) => {
+ const dataEngine = useDataEngine();
+ const { baseUrl, apiVersion } = useConfig();
+ const { fromServerDate } = useTimeZoneConversion();
+ const absoluteApiPath = buildUrl(baseUrl, `api/${apiVersion}`);
+
+ const querySingleResource = useMemo(
+ () => makeQuerySingleResource(dataEngine.query.bind(dataEngine)),
+ [dataEngine],
+ );
+
+ const queryKey = [ReactQueryAppNamespace, 'changelog', entityType, entityId, 'formattedData', { sortDirection, page, pageSize, programId }];
+
+ const { data: processedRecords, isError, isLoading } = useQuery(
+ queryKey,
+ () => fetchFormattedValues({
+ rawRecords,
+ dataItemDefinitions,
+ entityId,
+ entityData,
+ entityType,
+ programId,
+ absoluteApiPath,
+ querySingleResource,
+ fromServerDate,
+ }),
+ {
+ enabled: !!rawRecords && !!dataItemDefinitions && !!entityId && !!entityType,
+ staleTime: Infinity,
+ cacheTime: Infinity,
+ },
+ );
+
+ return {
+ processedRecords,
+ isError,
+ isLoading,
+ };
+};
diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/utils/getSubValueForChangelogData.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/utils/getSubValueForChangelogData.js
new file mode 100644
index 0000000000..85b13724e2
--- /dev/null
+++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/utils/getSubValueForChangelogData.js
@@ -0,0 +1,148 @@
+// @flow
+import log from 'loglevel';
+import i18n from '@dhis2/d2-i18n';
+import { errorCreator } from 'capture-core-utils';
+import { dataElementTypes } from '../../../../metaData';
+import type { QuerySingleResource } from '../../../../utils/api';
+
+type SubValueTEAProps = {
+ trackedEntity: Object,
+ attributeId: string,
+ programId: string,
+ absoluteApiPath: string,
+ querySingleResource: QuerySingleResource,
+ latestValue?: boolean,
+};
+
+type SubValuesDataElementProps = {
+ dataElement: Object,
+ querySingleResource: QuerySingleResource,
+ eventId: string,
+ absoluteApiPath: string,
+ latestValue?: boolean,
+};
+
+const buildTEAUrlByElementType: {|
+[string]: Function,
+|} = {
+ [dataElementTypes.FILE_RESOURCE]: async ({
+ trackedEntity,
+ attributeId,
+ programId,
+ absoluteApiPath,
+ querySingleResource,
+ latestValue,
+ }: SubValueTEAProps) => {
+ const { teiId, value } = trackedEntity;
+ if (!value) return null;
+ try {
+ if (!latestValue) {
+ return i18n.t('File');
+ }
+
+ const { id, displayName: name } = await querySingleResource({ resource: `fileResources/${value}` });
+
+ return {
+ id,
+ name,
+ url: `${absoluteApiPath}/tracker/trackedEntities/${teiId}/attributes/${attributeId}/file?program=${programId}`,
+ };
+ } catch (error) {
+ log.error(
+ errorCreator('Error fetching file resource')({ error }),
+ );
+ return null;
+ }
+ },
+ [dataElementTypes.IMAGE]: async ({
+ trackedEntity,
+ attributeId,
+ programId,
+ absoluteApiPath,
+ latestValue,
+ querySingleResource,
+ }: SubValueTEAProps) => {
+ const { teiId, value } = trackedEntity;
+ if (!value) return null;
+ try {
+ if (!latestValue) {
+ return i18n.t('Image');
+ }
+ const { id, displayName: name } = await querySingleResource({ resource: `fileResources/${value}` });
+
+ return {
+ id,
+ name,
+ url: `${absoluteApiPath}/tracker/trackedEntities/${teiId}/attributes/${attributeId}/image?program=${programId}`,
+ previewUrl: `${absoluteApiPath}/tracker/trackedEntities/${teiId}/attributes/${attributeId}/image?program=${programId}&dimension=small`,
+ };
+ } catch (error) {
+ log.error(
+ errorCreator('Error fetching image resource')({ error }),
+ );
+ return null;
+ }
+ },
+};
+
+const buildDataElementUrlByElementType: {|
+[string]: Function,
+|} = {
+ [dataElementTypes.FILE_RESOURCE]: async ({ dataElement, querySingleResource, eventId, absoluteApiPath, latestValue }: SubValuesDataElementProps) => {
+ const { id: dataElementId, value } = dataElement;
+ if (!value) return null;
+
+ try {
+ if (!latestValue) {
+ return i18n.t('File');
+ }
+
+ const { id, displayName: name } = await querySingleResource({ resource: `fileResources/${value}` });
+
+ return {
+ id,
+ name,
+ url: `${absoluteApiPath}/tracker/events/${eventId}/dataValues/${dataElementId}/file`,
+ };
+ } catch (error) {
+ log.error(
+ errorCreator('Error fetching file resource')({ error }),
+ );
+ return null;
+ }
+ },
+ [dataElementTypes.IMAGE]: async ({ dataElement, querySingleResource, eventId, absoluteApiPath, latestValue }: SubValuesDataElementProps) => {
+ const { id: dataElementId, value } = dataElement;
+ if (!value) return null;
+
+ try {
+ if (!latestValue) {
+ return i18n.t('Image');
+ }
+
+ const { id, displayName: name } = await querySingleResource({ resource: `fileResources/${value}` });
+
+ return {
+ id,
+ name,
+ url: `${absoluteApiPath}/tracker/events/${eventId}/dataValues/${dataElementId}/image`,
+ previewUrl: `${absoluteApiPath}/tracker/events/${eventId}/dataValues/${dataElementId}/image?dimension=small`,
+ };
+ } catch (error) {
+ log.error(
+ errorCreator('Error fetching image resource')({ error }),
+ );
+ return null;
+ }
+ },
+};
+
+export const RECORD_TYPE = Object.freeze({
+ event: 'event',
+ trackedEntity: 'trackedEntity',
+});
+
+export const subValueGetterByElementType = Object.freeze({
+ [RECORD_TYPE.trackedEntity]: buildTEAUrlByElementType,
+ [RECORD_TYPE.event]: buildDataElementUrlByElementType,
+});