From 5d5dbd8b2fde264cd077d2038c14926e9b03ef19 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sun, 5 Jan 2025 23:20:45 +0530 Subject: [PATCH 01/24] enhanced-resource-page --- public/locale/en.json | 1 + src/Routers/routes/ResourceRoutes.tsx | 12 +- src/components/Kanban/Board.tsx | 25 +- src/components/Resource/ResourceBoard.tsx | 162 ------------ src/components/Resource/ResourceList.tsx | 287 ---------------------- 5 files changed, 7 insertions(+), 480 deletions(-) delete mode 100644 src/components/Resource/ResourceBoard.tsx delete mode 100644 src/components/Resource/ResourceList.tsx diff --git a/public/locale/en.json b/public/locale/en.json index 910baca98cf..e7ef8c1e819 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1613,6 +1613,7 @@ "select_role": "Select Role", "select_seven_day_period": "Select a seven day period", "select_skills": "Select and add some skills", + "select_status": "Select Status", "select_time": "Select time", "select_time_slot": "Select time slot", "select_wards": "Select wards", diff --git a/src/Routers/routes/ResourceRoutes.tsx b/src/Routers/routes/ResourceRoutes.tsx index 547aeb53610..5c2577b19d3 100644 --- a/src/Routers/routes/ResourceRoutes.tsx +++ b/src/Routers/routes/ResourceRoutes.tsx @@ -1,19 +1,11 @@ -import { Redirect } from "raviger"; - -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 ResourcePage from "@/components/Resource/ResourcePage"; import { AppRoutes } from "@/Routers/AppRouter"; -const getDefaultView = () => - localStorage.getItem("defaultResourceView") === "list" ? "list" : "board"; - const ResourceRoutes: AppRoutes = { - "/resource": () => , - "/resource/board": () => , - "/resource/list": () => , + "/resource": () => , "/resource/:id": ({ id }) => , "/resource/:id/update": ({ id }) => , }; diff --git a/src/components/Kanban/Board.tsx b/src/components/Kanban/Board.tsx index 0023c494609..6f9783016d4 100644 --- a/src/components/Kanban/Board.tsx +++ b/src/components/Kanban/Board.tsx @@ -8,8 +8,6 @@ import { useInfiniteQuery } from "@tanstack/react-query"; import { ReactNode, RefObject, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; -import CareIcon from "@/CAREUI/icons/CareIcon"; - import { callApi } from "@/Utils/request/query"; import { QueryRoute } from "@/Utils/request/types"; import { QueryOptions } from "@/Utils/request/useQuery"; @@ -37,25 +35,9 @@ export default function KanbanBoard( const board = useRef(null); return ( -
-
+
+
{props.title}
-
- {[0, 1].map((button, i) => ( - - ))} -
@@ -152,7 +134,7 @@ export function KanbanSection(
{ const target = e.target as HTMLDivElement; if ( @@ -184,6 +166,7 @@ export function KanbanSection( )} ))} + {provided.placeholder} {isFetchingNextPage && (
diff --git a/src/components/Resource/ResourceBoard.tsx b/src/components/Resource/ResourceBoard.tsx deleted file mode 100644 index 0b903e82829..00000000000 --- a/src/components/Resource/ResourceBoard.tsx +++ /dev/null @@ -1,162 +0,0 @@ -import { navigate } from "raviger"; -import { Suspense, lazy, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; -import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; - -import { Button } from "@/components/ui/button"; - -import { ExportButton } from "@/components/Common/Export"; -import Loading from "@/components/Common/Loading"; -import PageTitle from "@/components/Common/PageTitle"; -import Tabs from "@/components/Common/Tabs"; -import SearchInput from "@/components/Form/SearchInput"; -import type { KanbanBoardType } from "@/components/Kanban/Board"; -import BadgesList from "@/components/Resource/ResourceBadges"; -import ResourceBlock from "@/components/Resource/ResourceBlock"; -import { formatFilter } from "@/components/Resource/ResourceCommons"; -import ListFilter from "@/components/Resource/ResourceFilter"; - -import useFilters from "@/hooks/useFilters"; - -import { RESOURCE_CHOICES } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; - -const KanbanBoard = lazy( - () => import("@/components/Kanban/Board"), -) as KanbanBoardType; - -const resourceStatusOptions = RESOURCE_CHOICES.map((obj) => obj.text); - -const COMPLETED = ["COMPLETED", "REJECTED"]; -const ACTIVE = resourceStatusOptions.filter((o) => !COMPLETED.includes(o)); - -export default function BoardView() { - const { qParams, FilterBadges, advancedFilter, updateQuery } = useFilters({ - limit: -1, - cacheBlacklist: ["title"], - }); - const [boardFilter, setBoardFilter] = useState(ACTIVE); - // eslint-disable-next-line - const appliedFilters = formatFilter(qParams); - const { t } = useTranslation(); - - const onListViewBtnClick = () => { - navigate("/resource/list", { query: qParams }); - localStorage.setItem("defaultResourceView", "list"); - }; - - return ( -
-
-
- { - const { data } = await request( - routes.downloadResourceRequests, - { - query: { ...appliedFilters, csv: true }, - }, - ); - return data ?? null; - }} - filenamePrefix="resource_requests" - /> - } - breadcrumbs={false} - /> -
- -
- updateQuery({ [e.name]: e.value })} - placeholder={t("search_resource")} - className="w-full md:w-60" - /> - setBoardFilter(tab ? COMPLETED : ACTIVE)} - currentTab={boardFilter !== ACTIVE ? 1 : 0} - /> -
- - advancedFilter.setShow(true)} - /> -
-
-
- }> - - title={} - sections={boardFilter.map((board) => ({ - id: board, - title: ( -

- {board}{" "} - { - const { data } = await request( - routes.downloadResourceRequests, - { - query: { - ...formatFilter({ ...qParams, status: board }), - csv: true, - }, - }, - ); - return data ?? null; - }} - filenamePrefix={`resource_requests_${board}`} - /> -

- ), - fetchOptions: (id) => ({ - route: routes.listResourceRequests, - options: { - query: formatFilter({ - ...qParams, - status: id, - }), - }, - }), - }))} - onDragEnd={(result) => { - if (result.source.droppableId !== result.destination?.droppableId) - navigate( - `/resource/${result.draggableId}/update?status=${result.destination?.droppableId}`, - ); - }} - itemRender={(resource) => } - /> -
- - -
- ); -} diff --git a/src/components/Resource/ResourceList.tsx b/src/components/Resource/ResourceList.tsx deleted file mode 100644 index bdcf0929701..00000000000 --- a/src/components/Resource/ResourceList.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import { Link, navigate } from "raviger"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; -import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; - -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; - -import { ExportButton } from "@/components/Common/Export"; -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import SearchInput from "@/components/Form/SearchInput"; -import BadgesList from "@/components/Resource/ResourceBadges"; -import { formatFilter } from "@/components/Resource/ResourceCommons"; -import ListFilter from "@/components/Resource/ResourceFilter"; - -import useFilters from "@/hooks/useFilters"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { formatDateTime } from "@/Utils/utils"; -import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; - -export default function ListView() { - const { - qParams, - Pagination, - FilterBadges, - advancedFilter, - resultsPerPage, - updateQuery, - } = useFilters({ cacheBlacklist: ["title"], limit: 12 }); - - const { t } = useTranslation(); - - const onBoardViewBtnClick = () => { - navigate("/resource/board", { query: qParams }); - localStorage.setItem("defaultResourceView", "board"); - }; - const appliedFilters = formatFilter(qParams); - - const { loading, data, refetch } = useTanStackQueryInstead( - routes.listResourceRequests, - { - query: formatFilter({ - ...qParams, - limit: resultsPerPage, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - }), - }, - ); - - const showResourceCardList = (data: ResourceRequest[]) => { - if (data && !data.length) { - return ( -
- {t("no_results_found")} -
- ); - } - - return data.map((resource: ResourceRequest, i) => ( -
-
-
-
{resource.title}
-
- -
-
-
- -
- {resource.category || ""} -
- -
-
- -
-
- {resource.status === "TRANSPORTATION TO BE ARRANGED" ? ( -
- - - - - {t(`${resource.status}`)} - -
- ) : ( -
- - - - - {t(`${resource.status}`)} - -
- )} - -
- {resource.emergency && ( - - {t("emergency")} - - )} -
-
- -
-
- -
- {formatDateTime(resource.modified_date) || "--"} -
- -
-
- -
-
- -
- {resource.origin_facility?.name} -
- - -
- -
- {resource.approving_facility?.name} -
- - -
- -
- {resource.assigned_facility?.name || t("yet_to_be_decided")} -
- -
-
- - {t("all_details")} - -
-
-
- )); - }; - - return ( - { - const { data } = await request(routes.downloadResourceRequests, { - query: { ...appliedFilters, csv: true }, - }); - return data ?? null; - }} - filenamePrefix="resource_requests" - /> - } - breadcrumbs={false} - options={ - <> -
-
- updateQuery({ [e.name]: e.value })} - placeholder={t("search_resource")} - /> -
- -
- - advancedFilter.setShow(true)} - /> -
- - } - > - - -
- {loading ? ( - - ) : ( -
-
- -
-
-
- {t("resource")} -
-
- {t("LOG_UPDATE_FIELD_LABEL__patient_category")} -
-
- {t("consent__status")} -
-
- {t("facilities")} -
-
- {t("LOG_UPDATE_FIELD_LABEL__action")} -
-
-
{showResourceCardList(data?.results || [])}
-
- -
-
- )} -
- -
- ); -} From 66126a44ae66d827d3c4f0e7de373d2624de0916 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sun, 5 Jan 2025 23:24:20 +0530 Subject: [PATCH 02/24] enhanced-resource-page --- src/components/Resource/ResourceCardList.tsx | 142 +++++++++ src/components/Resource/ResourcePage.tsx | 302 +++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 src/components/Resource/ResourceCardList.tsx create mode 100644 src/components/Resource/ResourcePage.tsx diff --git a/src/components/Resource/ResourceCardList.tsx b/src/components/Resource/ResourceCardList.tsx new file mode 100644 index 00000000000..eacbc6f9f6e --- /dev/null +++ b/src/components/Resource/ResourceCardList.tsx @@ -0,0 +1,142 @@ +import { Link } from "raviger"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { formatDateTime } from "@/Utils/utils"; +import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; + +import { Badge } from "../ui/badge"; + +interface ResourceCardListProps { + data: ResourceRequest[]; +} + +const ResourceCardList = ({ data }: ResourceCardListProps) => { + const { t } = useTranslation(); + + if (data && !data.length) { + return ( +
+ {t("no_results_found")} +
+ ); + } + + return data.map((resource: ResourceRequest, i) => ( +
+
+
+
{resource.title}
+
+ +
+
+ {resource.status === "TRANSPORTATION TO BE ARRANGED" ? ( +
+ + + + + {t(`${resource.status}`)} + +
+ ) : ( +
+ + + + + {t(`${resource.status}`)} + +
+ )} + +
+ {resource.emergency && ( + + {t("emergency")} + + )} +
+
+ +
+
+ +
+ {formatDateTime(resource.modified_date) || "--"} +
+ +
+
+ +
+
+ +
+ {resource.origin_facility?.name} +
+ + +
+ +
+ {resource.approving_facility?.name} +
+ + +
+ +
+ {resource.assigned_facility?.name || t("yet_to_be_decided")} +
+ +
+
+ + {t("all_details")} + +
+
+
+ )); +}; + +export default ResourceCardList; diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx new file mode 100644 index 00000000000..8328278dc78 --- /dev/null +++ b/src/components/Resource/ResourcePage.tsx @@ -0,0 +1,302 @@ +import { navigate } from "raviger"; +import { Suspense, lazy, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Button } from "@/components/ui/button"; + +import { ExportButton } from "@/components/Common/Export"; +import Loading from "@/components/Common/Loading"; +import type { KanbanBoardType } from "@/components/Kanban/Board"; +import BadgesList from "@/components/Resource/ResourceBadges"; +import ResourceBlock from "@/components/Resource/ResourceBlock"; +import { formatFilter } from "@/components/Resource/ResourceCommons"; +import ListFilter from "@/components/Resource/ResourceFilter"; + +import useExport from "@/hooks/useExport"; +import useFilters from "@/hooks/useFilters"; + +import { RESOURCE_CHOICES } from "@/common/constants"; + +import routes from "@/Utils/request/api"; +import request from "@/Utils/request/request"; +import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; + +import Page from "../Common/Page"; +import { Input } from "../ui/input"; +import { Label } from "../ui/label"; +import { ScrollArea, ScrollBar } from "../ui/scroll-area"; +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; +import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; +import ResourceCardList from "./ResourceCardList"; + +const KanbanBoard = lazy( + () => import("@/components/Kanban/Board"), +) as KanbanBoardType; + +const resourceStatusOptions = RESOURCE_CHOICES.map((obj) => obj.text); + +const COMPLETED = ["COMPLETED", "REJECTED"]; +const ACTIVE = resourceStatusOptions.filter((o) => !COMPLETED.includes(o)); + +const ResourcePage = () => { + const { + qParams, + Pagination, + FilterBadges, + advancedFilter, + resultsPerPage, + updateQuery, + } = useFilters({ + limit: 12, + cacheBlacklist: ["title"], + }); + const [boardFilter, setBoardFilter] = useState(ACTIVE); + // eslint-disable-next-line + const appliedFilters = formatFilter(qParams); + const [viewMode, setViewMode] = useState<"board" | "list">("board"); + const { exportFile } = useExport(); + const { loading, data, refetch } = useTanStackQueryInstead( + routes.listResourceRequests, + { + query: formatFilter({ + ...qParams, + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + }), + }, + ); + + const exportAction = async () => { + const { data } = await request(routes.downloadResourceRequests, { + query: { ...appliedFilters, csv: true }, + }); + return data ?? null; + }; + + const { t } = useTranslation(); + + return ( + +
+
+ + setViewMode(value as "board" | "list") + } + > + + + + {t("board")} + + + + {t("list")} + + + +
+ +
+ + } + > + {viewMode === "board" ? ( + <> +
+
+
+ + + +
+
+
+ updateQuery({ title: e.target.value })} + /> + +
+
+ + }> + + + title={} + sections={boardFilter.map((board) => ({ + id: board, + title: ( +

+ {board}{" "} + { + const { data } = await request( + routes.downloadResourceRequests, + { + query: { + ...formatFilter({ ...qParams, status: board }), + csv: true, + }, + }, + ); + return data ?? null; + }} + filenamePrefix={`resource_requests_${board}`} + /> +

+ ), + fetchOptions: (id) => ({ + route: routes.listResourceRequests, + options: { + query: formatFilter({ + ...qParams, + status: id, + }), + }, + }), + }))} + onDragEnd={(result) => { + if ( + result.source.droppableId !== + result.destination?.droppableId + ) + navigate( + `/resource/${result.draggableId}/update?status=${result.destination?.droppableId}`, + ); + }} + itemRender={(resource) => } + /> + +
+
+ + + ) : ( + <> +
+
+ updateQuery({ title: e.target.value })} + /> + +
+
+ + {loading ? ( + + ) : ( +
+
+ + + +
+ +
+
+ {t("resource")} +
+
+ {t("LOG_UPDATE_FIELD_LABEL__patient_category")} +
+
+ {t("consent__status")} +
+
+ {t("facilities")} +
+
+ {t("LOG_UPDATE_FIELD_LABEL__action")} +
+
+ +
+ +
+
+ )} + + + + )} +
+ ); +}; + +export default ResourcePage; From 7f8aab4bb205efef043383754913ace63ce5994b Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Thu, 9 Jan 2025 06:57:50 +0530 Subject: [PATCH 03/24] enhanced-resource-page --- src/components/Kanban/Board.tsx | 181 ------ src/components/Resource/ResourceCardList.tsx | 142 ----- src/components/Resource/ResourcePage.tsx | 593 ++++++++++++++----- 3 files changed, 441 insertions(+), 475 deletions(-) delete mode 100644 src/components/Kanban/Board.tsx delete mode 100644 src/components/Resource/ResourceCardList.tsx diff --git a/src/components/Kanban/Board.tsx b/src/components/Kanban/Board.tsx deleted file mode 100644 index 6f9783016d4..00000000000 --- a/src/components/Kanban/Board.tsx +++ /dev/null @@ -1,181 +0,0 @@ -import { - DragDropContext, - Draggable, - Droppable, - OnDragEndResponder, -} from "@hello-pangea/dnd"; -import { useInfiniteQuery } from "@tanstack/react-query"; -import { ReactNode, RefObject, useEffect, useRef } from "react"; -import { useTranslation } from "react-i18next"; - -import { callApi } from "@/Utils/request/query"; -import { QueryRoute } from "@/Utils/request/types"; -import { QueryOptions } from "@/Utils/request/useQuery"; - -interface KanbanBoardProps { - title?: ReactNode; - onDragEnd: OnDragEndResponder; - sections: { - id: string; - title: ReactNode; - fetchOptions: ( - id: string, - ...args: unknown[] - ) => { - route: QueryRoute; - options?: QueryOptions; - }; - }[]; - itemRender: (item: T) => ReactNode; -} - -export default function KanbanBoard( - props: KanbanBoardProps, -) { - const board = useRef(null); - - return ( -
-
-
{props.title}
-
- -
-
- {props.sections.map((section, i) => ( - - key={i} - section={section} - itemRender={props.itemRender} - boardRef={board} - /> - ))} -
-
-
-
- ); -} - -interface QueryResponse { - results: T[]; - next: string | null; - count: number; -} - -export function KanbanSection( - props: Omit, "sections" | "onDragEnd"> & { - section: KanbanBoardProps["sections"][number]; - boardRef: RefObject; - }, -) { - const { section } = props; - const sectionRef = useRef(null); - const defaultLimit = 14; - const { t } = useTranslation(); - const options = section.fetchOptions(section.id); - const fetchPage = async ({ pageParam = 0 }) => { - try { - const data = await callApi(options.route, { - ...options.options, - queryParams: { - ...options.options?.query, - offset: pageParam, - limit: defaultLimit, - }, - }); - return data as QueryResponse; - } catch (error) { - console.error("Error fetching section data:", error); - return { results: [], next: null, count: 0 }; - } - }; - - const { - data, - fetchNextPage, - hasNextPage, - isFetchingNextPage, - isLoading, - refetch, - } = useInfiniteQuery({ - queryKey: ["board", section.id, options.options?.query], - queryFn: fetchPage, - getNextPageParam: (lastPage, pages) => { - if (!lastPage.next) return undefined; - return pages.length * defaultLimit; - }, - initialPageParam: 0, - }); - - const items = data?.pages?.flatMap((page) => page.results || []) ?? []; - const totalCount = data?.pages[0]?.count ?? 0; - - useEffect(() => { - refetch(); - }, [section.id, refetch]); - - return ( - - {(provided, _snapshot) => ( -
-
-
-
{section.title}
-
- - {isLoading ? "..." : totalCount} - -
-
-
-
{ - const target = e.target as HTMLDivElement; - if ( - target.scrollTop + target.clientHeight >= - target.scrollHeight - 100 - ) { - if (hasNextPage && !isFetchingNextPage) { - fetchNextPage(); - } - } - }} - > - {!isLoading && items.length === 0 && ( -
- {t("no_results_found")} -
- )} - {items.map((item, index) => ( - - {(provided, _snapshot) => ( -
- {props.itemRender(item)} -
- )} -
- ))} - - {provided.placeholder} - {isFetchingNextPage && ( -
- )} -
-
- )} - - ); -} - -export type KanbanBoardType = typeof KanbanBoard; diff --git a/src/components/Resource/ResourceCardList.tsx b/src/components/Resource/ResourceCardList.tsx deleted file mode 100644 index eacbc6f9f6e..00000000000 --- a/src/components/Resource/ResourceCardList.tsx +++ /dev/null @@ -1,142 +0,0 @@ -import { Link } from "raviger"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { formatDateTime } from "@/Utils/utils"; -import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; - -import { Badge } from "../ui/badge"; - -interface ResourceCardListProps { - data: ResourceRequest[]; -} - -const ResourceCardList = ({ data }: ResourceCardListProps) => { - const { t } = useTranslation(); - - if (data && !data.length) { - return ( -
- {t("no_results_found")} -
- ); - } - - return data.map((resource: ResourceRequest, i) => ( -
-
-
-
{resource.title}
-
- -
-
- {resource.status === "TRANSPORTATION TO BE ARRANGED" ? ( -
- - - - - {t(`${resource.status}`)} - -
- ) : ( -
- - - - - {t(`${resource.status}`)} - -
- )} - -
- {resource.emergency && ( - - {t("emergency")} - - )} -
-
- -
-
- -
- {formatDateTime(resource.modified_date) || "--"} -
- -
-
- -
-
- -
- {resource.origin_facility?.name} -
- - -
- -
- {resource.approving_facility?.name} -
- - -
- -
- {resource.assigned_facility?.name || t("yet_to_be_decided")} -
- -
-
- - {t("all_details")} - -
-
-
- )); -}; - -export default ResourceCardList; diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index 8328278dc78..9b8b227464f 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -1,5 +1,19 @@ -import { navigate } from "raviger"; -import { Suspense, lazy, useState } from "react"; +import { + DragDropContext, + Draggable, + Droppable, + OnDragEndResponder, +} from "@hello-pangea/dnd"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { Link, navigate } from "raviger"; +import { + ReactNode, + RefObject, + Suspense, + useEffect, + useRef, + useState, +} from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -8,7 +22,6 @@ import { Button } from "@/components/ui/button"; import { ExportButton } from "@/components/Common/Export"; import Loading from "@/components/Common/Loading"; -import type { KanbanBoardType } from "@/components/Kanban/Board"; import BadgesList from "@/components/Resource/ResourceBadges"; import ResourceBlock from "@/components/Resource/ResourceBlock"; import { formatFilter } from "@/components/Resource/ResourceCommons"; @@ -20,11 +33,17 @@ import useFilters from "@/hooks/useFilters"; import { RESOURCE_CHOICES } from "@/common/constants"; import routes from "@/Utils/request/api"; +import { callApi } from "@/Utils/request/query"; import request from "@/Utils/request/request"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { QueryRoute } from "@/Utils/request/types"; +import useTanStackQueryInstead, { + QueryOptions, +} from "@/Utils/request/useQuery"; +import { formatDateTime } from "@/Utils/utils"; import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; import Page from "../Common/Page"; +import { Badge } from "../ui/badge"; import { Input } from "../ui/input"; import { Label } from "../ui/label"; import { ScrollArea, ScrollBar } from "../ui/scroll-area"; @@ -37,11 +56,6 @@ import { SelectValue, } from "../ui/select"; import { Tabs, TabsList, TabsTrigger } from "../ui/tabs"; -import ResourceCardList from "./ResourceCardList"; - -const KanbanBoard = lazy( - () => import("@/components/Kanban/Board"), -) as KanbanBoardType; const resourceStatusOptions = RESOURCE_CHOICES.map((obj) => obj.text); @@ -91,159 +105,119 @@ const ResourcePage = () => { hideBack={true} breadcrumbs={false} options={ - <> -
-
- - setViewMode(value as "board" | "list") - } - > - - - - {t("board")} - - - - {t("list")} - - - -
- +
+ } + > +
+
+
+ + +
- - } - > +
+
+ updateQuery({ title: e.target.value })} + /> + +
+
+ {viewMode === "board" ? ( <> -
-
-
- - - -
-
-
- updateQuery({ title: e.target.value })} - /> - -
-
- }> - - title={} - sections={boardFilter.map((board) => ({ - id: board, - title: ( -

- {board}{" "} - { - const { data } = await request( - routes.downloadResourceRequests, - { - query: { - ...formatFilter({ ...qParams, status: board }), - csv: true, - }, - }, - ); - return data ?? null; - }} - filenamePrefix={`resource_requests_${board}`} - /> -

- ), - fetchOptions: (id) => ({ - route: routes.listResourceRequests, - options: { - query: formatFilter({ - ...qParams, - status: id, - }), - }, - }), - }))} - onDragEnd={(result) => { - if ( - result.source.droppableId !== - result.destination?.droppableId - ) - navigate( - `/resource/${result.draggableId}/update?status=${result.destination?.droppableId}`, - ); - }} - itemRender={(resource) => } - /> - +
+ + title={} + sections={boardFilter.map((board) => ({ + id: board, + title: ( +

+ {board}{" "} +

+ ), + fetchOptions: (id) => ({ + route: routes.listResourceRequests, + options: { + query: formatFilter({ + ...qParams, + status: id, + }), + }, + }), + }))} + onDragEnd={(result) => { + if ( + result.source.droppableId !== + result.destination?.droppableId + ) + navigate( + `/resource/${result.draggableId}/update?status=${result.destination?.droppableId}`, + ); + }} + itemRender={(resource) => ( + + )} + /> + +
) : ( - <> -
-
- updateQuery({ title: e.target.value })} - /> - -
-
- +
{loading ? ( ) : ( @@ -281,7 +255,7 @@ const ResourcePage = () => { {t("LOG_UPDATE_FIELD_LABEL__action")}
- +
@@ -293,10 +267,325 @@ const ResourcePage = () => { showResourceStatus={true} key={window.location.search} /> - +
)} ); }; +interface ResourceColumnProps { + title?: ReactNode; + onDragEnd: OnDragEndResponder; + sections: { + id: string; + title: ReactNode; + fetchOptions: ( + id: string, + ...args: unknown[] + ) => { + route: QueryRoute; + options?: QueryOptions; + }; + }[]; + itemRender: (item: T) => ReactNode; +} + +function ResourceColumn( + props: ResourceColumnProps, +) { + const board = useRef(null); + return ( +
+
+
{props.title}
+
+ +
+
+ {props.sections.map((section, i) => ( + + key={i} + section={section} + itemRender={props.itemRender} + boardRef={board} + /> + ))} +
+
+
+
+ ); +} + +interface QueryResponse { + results: T[]; + next: string | null; + count: number; +} + +function ResourceCard( + props: Omit, "sections" | "onDragEnd"> & { + section: ResourceColumnProps["sections"][number]; + boardRef: RefObject; + }, +) { + const { section } = props; + const sectionRef = useRef(null); + const defaultLimit = 14; + const { t } = useTranslation(); + const options = section.fetchOptions(section.id); + const fetchPage = async ({ pageParam = 0 }) => { + try { + const data = await callApi(options.route, { + ...options.options, + queryParams: { + ...options.options?.query, + offset: pageParam, + limit: defaultLimit, + }, + }); + return data as QueryResponse; + } catch (error) { + console.error("Error fetching section data:", error); + return { results: [], next: null, count: 0 }; + } + }; + + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + refetch, + } = useInfiniteQuery({ + queryKey: ["board", section.id, options.options?.query], + queryFn: fetchPage, + getNextPageParam: (lastPage, pages) => { + if (!lastPage.next) return undefined; + return pages.length * defaultLimit; + }, + initialPageParam: 0, + }); + + const items = data?.pages?.flatMap((page) => page.results || []) ?? []; + const totalCount = data?.pages[0]?.count ?? 0; + const { qParams } = useFilters({ + limit: 12, + cacheBlacklist: ["title"], + }); + useEffect(() => { + refetch(); + }, [section.id, refetch]); + return ( + + {(provided, _snapshot) => ( +
+
+
+
{section.title}
+
+ + {isLoading ? "..." : totalCount} + +
+
+ { + const { data } = await request( + routes.downloadResourceRequests, + { + query: { + ...formatFilter({ ...qParams, status: section.id }), + csv: true, + }, + }, + ); + return data ?? null; + }} + filenamePrefix={`resource_requests_${section.id}`} + /> +
+
{ + const target = e.target as HTMLDivElement; + if ( + target.scrollTop + target.clientHeight >= + target.scrollHeight - 100 + ) { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + } + }} + > + {!isLoading && items.length === 0 && ( +
+

{t("no_results_found")}

+
+ )} + {items.map((item, index) => ( + + {(provided, _snapshot) => ( +
+ {props.itemRender(item)} +
+ )} +
+ ))} + + {provided.placeholder} + {isFetchingNextPage && ( +
+ )} +
+
+ )} + + ); +} + +interface ResourceRowProps { + data: ResourceRequest[]; +} + +function ResourceRow({ data }: ResourceRowProps) { + const { t } = useTranslation(); + + if (data && !data.length) { + return ( +
+ {t("no_results_found")} +
+ ); + } + + return data.map((resource: ResourceRequest, i) => ( +
+
+
+
{resource.title}
+
+ +
+
+ {resource.status === "TRANSPORTATION TO BE ARRANGED" ? ( +
+ + + + + {t(`${resource.status}`)} + +
+ ) : ( +
+ + + + + {t(`${resource.status}`)} + +
+ )} + +
+ {resource.emergency && ( + + {t("emergency")} + + )} +
+
+ +
+
+ +
+ {formatDateTime(resource.modified_date) || "--"} +
+ +
+
+ +
+
+ +
+ {resource.origin_facility?.name} +
+ + +
+ +
+ {resource.approving_facility?.name} +
+ + +
+ +
+ {resource.assigned_facility?.name || t("yet_to_be_decided")} +
+ +
+
+ + {t("all_details")} + +
+
+
+ )); +} + export default ResourcePage; From 774d88581def7b98b05c7180db751141778cd5ae Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Fri, 10 Jan 2025 23:34:35 +0530 Subject: [PATCH 04/24] removed console logs --- src/components/Resource/ResourcePage.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index 9b8b227464f..9f655f7aa90 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -346,7 +346,6 @@ function ResourceCard( }); return data as QueryResponse; } catch (error) { - console.error("Error fetching section data:", error); return { results: [], next: null, count: 0 }; } }; From f83d81e452a4d4309b3435d57ef13342aabba9f2 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sat, 11 Jan 2025 00:24:47 +0530 Subject: [PATCH 05/24] stored viewMode to localStorage --- src/components/Resource/ResourcePage.tsx | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index 9f655f7aa90..4b421f12f7a 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -97,6 +97,18 @@ const ResourcePage = () => { return data ?? null; }; + const handleViewMode = () => { + const newMode = viewMode === "board" ? "list" : "board"; + setViewMode(newMode); + localStorage.setItem("ResourceView", newMode); + }; + + useEffect(() => { + const mode = + localStorage.getItem("ResourceView") === "list" ? "list" : "board"; + setViewMode(mode); + }, []); + const { t } = useTranslation(); return ( @@ -106,10 +118,7 @@ const ResourcePage = () => { breadcrumbs={false} options={
- setViewMode(value as "board" | "list")} - > + From 09429b2ff287705875e3b8ae901ce38665de2a34 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sat, 11 Jan 2025 01:09:07 +0530 Subject: [PATCH 06/24] switch to useQuery for all api calls --- src/components/Resource/ResourcePage.tsx | 27 +++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index 4b421f12f7a..461c9cc5fb1 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -4,7 +4,7 @@ import { Droppable, OnDragEndResponder, } from "@hello-pangea/dnd"; -import { useInfiniteQuery } from "@tanstack/react-query"; +import { useInfiniteQuery, useQuery } from "@tanstack/react-query"; import { Link, navigate } from "raviger"; import { ReactNode, @@ -33,12 +33,10 @@ import useFilters from "@/hooks/useFilters"; import { RESOURCE_CHOICES } from "@/common/constants"; import routes from "@/Utils/request/api"; -import { callApi } from "@/Utils/request/query"; +import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; import { QueryRoute } from "@/Utils/request/types"; -import useTanStackQueryInstead, { - QueryOptions, -} from "@/Utils/request/useQuery"; +import { QueryOptions } from "@/Utils/request/useQuery"; import { formatDateTime } from "@/Utils/utils"; import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; @@ -79,16 +77,16 @@ const ResourcePage = () => { const appliedFilters = formatFilter(qParams); const [viewMode, setViewMode] = useState<"board" | "list">("board"); const { exportFile } = useExport(); - const { loading, data, refetch } = useTanStackQueryInstead( - routes.listResourceRequests, - { - query: formatFilter({ + const { isLoading, data, refetch } = useQuery({ + queryKey: ["title"], + queryFn: query(routes.listResourceRequests, { + queryParams: formatFilter({ ...qParams, limit: resultsPerPage, offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, }), - }, - ); + }), + }); const exportAction = async () => { const { data } = await request(routes.downloadResourceRequests, { @@ -227,7 +225,7 @@ const ResourcePage = () => { ) : (
- {loading ? ( + {isLoading ? ( ) : (
@@ -345,14 +343,13 @@ function ResourceCard( const options = section.fetchOptions(section.id); const fetchPage = async ({ pageParam = 0 }) => { try { - const data = await callApi(options.route, { - ...options.options, + const data = await query(options.route, { queryParams: { ...options.options?.query, offset: pageParam, limit: defaultLimit, }, - }); + })({ signal: new AbortController().signal }); return data as QueryResponse; } catch (error) { return { results: [], next: null, count: 0 }; From 50d2528c9da35cbdc8dd35c6858a7c436010ae80 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sat, 11 Jan 2025 01:17:18 +0530 Subject: [PATCH 07/24] changes for mobile screen --- src/components/Resource/ResourcePage.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index 461c9cc5fb1..cb669821fd5 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -115,14 +115,14 @@ const ResourcePage = () => { hideBack={true} breadcrumbs={false} options={ -
+
- - + + {t("board")} - + {t("list")} From 46e610c1a7d1b560bdd45f6deae3083a29546ec9 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sat, 11 Jan 2025 23:07:20 +0530 Subject: [PATCH 08/24] Fixed the count of status --- src/components/Resource/ResourceCreate.tsx | 2 +- src/components/Resource/ResourcePage.tsx | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/components/Resource/ResourceCreate.tsx b/src/components/Resource/ResourceCreate.tsx index 9838b78c8ef..0fb8352d8c3 100644 --- a/src/components/Resource/ResourceCreate.tsx +++ b/src/components/Resource/ResourceCreate.tsx @@ -177,7 +177,7 @@ export default function ResourceCreate(props: resourceProps) { const handleSubmit = async () => { const validForm = validateForm(); - if (validForm) { + if (!validForm) { setIsLoading(true); const resourceData: CreateResourceRequest = { diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index d79c5c7f17e..bf6271ab275 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -301,6 +301,7 @@ function ResourceColumn( props: ResourceColumnProps, ) { const board = useRef(null); + return (
@@ -341,6 +342,7 @@ function ResourceCard( const defaultLimit = 14; const { t } = useTranslation(); const options = section.fetchOptions(section.id); + const fetchPage = async ({ pageParam = 0 }) => { try { const data = await query(options.route, { @@ -350,6 +352,7 @@ function ResourceCard( limit: defaultLimit, }, })({ signal: new AbortController().signal }); + return data as QueryResponse; } catch (error) { return { results: [], next: null, count: 0 }; @@ -374,7 +377,11 @@ function ResourceCard( }); const items = data?.pages?.flatMap((page) => page.results || []) ?? []; - const totalCount = data?.pages[0]?.count ?? 0; + + const totalCount = data?.pages[0]?.results.filter( + (item: any) => item.status === section.id, + ).length; + const { qParams } = useFilters({ limit: 12, cacheBlacklist: ["title"], From 82514c41bb24710af0f0dc0df86a2b6df2dcb2f2 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sat, 11 Jan 2025 23:09:11 +0530 Subject: [PATCH 09/24] Fixed the count of status --- src/components/Resource/ResourceCreate.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Resource/ResourceCreate.tsx b/src/components/Resource/ResourceCreate.tsx index 0fb8352d8c3..9838b78c8ef 100644 --- a/src/components/Resource/ResourceCreate.tsx +++ b/src/components/Resource/ResourceCreate.tsx @@ -177,7 +177,7 @@ export default function ResourceCreate(props: resourceProps) { const handleSubmit = async () => { const validForm = validateForm(); - if (!validForm) { + if (validForm) { setIsLoading(true); const resourceData: CreateResourceRequest = { From bafcb02fda2a55c022de497e00234a117dde0b90 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sun, 12 Jan 2025 04:58:42 +0530 Subject: [PATCH 10/24] Fixed position of resource cards wrt their status --- public/locale/en.json | 1 + src/components/Resource/ResourcePage.tsx | 56 ++++++++++++------------ 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 3118eaba81a..d71cc7c4909 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1665,6 +1665,7 @@ "reset_password_note_self": "Enter your current password, then create and confirm your new password", "resource": "Resource", "resource_approving_facility": "Resource approving facility", + "resource_category": "Category", "resource_created_successfully": "Request created successfully", "resource_details": "Resource details", "resource_origin_facility": "Origin Facility", diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index bf6271ab275..82abd7d015f 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -179,6 +179,7 @@ const ResourcePage = () => {
+ {viewMode === "board" ? ( <> @@ -186,7 +187,6 @@ const ResourcePage = () => {
- title={} sections={boardFilter.map((board) => ({ id: board, title: ( @@ -230,8 +230,6 @@ const ResourcePage = () => { ) : (
- -
- {t("LOG_UPDATE_FIELD_LABEL__patient_category")} + {t("resource_category")}
{t("consent__status")} @@ -259,7 +257,7 @@ const ResourcePage = () => { {t("facilities")}
- {t("LOG_UPDATE_FIELD_LABEL__action")} + {t("action")}
@@ -281,7 +279,6 @@ const ResourcePage = () => { }; interface ResourceColumnProps { - title?: ReactNode; onDragEnd: OnDragEndResponder; sections: { id: string; @@ -304,9 +301,6 @@ function ResourceColumn( return (
-
-
{props.title}
-
@@ -443,20 +437,26 @@ function ResourceCard(

{t("no_results_found")}

)} - {items.map((item, index) => ( - - {(provided, _snapshot) => ( -
- {props.itemRender(item)} -
- )} -
- ))} + + {items.map((item: any, index) => { + if (item.status === section.id) { + return ( + + {(provided, _snapshot) => ( +
+ {props.itemRender(item)} +
+ )} +
+ ); + } + return null; + })} {provided.placeholder} {isFetchingNextPage && ( @@ -497,7 +497,7 @@ function ResourceRow({ data }: ResourceRowProps) {
@@ -509,7 +509,7 @@ function ResourceRow({ data }: ResourceRowProps) {
-
+
{resource.status === "TRANSPORTATION TO BE ARRANGED" ? (
{resource.emergency && ( - + {t("emergency")} )} From cc5e17a00c7f59f27a0bccecfe45c95c7b0155a7 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sun, 12 Jan 2025 18:42:50 +0530 Subject: [PATCH 11/24] Resolved conflicts --- src/components/Common/Export.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/Common/Export.tsx b/src/components/Common/Export.tsx index e926c44fc65..6f9c6bdbb7d 100644 --- a/src/components/Common/Export.tsx +++ b/src/components/Common/Export.tsx @@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button"; import useExport from "@/hooks/useExport"; import request from "@/Utils/request/request"; -import { Route } from "@/Utils/request/types"; +import { ApiRoute } from "@/Utils/request/types"; interface ExportButtonProps { disabled?: boolean | undefined; @@ -17,7 +17,7 @@ interface ExportButtonProps { tooltipClassName?: string; type?: "csv" | "json"; action?: Parameters["exportFile"]>[0]; - route?: Route; + route?: ApiRoute; parse?: (data: string) => string; filenamePrefix: string; className?: string; @@ -69,4 +69,3 @@ export const ExportButton = ({ ); }; -tMenu; From 8718369658c7ad971527262e123663dba3cf825b Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Sun, 12 Jan 2025 19:10:01 +0530 Subject: [PATCH 12/24] Resolved conflicts --- src/Utils/request/types.ts | 41 ++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index e63b5430c60..113fe2a5575 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -8,44 +8,47 @@ type QueryParamValue = export type QueryParams = Record; -interface RouteBase { +export interface ApiRoute { + method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + TBody?: TBody; path: string; TRes: TData; noAuth?: boolean; } -export interface QueryRoute extends RouteBase { - method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; - TBody?: TBody; -} - -export interface MutationRoute extends RouteBase { - method: "POST" | "PUT" | "PATCH" | "DELETE"; - TBody?: TBody; -} - -export type Route = - | QueryRoute - | MutationRoute; - +/** + * @deprecated in favor of useQuery/useMutation/callApi + */ export interface RequestResult { res: Response | undefined; data: TData | undefined; error: undefined | Record; } +/** + * @deprecated in favor of ApiCallOptions used by useQuery/useMutation/callApi + */ export interface RequestOptions { query?: QueryParams; body?: TBody; - pathParams?: Record; + pathParams?: Record; onResponse?: (res: RequestResult) => void; silent?: boolean; } -export interface APICallOptions { - pathParams?: Record; +type ExtractRouteParams = + T extends `${infer _Start}{${infer Param}}${infer Rest}` + ? Param | ExtractRouteParams + : never; + +type PathParams = { + [K in ExtractRouteParams]: string; +}; + +export interface ApiCallOptions> { + pathParams?: PathParams; queryParams?: QueryParams; - body?: TBody; + body?: Route["TBody"]; silent?: boolean; signal?: AbortSignal; headers?: HeadersInit; From 8d6ac575dbe1322ff07707eea9641994fb0d8651 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Mon, 13 Jan 2025 17:44:08 +0530 Subject: [PATCH 13/24] Resolved conflicts --- src/CAREUI/misc/PaginatedList.tsx | 4 ++-- src/Utils/request/types.ts | 1 + src/Utils/request/useQuery.ts | 4 ++-- src/components/Resource/ResourcePage.tsx | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx index 9cefca73aac..01b9b0538b5 100644 --- a/src/CAREUI/misc/PaginatedList.tsx +++ b/src/CAREUI/misc/PaginatedList.tsx @@ -6,7 +6,7 @@ import { Button, ButtonProps } from "@/components/ui/button"; import Pagination from "@/components/Common/Pagination"; -import { PaginatedResponse, QueryRoute } from "@/Utils/request/types"; +import { ApiRoute, PaginatedResponse } from "@/Utils/request/types"; import useTanStackQueryInstead, { QueryOptions, } from "@/Utils/request/useQuery"; @@ -35,7 +35,7 @@ function useContextualized() { } interface Props extends QueryOptions> { - route: QueryRoute>; + route: ApiRoute>; perPage?: number; initialPage?: number; onPageChange?: (page: number) => void; diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index 113fe2a5575..7cad3953431 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -10,6 +10,7 @@ export type QueryParams = Record; export interface ApiRoute { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + TBody?: TBody; path: string; TRes: TData; diff --git a/src/Utils/request/useQuery.ts b/src/Utils/request/useQuery.ts index b6977e74419..422e5f96868 100644 --- a/src/Utils/request/useQuery.ts +++ b/src/Utils/request/useQuery.ts @@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query"; import { useMemo, useRef } from "react"; import request from "@/Utils/request/request"; -import { QueryRoute, RequestOptions } from "@/Utils/request/types"; +import { ApiRoute, RequestOptions } from "@/Utils/request/types"; import { mergeRequestOptions } from "./utils"; @@ -15,7 +15,7 @@ export interface QueryOptions extends RequestOptions { * @deprecated use `useQuery` from `@tanstack/react-query` instead. */ export default function useTanStackQueryInstead( - route: QueryRoute, + route: ApiRoute, options?: QueryOptions, ) { const overridesRef = useRef>(); diff --git a/src/components/Resource/ResourcePage.tsx b/src/components/Resource/ResourcePage.tsx index 82abd7d015f..5605f343f4c 100644 --- a/src/components/Resource/ResourcePage.tsx +++ b/src/components/Resource/ResourcePage.tsx @@ -35,7 +35,7 @@ import { RESOURCE_CHOICES } from "@/common/constants"; import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import request from "@/Utils/request/request"; -import { QueryRoute } from "@/Utils/request/types"; +import { ApiRoute } from "@/Utils/request/types"; import { QueryOptions } from "@/Utils/request/useQuery"; import { formatDateTime } from "@/Utils/utils"; import { ResourceRequest } from "@/types/resourceRequest/resourceRequest"; @@ -287,7 +287,7 @@ interface ResourceColumnProps { id: string, ...args: unknown[] ) => { - route: QueryRoute; + route: ApiRoute; options?: QueryOptions; }; }[]; From 1f3d54df4fd3844767619be230db9ec7c6e766b4 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Tue, 14 Jan 2025 00:34:34 +0530 Subject: [PATCH 14/24] discard verifyPatient --- src/pages/Patients/VerifyPatient.tsx | 31 +++++++++++++++------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/pages/Patients/VerifyPatient.tsx b/src/pages/Patients/VerifyPatient.tsx index 9e1594032a2..83d10d041b3 100644 --- a/src/pages/Patients/VerifyPatient.tsx +++ b/src/pages/Patients/VerifyPatient.tsx @@ -2,6 +2,7 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { AlertCircle, CalendarIcon } from "lucide-react"; import { Link, useQueryParams } from "raviger"; import { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -28,6 +29,7 @@ import { formatPatientAge } from "@/Utils/utils"; import { Encounter } from "@/types/emr/encounter"; export default function VerifyPatient(props: { facilityId: string }) { + const { t } = useTranslation(); const [qParams] = useQueryParams(); const { phone_number, year_of_birth, partial_id } = qParams; @@ -69,7 +71,7 @@ export default function VerifyPatient(props: { facilityId: string }) { - Missing required parameters for patient verification + {t("missing_required_params_for_patient_verification")} ) : patientData ? ( @@ -87,7 +89,7 @@ export default function VerifyPatient(props: { facilityId: string }) {

{patientData.name} @@ -95,10 +97,10 @@ export default function VerifyPatient(props: { facilityId: string }) {

{formatPatientAge(patientData, true)},{" "} - {patientData.gender.replace("_", " ")},{" "} + {patientData.gender.replace("_", " ")} {patientData.blood_group && - patientData.blood_group.replace("_", " ")} + ", " + patientData.blood_group.replace("_", " ")}

@@ -109,9 +111,9 @@ export default function VerifyPatient(props: { facilityId: string }) { - Quick Actions + {t("quick_actions")} - Schedule an appointment or create a new encounter + {t("quick_actions_description")} @@ -131,10 +133,10 @@ export default function VerifyPatient(props: { facilityId: string }) {
- Schedule Appointment + {t("schedule_appointment")} - Book a new appointment + {t("book_a_new_appointment")}
@@ -165,10 +168,10 @@ export default function VerifyPatient(props: { facilityId: string }) {
- Create Encounter + {t("create_encounter")} - Start a new clinical encounter + {t("start_a_new_clinical_encounter")}
- Active Encounters + {t("active_encounters")} - View and manage patient encounters + {t("view_and_manage_patient_encounters")} @@ -206,10 +209,10 @@ export default function VerifyPatient(props: { facilityId: string }) { />

- No encounters found + {t("no_encounters_found")}

- Create a new encounter to get started + {t("create_a_new_encounter_to_get_started")}

)} From 434b2465b248bee36b15ed8f396c359d005e134b Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Tue, 14 Jan 2025 00:50:31 +0530 Subject: [PATCH 15/24] removed patient_creation --- .../e2e/patient_spec/patient_creation.cy.ts | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 cypress/e2e/patient_spec/patient_creation.cy.ts diff --git a/cypress/e2e/patient_spec/patient_creation.cy.ts b/cypress/e2e/patient_spec/patient_creation.cy.ts new file mode 100644 index 00000000000..941da15b51d --- /dev/null +++ b/cypress/e2e/patient_spec/patient_creation.cy.ts @@ -0,0 +1,74 @@ +import { patientCreation } from "pageObject/Patients/PatientCreation"; +import { patientDashboard } from "pageObject/Patients/PatientDashboard"; +import { patientVerify } from "pageObject/Patients/PatientVerify"; + +import { + generateAddress, + generatePatientName, + generatePhoneNumber, +} from "../../utils/commonUtils"; + +const ENCOUNTER_TYPE = "Observation"; +const ENCOUNTER_STATUS = "In Progress"; +const ENCOUNTER_PRIORITY = "ASAP"; + +describe("Patient Management", () => { + const TEST_PHONE = "9495031234"; + const PATIENT_DETAILS = { + name: "Nihal", + sex: "Male", + phone: TEST_PHONE, + }; + + const testPatientData = { + name: generatePatientName(), + phoneNumber: generatePhoneNumber(), + gender: "male", + bloodGroup: "B+", + dateOfBirth: "01-01-1990", + address: generateAddress(), + pincode: "682001", + state: "Kerala", + district: "Ernakulam", + localBody: "Aluva", + ward: "4", + }; + + beforeEach(() => { + cy.visit("/login"); + }); + + it("create a new patient and verify details", () => { + cy.loginByApi("doctor"); + patientCreation + .selectFacility("Arike") + .clickSearchPatients() + .clickCreateNewPatient() + .fillPatientDetails(testPatientData) + .submitPatientForm() + .assertPatientRegistrationSuccess(); + patientVerify + .verifyPatientName(testPatientData.name) + .verifyCreateEncounterButton() + .clickCreateEncounter() + .selectEncounterType(ENCOUNTER_TYPE) + .selectEncounterStatus(ENCOUNTER_STATUS) + .selectEncounterPriority(ENCOUNTER_PRIORITY) + .clickSubmitEncounter() + .assertEncounterCreationSuccess(); + patientDashboard.verifyEncounterPatientInfo([ + ENCOUNTER_TYPE, + ENCOUNTER_STATUS, + ENCOUNTER_PRIORITY, + ]); + }); + + it("search patient with phone number and verifies details", () => { + cy.loginByApi("staff"); + patientCreation + .selectFacility("Arike") + .clickSearchPatients() + .searchPatient(TEST_PHONE) + .verifySearchResults(PATIENT_DETAILS); + }); +}); From 2afa97855a38cb7fb5abe07650370e2be6ddbca2 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Tue, 14 Jan 2025 00:54:07 +0530 Subject: [PATCH 16/24] removed pageObject/Patient --- .../pageObject/Patients/PatientCreation.ts | 156 ++++++++++++++++++ .../pageObject/Patients/PatientDashboard.ts | 8 + cypress/pageObject/Patients/PatientVerify.ts | 63 +++++++ 3 files changed, 227 insertions(+) create mode 100644 cypress/pageObject/Patients/PatientCreation.ts create mode 100644 cypress/pageObject/Patients/PatientDashboard.ts create mode 100644 cypress/pageObject/Patients/PatientVerify.ts diff --git a/cypress/pageObject/Patients/PatientCreation.ts b/cypress/pageObject/Patients/PatientCreation.ts new file mode 100644 index 00000000000..05adc98e328 --- /dev/null +++ b/cypress/pageObject/Patients/PatientCreation.ts @@ -0,0 +1,156 @@ +interface PatientFormData { + name: string; + phoneNumber: string; + dateOfBirth: string; + gender: string; + bloodGroup: string; + address: string; + pincode: string; + state: string; + district: string; + localBody: string; + ward: string; +} + +export class PatientCreation { + // Selectors + private selectors = { + patientsButton: '[data-cy="patients-button"]', + searchInput: "#patient-search", + patientCard: "#patient-search-results", + patientName: '[data-cy="patient-name"]', + patientDetails: "#patient-search-results", + createNewPatientButton: '[data-cy="create-new-patient-button"]', + }; + + // Actions + clickCreateNewPatient() { + cy.get(this.selectors.createNewPatientButton).click(); + cy.url().should("include", "/patient/create"); + return this; + } + + searchPatient(searchQuery: string) { + cy.get(this.selectors.searchInput).type(searchQuery); + + // Wait for results to load + cy.get(this.selectors.patientCard).should("be.visible"); + return this; + } + + verifySearchResults(patientDetails: { + name: string; + sex: string; + phone: string; + }) { + // Convert object values to an array of strings + const detailsArray = Object.values(patientDetails); + cy.verifyContentPresence(this.selectors.patientDetails, detailsArray); + } + + selectFacility(facilityName: string) { + cy.verifyAndClickElement("[data-cy='facility-list']", facilityName); + return this; + } + + clickSearchPatients() { + cy.get('[data-sidebar="content"]').contains("Search Patients").click(); + return this; + } + + enterName(name: string) { + cy.typeIntoField('[data-cy="patient-name-input"]', name); + return this; + } + + enterPhoneNumber(phoneNumber: string) { + cy.typeIntoField('[data-cy="patient-phone-input"]', phoneNumber, { + skipVerification: true, + }); + return this; + } + + enterDateOfBirth(dateString: string) { + // Split the date string (expected format: "DD-MM-YYYY") + const [day, month, year] = dateString.split("-"); + + cy.get('[data-cy="dob-day-input"]').type(day); + cy.get('[data-cy="dob-month-input"]').type(month); + cy.get('[data-cy="dob-year-input"]').type(year); + + return this; + } + + selectGender(gender: string) { + const lowercaseGender = gender.toLowerCase(); + cy.get(`[data-cy="gender-radio-${lowercaseGender}"]`).click(); + return this; + } + + selectBloodGroup(bloodGroup: string) { + cy.clickAndSelectOption('[data-cy="blood-group-select"]', bloodGroup); + return this; + } + + enterAddress(address: string) { + cy.typeIntoField('[data-cy="current-address-input"]', address); + return this; + } + + enterPincode(pincode: string) { + cy.typeIntoField('[data-cy="pincode-input"]', pincode); + return this; + } + + fillPatientDetails(patient: PatientFormData) { + return this.enterName(patient.name) + .enterPhoneNumber(patient.phoneNumber) + .clickSamePhoneNumberCheckbox() + .selectGender(patient.gender) + .selectBloodGroup(patient.bloodGroup) + .enterDateOfBirth(patient.dateOfBirth) + .enterAddress(patient.address) + .enterPincode(patient.pincode) + .selectState(patient.state) + .selectDistrict(patient.district) + .selectLocalBody(patient.localBody) + .selectWard(patient.ward); + } + + selectState(state: string) { + cy.typeAndSelectOption('[data-cy="select-state"]', state); + return this; + } + + selectDistrict(district: string) { + cy.typeAndSelectOption('[data-cy="select-district"]', district); + return this; + } + + selectLocalBody(localBody: string) { + cy.typeAndSelectOption('[data-cy="select-local_body"]', localBody); + return this; + } + + selectWard(ward: string) { + cy.typeAndSelectOption('[data-cy="select-ward"]', ward); + return this; + } + + submitPatientForm() { + cy.clickSubmitButton("Save and Continue"); + return this; + } + + clickSamePhoneNumberCheckbox() { + cy.get('[data-cy="same-phone-number-checkbox"]').click(); + return this; + } + + assertPatientRegistrationSuccess() { + cy.verifyNotification("Patient Registered Successfully"); + return this; + } +} + +export const patientCreation = new PatientCreation(); diff --git a/cypress/pageObject/Patients/PatientDashboard.ts b/cypress/pageObject/Patients/PatientDashboard.ts new file mode 100644 index 00000000000..9d608b17ea6 --- /dev/null +++ b/cypress/pageObject/Patients/PatientDashboard.ts @@ -0,0 +1,8 @@ +class PatientDashboard { + verifyEncounterPatientInfo(contents: string[]) { + cy.verifyContentPresence("#patient-infobadges", contents); + return this; + } +} + +export const patientDashboard = new PatientDashboard(); diff --git a/cypress/pageObject/Patients/PatientVerify.ts b/cypress/pageObject/Patients/PatientVerify.ts new file mode 100644 index 00000000000..de98d9d8d61 --- /dev/null +++ b/cypress/pageObject/Patients/PatientVerify.ts @@ -0,0 +1,63 @@ +class PatientVerify { + verifyPatientName(expectedName: string) { + cy.get('[data-cy="verify-patient-name"]').should("contain", expectedName); + return this; + } + + verifyCreateEncounterButton() { + cy.get('[data-cy="create-encounter-button"]').should( + "contain", + "Create Encounter", + ); + return this; + } + + clickCreateEncounter() { + cy.verifyAndClickElement( + '[data-cy="create-encounter-button"]', + "Create Encounter", + ); + return this; + } + + // Map display text to data-cy values for better maintainability + private encounterTypeMap = { + Observation: "obsenc", + Inpatient: "imp", + Ambulatory: "amb", + Emergency: "emer", + Virtual: "vr", + "Home Health": "hh", + }; + + selectEncounterType(displayText: string) { + const dataCyValue = this.encounterTypeMap[displayText]; + cy.verifyAndClickElement( + `[data-cy="encounter-type-${dataCyValue}"]`, + displayText, + ); + return this; + } + + selectEncounterStatus(status: string) { + cy.clickAndSelectOption('[data-cy="encounter-status"]', status); + return this; + } + + selectEncounterPriority(priority: string) { + cy.clickAndSelectOption('[data-cy="encounter-priority"]', priority); + return this; + } + + clickSubmitEncounter() { + cy.clickSubmitButton("Create Encounter"); + return this; + } + + assertEncounterCreationSuccess() { + cy.verifyNotification("Encounter created successfully"); + return this; + } +} + +export const patientVerify = new PatientVerify(); From 1ca2a07ed4104815cdfc6113862c1cddada089dd Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Tue, 14 Jan 2025 00:56:27 +0530 Subject: [PATCH 17/24] commit_msg --- cypress/support/commands.ts | 39 +++++++++++++++++++++++++------------ cypress/support/index.ts | 2 +- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 540e1571228..31f46ed5762 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -85,7 +85,11 @@ Cypress.Commands.add("verifyNotification", (text: string) => { return cy .get("li[data-sonner-toast] div[data-title]") .should("exist") - .contains(text); + .contains(text) + .should("be.visible") + .then(() => { + cy.closeNotification(); + }); }); Cypress.Commands.add("clearAllFilters", () => { @@ -104,13 +108,19 @@ Cypress.Commands.add("clickCancelButton", (buttonText = "Cancel") => { Cypress.Commands.add( "typeAndSelectOption", - (element: string, reference: string) => { - cy.get(element) - .click() - .type(reference) - .then(() => { - cy.get("[role='option']").contains(reference).click(); - }); + (selector: string, value: string) => { + // Click to open the dropdown + cy.get(selector).click(); + + // Type in the command input + cy.get("[cmdk-input]").should("be.visible").clear().type(value); + + // Select the filtered option from command menu + cy.get("[cmdk-list]") + .find("[cmdk-item]") + .contains(value) + .should("be.visible") + .click(); }, ); @@ -192,10 +202,15 @@ Cypress.Commands.add("preventPrint", () => { }); Cypress.Commands.add("closeNotification", () => { - cy.get(".pnotify") - .should("exist") - .each(($div) => { - cy.wrap($div).click(); + return cy + .get("li[data-sonner-toast] div[data-title]") + .first() + .parents("li[data-sonner-toast]") + .then(($toast) => { + cy.wrap($toast) + .find('button[aria-label="Close toast"]', { timeout: 5000 }) + .should("be.visible") + .click(); }); }); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index da4fd19dfad..3da319f36cd 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -39,7 +39,7 @@ declare global { reference: string, ): Chainable; preventPrint(): Chainable; - closeNotification(): Chainable; + closeNotification(): Chainable>; verifyContentPresence( selector: string, texts: string[], From ac882de7e4e41e3d2cb3a63482a11a8b35aedf85 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Tue, 14 Jan 2025 01:02:57 +0530 Subject: [PATCH 18/24] commit_msg --- src/Utils/request/handleResponse.ts | 3 +++ src/Utils/request/mutate.ts | 10 ++++---- src/Utils/request/query.ts | 24 +++++++++---------- src/Utils/request/request.ts | 7 ++++-- src/Utils/request/types.ts | 1 - src/Utils/request/utils.ts | 19 --------------- src/pages/Appointments/AppointmentsPage.tsx | 6 +++-- src/pages/Appointments/BookAppointment.tsx | 4 +++- src/pages/Appointments/utils.ts | 4 +++- .../FacilityOrganizationView.tsx | 2 +- .../components/OrganizationSelector.tsx | 13 ++++++++-- 11 files changed, 47 insertions(+), 46 deletions(-) diff --git a/src/Utils/request/handleResponse.ts b/src/Utils/request/handleResponse.ts index df3a3a11ad7..1a97a5b8b24 100644 --- a/src/Utils/request/handleResponse.ts +++ b/src/Utils/request/handleResponse.ts @@ -5,6 +5,9 @@ import { toast } from "sonner"; import * as Notifications from "@/Utils/Notifications"; import { RequestResult } from "@/Utils/request/types"; +/** + * @deprecated in favor of useQuery/useMutation/callApi + */ export default function handleResponse( { res, error }: RequestResult, silent?: boolean, diff --git a/src/Utils/request/mutate.ts b/src/Utils/request/mutate.ts index 2372920c162..e500b18d909 100644 --- a/src/Utils/request/mutate.ts +++ b/src/Utils/request/mutate.ts @@ -1,5 +1,5 @@ import { callApi } from "@/Utils/request/query"; -import { APICallOptions, Route } from "@/Utils/request/types"; +import { ApiCallOptions, ApiRoute } from "@/Utils/request/types"; /** * Creates a TanStack Query compatible mutation function. @@ -16,11 +16,11 @@ import { APICallOptions, Route } from "@/Utils/request/types"; * }); * ``` */ -export default function mutate( - route: Route, - options?: APICallOptions, +export default function mutate>( + route: Route, + options?: ApiCallOptions, ) { - return (variables: TBody) => { + return (variables: Route["TBody"]) => { return callApi(route, { ...options, body: variables }); }; } diff --git a/src/Utils/request/query.ts b/src/Utils/request/query.ts index 3ad2f1d25c4..34fc3ce89ac 100644 --- a/src/Utils/request/query.ts +++ b/src/Utils/request/query.ts @@ -1,14 +1,14 @@ import careConfig from "@careConfig"; import { getResponseBody } from "@/Utils/request/request"; -import { APICallOptions, HTTPError, Route } from "@/Utils/request/types"; +import { ApiCallOptions, ApiRoute, HTTPError } from "@/Utils/request/types"; import { makeHeaders, makeUrl } from "@/Utils/request/utils"; import { sleep } from "@/Utils/utils"; -export async function callApi( - { path, method, noAuth }: Route, - options?: APICallOptions, -): Promise { +export async function callApi>( + { path, method, noAuth }: Route, + options?: ApiCallOptions, +): Promise { const url = `${careConfig.apiUrl}${makeUrl(path, options?.queryParams, options?.pathParams)}`; const fetchOptions: RequestInit = { @@ -29,7 +29,7 @@ export async function callApi( throw new Error("Network Error"); } - const data = await getResponseBody(res); + const data = await getResponseBody(res); if (!res.ok) { throw new HTTPError({ @@ -60,9 +60,9 @@ export async function callApi( * }); * ``` */ -export default function query( - route: Route, - options?: APICallOptions, +export default function query>( + route: Route, + options?: ApiCallOptions, ) { return ({ signal }: { signal: AbortSignal }) => { return callApi(route, { ...options, signal }); @@ -98,9 +98,9 @@ export default function query( * - When aborted, both the `sleep` promise and the fetch request are cancelled automatically * - TanStack Query handles the abortion and cleanup of previous in-flight requests */ -const debouncedQuery = ( - route: Route, - options?: APICallOptions & { debounceInterval?: number }, +const debouncedQuery = >( + route: Route, + options?: ApiCallOptions & { debounceInterval?: number }, ) => { return async ({ signal }: { signal: AbortSignal }) => { await sleep(options?.debounceInterval ?? 500); diff --git a/src/Utils/request/request.ts b/src/Utils/request/request.ts index 975cef9ad55..7bbfebb2614 100644 --- a/src/Utils/request/request.ts +++ b/src/Utils/request/request.ts @@ -1,7 +1,7 @@ import careConfig from "@careConfig"; import handleResponse from "@/Utils/request/handleResponse"; -import { RequestOptions, RequestResult, Route } from "@/Utils/request/types"; +import { ApiRoute, RequestOptions, RequestResult } from "@/Utils/request/types"; import { makeHeaders, makeUrl } from "@/Utils/request/utils"; type Options = RequestOptions & { @@ -10,9 +10,12 @@ type Options = RequestOptions & { /** * @deprecated use useQuery/useMutation/callApi instead + * + * This no longer ensures that the path params are provided correctly during runtime. + * Usages so far works as path params were passed correctly, but this should not be used anymore. */ export default async function request( - { path, method, noAuth }: Route, + { path, method, noAuth }: ApiRoute, { query, body, diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index 7cad3953431..113fe2a5575 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -10,7 +10,6 @@ export type QueryParams = Record; export interface ApiRoute { method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; - TBody?: TBody; path: string; TRes: TData; diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index 24c492633d3..ea5948b0479 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -1,5 +1,4 @@ import { Dispatch, SetStateAction } from "react"; -import { toast } from "sonner"; import { LocalStorageKeys } from "@/common/constants"; @@ -17,8 +16,6 @@ export function makeUrl( ); } - ensurePathNotMissingReplacements(path); - if (query) { path += `?${makeQueryParams(query)}`; } @@ -43,22 +40,6 @@ const makeQueryParams = (query: QueryParams) => { return qParams.toString(); }; -/** - * TODO: consider replacing this with inferring the types from the route and using a generic - * to ensure that the path params are not missing. - */ -const ensurePathNotMissingReplacements = (path: string) => { - const missingParams = path.match(/\{.*\}/g); - - if (missingParams) { - const msg = `Missing path params: ${missingParams.join( - ", ", - )}. Path: ${path}`; - toast.error(msg); - throw new Error(msg); - } -}; - export function makeHeaders(noAuth: boolean, additionalHeaders?: HeadersInit) { const headers = new Headers(additionalHeaders); diff --git a/src/pages/Appointments/AppointmentsPage.tsx b/src/pages/Appointments/AppointmentsPage.tsx index cb1f3d0d750..39d3431edb5 100644 --- a/src/pages/Appointments/AppointmentsPage.tsx +++ b/src/pages/Appointments/AppointmentsPage.tsx @@ -305,8 +305,10 @@ export default function AppointmentsPage(props: { facilityId?: string }) { queryFn: query(scheduleApis.slots.getSlotsForDay, { pathParams: { facility_id: facilityId }, body: { - user: practitioner?.id, - day: qParams.date_from, + // voluntarily coalesce to empty string since we know query would be + // enabled only if practitioner and date_from are present + user: practitioner?.id ?? "", + day: qParams.date_from ?? "", }, }), enabled: slotsFilterEnabled, diff --git a/src/pages/Appointments/BookAppointment.tsx b/src/pages/Appointments/BookAppointment.tsx index 4a15b24465b..39ddd8eea45 100644 --- a/src/pages/Appointments/BookAppointment.tsx +++ b/src/pages/Appointments/BookAppointment.tsx @@ -78,7 +78,9 @@ export default function BookAppointment(props: Props) { queryFn: query(scheduleApis.slots.getSlotsForDay, { pathParams: { facility_id: props.facilityId }, body: { - user: resourceId, + // voluntarily coalesce to empty string since we know query would be + // enabled only if resourceId and selectedDate are present + user: resourceId ?? "", day: dateQueryString(selectedDate), }, }), diff --git a/src/pages/Appointments/utils.ts b/src/pages/Appointments/utils.ts index 7bb2562b550..4b131e50382 100644 --- a/src/pages/Appointments/utils.ts +++ b/src/pages/Appointments/utils.ts @@ -79,7 +79,9 @@ export const useAvailabilityHeatmap = ({ let queryFn = query(scheduleApis.slots.availabilityStats, { pathParams: { facility_id: facilityId }, body: { - user: userId, + // voluntarily coalesce to empty string since we know query would be + // enabled only if userId is present + user: userId ?? "", from_date: fromDate, to_date: toDate, }, diff --git a/src/pages/FacilityOrganization/FacilityOrganizationView.tsx b/src/pages/FacilityOrganization/FacilityOrganizationView.tsx index 9481f1c215c..5def2fc8afa 100644 --- a/src/pages/FacilityOrganization/FacilityOrganizationView.tsx +++ b/src/pages/FacilityOrganization/FacilityOrganizationView.tsx @@ -41,7 +41,7 @@ export default function FacilityOrganizationView({ id, facilityId }: Props) { searchQuery, ], queryFn: query.debounced(routes.facilityOrganization.list, { - pathParams: { facilityId, organizationId: id }, + pathParams: { facilityId }, queryParams: { parent: id, offset: (page - 1) * limit, diff --git a/src/pages/Organization/components/OrganizationSelector.tsx b/src/pages/Organization/components/OrganizationSelector.tsx index a1d49eadf55..286940c87f9 100644 --- a/src/pages/Organization/components/OrganizationSelector.tsx +++ b/src/pages/Organization/components/OrganizationSelector.tsx @@ -110,13 +110,16 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { <> {/* Selected Levels */} {selectedLevels.map((level, index) => ( -
+
-
+
{level.name}
@@ -126,6 +129,7 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { size="icon" onClick={() => handleEdit(index)} type="button" + data-cy={`edit-${level.metadata?.govt_org_type?.toLowerCase()}`} > @@ -155,6 +159,11 @@ export default function OrganizationSelector(props: OrganizationSelectorProps) { handleLevelChange(value, selectedLevels.length) } onSearch={setSearchQuery} + data-cy={`select-${ + lastLevel?.metadata?.govt_org_children_type?.toLowerCase() || + lastLevel?.metadata?.govt_org_type?.toLowerCase() || + "state" + }`} />
)} From 439d503f8289c46757f62a97659d20c043bfc967 Mon Sep 17 00:00:00 2001 From: manmeetnagii Date: Tue, 14 Jan 2025 01:05:57 +0530 Subject: [PATCH 19/24] commit_msg --- src/components/Encounter/CreateEncounterForm.tsx | 5 +++-- src/components/Facility/FacilityUsers.tsx | 2 +- src/components/Patient/PatientIndex.tsx | 1 + .../Questionnaire/QuestionTypes/AppointmentQuestion.tsx | 4 +++- src/components/ui/autocomplete.tsx | 3 +++ 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/components/Encounter/CreateEncounterForm.tsx b/src/components/Encounter/CreateEncounterForm.tsx index 39a5e8363e2..0bbb85cd8a4 100644 --- a/src/components/Encounter/CreateEncounterForm.tsx +++ b/src/components/Encounter/CreateEncounterForm.tsx @@ -216,6 +216,7 @@ export default function CreateEncounterForm({