Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix filters cache restoration logic + cleaner URL query params and filters cache + fixes state management of facility, lsg body and district in patient filters #7157

Merged
merged 3 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Loading