diff --git a/CHANGELOG.md b/CHANGELOG.md index bb83bc17af..91bede54a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,3 @@ -## [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) diff --git a/i18n/en.pot b/i18n/en.pot index 11c24b0478..9abb22fa99 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-07T11:57:59.094Z\n" -"PO-Revision-Date: 2024-11-07T11:57:59.094Z\n" +"POT-Creation-Date: 2024-11-04T18:45:47.626Z\n" +"PO-Revision-Date: 2024-11-04T18:45:47.626Z\n" msgid "Choose one or more dates..." msgstr "Choose one or more dates..." @@ -1547,12 +1547,6 @@ 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/package.json b/package.json index 3af3dfcf5c..b54a12f078 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "capture-app", "homepage": ".", - "version": "101.16.2", + "version": "101.16.1", "cacheVersion": "7", "serverVersion": "38", "license": "BSD-3-Clause", @@ -10,7 +10,7 @@ "packages/rules-engine" ], "dependencies": { - "@dhis2/rules-engine-javascript": "101.16.2", + "@dhis2/rules-engine-javascript": "101.16.1", "@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 f2b60b8805..4526bc13fe 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.2", + "version": "101.16.1", "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 30af8fca67..70ee627719 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,7 +56,6 @@ const getStyles = () => ({ type Props = { showEditEvent: ?boolean, eventId: string, - eventData: Object, onOpenEditEvent: (orgUnit: Object, programCategory: ?ProgramCategory) => void, programStage: ProgramStage, eventAccess: { read: boolean, write: boolean }, @@ -77,7 +76,6 @@ const EventDetailsSectionPlain = (props: Props) => { const { classes, eventId, - eventData, onOpenEditEvent, showEditEvent, programStage, @@ -202,7 +200,6 @@ 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 46c97da75d..e57972d6bc 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,7 +10,6 @@ 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 e7041bdd74..d850589751 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, eventData, ...passOnProps }: Props) => { +export const EventChangelogWrapper = ({ formFoundation, eventId, ...passOnProps }: Props) => { const dataItemDefinitions = useMemo(() => { const elements = formFoundation.getElements(); const contextLabels = formFoundation.getLabels(); @@ -42,9 +42,19 @@ export const EventChangelogWrapper = ({ formFoundation, eventId, eventData, ...p return acc; }, {}); + const additionalFields = { + geometry: { + id: 'geometry', + name: formFoundation.featureType, + type: formFoundation.featureType === 'Polygon' ? + dataElementTypes.POLYGON : dataElementTypes.COORDINATE, + }, + }; + return { ...fieldElementsById, ...fieldElementsContext, + ...additionalFields, }; }, [formFoundation]); @@ -52,7 +62,6 @@ export const EventChangelogWrapper = ({ formFoundation, eventId, eventData, ...p ); 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 58cb1d981f..274891017e 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,5 +10,4 @@ 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 2a6cb8be80..075855e6cd 100644 --- a/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js +++ b/src/core_modules/capture-core/components/WidgetEventEdit/WidgetEventEdit.container.js @@ -236,7 +236,6 @@ 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 faf83dfe91..e711f1a1f7 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,7 +9,6 @@ import { TrackedEntityChangelogWrapper } from './TrackedEntityChangelogWrapper'; export const OverflowMenuComponent = ({ trackedEntity, - trackedEntityData, trackedEntityTypeName, canWriteData, canCascadeDeleteTei, @@ -69,7 +68,6 @@ 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 38d17ad0c0..031f7fd462 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,7 +8,6 @@ export const OverflowMenu = ({ trackedEntityTypeName, canWriteData, trackedEntity, - trackedEntityData, onDeleteSuccess, displayChangelog, teiId, @@ -22,7 +21,6 @@ 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 84ea57e86a..ad3cc687d4 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,7 +3,6 @@ export type Props = {| trackedEntity: { trackedEntity: string }, trackedEntityTypeName: string, - trackedEntityData: Object, canWriteData: boolean, onDeleteSuccess?: () => void, displayChangelog: boolean, @@ -14,7 +13,6 @@ 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 27db89634d..1dc3172af6 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,14 +5,9 @@ import { useFormFoundation } from '../../DataEntry/hooks'; import { WidgetTrackedEntityChangelog } from '../../../WidgetsChangelog'; import type { Props } from './TrackedEntityChangelogWrapper.types'; -export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, trackedEntityData, ...passOnProps }: Props) => { +export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, ...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(); @@ -63,7 +58,6 @@ export const TrackedEntityChangelogWrapper = ({ programAPI, teiId, setIsOpen, tr 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 c2c5d726f4..e0bb62461f 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,7 +5,6 @@ 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 f63800dd43..0204ac19a7 100644 --- a/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js +++ b/src/core_modules/capture-core/components/WidgetProfile/WidgetProfile.component.js @@ -162,7 +162,6 @@ 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 aabb240e4a..86137d295e 100644 --- a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js +++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetEventChangelog/WidgetEventChangelog.js @@ -5,7 +5,6 @@ import { Changelog, CHANGELOG_ENTITY_TYPES } from '../common/Changelog'; type Props = { eventId: string, - eventData: Object, dataItemDefinitions: ItemDefinitions, isOpen: boolean, setIsOpen: (boolean | boolean => boolean) => void, @@ -13,7 +12,6 @@ type Props = { export const WidgetEventChangelog = ({ eventId, - eventData, setIsOpen, ...passOnProps }: Props) => ( @@ -21,7 +19,6 @@ 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 a28370ea35..86e941142c 100644 --- a/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js +++ b/src/core_modules/capture-core/components/WidgetsChangelog/WidgetTrackedEntityChangelog/WidgetTrackedEntityChangelog.js @@ -9,20 +9,17 @@ 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, @@ -27,11 +25,9 @@ export const Changelog = ({ dataItemDefinitions, }: Props) => { const { - rawRecords, + records, pager, - isLoading: isChangelogLoading, - page, - pageSize, + isLoading, setPage, setPageSize, sortDirection, @@ -40,24 +36,10 @@ export const Changelog = ({ entityId, entityType, programId, - }); - - const { - processedRecords, - isLoading: isProcessingLoading, - } = useListDataValues({ - rawRecords, dataItemDefinitions, - entityId, - entityData, - entityType, - programId, - sortDirection, - page, - pageSize, }); - if (isChangelogLoading || isProcessingLoading) { + if (isLoading) { return ( @@ -69,7 +51,7 @@ export const Changelog = ({ , - previousValue?: string, - currentValue?: string, - classes: { - container: string, - previousValue: string, - currentValue: string, - arrow: string, - } -} - -const styles = { - container: { - display: 'flex', - flexDirection: 'row', - alignItems: 'center', - whiteSpace: 'normal', - height: '100%', - }, - previousValue: { - color: colors.grey700, - wordBreak: 'break-word', - }, - currentValue: { - color: colors.grey900, - wordBreak: 'break-word', - }, - arrow: { - margin: `0 ${spacers.dp4}`, - }, -}; - -const Updated = ({ previousValue, currentValue, classes }) => ( -
-
{previousValue}
-
-
{currentValue}
-
-); - -const Created = ({ currentValue, classes }) => ( -
- {currentValue} -
-); - -const Deleted = ({ previousValue, classes }) => ( -
- {previousValue} -
-); - -const ChangelogComponentsByType = { - [CHANGE_TYPES.UPDATED]: Updated, - [CHANGE_TYPES.CREATED]: Created, - [CHANGE_TYPES.DELETED]: Deleted, -}; - -const ChangelogValueCellPlain = ({ changeType, currentValue, previousValue, classes }: Props) => { - const ChangelogComponent = ChangelogComponentsByType[changeType]; - - if (!ChangelogComponent) { - log.error(errorCreator('Changelog component not found')({ changeType })); - return null; - } - - return ( - - ); -}; - -export const ChangelogValueCell = withStyles(styles)(ChangelogValueCellPlain); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCell.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCell.js new file mode 100644 index 0000000000..e5a46a32bf --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCell.js @@ -0,0 +1,99 @@ +// @flow +import React from 'react'; +import { withStyles } from '@material-ui/core/styles'; +import { colors, spacers } from '@dhis2/ui'; +import { CHANGE_TYPES } from '../../../Changelog/Changelog.constants'; +import { + DefaultChangelogComponentsByChangeType, + PlygonChangelogComponentsByChangeType, +} from './ChangelogValueCellComponents'; + +type Props = { + dataItemId: string, + changeType: $Values, + previousValue?: string, + currentValue?: string, + classes: { + container: string, + valueContainer: string, + buttonContainer: string, + previousValue: string, + currentValue: string, + updatePreviousValue: string, + updateCurrentValue: string, + updateArrow: string, + viewButton: string, + }, +}; + +const styles = { + container: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + whiteSpace: 'normal', + height: '100%', + }, + buttonContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + }, + previousValue: { + color: colors.grey700, + wordBreak: 'break-word', + }, + currentValue: { + color: colors.grey900, + wordBreak: 'break-word', + }, + updateArrow: { + display: 'inline-flex', + alignItems: 'center', + margin: spacers.dp4, + }, + viewButton: { + background: 'none', + border: 'none', + cursor: 'pointer', + color: colors.grey800, + display: 'flex', + alignItems: 'center', + '&:hover': { + textDecoration: 'underline', + color: 'black', + }, + }, +}; + +export const ChangelogValueCellPlain = ({ + dataItemId, + changeType, + currentValue, + previousValue, + classes, +}: Props) => { + if (!currentValue) { return null; } + const isPolygon = dataItemId === 'geometry' && currentValue.length > 2; + const ComponentsByChangeType = isPolygon + ? PlygonChangelogComponentsByChangeType + : DefaultChangelogComponentsByChangeType; + + const ChangelogComponent = ComponentsByChangeType[changeType]; + + if (!ChangelogComponent) { + console.error(`No component found for change type: ${changeType}`); + return null; + } + + return ( + + ); +}; + +export const ChangelogValueCell = withStyles(styles)(ChangelogValueCellPlain); + diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellComponents.types.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellComponents.types.js new file mode 100644 index 0000000000..784a342da4 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellComponents.types.js @@ -0,0 +1,13 @@ +// @flow +export type ChangelogValueCellProps = { + previousValue?: string, + currentValue?: string, + classes: { + container: string, + buttonContainer: string, + previousValue: string, + currentValue: string, + updateArrow: string, + viewButton: string, + }, + }; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellDefault.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellDefault.js new file mode 100644 index 0000000000..53d47340eb --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellDefault.js @@ -0,0 +1,33 @@ +// @flow +import React from 'react'; +import { IconArrowRight16 } from '@dhis2/ui'; +import { CHANGE_TYPES } from '../../../../Changelog/Changelog.constants'; +import type { ChangelogValueCellProps } from './ChangelogValueCellComponents.types'; + +const Updated = ({ previousValue, currentValue, classes }: ChangelogValueCellProps) => ( +
+ {previousValue} + + + + {currentValue} +
+); + +const Created = ({ currentValue, classes }: ChangelogValueCellProps) => ( +
+ {currentValue} +
+); + +const Deleted = ({ previousValue, classes }: ChangelogValueCellProps) => ( +
+ {previousValue} +
+); + +export const DefaultChangelogComponentsByChangeType = Object.freeze({ + [CHANGE_TYPES.UPDATED]: Updated, + [CHANGE_TYPES.CREATED]: Created, + [CHANGE_TYPES.DELETED]: Deleted, +}); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellPolygon.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellPolygon.js new file mode 100644 index 0000000000..2dd45fe46c --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/ChangelogValueCellPolygon.js @@ -0,0 +1,93 @@ +// @flow +import React, { useState } from 'react'; +import { IconArrowRight16, IconChevronUp16, IconChevronDown16 } from '@dhis2/ui'; +import { CHANGE_TYPES } from '../../../../Changelog/Changelog.constants'; +import type { ChangelogValueCellProps } from './ChangelogValueCellComponents.types'; + +const ValueDisplay = ({ value, showMore, className }) => ( + {showMore ? value : value?.slice(0, 1)} +); + +const ViewMoreButton = ({ showMore, onClick, classes }) => ( + +); + +const Updated = ({ previousValue, currentValue, classes }: ChangelogValueCellProps) => { + const [showMore, setShowMore] = useState(false); + + return ( + <> +
+ + + + + +
+
+ setShowMore(!showMore)} + classes={classes} + /> +
+ + + ); +}; + + +const Created = ({ currentValue, classes }: ChangelogValueCellProps) => { + const [showMore, setShowMore] = useState(false); + + return ( +
+ + setShowMore(!showMore)} + classes={classes} + /> +
+ ); +}; + +const Deleted = ({ previousValue, classes }: ChangelogValueCellProps) => { + const [showMore, setShowMore] = useState(false); + + return ( +
+ + setShowMore(!showMore)} + classes={classes} + /> +
+ ); +}; + +export const PlygonChangelogComponentsByChangeType = Object.freeze({ + [CHANGE_TYPES.UPDATED]: Updated, + [CHANGE_TYPES.CREATED]: Created, + [CHANGE_TYPES.DELETED]: Deleted, +}); diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/index.js new file mode 100644 index 0000000000..fc72eca0e2 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/ChangelogValueCellComponents/index.js @@ -0,0 +1,6 @@ +// @flow + +export { DefaultChangelogComponentsByChangeType } from './ChangelogValueCellDefault'; +export { PlygonChangelogComponentsByChangeType } from './ChangelogValueCellPolygon'; + +export type { ChangelogValueCellProps } from './ChangelogValueCellComponents.types'; diff --git a/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/index.js b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/index.js new file mode 100644 index 0000000000..88dbf63fb0 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetsChangelog/common/ChangelogTable/ChangelogCells/ChangelogValueCell/index.js @@ -0,0 +1,3 @@ +// @flow + +export { ChangelogValueCell } from './ChangelogValueCell'; 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 3169194fc1..e93571cbf2 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,11 +13,13 @@ type Props = { }, classes: { dataItemColumn: string, + valueColumn: string, }, }; const styles = { dataItemColumn: { wordWrap: 'break-word', hyphens: 'auto' }, + valueColumn: { wordWrap: 'break-word' }, }; const ChangelogTableRowPlain = ({ record, classes }: Props) => ( @@ -26,7 +28,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 7d2911cd6d..1cc9b74cc8 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,4 +1,3 @@ // @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 f9562cf4ac..3599cf48b2 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,31 +1,48 @@ // @flow -import { useState } from 'react'; +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 { useApiDataQuery } from '../../../../utils/reactQueryHelpers'; -import { - CHANGELOG_ENTITY_TYPES, - QUERY_KEYS_BY_ENTITY_TYPE, -} from '../Changelog/Changelog.constants'; -import type { - SortDirection, -} from '../Changelog/Changelog.types'; +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'; 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, property } = change; + const fieldId = dataElement ?? attribute ?? property; + 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 [page, setPage] = useState < number >(1); + const [pageSize, setPageSize] = useState < number >(DEFAULT_PAGE_SIZE); + const [sortDirection, setSortDirection] = useState < SortDirection >(DEFAULT_SORT_DIRECTION); + const { fromServerDate } = useTimeZoneConversion(); const handleChangePageSize = (newPageSize: number) => { setPage(1); @@ -33,7 +50,7 @@ export const useChangelogData = ({ }; const { data, isLoading, isError } = useApiDataQuery( - ['changelog', entityType, entityId, 'rawData', { sortDirection, page, pageSize, programId }], + ['changelog', entityType, entityId, { sortDirection, page, pageSize, programId }], { resource: `tracker/${QUERY_KEYS_BY_ENTITY_TYPE[entityType]}/${entityId}/changeLogs`, params: { @@ -50,15 +67,164 @@ export const useChangelogData = ({ }, ); + const mockData: any = useMemo(() => [ + { + createdAt: '2024-11-14T11:03:08.269', + createdBy: { + uid: 'xE7jOejl9FI', + username: 'admin', + firstName: 'John', + surname: 'Traore', + }, + type: 'UPDATE', + change: { + eventProperty: { + property: 'geometry', + previousValue: [-11.420051, 8.101209], + currentValue: [-11.450123, 8.125678], + }, + }, + }, + { + createdAt: '2024-11-14T11:05:47.437', + createdBy: { + uid: 'xE7jOejl9FI', + username: 'admin', + firstName: 'John', + surname: 'Traore', + }, + type: 'UPDATE', + change: { + eventProperty: { + property: 'geometry', + previousValue: [ + [-11.437732, 8.110726], + [-11.396705, 8.109876], + [-11.398421, 8.089143], + [-11.414729, 8.084894], + [-11.436873, 8.088803], + [-11.437732, 8.110726], + ], + currentValue: [ + [-11.440000, 8.120000], + [-11.400000, 8.115000], + [-11.402000, 8.095000], + [-11.418000, 8.090000], + [-11.440000, 8.120000], + ], + }, + }, + }, + { + createdAt: '2024-11-14T11:09:47.437', + createdBy: { + uid: 'xE7jOejl9FI', + username: 'admin', + firstName: 'John', + surname: 'Traore', + }, + type: 'UPDATE', + change: { + eventProperty: { + property: 'occurredAt', + previousValue: '2024-01-30T00:00:00.000', + currentValue: '2024-01-31T00:00:00.000', + }, + }, + }, + { + createdAt: '2024-11-14T11:15:00.000', + createdBy: { + uid: 'xE7jOejl9FI', + username: 'admin', + firstName: 'John', + surname: 'Traore', + }, + type: 'UPDATE', + change: { + eventProperty: { + property: 'scheduledAt', + previousValue: '2024-01-29T00:00:00.000', + currentValue: '2024-01-30T00:00:00.000', + }, + }, + }, + { + createdAt: '2019-09-27T00:02:11.604', + createdBy: { + uid: 'AIK2aQOJIbj', + username: 'tracker', + firstName: 'Tracker name', + surname: 'Tracker surname', + }, + type: 'UPDATE', + change: { + dataValue: { + dataElement: 'X8zyunlgUfM', + previousValue: 'Replacement', + currentValue: 'Fresh', + }, + }, + }, + ], []); + + + const records: ?Array = useMemo(() => { + if (!data) return undefined; + + return mockData.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, mockData]); + return { - rawRecords: data, + records, 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 deleted file mode 100644 index e94bf94a88..0000000000 --- a/src/core_modules/capture-core/components/WidgetsChangelog/common/hooks/useListDataValues.js +++ /dev/null @@ -1,177 +0,0 @@ -// @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 deleted file mode 100644 index 85b13724e2..0000000000 --- a/src/core_modules/capture-core/components/WidgetsChangelog/common/utils/getSubValueForChangelogData.js +++ /dev/null @@ -1,148 +0,0 @@ -// @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, -});