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 : View(list/card/board) retention for encounters/resource/users pages #10039

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
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
10 changes: 2 additions & 8 deletions src/Routers/routes/ResourceRoutes.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
import { Redirect } from "raviger";

import View from "@/components/Common/View";
import BoardView from "@/components/Resource/ResourceBoard";
import ResourceDetails from "@/components/Resource/ResourceDetails";
import { ResourceDetailsUpdate } from "@/components/Resource/ResourceDetailsUpdate";
import ListView from "@/components/Resource/ResourceList";

import { AppRoutes } from "@/Routers/AppRouter";

const getDefaultView = () =>
localStorage.getItem("defaultResourceView") === "list" ? "list" : "board";

const ResourceRoutes: AppRoutes = {
"/resource": () => <Redirect to={`/resource/${getDefaultView()}`} />,
"/resource/board": () => <BoardView />,
"/resource/list": () => <ListView />,
"/resource": () => <View name="resource" board={BoardView} list={ListView} />,
"/resource/:id": ({ id }) => <ResourceDetails id={id} />,
"/resource/:id/update": ({ id }) => <ResourceDetailsUpdate id={id} />,
};
Expand Down
23 changes: 23 additions & 0 deletions src/Utils/ViewCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const getKey = (name: string) => {
return `${name}`;
};

/**
* Clears the view preference.
*/
const invalidate = (name: string) => {
localStorage.removeItem(getKey(name));
};

/**
* Clears all the view preferences.
*/
const invalidateAll = () => {
const viewKeys = ["users", "resource", "appointments"];
viewKeys.forEach(invalidate);
};

export default {
invalidate,
invalidateAll,
};
26 changes: 26 additions & 0 deletions src/Utils/useView.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { useEffect, useState } from "react";

export function useView(
name: string,
defaultView: string,
): [string, (view: string) => void] {
const [view, setView] = useState(() => {
return localStorage.getItem(name) || defaultView;
});
const updateView = (newView: string) => {
localStorage.setItem(name, newView);
setView(newView);
};
useEffect(() => {
const interval = setInterval(() => {
const storedView = localStorage.getItem(name);
if (storedView !== view) {
setView(storedView || defaultView);
}
}, 100);
return () => {
clearInterval(interval);
};
}, [name, view]);
Comment on lines +14 to +24
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace polling with storage event listener.

The current implementation uses polling with a 100ms interval, which is inefficient. Consider using the storage event listener instead.

 useEffect(() => {
-  const interval = setInterval(() => {
+  const handleStorageChange = (e: StorageEvent) => {
+    if (e.key === name) {
+      const storedView = e.newValue;
+      if (storedView !== view) {
+        setView(storedView || defaultView);
+      }
+    }
+  };
+
+  window.addEventListener('storage', handleStorageChange);
+  return () => {
+    window.removeEventListener('storage', handleStorageChange);
+  };
-    const storedView = localStorage.getItem(name);
-    if (storedView !== view) {
-      setView(storedView || defaultView);
-    }
-  }, 100);
-  return () => {
-    clearInterval(interval);
-  };
 }, [name, view]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const interval = setInterval(() => {
const storedView = localStorage.getItem(name);
if (storedView !== view) {
setView(storedView || defaultView);
}
}, 100);
return () => {
clearInterval(interval);
};
}, [name, view]);
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === name) {
const storedView = e.newValue;
if (storedView !== view) {
setView(storedView || defaultView);
}
}
};
window.addEventListener('storage', handleStorageChange);
return () => {
window.removeEventListener('storage', handleStorageChange);
};
}, [name, view]);

return [view, updateView];
}
2 changes: 2 additions & 0 deletions src/components/Auth/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import BrowserWarning from "@/components/ErrorPages/BrowserWarning";
import { useAuthContext } from "@/hooks/useAuthUser";

import FiltersCache from "@/Utils/FiltersCache";
import ViewCache from "@/Utils/ViewCache";
import routes from "@/Utils/request/api";
import mutate from "@/Utils/request/mutate";
import request from "@/Utils/request/request";
Expand Down Expand Up @@ -242,6 +243,7 @@ const Login = (props: LoginProps) => {

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
ViewCache.invalidateAll();
const validated = validateData();
if (!validated) return;

Expand Down
24 changes: 24 additions & 0 deletions src/components/Common/View.tsx
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wont be needed once #9768 gets merged

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { ComponentType } from "react";

import { useView } from "@/Utils/useView";

export default function View({
name,
board,
list,
}: {
name: "resource";
board: ComponentType;
list: ComponentType;
}) {
Mahendar0701 marked this conversation as resolved.
Show resolved Hide resolved
const [view] = useView(name, "board");

const views: Record<"board" | "list", ComponentType> = {
board,
list,
};

const SelectedView = views[view as keyof typeof views] || board;

return <SelectedView />;
}
8 changes: 5 additions & 3 deletions src/components/Facility/FacilityUsers.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useQuery } from "@tanstack/react-query";
import { useState } from "react";
import { useTranslation } from "react-i18next";

import CareIcon from "@/CAREUI/icons/CareIcon";
Expand All @@ -17,6 +16,7 @@ import useFilters from "@/hooks/useFilters";

import routes from "@/Utils/request/api";
import query from "@/Utils/request/query";
import { useView } from "@/Utils/useView";

const UserCardSkeleton = () => (
<div>
Expand Down Expand Up @@ -121,7 +121,9 @@ export default function FacilityUsers(props: { facilityId: string }) {
limit: 15,
cacheBlacklist: ["username"],
});
const [activeTab, setActiveTab] = useState<"card" | "list">("card");
const [activeTab, setActiveTab] = useView("users", "card");

console.log("activeTab", activeTab);
const { facilityId } = props;

let usersList: JSX.Element = <></>;
Expand All @@ -147,7 +149,7 @@ export default function FacilityUsers(props: { facilityId: string }) {
<div>
<UserListAndCardView
users={userListData?.results ?? []}
activeTab={activeTab}
activeTab={activeTab === "card" ? "card" : "list"}
/>
<Pagination totalCount={userListData.count} />
</div>
Expand Down
9 changes: 3 additions & 6 deletions src/components/Resource/ResourceBoard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { RESOURCE_CHOICES } from "@/common/constants";

import routes from "@/Utils/request/api";
import request from "@/Utils/request/request";
import { useView } from "@/Utils/useView";
import { ResourceRequest } from "@/types/resourceRequest/resourceRequest";

const KanbanBoard = lazy(
Expand All @@ -36,6 +37,7 @@ const COMPLETED = ["COMPLETED", "REJECTED"];
const ACTIVE = resourceStatusOptions.filter((o) => !COMPLETED.includes(o));

export default function BoardView() {
const [, setView] = useView("resource", "board");
const { qParams, FilterBadges, advancedFilter, updateQuery } = useFilters({
limit: -1,
cacheBlacklist: ["title"],
Expand All @@ -45,11 +47,6 @@ export default function BoardView() {
const appliedFilters = formatFilter(qParams);
const { t } = useTranslation();

const onListViewBtnClick = () => {
navigate("/resource/list", { query: qParams });
localStorage.setItem("defaultResourceView", "list");
};

return (
<div className="flex-col px-2 pb-2">
<div className="flex w-full flex-col items-center justify-between lg:flex-row">
Expand Down Expand Up @@ -97,7 +94,7 @@ export default function BoardView() {
<div className="flex w-full flex-col gap-2 lg:mr-4 lg:w-fit lg:flex-row lg:gap-4">
<Button
variant={"primary"}
onClick={onListViewBtnClick}
onClick={() => setView("list")}
className="h-10.8 px-4 py-2"
>
<CareIcon icon="l-list-ul" className="mr-2" />
Expand Down
10 changes: 4 additions & 6 deletions src/components/Resource/ResourceList.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Link, navigate } from "raviger";
import { Link } from "raviger";
import { useTranslation } from "react-i18next";

import CareIcon from "@/CAREUI/icons/CareIcon";
Expand All @@ -20,10 +20,12 @@ import useFilters from "@/hooks/useFilters";
import routes from "@/Utils/request/api";
import request from "@/Utils/request/request";
import useTanStackQueryInstead from "@/Utils/request/useQuery";
import { useView } from "@/Utils/useView";
import { formatDateTime } from "@/Utils/utils";
import { ResourceRequest } from "@/types/resourceRequest/resourceRequest";

export default function ListView() {
const [, setView] = useView("resource", "list");
const {
qParams,
Pagination,
Expand All @@ -35,10 +37,6 @@ export default function ListView() {

const { t } = useTranslation();

const onBoardViewBtnClick = () => {
navigate("/resource/board", { query: qParams });
localStorage.setItem("defaultResourceView", "board");
};
const appliedFilters = formatFilter(qParams);

const { loading, data, refetch } = useTanStackQueryInstead(
Expand Down Expand Up @@ -222,7 +220,7 @@ export default function ListView() {
</div>

<div className="mt-2 flex w-full flex-col gap-2 lg:w-fit lg:flex-row lg:gap-4">
<Button variant={"primary"} onClick={onBoardViewBtnClick}>
<Button variant={"primary"} onClick={() => setView("board")}>
<CareIcon icon="l-list-ul" className="rotate-90 mr-2" />
{t("board_view")}
</Button>
Expand Down
9 changes: 5 additions & 4 deletions src/pages/Appointments/AppointmentsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import useAuthUser from "@/hooks/useAuthUser";

import mutate from "@/Utils/request/mutate";
import query from "@/Utils/request/query";
import { useView } from "@/Utils/useView";
import {
dateQueryString,
formatDisplayName,
Expand Down Expand Up @@ -238,7 +239,7 @@ export default function AppointmentsPage(props: { facilityId?: string }) {

const facilityId = props.facilityId ?? authUser.home_facility!;

const [viewMode, setViewMode] = useState<"board" | "list">("board");
const [activeTab, setActiveTab] = useView("appointments", "board");

const schedulableUsersQuery = useQuery({
queryKey: ["schedulable-users", facilityId],
Expand Down Expand Up @@ -328,8 +329,8 @@ export default function AppointmentsPage(props: { facilityId?: string }) {
breadcrumbs={false}
options={
<Tabs
value={viewMode}
onValueChange={(value) => setViewMode(value as "board" | "list")}
value={activeTab}
onValueChange={(value) => setActiveTab(value as "board" | "list")}
>
<TabsList>
<TabsTrigger value="board">
Expand Down Expand Up @@ -637,7 +638,7 @@ export default function AppointmentsPage(props: { facilityId?: string }) {
</div>
</div>

{viewMode === "board" ? (
{activeTab === "board" ? (
<ScrollArea>
<div className="flex w-max space-x-4">
{(
Expand Down
Loading