Skip to content

Commit 77aeab0

Browse files
committed
Make all points in local unit map same color
1 parent b1962a1 commit 77aeab0

File tree

13 files changed

+2438
-4036
lines changed

13 files changed

+2438
-4036
lines changed

app/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"papaparse": "^5.4.1",
5454
"react": "^18.2.0",
5555
"react-dom": "^18.2.0",
56+
"react-error-boundary": "^4.0.13",
5657
"react-router-dom": "^6.18.0",
5758
"sanitize-html": "^2.10.0"
5859
},

app/src/components/domain/BaseMap/index.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
useMemo,
44
} from 'react';
55
import { LanguageContext } from '@ifrc-go/ui/contexts';
6+
import { ErrorBoundary } from '@sentry/react';
67
import {
78
isDefined,
89
isFalsyString,
@@ -22,6 +23,8 @@ import {
2223
defaultNavControlPosition,
2324
} from '#utils/map';
2425

26+
import styles from './styles.module.css';
27+
2528
type MapProps = Parameters<typeof Map>[0];
2629

2730
type overrides = 'mapStyle' | 'mapOptions' | 'navControlShown' | 'navControlPosition' | 'navControlOptions' | 'scaleControlShown';
@@ -175,4 +178,21 @@ function BaseMap(props: Props) {
175178
);
176179
}
177180

178-
export default BaseMap;
181+
function BaseMapWithErrorBoundary(props: Props) {
182+
return (
183+
<ErrorBoundary
184+
fallback={(
185+
<div className={styles.mapError}>
186+
Failed to load map!
187+
</div>
188+
)}
189+
>
190+
<BaseMap
191+
// eslint-disable-next-line react/jsx-props-no-spreading
192+
{...props}
193+
/>
194+
</ErrorBoundary>
195+
);
196+
}
197+
198+
export default BaseMapWithErrorBoundary;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.map-error {
2+
display: flex;
3+
align-items: center;
4+
justify-content: center;
5+
background-color: var(--go-ui-color-background);
6+
min-height: 20rem;
7+
color: var(--go-ui-color-red);
8+
font-size: var(--go-ui-font-size-lg);
9+
}

app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsMap/i18n.json

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,13 @@
22
"namespace": "nationalSocietyLocalUnits",
33
"strings": {
44
"localUnitDetailAddress": "Address",
5-
"localUnitDetailPhoneNumber": "Phone Number",
6-
"localUnitDetailLastUpdate": "Last Updated",
7-
"localUnitDetailFocalPerson": "Focal Person",
8-
"localUnitTooltipMoreDetails": "More Details",
9-
"localUnitLegendLocalUnitTitle": "Local Units",
10-
"localUnitLocalUnitType": "Local Unit Type",
11-
"localUnitHealthFacilityType": "Health Facility Type",
12-
"localUnitLegendHealthCareTitle": "Health Care Local Units",
13-
"presentationModeButton":"Presentation Mode",
5+
"localUnitDetailPhoneNumber": "Phone number",
6+
"localUnitDetailLastUpdate": "Last updated",
7+
"localUnitDetailFocalPerson": "Focal person",
8+
"localUnitTooltipMoreDetails": "More details",
9+
"localUnitLocalUnitType": "Local unit types",
10+
"localUnitHealthFacilityType": "Health facility types",
11+
"presentationModeButton":"Presentation mode",
1412
"localUnitDetailEmail": "Email"
1513
}
1614
}

app/src/views/CountryNsOverviewContextAndStructure/NationalSocietyLocalUnits/LocalUnitsMap/index.tsx

Lines changed: 56 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@ import {
1212
import {
1313
Button,
1414
Container,
15-
LegendItem,
15+
Legend,
1616
List,
1717
TextOutput,
1818
} from '@ifrc-go/ui';
1919
import { useTranslation } from '@ifrc-go/ui/hooks';
20-
import { sumSafe } from '@ifrc-go/ui/utils';
20+
import {
21+
numericIdSelector,
22+
stringNameSelector,
23+
sumSafe,
24+
} from '@ifrc-go/ui/utils';
2125
import {
2226
isDefined,
2327
isNotDefined,
@@ -30,10 +34,7 @@ import {
3034
MapSource,
3135
} from '@togglecorp/re-map';
3236
import getBbox from '@turf/bbox';
33-
import type {
34-
CircleLayer,
35-
SymbolLayer,
36-
} from 'mapbox-gl';
37+
import { type SymbolLayer } from 'mapbox-gl';
3738

3839
import ActiveCountryBaseMapLayer from '#components/domain/ActiveCountryBaseMapLayer';
3940
import BaseMap from '#components/domain/BaseMap';
@@ -44,8 +45,7 @@ import useAuth from '#hooks/domain/useAuth';
4445
import useFilterState from '#hooks/useFilterState';
4546
import { getFirstTruthyString } from '#utils/common';
4647
import {
47-
COLOR_DARK_GREY,
48-
COLOR_PRIMARY_BLUE,
48+
COLOR_PRIMARY_RED,
4949
COLOR_WHITE,
5050
DEFAULT_MAP_PADDING,
5151
DURATION_MAP_ZOOM,
@@ -63,13 +63,22 @@ import {
6363
VALIDATED,
6464
} from '../common';
6565
import Filters, { FilterValue } from '../Filters';
66+
import { TYPE_HEALTH_CARE } from '../LocalUnitsFormModal/LocalUnitsForm/schema';
6667

6768
import i18n from './i18n.json';
6869
import styles from './styles.module.css';
6970

7071
const LOCAL_UNIT_ICON_KEY = 'local-units';
7172
const HEALTHCARE_ICON_KEY = 'healthcare';
7273

74+
function iconSourceSelector({ image_url }: { image_url?: string | undefined }) {
75+
return image_url;
76+
}
77+
78+
function primaryRedColorSelector() {
79+
return COLOR_PRIMARY_RED;
80+
}
81+
7382
const localUnitIconLayerOptions: Omit<SymbolLayer, 'id'> = {
7483
layout: {
7584
visibility: 'visible',
@@ -211,35 +220,6 @@ function LocalUnitsMap(props: Props) {
211220
[loadedIcons, localUnitsOptions],
212221
);
213222

214-
const localUnitPointLayerOptions: Omit<CircleLayer, 'id'> = useMemo(() => ({
215-
layout: {
216-
visibility: 'visible',
217-
},
218-
type: 'circle',
219-
paint: {
220-
'circle-radius': 12,
221-
'circle-color': isDefined(localUnitsOptions) ? [
222-
'match',
223-
['get', 'type'],
224-
...localUnitsOptions.type.flatMap(
225-
({ code, colour }) => [code, colour],
226-
),
227-
COLOR_DARK_GREY,
228-
] : COLOR_DARK_GREY,
229-
'circle-opacity': 0.6,
230-
'circle-stroke-color': isDefined(localUnitsOptions) ? [
231-
'match',
232-
['get', 'type'],
233-
...localUnitsOptions.type.flatMap(
234-
({ code, colour }) => [code, colour],
235-
),
236-
COLOR_DARK_GREY,
237-
] : COLOR_DARK_GREY,
238-
'circle-stroke-width': 1,
239-
'circle-stroke-opacity': 1,
240-
},
241-
}), [localUnitsOptions]);
242-
243223
const countryBounds = useMemo(() => (
244224
(countryResponse && countryResponse.bbox)
245225
? getBbox(countryResponse.bbox)
@@ -295,8 +275,9 @@ function LocalUnitsMap(props: Props) {
295275
properties: {
296276
id: localUnit.id,
297277
localUnitId: localUnit.id,
278+
radius: 12,
298279
type: localUnit.type,
299-
subType: isDefined(localUnit.health_details)
280+
subType: localUnit.type === TYPE_HEALTH_CARE
300281
? localUnit.health_details.health_facility_type
301282
: undefined,
302283
iconKey: isDefined(localUnit.health_details)
@@ -378,8 +359,8 @@ function LocalUnitsMap(props: Props) {
378359
>
379360
<div className={styles.mapContainerWithContactDetails}>
380361
<BaseMap
381-
mapStyle={localUnitMapStyle}
382362
withoutLabel
363+
mapStyle={localUnitMapStyle}
383364
baseLayers={(
384365
<ActiveCountryBaseMapLayer
385366
activeCountryIso3={countryResponse?.iso3}
@@ -390,6 +371,13 @@ function LocalUnitsMap(props: Props) {
390371
<MapContainerWithDisclaimer
391372
className={styles.mapContainer}
392373
/>
374+
{countryBounds && (
375+
<MapBounds
376+
duration={DURATION_MAP_ZOOM}
377+
padding={DEFAULT_MAP_PADDING}
378+
bounds={countryBounds}
379+
/>
380+
)}
393381
{localUnitsOptions?.type.map(
394382
(typeOption) => (
395383
<MapImage
@@ -419,7 +407,13 @@ function LocalUnitsMap(props: Props) {
419407
>
420408
<MapLayer
421409
layerKey="point"
422-
layerOptions={localUnitPointLayerOptions}
410+
layerOptions={{
411+
type: 'circle',
412+
paint: {
413+
'circle-radius': ['get', 'radius'],
414+
'circle-color': COLOR_PRIMARY_RED,
415+
},
416+
}}
423417
onClick={handlePointClick}
424418
/>
425419
{allIconsLoaded && (
@@ -429,11 +423,6 @@ function LocalUnitsMap(props: Props) {
429423
/>
430424
)}
431425
</MapSource>
432-
<MapBounds
433-
duration={DURATION_MAP_ZOOM}
434-
padding={DEFAULT_MAP_PADDING}
435-
bounds={countryBounds}
436-
/>
437426
{isDefined(clickedPointProperties) && clickedPointProperties.lngLat && (
438427
<MapPopup
439428
popupClassName={styles.mapPopup}
@@ -544,50 +533,28 @@ function LocalUnitsMap(props: Props) {
544533
</Container>
545534
)}
546535
</div>
547-
{
548-
isDefined(localUnitsOptions) && (
549-
<Container
550-
contentViewType="vertical"
551-
spacing="comfortable"
552-
>
553-
<Container
554-
heading={strings.localUnitLegendLocalUnitTitle}
555-
headingLevel={4}
556-
contentViewType="grid"
557-
numPreferredGridContentColumns={5}
558-
spacing="compact"
559-
>
560-
{localUnitsOptions?.type.map((legendItem) => (
561-
<LegendItem
562-
key={legendItem.id}
563-
iconSrc={legendItem.image_url}
564-
iconClassName={styles.legendIcon}
565-
color={legendItem.colour ?? COLOR_DARK_GREY}
566-
label={legendItem.name}
567-
/>
568-
))}
569-
</Container>
570-
<Container
571-
heading={strings.localUnitLegendHealthCareTitle}
572-
headingLevel={5}
573-
contentViewType="grid"
574-
numPreferredGridContentColumns={5}
575-
spacing="compact"
576-
>
577-
{localUnitsOptions?.health_facility_type.map((legendItem) => (
578-
<LegendItem
579-
key={legendItem.id}
580-
// FIXME: use color from server
581-
color={COLOR_PRIMARY_BLUE}
582-
iconSrc={legendItem.image_url}
583-
iconClassName={styles.legendIcon}
584-
label={legendItem.name}
585-
/>
586-
))}
587-
</Container>
588-
</Container>
589-
)
590-
}
536+
{filter.type === TYPE_HEALTH_CARE && (
537+
<Legend
538+
label={strings.localUnitHealthFacilityType}
539+
items={localUnitsOptions?.health_facility_type}
540+
keySelector={numericIdSelector}
541+
labelSelector={stringNameSelector}
542+
iconSrcSelector={iconSourceSelector}
543+
colorSelector={primaryRedColorSelector}
544+
iconElementClassName={styles.legendIcon}
545+
/>
546+
)}
547+
{filter.type !== TYPE_HEALTH_CARE && (
548+
<Legend
549+
label={strings.localUnitLocalUnitType}
550+
items={localUnitsOptions?.type}
551+
keySelector={numericIdSelector}
552+
labelSelector={stringNameSelector}
553+
iconSrcSelector={iconSourceSelector}
554+
colorSelector={primaryRedColorSelector}
555+
iconElementClassName={styles.legendIcon}
556+
/>
557+
)}
591558
</Container>
592559
);
593560
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useCallback } from 'react';
2+
import {
3+
_cs,
4+
isDefined,
5+
} from '@togglecorp/fujs';
6+
7+
import LegendItem, { Props as LegendItemProps } from '#components/LegendItem';
8+
import RawList from '#components/RawList';
9+
10+
import styles from './styles.module.css';
11+
12+
export interface Props<ITEM> {
13+
className?: string;
14+
label?: React.ReactNode;
15+
items: ITEM[] | undefined | null;
16+
keySelector: (item: ITEM) => React.Key;
17+
colorSelector?: (item: ITEM) => string | undefined;
18+
labelSelector?: (item: ITEM) => React.ReactNode;
19+
iconSrcSelector?: (item: ITEM) => string | undefined;
20+
colorElementClassName?: string;
21+
iconElementClassName?: string;
22+
itemClassName?: string;
23+
}
24+
25+
function Legend<ITEM>(props: Props<ITEM>) {
26+
const {
27+
className,
28+
label,
29+
items,
30+
keySelector,
31+
colorSelector,
32+
labelSelector,
33+
iconSrcSelector,
34+
itemClassName,
35+
iconElementClassName,
36+
colorElementClassName,
37+
} = props;
38+
39+
const legendItemRendererParams = useCallback(
40+
(_: React.Key, item: ITEM): LegendItemProps => ({
41+
className: itemClassName,
42+
colorClassName: colorElementClassName,
43+
label: labelSelector?.(item),
44+
iconSrc: iconSrcSelector?.(item),
45+
color: colorSelector?.(item),
46+
iconClassName: iconElementClassName,
47+
}),
48+
[
49+
colorElementClassName,
50+
iconElementClassName,
51+
labelSelector,
52+
iconSrcSelector,
53+
colorSelector,
54+
itemClassName,
55+
],
56+
);
57+
58+
return (
59+
<div className={_cs(styles.legend, className)}>
60+
{isDefined(label) && (
61+
<div className={styles.label}>
62+
{label}
63+
</div>
64+
)}
65+
<div className={styles.legendItems}>
66+
<RawList
67+
data={items}
68+
renderer={LegendItem}
69+
keySelector={keySelector}
70+
rendererParams={legendItemRendererParams}
71+
/>
72+
</div>
73+
</div>
74+
);
75+
}
76+
77+
export default Legend;

0 commit comments

Comments
 (0)