From cfdafd4e50004da78d19c9b6da96ab652ddc3a13 Mon Sep 17 00:00:00 2001 From: HarryMytilinaios Date: Thu, 13 Jul 2023 17:00:25 +0300 Subject: [PATCH] Generating configurations for reports creation; COUNTRY=cambodia (#892) Co-authored-by: Eric Boucher Co-authored-by: Amit W --- .../Common/ReportDialog/ReportDocLegend.tsx | 2 +- .../components/Common/ReportDialog/index.tsx | 51 ++-- .../Common/ReportDialog/reportDoc.tsx | 219 +++++++++--------- .../components/Common/ReportDialog/types.ts | 8 +- .../ExposureAnalysisActions/index.tsx | 35 ++- .../SwitchItem/ExposureAnalysisOption.tsx | 4 + .../MenuSwitch/SwitchItem/index.tsx | 14 +- frontend/src/config/afghanistan/index.ts | 2 + frontend/src/config/cambodia/index.ts | 2 + frontend/src/config/cambodia/reports.json | 46 ++++ frontend/src/config/cameroon/index.ts | 2 + frontend/src/config/colombia/index.ts | 2 + frontend/src/config/cuba/index.ts | 2 + frontend/src/config/ecuador/index.ts | 2 + frontend/src/config/global/index.ts | 2 + frontend/src/config/index.ts | 5 + frontend/src/config/indonesia/index.ts | 9 +- frontend/src/config/jordan/index.ts | 2 + frontend/src/config/kyrgyzstan/index.ts | 2 + frontend/src/config/mongolia/index.ts | 2 + frontend/src/config/mozambique/index.ts | 4 +- frontend/src/config/mozambique/reports.json | 54 +++++ frontend/src/config/myanmar/index.ts | 2 + frontend/src/config/myanmar/reports.json | 90 +++++++ frontend/src/config/namibia/index.ts | 2 + frontend/src/config/nigeria/index.ts | 2 + frontend/src/config/rbd/index.ts | 2 + frontend/src/config/sierraleone/index.ts | 2 + frontend/src/config/southsudan/index.ts | 2 + frontend/src/config/srilanka/index.ts | 2 + frontend/src/config/tajikistan/index.ts | 2 + frontend/src/config/types.ts | 38 ++- frontend/src/config/ukraine/index.ts | 2 + frontend/src/config/utils.ts | 61 ++++- frontend/src/config/zimbabwe/index.ts | 2 + .../src/context/analysisResultStateSlice.ts | 10 + 36 files changed, 514 insertions(+), 176 deletions(-) create mode 100644 frontend/src/config/cambodia/reports.json create mode 100644 frontend/src/config/mozambique/reports.json create mode 100644 frontend/src/config/myanmar/reports.json diff --git a/frontend/src/components/Common/ReportDialog/ReportDocLegend.tsx b/frontend/src/components/Common/ReportDialog/ReportDocLegend.tsx index d135dae37..59225d961 100644 --- a/frontend/src/components/Common/ReportDialog/ReportDocLegend.tsx +++ b/frontend/src/components/Common/ReportDialog/ReportDocLegend.tsx @@ -49,7 +49,7 @@ const ReportDocLegend = memo(({ theme, title, definition }: LegendProps) => { const renderedDefinitions = useMemo(() => { return definition.map(item => { return ( - + {item.value} diff --git a/frontend/src/components/Common/ReportDialog/index.tsx b/frontend/src/components/Common/ReportDialog/index.tsx index 1c6862c96..d3ee6776c 100644 --- a/frontend/src/components/Common/ReportDialog/index.tsx +++ b/frontend/src/components/Common/ReportDialog/index.tsx @@ -27,8 +27,8 @@ import { } from 'context/analysisResultStateSlice'; import { Column, ExposedPopulationResult } from 'utils/analysis-utils'; import LoadingBlinkingDots from 'components/Common/LoadingBlinkingDots'; +import { ReportType } from 'config/types'; import ReportDoc from './reportDoc'; -import { ReportType } from './types'; type Format = 'png' | 'jpeg'; @@ -36,7 +36,7 @@ const ReportDialog = memo( ({ classes, open, - reportType, + reportConfig, handleClose, tableData, columns, @@ -50,7 +50,7 @@ const ReportDialog = memo( analysisResultSelector, ) as ExposedPopulationResult; - const eventDate = useMemo(() => { + const reportDate = useMemo(() => { return analysisResult?.date ? moment(new Date(analysisResult?.date)).format('YYYY-MM-DD') : ''; @@ -106,14 +106,9 @@ const ReportDialog = memo( t={t} exposureLegendDefinition={analysisResult?.legend ?? []} theme={theme} - reportType={reportType} - tableName="Population Exposure" tableShowTotal - eventName={ - reportType === ReportType.Storm - ? `Storm Report (${eventDate})` - : `Flood Report (${eventDate})` - } + reportTitle={`${t(reportConfig.title)} ${reportDate}`} + reportConfig={reportConfig} mapImage={mapImage} tableData={tableData} columns={columns} @@ -124,9 +119,9 @@ const ReportDialog = memo( }, [ analysisResult, columns, - eventDate, mapImage, - reportType, + reportConfig, + reportDate, t, tableData, theme, @@ -177,14 +172,9 @@ const ReportDialog = memo( t={t} exposureLegendDefinition={analysisResult?.legend ?? []} theme={theme} - reportType={reportType} - tableName="Population Exposure" + reportTitle={`${t(reportConfig.title)} ${reportDate}`} + reportConfig={reportConfig} tableShowTotal - eventName={ - reportType === ReportType.Storm - ? `Storm Report (${eventDate})` - : `Flood Report (${eventDate})` - } mapImage={mapImage} tableData={tableData} columns={columns} @@ -200,22 +190,21 @@ const ReportDialog = memo( analysisResult, classes.actionButton, columns, - eventDate, getPDFName, mapImage, renderedLoadingButtonText, - reportType, + reportConfig, + reportDate, t, tableData, theme, ]); - // The report type text - const reportTypeText = useMemo(() => { - return reportType === ReportType.Storm - ? 'Storm impact Report' - : 'Flood Report'; - }, [reportType]); + const renderedSignatureText = useMemo(() => { + return reportConfig?.signatureText + ? t(reportConfig.signatureText) + : t('PRISM automated report'); + }, [reportConfig, t]); return ( - {t(reportTypeText)} + {t(reportConfig.title)} - - {t('P R I S M automated report')} - + {renderedSignatureText} {renderedDownloadPdfButton} @@ -312,7 +299,7 @@ const styles = (theme: Theme) => export interface ReportProps extends WithStyles { open: boolean; - reportType: ReportType; + reportConfig: ReportType; handleClose: () => void; tableData: AnalysisTableRow[]; columns: Column[]; diff --git a/frontend/src/components/Common/ReportDialog/reportDoc.tsx b/frontend/src/components/Common/ReportDialog/reportDoc.tsx index 192519a9a..bca156ea6 100644 --- a/frontend/src/components/Common/ReportDialog/reportDoc.tsx +++ b/frontend/src/components/Common/ReportDialog/reportDoc.tsx @@ -1,19 +1,19 @@ import React, { memo, useMemo } from 'react'; import { - Page, Document, + Image, + Page, StyleSheet, - View, Text, - Image, + View, } from '@react-pdf/renderer'; import { Theme } from '@material-ui/core'; import { TableRow as AnalysisTableRow } from 'context/analysisResultStateSlice'; import { getLegendItemLabel } from 'components/MapView/utils'; -import { LegendDefinition } from 'config/types'; +import { LegendDefinition, ReportType } from 'config/types'; import { TFunction } from 'utils/data-utils'; import { Column } from 'utils/analysis-utils'; -import { PDFLegendDefinition, ReportType } from './types'; +import { PDFLegendDefinition } from './types'; import ReportDocLegend from './ReportDocLegend'; import ReportDocTable from './ReportDocTable'; @@ -77,12 +77,11 @@ const makeStyles = (theme: Theme) => const ReportDoc = memo( ({ theme, - reportType, mapImage, - tableName, tableRowsNum, tableShowTotal, - eventName, + reportTitle, + reportConfig, exposureLegendDefinition, t, tableData, @@ -94,6 +93,12 @@ const ReportDoc = memo( return new Date().toUTCString(); }, []); + const tableName = useMemo(() => { + return reportConfig?.tableName + ? reportConfig?.tableName + : 'Population Exposure'; + }, [reportConfig]); + const showRowTotal = useMemo(() => { return columns.length > 2; }, [columns.length]); @@ -109,63 +114,39 @@ const ReportDoc = memo( }, [tableData, tableRowsNum, tableShowTotal]); const areasLegendDefinition: PDFLegendDefinition[] = useMemo(() => { - return [ - { - value: 'Province', - style: [styles.dash, { backgroundColor: '#000000' }], - }, - { - value: 'District', - style: [styles.dash, { backgroundColor: '#999797' }], - }, - { - value: 'Township', - style: [styles.dash, { backgroundColor: '#D8D6D6' }], - }, - ]; - }, [styles.dash]); + return reportConfig.areasLegendDefinition.items.map(areaDefinition => { + return { + value: t(areaDefinition.title), + style: [styles.dash, { backgroundColor: areaDefinition.color }], + }; + }); + }, [reportConfig.areasLegendDefinition.items, styles.dash, t]); - const stormWindBuffersLegendDefinition: PDFLegendDefinition[] = useMemo(() => { - return [ - { - value: 'Uncertainty Cones', - style: [ - styles.borderedBox, - { backgroundColor: '#ffffff', borderColor: '#b8b1b1' }, - ], - }, - { - value: 'Wind Buffer 60 km/h', - style: [ - styles.borderedBox, - { backgroundColor: '#fffcf1', borderColor: '#f7e705' }, - ], - }, - { - value: 'Wind Buffer 90 km/h', - style: [ - styles.borderedBox, - { backgroundColor: '#ffeed8', borderColor: '#f99408' }, - ], + const typeLegendDefinition: PDFLegendDefinition[] = useMemo(() => { + return reportConfig.typeLegendDefinition.items.map( + typeLegendDefinitionItem => { + return { + value: t(typeLegendDefinitionItem.title), + style: [ + typeLegendDefinitionItem?.border + ? styles.borderedBox + : styles.box, + { + backgroundColor: typeLegendDefinitionItem.color, + ...(typeLegendDefinitionItem?.border && { + borderColor: typeLegendDefinitionItem.border, + }), + }, + ], + }; }, - { - value: 'Wind Buffer 120 km/h', - style: [ - styles.borderedBox, - { backgroundColor: '#fcd4ce', borderColor: '#f90c08' }, - ], - }, - ]; - }, [styles.borderedBox]); - - const floodsLegendDefinition: PDFLegendDefinition[] = useMemo(() => { - return [ - { - value: 'flooded', - style: [styles.box, { backgroundColor: '#a50f15' }], - }, - ]; - }, [styles.box]); + ); + }, [ + reportConfig.typeLegendDefinition.items, + styles.borderedBox, + styles.box, + t, + ]); const populationExposureLegendDefinition: PDFLegendDefinition[] = useMemo(() => { return exposureLegendDefinition.map(item => ({ @@ -174,41 +155,72 @@ const ReportDoc = memo( })); }, [exposureLegendDefinition, styles.box, t]); - // The rendered report doc legend - const renderedReportDocLegend = useMemo(() => { - if (reportType === ReportType.Storm) { - return ( - - ); + const renderedMapFooterText = useMemo(() => { + if (!reportConfig?.mapFooterText) { + return null; } return ( - + + {reportConfig.mapFooterText} + + ); + }, [reportConfig, theme.pdf]); + + const renderedMapFooterSubText = useMemo(() => { + if (!reportConfig?.mapFooterSubText) { + return null; + } + return ( + + {reportConfig.mapFooterSubText} + + ); + }, [reportConfig, theme.pdf]); + + const renderedSourcesView = useMemo(() => { + if (!reportConfig?.mapFooterText && !reportConfig?.mapFooterSubText) { + return null; + } + return ( + + {renderedMapFooterText} + {renderedMapFooterSubText} + ); }, [ - floodsLegendDefinition, - reportType, - stormWindBuffersLegendDefinition, - theme, + renderedMapFooterSubText, + renderedMapFooterText, + reportConfig, + styles.section, ]); + const renderedSubText = useMemo(() => { + if (!reportConfig?.subText) { + return null; + } + return {t(reportConfig.subText)}; + }, [reportConfig, styles.subText, t]); + + const renderedSignatureText = useMemo(() => { + return reportConfig?.signatureText + ? t(reportConfig.signatureText) + : t('PRISM automated report'); + }, [reportConfig, t]); + return ( - Event name: {eventName} - Publication date: {date} - - This is an automated report. - Information should be treated as preliminary + {reportTitle} + + {t(reportConfig.publicationDateLabel)}: {date} + {renderedSubText} @@ -216,33 +228,21 @@ const ReportDoc = memo( - {renderedReportDocLegend} + - - - Sources WFP, UNGIWG, OCHA, GAUL, USGS, NASA, UCSB - - - The designations employed and the presentation of material in the - map(s) do not imply the expression of any opinion on the part of - WFP concerning the legal or constitutional status of any country, - territory, city or sea, or concerning the delimitation of its - frontiers or boundaries. - - + {renderedSourcesView} - P R I S M automated report + {renderedSignatureText} @@ -266,12 +266,11 @@ const ReportDoc = memo( interface ReportDocProps { theme: Theme; - reportType: ReportType; mapImage: string; - tableName: string; tableRowsNum?: number; + reportTitle: string; + reportConfig: ReportType; tableShowTotal: boolean; - eventName: string; exposureLegendDefinition: LegendDefinition; t: TFunction; tableData: AnalysisTableRow[]; diff --git a/frontend/src/components/Common/ReportDialog/types.ts b/frontend/src/components/Common/ReportDialog/types.ts index 66ad421e4..8ce499c0d 100644 --- a/frontend/src/components/Common/ReportDialog/types.ts +++ b/frontend/src/components/Common/ReportDialog/types.ts @@ -1,12 +1,8 @@ import { Style } from '@react-pdf/types'; - -export enum ReportType { - Storm, - Flood, -} +import { TFunctionResult } from 'i18next'; export interface PDFLegendDefinition { - value: string | number; + value: string | number | TFunctionResult; style: Style | Style[]; } diff --git a/frontend/src/components/MapView/LeftPanel/AnalysisPanel/ExposureAnalysisActions/index.tsx b/frontend/src/components/MapView/LeftPanel/AnalysisPanel/ExposureAnalysisActions/index.tsx index 5f2d3e211..0f8337301 100644 --- a/frontend/src/components/MapView/LeftPanel/AnalysisPanel/ExposureAnalysisActions/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/AnalysisPanel/ExposureAnalysisActions/index.tsx @@ -12,14 +12,14 @@ import { useSelector } from 'react-redux'; import { downloadToFile } from 'components/MapView/utils'; import { useSafeTranslation } from 'i18n'; import { + exposureLayerIdSelector, getCurrentDefinition, TableRow, TableRow as AnalysisTableRow, } from 'context/analysisResultStateSlice'; -import { layersSelector } from 'context/mapStateSlice/selectors'; -import { ReportType } from 'components/Common/ReportDialog/types'; import ReportDialog from 'components/Common/ReportDialog'; import { Column, quoteAndEscapeCell } from 'utils/analysis-utils'; +import { ReportsDefinitions } from 'config/utils'; function ExposureAnalysisActions({ analysisButton, @@ -31,14 +31,10 @@ function ExposureAnalysisActions({ // only display local names if local language is selected, otherwise display english name const { t } = useSafeTranslation(); const analysisDefinition = useSelector(getCurrentDefinition); - const selectedLayers = useSelector(layersSelector); + const exposureLayerId = useSelector(exposureLayerIdSelector); const [openReport, setOpenReport] = useState(false); - const isShowingStormData = useMemo(() => { - return selectedLayers.some(({ id }) => id === 'adamts_buffers'); - }, [selectedLayers]); - const getCellValue = useCallback((value: string | number, column: Column) => { if (column.format && typeof value === 'number') { return quoteAndEscapeCell(column.format(value)); @@ -58,6 +54,19 @@ function ExposureAnalysisActions({ ); }, [columns]); + const reportConfig = useMemo(() => { + // We use find here because exposure reports and layers have 1 - 1 sync. + // TODO Future enhancement if exposure reports are more than one for specific layer + const foundReportKeyBasedOnLayerId = Object.keys(ReportsDefinitions).find( + reportDefinitionKey => { + return ( + ReportsDefinitions[reportDefinitionKey].layerId === exposureLayerId + ); + }, + ); + return ReportsDefinitions[foundReportKeyBasedOnLayerId as string]; + }, [exposureLayerId]); + const tableDataRowsToRenderCsv = useMemo(() => { return tableData.map((tableRowData: TableRow) => { return columns.reduce( @@ -98,6 +107,12 @@ function ExposureAnalysisActions({ [analysisCsvData, analysisDefinition], ); + const handleToggleReport = (toggle: boolean) => { + return () => { + setOpenReport(toggle); + }; + }; + return ( <> )} - setOpenReport(false)} - reportType={isShowingStormData ? ReportType.Storm : ReportType.Flood} + handleClose={handleToggleReport(false)} + reportConfig={reportConfig} tableData={tableData} columns={columns} /> diff --git a/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/ExposureAnalysisOption.tsx b/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/ExposureAnalysisOption.tsx index f6755acba..748bc6485 100644 --- a/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/ExposureAnalysisOption.tsx +++ b/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/ExposureAnalysisOption.tsx @@ -15,6 +15,7 @@ import { ExposedPopulationDispatchParams, requestAndStoreExposedPopulation, setCurrentDataDefinition, + setExposureLayerId, } from 'context/analysisResultStateSlice'; import { setTabValue } from 'context/leftPanelStateSlice'; import { dateRangeSelector } from 'context/mapStateSlice/selectors'; @@ -59,6 +60,9 @@ function ExposureAnalysisOption({ ...hazardLayer, }; + // Set the exposure layer id in redux so that we can have access to the reports configurations through the layer id + dispatch(setExposureLayerId(layer.id)); + dispatch(requestAndStoreExposedPopulation(params)); dispatch( setCurrentDataDefinition({ diff --git a/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/index.tsx b/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/index.tsx index e125d1bfc..d9c911f7e 100644 --- a/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/index.tsx +++ b/frontend/src/components/MapView/LeftPanel/layersPanel/MenuSwitch/SwitchItem/index.tsx @@ -22,7 +22,11 @@ import React, { } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { LayerKey, LayerType } from 'config/types'; -import { getDisplayBoundaryLayers, LayerDefinitions } from 'config/utils'; +import { + getDisplayBoundaryLayers, + LayerDefinitions, + ReportsDefinitions, +} from 'config/utils'; import { clearDataset } from 'context/datasetStateSlice'; import { removeLayer } from 'context/mapStateSlice'; import { layersSelector, mapSelector } from 'context/mapStateSlice/selectors'; @@ -268,7 +272,13 @@ const SwitchItem = memo(({ classes, layer, extent }: SwitchItemProps) => { ]); const renderedExposureAnalysisOption = useMemo(() => { - if (!exposure) { + // find if there are reports with the specific layer id in the reports.json + const foundReports = Object.keys(ReportsDefinitions).filter( + reportDefinitionKey => { + return ReportsDefinitions[reportDefinitionKey].layerId === layer.id; + }, + ); + if (!exposure || !foundReports.length) { return null; } return ( diff --git a/frontend/src/config/afghanistan/index.ts b/frontend/src/config/afghanistan/index.ts index 2a54a418d..aff0727f5 100644 --- a/frontend/src/config/afghanistan/index.ts +++ b/frontend/src/config/afghanistan/index.ts @@ -3,11 +3,13 @@ import rawLayers from './layers.json'; const rawTables = {}; const translation = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'geoBoundaries-AFG-ADM1-extended.json', }; diff --git a/frontend/src/config/cambodia/index.ts b/frontend/src/config/cambodia/index.ts index 00b01c68f..38bfc3d24 100644 --- a/frontend/src/config/cambodia/index.ts +++ b/frontend/src/config/cambodia/index.ts @@ -1,5 +1,6 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; +import rawReports from './reports.json'; import cambodiaTranslation from './translation.json'; const rawTables = {}; @@ -9,6 +10,7 @@ export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'khm_bnd_admin3_gov_ed2022.json', }; diff --git a/frontend/src/config/cambodia/reports.json b/frontend/src/config/cambodia/reports.json new file mode 100644 index 000000000..bc42d49bc --- /dev/null +++ b/frontend/src/config/cambodia/reports.json @@ -0,0 +1,46 @@ +{ + "flood_extent_report": { + "layer_id": "flood_extent", + "title": "Flood Report", + "publication_date_label": "Publication Date", + "sub_text": "This is an automated report. Information should be treated as preliminary", + "areas_legend_definition": { + "title": "Boundaries", + "items": [ + { + "title": "Province", + "color": "#000000" + }, + { + "title": "District", + "color": "#999797" + }, + { + "title": "Commune", + "color": "#D8D6D6" + } + ] + }, + "type_legend_definition": { + "title": "Potential Flooding", + "items": [ + { + "title": "Flooded", + "color": "#a50f15" + }, + { + "title": "Permanent Water", + "color": "#0571b0" + }, + { + "title": "Seasonal Water", + "color": "#92c5de" + } + ] + }, + "map_footer_text": "Sources: Flood extent from SERVIR Mekong HYDRAFloods. Population data from WorldPop", + "map_footer_sub_text": "Contains modified Copernicus Sentinel data. The designations employed and the presentation of material in the map(s) do not imply the expression of any opinion on the part of WFP concerning the legal or constitutional status of any country, territory, city or sea, or concerning the delimitation of its frontiers or boundaries.", + "table_name": "Population exposed to floods", + "signature_text": "PRISM automated report" + } +} diff --git a/frontend/src/config/cameroon/index.ts b/frontend/src/config/cameroon/index.ts index ca1150fae..e81f94111 100644 --- a/frontend/src/config/cameroon/index.ts +++ b/frontend/src/config/cameroon/index.ts @@ -3,12 +3,14 @@ import rawLayers from './layers.json'; import frTranslation from './translation.json'; const rawTables = {}; +const rawReports = {}; const translation = { fr: frTranslation }; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'cmr_admbnda_adm2_wfp_ocha.json', }; diff --git a/frontend/src/config/colombia/index.ts b/frontend/src/config/colombia/index.ts index 4edb28f57..ccc92b5ff 100644 --- a/frontend/src/config/colombia/index.ts +++ b/frontend/src/config/colombia/index.ts @@ -2,12 +2,14 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; const translation = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'col_municipios.json', }; diff --git a/frontend/src/config/cuba/index.ts b/frontend/src/config/cuba/index.ts index 71cebccba..5497ef856 100644 --- a/frontend/src/config/cuba/index.ts +++ b/frontend/src/config/cuba/index.ts @@ -3,11 +3,13 @@ import rawLayers from './layers.json'; import cubaTranslation from './translation.json'; const rawTables = {}; +const rawReports = {}; const translation = { es: cubaTranslation }; export default { appConfig, rawLayers, + rawReports, rawTables, translation, defaultBoundariesFile: 'cub_admbnda_adm2_2019.json', diff --git a/frontend/src/config/ecuador/index.ts b/frontend/src/config/ecuador/index.ts index c74862e19..8a4508390 100644 --- a/frontend/src/config/ecuador/index.ts +++ b/frontend/src/config/ecuador/index.ts @@ -3,12 +3,14 @@ import rawLayers from './layers.json'; import ecuadorTranslation from './translation.json'; const rawTables = {}; +const rawReports = {}; const translation = { es: ecuadorTranslation }; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'ecu_admbnda_adm2.json', }; diff --git a/frontend/src/config/global/index.ts b/frontend/src/config/global/index.ts index 09048d839..b24018fec 100644 --- a/frontend/src/config/global/index.ts +++ b/frontend/src/config/global/index.ts @@ -2,12 +2,14 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; const translation = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'adm0_simplified.json', }; diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts index 427c6b849..064211db4 100644 --- a/frontend/src/config/index.ts +++ b/frontend/src/config/index.ts @@ -11,6 +11,7 @@ import { indonesiaConfig, indonesiaRawLayers, indonesiaRawTables, + indonesiaRawReports, } from './indonesia'; import jordan from './jordan'; import kyrgyzstan from './kyrgyzstan'; @@ -43,6 +44,7 @@ const configMap = { appConfig: indonesiaConfig, rawLayers: indonesiaRawLayers, rawTables: indonesiaRawTables, + rawReports: indonesiaRawReports, defaultBoundariesFile: 'idn_admin_boundaries.json', }, jordan, @@ -83,11 +85,13 @@ const { defaultBoundariesFile, rawLayers, rawTables, + rawReports, }: { appConfig: Record; defaultBoundariesFile: string; rawLayers: Record; rawTables: Record; + rawReports: Record; } = configMap[safeCountry]; const translation = get(configMap[safeCountry], 'translation', {}); @@ -124,6 +128,7 @@ export { defaultBoundariesPath, rawLayers, rawTables, + rawReports, msalInstance, msalRequest, enableNavigationDropdown, diff --git a/frontend/src/config/indonesia/index.ts b/frontend/src/config/indonesia/index.ts index 0c54f3d48..7736a9201 100644 --- a/frontend/src/config/indonesia/index.ts +++ b/frontend/src/config/indonesia/index.ts @@ -2,4 +2,11 @@ import indonesiaConfig from './prism.json'; import indonesiaRawLayers from './layers.json'; import indonesiaRawTables from './tables.json'; -export { indonesiaConfig, indonesiaRawLayers, indonesiaRawTables }; +const indonesiaRawReports = {}; + +export { + indonesiaConfig, + indonesiaRawLayers, + indonesiaRawTables, + indonesiaRawReports, +}; diff --git a/frontend/src/config/jordan/index.ts b/frontend/src/config/jordan/index.ts index ebdc69c5f..8e0adf9b1 100644 --- a/frontend/src/config/jordan/index.ts +++ b/frontend/src/config/jordan/index.ts @@ -3,12 +3,14 @@ import rawLayers from './layers.json'; import jordanTranslation from './translation.json'; const rawTables = {}; +const rawReports = {}; const translation = { عربى: jordanTranslation }; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'jor_admbnda_adm2_jdos.json', }; diff --git a/frontend/src/config/kyrgyzstan/index.ts b/frontend/src/config/kyrgyzstan/index.ts index d8a87c3c4..67dadee19 100644 --- a/frontend/src/config/kyrgyzstan/index.ts +++ b/frontend/src/config/kyrgyzstan/index.ts @@ -2,12 +2,14 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; const translation = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'District_KRYG.json', }; diff --git a/frontend/src/config/mongolia/index.ts b/frontend/src/config/mongolia/index.ts index 5ae7c012a..e71aaf64e 100644 --- a/frontend/src/config/mongolia/index.ts +++ b/frontend/src/config/mongolia/index.ts @@ -3,12 +3,14 @@ import rawLayers from './layers.json'; import rawTables from './tables.json'; import mongoliaTranslation from './translation.json'; +const rawReports = {}; const translation = { mn: mongoliaTranslation }; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'admin_boundaries.json', }; diff --git a/frontend/src/config/mozambique/index.ts b/frontend/src/config/mozambique/index.ts index 45c616a07..dea291e12 100644 --- a/frontend/src/config/mozambique/index.ts +++ b/frontend/src/config/mozambique/index.ts @@ -1,14 +1,16 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; +import rawTables from './tables.json'; +import rawReports from './reports.json'; import mozambiqueTranslation from './translation.json'; -const rawTables = {}; const translation = { pt: mozambiqueTranslation }; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'moz_bnd_adm2_WFP.json', }; diff --git a/frontend/src/config/mozambique/reports.json b/frontend/src/config/mozambique/reports.json new file mode 100644 index 000000000..40e7e9f9f --- /dev/null +++ b/frontend/src/config/mozambique/reports.json @@ -0,0 +1,54 @@ +{ + "adamts_buffers_report": { + "layer_id": "adamts_buffers", + "title": "Event name: Storm Report", + "publication_date_label": "Publication Date", + "sub_text": "This is an automated report. Information should be treated as preliminary", + "areas_legend_definition": { + "title": "Areas", + "items": [ + { + "title": "Province", + "color": "#000000" + }, + { + "title": "District", + "color": "#999797" + }, + { + "title": "Township", + "color": "#D8D6D6" + } + ] + }, + "type_legend_definition": { + "title": "Tropical Storms - Wind buffers", + "items": [ + { + "title": "Uncertainty Cones", + "color": "#ffffff", + "border": "#b8b1b1" + }, + { + "title": "Wind Buffer 60 km/h", + "color": "#fffcf1", + "border": "#f7e705" + }, + { + "title": "Wind Buffer 90 km/h", + "color": "#ffeed8", + "border": "#f99408" + }, + { + "title": "Wind Buffer 120 km/h", + "color": "#fcd4ce", + "border": "#f90c08" + } + ] + }, + "map_footer_text": "Sources WFP, UNGIWG, OCHA, GAUL, USGS, NASA, UCSB", + "map_footer_sub_text": "The designations employed and the presentation of material in the map(s) do not imply the expression of any opinion on the part of WFP concerning the legal or constitutional status of any country, territory, city or sea, or concerning the delimitation of its frontiers or boundaries.", + "table_name": "Population Exposure", + "signature_text": "PRISM automated report" + } +} diff --git a/frontend/src/config/myanmar/index.ts b/frontend/src/config/myanmar/index.ts index 0163a53a9..2ece2dc2c 100644 --- a/frontend/src/config/myanmar/index.ts +++ b/frontend/src/config/myanmar/index.ts @@ -1,5 +1,6 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; +import rawReports from './reports.json'; // import myanmarTranslation from './translation.json'; const rawTables = {}; @@ -9,6 +10,7 @@ export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'mmr_polbnda_adm3_250k_mimu.json', }; diff --git a/frontend/src/config/myanmar/reports.json b/frontend/src/config/myanmar/reports.json new file mode 100644 index 000000000..6d20e0f7d --- /dev/null +++ b/frontend/src/config/myanmar/reports.json @@ -0,0 +1,90 @@ +{ + "flood_extent_report": { + "layer_id": "flood_extent", + "title": "Event name: Flood Report", + "publication_date_label": "Publication Date", + "sub_text": "This is an automated report. Information should be treated as preliminary", + "areas_legend_definition": { + "title": "Areas", + "items": [ + { + "title": "Province", + "color": "#000000" + }, + { + "title": "District", + "color": "#999797" + }, + { + "title": "Township", + "color": "#D8D6D6" + } + ] + }, + "type_legend_definition": { + "title": "Potential Flooding", + "items": [ + { + "title": "Flooded", + "color": "#a50f15" + } + ] + }, + "map_footer_text": "Sources WFP, UNGIWG, OCHA, GAUL, USGS, NASA, UCSB", + "map_footer_sub_text": "The designations employed and the presentation of material in the map(s) do not imply the expression of any opinion on the part of WFP concerning the legal or constitutional status of any country, territory, city or sea, or concerning the delimitation of its frontiers or boundaries.", + "table_name": "Population Exposure", + "signature_text": "PRISM automated report" + }, + "adamts_buffers_report": { + "layer_id": "adamts_buffers", + "title": "Event name: Storm Report", + "publication_date_label": "Publication Date", + "sub_text": "This is an automated report. Information should be treated as preliminary", + "areas_legend_definition": { + "title": "Areas", + "items": [ + { + "title": "Province", + "color": "#000000" + }, + { + "title": "District", + "color": "#999797" + }, + { + "title": "Township", + "color": "#D8D6D6" + } + ] + }, + "type_legend_definition": { + "title": "Tropical Storms - Wind buffers", + "items": [ + { + "title": "Uncertainty Cones", + "color": "#ffffff", + "border": "#b8b1b1" + }, + { + "title": "Wind Buffer 60 km/h", + "color": "#fffcf1", + "border": "#f7e705" + }, + { + "title": "Wind Buffer 90 km/h", + "color": "#ffeed8", + "border": "#f99408" + }, + { + "title": "Wind Buffer 120 km/h", + "color": "#fcd4ce", + "border": "#f90c08" + } + ] + }, + "map_footer_text": "Sources WFP, UNGIWG, OCHA, GAUL, USGS, NASA, UCSB", + "map_footer_sub_text": "The designations employed and the presentation of material in the map(s) do not imply the expression of any opinion on the part of WFP concerning the legal or constitutional status of any country, territory, city or sea, or concerning the delimitation of its frontiers or boundaries.", + "table_name": "Population Exposure", + "signature_text": "PRISM automated report" + } +} diff --git a/frontend/src/config/namibia/index.ts b/frontend/src/config/namibia/index.ts index 68d67cd85..a1405c0d6 100644 --- a/frontend/src/config/namibia/index.ts +++ b/frontend/src/config/namibia/index.ts @@ -2,10 +2,12 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, defaultBoundariesFile: 'nam_admin2.json', }; diff --git a/frontend/src/config/nigeria/index.ts b/frontend/src/config/nigeria/index.ts index 8d95e70a7..4e4f68475 100644 --- a/frontend/src/config/nigeria/index.ts +++ b/frontend/src/config/nigeria/index.ts @@ -2,12 +2,14 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; const translation = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'nga_admbnda_adm2_osgof_20190417.json', }; diff --git a/frontend/src/config/rbd/index.ts b/frontend/src/config/rbd/index.ts index 680b102c3..a64709f21 100644 --- a/frontend/src/config/rbd/index.ts +++ b/frontend/src/config/rbd/index.ts @@ -5,11 +5,13 @@ import frTranslation from './translation.json'; const translation = { fr: frTranslation }; const rawTables = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'wca_admbnda_adm2_ocha.json', }; diff --git a/frontend/src/config/sierraleone/index.ts b/frontend/src/config/sierraleone/index.ts index c6d5bedf4..2b35e02ff 100644 --- a/frontend/src/config/sierraleone/index.ts +++ b/frontend/src/config/sierraleone/index.ts @@ -2,12 +2,14 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; const translation = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'sle_admin2new_boundary_20200309.json', }; diff --git a/frontend/src/config/southsudan/index.ts b/frontend/src/config/southsudan/index.ts index 269a904dc..2ee778da9 100644 --- a/frontend/src/config/southsudan/index.ts +++ b/frontend/src/config/southsudan/index.ts @@ -2,10 +2,12 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, defaultBoundariesFile: 'ssd_admbnda_adm2_imwg_nbs_20180817.json', }; diff --git a/frontend/src/config/srilanka/index.ts b/frontend/src/config/srilanka/index.ts index 1943e2493..48ae7664d 100644 --- a/frontend/src/config/srilanka/index.ts +++ b/frontend/src/config/srilanka/index.ts @@ -3,11 +3,13 @@ import rawLayers from './layers.json'; const rawTables = {}; const translation = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'lka_bnd_adm3.json', }; diff --git a/frontend/src/config/tajikistan/index.ts b/frontend/src/config/tajikistan/index.ts index daf72eee7..874c7cf1e 100644 --- a/frontend/src/config/tajikistan/index.ts +++ b/frontend/src/config/tajikistan/index.ts @@ -2,10 +2,12 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, defaultBoundariesFile: 'tjk_admin2_wgs84_clean.json', }; diff --git a/frontend/src/config/types.ts b/frontend/src/config/types.ts index 464b84f0e..8b457ab52 100644 --- a/frontend/src/config/types.ts +++ b/frontend/src/config/types.ts @@ -3,7 +3,7 @@ import { every, map } from 'lodash'; import { FillPaint, LinePaint } from 'mapbox-gl'; import 'reflect-metadata'; import { rawLayers } from '.'; -import type { TableKey } from './utils'; +import type { ReportKey, TableKey } from './utils'; // TODO currently unused. Could be harnessed within admin levels key typing export type BoundaryKey = 'CODE' | 'CODE1' | 'CODE2'; @@ -642,6 +642,42 @@ export interface ChartConfig { colors?: string[]; // Array of hex codes. } +export interface ReportLegendDefinitionItem { + title: string; + color: string; + border?: string; +} + +export interface ReportLegendDefinition { + title: string; + items: ReportLegendDefinitionItem[]; +} + +export class ReportType { + id: ReportKey; + layerId: LayerKey; + title: string; + publicationDateLabel: string; + + @optional + subText?: string; + + areasLegendDefinition: ReportLegendDefinition; + typeLegendDefinition: ReportLegendDefinition; + + @optional + mapFooterText?: string; + + @optional + mapFooterSubText?: string; + + @optional + tableName?: string; + + @optional + signatureText?: string; +} + export class TableType { id: TableKey; title: string; diff --git a/frontend/src/config/ukraine/index.ts b/frontend/src/config/ukraine/index.ts index 8e7b4a17e..0510f48d6 100644 --- a/frontend/src/config/ukraine/index.ts +++ b/frontend/src/config/ukraine/index.ts @@ -3,11 +3,13 @@ import rawLayers from './layers.json'; const rawTables = {}; const translation = {}; +const rawReports = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'ukr_admbnda_adm2_sspe_20220114.json', }; diff --git a/frontend/src/config/utils.ts b/frontend/src/config/utils.ts index 115b21d99..3e9a78be5 100644 --- a/frontend/src/config/utils.ts +++ b/frontend/src/config/utils.ts @@ -1,23 +1,33 @@ -import { camelCase, mapKeys, get } from 'lodash'; -import { rawLayers, rawTables, appConfig } from '.'; +import { camelCase, get, mapKeys } from 'lodash'; +import { appConfig, rawLayers, rawReports, rawTables } from '.'; import { + AdminLevelDataLayerProps, BoundaryLayerProps, checkRequiredKeys, - PointDataLayerProps, ImpactLayerProps, LayerKey, LayersMap, LayerType, - AdminLevelDataLayerProps, + PointDataLayerProps, + ReportType, + StaticRasterLayerProps, StatsApi, TableType, WMSLayerProps, - StaticRasterLayerProps, } from './types'; // Typescript does not handle our configuration methods very well -// So we override the type of TableKey to make it more flexible. +// So we override the type of TableKey and ReportKey to make it more flexible. export type TableKey = string; +export type ReportKey = string; + +/** + * Check if a string is an explicitly defined report in reports.json + * @param reportsKey the string to check + */ +export const isReportsKey = (reportsKey: string): reportsKey is ReportKey => { + return reportsKey in rawReports; +}; /** * Check if a string is an explicitly defined table in tables.json @@ -139,11 +149,9 @@ export const LayerDefinitions: LayersMap = (() => { })(); export function getBoundaryLayers(): BoundaryLayerProps[] { - const boundaryLayers = Object.values(LayerDefinitions).filter( + return Object.values(LayerDefinitions).filter( (layer): layer is BoundaryLayerProps => layer.type === 'boundary', ); - - return boundaryLayers; } export function getDisplayBoundaryLayers(): BoundaryLayerProps[] { @@ -217,17 +225,38 @@ export const isPrimaryBoundaryLayer = (layer: BoundaryLayerProps) => layer.id === getBoundaryLayerSingleton().id; export function getWMSLayersWithChart(): WMSLayerProps[] { - const chartsLayers = Object.values(LayerDefinitions).filter( + return Object.values(LayerDefinitions).filter( l => l.type === 'wms' && l.chartData, ) as WMSLayerProps[]; - - return chartsLayers; } +const isValidReportsDefinition = ( + maybeReport: object, +): maybeReport is ReportType => { + return checkRequiredKeys(ReportType, maybeReport, true); +}; + function isValidTableDefinition(maybeTable: object): maybeTable is TableType { return checkRequiredKeys(TableType, maybeTable, true); } +const getReportByKey = (key: ReportKey): ReportType => { + // Typescript does not handle our configuration methods very well + // So we temporarily override the type of rawReports to make it more flexible. + const reports = rawReports as Record; + const rawDefinition = { + id: key, + ...mapKeys(isReportsKey(key) ? reports[key] : {}, (v, k) => camelCase(k)), + }; + + if (isValidReportsDefinition(rawDefinition)) { + return rawDefinition; + } + throw new Error( + `Found invalid report definition for report '${key}'. Check config/reports.json`, + ); +}; + function getTableByKey(key: TableKey): TableType { // Typescript does not handle our configuration methods very well // So we temporarily override the type of rawTables to make it more flexible. @@ -252,3 +281,11 @@ export const TableDefinitions = Object.keys(rawTables).reduce( }), {}, ) as { [key in TableKey]: TableType }; + +export const ReportsDefinitions = Object.keys(rawReports).reduce( + (acc, reportsKey) => ({ + ...acc, + [reportsKey]: getReportByKey(reportsKey as ReportKey), + }), + {}, +) as { [key in ReportKey]: ReportType }; diff --git a/frontend/src/config/zimbabwe/index.ts b/frontend/src/config/zimbabwe/index.ts index 8332e62ee..055cf4102 100644 --- a/frontend/src/config/zimbabwe/index.ts +++ b/frontend/src/config/zimbabwe/index.ts @@ -2,12 +2,14 @@ import appConfig from './prism.json'; import rawLayers from './layers.json'; const rawTables = {}; +const rawReports = {}; const translation = {}; export default { appConfig, rawLayers, rawTables, + rawReports, translation, defaultBoundariesFile: 'zim_admin2_boundaries_v2.json', }; diff --git a/frontend/src/context/analysisResultStateSlice.ts b/frontend/src/context/analysisResultStateSlice.ts index 9f32b84a1..f3f6ea8b2 100644 --- a/frontend/src/context/analysisResultStateSlice.ts +++ b/frontend/src/context/analysisResultStateSlice.ts @@ -76,6 +76,7 @@ type AnalysisResultState = { tableData?: TableData; result?: AnalysisResult; error?: string; + exposureLayerId: string; isLoading: boolean; isMapLayerActive: boolean; isDataTableDrawerActive: boolean; @@ -99,6 +100,7 @@ const initialState: AnalysisResultState = { isLoading: false, isMapLayerActive: true, isDataTableDrawerActive: false, + exposureLayerId: '', isExposureLoading: false, analysisResultDataSortByKey: 'name', analysisResultDataSortOrder: 'asc', @@ -802,6 +804,10 @@ export const analysisResultSlice = createSlice({ ...state, isMapLayerActive: payload, }), + setExposureLayerId: (state, { payload }: PayloadAction) => ({ + ...state, + exposureLayerId: payload, + }), setIsDataTableDrawerActive: ( state, { payload }: PayloadAction, @@ -942,6 +948,9 @@ export const analysisResultSortOrderSelector = ( state: RootState, ): 'asc' | 'desc' => state.analysisResultState.analysisResultDataSortOrder; +export const exposureLayerIdSelector = (state: RootState): string => + state.analysisResultState.exposureLayerId; + export const analysisResultOpacitySelector = (state: RootState): number => state.analysisResultState.opacity; @@ -962,6 +971,7 @@ export const { setIsMapLayerActive, setIsDataTableDrawerActive, setAnalysisLayerOpacity, + setExposureLayerId, setCurrentDataDefinition, hideDataTableDrawer, clearAnalysisResult,