Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hidden citizenship filters #1409

Merged
merged 3 commits into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading