diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ab035123e..bb83bc17af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,17 @@ +## [101.16.2](https://github.com/dhis2/capture-app/compare/v101.16.1...v101.16.2) (2024-11-19) + + +### Bug Fixes + +* [DHIS2-16994] Image and File DE and TEA not Displayed in Changelog ([#3837](https://github.com/dhis2/capture-app/issues/3837)) ([9327210](https://github.com/dhis2/capture-app/commit/932721045126e02379f56a85af4f6586b836b4c0)) + +## [101.16.1](https://github.com/dhis2/capture-app/compare/v101.16.0...v101.16.1) (2024-11-17) + + +### Bug Fixes + +* **translations:** sync translations from transifex (master) ([37c32df](https://github.com/dhis2/capture-app/commit/37c32df3e7839e30b493ff8e8185de769c3e2fd4)) + # [101.16.0](https://github.com/dhis2/capture-app/compare/v101.15.0...v101.16.0) (2024-11-13) diff --git a/i18n/en.pot b/i18n/en.pot index 52a8d3100b..39bf445b7a 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..." @@ -1557,6 +1557,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/i18n/es_419.po b/i18n/es_419.po index c94ef95e6a..f996ede3ef 100644 --- a/i18n/es_419.po +++ b/i18n/es_419.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" -"POT-Creation-Date: 2024-10-14T14:53:34.553Z\n" +"POT-Creation-Date: 2024-10-25T18:18:11.518Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" "Last-Translator: Jaime Bosque , 2024\n" "Language-Team: Spanish (Latin America) (https://app.transifex.com/hisp-uio/teams/100509/es_419/)\n" @@ -700,8 +700,10 @@ msgid "Add new enrollment for {{teiDisplayName}} in this program." msgstr "" "Agregue una nueva inscripción para {{teiDisplayName}} en este programa." -msgid "No access to program owner." -msgstr "Sin acceso al propietario del programa." +msgid "" +"You do not have permissions to access to this program, registering unit or " +"record, contact your administrator for more information." +msgstr "" msgid "{{teiDisplayName}} is not enrolled in this program." msgstr "{{teiDisplayName}} no está inscrito en este programa." @@ -1098,7 +1100,7 @@ msgstr "" msgid "Assigned to" msgstr "" -msgid "You don't have access to edit this assignee" +msgid "You don't have access to edit the assigned user" msgstr "" msgid "Edit" @@ -1107,7 +1109,7 @@ msgstr "" msgid "No one is assigned to this event" msgstr "No hay nadie asignado a este evento." -msgid "You don't have access to assign an assignee" +msgid "You don't have access to assign a user to this event" msgstr "" msgid "Assign" @@ -1153,6 +1155,9 @@ msgstr "" msgid "Mark incomplete" msgstr "" +msgid "You do not have access to delete this enrollment" +msgstr "" + msgid "Delete enrollment" msgstr "" @@ -1534,7 +1539,7 @@ msgid "Change" msgstr "" msgid "Value" -msgstr "" +msgstr "Valor" msgid "New {{trackedEntityTypeName}} relationship" msgstr "" diff --git a/i18n/pt.po b/i18n/pt.po index 1a05f44f6b..7c9dc3090d 100644 --- a/i18n/pt.po +++ b/i18n/pt.po @@ -11,13 +11,14 @@ # Jason Pickering , 2024 # Helton Dias, 2024 # Shelsea Chumaio, 2024 +# Laurência Luís, 2024 # msgid "" msgstr "" "Project-Id-Version: i18next-conv\n" "POT-Creation-Date: 2024-10-25T18:18:11.518Z\n" "PO-Revision-Date: 2019-06-27 07:31+0000\n" -"Last-Translator: Shelsea Chumaio, 2024\n" +"Last-Translator: Laurência Luís, 2024\n" "Language-Team: Portuguese (https://app.transifex.com/hisp-uio/teams/100509/pt/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -340,7 +341,7 @@ msgstr "" msgid "" "Would you like to complete the enrollment and all active events as well?" -msgstr "" +msgstr "Gostaria de completar a inscrição e todos os eventos activos?" msgid "{{count}} event in {{programStageName}}" msgid_plural "{{count}} event in {{programStageName}}" @@ -349,13 +350,13 @@ msgstr[1] "" msgstr[2] "" msgid "Yes, complete enrollment and events" -msgstr "" +msgstr "Sim, completar a inscrição e os eventos" msgid "Complete enrollment only" -msgstr "" +msgstr "Completar apenas a inscrição" msgid "Would you like to complete the enrollment?" -msgstr "" +msgstr "Gostaria de completar a inscrição?" msgid "Complete enrollment" msgstr "Completar inscrição" @@ -391,7 +392,7 @@ msgid "validation failed" msgstr "falha na validação" msgid "No feedback for this event yet" -msgstr "" +msgstr "Ainda não há feedback para este evento" msgid "No indicator output for this event yet" msgstr "" @@ -701,8 +702,10 @@ msgstr "Não há inscrições ativas." msgid "Add new enrollment for {{teiDisplayName}} in this program." msgstr "Adicione uma nova inscrição para {{teiDisplayName}} neste programa." -msgid "No access to program owner." -msgstr "Sem acesso ao proprietário do programa." +msgid "" +"You do not have permissions to access to this program, registering unit or " +"record, contact your administrator for more information." +msgstr "" msgid "{{teiDisplayName}} is not enrolled in this program." msgstr "{{teiDisplayName}} não está inscrito neste programa." @@ -1107,7 +1110,7 @@ msgstr "" msgid "Assigned to" msgstr "Atribuído a" -msgid "You don't have access to edit this assignee" +msgid "You don't have access to edit the assigned user" msgstr "" msgid "Edit" @@ -1116,7 +1119,7 @@ msgstr "Editar" msgid "No one is assigned to this event" msgstr "Ninguém está atribuído a este evento" -msgid "You don't have access to assign an assignee" +msgid "You don't have access to assign a user to this event" msgstr "" msgid "Assign" diff --git a/i18n/ru.po b/i18n/ru.po index 23d7e47cd1..0d16b78d76 100644 --- a/i18n/ru.po +++ b/i18n/ru.po @@ -701,8 +701,10 @@ msgstr "" "Создать новую регистрационную запись для объекта {{teiDisplayName}} в данной" " программе." -msgid "No access to program owner." -msgstr "Нет доступа к владельцу программы." +msgid "" +"You do not have permissions to access to this program, registering unit or " +"record, contact your administrator for more information." +msgstr "" msgid "{{teiDisplayName}} is not enrolled in this program." msgstr "Объект {{teiDisplayName}} не зарегистрирован в данной программе." @@ -1106,8 +1108,8 @@ msgstr "Нет доступных отслеживаемых типов объе msgid "Assigned to" msgstr "Назначено пользователю" -msgid "You don't have access to edit this assignee" -msgstr "У вас нет доступа к редактированию данного назначенного пользователя" +msgid "You don't have access to edit the assigned user" +msgstr "" msgid "Edit" msgstr "Редактировать" @@ -1115,8 +1117,8 @@ msgstr "Редактировать" msgid "No one is assigned to this event" msgstr "Никто не назначен для данного события" -msgid "You don't have access to assign an assignee" -msgstr "У вас нет доступа к назначению пользователя" +msgid "You don't have access to assign a user to this event" +msgstr "" msgid "Assign" msgstr "Назначить" diff --git a/i18n/zh_CN.po b/i18n/zh_CN.po index 8d01178792..6a0a80ee90 100644 --- a/i18n/zh_CN.po +++ b/i18n/zh_CN.po @@ -667,8 +667,10 @@ msgstr "没有活跃的注册。" msgid "Add new enrollment for {{teiDisplayName}} in this program." msgstr "在此计划中为 {{teiDisplayName}} 添加新注册。" -msgid "No access to program owner." -msgstr "无法访问程序所有者。" +msgid "" +"You do not have permissions to access to this program, registering unit or " +"record, contact your administrator for more information." +msgstr "" msgid "{{teiDisplayName}} is not enrolled in this program." msgstr "该项目未报名{{teiDisplayName}} 。" @@ -1055,7 +1057,7 @@ msgstr "" msgid "Assigned to" msgstr "指派到" -msgid "You don't have access to edit this assignee" +msgid "You don't have access to edit the assigned user" msgstr "" msgid "Edit" @@ -1064,7 +1066,7 @@ msgstr "编辑" msgid "No one is assigned to this event" msgstr "没有人被分配到的该事件" -msgid "You don't have access to assign an assignee" +msgid "You don't have access to assign a user to this event" msgstr "" msgid "Assign" diff --git a/package.json b/package.json index 07eb53096a..3af3dfcf5c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "capture-app", "homepage": ".", - "version": "101.16.0", + "version": "101.16.2", "cacheVersion": "7", "serverVersion": "38", "license": "BSD-3-Clause", @@ -10,7 +10,7 @@ "packages/rules-engine" ], "dependencies": { - "@dhis2/rules-engine-javascript": "101.16.0", + "@dhis2/rules-engine-javascript": "101.16.2", "@dhis2/app-runtime": "^3.9.3", "@dhis2/d2-i18n": "^1.1.0", "@dhis2/d2-icons": "^1.0.1", diff --git a/packages/rules-engine/package.json b/packages/rules-engine/package.json index d0b7bd5ae9..f2b60b8805 100644 --- a/packages/rules-engine/package.json +++ b/packages/rules-engine/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/rules-engine-javascript", - "version": "101.16.0", + "version": "101.16.2", "license": "BSD-3-Clause", "main": "./build/cjs/index.js", "scripts": { 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 d4bb36c863..21b2e372ea 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -181,6 +181,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, +});