Skip to content

Commit

Permalink
Appoint a rep - search representative screen (#31604)
Browse files Browse the repository at this point in the history
* Added redux actions for rep search

* Created SelectAccreditedRepresentative component

* Added representative select page

* Added rep search reducer

* Refactored App container

* API constant

* API method for fetching representatives

* Removed redux logic

* Refactored SelectAccreditedRepresentative to make use of api method

* Fixed request url

* Initial search result components

* Splitting non-vet and vets into two chapters

* Added description to veteran-identification

* Temporarily removing confirmClaimantPersonalInformation

* Removed description from veteran personal information

* Passing either name or fullName to search result

* Added condition for veteran vs nonveteran

* Removed key from proptypes

* Removed unnecessary jsx

* Testing for contact rather than phone

---------

Co-authored-by: Ray Messina <48040208+rmessina1010@users.noreply.github.com>
  • Loading branch information
cosu419 and rmessina1010 authored Sep 5, 2024
1 parent b113c13 commit d645092
Show file tree
Hide file tree
Showing 11 changed files with 511 additions and 71 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import environment from 'platform/utilities/environment';
import { fetchAndUpdateSessionExpiration as fetch } from '@department-of-veterans-affairs/platform-utilities/api';
import { REPRESENTATIVES_API } from '../constants/api';
import manifest from '../manifest.json';

export const fetchRepresentatives = async ({ query }) => {
const apiSettings = {
mode: 'cors',
method: 'GET',
headers: {
'X-Key-Inflection': 'camel',
'Sec-Fetch-Mode': 'cors',
'Content-Type': 'application/json',
'Source-App-Name': manifest.entryName,
},
};

const startTime = new Date().getTime();

const apiUrl =
environment.BASE_URL === 'http://localhost:3001'
? `https://staging-api.va.gov`
: `${environment.API_URL}`;

return new Promise((resolve, reject) => {
fetch(`${apiUrl}${REPRESENTATIVES_API}?query=${query}`, apiSettings)
.then(res => {
if (!res.ok) {
throw Error(res.statusText);
}

return res.json();
})
.then(res => {
const endTime = new Date().getTime();
const resultTime = endTime - startTime;
res.meta = {
...res.meta,
resultTime,
};
return res;
})
.then(data => resolve(data), error => reject(error));
});
};
173 changes: 173 additions & 0 deletions src/applications/representative-appoint/components/SearchResult.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import React from 'react';
import PropTypes from 'prop-types';

// import { recordEvent } from '@department-of-veterans-affairs/platform-monitoring/exports';
import { parsePhoneNumber } from '../utilities/helpers';

const SearchResult = ({
representativeName,
addressLine1,
addressLine2,
addressLine3,
city,
stateCode,
zipCode,
phone,
distance,
email,
associatedOrgs,
representativeId,
query,
}) => {
const { contact, extension } = parsePhoneNumber(phone);

const addressExists = addressLine1 || city || stateCode || zipCode;

const address =
[
(addressLine1 || '').trim(),
(addressLine2 || '').trim(),
(addressLine3 || '').trim(),
]
.filter(Boolean)
.join(' ') +
(city ? ` ${city},` : '') +
(stateCode ? ` ${stateCode}` : '') +
(zipCode ? ` ${zipCode}` : '');

// pending analytics event
const recordContactLinkClick = () => {
// recordEvent({
// // prettier-ignore
// 'event': 'appoint-a-rep-search-results-click',
// 'search-query': query?.locationQueryString,
// 'search-filters-list': {
// 'representative-type': query?.representativeType,
// 'representative-name': query?.representativeQueryString,
// },
// 'search-selection': 'Find VA Accredited Rep',
// 'search-results-id': representativeId,
// 'search-results-total-count':
// searchResults?.meta?.pagination?.totalEntries,
// 'search-results-total-pages': searchResults?.meta?.pagination?.totalPages,
// 'search-result-position': key,
// });
};

return (
<va-card class="representative-result-card vads-u-padding--4">
<div className="representative-result-card-content">
<div className="representative-info-heading">
{distance && (
<div
id={`representative-${representativeId}`}
className="vads-u-font-weight--bold vads-u-font-family--serif"
>
{parseFloat(JSON.parse(distance).toFixed(2))} Mi
</div>
)}
{representativeName && (
<>
<div
className="vads-u-font-family--serif vads-u-margin-top--2p5"
id={`result-${representativeId}`}
>
<h3 aria-describedby={`representative-${representativeId}`}>
{representativeName}
</h3>
</div>
{associatedOrgs?.length === 1 && (
<p style={{ marginTop: 0 }}>{associatedOrgs[0]}</p>
)}
</>
)}
</div>
{associatedOrgs?.length > 1 && (
<div className="associated-organizations-info vads-u-margin-top--1p5">
<va-additional-info
trigger="See associated organizations"
disable-border
uswds
>
{associatedOrgs?.map((org, index) => {
return (
<>
<p>{org}</p>
{index < associatedOrgs.length - 1 ? (
<br style={{ lineHeight: '0.625rem' }} />
) : null}
</>
);
})}
</va-additional-info>
</div>
)}

<div className="representative-contact-section vads-u-margin-top--3">
{addressExists && (
<div className="address-link">
<a
href={`https://maps.google.com?saddr=${
query?.context?.location
}&daddr=${address}`}
tabIndex="0"
className="address-anchor"
onClick={() => recordContactLinkClick()}
target="_blank"
rel="noreferrer"
aria-label={`${address} (opens in a new tab)`}
>
{addressLine1}{' '}
{addressLine2 ? (
<>
<br /> {addressLine2}
</>
) : null}{' '}
<br />
{city}, {stateCode} {zipCode}
</a>
</div>
)}
{contact && (
<div className="vads-u-margin-top--1p5">
<va-telephone
contact={contact}
extension={extension}
onClick={() => recordContactLinkClick()}
disable-analytics
/>
</div>
)}
{email && (
<div className="vads-u-margin-top--1p5">
<a
href={`mailto:${email}`}
onClick={() => recordContactLinkClick()}
>
{email}
</a>
</div>
)}
</div>
</div>
</va-card>
);
};

SearchResult.propTypes = {
addressLine1: PropTypes.string,
addressLine2: PropTypes.string,
addressLine3: PropTypes.string,
associatedOrgs: PropTypes.array,
city: PropTypes.string,
distance: PropTypes.string,
email: PropTypes.string,
representativeName: PropTypes.string,
phone: PropTypes.string,
representativeId: PropTypes.string,
stateCode: PropTypes.string,
type: PropTypes.string,
zipCode: PropTypes.string,
};

export default SearchResult;
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import {
VaButton,
VaTextInput,
} from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { fetchRepresentatives } from '../api/fetchRepresentatives';
import SearchResult from './SearchResult';

const SelectAccreditedRepresentative = () => {
const [query, setQuery] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
const [representatives, setRepresentatives] = useState([]);

// const listProps = useMemo(() => ({ ...props, representatives, query }), [
// representatives,
// props,
// query,
// ]);

const handleChange = e => {
setQuery(e.target.value);
};

const handleClick = async () => {
if (!query.trim()) {
setError('Search for a representative');
return;
}

setLoading(true);
setError(null);
setRepresentatives(null);

try {
const representativeResults = await fetchRepresentatives({ query });
setRepresentatives(representativeResults);
} catch (err) {
setError(err.errorMessage);
} finally {
setLoading(false);
}
};

const searchResults = () => {
if (loading) {
return <va-loading-indicator message="Loading..." set-focus />;
}
if (representatives?.length) {
return (
<>
{representatives.map((rep, index) => {
const representative = rep.data;
return (
<SearchResult
representativeName={
representative.attributes.fullName ||
representative.attributes.name
}
key={index}
type={representative.type}
addressLine1={representative.attributes.addressLine1}
addressLine2={representative.attributes.addressLine2}
addressLine3={representative.attributes.addressLine3}
city={representative.attributes.city}
stateCode={representative.attributes.stateCode}
zipCode={representative.attributes.zipCode}
phone={representative.attributes.phone}
email={representative.attributes.email}
representativeId={representative.id}
/>
);
})}
</>
);
}
return null;
};

return (
<>
<va-card role="search">
<label
htmlFor="representative-search"
id="representative-search-label"
className="vads-u-margin-top--0 vads-u-margin-bottom--1p5"
>
<VaTextInput
id="representative_search"
name="representative_search"
error={error}
onInput={handleChange}
required
/>
<VaButton
data-testid="representative-search-btn"
text="Search"
onClick={handleClick}
/>
</label>
</va-card>

{searchResults()}
</>
);
};

SelectAccreditedRepresentative.propTypes = {
fetchRepresentatives: PropTypes.func,
};

export default SelectAccreditedRepresentative;
Loading

0 comments on commit d645092

Please sign in to comment.