From 23af9e05f50490412f630443c42aa6c3c7148d6f Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Wed, 20 Dec 2023 20:34:28 +0530 Subject: [PATCH] Add Consultation Specific route for Doctor Notes (#6851) * add new route * refactor * show consultation specific notes by passing consultation param in api * use useQuery * refactor * fix offset calls * refactor props * use common component * separate files for notes * auto fetch on adding new note * refactor * remove dispatch * remove dispatch on patient notes * use request instead of usequery on consultation notes page * replace useQuery with request on patient notes * remove dispatch * useQuery and change model name --- .../Facility/ConsultationDetails/index.tsx | 1 + .../ConsultationDoctorNotes/index.tsx | 135 ++++++++++++++++++ src/Components/Facility/DoctorNote.tsx | 45 ++++++ .../Facility/PatientConsultationNotesList.tsx | 87 +++++++++++ src/Components/Facility/PatientNotesList.tsx | 125 +++++----------- .../Facility/PatientNotesSlideover.tsx | 48 +++++-- src/Components/Facility/models.tsx | 6 + src/Components/Patient/PatientNotes.tsx | 40 ++++-- src/Redux/api.tsx | 5 + src/Routers/routes/ConsultationRoutes.tsx | 9 ++ 10 files changed, 388 insertions(+), 113 deletions(-) create mode 100644 src/Components/Facility/ConsultationDoctorNotes/index.tsx create mode 100644 src/Components/Facility/DoctorNote.tsx create mode 100644 src/Components/Facility/PatientConsultationNotesList.tsx diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 54eee52878e..c1086a67221 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -475,6 +475,7 @@ export const ConsultationDetails = (props: any) => { )} diff --git a/src/Components/Facility/ConsultationDoctorNotes/index.tsx b/src/Components/Facility/ConsultationDoctorNotes/index.tsx new file mode 100644 index 00000000000..dd96ef9af1e --- /dev/null +++ b/src/Components/Facility/ConsultationDoctorNotes/index.tsx @@ -0,0 +1,135 @@ +import { useState } from "react"; +import * as Notification from "../../../Utils/Notifications.js"; +import Page from "../../Common/components/Page"; +import TextFormField from "../../Form/FormFields/TextFormField"; +import ButtonV2 from "../../Common/components/ButtonV2"; +import CareIcon from "../../../CAREUI/icons/CareIcon"; +import { NonReadOnlyUsers } from "../../../Utils/AuthorizeFor"; +import { useMessageListener } from "../../../Common/hooks/useMessageListener"; +import PatientConsultationNotesList from "../PatientConsultationNotesList.js"; +import { PatientNoteStateType } from "../models.js"; +import routes from "../../../Redux/api.js"; +import request from "../../../Utils/request/request.js"; +import useQuery from "../../../Utils/request/useQuery.js"; + +interface ConsultationDoctorNotesProps { + patientId: string; + facilityId: string; + consultationId: string; +} + +const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { + const { patientId, facilityId, consultationId } = props; + + const [patientActive, setPatientActive] = useState(true); + const [noteField, setNoteField] = useState(""); + const [reload, setReload] = useState(false); + const [facilityName, setFacilityName] = useState(""); + const [patientName, setPatientName] = useState(""); + + const initialData: PatientNoteStateType = { + notes: [], + cPage: 1, + totalPages: 1, + }; + const [state, setState] = useState(initialData); + + const onAddNote = async () => { + const payload = { + note: noteField, + consultation: consultationId, + }; + if (!/\S+/.test(noteField)) { + Notification.Error({ + msg: "Note Should Contain At Least 1 Character", + }); + return; + } + + const { res } = await request(routes.addPatientNote, { + pathParams: { + patientId: patientId, + }, + body: payload, + }); + + if (res?.status === 201) { + Notification.Success({ msg: "Note added successfully" }); + setState({ ...state, cPage: 1 }); + setNoteField(""); + setReload(true); + } + }; + + useQuery(routes.getPatient, { + pathParams: { id: patientId }, + onResponse: ({ data }) => { + if (data) { + setPatientActive(data.is_active ?? true); + setPatientName(data.name ?? ""); + setFacilityName(data.facility_object?.name ?? ""); + } + }, + }); + + useMessageListener((data) => { + const message = data?.message; + if ( + (message?.from == "patient/doctor_notes/create" || + message?.from == "patient/doctor_notes/edit") && + message?.facility_id == facilityId && + message?.patient_id == patientId + ) { + setReload(true); + } + }); + + return ( + +
+ + +
+ setNoteField(e.value)} + className="grow" + type="text" + errorClassName="hidden" + placeholder="Type your Note" + disabled={!patientActive} + /> + + + +
+
+
+ ); +}; + +export default ConsultationDoctorNotes; diff --git a/src/Components/Facility/DoctorNote.tsx b/src/Components/Facility/DoctorNote.tsx new file mode 100644 index 00000000000..85703a1e3d8 --- /dev/null +++ b/src/Components/Facility/DoctorNote.tsx @@ -0,0 +1,45 @@ +import InfiniteScroll from "react-infinite-scroll-component"; +import CircularProgress from "../Common/components/CircularProgress"; +import PatientNoteCard from "./PatientNoteCard"; +import { PatientNoteStateType } from "./models"; + +interface DoctorNoteProps { + state: PatientNoteStateType; + handleNext: () => void; +} + +const DoctorNote = (props: DoctorNoteProps) => { + const { state, handleNext } = props; + return ( +
+ {state.notes.length ? ( + + +
+ } + className="flex h-full flex-col-reverse p-2" + inverse={true} + dataLength={state.notes.length} + scrollableTarget="patient-notes-list" + > + {state.notes.map((note: any) => ( + + ))} + + ) : ( +
+ No Notes Found +
+ )} + + ); +}; + +export default DoctorNote; diff --git a/src/Components/Facility/PatientConsultationNotesList.tsx b/src/Components/Facility/PatientConsultationNotesList.tsx new file mode 100644 index 00000000000..f38de51110b --- /dev/null +++ b/src/Components/Facility/PatientConsultationNotesList.tsx @@ -0,0 +1,87 @@ +import { useEffect, useState } from "react"; +import { RESULTS_PER_PAGE_LIMIT } from "../../Common/constants"; +import CircularProgress from "../Common/components/CircularProgress"; +import routes from "../../Redux/api"; +import { PatientNoteStateType } from "./models"; +import useSlug from "../../Common/hooks/useSlug"; +import DoctorNote from "./DoctorNote"; +import request from "../../Utils/request/request"; + +interface PatientNotesProps { + state: PatientNoteStateType; + setState: any; + patientId: string; + facilityId: string; + reload?: boolean; + setReload?: any; +} + +const pageSize = RESULTS_PER_PAGE_LIMIT; + +const PatientConsultationNotesList = (props: PatientNotesProps) => { + const { state, setState, reload, setReload } = props; + const consultationId = useSlug("consultation") ?? ""; + + const [isLoading, setIsLoading] = useState(true); + + const fetchNotes = async () => { + setIsLoading(true); + const { data }: any = await request(routes.getPatientNotes, { + pathParams: { + patientId: props.patientId, + }, + query: { + consultation: consultationId, + offset: (state.cPage - 1) * RESULTS_PER_PAGE_LIMIT, + }, + }); + + 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); + }; + + useEffect(() => { + if (reload) { + fetchNotes(); + } + }, [reload]); + + useEffect(() => { + setReload(true); + }, []); + + const handleNext = () => { + if (state.cPage < state.totalPages) { + setState((prevState: any) => ({ + ...prevState, + cPage: prevState.cPage + 1, + })); + setReload(true); + } + }; + + if (isLoading && !state.notes.length) { + return ( +
+ +
+ ); + } + + return ; +}; + +export default PatientConsultationNotesList; diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 330bca4b06d..96f9dcad871 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -1,86 +1,68 @@ -import { useCallback, useState, useEffect } from "react"; -import { useDispatch } from "react-redux"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { getPatientNotes } from "../../Redux/actions"; +import { useState, useEffect } from "react"; import { RESULTS_PER_PAGE_LIMIT } from "../../Common/constants"; import CircularProgress from "../Common/components/CircularProgress"; -import PatientNoteCard from "./PatientNoteCard"; -import InfiniteScroll from "react-infinite-scroll-component"; -import { NoteType } from "./PatientNoteCard"; +import DoctorNote from "./DoctorNote"; +import { PatientNoteStateType } from "./models"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; interface PatientNotesProps { - patientId: any; - facilityId: any; + state: PatientNoteStateType; + setState: any; + patientId: string; + facilityId: string; reload?: boolean; setReload?: any; } -interface StateType { - notes: NoteType[]; - cPage: number; - totalPages: number; -} - const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientNotesList = (props: PatientNotesProps) => { - const { reload, setReload } = props; + const { state, setState, reload, setReload } = props; - const dispatch: any = useDispatch(); - const initialData: StateType = { notes: [], cPage: 1, totalPages: 1 }; - const [state, setState] = useState(initialData); const [isLoading, setIsLoading] = useState(true); - const fetchData = useCallback( - async (page = 1, status: statusType = { aborted: false }) => { - setIsLoading(true); - const res = await dispatch( - getPatientNotes(props.patientId, pageSize, (page - 1) * pageSize) - ); - if (!status.aborted) { - if (res && res.data) { - if (page === 1) { - setState({ - notes: res.data?.results, - cPage: page, - totalPages: Math.ceil(res.data.count / pageSize), - }); - } else { - setState((prevState: any) => ({ - ...prevState, - notes: [...prevState.notes, ...res.data.results], - cPage: page, - totalPages: Math.ceil(res.data.count / pageSize), - })); - } - } - setIsLoading(false); - } - }, - [props.patientId, dispatch] - ); + 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 }, + }); + + 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); + }; useEffect(() => { if (reload) { - fetchData(1); - setReload(false); + fetchNotes(); } }, [reload]); - useAbortableEffect( - (status: statusType) => { - fetchData(1, status); - }, - [fetchData] - ); + useEffect(() => { + setReload(true); + }, []); const handleNext = () => { if (state.cPage < state.totalPages) { - fetchData(state.cPage + 1); setState((prevState: any) => ({ ...prevState, cPage: prevState.cPage + 1, })); + setReload(true); } }; @@ -92,36 +74,7 @@ const PatientNotesList = (props: PatientNotesProps) => { ); } - return ( -
- {state.notes.length ? ( - - -
- } - className="flex h-full flex-col-reverse p-2" - inverse={true} - dataLength={state.notes.length} - scrollableTarget="patient-notes-list" - > - {state.notes.map((note: any) => ( - - ))} - - ) : ( -
- No Notes Found -
- )} - - ); + return ; }; export default PatientNotesList; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index b268500f6cf..8b08d9767db 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -1,8 +1,5 @@ import { useState, useEffect, Dispatch, SetStateAction } from "react"; -import { getPatient, addPatientNote } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; -import { useDispatch } from "react-redux"; -import PatientNotesList from "./PatientNotesList"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { classNames } from "../../Utils/utils"; @@ -10,10 +7,15 @@ import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2 from "../Common/components/ButtonV2"; import { make as Link } from "../Common/components/Link.bs"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; +import PatientConsultationNotesList from "./PatientConsultationNotesList"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { PatientNoteStateType } from "./models"; interface PatientNotesProps { patientId: string; facilityId: string; + consultationId: string; setShowPatientNotesPopup: Dispatch>; } @@ -23,13 +25,20 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const [noteField, setNoteField] = useState(""); const [reload, setReload] = useState(false); - const dispatch = useDispatch(); + const initialData: PatientNoteStateType = { + notes: [], + cPage: 1, + totalPages: 1, + }; + const [state, setState] = useState(initialData); - const { facilityId, patientId, setShowPatientNotesPopup } = props; + const { facilityId, patientId, consultationId, setShowPatientNotesPopup } = + props; - const onAddNote = () => { + const onAddNote = async () => { const payload = { note: noteField, + consultation: consultationId, }; if (!/\S+/.test(noteField)) { Notification.Error({ @@ -37,11 +46,16 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { }); return; } - dispatch(addPatientNote(patientId, payload)).then(() => { + const { res } = await request(routes.addPatientNote, { + pathParams: { patientId: patientId }, + body: payload, + }); + if (res?.status === 201) { Notification.Success({ msg: "Note added successfully" }); setNoteField(""); - setReload(!reload); - }); + setState({ ...state, cPage: 1 }); + setReload(true); + } }; useMessageListener((data) => { @@ -59,21 +73,23 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { useEffect(() => { async function fetchPatientName() { if (patientId) { - const res = await dispatch(getPatient({ id: patientId })); - if (res.data) { - setPatientActive(res.data.is_active); + const { data } = await request(routes.getPatient, { + pathParams: { id: patientId }, + }); + if (data) { + setPatientActive(data.is_active ?? true); } } } fetchPatientName(); - }, [dispatch, patientId]); + }, [patientId]); const notesActionIcons = (
{show && ( @@ -122,7 +138,9 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { {notesActionIcons}
{/* Doctor Notes Body */} - { const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); - const dispatch = useDispatch(); + const initialData: PatientNoteStateType = { + notes: [], + cPage: 1, + totalPages: 1, + }; + const [state, setState] = useState(initialData); - const onAddNote = () => { + const onAddNote = async () => { const payload = { note: noteField, }; @@ -36,26 +42,34 @@ const PatientNotes = (props: PatientNotesProps) => { }); return; } - dispatch(addPatientNote(patientId, payload)).then(() => { + + const { res } = await request(routes.addPatientNote, { + pathParams: { patientId: patientId }, + body: payload, + }); + if (res?.status === 201) { Notification.Success({ msg: "Note added successfully" }); setNoteField(""); setReload(!reload); - }); + setState({ ...state, cPage: 1 }); + } }; useEffect(() => { async function fetchPatientName() { if (patientId) { - const res = await dispatch(getPatient({ id: patientId })); - if (res.data) { - setPatientActive(res.data.is_active); - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); + const { data } = await request(routes.getPatient, { + pathParams: { id: patientId }, + }); + if (data) { + setPatientActive(data.is_active ?? true); + setPatientName(data.name ?? ""); + setFacilityName(data.facility_object?.name ?? ""); } } } fetchPatientName(); - }, [dispatch, patientId]); + }, [patientId]); useMessageListener((data) => { const message = data?.message; @@ -81,6 +95,8 @@ const PatientNotes = (props: PatientNotesProps) => { >
(), TRes: Type(), }, updatePatient: { @@ -614,10 +616,13 @@ const routes = { getPatientNotes: { path: "/api/v1/patient/{patientId}/notes/", method: "GET", + TBody: Type(), + TRes: Type>(), }, addPatientNote: { path: "/api/v1/patient/{patientId}/notes/", method: "POST", + TRes: Type(), }, sampleTestList: { path: "/api/v1/patient/{patientId}/test_sample/", diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index 4f1d6e7d75d..401358717eb 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -8,6 +8,7 @@ import { FileUpload } from "../../Components/Patient/FileUpload"; import { make as CriticalCareRecording } from "../../Components/CriticalCareRecording/CriticalCareRecording.bs"; import { ConsultationDetails } from "../../Components/Facility/ConsultationDetails"; import TreatmentSummary from "../../Components/Facility/TreatmentSummary"; +import ConsultationDoctorNotes from "../../Components/Facility/ConsultationDoctorNotes"; export default { "/facility/:facilityId/patient/:patientId/consultation": ({ @@ -129,6 +130,14 @@ export default { dailyRoundsListData={[]} /> ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/notes": + ({ facilityId, patientId, consultationId }: any) => ( + + ), "/facility/:facilityId/patient/:patientId/consultation/:consultationId/:tab": ({ facilityId, patientId, consultationId, tab }: any) => (