Skip to content

Conversation

@BRaimbault
Copy link
Collaborator

@BRaimbault BRaimbault commented Nov 20, 2025

Parent PR: #3588 > chore: refactor ThematicDialog to functional component DHIS2-18209
Child PR: #3593 > feat: support multiple timeline layers DHIS2-19542

Implements DHIS2-19542

Description

This PR enables adding multiple split layers to a map.

image

Feats

  • Add support for multiple thematic layers in split view component (handles the map):
    • src/components/map/SplitView.jsx
    • SplitView parent: src/components/map/MapView.jsx to pass layers as an array instead of single layer
    • Helpers to support multiple split layers: src/util/helpers.js

 

  • Allow adding a new thematic layer when a split layer is already there: src/components/layers/overlays/LayerList.jsx
  • Hide manage layer sources button on split view: src/components/layers/overlays/AddLayerPopover.jsx
image

 

  • Display rendering strategy on top in period tab: src/components/edit/thematic/ThematicDialog.jsx and src/components/edit/styles/LayerDialog.module.css
  • Let the user change rendering strategy if selected period type is start-end dates: src/components/edit/thematic/ThematicDialog.jsx#L268-L288
image

 

  • Change the logic in the rendering strategy to only allow split if no other layer or only split layers are already there: src/components/periods/RenderingStrategy.jsx
  • Change the logic to set default rendering strategy to split if a split layer is already there: src/components/edit/thematic/ThematicDialog.jsx#L193-L204
  • Get filter (ie periods) from already present split layers: src/components/edit/thematic/ThematicDialog.jsx#L111-L117
  • Change the logic to get set default periods to match other split layer if exists: src/components/edit/thematic/ThematicDialog.jsx#L222-L224
image

 

  • Delegate logic to test number of periods is compatible with rendering strategy to thematic layer validation, ie. we don't change to rendering: single if the number of selected periods <2 anymore, affects:
    • cypress/integration/layers/thematiclayer.cy.js
    • src/components/periods/__tests__/RenderingStrategy.spec.js
    • src/components/edit/thematic/validateThematicLayer.js

 

  • Sync period change across split layers upon successful layer validation:
    • src/components/edit/thematic/ThematicDialog.jsx#L308-L312
    • src/actions/map.js
    • src/constants/actionTypes.js
    • src/reducers/map.js

 

  • Indicate if the layer is of type: "Split" or "Timeline" in layer card (legend):
    • src/util/legend.js
    • src/components/layers/LayerCard.jsx
    • src/components/plugin/LegendLayer.jsx
image

Fixes

  • Update src/components/map/MapItem.jsx to properly handle 2 map items (DHIS2-19808).
image

 

Chores

  • Extract thematic layer validation into a util: src/components/edit/thematic/validateThematicLayer.js.
  • Address unrelated prop type warning: src/components/periods/CalendarInput.jsx
  • Address unrelated inconsistent file type: src/hooks/useLayersLoader.js and src/components/app/App.jsx

Quality checklist

Add N/A to items that are not applicable.


Testing

BRaimbault and others added 30 commits June 11, 2025 11:55
…ds to sort [DHIS2-6683] (#3541)

Implements DHIS2-6683

Description
* Disable text selection while dragging the layer cards
* Disable map interactivity while dragging the layer cards

The reason for the timeout in onSortEnd is because sometimes when releasing the
drag (mouseUp), the css change happened so fast that some text got selected
right at mouseUp. Delaying it 100ms seemed to fix that.
…[DHIS2-19716] (#3547)

Support both ALL and F_EXTERNAL_MAP_LAYER_PUBLIC_ADD as maps-app admin authorities.
Update "Remove all existing layers to add a split map view."
to "Remove all other layers to add a split map view."
@dhis2-bot dhis2-bot temporarily deployed to netlify December 4, 2025 11:52 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify December 4, 2025 14:34 Inactive
@BRaimbault BRaimbault changed the title feat: support multiple timeline and splitmap layers [DHIS2-19542] feat: support multiple split layers [DHIS2-19542] Dec 4, 2025
@dhis2-bot dhis2-bot temporarily deployed to netlify December 4, 2025 17:23 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify December 5, 2025 09:14 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify December 5, 2025 11:55 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify December 5, 2025 14:06 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify December 5, 2025 14:23 Inactive
@dhis2-bot dhis2-bot temporarily deployed to netlify December 5, 2025 15:01 Inactive
Copy link
Collaborator Author

@BRaimbault BRaimbault left a comment

Choose a reason for hiding this comment

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

Ready for review.

@BRaimbault BRaimbault marked this pull request as ready for review December 5, 2025 15:06
Copy link
Contributor

@HendrikThePendric HendrikThePendric left a comment

Choose a reason for hiding this comment

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

It's a bit of a challenge for me to review a PR of this size in an app I am not super familiar with, but I had a go anyway. In general I think things are looking good. I left a few suggestions in the code which you can take or leave.

I've approved in case you want to go ahead and merge. If you are implementing any of my suggestions you can ask for a re-approval after the codebase has changed.

Comment on lines +36 to +41
const setErrorState = ({ key, msg, tab }) => {
errors[key] = msg
if (!errors.tab) {
errors.tab = tab
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This code probably works fine, but is very hard to digest. My confusion mainly stems from the the following:

  • errors is an object that can have a bunch of fields and one of these is tab
  • The single tab field gets populated with a string value, i.e. data, period, orgunits, etc.
  • In the code there are a lot of if clauses (not if/else) and these are based on different arguments (i.e. valueType and periodType)
  • So now, purely by reading the code and without any knowledge about the context, it looks like this is problematic because we could first encounter a "data-tab-error" and then a "period-tab-error" and the second would then overwrite the tab field which makes it look like information is lost.

Possibly this behaviour is intentional, and if this is the case I would like to see a name change here, perhaps lasteErrorTab or something.

Or perhaps it won't happen in practice, and if this is the case, it would be clearer to see some if/else clauses at the parts of the code where you switch between different tab-values.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The idea is to raise all errors found but since those can be in multiple tabs, we have to select which one to jump to.
The order in which the tab prop gets overriden enforce Data > Period > OrgUnit > Filter > Style.
I will rename/comment to clarify.

Comment on lines +553 to +599
{renderingStrategy ===
RENDERING_STRATEGY_SINGLE && (
<div>
<SegmentedControl
className={styles.flexRowFlow}
options={[
{
label: i18n.t(
'Choose from presets'
),
value: PREDEFINED_PERIODS,
},
{
label: i18n.t(
'Define start - end dates'
),
value: START_END_DATES,
},
]}
selected={periodType}
onChange={(e) =>
dispatch(
setPeriodType(
{ value: e.value },
true
)
)
}
/>
</div>
)}
{renderingStrategy ===
RENDERING_STRATEGY_TIMELINE && (
<div className={styles.periodText}>
{i18n.t(
'Choose period for all timeline layers'
)}
</div>
)}
{renderingStrategy ===
RENDERING_STRATEGY_SPLIT_BY_PERIOD && (
<div className={styles.periodText}>
{i18n.t(
'Choose period for all split layers'
)}
</div>
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider extracting this to a helper component with a switch

Comment on lines +88 to +90
key={`${period.id}-${layer.id}`}
index={layers.length - index}
{...layer} // needs to be before period
Copy link
Contributor

Choose a reason for hiding this comment

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

Can the layer have a key or index that you want to use to override the computed values above? If not, I would simply spread the props first and then define individual ones, which is more typical.

Copy link
Contributor

Choose a reason for hiding this comment

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

It could be that I am missing some nuances here which would prevent you from doing this, but I wonder if it could perhaps be possible to switch from the useEffect + useState hooks to a useMemo hook. As far as can tell you are calling setErrors inside two useEffect hooks, so I am thinking that if you add all the combined dependencies form those useEffect calls to the useMemo dependency array, then you should be able to have a single memoized errors variable (or perhaps an object with { isValid, errors }. And subsequently you could have a some useEffect hooks that dispatch things when the validity changes.

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.

5 participants