Skip to content

Commit

Permalink
(fix) Fixed the default login location coming in the every term search
Browse files Browse the repository at this point in the history
  • Loading branch information
vasharma05 committed Dec 14, 2023
1 parent 0038de5 commit e548826
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,36 @@ import {
RadioButtonGroup,
RadioButtonSkeleton,
} from '@carbon/react';
import { navigate, setSessionLocation, useConfig, useConnectivity, useSession } from '@openmrs/esm-framework';
import {
navigate,
setSessionLocation,
useConfig,
useConnectivity,
useDebounce,
useSession,
} from '@openmrs/esm-framework';
import type { LoginReferrer } from '../login/login.component';
import { useLoginLocations } from '../login.resource';
import styles from './location-picker.scss';
import { useDefaultLocation } from './location-picker.resource';
import { useDefaultLocation, useInfiniteScrolling, useLoginLocations } from './location-picker.resource';
import { ConfigSchema } from '../config-schema';

interface LocationPickerProps {
hideWelcomeMessage?: boolean;
currentLocationUuid?: string;
}
interface LocationPickerProps {}

const LocationPicker: React.FC<LocationPickerProps> = ({ hideWelcomeMessage, currentLocationUuid }) => {
const LocationPicker: React.FC<LocationPickerProps> = () => {
const { t } = useTranslation();
const config = useConfig<ConfigSchema>();
const { chooseLocation } = config;
const isLoginEnabled = useConnectivity();
const [searchTerm, setSearchTerm] = useState(null);
const debouncedSearchQuery = useDebounce(searchTerm);
const [searchParams] = useSearchParams();

const isUpdateFlow = useMemo(() => searchParams.get('update') === 'true', [searchParams]);
const { userDefaultLocationUuid, updateDefaultLocation, savePreference, setSavePreference, defaultLocationFhir } =
useDefaultLocation(isUpdateFlow);

const [searchTerm, setSearchTerm] = useState(null);
useDefaultLocation(isUpdateFlow, debouncedSearchQuery);

const { user, sessionLocation } = useSession();
const { currentUser, userProperties } = useMemo(
const { currentUser } = useMemo(
() => ({
currentUser: user?.display,
userUuid: user?.uuid,
Expand All @@ -50,22 +54,20 @@ const LocationPicker: React.FC<LocationPickerProps> = ({ hideWelcomeMessage, cur
hasMore,
loadingNewData,
setPage,
} = useLoginLocations(chooseLocation.useLoginLocationTag, chooseLocation.locationsPerRequest, searchTerm);
} = useLoginLocations(chooseLocation.useLoginLocationTag, chooseLocation.locationsPerRequest, debouncedSearchQuery);

const locations = useMemo(() => {
if (!defaultLocationFhir?.length || !fetchedLocations) {
return fetchedLocations;
}

return [
...(defaultLocationFhir ?? []),
...fetchedLocations?.filter(({ resource }) => resource.id !== defaultLocationFhir?.[0].resource.id),
];
}, [defaultLocationFhir, fetchedLocations]);

const [activeLocation, setActiveLocation] = useState(() => {
if (currentLocationUuid && hideWelcomeMessage) {
return currentLocationUuid;
}
return sessionLocation?.uuid ?? userDefaultLocationUuid;
});

Expand Down Expand Up @@ -124,9 +126,8 @@ const LocationPicker: React.FC<LocationPickerProps> = ({ hideWelcomeMessage, cur
}
}, [changeLocation, isSubmitting, userDefaultLocationUuid, isUpdateFlow]);

const search = (location: string) => {
setActiveLocation('');
setSearchTerm(location);
const handleSearch = (event) => {
setSearchTerm(event.target.value);
};

const handleSubmit = useCallback(
Expand All @@ -140,27 +141,15 @@ const LocationPicker: React.FC<LocationPickerProps> = ({ hideWelcomeMessage, cur
[activeLocation, changeLocation, savePreference],
);

// Infinite scroll
const observer = useRef(null);
const loadingIconRef = useCallback(
(node: HTMLDivElement) => {
if (loadingNewData) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && hasMore) {
setPage((page) => page + 1);
}
},
{
threshold: 1,
},
);
if (node) observer.current.observe(node);
},
[loadingNewData, hasMore, setPage],
);
const handleFetchNextSet = useCallback(() => {
setPage((page) => page + 1);
}, [setPage]);

const { observer, loadingIconRef } = useInfiniteScrolling({
inLoadingState: loadingNewData,
onIntersection: handleFetchNextSet,
shouldLoadMore: hasMore,
});
const reloadIndex = hasMore ? Math.floor(locations.length * 0.5) : -1;

return (
Expand All @@ -183,9 +172,10 @@ const LocationPicker: React.FC<LocationPickerProps> = ({ hideWelcomeMessage, cur
labelText={t('searchForLocation', 'Search for a location')}
id="search-1"
placeholder={t('searchForLocation', 'Search for a location')}
onChange={(event) => search(event.target.value)}
onChange={handleSearch}
name="searchForLocation"
size="lg"
value={searchTerm}
/>
<div className={styles.searchResults}>
{isLoading ? (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,153 @@
import { setUserProperties, showToast, useSession } from '@openmrs/esm-framework';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useValidateLocationUuid } from '../login.resource';
import useSwrInfinite from 'swr/infinite';
import useSwrImmutable from 'swr/immutable';
import {
FetchResponse,
fhirBaseUrl,
openmrsFetch,
setUserProperties,
showNotification,
showToast,
useSession,
} from '@openmrs/esm-framework';
import { LocationEntry, LocationResponse } from '../types';

export function useDefaultLocation(isUpdateFlow: boolean) {
interface LoginLocationData {
locations: Array<LocationEntry>;
isLoading: boolean;
totalResults: number;
hasMore: boolean;
loadingNewData: boolean;
setPage: (size: number | ((_size: number) => number)) => Promise<FetchResponse<LocationResponse>[]>;
}

export function useLoginLocations(
useLoginLocationTag: boolean,
count: number = 0,
searchQuery: string = '',
): LoginLocationData {
const { t } = useTranslation();
function constructUrl(page: number, prevPageData: FetchResponse<LocationResponse>) {
if (prevPageData) {
const nextLink = prevPageData.data?.link?.find((link) => link.relation === 'next');

if (!nextLink) {
return null;
}

const nextUrl = new URL(nextLink.url);
// default for production
if (nextUrl.origin === window.location.origin) {
return nextLink.url;
}

// in development, the request should be funnelled through the local proxy
return new URL(
`${nextUrl.pathname}${nextUrl.search ? `?${nextUrl.search}` : ''}`,
window.location.origin,
).toString();
}

let url = `${fhirBaseUrl}/Location?`;
let urlSearchParameters = new URLSearchParams();
urlSearchParameters.append('_summary', 'data');

if (count) {
urlSearchParameters.append('_count', '' + count);
}

if (page) {
urlSearchParameters.append('_getpagesoffset', '' + page * count);
}

if (useLoginLocationTag) {
urlSearchParameters.append('_tag', 'Login Location');
}

if (typeof searchQuery === 'string' && searchQuery != '') {
urlSearchParameters.append('name:contains', searchQuery);
}

return url + urlSearchParameters.toString();
}

const { data, isLoading, isValidating, setSize, error } = useSwrInfinite<FetchResponse<LocationResponse>, Error>(
constructUrl,
openmrsFetch,
);

if (error) {
showNotification({
title: t('errorLoadingLoginLocations', 'Error loading login locations'),
kind: 'error',
critical: true,
description: error?.message,
});
}

const memoizedLocations = useMemo(() => {
return {
locations: data?.length ? data?.flatMap((entries) => entries?.data?.entry ?? []) : null,
isLoading,
totalResults: data?.[0]?.data?.total ?? null,
hasMore: data?.length ? data?.[data.length - 1]?.data?.link?.some((link) => link.relation === 'next') : false,
loadingNewData: isValidating,
setPage: setSize,
};
}, [isLoading, data, isValidating, setSize]);

return memoizedLocations;
}

/**
* The hook is created to validate a locationUuid and also allow searching with a search term
* for the same location so that the searching by name is validated from the backend itself,
* which is similar to the hook `useLoginLocations`.
* The result from this hook and `useLoginLocations` are merged and hence searching is
* required as well in this hook
*/
export function useValidateLocationUuid(locationUuid: string, searchTerm?: string) {
const [isLocationValid, setIsLocationValid] = useState(false);
let urlSearchParameters = new URLSearchParams();
urlSearchParameters.append('_id', locationUuid);
if (searchTerm) {
urlSearchParameters.append('name:contains', searchTerm);
}
const url = locationUuid ? `/ws/fhir2/R4/Location?${urlSearchParameters.toString()}` : null;
const { data, error, isLoading } = useSwrImmutable<FetchResponse<LocationResponse>>(url, openmrsFetch, {
shouldRetryOnError(err) {
if (err?.response?.status) {
return err.response.status >= 500;
}
return false;
},
});

useEffect(() => {
// We can only validate a locationUuid if there is no search term filtering the result any more.
if (!searchTerm) {
setIsLocationValid(data?.ok && data?.data?.total > 0);
}
}, [data?.data?.total, data?.ok, searchTerm]);

const results = useMemo(
() => ({
isLocationValid,
location: data?.data?.entry ?? [],
error,
isLoading,
}),
[isLocationValid, data?.data?.entry, error, isLoading],
);
return results;
}

export function useDefaultLocation(isUpdateFlow: boolean, searchTerm: string) {
const { t } = useTranslation();
const [savePreference, setSavePreference] = useState(false);
const { user } = useSession();

const { userUuid, userProperties } = useMemo(
() => ({
userUuid: user?.uuid,
Expand All @@ -15,11 +156,12 @@ export function useDefaultLocation(isUpdateFlow: boolean) {
[user],
);

const userDefaultLocationUuid = useMemo(() => {
return userProperties?.defaultLocation;
}, [userProperties?.defaultLocation]);
const userDefaultLocationUuid = useMemo(() => userProperties?.defaultLocation, [userProperties?.defaultLocation]);

const { isLocationValid, defaultLocation: defaultLocationFhir } = useValidateLocationUuid(userDefaultLocationUuid);
const { isLocationValid, location: defaultFhirLocation } = useValidateLocationUuid(
userDefaultLocationUuid,
searchTerm,
);

useEffect(() => {
if (userDefaultLocationUuid) {
Expand Down Expand Up @@ -76,10 +218,44 @@ export function useDefaultLocation(isUpdateFlow: boolean) {
);

return {
defaultLocationFhir,
defaultFhirLocation,
userDefaultLocationUuid: isLocationValid ? userDefaultLocationUuid : null,
updateDefaultLocation,
savePreference,
setSavePreference,
};
}

export function useInfiniteScrolling({
inLoadingState,
shouldLoadMore,
onIntersection,
}: {
inLoadingState: boolean;
shouldLoadMore: boolean;
onIntersection: () => void;
}) {
const observer = useRef(null);
const loadingIconRef = useCallback(
(node: HTMLDivElement) => {
if (inLoadingState) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting && shouldLoadMore) {
onIntersection();
}
},
{
threshold: 1,
},
);
if (node) observer.current.observe(node);
},
[inLoadingState, shouldLoadMore, onIntersection],
);
return {
observer,
loadingIconRef,
};
}
Loading

0 comments on commit e548826

Please sign in to comment.