Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
6f38d12
fix: Handle soft deletable models in demo creation script
bramj Feb 20, 2026
3428a53
feat: Add sidebar to planning page
bramj Jan 23, 2026
cb5bc2f
Improve sidebar icon toggle state
bramj Jan 23, 2026
fa0e8d4
OU filter sidebar v1
bramj Jan 23, 2026
56e0dd1
Sidebar v2
bramj Jan 23, 2026
c259d10
Improve dropdown labels
bramj Jan 23, 2026
bd32d11
Pre-set the filter to national level
bramj Jan 23, 2026
d80a1c7
Improve loading states
bramj Jan 23, 2026
5c4a84a
Add filter state to URL query params + adapt zoom on main map
bramj Jan 26, 2026
c754320
Filter budget on displayed org units
bramj Feb 1, 2026
3410f51
Visually display the OU filter on the budget
bramj Feb 1, 2026
b337cad
Don't fetch too much data for the OU parents list
bramj Feb 1, 2026
dcac967
Filter intervention plan on displayed OU
bramj Feb 1, 2026
70d609c
Filter intervention plan map on displayed OU
bramj Feb 1, 2026
907b196
refac: Cleanup unneeded renaming
bramj Feb 2, 2026
9414014
Fix MUI console warnings
bramj Feb 2, 2026
f0444bd
refac: Map resizing when filter changes...
bramj Feb 2, 2026
a089aff
Add translations
bramj Feb 2, 2026
0f1f71a
refac: Extract FitBounds to its own file + add typing...
bramj Feb 8, 2026
e80b981
refac: Extract SX into styles object for Budgeting component
bramj Feb 8, 2026
a6eedb0
refac: Remove useless callback
bramj Feb 8, 2026
803ae9f
cleanup: Extract SX clause
bramj Feb 8, 2026
7bbfd1a
refac: Simplify the PlanningFiltersSidebar component
bramj Feb 9, 2026
992bec1
revert based on PR feedback
bramj Feb 10, 2026
d13b282
feat: Filter the budgeting on the OU selection
bramj Feb 18, 2026
a803535
fix: Give demo user the permission to read JsonDataStore
bramj Feb 20, 2026
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,21 @@ docker compose run iaso manage set_up_burkina_faso_account

6. Using the credentials you just received, you should now be able to log in and create a first scenario.

## Configuration

The planning page requires a configuration entry in the Iaso datastore to know which org unit types represent the country level and the intervention level. Without this configuration (or if it contains invalid values), the planning sidebar will only show the "National" display level.

To set it up, go to the Django admin for your account and create a datastore entry with the key `snt_malaria_config` and the following JSON data:

```json
{
"country_org_unit_type_id": <id>,
"intervention_org_unit_type_id": <id>
}
```

Replace `<id>` with the actual org unit type IDs for your country. Both values must be numbers.

## OpenHEXA import

This section describes how to fetch real data layers from OpenHEXA.
Expand Down
24 changes: 24 additions & 0 deletions js/src/components/FitBounds.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FC, useEffect } from 'react';
import L from 'leaflet';
import { useMap } from 'react-leaflet';

/**
* Fits the map to the given bounds whenever they change.
* Place this inside a `<MapContainer>` — react-leaflet treats
* `bounds` on MapContainer as an immutable prop, so this component
* is needed to react to bound changes (e.g. when filtered org units change).
*/
export const FitBounds: FC<{
bounds: L.LatLngBounds | undefined;
boundsOptions?: L.FitBoundsOptions;
}> = ({ bounds, boundsOptions = {} }) => {
const map = useMap();

useEffect(() => {
if (bounds && map) {
map.fitBounds(bounds, boundsOptions);
}
}, [bounds, boundsOptions, map]);

return null;
};
22 changes: 22 additions & 0 deletions js/src/components/InvalidateOnResize.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FC, useEffect } from 'react';
import { useMap } from 'react-leaflet';

/**
* Calls `map.invalidateSize()` whenever the map container is resized.
* 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: FC = () => {
const map = useMap();

useEffect(() => {
const container = map.getContainer();
const observer = new ResizeObserver(() => {
map.invalidateSize();
});
observer.observe(container);
return () => observer.disconnect();
}, [map]);

return null;
};
4 changes: 4 additions & 0 deletions js/src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import tiles from 'Iaso/constants/mapTiles';
import { OrgUnit } from 'Iaso/domains/orgUnits/types/orgUnit';
import { noOp } from 'Iaso/utils';
import { Bounds } from 'Iaso/utils/map/mapUtils';
import { FitBounds } from './FitBounds';
import { InvalidateOnResize } from './InvalidateOnResize';
import { mapTheme } from '../constants/map-theme';
import { MapLegend } from '../domains/planning/components/MapLegend';
import {
Expand Down Expand Up @@ -85,6 +87,8 @@ export const Map: FC<Props> = ({
zoomSnap={defaultZoomSnap}
zoomDelta={defaultZoomDelta}
>
<InvalidateOnResize />
<FitBounds bounds={bounds} boundsOptions={boundsOptions} />
<ZoomControl position="bottomright" />
{orgUnits?.map(orgUnit => {
const orgUnitMapMisc = getOrgUnitMapMisc(orgUnit.id);
Expand Down
15 changes: 12 additions & 3 deletions js/src/constants/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"iaso.snt_malaria.label.interventionList.tableNoContent": "Intervention and their districts will appear here.",
"iaso.snt_malaria.label.interventionPlanTitle": "Intervention plan",
"iaso.snt_malaria.label.layers": "Layers",
"iaso.snt_malaria.label.national": "National",
"iaso.snt_malaria.label.noInterventionPlanAvailable": "No intervention plan available",
"iaso.snt_malaria.label.none": "None",
"iaso.snt_malaria.label.ok": "OK",
Expand Down Expand Up @@ -139,9 +140,12 @@
"iaso.snt_malaria.budgetAssumptions.path": "Budgeting methodology based on tools and documentation developed by PATH.",
"iaso.snt_malaria.budgetAssumptions.minValue": "Value must be at least {min}",
"iaso.snt_malaria.budgetAssumptions.maxValue": "Value must be at most {max}",
"iaso.snt_malaria.label.showLegend": "Show Legend",
"iaso.snt_malaria.label.hideLegend": "Hide Legend",
"iaso.snt_malaria.label.allDisplayLevels": "All",
"iaso.snt_malaria.label.allOrgUnits": "All",
"iaso.snt_malaria.label.allParentOrgUnits": "All",
"iaso.snt_malaria.label.displayLevel": "Display level",
"iaso.snt_malaria.label.duplicate": "Duplicate",
"iaso.snt_malaria.label.hideLegend": "Hide Legend",
"iaso.snt_malaria.scenarioRule.apply": "Apply rules",
"iaso.snt_malaria.scenarioRule.create": "Create rule",
"iaso.snt_malaria.scenarioRule.edit": "Edit rule",
Expand All @@ -157,5 +161,10 @@
"iaso.snt_malaria.scenarioRule.operator": "Operator",
"iaso.snt_malaria.scenarioRule.value": "Value",
"iaso.snt_malaria.scenarioRule.selectionCriteria": "Selection Criteria",
"iaso.snt_malaria.scenarioRule.interventionProperties": "Interventions"
"iaso.snt_malaria.scenarioRule.interventionProperties": "Interventions",
"iaso.snt_malaria.label.hideSidebar": "Hide sidebar",
"iaso.snt_malaria.label.parentOrgUnit": "Parent",
"iaso.snt_malaria.label.showLegend": "Show Legend",
"iaso.snt_malaria.label.showSidebar": "Show sidebar",
"iaso.snt_malaria.label.sidebarTitle": "Display options"
}
15 changes: 12 additions & 3 deletions js/src/constants/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"iaso.snt_malaria.label.interventionList.tableNoContent": "Les interventions et leurs districts apparaîtront ici",
"iaso.snt_malaria.label.interventionPlanTitle": "Plan d'intervention",
"iaso.snt_malaria.label.layers": "Couches",
"iaso.snt_malaria.label.national": "National",
"iaso.snt_malaria.label.noInterventionPlanAvailable": "Aucun plan d'intervention disponible",
"iaso.snt_malaria.label.none": "Aucun",
"iaso.snt_malaria.label.ok": "OK",
Expand Down Expand Up @@ -139,9 +140,12 @@
"iaso.snt_malaria.budgetAssumptions.path": "Méthodologie de budgétisation basée sur les outils et la documentation développés par PATH.",
"iaso.snt_malaria.budgetAssumptions.minValue": "La valeur doit être au minimum {min}",
"iaso.snt_malaria.budgetAssumptions.maxValue": "La valeur doit être au maximum {max}",
"iaso.snt_malaria.label.showLegend": "Afficher la légende",
"iaso.snt_malaria.label.hideLegend": "Masquer la légende",
"iaso.snt_malaria.label.allDisplayLevels": "Tous",
"iaso.snt_malaria.label.allOrgUnits": "Tous",
"iaso.snt_malaria.label.allParentOrgUnits": "Tous",
"iaso.snt_malaria.label.displayLevel": "Niveau d'affichage",
"iaso.snt_malaria.label.duplicate": "Dupliquer",
"iaso.snt_malaria.label.hideLegend": "Masquer la légende",
"iaso.snt_malaria.scenarioRule.apply": "Appliquer les règles",
"iaso.snt_malaria.scenarioRule.create": "Créer une règle",
"iaso.snt_malaria.scenarioRule.edit": "Modifier la règle",
Expand All @@ -157,5 +161,10 @@
"iaso.snt_malaria.scenarioRule.operator": "Opérateur",
"iaso.snt_malaria.scenarioRule.value": "Valeur",
"iaso.snt_malaria.scenarioRule.selectionCriteria": "Critères de sélection",
"iaso.snt_malaria.scenarioRule.interventionProperties": "Interventions"
"iaso.snt_malaria.scenarioRule.interventionProperties": "Interventions",
"iaso.snt_malaria.label.hideSidebar": "Masquer le panneau latéral",
"iaso.snt_malaria.label.parentOrgUnit": "Parent",
"iaso.snt_malaria.label.showLegend": "Afficher la légende",
"iaso.snt_malaria.label.showSidebar": "Afficher le panneau latéral",
"iaso.snt_malaria.label.sidebarTitle": "Options d'affichage"
}
2 changes: 1 addition & 1 deletion js/src/constants/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { paginationPathParams } from 'Iaso/routing/common';
export const RouteConfigs: Record<string, RouteConfig> = {
planning: {
url: 'snt_malaria/planning',
params: ['scenarioId'],
params: ['scenarioId', 'displayOrgUnitTypeId', 'displayOrgUnitId'],
},
planningV2: {
url: 'snt_malaria/planning-v2',
Expand Down
84 changes: 60 additions & 24 deletions js/src/domains/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,60 +393,60 @@ export const MESSAGES = defineMessages({
budgetAssumptionsDescription_itn_campaign: {
id: 'iaso.snt_malaria.budgetAssumptions.description.itn_campaign',
defaultMessage: `To estimate the number of insecticide-treated nets (ITNs) needed for campaign delivery in targeted areas:<br></br>
The <b>target population</b> (default: total pop) of the area is multiplied by the <b>target intervention coverage</b>
(default: 100%) to estimate the target population for the campaign and then divided by the <b>number of people
assumed to use 1 net</b> (default: 1.8). A <b>buffer</b> (default: 10%) is applied to account for wastage and contingency.
The <b>target population</b> (default: total pop) of the area is multiplied by the <b>target intervention coverage</b>
(default: 100%) to estimate the target population for the campaign and then divided by the <b>number of people
assumed to use 1 net</b> (default: 1.8). A <b>buffer</b> (default: 10%) is applied to account for wastage and contingency.
The number of <b>bales</b> is calculated by dividing the number of nets by 50 (assuming 50 nets per bale).`,
},
budgetAssumptionsDescription_itn_routine: {
id: 'iaso.snt_malaria.budgetAssumptions.description.itn_routine',
defaultMessage: `To estimate the number of nets needed for routine delivery channels often through ANC and EPI
services, the <b>target population</b> (default: total under 5 and pregnant women population) of an area is multiplied
defaultMessage: `To estimate the number of nets needed for routine delivery channels often through ANC and EPI
services, the <b>target population</b> (default: total under 5 and pregnant women population) of an area is multiplied
by the <b>expected routine distribution coverage</b> (default: 30%) and a <b>procurement buffer</b> (default: 10%).`,
},
budgetAssumptionsDescription_iptp: {
id: 'iaso.snt_malaria.budgetAssumptions.description.iptp',
defaultMessage: `To estimate the amount of SP (blister packs of 3 pills) to procure for IPTp
we take the <b>target population</b> (default: pregnant women) of an area,
multiply by the <b>expected coverage at ANC attendence</b> (default: 80%) for the scheduled number of <b>touchpoints</b>
defaultMessage: `To estimate the amount of SP (blister packs of 3 pills) to procure for IPTp
we take the <b>target population</b> (default: pregnant women) of an area,
multiply by the <b>expected coverage at ANC attendence</b> (default: 80%) for the scheduled number of <b>touchpoints</b>
per woman (default: 3) and multiply this by a <b>procurement buffer</b> (default: 10%).`,
},
budgetAssumptionsDescription_smc: {
id: 'iaso.snt_malaria.budgetAssumptions.description.smc',
defaultMessage: `To estimate the number of SP+AQ co-blistered packets required for Seasonal Malaria Chemoprevention (SMC),
we use the following methodology with the default assumptions included here for clarity.
We first assume each packet contains one full course for a single cycle (1 tablet of SP and 3 tablets of AQ).
SMC is at default delivered over 4 <b>monthly cycles</b> and targets two age groups:
children aged 3 to <12 months and children aged >12 to 59 months.
We include the distribution of age-groups as the procurement costs of the co-blistered packets
defaultMessage: `To estimate the number of SP+AQ co-blistered packets required for Seasonal Malaria Chemoprevention (SMC),
we use the following methodology with the default assumptions included here for clarity.
We first assume each packet contains one full course for a single cycle (1 tablet of SP and 3 tablets of AQ).
SMC is at default delivered over 4 <b>monthly cycles</b> and targets two age groups:
children aged 3 to <12 months and children aged >12 to 59 months.
We include the distribution of age-groups as the procurement costs of the co-blistered packets
for these different age groups vary as a result of the age-based dosing requirements for SMC drugs.

We first estimate the <b>target population</b> by applying fixed proportions to the total number of children under 5 years of age.
<b>Coverage</b> of the target population is assumed to be 100%, unless otherwise specified,
We first estimate the <b>target population</b> by applying fixed proportions to the total number of children under 5 years of age.
<b>Coverage</b> of the target population is assumed to be 100%, unless otherwise specified,
and is applied before the buffer is calculated. A <b>10% buffer</b> is then included to account for re-dosing, wastage, and the treatment of children from outside the catchment area.`,
},
budgetAssumptionsDescription_pmc: {
id: 'iaso.snt_malaria.budgetAssumptions.description.pmc',
defaultMessage: `To estimate the quantity of sulfadoxine-pyrimethamine (SP) required for Perennial Malaria Chemoprevention (PMC),
we assume delivery is integrated into routine Expanded Programme on Immunization (EPI).
defaultMessage: `To estimate the quantity of sulfadoxine-pyrimethamine (SP) required for Perennial Malaria Chemoprevention (PMC),
we assume delivery is integrated into routine Expanded Programme on Immunization (EPI).
Each eligible child receives SP at four routine immunization touchpoints per year, with age-specific dosing:
<ul>
<li>Children aged <b>0-1 years</b> receive 1 tablet of SP per contact.</li>

<li>Children aged <b>1-2 years</b> receive 2 tablets of SP per contact.</li>
</ul>
To account for <b>underdosing due to low weight</b>, which affects approximately <b>25% of children in each age group</b>,
a <b>scaling factor of 0.75</b> is applied to both age groups.
To account for <b>underdosing due to low weight</b>, which affects approximately <b>25% of children in each age group</b>,
a <b>scaling factor of 0.75</b> is applied to both age groups.
This factor reflects the average reduction in tablets required due to dose adjustment (e.g., half tablets for underweight infants).

An <b>85% coverage rate</b> is assumed and a <b>10% procurement buffer</b> is then included to cover wastage, re-dosing, and stockouts.`,
},
budgetAssumptionsDescription_vacc: {
id: 'iaso.snt_malaria.budgetAssumptions.description.vacc',
defaultMessage: `To estimate the number of malaria vaccine doses required, at default,
we assume each eligible child will receive a <b>4-dose schedule</b>.
Assuming the first three doses are delivered monthly and start around 5 months of age and the 4th dose is delivered ~12 - 15 months following the 3rd dose.
The vaccine is delivered through routine immunization contacts with an <b>expected coverage of 84%</b> among the target population.
defaultMessage: `To estimate the number of malaria vaccine doses required, at default,
we assume each eligible child will receive a <b>4-dose schedule</b>.
Assuming the first three doses are delivered monthly and start around 5 months of age and the 4th dose is delivered ~12 - 15 months following the 3rd dose.
The vaccine is delivered through routine immunization contacts with an <b>expected coverage of 84%</b> among the target population.
A 10% buffer is included to account for losses during transportation, storage, and administration.`,
},
budgetAssumptionsPath: {
Expand Down Expand Up @@ -522,4 +522,40 @@ export const MESSAGES = defineMessages({
id: 'iaso.snt_malaria.scenarioRule.interventionProperties',
defaultMessage: 'Interventions',
},
showSidebar: {
id: 'iaso.snt_malaria.label.showSidebar',
defaultMessage: 'Show sidebar',
},
hideSidebar: {
id: 'iaso.snt_malaria.label.hideSidebar',
defaultMessage: 'Hide sidebar',
},
sidebarTitle: {
id: 'iaso.snt_malaria.label.sidebarTitle',
defaultMessage: 'Display options',
},
parentOrgUnit: {
id: 'iaso.snt_malaria.label.parentOrgUnit',
defaultMessage: 'Parent',
},
allParentOrgUnits: {
id: 'iaso.snt_malaria.label.allParentOrgUnits',
defaultMessage: 'All',
},
displayLevel: {
id: 'iaso.snt_malaria.label.displayLevel',
defaultMessage: 'Display level',
},
allDisplayLevels: {
id: 'iaso.snt_malaria.label.allDisplayLevels',
defaultMessage: 'All',
},
allOrgUnits: {
id: 'iaso.snt_malaria.label.allOrgUnits',
defaultMessage: 'All',
},
national: {
id: 'iaso.snt_malaria.label.national',
defaultMessage: 'National',
},
});
Loading