Skip to content

Commit 9d33095

Browse files
committed
feat(country-risk-watch): add JBA and ARC as risk event sources
1 parent 4fd6c28 commit 9d33095

22 files changed

Lines changed: 1790 additions & 26 deletions

File tree

app/src/components/GoMapContainer/index.tsx

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ interface Props {
5151
onPresentationModeChange?: (newPresentationMode: boolean) => void;
5252
children?: React.ReactNode;
5353
withFullHeight?: boolean;
54+
layerSelection?: React.ReactNode;
5455
}
5556

5657
function GoMapContainer(props: Props) {
@@ -65,6 +66,7 @@ function GoMapContainer(props: Props) {
6566
onPresentationModeChange,
6667
children,
6768
withFullHeight,
69+
layerSelection,
6870
} = props;
6971

7072
const strings = useTranslation(i18n);
@@ -311,15 +313,23 @@ function GoMapContainer(props: Props) {
311313
</ListView>
312314
)}
313315
/>
314-
{withPresentationMode && !printMode && !presentationMode && (
315-
<Button
316-
className={styles.presentationModeButton}
317-
name={undefined}
318-
before={<ArtboardLineIcon />}
319-
onClick={enterPresentationMode}
316+
{(withPresentationMode || layerSelection) && (
317+
<ListView
318+
layout="block"
319+
className={styles.topLeftActions}
320320
>
321-
{strings.presentationModeButtonLabel}
322-
</Button>
321+
{withPresentationMode && !printMode && !presentationMode && (
322+
<Button
323+
className={styles.presentationModeButton}
324+
name={undefined}
325+
before={<ArtboardLineIcon />}
326+
onClick={enterPresentationMode}
327+
>
328+
{strings.presentationModeButtonLabel}
329+
</Button>
330+
)}
331+
{layerSelection}
332+
</ListView>
323333
)}
324334
{!printMode && !presentationMode && !withoutDownloadButton && (
325335
<RawButton

app/src/components/GoMapContainer/styles.module.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
font-size: var(--go-ui-font-size-sm);
3333
}
3434

35-
.presentation-mode-button {
35+
.top-left-actions {
3636
position: absolute;
3737
top: var(--go-ui-spacing-sm);
3838
left: var(--go-ui-spacing-sm);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import {
2+
Label,
3+
ListView,
4+
} from '@ifrc-go/ui';
5+
import { _cs } from '@togglecorp/fujs';
6+
7+
import styles from './styles.module.css';
8+
9+
export interface StepGradientBarStep {
10+
color: string;
11+
label: React.ReactNode;
12+
}
13+
14+
interface Props {
15+
className?: string;
16+
steps: StepGradientBarStep[];
17+
}
18+
19+
function StepGradientBar(props: Props) {
20+
const {
21+
className,
22+
steps,
23+
} = props;
24+
25+
return (
26+
<ListView
27+
className={_cs(styles.stepGradientBar, className)}
28+
spacing="none"
29+
>
30+
{steps.map((step, index) => (
31+
<div
32+
// eslint-disable-next-line react/no-array-index-key
33+
key={index}
34+
className={styles.step}
35+
>
36+
<div
37+
className={styles.swatch}
38+
style={{ backgroundColor: step.color }}
39+
/>
40+
<Label textSize="sm">
41+
{step.label}
42+
</Label>
43+
</div>
44+
))}
45+
</ListView>
46+
);
47+
}
48+
49+
export default StepGradientBar;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.step-gradient-bar {
2+
width: min(30cqi, 10rem);
3+
4+
.step {
5+
flex-grow: 1;
6+
7+
.swatch {
8+
height: 0.5rem;
9+
}
10+
}
11+
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import {
2+
COLOR_BLUE_GRADIENT_5,
3+
COLOR_RED_GRADIENT_5,
4+
} from '#utils/constants';
5+
6+
// Recipe table for HDX-sourced background layers rendered as admin2 choropleths.
7+
// CSVs are admin2-keyed via the `ADM2_PCODE` column (HDX convention) and joined
8+
// against the Mapbox `go-admin2-${iso3}-staging` tileset's feature `code`.
9+
//
10+
// One CSV may expose multiple metrics. Each metric becomes a flat option in the
11+
// layer-selection radio, labelled "{dataset.label} — {metric.label}".
12+
//
13+
// Order is intentional (semantic grouping), not alphabetical:
14+
// 1. hazard inputs: flood_exposure, vulnerability
15+
// 2. capacity: facilities, access
16+
// 3. context: demographics, rural_population
17+
//
18+
// Unknown HDX datasets returned by the backend are silently skipped.
19+
20+
export type HdxColorRamp = readonly string[];
21+
22+
export interface HdxMetricRecipe {
23+
column: string;
24+
label: string;
25+
// 'percent' assumes the source value is already on a 0-100 scale.
26+
format?: 'number' | 'percent';
27+
}
28+
29+
export interface HdxLayerRecipe {
30+
datasetName: string;
31+
label: string;
32+
joinColumn: string;
33+
colorRamp: HdxColorRamp;
34+
metrics: HdxMetricRecipe[];
35+
}
36+
37+
const ADM2_JOIN = 'ADM2_PCODE';
38+
39+
export const HDX_LAYER_RECIPES: HdxLayerRecipe[] = [
40+
{
41+
datasetName: 'MWI_ADM2_flood_exposure',
42+
label: 'Flood exposure (RP100)',
43+
joinColumn: ADM2_JOIN,
44+
colorRamp: COLOR_RED_GRADIENT_5,
45+
metrics: [
46+
{ column: 'RP100_pop_u15_30cm', label: 'Under-15 population exposed' },
47+
{ column: 'RP100_female_pop_30cm', label: 'Female population exposed' },
48+
{ column: 'RP100_elderly_30cm', label: 'Elderly population exposed' },
49+
{ column: 'RP100_hospitals_30cm_pct', label: 'Hospitals at risk (%)', format: 'percent' },
50+
{ column: 'RP100_education_30cm_pct', label: 'Education facilities at risk (%)', format: 'percent' },
51+
],
52+
},
53+
{
54+
datasetName: 'MWI_ADM2_vulnerability',
55+
label: 'Vulnerability',
56+
joinColumn: ADM2_JOIN,
57+
colorRamp: COLOR_RED_GRADIENT_5,
58+
metrics: [
59+
{ column: 'pop_u15', label: 'Under-15 population' },
60+
{ column: 'female_pop', label: 'Female population' },
61+
{ column: 'elderly', label: 'Elderly population' },
62+
{ column: 'rural_pop_perc', label: 'Rural population (%)', format: 'percent' },
63+
],
64+
},
65+
{
66+
datasetName: 'MWI_ADM2_facilities',
67+
label: 'Facilities',
68+
joinColumn: ADM2_JOIN,
69+
colorRamp: COLOR_BLUE_GRADIENT_5,
70+
metrics: [
71+
{ column: 'hospitals_count', label: 'Hospitals' },
72+
],
73+
},
74+
{
75+
datasetName: 'MWI_ADM2_access',
76+
label: 'Access',
77+
joinColumn: ADM2_JOIN,
78+
colorRamp: COLOR_BLUE_GRADIENT_5,
79+
metrics: [
80+
{ column: 'access_pop_hospitals_30min', label: 'Pop. within 30 min of hospital' },
81+
{ column: 'access_pop_primary_healthcare_30min', label: 'Pop. within 30 min of primary care' },
82+
{ column: 'access_pop_education_5km', label: 'Pop. within 5 km of education' },
83+
],
84+
},
85+
{
86+
datasetName: 'MWI_ADM2_demographics',
87+
label: 'Demographics',
88+
joinColumn: ADM2_JOIN,
89+
colorRamp: COLOR_BLUE_GRADIENT_5,
90+
metrics: [
91+
{ column: 'pop_u15', label: 'Under-15 population' },
92+
{ column: 'elderly', label: 'Elderly population' },
93+
{ column: 'female_pop', label: 'Female population' },
94+
],
95+
},
96+
{
97+
datasetName: 'MWI_ADM2_rural_population',
98+
label: 'Rural population',
99+
joinColumn: ADM2_JOIN,
100+
colorRamp: COLOR_BLUE_GRADIENT_5,
101+
metrics: [
102+
{ column: 'rural_pop_perc', label: 'Rural (%)', format: 'percent' },
103+
{ column: 'pop_u15_rural', label: 'Rural under-15 population' },
104+
],
105+
},
106+
];
107+
108+
export type HdxOptionKey = string;
109+
110+
export interface HdxOption {
111+
key: HdxOptionKey;
112+
label: string;
113+
recipe: HdxLayerRecipe;
114+
metric: HdxMetricRecipe;
115+
}
116+
117+
// Build a flat list of `{datasetName} — {metricColumn}` options from a list of
118+
// known dataset names returned by the backend. Datasets not in the recipe table
119+
// are dropped.
120+
export function buildHdxOptions(availableDatasetNames: Set<string>): HdxOption[] {
121+
return HDX_LAYER_RECIPES
122+
.filter((recipe) => availableDatasetNames.has(recipe.datasetName))
123+
.flatMap((recipe) => recipe.metrics.map((metric) => ({
124+
key: `${recipe.datasetName}__${metric.column}`,
125+
label: `${recipe.label}${metric.label}`,
126+
recipe,
127+
metric,
128+
})));
129+
}

0 commit comments

Comments
 (0)