Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const CustomFormIndicatorCell = ({ indicatorId, metadata }) => {
numerator={numerator}
factor={factor}
decimals={decimals}
indicatorId={indicatorId}
/>
)
}
Expand Down
118 changes: 68 additions & 50 deletions src/data-workspace/data-details-sidebar/basic-information.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,58 +26,76 @@ const BasicInformation = ({ item }) => {
{item.categoryOptionComboName &&
` | ${item.categoryOptionComboName}`}
</h1>

<ul className={styles.elements}>
{item.description && (
<li>
{i18n.t('Description: {{- description}}', {
description: item.description,
nsSeparator: '-:-',
})}
</li>
)}
{item.code && (
<li>
{i18n.t('Code: {{code}}', {
code: item.code,
nsSeparator: '-:-',
})}
</li>
)}
<li>
{i18n.t('Data element ID: {{id}}', {
id: item.dataElement,
nsSeparator: '-:-',
})}
</li>
<li>
{i18n.t('Category option combo ID: {{id}}', {
id: item.categoryOptionCombo,
nsSeparator: '-:-',
})}
</li>
<li>
{item.lastUpdated && (
<Tooltip content={<DateText date={item.lastUpdated} />}>
{i18n.t(
'Last updated {{- timeAgo}} by {{- name}}',
{
timeAgo,
name: item.storedBy,
}
)}
</Tooltip>
{item?.isIndicator ? (
<ul className={styles.elements}>
{item.description && (
<li>
{i18n.t('Description: {{- description}}', {
description: item.description,
nsSeparator: '-:-',
})}
</li>
)}
</li>
</ul>
) : (
<>
<ul className={styles.elements}>
{item.description && (
<li>
{i18n.t('Description: {{- description}}', {
description: item.description,
nsSeparator: '-:-',
})}
</li>
)}
{item.code && (
<li>
{i18n.t('Code: {{code}}', {
code: item.code,
nsSeparator: '-:-',
})}
</li>
)}
<li>
{i18n.t('Data element ID: {{id}}', {
id: item.dataElement,
nsSeparator: '-:-',
})}
</li>
<li>
{i18n.t('Category option combo ID: {{id}}', {
id: item.categoryOptionCombo,
nsSeparator: '-:-',
})}
</li>
<li>
{item.lastUpdated && (
<Tooltip
content={
<DateText date={item.lastUpdated} />
}
>
{i18n.t(
'Last updated {{- timeAgo}} by {{- name}}',
{
timeAgo,
name: item.storedBy,
}
)}
</Tooltip>
)}
</li>

{item.followUp ? (
<li className={styles.markedForFollowup}>
<IconFlag16 color={colors.yellow700} />
{i18n.t('Marked for follow-up')}
</li>
) : null}
</ul>
<FollowUpButton item={item} />
{item.followUp ? (
<li className={styles.markedForFollowup}>
<IconFlag16 color={colors.yellow700} />
{i18n.t('Marked for follow-up')}
</li>
) : null}
</ul>
<FollowUpButton item={item} />
</>
)}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export default function DataDetailsSidebar({ hide }) {
if (!dataValue) {
return null
}
if (dataValue?.isIndicator) {
return (
<Sidebar>
<Title onClose={hide}>{i18n.t('Details')}</Title>
<BasicInformation item={dataValue} />
</Sidebar>
)
}
return (
<Sidebar>
<Title onClose={hide}>{i18n.t('Details')}</Title>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
})
45 changes: 43 additions & 2 deletions src/data-workspace/indicators-table-body/indicator-table-cell.jsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -25,6 +30,7 @@
export const IndicatorTableCell = ({
denominator,
factor,
indicatorId,
numerator,
decimals,
}) => {
Expand All @@ -39,6 +45,23 @@
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 (
<InvalidIndicatorWrapper
Expand All @@ -62,13 +85,31 @@
}

return (
<TableCell className={styles.indicatorCell}>{indicatorValue}</TableCell>
<TableCell className={styles.indicatorCell}>
<div
tabIndex="0"

Check warning on line 90 in src/data-workspace/indicators-table-body/indicator-table-cell.jsx

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`tabIndex` should only be declared on interactive elements.

See more on https://sonarcloud.io/project/issues?id=dhis2_aggregate-data-entry-app&issues=AZ9g4jAhGDa65pI5a1xB&open=AZ9g4jAhGDa65pI5a1xB&pullRequest=576
className={cx(styles.indicatorCellFocusWrapper, {
[styles.activeCell]: active,
[styles.highlightedCell]: highlighted,
})}
onFocus={() => {
setActive(true)
setHighlightedFieldId({ indicatorId: indicatorId })
}}
onBlur={() => {
setActive(false)
}}
>
{indicatorValue}
</div>
</TableCell>
)
}

IndicatorTableCell.propTypes = {
denominator: PropTypes.string.isRequired,
factor: PropTypes.number.isRequired,
indicatorId: PropTypes.string.isRequired,
numerator: PropTypes.string.isRequired,
decimals: PropTypes.number,
}
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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(
Expand Down Expand Up @@ -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(
<>
<IndicatorTableCell
{...MOCK_INDICATOR_INFO}
indicatorId="indicator1"
/>
<IndicatorTableCell
{...MOCK_INDICATOR_INFO}
indicatorId="indicator2"
/>
</>
)

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',
})
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export const IndicatorsTableBody = ({
numerator={indicator.numerator}
factor={indicator.indicatorType.factor}
decimals={indicator.decimals}
indicatorId={indicator.id}
/>
{padColumns.map((_, i) => (
<TableCell
Expand Down
23 changes: 23 additions & 0 deletions src/data-workspace/table-body.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,15 @@
composes: tableCell;
background: var(--colors-grey300);
text-align: end;
position: relative;
}

.indicatorCellFocusWrapper {
position: absolute;
inset: 0;
display: block;
box-sizing: border-box;
padding: 8px;
}

.indicatorCellNoncalculable {
Expand Down Expand Up @@ -113,3 +122,17 @@
.category {
font-weight: 300;
}

.highlightedCell {
outline: 3px solid var(--colors-grey800);
border: none;
/* Fix to prevent bottom outline to be clipped by next cell */
z-index: 1;
}

.activeCell {
outline: 3px solid var(--theme-focus);
border: none;
/* Fix to prevent bottom outline to be clipped by next cell */
z-index: 1;
}
Loading
Loading