From c483e81203b6408b433a4d04b4ae3e6117861b13 Mon Sep 17 00:00:00 2001 From: Uday Sagar <111575806+UdaySagar-Git@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:50:55 +0530 Subject: [PATCH] Reply feature for doctor notes (#7805) Co-authored-by: Rithvik Nishad --- .../ConsultationDoctorNotes/index.tsx | 71 ++++++++++-------- src/Components/Facility/DoctorNote.tsx | 21 ++++-- .../Facility/DoctorNoteReplyPreviewCard.tsx | 64 +++++++++++++++++ .../Facility/PatientConsultationNotesList.tsx | 12 +++- src/Components/Facility/PatientNoteCard.tsx | 37 ++++++---- src/Components/Facility/PatientNotesList.tsx | 10 ++- .../Facility/PatientNotesSlideover.tsx | 72 +++++++++++-------- src/Components/Facility/models.tsx | 9 +++ src/Components/Patient/PatientNotes.tsx | 65 ++++++++++------- src/Redux/api.tsx | 5 +- 10 files changed, 255 insertions(+), 111 deletions(-) create mode 100644 src/Components/Facility/DoctorNoteReplyPreviewCard.tsx diff --git a/src/Components/Facility/ConsultationDoctorNotes/index.tsx b/src/Components/Facility/ConsultationDoctorNotes/index.tsx index cf7d9c61ef9..3d334ba2cb6 100644 --- a/src/Components/Facility/ConsultationDoctorNotes/index.tsx +++ b/src/Components/Facility/ConsultationDoctorNotes/index.tsx @@ -6,7 +6,7 @@ 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 { PatientNoteStateType, PaitentNotesReplyModel } from "../models.js"; import routes from "../../../Redux/api.js"; import request from "../../../Utils/request/request.js"; import useQuery from "../../../Utils/request/useQuery.js"; @@ -15,6 +15,7 @@ import { classNames, isAppleDevice, keysOf } from "../../../Utils/utils.js"; import AutoExpandingTextInputFormField from "../../Form/FormFields/AutoExpandingTextInputFormField.js"; import { PATIENT_NOTES_THREADS } from "../../../Common/constants.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; +import DoctorNoteReplyPreviewCard from "../DoctorNoteReplyPreviewCard.js"; import { t } from "i18next"; interface ConsultationDoctorNotesProps { @@ -39,6 +40,9 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); const [focused, setFocused] = useState(false); + const [reply_to, setReplyTo] = useState( + undefined, + ); const initialData: PatientNoteStateType = { notes: [], @@ -65,6 +69,7 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { note: noteField, thread, consultation: consultationId, + reply_to: reply_to?.id, }, }); @@ -73,6 +78,7 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { setState({ ...state, cPage: 1 }); setNoteField(""); setReload(true); + setReplyTo(undefined); } }; @@ -145,36 +151,41 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { reload={reload} setReload={setReload} thread={thread} + setReplyTo={setReplyTo} /> - -
- setNoteField(e.value)} - className="w-full grow" - innerClassName="pr-10" - errorClassName="hidden" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - - -
+ setReplyTo(undefined)} + > +
+ setNoteField(e.value)} + className="w-full grow" + innerClassName="pr-10" + errorClassName="hidden" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + /> + + + +
+
); diff --git a/src/Components/Facility/DoctorNote.tsx b/src/Components/Facility/DoctorNote.tsx index 91c361cc82e..c2bf8155cf2 100644 --- a/src/Components/Facility/DoctorNote.tsx +++ b/src/Components/Facility/DoctorNote.tsx @@ -1,17 +1,19 @@ import InfiniteScroll from "react-infinite-scroll-component"; import CircularProgress from "../Common/components/CircularProgress"; import PatientNoteCard from "./PatientNoteCard"; -import { PatientNoteStateType } from "./models"; +import { PatientNoteStateType, PatientNotesModel } from "./models"; +import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard"; interface DoctorNoteProps { state: PatientNoteStateType; setReload: any; handleNext: () => void; disableEdit?: boolean; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; } const DoctorNote = (props: DoctorNoteProps) => { - const { state, handleNext, setReload, disableEdit } = props; + const { state, handleNext, setReload, disableEdit, setReplyTo } = props; return (
{ scrollableTarget="patient-notes-list" > {state.notes.map((note) => ( - + parentNote={note.reply_to_object} + > + + ))} ) : ( diff --git a/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx b/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx new file mode 100644 index 00000000000..470f05f2bfe --- /dev/null +++ b/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { PaitentNotesReplyModel } from "./models"; +import { USER_TYPES_MAP } from "../../Common/constants"; +import { formatDateTime, relativeDate } from "../../Utils/utils"; + +interface Props { + parentNote: PaitentNotesReplyModel | undefined; + children: React.ReactNode; + cancelReply?: () => void; +} + +const DoctorNoteReplyPreviewCard = ({ + parentNote, + children, + cancelReply, +}: Props) => { + return ( +
+ {parentNote ? ( +
+
+
+
+
+ + {parentNote.created_by_object?.first_name || "Unknown"}{" "} + {parentNote.created_by_object?.last_name} + + {parentNote.user_type && ( + + {`(${USER_TYPES_MAP[parentNote.user_type]})`} + + )} +
+
+
+ + {formatDateTime(parentNote.created_date)} + + Created {relativeDate(parentNote.created_date, true)} +
+
+
+ {cancelReply && ( +
+ Cancel +
+ )} +
+
{parentNote.note}
+
+
{children}
+
+ ) : ( +
{children}
+ )} +
+ ); +}; + +export default DoctorNoteReplyPreviewCard; diff --git a/src/Components/Facility/PatientConsultationNotesList.tsx b/src/Components/Facility/PatientConsultationNotesList.tsx index f81ef122f6c..15238ff189f 100644 --- a/src/Components/Facility/PatientConsultationNotesList.tsx +++ b/src/Components/Facility/PatientConsultationNotesList.tsx @@ -14,12 +14,21 @@ interface PatientNotesProps { setReload?: (value: boolean) => void; disableEdit?: boolean; thread: PatientNotesModel["thread"]; + setReplyTo?: (value: PatientNotesModel | undefined) => void; } const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientConsultationNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, disableEdit, thread } = props; + const { + state, + setState, + reload, + setReload, + disableEdit, + thread, + setReplyTo, + } = props; const consultationId = useSlug("consultation") ?? ""; const [isLoading, setIsLoading] = useState(true); @@ -95,6 +104,7 @@ const PatientConsultationNotesList = (props: PatientNotesProps) => { handleNext={handleNext} setReload={setReload} disableEdit={disableEdit} + setReplyTo={setReplyTo} /> ); }; diff --git a/src/Components/Facility/PatientNoteCard.tsx b/src/Components/Facility/PatientNoteCard.tsx index 7d2a8c6eb70..9ddfbc009e6 100644 --- a/src/Components/Facility/PatientNoteCard.tsx +++ b/src/Components/Facility/PatientNoteCard.tsx @@ -23,10 +23,12 @@ const PatientNoteCard = ({ note, setReload, disableEdit, + setReplyTo, }: { note: PatientNotesModel; setReload: any; disableEdit?: boolean; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; }) => { const patientId = useSlug("patient"); const [isEditing, setIsEditing] = useState(false); @@ -128,19 +130,28 @@ const PatientNoteCard = ({ ) }
- - {!disableEdit && - note.created_by_object.id === authUser.id && - !isEditing && ( - { - setIsEditing(true); - }} - > - - - )} +
+ {!disableEdit && + note.created_by_object.id === authUser.id && + !isEditing && ( + { + setIsEditing(true); + }} + > + + + )} + { + setReplyTo && setReplyTo(note); + }} + > + + +
{
diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 4e1d0a5a7a6..bbd037e866c 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -14,12 +14,13 @@ interface PatientNotesProps { reload?: boolean; setReload?: any; thread: PatientNotesModel["thread"]; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; } const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, thread } = props; + const { state, setState, reload, setReload, thread, setReplyTo } = props; const [isLoading, setIsLoading] = useState(true); @@ -83,7 +84,12 @@ const PatientNotesList = (props: PatientNotesProps) => { } return ( - + ); }; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index d7847c3add7..52f99aee763 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -8,11 +8,12 @@ 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"; +import { PatientNoteStateType, PaitentNotesReplyModel } from "./models"; import useKeyboardShortcut from "use-keyboard-shortcut"; import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { PATIENT_NOTES_THREADS } from "../../Common/constants.js"; +import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard.js"; import useNotificationSubscriptionState from "../../Common/hooks/useNotificationSubscriptionState.js"; import { Link } from "raviger"; import { t } from "i18next"; @@ -36,6 +37,9 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const [patientActive, setPatientActive] = useState(true); const [reload, setReload] = useState(false); const [focused, setFocused] = useState(false); + const [reply_to, setReplyTo] = useState( + undefined, + ); useEffect(() => { if (notificationSubscriptionState === "unsubscribed") { @@ -79,6 +83,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { note: noteField, consultation: consultationId, thread, + reply_to: reply_to?.id, }, }); if (res?.status === 201) { @@ -86,6 +91,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setNoteField(""); setState({ ...state, cPage: 1 }); setReload(true); + setReplyTo(undefined); } }; @@ -216,36 +222,42 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setReload={setReload} disableEdit={!patientActive} thread={thread} + setReplyTo={setReplyTo} /> -
- setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - - -
+ setReplyTo(undefined)} + > +
+ setNoteField(e.value)} + className="w-full grow" + errorClassName="hidden" + innerClassName="pr-10" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + /> + + + +
+
)} diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index cdb78a5f712..7756bf73f69 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -546,6 +546,14 @@ export interface PatientNotesEditModel { note: string; } +export interface PaitentNotesReplyModel { + id: string; + note: string; + user_type?: UserRole | "RemoteSpecialist"; + created_by_object: BaseUserModel; + created_date: string; +} + export interface PatientNotesModel { id: string; note: string; @@ -556,6 +564,7 @@ export interface PatientNotesModel { created_date: string; last_edited_by?: BaseUserModel; last_edited_date?: string; + reply_to_object?: PaitentNotesReplyModel; } export interface PatientNoteStateType { diff --git a/src/Components/Patient/PatientNotes.tsx b/src/Components/Patient/PatientNotes.tsx index 7138f4df8e5..da97e5d3f4f 100644 --- a/src/Components/Patient/PatientNotes.tsx +++ b/src/Components/Patient/PatientNotes.tsx @@ -6,11 +6,12 @@ import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import PatientNotesList from "../Facility/PatientNotesList"; import Page from "../Common/components/Page"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; -import { PatientNoteStateType } from "../Facility/models"; +import { PatientNoteStateType, PatientNotesModel } from "../Facility/models"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import { PATIENT_NOTES_THREADS } from "../../Common/constants.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; +import DoctorNoteReplyPreviewCard from "../Facility/DoctorNoteReplyPreviewCard.js"; import { classNames, keysOf } from "../../Utils/utils.js"; import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; import { t } from "i18next"; @@ -35,6 +36,9 @@ const PatientNotes = (props: PatientNotesProps) => { const [reload, setReload] = useState(false); const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); + const [reply_to, setReplyTo] = useState( + undefined, + ); const initialData: PatientNoteStateType = { notes: [], @@ -56,6 +60,7 @@ const PatientNotes = (props: PatientNotesProps) => { body: { note: noteField, thread, + reply_to: reply_to?.id, }, }); if (res?.status === 201) { @@ -63,6 +68,7 @@ const PatientNotes = (props: PatientNotesProps) => { setNoteField(""); setReload(!reload); setState({ ...state, cPage: 1 }); + setReplyTo(undefined); } }; @@ -130,33 +136,38 @@ const PatientNotes = (props: PatientNotesProps) => { reload={reload} setReload={setReload} thread={thread} + setReplyTo={setReplyTo} /> - -
- setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - /> - - - -
+ setReplyTo(undefined)} + > +
+ setNoteField(e.value)} + className="w-full grow" + errorClassName="hidden" + innerClassName="pr-10" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + /> + + + +
+
); diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index e53342e3d7c..0243db57df7 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -774,7 +774,10 @@ const routes = { method: "POST", TRes: Type(), TBody: Type< - Pick & { consultation?: string } + Pick & { + consultation?: string; + reply_to?: string; + } >(), }, updatePatientNote: {