src/applications/caregivers/components/FormFields/FacilitySearch.jsx
import React, { useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import { VaSearchInput } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { setData } from 'platform/forms-system/src/js/actions';
import FormNavButtons from 'platform/forms-system/src/js/components/FormNavButtons';
import { focusElement } from 'platform/utilities/ui';
import { fetchMapBoxGeocoding } from '../../actions/fetchMapBoxGeocoding';
import { fetchFacilities } from '../../actions/fetchFacilities';
import FacilityList from './FacilityList';
import content from '../../locales/en/content.json';
const FacilitySearch = props => {
const { data: formData, goBack, goForward, goToPath } = props;
const [query, setQuery] = useState('');
const [submittedQuery, setSubmittedQuery] = useState('');
const [loading, setLoading] = useState(false);
const [loadingMoreFacilities, setLoadingMoreFacilities] = useState(false);
const [searchInputError, setSearchInputError] = useState(null);
const [facilitiesListError, setFacilitiesListError] = useState(null);
const [facilities, setFacilities] = useState([]);
const [pagination, setPagination] = useState({
currentPage: 0,
totalEntries: 0,
});
const dispatch = useDispatch();
const [coordinates, setCoordinates] = useState({ lat: '', long: '' });
const radius = 500;
const hasFacilities = () => {
return facilities?.length > 0;
};
const hasMoreFacilities = () => {
return facilities?.length < pagination.totalEntries;
};
const isReviewPage = () => {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('review') === 'true';
};
const isCaregiverFacility = () => {
const plannedClinic = formData?.['view:plannedClinic'];
return (
plannedClinic?.veteranSelected?.id === plannedClinic?.caregiverSupport?.id
);
};
const reviewPageGoToPath = () => {
if (isCaregiverFacility()) {
goToPath('/review-and-submit');
} else {
goToPath('/veteran-information/va-medical-center/confirm?review=true');
}
};
const onGoBack = () => {
if (isReviewPage()) {
goToPath('/review-and-submit');
} else {
goBack(formData);
}
};
const onGoForward = () => {
const caregiverSupportFacilityId =
formData?.['view:plannedClinic']?.caregiverSupport?.id;
if (!caregiverSupportFacilityId) {
if (hasFacilities()) {
setFacilitiesListError(
content['validation-facilities--default-required'],
);
} else {
setSearchInputError(content['validation-facilities--default-required']);
}
} else if (isReviewPage()) {
reviewPageGoToPath();
} else {
goForward(formData);
}
};
const facilityListProps = useMemo(
() => {
const caregiverSupport = async facility => {
const offersCaregiverSupport = facility.services?.health?.some(
service => service.serviceId === 'caregiverSupport',
);
if (offersCaregiverSupport) {
return facility;
}
const loadedParent = facilities.find(
entry => entry.id === facility.parent.id,
);
if (loadedParent) {
return loadedParent;
}
const parentFacilityResponse = await fetchFacilities({
facilityIds: [facility.parent.id],
});
if (parentFacilityResponse.errorMessage) {
setSearchInputError(parentFacilityResponse.errorMessage);
return null;
}
return parentFacilityResponse.facilities[0];
};
const setSelectedFacilities = async facilityId => {
setFacilitiesListError(null);
setSearchInputError(null);
const facility = facilities.find(f => f.id === facilityId);
const caregiverSupportFacility = await caregiverSupport(facility);
dispatch(
setData({
...formData,
'view:plannedClinic': {
veteranSelected: facility,
caregiverSupport: caregiverSupportFacility,
},
}),
);
};
return {
...props,
value: formData?.['view:plannedClinic']?.veteranSelected?.id,
onChange: setSelectedFacilities,
facilities,
query: submittedQuery,
error: facilitiesListError,
};
},
[
facilities,
submittedQuery,
props,
dispatch,
formData,
facilitiesListError,
],
);
const handleChange = e => {
setQuery(e.target.value);
};
const handleSearch = async () => {
if (!query.trim()) {
setSearchInputError(content['validation-facilities--search-required']);
return;
}
setLoading(true);
setFacilitiesListError(null);
setSearchInputError(null);
setFacilities([]);
const mapboxResponse = await fetchMapBoxGeocoding(query);
if (mapboxResponse.errorMessage) {
setSearchInputError(mapboxResponse.errorMessage);
setLoading(false);
return;
}
const [longitude, latitude] = mapboxResponse.center;
setCoordinates({ long: longitude, lat: latitude });
const facilitiesResponse = await fetchFacilities({
long: longitude,
lat: latitude,
radius,
perPage: 5,
page: 1,
});
if (facilitiesResponse.errorMessage) {
setSearchInputError(facilitiesResponse.errorMessage);
setLoading(false);
return;
}
setFacilities(facilitiesResponse.facilities);
setPagination(facilitiesResponse.meta.pagination);
setSubmittedQuery(query);
setLoading(false);
focusElement('#caregiver_facility_results');
};
const showMoreFacilities = async e => {
e.preventDefault();
setLoadingMoreFacilities(true);
const facilitiesResponse = await fetchFacilities({
...coordinates,
page: pagination.currentPage + 1,
radius,
perPage: 5,
});
if (facilitiesResponse.errorMessage) {
setSearchInputError(facilitiesResponse.errorMessage);
setLoadingMoreFacilities(false);
return;
}
setFacilities([...facilities, ...facilitiesResponse.facilities]);
setPagination(facilitiesResponse.meta.pagination);
setSubmittedQuery(query);
setLoadingMoreFacilities(false);
};
const loader = () => {
return (
<va-loading-indicator
label={content['app-loading-generic-text']}
message={content['facilities-loading-text']}
set-focus
/>
);
};
const searchResults = () => {
if (loading) {
return loader();
}
if (hasFacilities()) {
return (
<>
<FacilityList {...facilityListProps} />
{loadingMoreFacilities && loader()}
{hasMoreFacilities() && (
<button
type="button"
className="va-button-link"
onClick={showMoreFacilities}
>
Load more facilities
</button>
)}
</>
);
}
return null;
};
const searchError = () => {
return (
<span
className="usa-input-error-message vads-u-margin-bottom--0p5"
role="alert"
>
<span className="sr-only">Error</span>
{searchInputError}
</span>
);
};
return (
<div className="progress-box progress-box-schemaform vads-u-padding-x--0">
<div className="vads-u-margin-y--2 form-panel">
<h3 className="vads-u-color--gray-dark vads-u-margin-top--0">
{content['vet-med-center-search-description']}
</h3>
<p>
Where the VA medical center is located may be different from the
Veteran’s home address.
</p>
<va-card role="search" background>
<div
className={`${
searchInputError ? 'caregiver-facilities-search-input-error' : ''
}`}
>
<label
htmlFor="facility-search"
className="vads-u-margin-top--0 vads-u-margin-bottom--1"
>
{content['form-facilities-search-label']}
<span className="vads-u-color--secondary-dark"> (*Required)</span>
</label>
{searchInputError && searchError()}
<VaSearchInput
label={content['form-facilities-search-label']}
value={query}
onInput={handleChange}
onSubmit={handleSearch}
uswds
/>
</div>
</va-card>
{searchResults()}
<p>
<strong>Note:</strong> We use the location of the Veteran’s health
care facility to find the nearest facility that processes
applications. Only some facilities process caregiver program
applications.
</p>
<FormNavButtons goBack={onGoBack} goForward={onGoForward} />
</div>
</div>
);
};
FacilitySearch.propTypes = {
data: PropTypes.object,
goBack: PropTypes.func,
goForward: PropTypes.func,
goToPath: PropTypes.func,
value: PropTypes.string,
};
export default FacilitySearch;