', () => {
const wrapper = shallow(Child)
- expect(wrapper.find('NoticeBox')).toHaveLength(1)
+ expect(wrapper.find(ErrorMessage)).toHaveLength(1)
})
it('renders the children for authorised users', () => {
diff --git a/src/auth/use-is-authorized.js b/src/auth/use-is-authorized.js
index f1199396..a8bf4523 100644
--- a/src/auth/use-is-authorized.js
+++ b/src/auth/use-is-authorized.js
@@ -1,7 +1,7 @@
-import { useCurrentUser } from '../current-user/index.js'
+import { useAppData } from '../app-data/index.js'
export const useIsAuthorized = () => {
- const { authorities } = useCurrentUser()
+ const { authorities } = useAppData()
return authorities.some(
authority => authority === 'ALL' || authority === 'M_dhis-web-approval'
)
diff --git a/src/auth/use-is-authorized.test.js b/src/auth/use-is-authorized.test.js
index 29f95a08..f589c6ff 100644
--- a/src/auth/use-is-authorized.test.js
+++ b/src/auth/use-is-authorized.test.js
@@ -1,6 +1,6 @@
import { renderHook } from '@testing-library/react-hooks'
import React from 'react'
-import { CurrentUserContext } from '../current-user/index.js'
+import { AppDataContext } from '../app-data/index.js'
import { useIsAuthorized } from './use-is-authorized.js'
describe('useIsAuthorized', () => {
@@ -10,9 +10,9 @@ describe('useIsAuthorized', () => {
}
const wrapper = ({ children }) => (
-
+
{children}
-
+
)
const { result } = renderHook(() => useIsAuthorized(), { wrapper })
@@ -26,9 +26,9 @@ describe('useIsAuthorized', () => {
}
const wrapper = ({ children }) => (
-
+
{children}
-
+
)
const { result } = renderHook(() => useIsAuthorized(), { wrapper })
@@ -42,9 +42,9 @@ describe('useIsAuthorized', () => {
}
const wrapper = ({ children }) => (
-
+
{children}
-
+
)
const { result } = renderHook(() => useIsAuthorized(), { wrapper })
diff --git a/src/bottom-bar/bottom-bar.js b/src/bottom-bar/bottom-bar.js
index 1dc08a30..20119076 100644
--- a/src/bottom-bar/bottom-bar.js
+++ b/src/bottom-bar/bottom-bar.js
@@ -1,5 +1,15 @@
import React from 'react'
+import { StatusTag } from '../shared/index.js'
+import { useWorkflowContext } from '../workflow-context/index.js'
-const BottomBar = () => BottomBar placeholder
+const BottomBar = () => {
+ const { approvalStatus } = useWorkflowContext()
+
+ return (
+ <>
+
+ >
+ )
+}
export { BottomBar }
diff --git a/src/current-user/current-user-context.js b/src/current-user/current-user-context.js
deleted file mode 100644
index 36518bd5..00000000
--- a/src/current-user/current-user-context.js
+++ /dev/null
@@ -1,5 +0,0 @@
-import { createContext } from 'react'
-
-const CurrentUserContext = createContext({})
-
-export { CurrentUserContext }
diff --git a/src/current-user/index.js b/src/current-user/index.js
deleted file mode 100644
index f95b2bcb..00000000
--- a/src/current-user/index.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export { CurrentUserContext } from './current-user-context.js'
-export { CurrentUserProvider } from './current-user-provider.js'
-export { useCurrentUser } from './use-current-user.js'
diff --git a/src/current-user/use-current-user.js b/src/current-user/use-current-user.js
deleted file mode 100644
index 233124c0..00000000
--- a/src/current-user/use-current-user.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import { useContext } from 'react'
-import { CurrentUserContext } from './current-user-context.js'
-
-export const useCurrentUser = () => useContext(CurrentUserContext)
diff --git a/src/data-workspace/data-workspace.js b/src/data-workspace/data-workspace.js
index 1d5939f2..6a8f4452 100644
--- a/src/data-workspace/data-workspace.js
+++ b/src/data-workspace/data-workspace.js
@@ -1,13 +1,17 @@
import React from 'react'
-import { useQueryParams } from '../navigation/index.js'
+import { useWorkflowContext } from '../workflow-context/index.js'
+import { TitleBar } from './title-bar.js'
const DataWorkspace = () => {
- const query = useQueryParams()
+ const workflow = useWorkflowContext()
return (
<>
- Data workspace placeholder
- {JSON.stringify(query, null, 4)}
+
>
)
}
diff --git a/src/data-workspace/title-bar.js b/src/data-workspace/title-bar.js
new file mode 100644
index 00000000..05970ee7
--- /dev/null
+++ b/src/data-workspace/title-bar.js
@@ -0,0 +1,27 @@
+import i18n from '@dhis2/d2-i18n'
+import { IconDimensionDataSet16 } from '@dhis2/ui'
+import PropTypes from 'prop-types'
+import React from 'react'
+import { StatusTag } from '../shared/status-tag/index.js'
+import styles from './title-bar.module.css'
+
+const TitleBar = ({ name, dataSetsCount, approvalState }) => (
+
+ {name}
+
+
+ {i18n.t('{{dataSetsCount}} data sets', {
+ dataSetsCount,
+ })}
+
+
+
+)
+
+TitleBar.propTypes = {
+ approvalState: PropTypes.string.isRequired,
+ dataSetsCount: PropTypes.number.isRequired,
+ name: PropTypes.string.isRequired,
+}
+
+export { TitleBar }
diff --git a/src/data-workspace/title-bar.module.css b/src/data-workspace/title-bar.module.css
new file mode 100644
index 00000000..efccea81
--- /dev/null
+++ b/src/data-workspace/title-bar.module.css
@@ -0,0 +1,20 @@
+.titleBar {
+ display: grid;
+ grid-template-columns: max-content max-content max-content;
+ align-items: center;
+ grid-gap: var(--spacers-dp12);
+}
+
+.workflowName {
+ font-weight: 500;
+ font-size: 20px;
+ line-height: 24px;
+}
+
+.workflowDataSetsCount {
+ display: grid;
+ grid-template-columns: auto auto;
+ align-items: center;
+ grid-gap: var(--spacers-dp4);
+ color: var(--colors-grey600);
+}
diff --git a/src/navigation/index.js b/src/navigation/index.js
index d4750a47..bc455916 100644
--- a/src/navigation/index.js
+++ b/src/navigation/index.js
@@ -1,4 +1,4 @@
+export { createHref } from './create-href.js'
+export { history } from './history.js'
export { pushStateToHistory } from './push-state-to-history.js'
export { readQueryParams } from './read-query-params.js'
-export { createHref } from './create-href.js'
-export { useQueryParams } from './use-query-params.js'
diff --git a/src/navigation/use-query-params.js b/src/navigation/use-query-params.js
deleted file mode 100644
index a7325893..00000000
--- a/src/navigation/use-query-params.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import { useState, useEffect } from 'react'
-import { history } from './history.js'
-import { readQueryParams } from './read-query-params.js'
-
-export const useQueryParams = () => {
- const [params, setParams] = useState(readQueryParams)
-
- useEffect(
- () =>
- // The call to listen returns an `unlisten` function to clean up the effect
- history.listen(() => {
- setParams(readQueryParams())
- }),
- []
- )
-
- return params
-}
diff --git a/src/shared/error-message.js b/src/shared/error-message.js
new file mode 100644
index 00000000..64fc279a
--- /dev/null
+++ b/src/shared/error-message.js
@@ -0,0 +1,19 @@
+import { NoticeBox } from '@dhis2/ui'
+import PropTypes from 'prop-types'
+import React from 'react'
+import classes from './error-message.module.css'
+
+const ErrorMessage = ({ children, title }) => (
+
+
+ {children}
+
+
+)
+
+ErrorMessage.propTypes = {
+ children: PropTypes.node.isRequired,
+ title: PropTypes.string.isRequired,
+}
+
+export { ErrorMessage }
diff --git a/src/auth/auth-wall.module.css b/src/shared/error-message.module.css
similarity index 77%
rename from src/auth/auth-wall.module.css
rename to src/shared/error-message.module.css
index 5ed0a48d..4a92cc0e 100644
--- a/src/auth/auth-wall.module.css
+++ b/src/shared/error-message.module.css
@@ -1,4 +1,5 @@
.wrapper {
+ min-width: 400px;
max-width: 800px;
margin: var(--spacers-dp24) auto 0;
}
diff --git a/src/shared/index.js b/src/shared/index.js
new file mode 100644
index 00000000..bb6abf71
--- /dev/null
+++ b/src/shared/index.js
@@ -0,0 +1,3 @@
+export { ErrorMessage } from './error-message.js'
+export { Loader } from './loader.js'
+export { StatusTag } from './status-tag/index.js'
diff --git a/src/shared/loader.js b/src/shared/loader.js
new file mode 100644
index 00000000..b4603927
--- /dev/null
+++ b/src/shared/loader.js
@@ -0,0 +1,10 @@
+import { CenteredContent, CircularLoader } from '@dhis2/ui'
+import React from 'react'
+
+const Loader = () => (
+
+
+
+)
+
+export { Loader }
diff --git a/src/shared/status-tag/icons.js b/src/shared/status-tag/icons.js
new file mode 100644
index 00000000..d02c945e
--- /dev/null
+++ b/src/shared/status-tag/icons.js
@@ -0,0 +1,42 @@
+import React from 'react'
+
+const Approved = () => (
+
+)
+
+const Ready = () => (
+
+)
+
+const Waiting = () => (
+
+)
+
+export { Approved, Ready, Waiting }
diff --git a/src/shared/status-tag/index.js b/src/shared/status-tag/index.js
new file mode 100644
index 00000000..eac87ebf
--- /dev/null
+++ b/src/shared/status-tag/index.js
@@ -0,0 +1 @@
+export { StatusTag } from './status-tag.js'
diff --git a/src/shared/status-tag/status-tag.js b/src/shared/status-tag/status-tag.js
new file mode 100644
index 00000000..63d63f7b
--- /dev/null
+++ b/src/shared/status-tag/status-tag.js
@@ -0,0 +1,29 @@
+import { Tag } from '@dhis2/ui'
+import PropTypes from 'prop-types'
+import React from 'react'
+import { useApprovalState } from './use-approval-state.js'
+
+const StatusTag = ({ approvalState }) => {
+ const { icon: Icon, displayName, type } = useApprovalState(approvalState)
+ const props = {
+ [type]: true,
+ icon: ,
+ }
+
+ return {displayName}
+}
+
+StatusTag.propTypes = {
+ approvalState: PropTypes.oneOf([
+ 'APPROVED_HERE',
+ 'APPROVED_ELSEWHERE',
+ 'ACCEPTED_HERE',
+ 'ACCEPTED_ELSEWHERE',
+ 'UNAPPROVED_READY',
+ 'UNAPPROVED_WAITING',
+ 'UNAPPROVED_ELSEWHERE',
+ 'UNAPPROVABLE',
+ ]),
+}
+
+export { StatusTag }
diff --git a/src/shared/status-tag/use-approval-state.js b/src/shared/status-tag/use-approval-state.js
new file mode 100644
index 00000000..a13ea816
--- /dev/null
+++ b/src/shared/status-tag/use-approval-state.js
@@ -0,0 +1,55 @@
+import i18n from '@dhis2/d2-i18n'
+import { Approved, Ready, Waiting } from './icons.js'
+
+/*
+ * TODO: The current classification logic was discussed with
+ * Joe Cooper, but needs to be confirmed by either Lars or Jim.
+ * Specifically these cases are not clear:
+ * A. Do ACCEPTED_HERE and ACCEPTED_ELSEWHERE fall into "Ready for
+ * approval and accepted"? This doesn't seem to match the webapi docs.
+ * B. Should we show a red tag for UNAPPROVABLE and show a negative tag?
+ * This was not included in the design specs.
+ */
+const useApprovalState = approvalState => {
+ switch (approvalState) {
+ case 'APPROVED_HERE':
+ case 'APPROVED_ELSEWHERE':
+ return {
+ icon: Approved,
+ displayName: i18n.t('Approved'),
+ type: 'positive',
+ }
+
+ case 'ACCEPTED_HERE':
+ case 'ACCEPTED_ELSEWHERE':
+ return {
+ icon: Ready,
+ displayName: i18n.t('Ready for approval and accepted'),
+ type: 'neutral',
+ }
+
+ case 'UNAPPROVED_READY':
+ return {
+ icon: Ready,
+ displayName: i18n.t('Ready for approval'),
+ type: 'neutral',
+ }
+
+ case 'UNAPPROVED_WAITING':
+ case 'UNAPPROVED_ELSEWHERE':
+ return {
+ icon: Waiting,
+ displayName: i18n.t('Waiting'),
+ type: 'default',
+ }
+
+ case 'UNAPPROVABLE':
+ return {
+ icon: Waiting,
+ displayName: i18n.t('Cannot approve'),
+ type: 'negative',
+ }
+ }
+}
+
+export { useApprovalState }
diff --git a/src/top-bar/clear-all-button/clear-all-button.js b/src/top-bar/clear-all-button/clear-all-button.js
index 3bc7b089..177f1741 100644
--- a/src/top-bar/clear-all-button/clear-all-button.js
+++ b/src/top-bar/clear-all-button/clear-all-button.js
@@ -1,7 +1,7 @@
import i18n from '@dhis2/d2-i18n'
import { Button } from '@dhis2/ui'
import React from 'react'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import classes from './clear-all-button.module.css'
const ClearAllButton = () => {
diff --git a/src/top-bar/clear-all-button/clear-all-button.test.js b/src/top-bar/clear-all-button/clear-all-button.test.js
index df55c72e..6039162a 100644
--- a/src/top-bar/clear-all-button/clear-all-button.test.js
+++ b/src/top-bar/clear-all-button/clear-all-button.test.js
@@ -1,9 +1,9 @@
import { shallow } from 'enzyme'
import React from 'react'
-import { useSelectionContext } from '../selection/use-selection-context.js'
+import { useSelectionContext } from '../selection-context/use-selection-context.js'
import { ClearAllButton } from './clear-all-button.js'
-jest.mock('../selection/use-selection-context.js', () => ({
+jest.mock('../selection-context/use-selection-context.js', () => ({
useSelectionContext: jest.fn(),
}))
diff --git a/src/top-bar/org-unit-select/org-unit-select.js b/src/top-bar/org-unit-select/org-unit-select.js
index 6e1f3fc0..ad814de5 100644
--- a/src/top-bar/org-unit-select/org-unit-select.js
+++ b/src/top-bar/org-unit-select/org-unit-select.js
@@ -1,15 +1,15 @@
import i18n from '@dhis2/d2-i18n'
import { OrganisationUnitTree } from '@dhis2/ui'
import React from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { ContextSelect } from '../context-select/index.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import classes from './org-unit-select.module.css'
export const ORG_UNIT = 'ORG_UNIT'
const OrgUnitSelect = () => {
- const { organisationUnits } = useCurrentUser()
+ const { organisationUnits } = useAppData()
const {
orgUnit,
selectOrgUnit,
diff --git a/src/top-bar/org-unit-select/org-unit-select.test.js b/src/top-bar/org-unit-select/org-unit-select.test.js
index 038b97b8..60351a0c 100644
--- a/src/top-bar/org-unit-select/org-unit-select.test.js
+++ b/src/top-bar/org-unit-select/org-unit-select.test.js
@@ -2,10 +2,10 @@ import { useDataQuery } from '@dhis2/app-runtime'
import { Popover, Layer, OrganisationUnitTree, Tooltip } from '@dhis2/ui'
import { shallow } from 'enzyme'
import React from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { readQueryParams } from '../../navigation/read-query-params.js'
import { ContextSelect } from '../context-select/context-select.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { ORG_UNIT, OrgUnitSelect } from './org-unit-select.js'
jest.mock('@dhis2/app-runtime', () => ({
@@ -15,11 +15,11 @@ jest.mock('../../navigation/read-query-params.js', () => ({
readQueryParams: jest.fn(),
}))
-jest.mock('../../current-user/index.js', () => ({
- useCurrentUser: jest.fn(),
+jest.mock('../../app-data/index.js', () => ({
+ useAppData: jest.fn(),
}))
-jest.mock('../selection/index.js', () => ({
+jest.mock('../selection-context/index.js', () => ({
useSelectionContext: jest.fn(),
}))
@@ -47,7 +47,7 @@ afterEach(() => {
})
beforeEach(() => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
organisationUnits: mockOrgUnitRoots,
}))
diff --git a/src/top-bar/period-select/period-menu.js b/src/top-bar/period-select/period-menu.js
index 8f8a82d6..e27789e7 100644
--- a/src/top-bar/period-select/period-menu.js
+++ b/src/top-bar/period-select/period-menu.js
@@ -2,7 +2,7 @@ import { Menu, MenuItem } from '@dhis2/ui'
import PropTypes from 'prop-types'
import React from 'react'
import { createHref } from '../../navigation/index.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { getFixedPeriodsByTypeAndYear } from './fixed-periods.js'
import classes from './period-menu.module.css'
diff --git a/src/top-bar/period-select/period-menu.test.js b/src/top-bar/period-select/period-menu.test.js
index 2d406aa4..24ef3389 100644
--- a/src/top-bar/period-select/period-menu.test.js
+++ b/src/top-bar/period-select/period-menu.test.js
@@ -1,10 +1,10 @@
import { MenuItem, Menu } from '@dhis2/ui'
import { shallow } from 'enzyme'
import React from 'react'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { PeriodMenu } from './period-menu.js'
-jest.mock('../selection/index.js', () => ({
+jest.mock('../selection-context/index.js', () => ({
useSelectionContext: jest.fn(),
}))
diff --git a/src/top-bar/period-select/period-select.js b/src/top-bar/period-select/period-select.js
index 0750d840..9321c03e 100644
--- a/src/top-bar/period-select/period-select.js
+++ b/src/top-bar/period-select/period-select.js
@@ -1,7 +1,7 @@
import i18n from '@dhis2/d2-i18n'
import React, { useEffect, useState } from 'react'
import { ContextSelect } from '../context-select/index.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { PeriodMenu } from './period-menu.js'
import { YearNavigator, currentYear } from './year-navigator.js'
diff --git a/src/top-bar/period-select/period-select.test.js b/src/top-bar/period-select/period-select.test.js
index 8cfb7c34..b7e0b69e 100644
--- a/src/top-bar/period-select/period-select.test.js
+++ b/src/top-bar/period-select/period-select.test.js
@@ -1,10 +1,10 @@
import { Popover, Layer, MenuItem, Tooltip } from '@dhis2/ui'
import { shallow } from 'enzyme'
import React from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { readQueryParams } from '../../navigation/read-query-params.js'
import { ContextSelect } from '../context-select/context-select.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { PeriodMenu } from './period-menu.js'
import { PERIOD, PeriodSelect } from './period-select.js'
import { YearNavigator } from './year-navigator.js'
@@ -13,11 +13,11 @@ jest.mock('../../navigation/read-query-params.js', () => ({
readQueryParams: jest.fn(),
}))
-jest.mock('../../current-user/index.js', () => ({
- useCurrentUser: jest.fn(),
+jest.mock('../../app-data/index.js', () => ({
+ useAppData: jest.fn(),
}))
-jest.mock('../selection/index.js', () => ({
+jest.mock('../selection-context/index.js', () => ({
useSelectionContext: jest.fn(),
}))
@@ -35,7 +35,7 @@ const mockWorkflows = [
]
beforeEach(() => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
diff --git a/src/top-bar/selection/index.js b/src/top-bar/selection-context/index.js
similarity index 100%
rename from src/top-bar/selection/index.js
rename to src/top-bar/selection-context/index.js
diff --git a/src/top-bar/selection/initial-values.js b/src/top-bar/selection-context/initial-values.js
similarity index 100%
rename from src/top-bar/selection/initial-values.js
rename to src/top-bar/selection-context/initial-values.js
diff --git a/src/top-bar/selection/initial-values.test.js b/src/top-bar/selection-context/initial-values.test.js
similarity index 100%
rename from src/top-bar/selection/initial-values.test.js
rename to src/top-bar/selection-context/initial-values.test.js
diff --git a/src/top-bar/selection/selection-context.js b/src/top-bar/selection-context/selection-context.js
similarity index 100%
rename from src/top-bar/selection/selection-context.js
rename to src/top-bar/selection-context/selection-context.js
diff --git a/src/top-bar/selection/selection-provider.js b/src/top-bar/selection-context/selection-provider.js
similarity index 97%
rename from src/top-bar/selection/selection-provider.js
rename to src/top-bar/selection-context/selection-provider.js
index 87a91686..12981c0b 100644
--- a/src/top-bar/selection/selection-provider.js
+++ b/src/top-bar/selection-context/selection-provider.js
@@ -1,6 +1,6 @@
import { PropTypes } from '@dhis2/prop-types'
import React, { useEffect, useReducer } from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { pushStateToHistory } from '../../navigation/index.js'
import { initialValues, initialWorkflowValue } from './initial-values.js'
import { SelectionContext } from './selection-context.js'
@@ -63,7 +63,7 @@ const reducer = (state, { type, payload }) => {
}
const SelectionProvider = ({ children }) => {
- const { dataApprovalWorkflows } = useCurrentUser()
+ const { dataApprovalWorkflows } = useAppData()
const [{ openedSelect, workflow, period, orgUnit }, dispatch] = useReducer(
reducer,
{
diff --git a/src/top-bar/selection/use-selection-context.js b/src/top-bar/selection-context/use-selection-context.js
similarity index 100%
rename from src/top-bar/selection/use-selection-context.js
rename to src/top-bar/selection-context/use-selection-context.js
diff --git a/src/top-bar/selection/use-selection-context.test.js b/src/top-bar/selection-context/use-selection-context.test.js
similarity index 97%
rename from src/top-bar/selection/use-selection-context.test.js
rename to src/top-bar/selection-context/use-selection-context.test.js
index 11f0b4dc..ec7897f6 100644
--- a/src/top-bar/selection/use-selection-context.test.js
+++ b/src/top-bar/selection-context/use-selection-context.test.js
@@ -1,6 +1,6 @@
import { act, renderHook } from '@testing-library/react-hooks'
import React from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { pushStateToHistory } from '../../navigation/push-state-to-history.js'
import { readQueryParams } from '../../navigation/read-query-params.js'
import { SelectionProvider } from './selection-provider.js'
@@ -14,8 +14,8 @@ jest.mock('../../navigation/read-query-params.js', () => ({
readQueryParams: jest.fn(),
}))
-jest.mock('../../current-user/index.js', () => ({
- useCurrentUser: jest.fn(),
+jest.mock('../../app-data/index.js', () => ({
+ useAppData: jest.fn(),
}))
afterEach(() => {
@@ -36,7 +36,7 @@ const mockWorkflows = [
]
beforeEach(() => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
})
diff --git a/src/top-bar/top-bar.js b/src/top-bar/top-bar.js
index 7c16f784..9bf4d843 100644
--- a/src/top-bar/top-bar.js
+++ b/src/top-bar/top-bar.js
@@ -2,7 +2,7 @@ import React from 'react'
import { ClearAllButton } from './clear-all-button/index.js'
import { OrgUnitSelect } from './org-unit-select/index.js'
import { PeriodSelect } from './period-select/index.js'
-import { SelectionProvider } from './selection/index.js'
+import { SelectionProvider } from './selection-context/index.js'
import { WorkflowSelect } from './workflow-select/index.js'
const TopBar = () => (
diff --git a/src/top-bar/workflow-select/workflow-select.js b/src/top-bar/workflow-select/workflow-select.js
index 3aaa4a34..c7beaba7 100644
--- a/src/top-bar/workflow-select/workflow-select.js
+++ b/src/top-bar/workflow-select/workflow-select.js
@@ -1,16 +1,16 @@
import i18n from '@dhis2/d2-i18n'
import { Menu } from '@dhis2/ui'
import React from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { ContextSelect } from '../context-select/index.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { WorkflowSelectOption } from './workflow-select-option.js'
import classes from './workflow-select.module.css'
const WORKFLOW = 'WORKFLOW'
const WorkflowSelect = () => {
- const { dataApprovalWorkflows } = useCurrentUser()
+ const { dataApprovalWorkflows } = useAppData()
const {
workflow: selectedWorkflow,
selectWorkflow,
diff --git a/src/top-bar/workflow-select/workflow-select.test.js b/src/top-bar/workflow-select/workflow-select.test.js
index 026b38bb..c53ce370 100644
--- a/src/top-bar/workflow-select/workflow-select.test.js
+++ b/src/top-bar/workflow-select/workflow-select.test.js
@@ -1,10 +1,10 @@
import { Popover, Layer } from '@dhis2/ui'
import { shallow } from 'enzyme'
import React from 'react'
-import { useCurrentUser } from '../../current-user/index.js'
+import { useAppData } from '../../app-data/index.js'
import { readQueryParams } from '../../navigation/read-query-params.js'
import { ContextSelect } from '../context-select/context-select.js'
-import { useSelectionContext } from '../selection/index.js'
+import { useSelectionContext } from '../selection-context/index.js'
import { WorkflowSelectOption } from './workflow-select-option.js'
import { WORKFLOW, WorkflowSelect } from './workflow-select.js'
@@ -12,11 +12,11 @@ jest.mock('../../navigation/read-query-params.js', () => ({
readQueryParams: jest.fn(),
}))
-jest.mock('../../current-user/index.js', () => ({
- useCurrentUser: jest.fn(),
+jest.mock('../../app-data/index.js', () => ({
+ useAppData: jest.fn(),
}))
-jest.mock('../selection/index.js', () => ({
+jest.mock('../selection-context/index.js', () => ({
useSelectionContext: jest.fn(),
}))
@@ -37,7 +37,7 @@ describe('', () => {
]
it('renders a ContextSelect with WorkflowSelectOptions', () => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
@@ -54,7 +54,7 @@ describe('', () => {
})
it('renders a placeholder text when no workflow is selected', () => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
@@ -72,7 +72,7 @@ describe('', () => {
})
it('renders a the value when a workflow is selected', () => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
@@ -91,7 +91,7 @@ describe('', () => {
})
it('opens the ContextSelect when the opened select matches "WORKFLOW"', () => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
@@ -107,7 +107,7 @@ describe('', () => {
})
it('shows an info message when no workflows have been found', () => {
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: [],
}))
readQueryParams.mockImplementation(() => ({}))
@@ -130,7 +130,7 @@ describe('', () => {
it('calls the setOpenedSelect to open when clicking the ContextSelect button', () => {
const setOpenedSelect = jest.fn()
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
@@ -152,7 +152,7 @@ describe('', () => {
it('calls the selectWorkflow when clicking a WorkflowSelectOptions', () => {
const selectWorkflow = jest.fn()
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
@@ -174,7 +174,7 @@ describe('', () => {
it('calls the setOpenedSelect to close when clicking the backdrop', () => {
const setOpenedSelect = jest.fn()
- useCurrentUser.mockImplementation(() => ({
+ useAppData.mockImplementation(() => ({
dataApprovalWorkflows: mockWorkflows,
}))
readQueryParams.mockImplementation(() => ({}))
diff --git a/src/workflow-context/index.js b/src/workflow-context/index.js
new file mode 100644
index 00000000..7c41c62a
--- /dev/null
+++ b/src/workflow-context/index.js
@@ -0,0 +1,3 @@
+export { WorkflowContext } from './workflow-context.js'
+export { WorkflowProvider } from './workflow-provider.js'
+export { useWorkflowContext } from './use-workflow-context.js'
diff --git a/src/workflow-context/use-selected-workflow.js b/src/workflow-context/use-selected-workflow.js
new file mode 100644
index 00000000..8e1f6f29
--- /dev/null
+++ b/src/workflow-context/use-selected-workflow.js
@@ -0,0 +1,13 @@
+import { useAppData } from '../app-data/index.js'
+
+const useSelectedWorkflow = params => {
+ const { dataApprovalWorkflows } = useAppData()
+
+ if (!(params && params.wf && dataApprovalWorkflows)) {
+ return {}
+ }
+
+ return dataApprovalWorkflows.find(({ id }) => id === params.wf) || {}
+}
+
+export { useSelectedWorkflow }
diff --git a/src/workflow-context/use-selection-params.js b/src/workflow-context/use-selection-params.js
new file mode 100644
index 00000000..fae432e2
--- /dev/null
+++ b/src/workflow-context/use-selection-params.js
@@ -0,0 +1,33 @@
+import { useState, useEffect } from 'react'
+import { history, readQueryParams } from '../navigation/index.js'
+
+const getParamsIfAllAvailable = () => {
+ const { wf, pe, ou: orgUnitPath } = readQueryParams()
+ if (wf && pe && orgUnitPath) {
+ const orgUnitPathSegments = orgUnitPath.split('/')
+ const orgUnitId = orgUnitPathSegments[orgUnitPathSegments.length - 1]
+
+ return {
+ wf,
+ pe,
+ ou: orgUnitId,
+ }
+ }
+
+ return null
+}
+
+export const useSelectionParams = () => {
+ const [params, setParams] = useState(getParamsIfAllAvailable)
+
+ useEffect(
+ () =>
+ // The call to listen returns an `unlisten` function to clean up the effect
+ history.listen(() => {
+ setParams(getParamsIfAllAvailable())
+ }),
+ []
+ )
+
+ return params
+}
diff --git a/src/workflow-context/use-workflow-context.js b/src/workflow-context/use-workflow-context.js
new file mode 100644
index 00000000..bc9537aa
--- /dev/null
+++ b/src/workflow-context/use-workflow-context.js
@@ -0,0 +1,4 @@
+import { useContext } from 'react'
+import { WorkflowContext } from './workflow-context.js'
+
+export const useWorkflowContext = () => useContext(WorkflowContext)
diff --git a/src/workflow-context/workflow-context.js b/src/workflow-context/workflow-context.js
new file mode 100644
index 00000000..9b4d09e4
--- /dev/null
+++ b/src/workflow-context/workflow-context.js
@@ -0,0 +1,9 @@
+import { createContext } from 'react'
+
+const WorkflowContext = createContext({
+ displayName: '',
+ dataSets: [],
+ status: {},
+})
+
+export { WorkflowContext }
diff --git a/src/workflow-context/workflow-provider.js b/src/workflow-context/workflow-provider.js
new file mode 100644
index 00000000..e0334d08
--- /dev/null
+++ b/src/workflow-context/workflow-provider.js
@@ -0,0 +1,63 @@
+import { useDataQuery } from '@dhis2/app-runtime'
+import i18n from '@dhis2/d2-i18n'
+import { PropTypes } from '@dhis2/prop-types'
+import React, { useEffect } from 'react'
+import { ErrorMessage, Loader } from '../shared/index.js'
+import { useSelectedWorkflow } from './use-selected-workflow.js'
+import { useSelectionParams } from './use-selection-params.js'
+import { WorkflowContext } from './workflow-context.js'
+
+const query = {
+ approvalStatus: {
+ resource: 'dataApprovals',
+ params: ({ wf, pe, ou }) => ({ wf, pe, ou }),
+ },
+}
+
+const WorkflowProvider = ({ children }) => {
+ const params = useSelectionParams()
+ const { displayName, dataSets } = useSelectedWorkflow(params)
+ const { loading, error, data, called, refetch } = useDataQuery(query, {
+ lazy: true,
+ })
+
+ useEffect(() => {
+ if (params) {
+ refetch(params)
+ }
+ }, [params])
+
+ if (!params || !called) {
+ return null
+ }
+
+ if (loading) {
+ return
+ }
+
+ if (error) {
+ return (
+
+ {error.message}
+
+ )
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+WorkflowProvider.propTypes = {
+ children: PropTypes.node.isRequired,
+}
+
+export { WorkflowProvider }