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
11 changes: 9 additions & 2 deletions i18n/en.pot
Original file line number Diff line number Diff line change
Expand Up @@ -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: 2026-05-20T02:12:18.107Z\n"
"PO-Revision-Date: 2026-05-20T02:12:18.108Z\n"
"POT-Creation-Date: 2026-07-21T00:39:16.884Z\n"
"PO-Revision-Date: 2026-07-21T00:39:16.884Z\n"

msgid "Not authorized"
msgstr "Not authorized"
Expand Down Expand Up @@ -343,5 +343,12 @@ msgstr ""
"Please verify the workflow configuration or select a different workflow "
"that includes data sets."

msgid ""
"Workflow \"{{ workflowName }}\" and period \"{{periodName}}\" does not "
"contain any category combos."
msgstr ""
"Workflow \"{{ workflowName }}\" and period \"{{periodName}}\" does not "
"contain any category combos."

msgid "Retry loading approval data"
msgstr "Retry loading approval data"
11 changes: 6 additions & 5 deletions src/app-context/app-provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,11 +139,12 @@ const AppProvider = ({ children }) => {
authorities,
organisationUnits,
dataApprovalWorkflows,
metadata: normalizeMetadata(
categoryCombos,
categories,
categoryOptionCombos
),
metadata: normalizeMetadata({
originalCatCombos: categoryCombos,
originalCategories: categories,
originalCategoryOptionCombos: categoryOptionCombos,
organisationUnits,
}),
}

return (
Expand Down
5 changes: 3 additions & 2 deletions src/top-bar/attribute-combo-select/attribute-combo-select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,9 @@ const AttributeComboSelect = () => {
style={{
height:
attributeCombos?.length == 1
? '270px'
: '350px',
? 270
: 350,
overflowY: 'auto',
}}
>
{/* Only show Category Combo dropdown when there are more than one categoryCombo in the list */}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.menu {
height: 330px;
width: 400px;
overflow-y: auto;
display: flex;
flex-direction: column;
}

.bordered {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
}

.menu {
max-height: 70vh;
overflow-y: auto;
}

Expand Down
10 changes: 9 additions & 1 deletion src/top-bar/org-unit-select/org-unit-select.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const OrgUnitSelect = () => {
openedSelect,
setOpenedSelect,
attributeOptionCombo,
attributeCombos,
} = useSelectionContext()
const open = openedSelect === ORG_UNIT
const value = orgUnit?.displayName
Expand Down Expand Up @@ -50,7 +51,14 @@ const OrgUnitSelect = () => {
placeholder={i18n.t('Choose an organisation unit')}
value={requiredValuesMessage === null ? value : ''}
open={open}
disabled={!(workflow?.id && period?.id && attributeOptionCombo?.id)}
disabled={
!(
workflow?.id &&
period?.id &&
attributeOptionCombo?.id &&
attributeCombos?.length > 0
)
}
onOpen={() => setOpenedSelect(ORG_UNIT)}
onClose={() => setOpenedSelect('')}
requiredValuesMessage={getRequiredValuesMessage()}
Expand Down
13 changes: 7 additions & 6 deletions src/top-bar/org-unit-select/org-unit-select.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ describe('<OrgUnitSelect>', () => {
period: { id: '20120402' },
orgUnit: {},
attributeOptionCombo: { id: '1234' },
attributeCombos: [{ id: '1' }],
openedSelect: '',
selectWorkflow: () => {},
setOpenedSelect: () => {},
Expand Down Expand Up @@ -124,6 +125,7 @@ describe('<OrgUnitSelect>', () => {
},
orgUnit: {},
attributeOptionCombo: { id: '1234' },
attributeCombos: [{ id: '1' }],
openedSelect: '',
selectWorkflow: () => {},
setOpenedSelect: () => {},
Expand Down Expand Up @@ -179,6 +181,7 @@ describe('<OrgUnitSelect>', () => {
attributeOptionCombo: {
id: 'wertyuiopas',
},
attributeCombos: [{ id: '1' }],
openedSelect: '',
selectWorkflow: () => {},
setOpenedSelect: () => {},
Expand Down Expand Up @@ -218,16 +221,14 @@ describe('<OrgUnitSelect>', () => {
},
orgUnit: {},
attributeOptionCombo: { id: '1234' },
attributeCombos: [{ id: '1' }],
openedSelect: '',
selectWorkflow: () => {},
setOpenedSelect,
}))

shallow(<OrgUnitSelect />)
.find(ContextSelect)
.dive()
.find('button')
.simulate('click')
const wrapper = shallow(<OrgUnitSelect />)
wrapper.find(ContextSelect).prop('onOpen')()

expect(setOpenedSelect).toHaveBeenCalledTimes(1)
expect(setOpenedSelect).toHaveBeenCalledWith(ORG_UNIT)
Expand Down Expand Up @@ -265,7 +266,7 @@ describe('<OrgUnitSelect>', () => {
</CustomDataProvider>
)

await waitFor(() => screen.getByText('Sierra Leone'))
await screen.findByText('Sierra Leone')
await userEvent.click(screen.getByText('Sierra Leone'))

expect(selectOrgUnit).toHaveBeenCalledTimes(1)
Expand Down
51 changes: 44 additions & 7 deletions src/utils/app-provider-util.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { cloneJSON, sortList } from './array-utils.js'

export const normalizeMetadata = (
export const normalizeMetadata = ({
originalCatCombos,
originalCategories,
originalCategoryOptionCombos
) => {
originalCategoryOptionCombos,
organisationUnits,
}) => {
const categoryCombos = normalizeCatCombos(originalCatCombos)
const categoryMap = normalizeCategoriesAndOptions(originalCategories)
const categoryMap = normalizeCategoriesAndOptions(
originalCategories,
organisationUnits
)

const metadata = {
categoryCombos,
Expand Down Expand Up @@ -73,20 +77,31 @@ const normalizeCatCombos = (originalCatCombos) => {
},...
},
*/
const normalizeCategoriesAndOptions = (originalCategories) => {
const normalizeCategoriesAndOptions = (
originalCategories,
organisationUnits
) => {
const normalized = { categories: {}, categoryOptions: {} }
for (const category of originalCategories) {
// Map each unique categoryOption by ID
const options = cloneJSON(category.categoryOptions || [])
const validOptions = []
for (const option of options) {
const found = normalized.categoryOptions[option.id]
if (!found) {
normalized.categoryOptions[option.id] = option
const isAssigned = isOptionAssignedToUserOrgUnits(
option,
organisationUnits
)
if (isAssigned) {
normalized.categoryOptions[option.id] = option
validOptions.push(option)
}
}
}

// Map category by ID
const categoryOptionIds = options.map((item) => item.id)
const categoryOptionIds = validOptions.map((item) => item.id)
delete category.categoryOptions
normalized.categories[category.id] = {
...category,
Expand Down Expand Up @@ -156,3 +171,25 @@ const omitField = (obj, keyToRemove) => {
const { [keyToRemove]: _, ...rest } = obj
return rest
}

const isChildOfAnyParent = (userOrgUnitPaths, catOptionOrgUnitPath) =>
userOrgUnitPaths.some(
(userOrgUnitPath) =>
catOptionOrgUnitPath === userOrgUnitPath ||
catOptionOrgUnitPath.startsWith(userOrgUnitPath + '/')
)

const isOptionAssignedToUserOrgUnits = (
categoryOption,
userOrganisationUnits
) => {
// by default,
if (!categoryOption?.organisationUnits?.length) {
return true
}

const userOrgUnitPaths = userOrganisationUnits.map((ou) => ou.path)
return categoryOption.organisationUnits.some((catOptionOrgUnit) =>
isChildOfAnyParent(userOrgUnitPaths, catOptionOrgUnit.path)
)
}
20 changes: 11 additions & 9 deletions src/utils/selection-provider-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ const shouldShowAttributeCombo = ({
if (
attributeCombos.length == 1 &&
singleCategoryCombo.categoryIds?.length === 1 &&
firstCategory.categoryOptionIds?.length <= 1
firstCategory.categoryOptionIds?.length === 0
) {
return false
}
Expand All @@ -144,14 +144,7 @@ export const getAttributeComboState = ({

let _attributeCombo = attributeCombo
let _attributeOptionCombo = null
const isVisible = shouldShowAttributeCombo({
workflow,
period,
attributeCombos: _attributeCombos,
attributeCombo: _attributeCombo,
metadata,
calendar,
})

let attributeComboValue = i18n.t('0 selections')

const processCategoryOptions = (metadata, attributeOptionCombo) => {
Expand Down Expand Up @@ -232,6 +225,15 @@ export const getAttributeComboState = ({
}
}

const isVisible = shouldShowAttributeCombo({
workflow,
period,
attributeCombos: _attributeCombos,
selectedAttrCombo: _attributeCombo,
metadata,
calendar,
})

return {
attributeCombos: _attributeCombos,
attributeCombo: _attributeCombo,
Expand Down
16 changes: 15 additions & 1 deletion src/workflow-context/workflow-provider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const query = {
}

const WorkflowProvider = ({ children }) => {
const { workflow, period, orgUnit, attributeOptionCombo } =
const { workflow, period, orgUnit, attributeOptionCombo, attributeCombos } =
useSelectionContext()
const { fetching, error, data, called, refetch } = useDataQuery(query, {
lazy: true,
Expand Down Expand Up @@ -55,6 +55,20 @@ const WorkflowProvider = ({ children }) => {
)
}

if (workflow && period && attributeCombos?.length === 0) {
return (
<ErrorMessage title={i18n.t('Could not load approval data')}>
{i18n.t(
'Workflow "{{ workflowName }}" and period "{{periodName}}" does not contain any category combos.',
{
workflowName: workflow?.displayName,
periodName: period?.displayName,
}
)}
</ErrorMessage>
)
}

// Handle missing required selections
if (!workflow || !period || !orgUnit || !attributeOptionCombo) {
return null
Expand Down
Loading