Skip to content

Comments

SNT-225 Users can work on a scenario by filtering on a specific "parent OU"#180

Open
bramj wants to merge 26 commits intomainfrom
SNT-225-filter-by-province
Open

SNT-225 Users can work on a scenario by filtering on a specific "parent OU"#180
bramj wants to merge 26 commits intomainfrom
SNT-225-filter-by-province

Conversation

@bramj
Copy link
Collaborator

@bramj bramj commented Feb 2, 2026

Changes

  • Add a sidebar to the planning page that allows filtering by province (org unit), with filter state persisted in URL query params
  • Apply the org unit filter across budget, intervention plan, and map views
  • Handle map resizing when filters change or the sidebar is toggled

Related JIRA tickets

https://bluesquare.atlassian.net/browse/SNT-225

Print screen / video

filter-by-ou-parent-2026-02-02_11.08.17.webm

How to test

  • Verify sidebar opens/closes and map tiles re-render correctly
  • Select a province filter and confirm budget, intervention plan, and maps all update
  • Verify filter state persists in URL and survives page reload
  • Check that clearing the filter returns to national-level view

Copy link
Collaborator Author

@bramj bramj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: I think this PR stands up fairly well on its own, but will come together better with https://bluesquare.atlassian.net/browse/SNT-254 (showing the province boundaries)

I propose to better handle the visualization of this in a PR for that ticket.

}, [bounds, boundsOptions, map]);

return null;
};
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bewi I'll be honest: My frontend skills are not sufficient to know if this is the correct approach. It works well, and it feels like a clean pattern, but would be great if you can verify.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, I would only move FitBounds on its own file and type the InvalidateOnResize to be a FC

// ancestors of the districts/zones de sante, so we can show these borders on the
// map.
// I think this will require us to configure somewhere the OU type that we're
// assigning interventions on.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's of complexity here can be simplified when we have a config for the country that states which OU type we actually want to show, instead of relying on the "valid OUs with geoms".

const currDepth =
(curr.original as { depth?: number | null }).depth ??
Infinity;
return currDepth < prevDepth ? curr : prev;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, this is ugly, can can be simplified, but out of scope for this PR I'd say

@bramj bramj requested a review from Bewi February 2, 2026 11:08
};
if (orgUnitParentId) {
params.orgUnitParentId = orgUnitParentId;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to go with backend filtering since the OU payload doesn't contain enough data about the whole ancestor chain (e.g. for BFA, you probably want to filter on Region instead of Province, which is 2 levels up from the district)

Copy link
Contributor

@Bewi Bewi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall it looks good.
I do wonder few things:

  • When country OU type is selected, do we really want to display the org unit selector ?
  • Overall it feels super complex, I suggest we have another task to try to think of a simplified approach.
  • Budgeting is acting weird on my side, the breakdown chart seems broken

* Place this inside a `<MapContainer>` to handle dynamic layout changes
* (e.g. sidebar open/close) that Leaflet can't detect on its own.
*/
export const InvalidateOnResize = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would type this as a FC as well to know exactly what it is, I thought it was an utility function at first :D

}, [bounds, boundsOptions, map]);

return null;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, I would only move FitBounds on its own file and type the InvalidateOnResize to be a FC

{formatMessage(MESSAGES.total)} :{' '}
{formatBigNumber(totalCost)}
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SX should be in a style object, it makes it easier to track and reduces html complexity

}, [orgUnits]);

// Filter org unit types to show only types with depth <= parentTypeDepth
// Note: The original object from API includes depth, but OriginalOrgUnitType type doesn't declare it
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could just add it and make it nullable on the model ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember now: I wanted to avoid having to add something to such a basic type in IASO for fear of breaking something

[params, redirectToReplace],
);

const handleToggleSidebar = useCallback(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like this useCallback is pointless, could use setIsSidebarOpen directly

</Typography>
</MenuItem>
)}
<MenuItem value={0}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure about this, don't like having an intervention selector if there is only one assigned to all org units.
Even if the user has stripped down his view.

I am guessing that you changed this because when we select a parent OU, we might reach a point when we only have one intervention assigned but for the country we might have mulitple ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I changed that one because it produced a ton of MUI warnings in the console, and it felt like an unnecessary check. Here is the commit: a101190, we can always revert it

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll revert for now 👍

</MenuItem>
)}
{showAllOption && sortedInterventions.length > 1 && (
{showAllOption && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, not sure we still want to display this if there is only one intervention

@bramj
Copy link
Collaborator Author

bramj commented Feb 3, 2026

@Bewi

When country OU type is selected, do we really want to display the org unit selector ?

I agree, but the OU type names are not fixed, so we'd need to be smart about it. In this implementation, I take all ancestors that are higher than the OU level at which we do intervention planning. We don't know beforehand how many ancestors there are, and what they're named. Not sure how to work around that 🤔

Overall it feels super complex, I suggest we have another task to try to think of a simplified approach.

I was also surprised at how complex this all turned out to be 😅 .

Budgeting is acting weird on my side, the breakdown chart seems broken

That's a bug, I'll check this.

@bramj bramj force-pushed the SNT-225-filter-by-province branch from 6534452 to 2af2b6e Compare February 8, 2026 20:30
@bramj
Copy link
Collaborator Author

bramj commented Feb 9, 2026

@Bewi Okay I fixed most of your review comments, thanks for them 🙏

I also had a look at the complexity again, and I agree, it hurts the head. One of the main sources for complexity is the automatic determining of the OU level at which we do intervention planning.

I feel like I need a "country account configuration", like the BudgetSettings singleton. Here we could then store things like:

  • the OU type that we use for intervention planning
  • other OU types that we want to show shapes for (e.g. provinces)

Perhaps I can use the IASO JsonDataStore (iaso/models/data_store.py)? Maybe we can do a quick call to discuss.

@bramj
Copy link
Collaborator Author

bramj commented Feb 20, 2026

Finally! I'm happy with the implementation. I'll do a rebase after getting this one merged #195 and then it's good to go I think.

I made big simplifications mainly on:

  • Deciding which filters to add to the sidebar: an admin can now add a JsonDataStore record to configure the country + district level. That way we populate the "National" + in between layers. Still a bit complicated, but way better than "guessing" the layers based on shape presence.
    image
  • The Budgeting module was simplified a lot by making the budget function return the full breakdown per OU: feat: Add full cost_class details per intervention to get_places_costs() output (SNT-225) snt-malaria-budgeting#40. This allows the frontend to simply filter on OU and aggregate the numbers.

@bramj bramj force-pushed the SNT-225-filter-by-province branch from f24a915 to a803535 Compare February 20, 2026 20:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants