src/applications/gi/containers/search/ResultCard.jsx
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classNames from 'classnames';
import appendQuery from 'append-query';
import { Link } from 'react-router-dom';
import recordEvent from 'platform/monitoring/record-event';
import { useFeatureToggle } from 'platform/utilities/feature-toggles';
import {
addCompareInstitution,
removeCompareInstitution,
showModal,
} from '../../actions';
import { MINIMUM_RATING_COUNT } from '../../constants';
import CompareCheckbox from '../../components/CompareCheckbox';
import { estimatedBenefits } from '../../selectors/estimator';
import {
formatCurrency,
createId,
convertRatingToStars,
showSchoolContentBasedOnType,
} from '../../utils/helpers';
import { CautionFlagAdditionalInfo } from '../../components/CautionFlagAdditionalInfo';
import RatingsStars from '../../components/profile/schoolRatings/RatingsStars';
import SchoolClassification from '../../components/SchoolClassification';
export function ResultCard({
compare,
estimated,
dispatchAddCompareInstitution,
dispatchRemoveCompareInstitution,
dispatchShowModal,
institution,
location = false,
header = null,
active = false,
version,
paginationRef,
}) {
const {
name,
city,
state,
studentCount,
accreditationType,
cautionFlags,
facilityCode,
vetTecProvider,
schoolProvider,
employerProvider,
tuitionOutOfState,
preferredProvider,
programCount,
programLengthInHours,
type,
} = institution;
const { useToggleValue, TOGGLE_NAMES } = useFeatureToggle();
// environment variable to keep ratings out of production until ready
const isShowRatingsToggle = useToggleValue(
TOGGLE_NAMES.giComparisonToolShowRatings,
);
let ratingCount = 0;
let ratingAverage = false;
let institutionRatingIsNotNull = false;
let institutionCountIsNotNull = false;
let institutionOverallAvgIsNotNull = false;
/** ***CHECK IF INSTITUTION.INSTITUTIONRATING IS NULL**** */
if (institution.institutionRating != null) {
institutionRatingIsNotNull = true;
}
if (
institutionRatingIsNotNull &&
institution.institutionRating.institutionRatingCount != null
) {
institutionCountIsNotNull = true;
}
if (
institutionRatingIsNotNull &&
institutionCountIsNotNull &&
institution.institutionRating.overallAvg != null
) {
institutionOverallAvgIsNotNull = true;
}
if (
institutionRatingIsNotNull &&
institutionCountIsNotNull &&
institutionOverallAvgIsNotNull
) {
const {
institutionRatingCount,
overallAvg,
} = institution.institutionRating;
ratingCount = institutionRatingCount;
ratingAverage = overallAvg;
}
/// /////////////////////////////////////////////////////////
const compareChecked = !!compare.search.institutions[facilityCode];
const compareLength = compare.search.loaded.length;
const handleCompareUpdate = e => {
if (e.target.checked && !compareChecked) {
if (compareLength === 3) {
recordEvent({
event: 'gibct-form-change',
'gibct-form-field': 'compareCheckbox',
'gibct-form-value': `Limit Reached | ${compareLength}`,
'school-name': institution.name,
});
dispatchShowModal('comparisonLimit');
} else {
recordEvent({
event: 'gibct-form-change',
'gibct-form-field': 'compareCheckbox',
'gibct-form-value': `Add | ${compareLength + 1}`,
'school-name': institution.name,
});
dispatchAddCompareInstitution(institution);
}
} else {
recordEvent({
event: 'gibct-form-change',
'gibct-form-field': 'compareCheckbox',
'gibct-form-value': `Remove | ${compareLength - 1}`,
'school-name': institution.name,
});
dispatchRemoveCompareInstitution(facilityCode);
}
};
const [expanded, toggleExpansion] = useState(false);
const profileLink = version
? appendQuery(`/institution/${facilityCode}`, { version })
: `/institution/${facilityCode}`;
const resultCardClasses = classNames('result-card', {
'vads-u-margin-bottom--2': location,
'vads-u-padding-right--1': location && !active,
'vads-u-padding--0p5': active,
active,
});
const containerClasses = classNames({
'vads-u-margin-bottom--2': !location,
'mobile-lg:vads-u-margin-left--2p5': !location,
'vads-u-margin--0': location,
'vads-u-padding--0': location,
});
const nameClasses = classNames({
'vads-u-margin-top--2': !location,
'vads-u-margin-top--1': location,
});
const nameCityStateHeader = (
<>
<div>
<h3 className={nameClasses} id={`label-${institution.facilityCode}`}>
<Link
ref={paginationRef}
to={profileLink}
onClick={() =>
recordEvent({
event: 'gibct-view-profile',
'school-name': name,
'has-warnings': cautionFlags.length > 0,
})
}
>
{name}
<span className="vads-u-visibility--screen-reader">
{city}
{state && `, ${state}`}
</span>
</Link>
</h3>
</div>
<p className="vads-u-padding--0">
{city}
{state && `, ${state}`}
</p>
</>
);
// toggle for production/staging------------------------------------------------
let ratingsInformation = false;
if (isShowRatingsToggle) {
const stars = convertRatingToStars(ratingAverage);
const displayStars = stars && ratingCount >= MINIMUM_RATING_COUNT;
ratingsInformation = displayStars ? (
<div>
<div className="vads-l-grid-container search-star-container">
<div className="vads-u-margin-bottom--2 vads-l-row">
<div className="star-icons">
<RatingsStars rating={ratingAverage} />
</div>
<div className="xsmall-screen:vads-l-col--12 medium-screen:vads-l-col--8">
<strong>
{Math.round(10 * ratingAverage) / 10} out of 4 overall
</strong>
</div>
<div className="vads-l-row">
<div className="vads-l-col--12">
<strong>{ratingCount} veterans rated this institution</strong>
</div>
</div>
</div>
</div>
</div>
) : (
<div>
<p>
<strong>Not yet rated by Veterans</strong>
</p>
</div>
);
}
// end toggle for production/staging------------------------------------------------
const estimateHousing = ({ qualifier, value }) => {
return qualifier === null ? value : <span>{formatCurrency(value)}</span>;
};
const estimateTuition = ({ qualifier, value }) => {
if (qualifier === null) {
return value;
}
if (qualifier === '% of instate tuition') {
return <span>{value}% in-state</span>;
}
const lesserVal = tuitionOutOfState
? Math.min(value, tuitionOutOfState)
: value;
return <span>{formatCurrency(lesserVal)}</span>;
};
const tuition = estimateTuition(estimated.tuition);
const housing = estimateHousing(estimated.housing);
const tuitionAndEligibility = (
<>
<p>
<strong>You may be eligible for up to:</strong>
</p>
<div className="vads-u-display--flex vads-u-margin-top--0 vads-u-margin-bottom--2">
<div className="vads-u-flex--1">
<p className="secondary-info-label-large">Tuition benefit:</p>
<p className="vads-u-margin-y--0">{tuition}</p>
</div>
<div className="vads-u-flex--1">
<p className="secondary-info-label-large">Housing benefit:</p>
<p className="vads-u-margin-y--0">
{housing} /mo
{employerProvider && '*'}
</p>
</div>
</div>
{employerProvider && (
<p className="asterisk-text">
* Housing rate and the amount of entitlement used decrease every 6
months as training progresses
</p>
)}
</>
);
const schoolEmployerInstitutionDetails = (
<>
<div className="vads-u-flex--1">
<p className="secondary-info-label-large">
<strong>Accreditation:</strong>
</p>
<p className="vads-u-margin-top--1 vads-u-margin-bottom--2p5">
{(accreditationType && (
<span className="capitalize-value">{accreditationType}</span>
)) ||
'N/A'}
</p>
</div>
<div className="vads-u-flex--1">
<p className="secondary-info-label-large">
<strong>GI Bill students:</strong>
</p>
<p className="vads-u-margin-top--1 vads-u-margin-bottom--2p5">
{studentCount || '0'}
</p>
</div>
</>
);
const programHours = () => {
if (programLengthInHours && programLengthInHours.length > 0) {
const maxHours = Math.max(...programLengthInHours);
const minHours = Math.min(...programLengthInHours);
return `${
minHours === maxHours ? minHours : `${minHours} - ${maxHours}`
} hours`;
}
return 'TBD';
};
const vettecInstitutionDetails = (
<>
<div className="vads-u-flex--1">
<p className="secondary-info-label-large">
<strong>Approved programs:</strong>
</p>
<p className="vads-u-margin-top--1 vads-u-margin-bottom--2p5">
{programCount || 0}
</p>
</div>
<div className="vads-u-flex--1">
<p className="secondary-info-label-large">
<strong>Program length:</strong>
</p>
<p className="vads-u-margin-top--1 vads-u-margin-bottom--2p5">
{programHours()}
</p>
</div>
</>
);
return (
<div
key={institution.facilityCode}
className={resultCardClasses}
id={`${createId(name)}-result-card`}
>
<div className={containerClasses}>
{location && <span id={`${createId(name)}-result-card-placeholder`} />}
{header}
<div className="result-card-container vads-u-background-color--gray-lightest">
<SchoolClassification institution={institution} />
<div className="vads-u-padding-x--2 vads-u-margin-bottom--1">
{nameCityStateHeader}
{schoolProvider && ratingsInformation}
{preferredProvider && (
<span className="preferred-provider-text">
<va-icon icon="star" size={3} class="vads-u-color--gold" />
<strong> Preferred Provider</strong>
</span>
)}
</div>
{cautionFlags.length > 0 && (
<div className="caution-flag-section">
<CautionFlagAdditionalInfo
cautionFlags={cautionFlags}
expanded={expanded}
toggleExpansion={toggleExpansion}
/>
</div>
)}
<>
{showSchoolContentBasedOnType(type) &&
type !== 'FOREIGN' && (
<div
className={classNames(
'vads-u-padding-x--2 vads-u-margin-bottom--4',
{
'vads-u-border-top--3px': cautionFlags.length === 0,
'vads-u-border-color--white': cautionFlags.length === 0,
},
)}
>
{tuitionAndEligibility}
</div>
)}
<div className="vads-u-border-top--3px vads-u-border-color--white vads-u-padding-x--2">
<div className="vads-u-display--flex vads-u-margin-top--1 ">
{!vetTecProvider
? schoolEmployerInstitutionDetails
: vettecInstitutionDetails}
</div>
</div>
</>
<div
className={classNames(
'vads-u-display--flex, vads-u-text-align--center',
{
'vads-u-border-top--3px': !expanded,
'vads-u-border-color--white': !expanded,
},
)}
>
<div className="card-bottom-cell vads-u-flex--1 vads-u-margin--0">
<CompareCheckbox
institution={name}
cityState={`${city}${state && `, ${state}`}`}
compareChecked={compareChecked}
handleCompareUpdate={handleCompareUpdate}
/>
</div>
</div>
</div>
</div>
</div>
);
}
const mapStateToProps = (state, props) => ({
compare: state.compare,
estimated: estimatedBenefits(state, props),
});
const mapDispatchToProps = {
dispatchAddCompareInstitution: addCompareInstitution,
dispatchRemoveCompareInstitution: removeCompareInstitution,
dispatchShowModal: showModal,
};
ResultCard.propTypes = {
compare: PropTypes.object.isRequired,
dispatchAddCompareInstitution: PropTypes.func.isRequired,
dispatchRemoveCompareInstitution: PropTypes.func.isRequired,
dispatchShowModal: PropTypes.func.isRequired,
estimated: PropTypes.object.isRequired,
institution: PropTypes.object.isRequired,
active: PropTypes.bool,
header: PropTypes.node,
location: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]),
paginationRef: PropTypes.oneOfType([
PropTypes.func,
PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
]),
version: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ResultCard);