diff --git a/src/data-workspace/custom-form/custom-form-indicator-cell.jsx b/src/data-workspace/custom-form/custom-form-indicator-cell.jsx index 4d7f7e14f0..b2ad2efae5 100644 --- a/src/data-workspace/custom-form/custom-form-indicator-cell.jsx +++ b/src/data-workspace/custom-form/custom-form-indicator-cell.jsx @@ -16,6 +16,7 @@ export const CustomFormIndicatorCell = ({ indicatorId, metadata }) => { numerator={numerator} factor={factor} decimals={decimals} + indicatorId={indicatorId} /> ) } diff --git a/src/data-workspace/data-details-sidebar/basic-information.jsx b/src/data-workspace/data-details-sidebar/basic-information.jsx index b1e58b61e0..7caf3c1719 100644 --- a/src/data-workspace/data-details-sidebar/basic-information.jsx +++ b/src/data-workspace/data-details-sidebar/basic-information.jsx @@ -26,58 +26,76 @@ const BasicInformation = ({ item }) => { {item.categoryOptionComboName && ` | ${item.categoryOptionComboName}`} - - + + + )} ) } diff --git a/src/data-workspace/data-details-sidebar/data-details-sidebar.jsx b/src/data-workspace/data-details-sidebar/data-details-sidebar.jsx index eedcc98247..ca397273fa 100644 --- a/src/data-workspace/data-details-sidebar/data-details-sidebar.jsx +++ b/src/data-workspace/data-details-sidebar/data-details-sidebar.jsx @@ -24,6 +24,14 @@ export default function DataDetailsSidebar({ hide }) { if (!dataValue) { return null } + if (dataValue?.isIndicator) { + return ( + + {i18n.t('Details')} + + + ) + } return ( {i18n.t('Details')} diff --git a/src/data-workspace/data-details-sidebar/data-details-sidebar.test.jsx b/src/data-workspace/data-details-sidebar/data-details-sidebar.test.jsx index 8cc3f576db..057090058e 100644 --- a/src/data-workspace/data-details-sidebar/data-details-sidebar.test.jsx +++ b/src/data-workspace/data-details-sidebar/data-details-sidebar.test.jsx @@ -86,4 +86,53 @@ describe('DataDetailsSideBar', () => { ).toBeInTheDocument() }) }) + + describe('Indicator info', () => { + const mockIndicatorHighlightedFieldReturn = { + isIndicator: true, + displayFormName: 'EXP Drugs Expense Ratio', + description: 'Ratio of drug expenses to total expenses', + } + + beforeAll(() => { + useHighlightedField.mockReturnValue({ + ...mockIndicatorHighlightedFieldReturn, + }) + useUserInfo.mockImplementation(() => ({ + data: { + authorities: ['ALL'], + }, + })) + }) + + it('should only display the name and description', async () => { + const { getByText, queryByText } = renderComponent() + + expect( + getByText(mockIndicatorHighlightedFieldReturn.displayFormName, { + exact: false, + }) + ).toBeInTheDocument() + expect( + getByText( + `Description: ${mockIndicatorHighlightedFieldReturn.description}`, + { exact: false } + ) + ).toBeInTheDocument() + + expect(queryByText(/Code:/)).not.toBeInTheDocument() + expect(queryByText(/Data element ID:/)).not.toBeInTheDocument() + expect( + queryByText(/Category option combo ID:/) + ).not.toBeInTheDocument() + expect(queryByText(/Last updated/)).not.toBeInTheDocument() + expect(queryByText(/Marked for follow-up/)).not.toBeInTheDocument() + + // the other sidebar sections should not be rendered for indicators + expect(queryByText('Comment')).not.toBeInTheDocument() + expect(queryByText('Min and max limits')).not.toBeInTheDocument() + expect(queryByText('History')).not.toBeInTheDocument() + expect(queryByText('Audit log')).not.toBeInTheDocument() + }) + }) }) diff --git a/src/data-workspace/indicators-table-body/indicator-table-cell.jsx b/src/data-workspace/indicators-table-body/indicator-table-cell.jsx index 27cf981f53..f7c8165855 100644 --- a/src/data-workspace/indicators-table-body/indicator-table-cell.jsx +++ b/src/data-workspace/indicators-table-body/indicator-table-cell.jsx @@ -1,7 +1,12 @@ import i18n from '@dhis2/d2-i18n' import { TableCell, Tooltip, IconInfo16, IconWarning16 } from '@dhis2/ui' +import cx from 'classnames' import PropTypes from 'prop-types' -import React from 'react' +import React, { useState } from 'react' +import { + useHighlightedFieldStore, + useComponentWillUnmount, +} from '../../shared/index.js' import styles from '../table-body.module.css' import { NONCALCULABLE_VALUE, @@ -25,6 +30,7 @@ InvalidIndicatorWrapper.propTypes = { export const IndicatorTableCell = ({ denominator, factor, + indicatorId, numerator, decimals, }) => { @@ -39,6 +45,23 @@ export const IndicatorTableCell = ({ decimals, }) + const setHighlightedFieldId = useHighlightedFieldStore( + (state) => state.setHighlightedField + ) + + const [active, setActive] = useState(false) + const highlighted = useHighlightedFieldStore((state) => + state.isFieldHighlighted({ + indicatorId: indicatorId, + }) + ) + + useComponentWillUnmount(() => { + if (highlighted) { + setHighlightedFieldId(null) + } + }, [highlighted, setHighlightedFieldId]) + if (indicatorValue === NONCALCULABLE_VALUE) { return ( {indicatorValue} + +
{ + setActive(true) + setHighlightedFieldId({ indicatorId: indicatorId }) + }} + onBlur={() => { + setActive(false) + }} + > + {indicatorValue} +
+
) } IndicatorTableCell.propTypes = { denominator: PropTypes.string.isRequired, factor: PropTypes.number.isRequired, + indicatorId: PropTypes.string.isRequired, numerator: PropTypes.string.isRequired, decimals: PropTypes.number, } diff --git a/src/data-workspace/indicators-table-body/indicator-table-cell.test.js b/src/data-workspace/indicators-table-body/indicator-table-cell.test.js index 4f20de8dfd..bdcdd12bc6 100644 --- a/src/data-workspace/indicators-table-body/indicator-table-cell.test.js +++ b/src/data-workspace/indicators-table-body/indicator-table-cell.test.js @@ -1,6 +1,8 @@ import { IconInfo16, IconWarning16 } from '@dhis2/ui' import { fireEvent } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import React from 'react' +import { useHighlightedFieldStore } from '../../shared/index.js' import { render } from '../../test-utils/index.js' import { IndicatorTableCell } from './indicator-table-cell.jsx' import { useIndicatorValue } from './use-indicator-value.js' @@ -20,9 +22,16 @@ const MOCK_INDICATOR_INFO = { factor: 1, numerator: '#{fakeUID1}', denominator: '#{fakeUID2}', + indicatorId: 'indicator1', } describe('IndicatorTableCell', () => { + const initialHighlightedFieldState = useHighlightedFieldStore.getState() + + beforeEach(() => { + useHighlightedFieldStore.setState(initialHighlightedFieldState) + }) + it('shows the value returned by useIndicatorValue if valid', async () => { useIndicatorValue.mockReturnValue({ value: '42' }) const { findByText } = render( @@ -74,4 +83,40 @@ describe('IndicatorTableCell', () => { // check that the mocked value of info icon is present expect(await findByText('WARNING_ICON')).toBeInTheDocument() }) + + it('calls setHighlightedFieldId with the indicatorId of the focused cell', async () => { + useIndicatorValue.mockReturnValue({ value: '42' }) + const { container, findAllByText } = render( + <> + + + + ) + + await findAllByText('42') + const [cellA, cellB] = container.querySelectorAll('[tabindex="0"]') + + expect( + useHighlightedFieldStore.getState().highlightedFieldId + ).not.toEqual({ indicatorId: 'indicator1' }) + + // activate the first cell by clicking it + await userEvent.click(cellA) + expect(useHighlightedFieldStore.getState().highlightedFieldId).toEqual({ + indicatorId: 'indicator1', + }) + + // tab into the second cell + await userEvent.tab() + expect(document.activeElement).toBe(cellB) + expect(useHighlightedFieldStore.getState().highlightedFieldId).toEqual({ + indicatorId: 'indicator2', + }) + }) }) diff --git a/src/data-workspace/indicators-table-body/indicators-table-body.jsx b/src/data-workspace/indicators-table-body/indicators-table-body.jsx index 3f159c73e1..d4615c8fdc 100644 --- a/src/data-workspace/indicators-table-body/indicators-table-body.jsx +++ b/src/data-workspace/indicators-table-body/indicators-table-body.jsx @@ -62,6 +62,7 @@ export const IndicatorsTableBody = ({ numerator={indicator.numerator} factor={indicator.indicatorType.factor} decimals={indicator.decimals} + indicatorId={indicator.id} /> {padColumns.map((_, i) => ( { - if (!item || !dataElement) { + if (!item || !dataElementOrIndicator) { return null } + if (item.indicatorId) { + return { isIndicator: true, ...dataElementOrIndicator } + } + return gatherHighlightedFieldData({ metadata, dataValue, - dataElement, + dataElement: dataElementOrIndicator, categoryOptionComboId: item.categoryOptionComboId, }) - }, [item, dataElement, dataValue, metadata]) + }, [item, dataElementOrIndicator, dataValue, metadata]) } diff --git a/src/shared/metadata/selectors.js b/src/shared/metadata/selectors.js index 1cff44a7d6..b893c2a4e2 100644 --- a/src/shared/metadata/selectors.js +++ b/src/shared/metadata/selectors.js @@ -40,6 +40,7 @@ export const getOptionSetById = (metadata, id) => getOptionSets(metadata)[id] export const getDataSetById = (metadata, id) => getDataSets(metadata)?.[id] export const getDataElementById = (metadata, id) => getDataElements(metadata)?.[id] +export const getIndicatorById = (metadata, id) => getIndicators(metadata)?.[id] export const getSectionsByDataSetId = (metadata, dataSetId) => { const dataSetSections = getDataSetById(metadata, dataSetId)?.sections diff --git a/src/shared/stores/highlighted-field-store.js b/src/shared/stores/highlighted-field-store.js index 47071a9586..3fca34fe4a 100644 --- a/src/shared/stores/highlighted-field-store.js +++ b/src/shared/stores/highlighted-field-store.js @@ -8,17 +8,35 @@ export const useHighlightedFieldStore = create((set, get) => ({ * @param {} field * @param {} field.dataElementId id of the dataElement * @param {} field.categoryOptionComboId id of the categoryOptionCombo + * @param {} field.indicatorId id of the indicator * @returns */ setHighlightedField: (highlightedFieldId) => set({ highlightedFieldId }), getHighlightedField: () => get().highlightedFieldId, - isFieldHighlighted: ({ dataElementId, categoryOptionComboId }) => { + isFieldHighlighted: ({ + dataElementId, + categoryOptionComboId, + indicatorId, + }) => { const highlightedField = get().highlightedFieldId - return ( - !!highlightedField && + if (!highlightedField) { + return false + } + + if ( + dataElementId && highlightedField.dataElementId === dataElementId && + categoryOptionComboId && highlightedField.categoryOptionComboId === categoryOptionComboId - ) + ) { + return true + } + + if (indicatorId && highlightedField?.indicatorId === indicatorId) { + return true + } + + return false }, }))