From dea3818183669c56197fb745c20426b48ca91b65 Mon Sep 17 00:00:00 2001 From: Taras Kurilo Date: Wed, 18 Dec 2024 16:17:21 -0500 Subject: [PATCH] edm-415 yellow ribbon card dropdown ui (#33672) --- .../components/profile/InstitutionProfile.jsx | 21 +- .../profile/YellowRibbonSelector.jsx | 206 ++++++++++++++++++ src/applications/gi/sass/gi.scss | 44 ++++ .../gi/tests/utils/helpers.unit.spec.js | 45 ++++ src/applications/gi/utils/helpers.js | 30 +++ 5 files changed, 337 insertions(+), 9 deletions(-) create mode 100644 src/applications/gi/components/profile/YellowRibbonSelector.jsx diff --git a/src/applications/gi/components/profile/InstitutionProfile.jsx b/src/applications/gi/components/profile/InstitutionProfile.jsx index 08352596c7c3..2db71cbb4666 100644 --- a/src/applications/gi/components/profile/InstitutionProfile.jsx +++ b/src/applications/gi/components/profile/InstitutionProfile.jsx @@ -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({ @@ -202,7 +202,7 @@ export default function InstitutionProfile({ {institution.yr === true && toggleValue && (

@@ -220,13 +220,17 @@ export default function InstitutionProfile({ -

-

- What to know about the content displayed in this table -

-
    +
    +
    +

    + What to know about the content displayed below +

    +
    + +
    • Degree level: Type of degree such as Undergraduate, Graduate, Masters, or Doctorate. @@ -248,9 +252,8 @@ export default function InstitutionProfile({
    {institution.yellowRibbonPrograms.length > 0 ? ( - ) : (

    diff --git a/src/applications/gi/components/profile/YellowRibbonSelector.jsx b/src/applications/gi/components/profile/YellowRibbonSelector.jsx new file mode 100644 index 000000000000..5cbc03c1ccb8 --- /dev/null +++ b/src/applications/gi/components/profile/YellowRibbonSelector.jsx @@ -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 ( + +

    +

    + College or professional school +

    +

    + {program.divisionProfessionalSchool} +

    +
    +
    +

    + Funding available +

    +

    + {deriveEligibleStudents(program.numberOfStudents)} +

    +
    +
    +

    + Max school contribution +

    +

    + {deriveMaxAmount(program.contributionAmount)} +

    +
    + + ); +}; + +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 ( +
    + {degreeLevelOptions.length > 1 && ( +
    +

    + Select a degree level to get started. +

    +
    + + {degreeLevelOptions.map(degreeLevel => ( + + ))} + + + +
    +
    + )} + +
    + {filteredPrograms.length > 0 && ( +

    + {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} + + ); + })()} +

    + )} +
    + {currentPrograms.map(program => ( + + ))} +
    + {currentPrograms.length > 0 && ( + handlePageChange(e.detail.page)} + className="vads-u-border-top--0 vads-u-padding-y--0 vads-u-margin-bottom--0" + /> + )} +
    +
    + ); +}; + +export default YellowRibbonSelector; diff --git a/src/applications/gi/sass/gi.scss b/src/applications/gi/sass/gi.scss index bd3c9b4565c1..0afd97980541 100644 --- a/src/applications/gi/sass/gi.scss +++ b/src/applications/gi/sass/gi.scss @@ -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%; + } + } + } +} diff --git a/src/applications/gi/tests/utils/helpers.unit.spec.js b/src/applications/gi/tests/utils/helpers.unit.spec.js index 9066d5dbdacf..40e450e5568a 100644 --- a/src/applications/gi/tests/utils/helpers.unit.spec.js +++ b/src/applications/gi/tests/utils/helpers.unit.spec.js @@ -28,6 +28,8 @@ import { isReviewInstance, isSmallScreenLogic, handleUpdateLcFilterDropdowns, + deriveMaxAmount, + deriveEligibleStudents, } from '../../utils/helpers'; describe('GIBCT helpers:', () => { @@ -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 " students" for values other than 1 and less than 99999', () => { + expect(deriveEligibleStudents(2)).to.equal('2 students'); + expect(deriveEligibleStudents(50)).to.equal('50 students'); + }); + }); }); diff --git a/src/applications/gi/utils/helpers.js b/src/applications/gi/utils/helpers.js index 1f9e2967b214..33fbc52b788e 100644 --- a/src/applications/gi/utils/helpers.js +++ b/src/applications/gi/utils/helpers.js @@ -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`; +};