Skip to content

Commit

Permalink
edm-415 yellow ribbon card dropdown ui (#33672)
Browse files Browse the repository at this point in the history
  • Loading branch information
flex2016 authored Dec 18, 2024
1 parent bce8967 commit dea3818
Show file tree
Hide file tree
Showing 5 changed files with 337 additions and 9 deletions.
21 changes: 12 additions & 9 deletions src/applications/gi/components/profile/InstitutionProfile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import Academics from './Academics';
import VeteranProgramsAndSupport from './VeteranProgramsAndSupport';
import BackToTop from '../BackToTop';
import CautionaryInformationLearMore from '../CautionaryInformationLearMore';
import YellowRibbonTable from './YellowRibbonTable';
import YellowRibbonSelector from './YellowRibbonSelector';
import Programs from './Programs';

export default function InstitutionProfile({
Expand Down Expand Up @@ -202,7 +202,7 @@ export default function InstitutionProfile({
{institution.yr === true &&
toggleValue && (
<ProfileSection
label="Yellow ribbon program information"
label="Yellow Ribbon Program information"
id="yellow-ribbon-program-information"
>
<p>
Expand All @@ -220,13 +220,17 @@ export default function InstitutionProfile({
<va-link
href="/education/about-gi-bill-benefits/post-9-11/yellow-ribbon-program/"
text="Find out if you qualify for the Yellow Ribbon Program"
className="vads-u-margin-bottom--2"
/>

<div className="additional-info-wrapper vads-u-padding-top--4">
<p className="vads-u-font-weight--bold ">
What to know about the content displayed in this table
</p>
<ul>
<div className="additional-info-wrapper vads-u-margin-top--2p5">
<div className="subsection vads-u-margin-bottom--2">
<h3 className="small-screen-header">
What to know about the content displayed below
</h3>
</div>

<ul className="getting-started-with-benefits-li">
<li>
Degree level: Type of degree such as Undergraduate, Graduate,
Masters, or Doctorate.
Expand All @@ -248,9 +252,8 @@ export default function InstitutionProfile({
</ul>
</div>
{institution.yellowRibbonPrograms.length > 0 ? (
<YellowRibbonTable
<YellowRibbonSelector
programs={institution.yellowRibbonPrograms}
smallScreen={smallScreen}
/>
) : (
<p className="vads-u-font-weight--bold vads-u-padding-top--3">
Expand Down
206 changes: 206 additions & 0 deletions src/applications/gi/components/profile/YellowRibbonSelector.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import React, { useState, useEffect } from 'react';
import {
VaSelect,
VaButton,
VaPagination,
VaCard,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { yellowRibbonDegreeLevelTypeHash } from '../../constants';
import { deriveEligibleStudents, deriveMaxAmount } from '../../utils/helpers';

const ProgramCard = ({ program }) => {
return (
<VaCard
background
key={program.divisionProfessionalSchool}
className="degree-level-card"
>
<div>
<p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
College or professional school
</p>
<p className="vads-u-margin-top--0">
{program.divisionProfessionalSchool}
</p>
</div>
<div>
<p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
Funding available
</p>
<p className="vads-u-margin-top--0">
<span>{deriveEligibleStudents(program.numberOfStudents)}</span>
</p>
</div>
<div>
<p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
Max school contribution
</p>
<p className="vads-u-margin-top--0">
<span>{deriveMaxAmount(program.contributionAmount)}</span>
</p>
</div>
</VaCard>
);
};

const YellowRibbonSelector = ({ programs }) => {
const [selectedOption, setSelectedOption] = useState('');
const [activeOption, setActiveOption] = useState('');
const [filteredPrograms, setFilteredPrograms] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const itemsPerPage = 3;

const degreeLevelOrder = [
'All',
'Certificate',
'Associates',
'Bachelors',
'Undergraduate',
'Masters',
'Doctoral',
'Graduate',
'Other',
];

const getDegreeLevels = degreeLevelKey => {
return yellowRibbonDegreeLevelTypeHash[degreeLevelKey] || [];
};

const degreeLevelOptions = [
...new Set(
programs.reduce((acc, program) => {
const degreeLevels = getDegreeLevels(program.degreeLevel);
return acc.concat(degreeLevels);
}, []),
),
].sort((a, b) => degreeLevelOrder.indexOf(a) - degreeLevelOrder.indexOf(b));

const filterPrograms = degreeLevel => {
const filtered = programs.filter(program =>
getDegreeLevels(program.degreeLevel).includes(degreeLevel),
);
setFilteredPrograms(filtered);
setCurrentPage(1);
};

useEffect(() => {
if (degreeLevelOptions.length === 1) {
const autoSelectOption = degreeLevelOptions[0];
setSelectedOption(autoSelectOption);
setActiveOption(autoSelectOption);
filterPrograms(autoSelectOption);
}
}, []);

const handleSelectionChange = event => {
setSelectedOption(event.target.value);
};

const handleDisplayResults = () => {
setActiveOption(selectedOption);
filterPrograms(selectedOption);
};

const handlePageChange = page => {
setCurrentPage(page);
};

const totalPages = Math.ceil(filteredPrograms.length / itemsPerPage);
const currentPrograms = filteredPrograms.slice(
(currentPage - 1) * itemsPerPage,
currentPage * itemsPerPage,
);

// Calculate start and end indices for the displayed programs
const startIndex = (currentPage - 1) * itemsPerPage + 1;
const endIndex = Math.min(
currentPage * itemsPerPage,
filteredPrograms.length,
);

return (
<div className="yellow-ribbon-selector-container">
{degreeLevelOptions.length > 1 && (
<div>
<p className="vads-u-font-weight--bold vads-u-margin-bottom--0">
Select a degree level to get started.
</p>
<div className="selector-wrapper vads-u-display--flex vads-u-align-items--flex-end ">
<VaSelect
className="degree-selector "
id="degree"
name="degree"
label="Degree level"
value={selectedOption}
onVaSelect={handleSelectionChange}
uswds
>
{degreeLevelOptions.map(degreeLevel => (
<option key={degreeLevel} value={degreeLevel}>
{degreeLevel}
</option>
))}
</VaSelect>

<VaButton
onClick={handleDisplayResults}
secondary
text="Display Results"
className="degree-selector-btn vads-u-margin-left--2p5"
/>
</div>
</div>
)}

<div className="vads-u-margin-bottom--0">
{filteredPrograms.length > 0 && (
<p
id="results-summary"
className="vads-u-margin-top--3 vads-u-margin-bottom--3"
>
{activeOption &&
(() => {
let activeOptionLabel;
if (activeOption === 'All' || activeOption === 'Other') {
activeOptionLabel = `${activeOption} degree levels`;
} else if (degreeLevelOptions.length === 1) {
activeOptionLabel = `${activeOption} degree level`;
} else {
activeOptionLabel = `"${activeOption}" degree level`;
}

return (
<>
{`Showing ${startIndex}-${endIndex} of ${
filteredPrograms.length
} results for `}
{activeOptionLabel}
</>
);
})()}
</p>
)}
<div className="degree-level-results vads-u-margin-bottom--1 vads-u-display--flex vads-u-flex-direction--column vads-u-align-items--stretch mobile-lg:vads-u-flex-direction--row">
{currentPrograms.map(program => (
<ProgramCard
key={program.divisionProfessionalSchool}
program={program}
/>
))}
</div>
{currentPrograms.length > 0 && (
<VaPagination
page={currentPage}
pages={totalPages}
maxPageListLength={3}
showLastPage
onPageSelect={e => handlePageChange(e.detail.page)}
className="vads-u-border-top--0 vads-u-padding-y--0 vads-u-margin-bottom--0"
/>
)}
</div>
</div>
);
};

export default YellowRibbonSelector;
44 changes: 44 additions & 0 deletions src/applications/gi/sass/gi.scss
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,47 @@
margin-bottom: 20px;
}
}

.yellow-ribbon-selector-container {
.selector-wrapper {
display: flex;
align-items: flex-end;
.degree-selector {
width: 100%;
max-width: 267px;
}
.degree-selector-btn {
white-space: nowrap;
}
@media screen and (max-width: $small-screen) {
flex-direction: column;
.degree-selector {
max-width: 100%;
margin-bottom: 10px;
}
.degree-selector-btn {
width: 100%;
text-align: center;
}
}
}
.degree-level-results {
display: flex;
flex-wrap: wrap;
gap: 20px;
.degree-level-card {
flex: 1 1 calc(33.33% - 30px);
max-width: calc(33.33% - 10px);
box-sizing: border-box;
margin: 0;
}
}
@media screen and (max-width: $medium-screen) {
.degree-level-results {
.degree-level-card {
flex: 1 1 100%;
max-width: 100%;
}
}
}
}
45 changes: 45 additions & 0 deletions src/applications/gi/tests/utils/helpers.unit.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
isReviewInstance,
isSmallScreenLogic,
handleUpdateLcFilterDropdowns,
deriveMaxAmount,
deriveEligibleStudents,
} from '../../utils/helpers';

describe('GIBCT helpers:', () => {
Expand Down Expand Up @@ -622,4 +624,47 @@ describe('GIBCT helpers:', () => {
expect(result).to.deep.equal(expectedResult);
});
});
describe('deriveMaxAmount', () => {
it('should return "Not provided" if no contributionAmount is given', () => {
expect(deriveMaxAmount()).to.equal('Not provided');
expect(deriveMaxAmount(null)).to.equal('Not provided');
expect(deriveMaxAmount('')).to.equal('Not provided');
});

it('should return a specific string when contributionAmount >= 99999', () => {
expect(deriveMaxAmount('99999')).to.equal(
"Pays remaining tuition that Post-9/11 GI Bill doesn't cover",
);
expect(deriveMaxAmount('100000')).to.equal(
"Pays remaining tuition that Post-9/11 GI Bill doesn't cover",
);
});

it('should format currency correctly for values less than 99999', () => {
expect(deriveMaxAmount('5000')).to.equal('$5,000');
expect(deriveMaxAmount('1234.56')).to.equal('$1,235');
expect(deriveMaxAmount(300)).to.equal('$300');
});
});
describe('deriveEligibleStudents', () => {
it('should return "Not provided" if no numberOfStudents is given', () => {
expect(deriveEligibleStudents()).to.equal('Not provided');
expect(deriveEligibleStudents(null)).to.equal('Not provided');
expect(deriveEligibleStudents('')).to.equal('Not provided');
});

it('should return "All eligible students" if numberOfStudents >= 99999', () => {
expect(deriveEligibleStudents(99999)).to.equal('All eligible students');
expect(deriveEligibleStudents(100000)).to.equal('All eligible students');
});

it('should return "1 student" if numberOfStudents is exactly 1', () => {
expect(deriveEligibleStudents(1)).to.equal('1 student');
});

it('should return "<X> students" for values other than 1 and less than 99999', () => {
expect(deriveEligibleStudents(2)).to.equal('2 students');
expect(deriveEligibleStudents(50)).to.equal('50 students');
});
});
});
30 changes: 30 additions & 0 deletions src/applications/gi/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -658,3 +658,33 @@ export const generateMockPrograms = numPrograms => {
},
}));
};

export const deriveMaxAmount = contributionAmount => {
if (!contributionAmount) {
return 'Not provided';
}
const contributionAmountNum = parseFloat(contributionAmount);
if (contributionAmountNum >= 99999) {
return "Pays remaining tuition that Post-9/11 GI Bill doesn't cover";
}

return contributionAmountNum.toLocaleString('en-US', {
currency: 'USD',
maximumFractionDigits: 0,
minimumFractionDigits: 0,
style: 'currency',
});
};

export const deriveEligibleStudents = numberOfStudents => {
if (!numberOfStudents) {
return 'Not provided';
}
if (numberOfStudents >= 99999) {
return 'All eligible students';
}
if (numberOfStudents === 1) {
return '1 student';
}
return `${numberOfStudents} students`;
};

0 comments on commit dea3818

Please sign in to comment.