Skip to content

Commit b1f2558

Browse files
roshni73samshara
authored andcommitted
Use GlobalMap instead of BaseMap
1 parent f97dff1 commit b1f2558

File tree

6 files changed

+523
-14
lines changed

6 files changed

+523
-14
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"namespace": "operationalLearning",
3+
"strings": {
4+
"downloadMapTitle": "Operational learning map",
5+
"learningLegendLabel": "Learnings",
6+
"learningCountLegendLabel":"Learning count"
7+
}
8+
}
Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
import {
2+
useCallback,
3+
useMemo,
4+
useState,
5+
} from 'react';
6+
import {
7+
Container,
8+
NumberOutput,
9+
TextOutput,
10+
} from '@ifrc-go/ui';
11+
import { useTranslation } from '@ifrc-go/ui/hooks';
12+
import { maxSafe } from '@ifrc-go/ui/utils';
13+
import {
14+
_cs,
15+
isDefined,
16+
isNotDefined,
17+
} from '@togglecorp/fujs';
18+
import {
19+
MapLayer,
20+
MapSource,
21+
} from '@togglecorp/re-map';
22+
23+
import GlobalMap from '#components/domain/GlobalMap';
24+
import Link from '#components/Link';
25+
import MapContainerWithDisclaimer from '#components/MapContainerWithDisclaimer';
26+
import MapPopup from '#components/MapPopup';
27+
import useCountry from '#hooks/domain/useCountry';
28+
import {
29+
COLOR_BLUE,
30+
COLOR_LIGHT_BLUE,
31+
} from '#utils/constants';
32+
import {
33+
adminFillLayerOptions,
34+
getPointCircleHaloPaint,
35+
} from '#utils/map';
36+
import { type GoApiResponse } from '#utils/restRequest';
37+
38+
import i18n from './i18n.json';
39+
import styles from './styles.module.css';
40+
41+
type learningStatsResponse = GoApiResponse<'/api/v2/ops-learning/stats/'>;
42+
const sourceOptions: mapboxgl.GeoJSONSourceRaw = {
43+
type: 'geojson',
44+
};
45+
46+
interface CountryProperties {
47+
country_id: number;
48+
name: string;
49+
operation_count: number;
50+
}
51+
interface ClickedPoint {
52+
feature: GeoJSON.Feature<GeoJSON.Point, CountryProperties>;
53+
lngLat: mapboxgl.LngLatLike;
54+
}
55+
56+
const LEARNINGS_LOW_COLOR = COLOR_LIGHT_BLUE;
57+
const LEARNINGS_HIGH_COLOR = COLOR_BLUE;
58+
59+
interface Props {
60+
className?: string;
61+
learning: learningStatsResponse | undefined;
62+
}
63+
64+
function OperationalLearningMap(props: Props) {
65+
const strings = useTranslation(i18n);
66+
const {
67+
className,
68+
learning,
69+
} = props;
70+
71+
const [
72+
clickedPointProperties,
73+
setClickedPointProperties,
74+
] = useState<ClickedPoint | undefined>();
75+
76+
const countryResponse = useCountry();
77+
78+
const learningByCountry = useMemo(() => {
79+
const map: Record<number, { count: number }> = {};
80+
learning?.learning_by_country.forEach((item) => {
81+
map[item.country_id] = item;
82+
});
83+
return map;
84+
}, [learning]);
85+
86+
const countryCentroidGeoJson = useMemo(
87+
(): GeoJSON.FeatureCollection<GeoJSON.Geometry> => {
88+
const features = countryResponse
89+
?.map((country) => {
90+
const learningList = learningByCountry[country.id];
91+
if (isNotDefined(learningList)) {
92+
return undefined;
93+
}
94+
const units = learningList.count;
95+
return {
96+
type: 'Feature' as const,
97+
geometry: country.centroid as {
98+
type: 'Point',
99+
coordinates: [0, 0],
100+
},
101+
properties: {
102+
country_id: country.id,
103+
name: country.name,
104+
operation_count: units,
105+
},
106+
};
107+
})
108+
.filter(isDefined) ?? [];
109+
return {
110+
type: 'FeatureCollection',
111+
features,
112+
};
113+
},
114+
[countryResponse, learningByCountry],
115+
);
116+
117+
const bluePointHaloCirclePaint = useMemo(() => {
118+
const learningCount = learning?.learning_by_country
119+
.filter((country) => country.count > 0);
120+
121+
const maxScaleValue = learningCount && learningCount.length > 0
122+
? Math.max(
123+
...(learningCount
124+
.map((activity: { count: number; }) => activity.count)
125+
.filter(isDefined) ?? []),
126+
)
127+
: 0;
128+
129+
return getPointCircleHaloPaint(COLOR_BLUE, 'operation_count', maxScaleValue);
130+
}, [learning]);
131+
132+
const handlePointClose = useCallback(() => {
133+
setClickedPointProperties(undefined);
134+
}, []);
135+
136+
const handlePointClick = useCallback(
137+
(feature: mapboxgl.MapboxGeoJSONFeature, lngLat: mapboxgl.LngLatLike) => {
138+
setClickedPointProperties({
139+
feature: feature as unknown as ClickedPoint['feature'],
140+
lngLat,
141+
});
142+
return true;
143+
},
144+
[setClickedPointProperties],
145+
);
146+
147+
const maxLearning = useMemo(() => {
148+
const learningData = learning?.learning_by_country.filter(
149+
({ count }) => isDefined(count),
150+
);
151+
152+
return maxSafe(
153+
learningData?.map(({ count }) => count),
154+
);
155+
}, [learning]);
156+
157+
return (
158+
<Container
159+
className={_cs(styles.learningMap, className)}
160+
footerClassName={styles.footer}
161+
footerContent={(
162+
<div className={styles.learningsLegend}>
163+
<div className={styles.legendLabel}>{strings.learningLegendLabel}</div>
164+
<div className={styles.legendContent}>
165+
<div
166+
className={styles.learningGradient}
167+
style={{ background: `linear-gradient(90deg, ${LEARNINGS_LOW_COLOR}, ${LEARNINGS_HIGH_COLOR})` }}
168+
/>
169+
<div className={styles.labelList}>
170+
<NumberOutput
171+
value={0}
172+
/>
173+
<NumberOutput
174+
value={maxLearning}
175+
/>
176+
</div>
177+
</div>
178+
</div>
179+
)}
180+
>
181+
<GlobalMap
182+
baseLayers={(
183+
<MapLayer
184+
layerKey="admin-0"
185+
hoverable
186+
layerOptions={adminFillLayerOptions}
187+
onClick={handlePointClick}
188+
/>
189+
)}
190+
>
191+
<MapContainerWithDisclaimer
192+
className={styles.mapContainer}
193+
title={strings.downloadMapTitle}
194+
/>
195+
<MapSource
196+
sourceKey="points"
197+
sourceOptions={sourceOptions}
198+
geoJson={countryCentroidGeoJson}
199+
>
200+
<MapLayer
201+
layerKey="points-circle"
202+
onClick={handlePointClick}
203+
layerOptions={{
204+
type: 'circle',
205+
paint: bluePointHaloCirclePaint,
206+
}}
207+
/>
208+
</MapSource>
209+
{clickedPointProperties?.lngLat && (
210+
<MapPopup
211+
onCloseButtonClick={handlePointClose}
212+
coordinates={clickedPointProperties.lngLat}
213+
heading={(
214+
<Link
215+
to="countriesLayout"
216+
urlParams={{
217+
countryId: clickedPointProperties.feature.properties.country_id,
218+
}}
219+
>
220+
{clickedPointProperties.feature.properties.name}
221+
</Link>
222+
)}
223+
childrenContainerClassName={styles.popupContent}
224+
>
225+
<Container
226+
className={styles.popupEvent}
227+
childrenContainerClassName={styles.popupEventDetail}
228+
headingLevel={5}
229+
>
230+
<TextOutput
231+
value={clickedPointProperties.feature.properties.operation_count}
232+
label={strings.learningCountLegendLabel}
233+
valueType="number"
234+
/>
235+
</Container>
236+
</MapPopup>
237+
)}
238+
</GlobalMap>
239+
</Container>
240+
);
241+
}
242+
243+
export default OperationalLearningMap;
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
.learning-map {
2+
.map-container {
3+
height: 40rem;
4+
}
5+
.footer {
6+
background-color: var(--go-ui-color-background);
7+
padding: var(--go-ui-spacing-md);
8+
}
9+
10+
.legend-label {
11+
font-size: var(--go-ui-font-size-sm);
12+
font-weight: var(--go-ui-font-weight-medium);
13+
}
14+
15+
.learnings-legend {
16+
display: flex;
17+
align-items: center;
18+
flex-wrap: wrap;
19+
gap: var(--go-ui-spacing-xs) var(--go-ui-spacing-md);
20+
21+
.legend-content {
22+
.learning_gradient {
23+
width: 10rem;
24+
height: 0.5rem;
25+
}
26+
27+
.label-list {
28+
display: flex;
29+
justify-content: space-between;
30+
font-size: var(--go-ui-font-size-xs);
31+
font-weight: var(--go-ui-font-weight-medium);
32+
}
33+
}
34+
}
35+
}
36+
.popup-content {
37+
display: flex;
38+
flex-direction: column;
39+
gap: var(--go-ui-spacing-md);
40+
41+
42+
.popup-appeal {
43+
gap: var(--go-ui-spacing-xs);
44+
45+
.popup-appeal-detail {
46+
display: flex;
47+
flex-direction: column;
48+
font-size: var(--go-ui-font-size-sm);
49+
}
50+
}
51+
}

app/src/views/OperationalLearning/i18n.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
"operationsIncluded": "Operations Included",
2525
"sourcesUsed": "Sources Used",
2626
"learningExtract": "Learning Extracts",
27-
"sectorsCovered": "Sectors Covered"
27+
"sectorsCovered": "Sectors Covered",
28+
"learningBySector": "Learning by sectors",
29+
"learningByRegions": "Learning by regions",
30+
"sourceOvertime": "Sources overtime",
31+
"sourceOvertimeResponseError" :"Chart not available"
2832
}
2933
}

0 commit comments

Comments
 (0)