-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
{ | ||
"namespace": "learningMap", | ||
"strings": { | ||
"learningDownloadMapTitle": "Operational learning", | ||
"presentationModeButton":"Presentation Mode" | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,228 @@ | ||
import { | ||
useCallback, | ||
useMemo, | ||
useState, | ||
} from 'react'; | ||
import { ArtboardLineIcon } from '@ifrc-go/icons'; | ||
Check warning on line 6 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
import { | ||
Button, | ||
Check warning on line 8 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
Container, | ||
TextOutput, | ||
} from '@ifrc-go/ui'; | ||
import { useTranslation } from '@ifrc-go/ui/hooks'; | ||
import { | ||
_cs, | ||
isDefined, | ||
isNotDefined, | ||
} from '@togglecorp/fujs'; | ||
import Map, { | ||
Check warning on line 18 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
MapBounds, | ||
Check warning on line 19 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
MapLayer, | ||
MapSource, | ||
} from '@togglecorp/re-map'; | ||
import { LngLatBoundsLike } from 'mapbox-gl'; | ||
Check warning on line 23 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
|
||
import BaseMap from '#components/domain/BaseMap'; | ||
import Link from '#components/Link'; | ||
import MapContainerWithDisclaimer from '#components/MapContainerWithDisclaimer'; | ||
import MapPopup from '#components/MapPopup'; | ||
import useCountryRaw from '#hooks/domain/useCountryRaw'; | ||
import { | ||
DEFAULT_MAP_PADDING, | ||
Check warning on line 31 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
DURATION_MAP_ZOOM, | ||
Check warning on line 32 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
} from '#utils/constants'; | ||
|
||
import { | ||
adminFillLayerOptions, | ||
basePointLayerOptions, | ||
outerCircleLayerOptionsForPersonnel, | ||
} from './utils'; | ||
|
||
import i18n from './i18n.json'; | ||
import styles from './styles.module.css'; | ||
|
||
const sourceOptions: mapboxgl.GeoJSONSourceRaw = { | ||
type: 'geojson', | ||
}; | ||
|
||
interface CountryProperties { | ||
country_id: number; | ||
name: string; | ||
units: number; | ||
} | ||
|
||
interface ClickedPoint { | ||
feature: GeoJSON.Feature<GeoJSON.Point, CountryProperties>; | ||
lngLat: mapboxgl.LngLatLike; | ||
} | ||
|
||
interface Props { | ||
className?: string; | ||
} | ||
|
||
function OperationalLearningMap(props: Props) { | ||
const strings = useTranslation(i18n); | ||
const { | ||
className, | ||
} = props; | ||
|
||
const [ | ||
clickedPointProperties, | ||
setClickedPointProperties] = useState<ClickedPoint | undefined>(); | ||
|
||
const learning_by_country = [ | ||
{ | ||
country_name: 'Afghanistan', | ||
country_id: 14, | ||
operation_count: 4, | ||
}, | ||
{ | ||
country_name: 'Albania', | ||
country_id: 15, | ||
operation_count: 1, | ||
}, | ||
{ | ||
country_name: 'Argentina', | ||
country_id: 20, | ||
operation_count: 1, | ||
}, | ||
{ | ||
country_name: 'Australia', | ||
country_id: 22, | ||
operation_count: 1, | ||
}, | ||
{ | ||
country_name: 'Belgium', | ||
country_id: 30, | ||
operation_count: 1, | ||
}, | ||
{ | ||
country_name: 'Canada', | ||
country_id: 42, | ||
operation_count: 1, | ||
}, | ||
]; | ||
|
||
const countryResponse = useCountryRaw(); | ||
|
||
const countryCentroidGeoJson = useMemo( | ||
(): GeoJSON.FeatureCollection<GeoJSON.Geometry> => ({ | ||
type: 'FeatureCollection', | ||
features: countryResponse | ||
?.map((country) => { | ||
if ( | ||
(!country.independent && isNotDefined(country.record_type)) | ||
|| isNotDefined(country.centroid) | ||
|| isNotDefined(country.iso3) | ||
) { | ||
return undefined; | ||
} | ||
|
||
const learningList = learning_by_country.find( | ||
(item) => item.country_id, | ||
); | ||
if (isNotDefined(learningList)) { | ||
return undefined; | ||
} | ||
|
||
const units = learningList.operation_count ?? 0; | ||
|
||
return { | ||
type: 'Feature' as const, | ||
geometry: country.centroid as { | ||
type: 'Point', | ||
coordinates: [number, number], | ||
}, | ||
properties: { | ||
id: country, | ||
name: country.name, | ||
units, | ||
}, | ||
}; | ||
}) | ||
.filter(isDefined) ?? [], | ||
}), | ||
[], | ||
Check warning on line 145 in app/src/views/OperationalLearning/LearningMap/index.tsx GitHub Actions / Lint JS
|
||
); | ||
|
||
const handleCountryClick = useCallback( | ||
(feature: mapboxgl.MapboxGeoJSONFeature, lngLat: mapboxgl.LngLatLike) => { | ||
setClickedPointProperties({ | ||
feature: feature as unknown as ClickedPoint['feature'], | ||
lngLat, | ||
}); | ||
return false; | ||
}, | ||
[], | ||
); | ||
|
||
const handlePointClose = useCallback(() => { | ||
setClickedPointProperties(undefined); | ||
}, []); | ||
|
||
return ( | ||
<Container className={_cs(styles.learningMap, className)}> | ||
<BaseMap | ||
baseLayers={( | ||
<MapLayer | ||
layerKey="admin-0" | ||
hoverable | ||
layerOptions={adminFillLayerOptions} | ||
onClick={handleCountryClick} | ||
/> | ||
)} | ||
> | ||
<MapContainerWithDisclaimer | ||
className={styles.mapContainer} | ||
title={strings.learningDownloadMapTitle} | ||
/> | ||
<MapSource | ||
sourceKey="points" | ||
sourceOptions={sourceOptions} | ||
geoJson={countryCentroidGeoJson} | ||
> | ||
<MapLayer | ||
layerKey="point-circle" | ||
layerOptions={basePointLayerOptions} | ||
/> | ||
<MapLayer | ||
layerKey="outer-circle" | ||
layerOptions={outerCircleLayerOptionsForPersonnel} | ||
/> | ||
</MapSource> | ||
{clickedPointProperties?.lngLat && ( | ||
<MapPopup | ||
onCloseButtonClick={handlePointClose} | ||
coordinates={clickedPointProperties.lngLat} | ||
heading={( | ||
<Link | ||
to="countriesLayout" | ||
urlParams={{ | ||
countryId: clickedPointProperties.feature.properties.country_id, | ||
}} | ||
> | ||
{clickedPointProperties.feature.properties.name} | ||
</Link> | ||
)} | ||
childrenContainerClassName={styles.popupContent} | ||
> | ||
<Container | ||
className={styles.popupEvent} | ||
childrenContainerClassName={styles.popupEventDetail} | ||
heading="" | ||
headingLevel={5} | ||
> | ||
<TextOutput | ||
value={clickedPointProperties.feature.properties.units} | ||
description="" | ||
valueType="number" | ||
/> | ||
</Container> | ||
</MapPopup> | ||
)} | ||
</BaseMap> | ||
</Container> | ||
); | ||
} | ||
|
||
export default OperationalLearningMap; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
.learning-map { | ||
|
||
.map-container { | ||
height: 40rem; | ||
} | ||
} | ||
|
||
.popup-content { | ||
display: flex; | ||
flex-direction: column; | ||
gap: var(--go-ui-spacing-md); | ||
|
||
.popup-appeal { | ||
gap: var(--go-ui-spacing-xs); | ||
|
||
.popup-appeal-detail { | ||
display: flex; | ||
flex-direction: column; | ||
font-size: var(--go-ui-font-size-sm); | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
import type { | ||
CircleLayer, | ||
CirclePaint, | ||
FillLayer, | ||
} from 'mapbox-gl'; | ||
|
||
import { | ||
COLOR_BLACK, | ||
COLOR_BLUE, | ||
COLOR_DARK_GREY, | ||
COLOR_LIGHT_GREY, | ||
COLOR_RED, | ||
COLOR_YELLOW, | ||
} from '#utils/constants'; | ||
|
||
const COLOR_LEARNING_AND_PERSONNEL = COLOR_BLUE; | ||
const COLOR_LEARNING_ONLY = COLOR_RED; | ||
const COLOR_PERSONNEL_ONLY = COLOR_YELLOW; | ||
const COLOR_DEFAULT = COLOR_BLACK; | ||
|
||
export const adminFillLayerOptions: Omit<FillLayer, 'id'> = { | ||
type: 'fill', | ||
paint: { | ||
'fill-color': [ | ||
'case', | ||
['boolean', ['feature-state', 'hovered'], false], | ||
COLOR_DARK_GREY, | ||
COLOR_LIGHT_GREY, | ||
], | ||
}, | ||
}; | ||
|
||
const circleColor: CirclePaint['circle-color'] = [ | ||
'case', | ||
COLOR_LEARNING_AND_PERSONNEL, | ||
COLOR_LEARNING_ONLY, | ||
COLOR_PERSONNEL_ONLY, | ||
COLOR_DEFAULT, | ||
]; | ||
|
||
const basePointPaint: CirclePaint = { | ||
'circle-radius': 5, | ||
'circle-color': circleColor, | ||
'circle-opacity': 0.8, | ||
}; | ||
|
||
export const basePointLayerOptions: Omit<CircleLayer, 'id'> = { | ||
type: 'circle', | ||
paint: basePointPaint, | ||
}; | ||
|
||
const baseOuterCirclePaint: CirclePaint = { | ||
'circle-color': circleColor, | ||
'circle-opacity': 0.4, | ||
}; | ||
|
||
const outerCirclePaintForPersonnel: CirclePaint = { | ||
...baseOuterCirclePaint, | ||
'circle-radius': [ | ||
'interpolate', | ||
['linear', 1], | ||
['get', 'learning'], | ||
|
||
2, | ||
5, | ||
4, | ||
7, | ||
6, | ||
9, | ||
8, | ||
11, | ||
10, | ||
13, | ||
12, | ||
15, | ||
], | ||
}; | ||
|
||
export const outerCircleLayerOptionsForPersonnel: Omit<CircleLayer, 'id'> = { | ||
type: 'circle', | ||
paint: outerCirclePaintForPersonnel, | ||
}; |