mirror of
https://github.com/DOI-DO/j40-cejst-2.git
synced 2025-07-31 20:31:16 -07:00
adds map side panel (#406)
* initial map side panel * componentize MapSidePanel * remove selection from J40Map * adds isFeatureSelected to toggle component * filters data from server for client UI * styling and refactor * added TODO * adds styling to intro and pairing feedback * add mobile styling * adds popup back to fs feature flag * adds tests and aria roles * makes mobile content same as desktop * prettier update * initial e2e mapSidePanel test * adds cypress tests on desktop and mobile * adds sass util and updates cypress tests * cleans up tests * reverts tsconfig file * fixes map alignment * renaming and using constants * renaming sidePanel to infoPanel * intl messaging * adds snapshot testing and utility sass file * PR feedback - adds intl messages - adds data-cy attr to cy tests - snapshot testing for unit tests - fixes bug where side panel extends past the map - moves all wrapper content in MapWrapper * logs isMobile to troubleshoot deployed PR * adds react-device-detect for isMobile detection * adds new instance of map for mobile * adds instance * adds isMobile to state * tests the fix for mobile map view on PR * PR review feedback - localize MapIntroduction - update snapshot tests - QA feedback - constants.isMobile points to react-device-detect
This commit is contained in:
parent
a787bd71ab
commit
36f43b2d44
25 changed files with 1430 additions and 27185 deletions
|
@ -1,92 +1,210 @@
|
|||
// External Libs:
|
||||
import * as React from 'react';
|
||||
import * as constants from '../data/constants';
|
||||
import {useIntl} from 'gatsby-plugin-intl';
|
||||
import {defineMessages} from 'react-intl';
|
||||
|
||||
// Components:
|
||||
|
||||
// Styles and constants
|
||||
import * as styles from './areaDetail.module.scss';
|
||||
import * as constants from '../data/constants';
|
||||
|
||||
export const readablePercent = (percent: number) => {
|
||||
return `${(percent * 100).toFixed(1)}`;
|
||||
};
|
||||
|
||||
export const getCategorization = (percentile: number) => {
|
||||
let categorization;
|
||||
let categoryCircleStyle;
|
||||
|
||||
if (percentile >= constants.SCORE_BOUNDARY_PRIORITIZED ) {
|
||||
categorization = 'Prioritized';
|
||||
categoryCircleStyle = styles.prioritized;
|
||||
} else if (constants.SCORE_BOUNDARY_THRESHOLD <= percentile && percentile < constants.SCORE_BOUNDARY_PRIORITIZED) {
|
||||
categorization = 'Threshold';
|
||||
categoryCircleStyle = styles.threshold;
|
||||
} else {
|
||||
categorization = 'Non-prioritized';
|
||||
categoryCircleStyle = styles.nonPrioritized;
|
||||
}
|
||||
return [categorization, categoryCircleStyle];
|
||||
};
|
||||
|
||||
interface IAreaDetailProps {
|
||||
properties: constants.J40Properties,
|
||||
}
|
||||
|
||||
|
||||
const AreaDetail = ({properties}:IAreaDetailProps) => {
|
||||
const readablePercent = (percent: number) => {
|
||||
return `${(percent * 100).toFixed(2)}`;
|
||||
const intl = useIntl();
|
||||
const messages = defineMessages({
|
||||
cumulativeIndexScore: {
|
||||
id: 'areaDetail.priorityInfo.cumulativeIndexScore',
|
||||
defaultMessage: 'Cumulative Index Score',
|
||||
description: 'the cumulative score of the feature selected',
|
||||
},
|
||||
percentile: {
|
||||
id: 'areaDetail.priorityInfo.percentile',
|
||||
defaultMessage: 'percentile',
|
||||
description: 'the percentil of the feature selected',
|
||||
},
|
||||
categorization: {
|
||||
id: 'areaDetail.priorityInfo.categorization',
|
||||
defaultMessage: 'Categorization',
|
||||
description: 'the categorization of prioritized, threshold or non-prioritized',
|
||||
},
|
||||
censusBlockGroup: {
|
||||
id: 'areaDetail.geographicInfo.censusBlockGroup',
|
||||
defaultMessage: 'Census block group:',
|
||||
description: 'the census block group id number of the feature selected',
|
||||
},
|
||||
county: {
|
||||
id: 'areaDetail.geographicInfo.county',
|
||||
defaultMessage: 'County:',
|
||||
description: 'the county of the feature selected',
|
||||
},
|
||||
state: {
|
||||
id: 'areaDetail.geographicInfo.state',
|
||||
defaultMessage: 'State: ',
|
||||
description: 'the state of the feature selected',
|
||||
},
|
||||
population: {
|
||||
id: 'areaDetail.geographicInfo.population',
|
||||
defaultMessage: 'Population:',
|
||||
description: 'the population of the feature selected',
|
||||
},
|
||||
indicatorColumnHeader: {
|
||||
id: 'areaDetail.indicators.indicatorColumnHeader',
|
||||
defaultMessage: 'INDICATORS',
|
||||
description: 'the population of the feature selected',
|
||||
},
|
||||
percentileColumnHeader: {
|
||||
id: 'areaDetail.indicators.percentileColumnHeader',
|
||||
defaultMessage: 'PERCENTILE (0-100)',
|
||||
description: 'the population of the feature selected',
|
||||
},
|
||||
poverty: {
|
||||
id: 'areaDetail.indicator.poverty',
|
||||
defaultMessage: 'Poverty',
|
||||
description: 'Household income is less than or equal to twice the federal "poverty level"',
|
||||
},
|
||||
education: {
|
||||
id: 'areaDetail.indicator.education',
|
||||
defaultMessage: 'Education',
|
||||
description: 'Percent of people age 25 or older that didn’t get a high school diploma',
|
||||
},
|
||||
linguisticIsolation: {
|
||||
id: 'areaDetail.indicator.linguisticIsolation',
|
||||
defaultMessage: 'Linguistic isolation',
|
||||
description: 'Households in which all members speak a non-English language and ' +
|
||||
'speak English less than "very well"',
|
||||
},
|
||||
unemployment: {
|
||||
id: 'areaDetail.indicator.unemployment',
|
||||
defaultMessage: 'Unemployment rate',
|
||||
description: 'Number of unemployed people as a percentage of the labor force',
|
||||
},
|
||||
houseBurden: {
|
||||
id: 'areaDetail.indicator.houseBurden',
|
||||
defaultMessage: 'Housing Burden',
|
||||
description: 'Households that are low income and spend more than 30% of their income to housing costs',
|
||||
},
|
||||
});
|
||||
|
||||
const score = properties[constants.SCORE_PROPERTY_HIGH] as number;
|
||||
const blockGroup = properties[constants.GEOID_PROPERTY];
|
||||
const population = properties[constants.TOTAL_POPULATION];
|
||||
|
||||
interface indicatorInfo {
|
||||
label: string,
|
||||
description: string,
|
||||
value: number,
|
||||
}
|
||||
|
||||
// Todo: Ticket #367 will be replacing descriptions with YAML file
|
||||
const povertyInfo:indicatorInfo = {
|
||||
label: intl.formatMessage(messages.poverty),
|
||||
description: 'Household income is less than or equal to twice the federal "poverty level"',
|
||||
value: properties[constants.POVERTY_PROPERTY_PERCENTILE],
|
||||
};
|
||||
const eduInfo:indicatorInfo = {
|
||||
label: intl.formatMessage(messages.education),
|
||||
description: 'Percent of people age 25 or older that didn’t get a high school diploma',
|
||||
value: properties[constants.EDUCATION_PROPERTY_PERCENTILE],
|
||||
};
|
||||
const linIsoInfo:indicatorInfo = {
|
||||
label: intl.formatMessage(messages.linguisticIsolation),
|
||||
description: 'Households in which all members speak a non-English language and speak English less than "very well"',
|
||||
value: properties[constants.LINGUISTIC_ISOLATION_PROPERTY_PERCENTILE],
|
||||
};
|
||||
const umemployInfo:indicatorInfo = {
|
||||
label: intl.formatMessage(messages.unemployment),
|
||||
description: 'Number of unemployed people as a percentage of the labor force',
|
||||
value: properties[constants.UNEMPLOYMENT_PROPERTY_PERCENTILE],
|
||||
};
|
||||
const houseBurden:indicatorInfo = {
|
||||
label: intl.formatMessage(messages.houseBurden),
|
||||
description: 'Households that are low income and spend more than 30% of their income to housing costs',
|
||||
value: properties[constants.HOUSING_BURDEN_PROPERTY_PERCENTILE],
|
||||
};
|
||||
|
||||
const getCategorization = (percentile: number) => {
|
||||
let categorization;
|
||||
if (percentile >= 0.75 ) {
|
||||
categorization = 'Prioritized';
|
||||
} else if (0.60 <= percentile && percentile < 0.75) {
|
||||
categorization = 'Threshold';
|
||||
} else {
|
||||
categorization = 'Non-prioritized';
|
||||
}
|
||||
return categorization;
|
||||
};
|
||||
|
||||
const getTitleContent = () => {
|
||||
const blockGroup = properties[constants.GEOID_PROPERTY];
|
||||
const score = properties[constants.SCORE_PROPERTY_HIGH] as number;
|
||||
return (
|
||||
<div className={styles.titleContainer}>
|
||||
<div>
|
||||
<span className={styles.titleIndicatorName}>Census Block Group: </span>
|
||||
<span>{blockGroup}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.titleIndicatorName}>Just Progress Categorization: </span>
|
||||
<span>{getCategorization(score)}</span>
|
||||
</div>
|
||||
<div>
|
||||
<span className={styles.titleIndicatorName}>Cumulative Index Score: </span>
|
||||
<span>{readablePercent(score)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getBodyContent = () => {
|
||||
const rows = [];
|
||||
const sortedKeys = Object.entries(properties).sort();
|
||||
for (let [key, value] of sortedKeys) {
|
||||
// We should only format floats
|
||||
if (typeof value === 'number' && value % 1 !== 0) {
|
||||
value = readablePercent(value);
|
||||
}
|
||||
|
||||
// Filter out all caps
|
||||
if (!key.match(/^[A-Z0-9]+$/)) {
|
||||
rows.push(<tr key={key} >
|
||||
<td>{key}</td>
|
||||
<td>{value}</td>
|
||||
</tr>);
|
||||
}
|
||||
}
|
||||
return rows;
|
||||
};
|
||||
const indicators = [povertyInfo, eduInfo, linIsoInfo, umemployInfo, houseBurden];
|
||||
|
||||
const [categorization, categoryCircleStyle] = getCategorization(score);
|
||||
|
||||
return (
|
||||
<>
|
||||
{properties ?
|
||||
<div className={styles.areaDetailContainer}>
|
||||
{getTitleContent()}
|
||||
<div className={styles.areaDetailTableContainer}>
|
||||
<table className={'usa-table usa-table--borderless ' + styles.areaDetailTable}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">INDICATOR</th>
|
||||
<th scope="col">VALUE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{getBodyContent()}
|
||||
</tbody>
|
||||
</table>
|
||||
<aside className={styles.areaDetailContainer} data-cy={'aside'}>
|
||||
<header className={styles.topRow }>
|
||||
<div className={styles.cumulativeIndexScore}>
|
||||
<div className={styles.topRowTitle}>{intl.formatMessage(messages.cumulativeIndexScore)}</div>
|
||||
<div className={styles.score} data-cy={'score'}>{`${readablePercent(score)}`}
|
||||
<sup className={styles.scoreSuperscript}><span>th</span></sup>
|
||||
</div>
|
||||
<div className={styles.topRowSubTitle}>{intl.formatMessage(messages.percentile)}</div>
|
||||
</div>
|
||||
</div> :
|
||||
'' }
|
||||
</>
|
||||
<div className={styles.categorization}>
|
||||
<div className={styles.topRowTitle}>{intl.formatMessage(messages.categorization)}</div>
|
||||
<div className={styles.priority}>
|
||||
<div className={categoryCircleStyle} />
|
||||
<div className={styles.prioritization}>{categorization}</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
<ul className={styles.censusRow}>
|
||||
<li>
|
||||
<span className={styles.censusLabel}>{intl.formatMessage(messages.censusBlockGroup)} </span>
|
||||
<span className={styles.censusText}>{blockGroup}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={styles.censusLabel}>{intl.formatMessage(messages.county)} </span>
|
||||
<span className={styles.censusText}>{'Washington County*'}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={styles.censusLabel}>{intl.formatMessage(messages.state)}</span>
|
||||
<span className={styles.censusText}>{'District of Columbia*'}</span>
|
||||
</li>
|
||||
<li>
|
||||
<span className={styles.censusLabel}>{intl.formatMessage(messages.population)} </span>
|
||||
<span className={styles.censusText}>{population.toLocaleString()}</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div className={styles.divider}>
|
||||
<div>{intl.formatMessage(messages.indicatorColumnHeader)}</div>
|
||||
<div>{intl.formatMessage(messages.percentileColumnHeader)}</div>
|
||||
</div>
|
||||
|
||||
{indicators.map((indicator, index) => (
|
||||
<li key={index} className={styles.indicatorBox} data-cy={'indicatorBox'}>
|
||||
<div className={styles.indicatorInfo}>
|
||||
<div className={styles.indicatorTitle}>{indicator.label}</div>
|
||||
<div className={styles.indicatorDescription}>
|
||||
{indicator.description}
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.indicatorValue}>{readablePercent(indicator.value)}</div>
|
||||
</li>
|
||||
))}
|
||||
|
||||
</aside>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue