From 53568617d35fe49060c69c75393af7f482161aa1 Mon Sep 17 00:00:00 2001 From: Bhavik Agarwal Date: Fri, 7 Jul 2023 11:26:55 +0530 Subject: [PATCH 01/17] feat: add patient notes pop-up on consultation page --- package-lock.json | 33 +++++ package.json | 1 + .../Facility/ConsultationDetails.tsx | 3 + src/Components/Facility/PatientNoteCard.tsx | 39 ++++++ src/Components/Facility/PatientNotesList.tsx | 125 ++++++++++++++++++ .../Facility/PatientNotesSlideover.tsx | 117 ++++++++++++++++ 6 files changed, 318 insertions(+) create mode 100644 src/Components/Facility/PatientNoteCard.tsx create mode 100644 src/Components/Facility/PatientNotesList.tsx create mode 100644 src/Components/Facility/PatientNotesSlideover.tsx diff --git a/package-lock.json b/package-lock.json index 1d07e4fe19e..817cd903efe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "react-dom": "18.2.0", "react-google-recaptcha": "^2.1.0", "react-i18next": "^11.18.6", + "react-infinite-scroll-component": "^6.1.0", "react-player": "^2.11.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.0.4", @@ -14062,6 +14063,17 @@ } } }, + "node_modules/react-infinite-scroll-component": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz", + "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==", + "dependencies": { + "throttle-debounce": "^2.1.0" + }, + "peerDependencies": { + "react": ">=16.0.0" + } + }, "node_modules/react-inspector": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.1.tgz", @@ -15870,6 +15882,14 @@ "dev": true, "license": "MIT" }, + "node_modules/throttle-debounce": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", + "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", @@ -27049,6 +27069,14 @@ "html-parse-stringify": "^3.0.1" } }, + "react-infinite-scroll-component": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz", + "integrity": "sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ==", + "requires": { + "throttle-debounce": "^2.1.0" + } + }, "react-inspector": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/react-inspector/-/react-inspector-6.0.1.tgz", @@ -28397,6 +28425,11 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, + "throttle-debounce": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-2.3.0.tgz", + "integrity": "sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ==" + }, "throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", diff --git a/package.json b/package.json index fb232281bad..64145215a96 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "react-dom": "18.2.0", "react-google-recaptcha": "^2.1.0", "react-i18next": "^11.18.6", + "react-infinite-scroll-component": "^6.1.0", "react-player": "^2.11.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.0.4", diff --git a/src/Components/Facility/ConsultationDetails.tsx b/src/Components/Facility/ConsultationDetails.tsx index 08681ba55fc..acdae779bab 100644 --- a/src/Components/Facility/ConsultationDetails.tsx +++ b/src/Components/Facility/ConsultationDetails.tsx @@ -53,6 +53,7 @@ import moment from "moment"; import { navigate } from "raviger"; import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; +import PatientNotesSlideover from "./PatientNotesSlideover"; const Loading = loadable(() => import("../Common/Loading")); const PageTitle = loadable(() => import("../Common/PageTitle")); @@ -1236,6 +1237,8 @@ export const ConsultationDetails = (props: any) => { show={showDoctors} setShow={setShowDoctors} /> + + ); }; diff --git a/src/Components/Facility/PatientNoteCard.tsx b/src/Components/Facility/PatientNoteCard.tsx new file mode 100644 index 00000000000..b90b089352c --- /dev/null +++ b/src/Components/Facility/PatientNoteCard.tsx @@ -0,0 +1,39 @@ +import { relativeDate, formatDate } from "../../Utils/utils"; + +const PatientNoteCard = ({ + note, + facilityId, +}: { + note: any; + facilityId: any; +}) => { + return ( +
+
+ + {note.created_by_object?.first_name || "Unknown"}{" "} + {note.created_by_object?.last_name} + + + {note.created_by_object.id === facilityId + ? "Remote Specialist" + : "Local Doctor"} + +
+ {note.note} +
+
+ + {formatDate(note.created_date)} + + {relativeDate(note.created_date)} +
+
+
+ ); +}; + +export default PatientNoteCard; diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx new file mode 100644 index 00000000000..45464895697 --- /dev/null +++ b/src/Components/Facility/PatientNotesList.tsx @@ -0,0 +1,125 @@ +import { useCallback, useState, useEffect } from "react"; +import { useDispatch } from "react-redux"; +import { statusType, useAbortableEffect } from "../../Common/utils"; +import { getPatientNotes } from "../../Redux/actions"; +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"; + +interface PatientNotesProps { + patientId: any; + facilityId: any; + reload?: boolean; + setReload?: any; +} + +const pageSize = RESULTS_PER_PAGE_LIMIT; + +const PatientNotesList = (props: PatientNotesProps) => { + const { facilityId, reload, setReload } = props; + + const dispatch: any = useDispatch(); + const initialData: any = { 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] + ); + + useEffect(() => { + if (reload) { + fetchData(1); + setReload(false); + } + }, [reload]); + + useAbortableEffect( + (status: statusType) => { + fetchData(1, status); + }, + [fetchData] + ); + + const handleNext = () => { + if (state.cPage < state.totalPages) { + fetchData(state.cPage + 1); + setState((prevState: any) => ({ + ...prevState, + cPage: prevState.cPage + 1, + })); + } + }; + + if (isLoading && !state.notes.length) { + return ( +
+ +
+ ); + } + + return ( +
+ {state.notes.length ? ( + + +
+ } + className="flex 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 PatientNotesList; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx new file mode 100644 index 00000000000..a9a9ac72a26 --- /dev/null +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -0,0 +1,117 @@ +import { useState, useEffect } 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 AuthorizedChild from "../../CAREUI/misc/AuthorizedChild"; +import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import CareIcon from "../../CAREUI/icons/CareIcon"; + +interface PatientNotesProps { + patientId: any; + facilityId: any; +} + +export default function PatientNotesSlideover(props: PatientNotesProps) { + const [show, setShow] = useState(false); + const [patientActive, setPatientActive] = useState(true); + const [noteField, setNoteField] = useState(""); + const [reload, setReload] = useState(false); + + const dispatch = useDispatch(); + + const { facilityId, patientId } = props; + + const onAddNote = () => { + const payload = { + note: noteField, + }; + if (!/\S+/.test(noteField)) { + Notification.Error({ + msg: "Note Should Contain At Least 1 Character", + }); + return; + } + dispatch(addPatientNote(patientId, payload)).then(() => { + Notification.Success({ msg: "Note added successfully" }); + setNoteField(""); + setReload(!reload); + }); + }; + + useEffect(() => { + async function fetchPatientName() { + if (patientId) { + const res = await dispatch(getPatient({ id: patientId })); + if (res.data) { + setPatientActive(res.data.is_active); + } + } + } + fetchPatientName(); + }, [dispatch, patientId]); + + return ( +
+ {!show ? ( +
+ Doctor's Notes +
setShow(!show)} + > + +
+
+ ) : ( +
+ {/* Doctor Notes Header */} +
+ Doctor's Notes +
setShow(!show)} + > + +
+
+ {/* Doctor Notes Body */} + + + {({ isAuthorized }) => ( +
+ setNoteField(e.target.value)} + disabled={!patientActive || !isAuthorized} + /> + +
+ )} +
+
+ )} +
+ ); +} From a7155e4d59e2d8b5bee9942c96ebe5c801694445 Mon Sep 17 00:00:00 2001 From: Bhavik Agarwal Date: Fri, 7 Jul 2023 13:23:00 +0530 Subject: [PATCH 02/17] Migrate form elements to CAREUI --- src/Components/Facility/PatientNotesList.tsx | 4 +- .../Facility/PatientNotesSlideover.tsx | 72 ++++++++++--------- 2 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 45464895697..95ada03f458 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -79,7 +79,7 @@ const PatientNotesList = (props: PatientNotesProps) => { if (isLoading && !state.notes.length) { return ( -
+
); @@ -94,7 +94,7 @@ const PatientNotesList = (props: PatientNotesProps) => { diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index a9a9ac72a26..45b6857b412 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -3,9 +3,11 @@ import { getPatient, addPatientNote } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; import { useDispatch } from "react-redux"; import PatientNotesList from "./PatientNotesList"; -import AuthorizedChild from "../../CAREUI/misc/AuthorizedChild"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { classNames } from "../../Utils/utils"; +import TextFormField from "../Form/FormFields/TextFormField"; +import ButtonV2 from "../Common/components/ButtonV2"; interface PatientNotesProps { patientId: any; @@ -53,34 +55,37 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { return (
{!show ? (
Doctor's Notes
setShow(!show)} > - +
) : ( -
+
{/* Doctor Notes Header */} -
+
Doctor's Notes
setShow(!show)} > - +
{/* Doctor Notes Body */} @@ -90,26 +95,29 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { reload={reload} setReload={setReload} /> - - {({ isAuthorized }) => ( -
- setNoteField(e.target.value)} - disabled={!patientActive || !isAuthorized} - /> - -
- )} -
+
+ setNoteField(e.value)} + className="grow" + type="text" + errorClassName="hidden" + placeholder="Type your Note" + disabled={!patientActive} + /> + + + +
)}
From 10a77613a36cce4ac77cf20044efc8f8dd6551b9 Mon Sep 17 00:00:00 2001 From: Bhavik Agarwal Date: Sat, 8 Jul 2023 13:33:02 +0530 Subject: [PATCH 03/17] made patient notes popup responsive --- src/Components/Facility/PatientNotesList.tsx | 5 ++--- src/Components/Facility/PatientNotesSlideover.tsx | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 95ada03f458..69dda827f4c 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -87,20 +87,19 @@ const PatientNotesList = (props: PatientNotesProps) => { return (
{state.notes.length ? (
} - className="flex flex-col-reverse p-2" + className="flex flex-col-reverse p-2 h-full" inverse={true} dataLength={state.notes.length} scrollableTarget="patient-notes-list" diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index 45b6857b412..d377064c6dc 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -56,8 +56,10 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { return (
{!show ? ( @@ -74,7 +76,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) {
) : ( -
+
{/* Doctor Notes Header */}
Doctor's Notes From e3c3966fabc82224b1fd78407f4e53415aa177d7 Mon Sep 17 00:00:00 2001 From: Bhavik Agarwal Date: Wed, 12 Jul 2023 13:05:35 +0530 Subject: [PATCH 04/17] feat: updated ui of patient notes dedicated page --- src/Components/Facility/PatientNoteCard.tsx | 8 +- src/Components/Patient/PatientNotes.tsx | 196 ++++++-------------- 2 files changed, 57 insertions(+), 147 deletions(-) diff --git a/src/Components/Facility/PatientNoteCard.tsx b/src/Components/Facility/PatientNoteCard.tsx index b90b089352c..8029ec915c1 100644 --- a/src/Components/Facility/PatientNoteCard.tsx +++ b/src/Components/Facility/PatientNoteCard.tsx @@ -18,9 +18,11 @@ const PatientNoteCard = ({ {note.created_by_object?.last_name} - {note.created_by_object.id === facilityId - ? "Remote Specialist" - : "Local Doctor"} + {note.created_by_object.user_type === "Doctor" + ? note.created_by_object.home_facility !== facilityId + ? "Remote Specialist" + : "" + : note.created_by_object.user_type}
{note.note} diff --git a/src/Components/Patient/PatientNotes.tsx b/src/Components/Patient/PatientNotes.tsx index 0756883d1d2..f5d09c80473 100644 --- a/src/Components/Patient/PatientNotes.tsx +++ b/src/Components/Patient/PatientNotes.tsx @@ -1,88 +1,29 @@ -import React, { useCallback, useState, useEffect } from "react"; +import { useState, useEffect } from "react"; import { useDispatch } from "react-redux"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { - getPatientNotes, - addPatientNote, - getPatient, -} from "../../Redux/actions"; +import { addPatientNote, getPatient } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; import PageTitle from "../Common/PageTitle"; -import Pagination from "../Common/Pagination"; -import { navigate } from "raviger"; -import { RESULTS_PER_PAGE_LIMIT } from "../../Common/constants"; -import Loading from "../Common/Loading"; -import { formatDate } from "../../Utils/utils"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import PatientNotesList from "../Facility/PatientNotesList"; interface PatientNotesProps { patientId: any; facilityId: any; } -const pageSize = RESULTS_PER_PAGE_LIMIT; - const PatientNotes = (props: PatientNotesProps) => { const { patientId, facilityId } = props; - const dispatch: any = useDispatch(); - const initialData: any = { notes: [], cPage: 1, count: 1 }; - const [state, setState] = useState(initialData); + const [patientActive, setPatientActive] = useState(true); const [noteField, setNoteField] = useState(""); - const [isLoading, setIsLoading] = useState(true); + const [reload, setReload] = useState(false); const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); - const [patientActive, setPatientActive] = 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) { - setState({ - ...state, - count: res.data?.count, - notes: res.data?.results, - cPage: page, - }); - } - setIsLoading(false); - } - }, - [props.patientId, dispatch] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(1, status); - }, - [fetchData] - ); - - useEffect(() => { - async function fetchPatientName() { - if (patientId) { - const res = await dispatch(getPatient({ id: patientId })); - if (res.data) { - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); - setPatientActive(res.data.is_active); - } - } else { - setPatientName(""); - setFacilityName(""); - } - } - fetchPatientName(); - }, [dispatch, patientId]); - function handlePagination(page: number) { - fetchData(page); - } + const dispatch = useDispatch(); const onAddNote = () => { const payload = { @@ -94,16 +35,26 @@ const PatientNotes = (props: PatientNotesProps) => { }); return; } - dispatch(addPatientNote(props.patientId, payload)).then(() => { + dispatch(addPatientNote(patientId, payload)).then(() => { Notification.Success({ msg: "Note added successfully" }); setNoteField(""); - fetchData(); + setReload(!reload); }); }; - if (isLoading) { - return ; - } + 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); + } + } + } + fetchPatientName(); + }, [dispatch, patientId]); return (
@@ -116,80 +67,37 @@ const PatientNotes = (props: PatientNotesProps) => { }} backUrl={`/facility/${facilityId}/patient/${patientId}`} /> -

Add new notes

-