diff --git a/src/Utils/request/useInfiniteQuery.ts b/src/Utils/request/useInfiniteQuery.ts new file mode 100644 index 00000000000..903b904cc28 --- /dev/null +++ b/src/Utils/request/useInfiniteQuery.ts @@ -0,0 +1,62 @@ +import { useCallback, useState } from "react"; + +import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants"; + +import { PaginatedResponse, QueryRoute } from "@/Utils/request/types"; +import useQuery, { QueryOptions } from "@/Utils/request/useQuery"; + +export interface InfiniteQueryOptions + extends QueryOptions> { + deduplicateBy: (item: TItem) => string | number; +} + +export function useInfiniteQuery( + route: QueryRoute>, + options?: InfiniteQueryOptions, +) { + const [items, setItems] = useState([]); + const [totalCount, setTotalCount] = useState(); + const [offset, setOffset] = useState(0); + + const { refetch, loading, ...queryResponse } = useQuery(route, { + ...options, + query: { + ...(options?.query ?? {}), + offset, + }, + onResponse: ({ data }) => { + if (!data) return; + const allItems = items.concat(data.results); + + const deduplicatedItems = options?.deduplicateBy + ? Array.from( + allItems + .reduce( + (map, item) => map.set(options.deduplicateBy!(item), item), + new Map(), + ) + .values(), + ) + : allItems; + + setItems(deduplicatedItems); + setTotalCount(data.count); + }, + }); + + const fetchNextPage = useCallback(async () => { + if (loading) return; + + setOffset((prevOffset) => prevOffset + RESULTS_PER_PAGE_LIMIT); + }, [offset, loading]); + + return { + items, + loading, + fetchNextPage, + refetch, + totalCount, + hasMore: items.length < (totalCount ?? 0), + ...queryResponse, + }; +} diff --git a/src/components/Facility/ConsultationDoctorNotes/index.tsx b/src/components/Facility/ConsultationDoctorNotes/index.tsx index 098a4b392e3..0661daba430 100644 --- a/src/components/Facility/ConsultationDoctorNotes/index.tsx +++ b/src/components/Facility/ConsultationDoctorNotes/index.tsx @@ -54,8 +54,6 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { const initialData: PatientNoteStateType = { notes: [], - cPage: 1, - totalPages: 1, facilityId: facilityId, patientId: patientId, }; @@ -69,7 +67,7 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { return; } - const { res } = await request(routes.addPatientNote, { + const { res, data } = await request(routes.addPatientNote, { pathParams: { patientId: patientId, }, @@ -81,12 +79,14 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { }, }); - if (res?.status === 201) { - Notification.Success({ msg: "Note added successfully" }); - setState({ ...state, cPage: 1 }); + if (res?.status === 201 && data) { setNoteField(""); - setReload(true); + setState((prevState) => ({ + ...prevState, + notes: [data, ...prevState.notes], + })); setReplyTo(undefined); + Notification.Success({ msg: "Note added successfully" }); } }; @@ -147,13 +147,17 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { ? "border-primary-500 font-bold text-secondary-800" : "border-secondary-300 text-secondary-800", )} - onClick={() => setThread(PATIENT_NOTES_THREADS[current])} + onClick={() => { + setThread(PATIENT_NOTES_THREADS[current]); + setState(initialData); + }} > {t(`patient_notes_thread__${current}`)} ))} void; disableEdit?: boolean; setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; + hasMore: boolean; } const DoctorNote = (props: DoctorNoteProps) => { - const { state, handleNext, setReload, disableEdit, setReplyTo } = props; + const { state, handleNext, setReload, disableEdit, setReplyTo, hasMore } = + props; return (
{ {state.notes.length ? ( diff --git a/src/components/Facility/PatientConsultationNotesList.tsx b/src/components/Facility/PatientConsultationNotesList.tsx index 243821b7d6d..2ef5740b932 100644 --- a/src/components/Facility/PatientConsultationNotesList.tsx +++ b/src/components/Facility/PatientConsultationNotesList.tsx @@ -1,4 +1,4 @@ -import { Dispatch, SetStateAction, useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useEffect } from "react"; import CircularProgress from "@/components/Common/CircularProgress"; import DoctorNote from "@/components/Facility/DoctorNote"; @@ -9,10 +9,8 @@ import { import useSlug from "@/hooks/useSlug"; -import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants"; - import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; +import { useInfiniteQuery } from "@/Utils/request/useInfiniteQuery"; interface PatientNotesProps { state: PatientNoteStateType; @@ -24,80 +22,36 @@ interface PatientNotesProps { setReplyTo?: (value: PatientNotesModel | undefined) => void; } -const pageSize = RESULTS_PER_PAGE_LIMIT; - const PatientConsultationNotesList = (props: PatientNotesProps) => { - const { - state, - setState, - reload, - setReload, - disableEdit, - thread, - setReplyTo, - } = props; - const consultationId = useSlug("consultation") ?? ""; - - const [isLoading, setIsLoading] = useState(true); - - const fetchNotes = async () => { - setIsLoading(true); - - const { data } = await request(routes.getPatientNotes, { - pathParams: { - patientId: props.state.patientId || "", - }, - query: { - consultation: consultationId, - thread, - offset: (state.cPage - 1) * RESULTS_PER_PAGE_LIMIT, - }, - }); + const { state, setState, setReload, disableEdit, thread, setReplyTo } = props; - if (data) { - if (state.cPage === 1) { - setState((prevState) => ({ - ...prevState, - notes: data.results, - totalPages: Math.ceil(data.count / pageSize), - })); - } else { - setState((prevState) => ({ - ...prevState, - notes: [...prevState.notes, ...data.results], - totalPages: Math.ceil(data.count / pageSize), - })); - } - } - setIsLoading(false); - setReload?.(false); - }; - - useEffect(() => { - if (reload) { - fetchNotes(); - } - }, [reload]); + const consultationId = useSlug("consultation") ?? ""; - useEffect(() => { - fetchNotes(); - }, [thread]); + const { + items: notes, + loading, + fetchNextPage, + hasMore, + } = useInfiniteQuery(routes.getPatientNotes, { + deduplicateBy: (note) => note.id, + pathParams: { + patientId: props.state.patientId || "", + }, + query: { + consultation: consultationId, + thread, + }, + prefetch: true, + }); useEffect(() => { - setReload?.(true); - }, []); - - const handleNext = () => { - if (state.cPage < state.totalPages) { - setState((prevState) => ({ - ...prevState, - cPage: prevState.cPage + 1, - })); - setReload?.(true); - } - }; + setState((prevState: any) => ({ + ...prevState, + notes, + })); + }, [notes, setState]); - if (isLoading) { + if (loading && !state.notes.length) { return (
@@ -108,10 +62,11 @@ const PatientConsultationNotesList = (props: PatientNotesProps) => { return ( ); }; diff --git a/src/components/Facility/PatientNoteCard.tsx b/src/components/Facility/PatientNoteCard.tsx index 7896cbada4d..711c718b9fc 100644 --- a/src/components/Facility/PatientNoteCard.tsx +++ b/src/components/Facility/PatientNoteCard.tsx @@ -29,7 +29,6 @@ import { const PatientNoteCard = ({ note, - setReload, disableEdit, setReplyTo, }: { @@ -76,13 +75,11 @@ const PatientNoteCard = ({ if (res?.status === 200) { Success({ msg: "Note updated successfully" }); setIsEditing(false); - setReload(true); } }; return ( <> - {" "}
void; } - -const pageSize = RESULTS_PER_PAGE_LIMIT; - const PatientNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, thread, setReplyTo } = props; - - const [isLoading, setIsLoading] = useState(true); - - const fetchNotes = async () => { - setIsLoading(true); - const { data }: any = await request(routes.getPatientNotes, { - pathParams: { patientId: props.patientId }, - query: { - offset: (state.cPage - 1) * RESULTS_PER_PAGE_LIMIT, - thread, - }, - }); - - if (state.cPage === 1) { - setState((prevState: any) => ({ - ...prevState, - notes: data.results, - totalPages: Math.ceil(data.count / pageSize), - })); - } else { - setState((prevState: any) => ({ - ...prevState, - notes: [...prevState.notes, ...data.results], - totalPages: Math.ceil(data.count / pageSize), - })); - } - setIsLoading(false); - setReload(false); - }; + const { state, setState, thread, setReplyTo, setReload } = props; + + const { + items: notes, + loading, + fetchNextPage, + hasMore, + } = useInfiniteQuery(routes.getPatientNotes, { + deduplicateBy: (note) => note.id, + query: { + thread, + offset: 0, + }, + pathParams: { + patientId: props.patientId, + }, + }); useEffect(() => { - if (reload) { - fetchNotes(); - } - }, [reload]); - - useEffect(() => { - fetchNotes(); - }, [thread]); - - useEffect(() => { - setReload(true); - }, []); - - const handleNext = () => { - if (state.cPage < state.totalPages) { - setState((prevState: any) => ({ - ...prevState, - cPage: prevState.cPage + 1, - })); - setReload(true); - } - }; + setState((prevState: any) => ({ + ...prevState, + notes, + })); + }, [notes, setState]); - if (isLoading) { + if (loading && !state.notes.length) { return (
@@ -92,9 +57,10 @@ const PatientNotesList = (props: PatientNotesProps) => { return ( ); }; diff --git a/src/components/Facility/PatientNotesSlideover.tsx b/src/components/Facility/PatientNotesSlideover.tsx index 89d38a5f168..01c5734318d 100644 --- a/src/components/Facility/PatientNotesSlideover.tsx +++ b/src/components/Facility/PatientNotesSlideover.tsx @@ -63,8 +63,6 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const initialData: PatientNoteStateType = { notes: [], - cPage: 1, - totalPages: 1, patientId: props.patientId, facilityId: props.facilityId, }; @@ -85,7 +83,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { }); return; } - const { res } = await request(routes.addPatientNote, { + const { res, data } = await request(routes.addPatientNote, { pathParams: { patientId: patientId }, body: { note: noteField, @@ -94,12 +92,14 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { reply_to: reply_to?.id, }, }); - if (res?.status === 201) { - Notification.Success({ msg: "Note added successfully" }); + if (res?.status === 201 && data) { setNoteField(""); - setState({ ...state, cPage: 1 }); - setReload(true); + setState((prevState) => ({ + ...prevState, + notes: [data, ...prevState.notes], + })); setReplyTo(undefined); + Notification.Success({ msg: "Note added successfully" }); } }; @@ -235,13 +235,17 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { ? "border-primary-500 font-medium text-white" : "border-primary-800 text-white/70", )} - onClick={() => setThread(PATIENT_NOTES_THREADS[current])} + onClick={() => { + setThread(PATIENT_NOTES_THREADS[current]); + setState(initialData); + }} > {t(`patient_notes_thread__${current}`)} ))}
{ const initialData: PatientNoteStateType = { notes: [], - cPage: 1, - totalPages: 1, }; const [state, setState] = useState(initialData); @@ -62,7 +60,7 @@ const PatientNotes = (props: PatientNotesProps) => { } try { - const { res } = await request(routes.addPatientNote, { + const { res, data } = await request(routes.addPatientNote, { pathParams: { patientId: patientId }, body: { note: noteField, @@ -70,10 +68,12 @@ const PatientNotes = (props: PatientNotesProps) => { reply_to: reply_to?.id, }, }); - if (res?.status === 201) { + if (res?.status === 201 && data) { setNoteField(""); - setReload(!reload); - setState({ ...state, cPage: 1 }); + setState((prevState) => ({ + ...prevState, + notes: [data, ...prevState.notes], + })); setReplyTo(undefined); Notification.Success({ msg: "Note added successfully" }); } @@ -130,13 +130,17 @@ const PatientNotes = (props: PatientNotesProps) => { ? "border-primary-500 font-bold text-secondary-800" : "border-secondary-300 text-secondary-800", )} - onClick={() => setThread(PATIENT_NOTES_THREADS[current])} + onClick={() => { + setThread(PATIENT_NOTES_THREADS[current]); + setState(initialData); + }} > {t(`patient_notes_thread__${current}`)} ))}
{ const initialData: PatientNoteStateType = { notes: [], - cPage: 1, - totalPages: 1, }; const [state, setState] = useState(initialData); @@ -63,7 +61,7 @@ const PatientNotes = (props: PatientNotesProps) => { return; } - const { res } = await request(routes.addPatientNote, { + const { res, data } = await request(routes.addPatientNote, { pathParams: { patientId: patientId }, body: { note: noteField, @@ -71,12 +69,15 @@ const PatientNotes = (props: PatientNotesProps) => { reply_to: reply_to?.id, }, }); - if (res?.status === 201) { - Notification.Success({ msg: "Note added successfully" }); + + if (res?.status === 201 && data) { setNoteField(""); - setReload(!reload); - setState({ ...state, cPage: 1 }); + setState((prevState) => ({ + ...prevState, + notes: [data, ...prevState.notes], + })); setReplyTo(undefined); + Notification.Success({ msg: "Note added successfully" }); } }; @@ -130,13 +131,17 @@ const PatientNotes = (props: PatientNotesProps) => { ? "border-primary-500 font-bold text-secondary-800" : "border-secondary-300 text-secondary-800", )} - onClick={() => setThread(PATIENT_NOTES_THREADS[current])} + onClick={() => { + setThread(PATIENT_NOTES_THREADS[current]); + setState(initialData); + }} > {t(`patient_notes_thread__${current}`)} ))}