Skip to content

Commit

Permalink
Merge branch 'main' into O3-4022
Browse files Browse the repository at this point in the history
  • Loading branch information
chibongho authored Oct 21, 2024
2 parents 2743e35 + ffca60d commit c5a2c17
Show file tree
Hide file tree
Showing 34 changed files with 695 additions and 547 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useCallback, useState, useMemo, useRef, useEffect } from 'react';
import PatientSearch from '../compact-patient-search/patient-search.component';
import { Search, Button } from '@carbon/react';
import { Button, Search } from '@carbon/react';
import { useTranslation } from 'react-i18next';
import styles from './compact-patient-search.scss';
import { useInfinitePatientSearch } from '../patient-search.resource';
import { useConfig, navigate, interpolateString } from '@openmrs/esm-framework';
import useArrowNavigation from '../hooks/useArrowNavigation';
import { type PatientSearchConfig } from '../config-schema';
import { useInfinitePatientSearch } from '../patient-search.resource';
import { PatientSearchContext } from '../patient-search-context';
import useArrowNavigation from '../hooks/useArrowNavigation';
import PatientSearch from '../compact-patient-search/patient-search.component';
import styles from './compact-patient-search.scss';

interface CompactPatientSearchProps {
initialSearchTerm: string;
Expand All @@ -21,48 +22,40 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({
buttonProps,
}) => {
const { t } = useTranslation();
const config = useConfig<PatientSearchConfig>();
const inputRef = useRef<HTMLInputElement>(null);
const bannerContainerRef = useRef(null);
const [searchTerm, setSearchTerm] = useState(initialSearchTerm);
const handleChange = useCallback((val) => setSearchTerm(val), [setSearchTerm]);
const showSearchResults = useMemo(() => !!searchTerm?.trim(), [searchTerm]);
const config = useConfig();
const patientSearchResponse = useInfinitePatientSearch(searchTerm, config.includeDead, showSearchResults);
const { data: patients } = patientSearchResponse;

const handleSubmit = useCallback((evt) => {
evt.preventDefault();
}, []);

const handleClear = useCallback(() => {
setSearchTerm('');
}, [setSearchTerm]);
const handleChange = useCallback((val) => setSearchTerm(val), [setSearchTerm]);

const handleReset = useCallback(() => {
setSearchTerm('');
}, [setSearchTerm]);
const handleClear = useCallback(() => setSearchTerm(''), [setSearchTerm]);

// handlePatientSelection: Manually handles everything that needs to happen when a patient
// from the result list is selected. This is used for the arrow navigation, but is not used
// for click handling.
/**
* handlePatientSelection: Manually handles everything that needs to happen when a patient
* from the result list is selected. This is used for the arrow navigation, but is not used
* for click handling.
*/
const handlePatientSelection = useCallback(
(evt, index: number) => {
evt.preventDefault();
(event, index: number) => {
event.preventDefault();
if (selectPatientAction) {
selectPatientAction(patients[index].uuid);
} else {
navigate({
to: `${interpolateString(config.search.patientResultUrl, {
to: interpolateString(config.search.patientChartUrl, {
patientUuid: patients[index].uuid,
})}`,
}),
});
}
handleReset();
handleClear();
},
[config.search, selectPatientAction, patients, handleReset],
[config.search, selectPatientAction, patients, handleClear],
);

const bannerContainerRef = useRef(null);
const inputRef = useRef<HTMLInputElement>(null);

const handleFocusToInput = useCallback(() => {
let len = inputRef.current.value?.length ?? 0;
inputRef.current.setSelectionRange(len, len);
Expand All @@ -86,7 +79,7 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({

return (
<div className={styles.patientSearchBar}>
<form onSubmit={handleSubmit} className={styles.searchArea}>
<form onSubmit={(event) => event.preventDefault()} className={styles.searchArea}>
<Search
autoFocus
className={styles.patientSearchInput}
Expand All @@ -95,19 +88,19 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({
onChange={(event) => handleChange(event.target.value)}
onClear={handleClear}
placeholder={t('searchForPatient', 'Search for a patient by name or identifier number')}
value={searchTerm}
size="lg"
ref={inputRef}
size="lg"
value={searchTerm}
/>
<Button type="submit" onClick={handleSubmit} {...buttonProps}>
<Button type="submit" onClick={(event) => event.preventDefault()} {...buttonProps}>
{t('search', 'Search')}
</Button>
</form>
{showSearchResults && (
<PatientSearchContext.Provider
value={{
nonNavigationSelectPatientAction: selectPatientAction,
patientClickSideEffect: handleReset,
patientClickSideEffect: handleClear,
}}>
<div className={styles.floatingSearchResultsContainer}>
<PatientSearch query={searchTerm} ref={bannerContainerRef} {...patientSearchResponse} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
useConfig,
} from '@openmrs/esm-framework';
import type { FHIRIdentifier, FHIRPatientType, Identifier, SearchedPatient } from '../types';
import { type PatientSearchConfig } from '../config-schema';
import { PatientSearchContext } from '../patient-search-context';
import styles from './compact-patient-banner.scss';

Expand All @@ -30,7 +31,7 @@ interface IdentifierTagProps {
}

const CompactPatientBanner = forwardRef<HTMLDivElement, CompactPatientBannerProps>(({ patients }, ref) => {
const config = useConfig();
const config = useConfig<PatientSearchConfig>();
const { t } = useTranslation();

const getGender = (gender: string) => {
Expand Down Expand Up @@ -135,7 +136,7 @@ const CompactPatientBanner = forwardRef<HTMLDivElement, CompactPatientBannerProp

const ClickablePatientContainer = ({ patient, children }: ClickablePatientContainerProps) => {
const { nonNavigationSelectPatientAction, patientClickSideEffect } = useContext(PatientSearchContext);
const config = useConfig();
const config = useConfig<PatientSearchConfig>();
const isDeceased = Boolean(patient?.person?.deathDate);

if (nonNavigationSelectPatientAction) {
Expand All @@ -160,9 +161,9 @@ const ClickablePatientContainer = ({ patient, children }: ClickablePatientContai
})}
key={patient.uuid}
onBeforeNavigate={() => patientClickSideEffect?.(patient.uuid)}
to={`${interpolateString(config.search.patientResultUrl, {
to={interpolateString(config.search.patientChartUrl, {
patientUuid: patient.uuid,
})}`}>
})}>
{children}
</ConfigurableLink>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import React, { useCallback, useRef, useState, useEffect } from 'react';
import { navigate, interpolateString, useConfig, useSession, useDebounce } from '@openmrs/esm-framework';
import useArrowNavigation from '../hooks/useArrowNavigation';
import type { SearchedPatient } from '../types';
import { useRecentlyViewedPatients, useInfinitePatientSearch, useRESTPatients } from '../patient-search.resource';
import { useTranslation } from 'react-i18next';
import { navigate, interpolateString, useConfig, useSession, useDebounce, showSnackbar } from '@openmrs/esm-framework';
import { type PatientSearchConfig } from '../config-schema';
import { type SearchedPatient } from '../types';
import { useRecentlyViewedPatients, useInfinitePatientSearch, useRestPatients } from '../patient-search.resource';
import { PatientSearchContext } from '../patient-search-context';
import useArrowNavigation from '../hooks/useArrowNavigation';
import PatientSearch from './patient-search.component';
import PatientSearchBar from '../patient-search-bar/patient-search-bar.component';
import RecentlySearchedPatients from './recently-searched-patients.component';
Expand All @@ -22,43 +24,64 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({
onPatientSelect,
shouldNavigateToPatientSearchPage,
}) => {
const { t } = useTranslation();

const bannerContainerRef = useRef(null);
const searchInputRef = useRef<HTMLInputElement>(null);

const [searchTerm, setSearchTerm] = useState(initialSearchTerm);
const debouncedSearchTerm = useDebounce(searchTerm);
const hasSearchTerm = Boolean(debouncedSearchTerm.trim());
const bannerContainerRef = useRef(null);
const searchInputRef = useRef<HTMLInputElement>(null);
const config = useConfig();

const config = useConfig<PatientSearchConfig>();
const { showRecentlySearchedPatients } = config.search;
const patientSearchResponse = useInfinitePatientSearch(debouncedSearchTerm, config.includeDead);
const { data: searchedPatients } = patientSearchResponse;
const { recentlyViewedPatients, addViewedPatient, mutateUserProperties } =
useRecentlyViewedPatients(showRecentlySearchedPatients);
const recentPatientSearchResponse = useRESTPatients(recentlyViewedPatients, !hasSearchTerm);
const { data: recentPatients } = recentPatientSearchResponse;

const {
user,
sessionLocation: { uuid: currentLocation },
} = useSession();

const patientSearchResponse = useInfinitePatientSearch(debouncedSearchTerm, config.includeDead);
const { data: searchedPatients } = patientSearchResponse;

const {
error: errorFetchingUserProperties,
mutateUserProperties,
recentlyViewedPatientUuids,
updateRecentlyViewedPatients,
} = useRecentlyViewedPatients(showRecentlySearchedPatients);

const recentPatientSearchResponse = useRestPatients(recentlyViewedPatientUuids, !hasSearchTerm);
const { data: recentPatients, fetchError } = recentPatientSearchResponse;

const handleFocusToInput = useCallback(() => {
const len = searchInputRef.current.value?.length ?? 0;
searchInputRef.current.setSelectionRange(len, len);
searchInputRef.current.focus();
}, [searchInputRef]);
if (searchInputRef.current) {
const inputElement = searchInputRef.current;
inputElement.setSelectionRange(inputElement.value.length, inputElement.value.length);
inputElement.focus();
}
}, []);

const handleCloseSearchResults = useCallback(() => {
setSearchTerm('');
onPatientSelect?.();
}, [onPatientSelect, setSearchTerm]);

const addViewedPatientAndCloseSearchResults = useCallback(
(patientUuid: string) => {
addViewedPatient(patientUuid).then(() => {
mutateUserProperties();
});
async (patientUuid: string) => {
handleCloseSearchResults();
try {
await updateRecentlyViewedPatients(patientUuid);
await mutateUserProperties();
} catch (error) {
showSnackbar({
kind: 'error',
title: t('errorUpdatingRecentlyViewedPatients', 'Error updating recently viewed patients'),
subtitle: error instanceof Error ? error.message : String(error),
});
}
},
[handleCloseSearchResults, mutateUserProperties, addViewedPatient],
[handleCloseSearchResults, mutateUserProperties, updateRecentlyViewedPatients, t],
);

const handlePatientSelection = useCallback(
Expand All @@ -67,13 +90,13 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({
if (patients) {
addViewedPatientAndCloseSearchResults(patients[index].uuid);
navigate({
to: `${interpolateString(config.search.patientResultUrl, {
to: interpolateString(config.search.patientChartUrl, {
patientUuid: patients[index].uuid,
})}`,
}),
});
}
},
[config.search.patientResultUrl, user, currentLocation],
[config.search.patientChartUrl, user, currentLocation],
);
const focusedResult = useArrowNavigation(
!recentPatients ? searchedPatients?.length ?? 0 : recentPatients?.length ?? 0,
Expand All @@ -95,6 +118,24 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({
}
}, [focusedResult, bannerContainerRef, handleFocusToInput]);

useEffect(() => {
if (fetchError) {
showSnackbar({
kind: 'error',
title: t('errorFetchingPatients', 'Error fetching patients'),
subtitle: fetchError?.message,
});
}

if (errorFetchingUserProperties) {
showSnackbar({
kind: 'error',
title: t('errorFetchingUserProperties', 'Error fetching user properties'),
subtitle: errorFetchingUserProperties?.message,
});
}
}, [fetchError, errorFetchingUserProperties]);

const handleSubmit = useCallback(
(debouncedSearchTerm) => {
if (shouldNavigateToPatientSearchPage && debouncedSearchTerm.trim()) {
Expand Down Expand Up @@ -122,7 +163,7 @@ const CompactPatientSearchComponent: React.FC<CompactPatientSearchProps> = ({
}}>
<div className={styles.patientSearchBar}>
<PatientSearchBar
small
isCompact
initialSearchTerm={initialSearchTerm ?? ''}
onChange={handleSearchTermChange}
onSubmit={handleSubmit}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ describe('CompactPatientSearchComponent', () => {
search: {
showRecentlySearchedPatients: true,
disableTabletSearchOnKeyUp: true,
patientResultUrl: configSchema.search.patientResultUrl._default,
},
} as PatientSearchConfig['search'],
});
render(<CompactPatientSearchComponent isSearchPage={false} initialSearchTerm="" />);
const searchResultsContainer = screen.getByTestId('floatingSearchResultsContainer');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ interface PatientSearchProps extends PatientSearchResponse {
}

const PatientSearch = React.forwardRef<HTMLDivElement, PatientSearchProps>(
({ isLoading, data: searchResults, fetchError, loadingNewData, setPage, hasMore, totalResults }, ref) => {
({ data: searchResults, fetchError, hasMore, isLoading, isValidating, setPage, totalResults }, ref) => {
const { t } = useTranslation();
const observer = useRef(null);
const loadingIconRef = useCallback(
(node) => {
if (loadingNewData) {
if (isValidating) {
return;
}
if (observer.current) {
Expand All @@ -37,7 +37,7 @@ const PatientSearch = React.forwardRef<HTMLDivElement, PatientSearchProps>(
observer.current.observe(node);
}
},
[loadingNewData, hasMore, setPage],
[isValidating, hasMore, setPage],
);

if (isLoading) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,24 @@
color: $text-02;
line-height: layout.$spacing-05;
margin: layout.$spacing-03 layout.$spacing-05;
display: flex;
align-items: center;
}

.resultsTextCount {
flex: 1;
}

.validationIcon {
flex: 1;
justify-content: center;
align-items: center;
}

.spinner {
&:global(.cds--inline-loading) {
min-height: layout.$spacing-05 !important;
}
}

.helperText {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const defaultProps = {
fetchError: null,
hasMore: false,
isLoading: false,
loadingNewData: false,
isValidating: false,
setPage: jest.fn(),
totalResults: 1,
query: 'John',
Expand Down
Loading

0 comments on commit c5a2c17

Please sign in to comment.