Skip to content

Commit

Permalink
Fix filters cache restoration logic + cleaner URL query params and fi…
Browse files Browse the repository at this point in the history
…lters cache + fixes state management of facility, lsg body and district in patient filters (#7157)

* Fix when filters cache is applied and ignore unapllied filters in query params and cache

* fixes #7166; fix selected state of facility, lsg body and district

* remove unused redux actions
  • Loading branch information
rithviknishad authored Feb 5, 2024
1 parent d259c20 commit c08a7f5
Show file tree
Hide file tree
Showing 9 changed files with 140 additions and 102 deletions.
55 changes: 25 additions & 30 deletions src/Common/hooks/useFilters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import GenericFilterBadge from "../../CAREUI/display/FilterBadge";
import PaginationComponent from "../../Components/Common/Pagination";
import useConfig from "./useConfig";
import { classNames } from "../../Utils/utils";
import FiltersCache from "../../Utils/FiltersCache";

export type FilterState = Record<string, unknown>;
export type FilterParamKeys = string | string[];

interface FilterBadgeProps {
name: string;
value?: string;
paramKey: FilterParamKeys;
paramKey: string | string[];
}

/**
Expand All @@ -32,18 +32,18 @@ export default function useFilters({
const [showFilters, setShowFilters] = useState(false);
const [qParams, _setQueryParams] = useQueryParams();

const updateCache = (query: QueryParam) => {
const blacklist = FILTERS_CACHE_BLACKLIST.concat(cacheBlacklist);
FiltersCache.set(query, blacklist);
};

const setQueryParams = (
query: QueryParam,
options?: setQueryParamsOptions
) => {
const updatedQParams = { ...query };

for (const param of cacheBlacklist) {
delete updatedQParams[param];
}

query = FiltersCache.utils.clean(query);
_setQueryParams(query, options);
updateFiltersCache(updatedQParams);
updateCache(query);
};

const updateQuery = (filter: FilterState) => {
Expand All @@ -61,15 +61,22 @@ export default function useFilters({
const removeFilter = (param: string) => removeFilters([param]);

useEffect(() => {
const cache = getFiltersCache();
const qParamKeys = Object.keys(qParams);
const canSkip = Object.keys(cache).every(
(key) => qParamKeys.includes(key) && qParams[key] === cache[key]
);
if (canSkip) return;
if (Object.keys(cache).length) {
setQueryParams(cache);

// If we navigate to a path that has query params set on mount,
// skip restoring the cache, instead update the cache with new filters.
if (qParamKeys.length) {
updateCache(qParams);
return;
}

const cache = FiltersCache.get();
if (!cache) {
return;
}

// Restore cache
setQueryParams(cache);
}, []);

const FilterBadge = ({ name, value, paramKey }: FilterBadgeProps) => {
Expand Down Expand Up @@ -99,7 +106,7 @@ export default function useFilters({
};

const badgeUtils = {
badge(name: string, paramKey: FilterParamKeys) {
badge(name: string, paramKey: FilterBadgeProps["paramKey"]) {
return { name, paramKey };
},
ordering(name = "Sort by", paramKey = "ordering") {
Expand All @@ -109,7 +116,7 @@ export default function useFilters({
value: qParams[paramKey] && t("SortOptions." + qParams[paramKey]),
};
},
value(name: string, paramKey: FilterParamKeys, value: string) {
value(name: string, paramKey: FilterBadgeProps["paramKey"], value: string) {
return { name, value, paramKey };
},
phoneNumber(name = "Phone Number", paramKey = "phone_number") {
Expand Down Expand Up @@ -278,15 +285,3 @@ const removeFromQuery = (query: Record<string, unknown>, params: string[]) => {
};

const FILTERS_CACHE_BLACKLIST = ["page", "limit", "offset"];

const getFiltersCacheKey = () => `filters--${window.location.pathname}`;
const getFiltersCache = () => {
return JSON.parse(localStorage.getItem(getFiltersCacheKey()) || "{}");
};
const updateFiltersCache = (cache: Record<string, unknown>) => {
const result = { ...cache };
for (const param of FILTERS_CACHE_BLACKLIST) {
delete result[param];
}
localStorage.setItem(getFiltersCacheKey(), JSON.stringify(result));
};
4 changes: 2 additions & 2 deletions src/Components/Auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import useConfig from "../../Common/hooks/useConfig";
import CircularProgress from "../Common/components/CircularProgress";
import ReactMarkdown from "react-markdown";
import rehypeRaw from "rehype-raw";
import { invalidateFiltersCache } from "../../Utils/utils";
import { useAuthContext } from "../../Common/hooks/useAuthUser";
import FiltersCache from "../../Utils/FiltersCache";

export const Login = (props: { forgot?: boolean }) => {
const { signIn } = useAuthContext();
Expand Down Expand Up @@ -93,7 +93,7 @@ export const Login = (props: { forgot?: boolean }) => {
const handleSubmit = async (e: any) => {
e.preventDefault();
setLoading(true);
invalidateFiltersCache();
FiltersCache.invaldiateAll();
const validated = validateData();
if (!validated) {
setLoading(false);
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Common/FacilitySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface FacilitySelectProps {
showAll?: boolean;
showNOptions?: number;
freeText?: boolean;
selected: FacilityModel | FacilityModel[] | null;
selected?: FacilityModel | FacilityModel[] | null;
setSelected: (selected: FacilityModel | FacilityModel[] | null) => void;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Components/Facility/FacilityFilter/DistrictSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ interface DistrictSelectProps {
errors: string;
className?: string;
multiple?: boolean;
selected: string;
selected?: string;
setSelected: (selected: string) => void;
}

Expand Down
89 changes: 32 additions & 57 deletions src/Components/Patient/PatientFilter.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import dayjs from "dayjs";
import { useCallback, useEffect } from "react";
import CareIcon from "../../CAREUI/icons/CareIcon";
import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover";
import {
Expand All @@ -12,12 +11,6 @@ import {
} from "../../Common/constants";
import useConfig from "../../Common/hooks/useConfig";
import useMergeState from "../../Common/hooks/useMergeState";
import {
getAllLocalBody,
getAnyFacility,
getDistrict,
} from "../../Redux/actions";
import { useDispatch } from "react-redux";
import { dateQueryString } from "../../Utils/utils";
import { DateRange } from "../Common/DateRangeInputV2";
import { FacilitySelect } from "../Common/FacilitySelect";
Expand All @@ -35,6 +28,9 @@ import {
import MultiSelectMenuV2 from "../Form/MultiSelectMenuV2";
import SelectMenuV2 from "../Form/SelectMenuV2";
import DiagnosesFilter, { FILTER_BY_DIAGNOSES_KEYS } from "./DiagnosesFilter";
import useQuery from "../../Utils/request/useQuery";
import routes from "../../Redux/api";
import request from "../../Utils/request/request";

const getDate = (value: any) =>
value && dayjs(value).isValid() && dayjs(value).toDate();
Expand Down Expand Up @@ -105,37 +101,24 @@ export default function PatientFilter(props: any) {
diagnoses_unconfirmed: filter.diagnoses_unconfirmed || null,
diagnoses_differential: filter.diagnoses_differential || null,
});
const dispatch: any = useDispatch();

useEffect(() => {
async function fetchData() {
if (filter.facility) {
const { data: facilityData } = await dispatch(
getAnyFacility(filter.facility, "facility")
);
setFilterState({ facility_ref: facilityData });
}

if (filter.district) {
const { data: districtData } = await dispatch(
getDistrict(filter.district, "district")
);
setFilterState({ district_ref: districtData });
}
useQuery(routes.getAnyFacility, {
pathParams: { id: filter.facility },
prefetch: !!filter.facility,
onResponse: ({ data }) => setFilterState({ facility_ref: data }),
});

if (filter.lsgBody) {
const { data: lsgRes } = await dispatch(getAllLocalBody({}));
const lsgBodyData = lsgRes.results;
useQuery(routes.getDistrict, {
pathParams: { id: filter.district },
prefetch: !!filter.district,
onResponse: ({ data }) => setFilterState({ district_ref: data }),
});

setFilterState({
lsgBody_ref: lsgBodyData.filter(
(obj: any) => obj.id.toString() === filter.lsgBody.toString()
)[0],
});
}
}
fetchData();
}, [dispatch]);
useQuery(routes.getLocalBody, {
pathParams: { id: filter.lsgBody },
prefetch: !!filter.lsgBody,
onResponse: ({ data }) => setFilterState({ lsgBody_ref: data }),
});

const VACCINATED_FILTER = [
{ id: "0", text: "Unvaccinated" },
Expand All @@ -161,21 +144,19 @@ export default function PatientFilter(props: any) {
{ id: "false", text: "No" },
];

const setFacility = (selected: any, name: string) => {
const filterData: any = { ...filterState };
filterData[`${name}_ref`] = selected;
filterData[name] = (selected || {}).id;

setFilterState(filterData);
const setFilterWithRef = (name: string, selected?: any) => {
setFilterState({
[`${name}_ref`]: selected,
[name]: selected?.id,
});
};

const lsgSearch = useCallback(
async (search: string) => {
const res = await dispatch(getAllLocalBody({ local_body_name: search }));
return res?.data?.results;
},
[dispatch]
);
const lsgSearch = async (search: string) => {
const { data } = await request(routes.getAllLocalBody, {
query: { local_body_name: search },
});
return data?.results;
};

const applyFilter = () => {
const {
Expand Down Expand Up @@ -585,7 +566,7 @@ export default function PatientFilter(props: any) {
name="facility"
showAll={false}
selected={filterState.facility_ref}
setSelected={(obj) => setFacility(obj, "facility")}
setSelected={(obj) => setFilterWithRef("facility", obj)}
/>
</div>
{filterState.facility && (
Expand Down Expand Up @@ -629,13 +610,7 @@ export default function PatientFilter(props: any) {
name="lsg_body"
selected={filterState.lsgBody_ref}
fetchData={lsgSearch}
onChange={(selected) =>
setFilterState({
...filterState,
lsgBody_ref: selected,
lsgBody: selected.id,
})
}
onChange={(obj) => setFilterWithRef("lsgBody", obj)}
optionLabel={(option) => option.name}
compareBy="id"
/>
Expand All @@ -648,7 +623,7 @@ export default function PatientFilter(props: any) {
multiple={false}
name="district"
selected={filterState.district_ref}
setSelected={(obj: any) => setFacility(obj, "district")}
setSelected={(obj) => setFilterWithRef("district", obj)}
errors={""}
/>
</div>
Expand Down
3 changes: 0 additions & 3 deletions src/Redux/actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,6 @@ export const getWardByLocalBody = (pathParam: object) => {
export const getLocalBody = (pathParam: object) => {
return fireRequest("getLocalBody", [], {}, pathParam);
};
export const getAllLocalBody = (params: object) => {
return fireRequest("getAllLocalBody", [], params);
};

// Sample Test
export const getSampleTestList = (params: object, pathParam: object) => {
Expand Down
2 changes: 2 additions & 0 deletions src/Redux/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,8 @@ const routes = {
},
getAllLocalBody: {
path: "/api/v1/local_body/",
method: "GET",
TRes: Type<PaginatedResponse<LocalBodyModel>>(),
},
getLocalbodyByName: {
path: "/api/v1/local_body/",
Expand Down
77 changes: 77 additions & 0 deletions src/Utils/FiltersCache.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
type Filters = Record<string, unknown>;

/**
* @returns The filters cache key associated to the current window URL
*/
const getKey = () => {
return `filters--${window.location.pathname}`;
};

/**
* Returns a sanitized filter object that ignores filters with no value or
* filters that are part of the blacklist.
*
* @param filters Input filters to be sanitized
* @param blacklist Optional array of filter keys that are to be ignored.
*/
const clean = (filters: Filters, blacklist?: string[]) => {
const reducer = (cleaned: Filters, key: string) => {
const valueAllowed = (filters[key] ?? "") != "";
if (valueAllowed && !blacklist?.includes(key)) {
cleaned[key] = filters[key];
}
return cleaned;
};

return Object.keys(filters).reduce(reducer, {});
};

/**
* Retrieves the cached filters
*/
const get = (key?: string) => {
const content = localStorage.getItem(key ?? getKey());
return content ? (JSON.parse(content) as Filters) : null;
};

/**
* Sets the filters cache with the specified filters.
*/
const set = (filters: Filters, blacklist?: string[], key?: string) => {
key ??= getKey();
filters = clean(filters, blacklist);

if (Object.keys(filters).length) {
localStorage.setItem(key, JSON.stringify(filters));
} else {
invalidate(key);
}
};

/**
* Removes the filters cache for the specified key or current URL.
*/
const invalidate = (key?: string) => {
localStorage.removeItem(key ?? getKey());
};

/**
* Removes all filters cache in the platform.
*/
const invaldiateAll = () => {
for (const key in localStorage) {
if (key.startsWith("filters--")) {
invalidate(key);
}
}
};

export default {
get,
set,
invalidate,
invaldiateAll,
utils: {
clean,
},
};
Loading

0 comments on commit c08a7f5

Please sign in to comment.