Skip to content

Commit

Permalink
Merge pull request #1409 from Gary-Community-Ventures/refactor/citize…
Browse files Browse the repository at this point in the history
…nship_filters

Hidden citizenship filters
  • Loading branch information
CalebPena authored Jan 3, 2025
2 parents a54dd56 + 9da6bef commit 3d73675
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 133 deletions.
43 changes: 17 additions & 26 deletions src/Assets/citizenshipFilterFormControlLabels.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { FormattedMessage } from 'react-intl';
import { FormData } from '../Types/FormData';
import { FormattedMessageType } from '../Types/Questions';
import { calcAge } from './age';

export type CitizenLabels =
| 'citizen'
Expand All @@ -9,7 +11,6 @@ export type CitizenLabels =
| 'gc_5plus'
| 'gc_18plus_no5'
| 'gc_under18_no5'
| 'other'
| 'otherWithWorkPermission'
| 'otherHealthCareUnder19'
| 'otherHealthCarePregnant';
Expand All @@ -21,18 +22,26 @@ export type CitizenLabelOptions =
| 'gc_5plus'
| 'gc_18plus_no5'
| 'gc_under18_no5'
| 'other'
| 'otherWithWorkPermission'
| 'otherHealthCareUnder19'
| 'otherHealthCarePregnant';
| 'otherWithWorkPermission';

export type CalculatedCitizenLabel = 'otherHealthCareUnder19' | 'otherHealthCarePregnant';

export const filterNestedMap = new Map<CitizenLabels, CitizenLabelOptions[]>([
export const filterNestedMap = new Map<CitizenLabelOptions, CitizenLabelOptions[]>([
['non_citizen', []],
['green_card', ['gc_5plus', 'gc_18plus_no5', 'gc_under18_no5']],
['refugee', []],
['other', ['otherWithWorkPermission', 'otherHealthCareUnder19', 'otherHealthCarePregnant']],
['otherWithWorkPermission', []],
]);

export const calculatedCitizenshipFilters: Record<CalculatedCitizenLabel, (formData: FormData) => boolean> = {
otherHealthCarePregnant: (formData) => {
return formData.householdData.some((member) => member.conditions.pregnant);
},
otherHealthCareUnder19: (formData) => {
return formData.householdData.some((member) => calcAge(member) < 19);
},
};

const citizenshipFilterFormControlLabels: Record<CitizenLabelOptions, FormattedMessageType> = {
non_citizen: (
<FormattedMessage
Expand Down Expand Up @@ -60,28 +69,10 @@ const citizenshipFilterFormControlLabels: Record<CitizenLabelOptions, FormattedM
defaultMessage="Admitted refugees or asylees (special rules or waiting periods may apply)"
/>
),
other: (
<FormattedMessage
id="citizenshipFCtrlLabel-other"
defaultMessage="Other lawfully present noncitizens (includes DACA recipients)"
/>
),
otherWithWorkPermission: (
<FormattedMessage
id="citizenshipFCtrlLabel-other_work_permission"
defaultMessage="with permission to live or work in the U.S. (other rules may apply)"
/>
),
otherHealthCareUnder19: (
<FormattedMessage
id="citizenshipFCtrlLabel-other_health_care_under19"
defaultMessage="for health care benefits - younger than 19"
/>
),
otherHealthCarePregnant: (
<FormattedMessage
id="citizenshipFCtrlLabel-other_health_care_pregnant"
defaultMessage="for health care benefits - pregnant"
defaultMessage="Other lawfully present noncitizens with permission to live or work in the U.S. (includes DACA recipients, other rules may apply)"
/>
),
};
Expand Down
39 changes: 29 additions & 10 deletions src/Components/Results/ProgramPage/ProgramPage.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { useParams } from 'react-router-dom';
import { Program } from '../../../Types/Results';
import ResultsTranslate from '../Translate/Translate.tsx';
import { headingOptionsMappings } from '../CategoryHeading/CategoryHeading.tsx';
import BackAndSaveButtons from '../BackAndSaveButtons/BackAndSaveButtons.tsx';
import ResultsTranslate from '../Translate/Translate';
import { headingOptionsMappings } from '../CategoryHeading/CategoryHeading';
import BackAndSaveButtons from '../BackAndSaveButtons/BackAndSaveButtons';
import { FormattedMessage } from 'react-intl';
import { useFormatYearlyValue } from '../FormattedValue';
import './ProgramPage.css';
import WarningMessage from '../../WarningComponent/WarningMessage.tsx';
import { useContext } from 'react';
import WarningMessage from '../../WarningComponent/WarningMessage';
import { useContext, useMemo } from 'react';
import { Context } from '../../Wrapper/Wrapper';
import { findValidationForProgram, useResultsContext, useResultsLink } from '../Results';
import { deleteValidation, postValidation } from '../../../apiCalls';
import { Language } from '../../../Assets/languageOptions.tsx';
import { allNavigatorLanguages } from './NavigatorLanguages.tsx';
import { Language } from '../../../Assets/languageOptions';
import { allNavigatorLanguages } from './NavigatorLanguages';
import { CitizenLabels } from '../../../Assets/citizenshipFilterFormControlLabels';

type ProgramPageProps = {
program: Program;
Expand All @@ -25,7 +26,7 @@ type IconRendererProps = {
const ProgramPage = ({ program }: ProgramPageProps) => {
const { uuid } = useParams();
const { formData, setFormData, staffToken } = useContext(Context);
const { isAdminView, validations, setValidations, programCategories } = useResultsContext();
const { isAdminView, validations, setValidations, programCategories, filtersChecked } = useResultsContext();
const IconRenderer: React.FC<IconRendererProps> = ({ headingType }) => {
const IconComponent = headingOptionsMappings[headingType];

Expand Down Expand Up @@ -110,6 +111,24 @@ const ProgramPage = ({ program }: ProgramPageProps) => {
};
const value = useFormatYearlyValue(program);

const warningMessages = useMemo(() => {
return program.warning_messages.filter((warningMessage) => {
if (warningMessage.legal_statuses.length === 0) {
// if no legal statuses are selected,
// then assume that the waring is for all legal statuses
return true;
}

for (const status of warningMessage.legal_statuses) {
if (filtersChecked[status]) {
return true;
}
}

return false;
});
}, [filtersChecked, program]);

const displayEstimatedValueAndTime = (program: Program) => {
return (
<section className="estimation">
Expand Down Expand Up @@ -160,8 +179,8 @@ const ProgramPage = ({ program }: ProgramPageProps) => {
{displayEstimatedValueAndTime(program)}
</div>
<div className="results-program-page-warning-container">
{program.warning_messages.map((message, key) => {
return <WarningMessage message={message} key={key} />;
{warningMessages.map((warning, key) => {
return <WarningMessage message={warning.message} key={key} />;
})}
</div>
<div className="apply-button-container">
Expand Down
169 changes: 76 additions & 93 deletions src/Components/Results/Programs/Filter.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,31 @@
import { useEffect, useState } from 'react';
import { ReactNode, useContext, useEffect, useState } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { useResultsContext } from '../Results';
import { Button, Popover, Checkbox } from '@mui/material';
import { CitizenLabelOptions, CitizenLabels } from '../../../Assets/citizenshipFilterFormControlLabels';
import {
CalculatedCitizenLabel,
calculatedCitizenshipFilters,
CitizenLabelOptions,
filterNestedMap,
} from '../../../Assets/citizenshipFilterFormControlLabels';
import citizenshipFilterFormControlLabels from '../../../Assets/citizenshipFilterFormControlLabels';
import { FormattedMessageType } from '../../../Types/Questions';
import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight';
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
import FormControlLabel from '@mui/material/FormControlLabel';
import CloseIcon from '@mui/icons-material/Close';
import IconButton from '@mui/material/IconButton';
import './Filter.css';
import HelpButton from '../../HelpBubbleIcon/HelpButton';
import { Context } from '../../Wrapper/Wrapper';

export const Filter = () => {
const [citizenshipFilterIsOpen, setCitizenshipFilterIsOpen] = useState(false);
const [citizenshipPopoverAnchor, setCitizenshipPopoverAnchor] = useState<null | Element>(null);
const { filtersChecked, setFiltersChecked } = useResultsContext();
const { formData } = useContext(Context);
const [citButtonClass, setCitButtonClass] = useState('citizenship-button');
const intl = useIntl();

useEffect(() => {
const filtersCheckedStrArr = Object.entries(filtersChecked)
.filter((filterKeyValPair) => {
return filterKeyValPair[1];
})
.map((filteredKeyValPair) => filteredKeyValPair[0]);

//if a filter is selected/truthy then remove citizen from the filtersCheckedStrArray and set citizen state to false
//and add the active-blue css class to the citizenshipButtonClass
if (filtersCheckedStrArr.includes('citizen') && filtersCheckedStrArr.length > 1) {
filtersCheckedStrArr.filter((filter) => filter !== 'citizen');
setFiltersChecked({ ...filtersChecked, citizen: false });
setCitButtonClass(citButtonClass + ' active-blue');
}

//if they deselect all of the filters then we want the filtersChecked to start with a truthy citizen value
//and the default citButtonClass
if (filtersCheckedStrArr.length === 0 && !citizenshipFilterIsOpen) {
setFiltersChecked({ ...filtersChecked, citizen: true });
setCitButtonClass('citizenship-button');
} else if (filtersCheckedStrArr.length === 0 && citizenshipFilterIsOpen) {
setFiltersChecked({ ...filtersChecked, citizen: true });
setCitButtonClass('citizenship-button flat-white-border-bottom');
}
}, [filtersChecked]);

useEffect(() => {
if (citizenshipFilterIsOpen) {
setCitButtonClass(citButtonClass + ' flat-white-border-bottom');
Expand All @@ -57,78 +37,82 @@ export const Filter = () => {
setCitizenshipPopoverAnchor(event.currentTarget);
};

const handleFilterSelect = (selectedFilterStr: CitizenLabels) => {
const updatedSelectedFilterValue = !filtersChecked[selectedFilterStr];
const greenCardSubFilters: CitizenLabels[] = ['gc_5plus', 'gc_18plus_no5', 'gc_under18_no5'];
const otherSubFilters: CitizenLabels[] = [
'otherWithWorkPermission',
'otherHealthCareUnder19',
'otherHealthCarePregnant',
];
const handleFilterSelect = (selectedFilterStr: CitizenLabelOptions) => {
const newFiltersChecked = { ...filtersChecked };

if (selectedFilterStr === 'green_card') {
const updatedFiltersChecked = { ...filtersChecked };
greenCardSubFilters.forEach((subfilter) => {
updatedFiltersChecked[subfilter] = updatedSelectedFilterValue;
});
const newSelected = !newFiltersChecked[selectedFilterStr];

setFiltersChecked({ ...updatedFiltersChecked, [selectedFilterStr]: updatedSelectedFilterValue });
} else if (selectedFilterStr === 'other') {
const updatedFiltersChecked = { ...filtersChecked };
otherSubFilters.forEach((subfilter) => {
updatedFiltersChecked[subfilter] = updatedSelectedFilterValue;
});
newFiltersChecked[selectedFilterStr] = newSelected;

setFiltersChecked({ ...updatedFiltersChecked, [selectedFilterStr]: updatedSelectedFilterValue });
} else {
setFiltersChecked({ ...filtersChecked, [selectedFilterStr]: updatedSelectedFilterValue });
// select or deselect all subfilters when main filter is selected
const subFilters = filterNestedMap.get(selectedFilterStr);
if (subFilters !== undefined) {
for (const subFilter of subFilters) {
newFiltersChecked[subFilter] = newSelected;
}
}
};

const renderCitizenshipFilters = (
citizenshipFCLabels: Record<CitizenLabelOptions, FormattedMessageType>,
filtersChecked: Record<CitizenLabels, boolean>,
) => {
const greenCardSubFilters = ['gc_5plus', 'gc_18plus_no5', 'gc_under18_no5'];
const otherSubFilters = ['otherWithWorkPermission', 'otherHealthCareUnder19', 'otherHealthCarePregnant'];
const filters: JSX.Element[] = [];
// if all subfilters are unchecked, uncheck the main filter
filterNestedMap.forEach((subFilters, mainFilter) => {
if (!subFilters.includes(selectedFilterStr)) {
return;
}

Object.entries(citizenshipFCLabels).forEach((citizenshipFCLEntry) => {
const citizenshipFCLKey = citizenshipFCLEntry[0] as CitizenLabelOptions;
const citizenshipFCLabel = citizenshipFCLEntry[1];
const allSubFiltersDeselected = subFilters.every((subFilter) => !newFiltersChecked[subFilter]);

const isAMainFilter =
!greenCardSubFilters.includes(citizenshipFCLKey) && !otherSubFilters.includes(citizenshipFCLKey);
const isSubfilterAndMainFilterIsChecked =
(greenCardSubFilters.includes(citizenshipFCLKey) && filtersChecked.green_card === true) ||
(otherSubFilters.includes(citizenshipFCLKey) && filtersChecked.other === true);
if (allSubFiltersDeselected) {
newFiltersChecked[mainFilter] = false;
}
});

//if this is a main filter push it otherwise check to see if the main filter is truthy and then push it
if (isAMainFilter) {
filters.push(
<FormControlLabel
key={citizenshipFCLKey}
label={citizenshipFCLabel}
control={
<Checkbox
checked={filtersChecked[citizenshipFCLKey]}
onChange={() => handleFilterSelect(citizenshipFCLKey)}
/>
}
className="vertical-align"
/>,
);
} else if (isSubfilterAndMainFilterIsChecked) {
// if all filters are unchecked set citizenship to false
let isCitizen = true;
for (const filter in citizenshipFilterFormControlLabels) {
if (newFiltersChecked[filter as CitizenLabelOptions]) {
isCitizen = false;

break;
}
}
newFiltersChecked.citizen = isCitizen;

// calculate hidden filters if the user is not a citizen
Object.entries(calculatedCitizenshipFilters).map(([filterName, func]) => {
if (isCitizen) {
newFiltersChecked[filterName as CalculatedCitizenLabel] = false;
return;
}

newFiltersChecked[filterName as CalculatedCitizenLabel] = func(formData);
});

setFiltersChecked(newFiltersChecked);
};

const renderCitizenshipFilters = () => {
const filters: ReactNode[] = [];
filterNestedMap.forEach((subFilters, mainFilter) => {
const mainFilterIsChecked = filtersChecked[mainFilter];

filters.push(
<FormControlLabel
key={mainFilter}
label={citizenshipFilterFormControlLabels[mainFilter]}
control={<Checkbox checked={mainFilterIsChecked} onChange={() => handleFilterSelect(mainFilter)} />}
className="vertical-align"
/>,
);

if (!mainFilterIsChecked) {
return;
}

for (const subFilter of subFilters) {
filters.push(
<FormControlLabel
key={citizenshipFCLKey}
label={citizenshipFCLabel}
control={
<Checkbox
checked={filtersChecked[citizenshipFCLKey]}
onChange={() => handleFilterSelect(citizenshipFCLKey)}
/>
}
key={subFilter}
label={citizenshipFilterFormControlLabels[subFilter]}
control={<Checkbox checked={filtersChecked[subFilter]} onChange={() => handleFilterSelect(subFilter)} />}
className="subcategory-indentation vertical-align"
sx={{ padding: '.5rem 0' }}
/>,
Expand Down Expand Up @@ -184,7 +168,7 @@ export const Filter = () => {
<CloseIcon fontSize="small" />
</IconButton>
</div>
{renderCitizenshipFilters(citizenshipFilterFormControlLabels, filtersChecked)}
{renderCitizenshipFilters()}
</Popover>
</section>
);
Expand Down Expand Up @@ -220,7 +204,6 @@ export const Filter = () => {
gc_5plus: false,
gc_18plus_no5: false,
gc_under18_no5: false,
other: false,
otherWithWorkPermission: false,
otherHealthCareUnder19: false,
otherHealthCarePregnant: false,
Expand Down
1 change: 0 additions & 1 deletion src/Components/Results/Results.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ const Results = ({ type, handleTextfieldChange }: ResultsProps) => {
gc_5plus: false,
gc_18plus_no5: false,
gc_under18_no5: false,
other: false,
otherWithWorkPermission: false,
otherHealthCareUnder19: false,
otherHealthCarePregnant: false,
Expand Down
4 changes: 2 additions & 2 deletions src/Components/Results/ResultsError/ResultsError.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
import './ResultsError.css';

const ResultsError = () => {
const { uuid } = useParams();
const { uuid, whiteLabel } = useParams();
const navigate = useNavigate();

return (
Expand All @@ -24,7 +24,7 @@ const ResultsError = () => {
<Button
className="error-button"
onClick={() => {
navigate(`/${uuid}/confirm-information`);
navigate(`/${whiteLabel}/${uuid}/confirm-information`);
}}
variant="contained"
>
Expand Down
Loading

0 comments on commit 3d73675

Please sign in to comment.