From 6b1ebc66c3b6fde4c1ae6045974decff80b49a30 Mon Sep 17 00:00:00 2001 From: Gokulram A <gokulram2010418@ssn.edu.in> Date: Thu, 28 Dec 2023 06:35:29 +0530 Subject: [PATCH 01/20] Add keyboard shortcut for adding notes (#6915) --- .../Facility/ConsultationDoctorNotes/index.tsx | 17 +++++++++++++++++ .../Facility/PatientNotesSlideover.tsx | 18 +++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/Components/Facility/ConsultationDoctorNotes/index.tsx b/src/Components/Facility/ConsultationDoctorNotes/index.tsx index dd96ef9af1e..8e39ee04e4e 100644 --- a/src/Components/Facility/ConsultationDoctorNotes/index.tsx +++ b/src/Components/Facility/ConsultationDoctorNotes/index.tsx @@ -11,6 +11,8 @@ 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"; +import useKeyboardShortcut from "use-keyboard-shortcut"; +import { isAppleDevice } from "../../../Utils/utils.js"; interface ConsultationDoctorNotesProps { patientId: string; @@ -26,6 +28,7 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { const [reload, setReload] = useState(false); const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); + const [focused, setFocused] = useState(false); const initialData: PatientNoteStateType = { notes: [], @@ -84,6 +87,18 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { } }); + useKeyboardShortcut( + [isAppleDevice ? "Meta" : "Shift", "Enter"], + () => { + if (focused) { + onAddNote(); + } + }, + { + ignoreInputFields: false, + } + ); + return ( <Page title="Doctor Notes" @@ -114,6 +129,8 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} /> <ButtonV2 onClick={onAddNote} diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index 8b08d9767db..4279e3c1877 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -2,7 +2,7 @@ import { useState, useEffect, Dispatch, SetStateAction } from "react"; import * as Notification from "../../Utils/Notifications.js"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import { classNames } from "../../Utils/utils"; +import { classNames, isAppleDevice } from "../../Utils/utils"; import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2 from "../Common/components/ButtonV2"; import { make as Link } from "../Common/components/Link.bs"; @@ -11,6 +11,7 @@ import PatientConsultationNotesList from "./PatientConsultationNotesList"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import { PatientNoteStateType } from "./models"; +import useKeyboardShortcut from "use-keyboard-shortcut"; interface PatientNotesProps { patientId: string; @@ -24,6 +25,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const [patientActive, setPatientActive] = useState(true); const [noteField, setNoteField] = useState(""); const [reload, setReload] = useState(false); + const [focused, setFocused] = useState(false); const initialData: PatientNoteStateType = { notes: [], @@ -84,6 +86,18 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { fetchPatientName(); }, [patientId]); + useKeyboardShortcut( + [isAppleDevice ? "Meta" : "Shift", "Enter"], + () => { + if (focused) { + onAddNote(); + } + }, + { + ignoreInputFields: false, + } + ); + const notesActionIcons = ( <div className="flex gap-1"> {show && ( @@ -157,6 +171,8 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} /> <ButtonV2 id="add_doctor_note_button" From 71fbb5e6ce7524cd8ca9fbb802fbb305cd707d3c Mon Sep 17 00:00:00 2001 From: Rithvik Nishad <mail@rithviknishad.dev> Date: Thu, 28 Dec 2023 06:35:53 +0530 Subject: [PATCH 02/20] Rename encounter date field labels (#6914) --- src/Components/Facility/ConsultationForm.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 72944389235..77962cc7703 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -1213,9 +1213,9 @@ export const ConsultationForm = (props: any) => { A: "Date & Time of Admission to the Facility", DC: "Date & Time of Domiciliary Care commencement", OP: "Date & Time of Out-patient visit", - DD: "Date & Time of Encounter", - HI: "Date & Time of Encounter", - R: "Date & Time of Encounter", + DD: "Date & Time of Consultation", + HI: "Date & Time of Consultation", + R: "Date & Time of Consultation", }[state.form.suggestion] } type="datetime-local" From 72f52855285b424dc724606a4783b4c3e868a1e3 Mon Sep 17 00:00:00 2001 From: konavivekramakrishna <101407963+konavivekramakrishna@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:36:39 +0530 Subject: [PATCH 03/20] Support for self-healing Consultation URL (#6893) * added newRoute consultation/:id * fix based on review * minor fix * minor fix --- src/Components/Facility/ConsultationDetails/index.tsx | 10 ++++++---- .../Facility/Consultations/DailyRoundsList.tsx | 8 ++------ src/Routers/routes/ConsultationRoutes.tsx | 3 +++ 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index c438fa3fca7..abc7305a00b 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -46,8 +46,6 @@ const symptomChoices = [...SYMPTOM_CHOICES]; export interface ConsultationTabProps { consultationId: string; - facilityId: string; - patientId: string; consultationData: ConsultationModel; patientData: PatientModel; } @@ -120,6 +118,11 @@ export const ConsultationDetails = (props: any) => { }); data.symptoms_text = symptoms.join(", "); } + if (facilityId != data.facility || patientId != data.patient) { + navigate( + `/facility/${data.facility}/patient/${data.patient}/consultation/${data?.id}` + ); + } setConsultationData(data); const assetRes = data?.current_bed?.bed_object?.id ? await dispatch( @@ -153,6 +156,7 @@ export const ConsultationDetails = (props: any) => { : "No", is_vaccinated: patientData.is_vaccinated ? "Yes" : "No", }; + setPatientData(data); } @@ -184,8 +188,6 @@ export const ConsultationDetails = (props: any) => { const consultationTabProps: ConsultationTabProps = { consultationId, - facilityId, - patientId, consultationData, patientData, }; diff --git a/src/Components/Facility/Consultations/DailyRoundsList.tsx b/src/Components/Facility/Consultations/DailyRoundsList.tsx index 46072c16dd8..3b63b60a427 100644 --- a/src/Components/Facility/Consultations/DailyRoundsList.tsx +++ b/src/Components/Facility/Consultations/DailyRoundsList.tsx @@ -16,14 +16,10 @@ interface Props { } export default function DailyRoundsList({ consultation }: Props) { - const [facilityId, patientId, consultationId] = useSlugs( - "facility", - "patient", - "consultation" - ); + const [consultationId] = useSlugs("consultation"); const { t } = useTranslation(); - const consultationUrl = `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`; + const consultationUrl = `/facility/${consultation.facility}/patient/${consultation.patient}/consultation/${consultation.id}`; return ( <PaginatedList diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index 401358717eb..6dc5fa9c05d 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -121,6 +121,9 @@ export default { tab={"updates"} /> ), + "/consultation/:consultationId": ({ consultationId }: any) => ( + <ConsultationDetails consultationId={consultationId} tab={"updates"} /> + ), "/facility/:facilityId/patient/:patientId/consultation/:consultationId/treatment-summary": ({ facilityId, patientId, consultationId }: any) => ( <TreatmentSummary From 95353a4f456c8671b9552b2e314b3f5da78e80e6 Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:38:25 +0530 Subject: [PATCH 04/20] Timeline style view for Daily Rounds (#6808) * make template timeline design * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> * Update src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> * Update src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> * add border and user icon * refactor * always display time on right * use measured at time instead of created at * enhance UI spacing * show user type and fix tooltip * enhance the loader * refactor * complete merge * remove empty card in loader --------- Co-authored-by: Rithvik Nishad <rithvikn2001@gmail.com> Co-authored-by: Aakash Singh <mail@singhaakash.dev> --- src/CAREUI/display/RecordMeta.tsx | 2 +- src/CAREUI/display/Timeline.tsx | 40 +++--- src/CAREUI/misc/PaginatedList.tsx | 43 ++++--- .../DailyRounds/DefaultLogUpdateCard.tsx | 116 ++++++------------ .../VirtualNursingAssistantLogUpdateCard.tsx | 17 +-- .../Consultations/DailyRoundsList.tsx | 63 +++++++--- 6 files changed, 140 insertions(+), 141 deletions(-) diff --git a/src/CAREUI/display/RecordMeta.tsx b/src/CAREUI/display/RecordMeta.tsx index 818553d9207..d4d32e437c8 100644 --- a/src/CAREUI/display/RecordMeta.tsx +++ b/src/CAREUI/display/RecordMeta.tsx @@ -37,7 +37,7 @@ const RecordMeta = ({ let child = ( <div className="tooltip"> <span className="underline">{relativeTime(time)}</span> - <span className="tooltip-text tooltip-bottom flex -translate-x-1/2 gap-1 text-xs font-medium tracking-wider"> + <span className="tooltip-text tooltip-left flex gap-1 text-xs font-medium tracking-wider"> {formatDateTime(time)} {user && !inlineUser && ( <span className="flex items-center gap-1"> diff --git a/src/CAREUI/display/Timeline.tsx b/src/CAREUI/display/Timeline.tsx index 7549fbfd69f..276c437056c 100644 --- a/src/CAREUI/display/Timeline.tsx +++ b/src/CAREUI/display/Timeline.tsx @@ -76,23 +76,29 @@ export const TimelineNode = (props: TimelineNodeProps) => { > {props.title || ( <TimelineNodeTitle event={props.event}> - <p className="flex-auto py-0.5 text-xs leading-5 text-gray-600"> - {props.event.by && ( - <span className="font-medium text-gray-900"> - {formatName(props.event.by)}{" "} - </span> - )} - {props.titleSuffix - ? props.titleSuffix - : `${props.event.type} the ${props.name || name}.`} - </p> - {props.actions && ( - <TimelineNodeActions>{props.actions}</TimelineNodeActions> - )} - <RecordMeta - className="flex-none py-0.5 text-xs leading-5 text-gray-500" - time={props.event.timestamp} - /> + <div className="flex w-full justify-between gap-2"> + <p className="flex-auto py-0.5 text-xs leading-5 text-gray-600 md:w-2/3"> + {props.event.by && ( + <span className="font-medium text-gray-900"> + {formatName(props.event.by)}{" "} + {props.event.by.user_type && + `(${props.event.by.user_type}) `} + </span> + )} + {props.titleSuffix + ? props.titleSuffix + : `${props.event.type} the ${props.name || name}.`} + </p> + <div className="md:w-fit"> + {props.actions && ( + <TimelineNodeActions>{props.actions}</TimelineNodeActions> + )} + <RecordMeta + className="flex-none py-0.5 text-xs leading-5 text-gray-500" + time={props.event.timestamp} + /> + </div> + </div> </TimelineNodeTitle> )} </div> diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx index 02ee0e3d90e..61c67f97ae2 100644 --- a/src/CAREUI/misc/PaginatedList.tsx +++ b/src/CAREUI/misc/PaginatedList.tsx @@ -7,6 +7,7 @@ import ButtonV2, { import CareIcon from "../icons/CareIcon"; import { classNames } from "../../Utils/utils"; import Pagination from "../../Components/Common/Pagination"; +import Timeline from "../display/Timeline"; const DEFAULT_PER_PAGE_LIMIT = 14; @@ -129,20 +130,26 @@ interface ItemsProps<TItem> { const Items = <TItem extends object>(props: ItemsProps<TItem>) => { const { loading, items } = useContextualized<TItem>(); + if (loading) { + return null; + } + return ( - <ul className={props.className}> - {loading && props.shimmer - ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( - <li key={i} className="w-full"> - {props.shimmer} - </li> - )) - : items.map((item, index, items) => ( - <li key={index} className="w-full"> - {props.children(item, items)} - </li> - ))} - </ul> + <Timeline className="rounded-lg bg-white p-2 shadow" name="log update"> + <ul className={props.className}> + {loading && props.shimmer + ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( + <li key={i} className="w-full"> + {props.shimmer} + </li> + )) + : items.map((item, index, items) => ( + <li key={index} className="w-full"> + {props.children(item, items)} + </li> + ))} + </ul> + </Timeline> ); }; @@ -153,8 +160,16 @@ interface PaginatorProps { hideIfSinglePage?: boolean; } -const Paginator = ({ className, hideIfSinglePage }: PaginatorProps) => { +const Paginator = <TItem extends object>({ + className, + hideIfSinglePage, +}: PaginatorProps) => { const { data, perPage, currentPage, setPage } = useContextualized<object>(); + const { loading } = useContextualized<TItem>(); + + if (loading) { + return null; + } if (hideIfSinglePage && (data?.count ?? 0) <= perPage) { return null; diff --git a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx index a0a4f2280cf..4cb90c900b1 100644 --- a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx @@ -1,5 +1,4 @@ import { useTranslation } from "react-i18next"; -import RecordMeta from "../../../../CAREUI/display/RecordMeta"; import CareIcon from "../../../../CAREUI/icons/CareIcon"; import ButtonV2 from "../../../Common/components/ButtonV2"; import { DailyRoundsModel } from "../../../Patient/models"; @@ -12,87 +11,48 @@ interface Props { onViewDetails: () => void; onUpdateLog?: () => void; } - -const getName = (item: any) => { - return `${item?.first_name} ${item?.last_name} (${item?.user_type})`; -}; - const DefaultLogUpdateCard = ({ round, ...props }: Props) => { const { t } = useTranslation(); - const telemedicine_doctor_update = - round.created_by_telemedicine || round.last_updated_by_telemedicine; - - const by = props.consultationData.assigned_to_object || round.created_by; return ( - <div - className={`flex w-full flex-col gap-4 rounded-lg p-4 shadow @container ${ - telemedicine_doctor_update ? "bg-purple-200" : "bg-white" - }`} - > - <div className="flex flex-col items-start gap-1"> - <div className="flex w-min items-center gap-2 rounded-full border bg-gray-50 text-gray-500"> - <div className="rounded-full bg-gray-100 px-1.5 py-0.5"> - <CareIcon className="care-l-user-nurse text-lg" /> - </div> - <span className="flex gap-1 whitespace-nowrap pr-3 text-sm tracking-wider"> - <span className="font-semibold">{`${by?.first_name} ${by?.last_name}`}</span> - <span className="hidden font-medium @xs:block"> - ({by?.user_type}) - </span> - </span> - </div> - <span className="flex gap-1 text-xs text-gray-700"> - {t("created")} <RecordMeta time={round.created_date} /> - </span> - </div> - <div className="flex flex-col gap-2"> - {!telemedicine_doctor_update && round?.last_edited_by && ( - <LogUpdateCardAttribute - attributeKey={"Updated By" as any} - attributeValue={getName(round.last_edited_by)} - /> - )} - <LogUpdateCardAttribute - attributeKey={"Round Type" as any} - attributeValue={t(round.rounds_type)} - /> - <LogUpdateCardAttribute - attributeKey="patient_category" - attributeValue={round.patient_category} - /> - <LogUpdateCardAttribute - attributeKey="physical_examination_info" - attributeValue={round.physical_examination_info} - /> - <LogUpdateCardAttribute - attributeKey="other_details" - attributeValue={round.other_details} - /> - - <div className="mt-2 flex flex-col space-x-0 space-y-2 @xs:flex-row @xs:space-x-2 @xs:space-y-0"> - <ButtonV2 - variant="secondary" - border - ghost - onClick={props.onViewDetails} - > - <CareIcon className="care-l-eye text-lg" /> - <span>{t("view_details")}</span> - </ButtonV2> - {props.onUpdateLog && ( - <ButtonV2 - variant="secondary" - border - ghost - className="tooltip" - onClick={props.onUpdateLog} - > - <CareIcon className="care-l-pen text-lg" /> - <span>{t("update_log")}</span> - </ButtonV2> - )} - </div> + <div className="flex w-full flex-col gap-4 rounded-lg border border-gray-400 p-4 @container"> + <LogUpdateCardAttribute + attributeKey={"Round Type" as any} + attributeValue={t(round.rounds_type)} + /> + <LogUpdateCardAttribute + attributeKey="patient_category" + attributeValue={round.patient_category} + /> + <LogUpdateCardAttribute + attributeKey="physical_examination_info" + attributeValue={round.physical_examination_info} + /> + <LogUpdateCardAttribute + attributeKey="other_details" + attributeValue={round.other_details} + /> + <div className="mt-2 flex items-center gap-2"> + <ButtonV2 + variant="secondary" + border + ghost + size="small" + onClick={props.onViewDetails} + > + <CareIcon className="care-l-eye text-lg" /> + <span>{t("view_details")}</span> + </ButtonV2> + <ButtonV2 + variant="secondary" + border + ghost + size="small" + onClick={props.onUpdateLog} + > + <CareIcon className="care-l-pen text-lg" /> + <span>{t("update_log")}</span> + </ButtonV2> </div> </div> ); diff --git a/src/Components/Facility/Consultations/DailyRounds/VirtualNursingAssistantLogUpdateCard.tsx b/src/Components/Facility/Consultations/DailyRounds/VirtualNursingAssistantLogUpdateCard.tsx index 00f33790675..505c2f326fa 100644 --- a/src/Components/Facility/Consultations/DailyRounds/VirtualNursingAssistantLogUpdateCard.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/VirtualNursingAssistantLogUpdateCard.tsx @@ -1,6 +1,4 @@ import { useTranslation } from "react-i18next"; -import RecordMeta from "../../../../CAREUI/display/RecordMeta"; -import CareIcon from "../../../../CAREUI/icons/CareIcon"; import { DailyRoundsModel } from "../../../Patient/models"; import LogUpdateCardAttribute from "./LogUpdateCardAttribute"; @@ -67,20 +65,7 @@ const VirtualNursingAssistantLogUpdateCard = (props: Props) => { const diffKeys = Object.keys(diff); return ( - <div className="flex w-full flex-col gap-4 rounded-lg border border-green-300 bg-white p-4 shadow shadow-primary-500/20"> - <div className="flex flex-col items-start gap-1"> - <div className="flex w-min items-center gap-2 rounded-full border bg-green-50 text-primary-400"> - <div className="rounded-full bg-green-100 px-1.5 py-0.5"> - <CareIcon className="care-l-robot text-lg" /> - </div> - <span className="whitespace-nowrap pr-3 text-sm font-semibold tracking-wider"> - {t("virtual_nursing_assistant")} - </span> - </div> - <span className="flex gap-1 text-xs text-gray-700"> - {t("created")} <RecordMeta time={props.round.created_date} /> - </span> - </div> + <div className="flex w-full flex-col gap-4 rounded-lg border border-green-300 bg-white p-4 shadow-primary-500/20"> <div className="flex flex-col gap-1"> {diffKeys.length > 0 ? ( Object.keys(diff).map((key) => ( diff --git a/src/Components/Facility/Consultations/DailyRoundsList.tsx b/src/Components/Facility/Consultations/DailyRoundsList.tsx index 3b63b60a427..828a41c8c24 100644 --- a/src/Components/Facility/Consultations/DailyRoundsList.tsx +++ b/src/Components/Facility/Consultations/DailyRoundsList.tsx @@ -10,6 +10,7 @@ import PageTitle from "../../Common/PageTitle"; import DailyRoundsFilter from "./DailyRoundsFilter"; import { ConsultationModel } from "../models"; import { useSlugs } from "../../../Common/hooks/useSlug"; +import { TimelineNode } from "../../../CAREUI/display/Timeline"; interface Props { consultation: ConsultationModel; @@ -41,20 +42,34 @@ export default function DailyRoundsList({ consultation }: Props) { </span> </PaginatedList.WhenEmpty> <PaginatedList.WhenLoading> - <> - {Array.from({ length: 3 }).map((_, i) => ( - <LoadingLogUpdateCard key={i} /> - ))} - </> + <LoadingLogUpdateCard /> </PaginatedList.WhenLoading> <PaginatedList.Items<DailyRoundsModel> className="flex grow flex-col gap-3"> {(item, items) => { if (item.rounds_type === "AUTOMATED") { return ( - <VirtualNursingAssistantLogUpdateCard - round={item} - previousRound={items[items.indexOf(item) + 1]} - /> + <TimelineNode + event={{ + type: "created", + timestamp: item.taken_at?.toString() ?? "", + by: { + user_type: "", + first_name: "Virtual", + last_name: "Assistant", + username: "", + id: "", + email: "", + last_login: "", + }, + icon: "l-robot", + }} + isLast={items.indexOf(item) == items.length - 1} + > + <VirtualNursingAssistantLogUpdateCard + round={item} + previousRound={items[items.indexOf(item) + 1]} + /> + </TimelineNode> ); } @@ -65,12 +80,30 @@ export default function DailyRoundsList({ consultation }: Props) { : `${consultationUrl}/daily_rounds/${item.id}`; return ( - <DefaultLogUpdateCard - round={item} - consultationData={consultation} - onViewDetails={() => navigate(itemUrl)} - onUpdateLog={() => navigate(`${itemUrl}/update`)} - /> + <TimelineNode + event={{ + type: "created", + timestamp: item.taken_at?.toString() ?? "", + by: { + user_type: item.created_by?.user_type ?? "", + first_name: item.created_by?.first_name ?? "", + last_name: item.created_by?.last_name ?? "", + username: "", + id: "", + email: "", + last_login: "", + }, + icon: "l-user-nurse", + }} + isLast={items.indexOf(item) == items.length - 1} + > + <DefaultLogUpdateCard + round={item} + consultationData={consultation} + onViewDetails={() => navigate(itemUrl)} + onUpdateLog={() => navigate(`${itemUrl}/update`)} + /> + </TimelineNode> ); }} </PaginatedList.Items> From 4a6569851b7df95794d4f6031d71eda7c583e696 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:40:23 +0530 Subject: [PATCH 05/20] facility pagination and redirection (#6912) --- .../e2e/facility_spec/facility_homepage.cy.ts | 35 +++++++++- cypress/e2e/facility_spec/locations.cy.ts | 69 +++++++++++-------- .../pageobject/Facility/FacilityCreation.ts | 4 ++ cypress/pageobject/Facility/FacilityHome.ts | 43 ++++++++++++ .../pageobject/Facility/FacilityLocation.ts | 6 +- cypress/pageobject/Users/ManageUserPage.ts | 1 + src/Components/Facility/FacilityCard.tsx | 2 + 7 files changed, 128 insertions(+), 32 deletions(-) diff --git a/cypress/e2e/facility_spec/facility_homepage.cy.ts b/cypress/e2e/facility_spec/facility_homepage.cy.ts index 3d916b4ba47..7269c3a2581 100644 --- a/cypress/e2e/facility_spec/facility_homepage.cy.ts +++ b/cypress/e2e/facility_spec/facility_homepage.cy.ts @@ -5,6 +5,7 @@ import FacilityHome from "../../pageobject/Facility/FacilityHome"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import { UserPage } from "../../pageobject/Users/UserSearch"; +import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; describe("Facility Homepage Function", () => { const loginPage = new LoginPage(); @@ -12,6 +13,7 @@ describe("Facility Homepage Function", () => { const facilityPage = new FacilityPage(); const manageUserPage = new ManageUserPage(); const userPage = new UserPage(); + const assetPagination = new AssetPagination(); const facilitiesAlias = "downloadFacilitiesCSV"; const capacitiesAlias = "downloadCapacitiesCSV"; const doctorsAlias = "downloadDoctorsCSV"; @@ -19,6 +21,7 @@ describe("Facility Homepage Function", () => { const facilityname = "Dummy Facility 1"; const statename = "Kerala"; const district = "Ernakulam"; + const localbody = "Aikaranad"; const facilitytype = "Private Hospital"; before(() => { @@ -31,24 +34,52 @@ describe("Facility Homepage Function", () => { cy.awaitUrl("/facility"); }); + it("Verify the Facility card button redirection", () => { + // view cns button + facilityHome.clickViewCnsButton(); + facilityHome.verifyCnsUrl(); + facilityHome.navigateBack(); + // view notify button + facilityHome.clickFacilityNotifyButton(); + facilityHome.verifyAndCloseNotifyModal(); + // view facility button + facilityHome.clickViewFacilityDetails(); + facilityPage.getFacilityName().should("be.visible"); + facilityHome.verifyFacilityDetailsUrl(); + facilityHome.navigateBack(); + // view patient button + manageUserPage.clickFacilityPatients(); + facilityHome.verifyPatientListVisibility(); + facilityHome.verifyPatientListUrl(); + facilityHome.navigateBack(); + // occupancy badge + facilityHome.verifyOccupancyBadgeVisibility(); + }); + it("Verify the functionality of advance filter", () => { userPage.clickAdvancedFilters(); facilityPage.selectState(statename); facilityPage.selectDistrict(district); - // facilityPage.selectLocalBody("Anthikad Grama"); current dummy data have issue in local body + facilityPage.selectLocalBody(localbody); facilityPage.clickUpdateFacilityType(facilitytype); userPage.applyFilter(); facilityPage.verifyStateBadgeContent(statename); facilityPage.verifyDistrictBadgeContent(district); + facilityPage.verifyLocalBodyBadgeContent(localbody); facilityPage.verifyFacilityTypeBadgeContent(facilitytype); manageUserPage.assertFacilityInCard(facilityname); userPage.clearFilters(); userPage.verifyDataTestIdNotVisible("State"); userPage.verifyDataTestIdNotVisible("District"); userPage.verifyDataTestIdNotVisible("Facility type"); + userPage.verifyDataTestIdNotVisible("Local Body"); }); - it("Search a facility in homepage", () => { + it("Search a facility in homepage and pagination", () => { + // pagination of the facility page + assetPagination.navigateToNextPage(); + assetPagination.navigateToPreviousPage(); + // search for a facility manageUserPage.typeFacilitySearch(facilityname); facilityPage.verifyFacilityBadgeContent(facilityname); manageUserPage.assertFacilityInCard(facilityname); diff --git a/cypress/e2e/facility_spec/locations.cy.ts b/cypress/e2e/facility_spec/locations.cy.ts index 26364048ca2..b9d26ee3100 100644 --- a/cypress/e2e/facility_spec/locations.cy.ts +++ b/cypress/e2e/facility_spec/locations.cy.ts @@ -3,6 +3,7 @@ import { AssetPage } from "../../pageobject/Asset/AssetCreation"; import { UserCreationPage } from "../../pageobject/Users/UserCreation"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityLocation from "../../pageobject/Facility/FacilityLocation"; +import FacilityHome from "../../pageobject/Facility/FacilityHome"; // import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; describe("Location Management Section", () => { @@ -10,6 +11,7 @@ describe("Location Management Section", () => { const userCreationPage = new UserCreationPage(); const facilityPage = new FacilityPage(); const facilityLocation = new FacilityLocation(); + const facilityHome = new FacilityHome(); // const assetPagination = new AssetPagination(); const EXPECTED_LOCATION_ERROR_MESSAGES = [ "Name is required", @@ -27,7 +29,7 @@ describe("Location Management Section", () => { const locationModifiedDescription = "Test Modified Description"; const locationModifiedType = "ICU"; const locationModifiedMiddleware = "dev-middleware.coronasafe.live"; - const bedName = "Test Bed"; + const bedName = "Test-Bed"; const bedDescrption = "test description"; const bedType = "ICU"; const bedStatus = "Vacant"; @@ -56,6 +58,42 @@ describe("Location Management Section", () => { cy.get("[id=location-management]").click(); }); + it("Add a Bed to facility location along with duplication and deleting a bed", () => { + // mandatory field verification in bed creation + facilityLocation.clickManageBedButton(); + facilityLocation.clickAddBedButton(); + assetPage.clickassetupdatebutton(); + userCreationPage.verifyErrorMessages(EXPECTED_BED_ERROR_MESSAGES); + // create a new single bed and verify + facilityLocation.enterBedName(bedName); + facilityLocation.enterBedDescription(bedDescrption); + facilityLocation.selectBedType(bedType); + assetPage.clickassetupdatebutton(); + // Verify the bed creation + facilityLocation.verifyBedNameBadge(bedName); + facilityLocation.verifyBedBadge(bedType); + facilityLocation.verifyBedBadge(bedStatus); + // Try to create duplication bed and verify the error + facilityLocation.clickAddBedButton(); + facilityLocation.enterBedName(bedName); + facilityLocation.selectBedType(bedType); + assetPage.clickassetupdatebutton(); + facilityLocation.verifyNotification( + "Name - Bed with same name already exists in location" + ); + facilityHome.verifyAndCloseNotifyModal(); + // edit the created bed + facilityLocation.clickEditBedButton(); + facilityLocation.enterBedName(bedModifiedName); + facilityLocation.enterBedDescription(bedModifiedDescrption); + facilityLocation.selectBedType(bedModifiedType); + assetPage.clickassetupdatebutton(); + // verify the modification + facilityLocation.verifyBedNameBadge(bedModifiedName); + facilityLocation.verifyBedBadge(bedModifiedType); + facilityLocation.verifyBedBadge(bedStatus); + }); + it("Adds Location to a facility and modify it", () => { // add a new location form mandatory error facilityLocation.clickAddNewLocationButton(); @@ -86,7 +124,7 @@ describe("Location Management Section", () => { facilityLocation.verifyLocationMiddleware(locationModifiedMiddleware); }); - it("Add Multiple Bed to a facility location and delete a bed", () => { + it("Multiple Bed to a facility location and delete a bed", () => { // create multiple bed and verify facilityLocation.clickManageBedButton(); facilityLocation.clickAddBedButton(); @@ -120,33 +158,6 @@ describe("Location Management Section", () => { // assetPagination.navigateToPreviousPage(); // }); need to be unblocked upon issue #6906 is solved - it("Add Single Bed to a facility location and modify it", () => { - // mandatory field verification in bed creation - facilityLocation.clickManageBedButton(); - facilityLocation.clickAddBedButton(); - assetPage.clickassetupdatebutton(); - userCreationPage.verifyErrorMessages(EXPECTED_BED_ERROR_MESSAGES); - // create a new single bed and verify - facilityLocation.enterBedName(bedName); - facilityLocation.enterBedDescription(bedDescrption); - facilityLocation.selectBedType(bedType); - assetPage.clickassetupdatebutton(); - // Verify the bed creation - facilityLocation.verifyBedNameBadge(bedName); - facilityLocation.verifyBedBadge(bedType); - facilityLocation.verifyBedBadge(bedStatus); - // edit the created bed - facilityLocation.clickEditBedButton(); - facilityLocation.enterBedName(bedModifiedName); - facilityLocation.enterBedDescription(bedModifiedDescrption); - facilityLocation.selectBedType(bedModifiedType); - assetPage.clickassetupdatebutton(); - // verify the modification - facilityLocation.verifyBedNameBadge(bedModifiedName); - facilityLocation.verifyBedBadge(bedModifiedType); - facilityLocation.verifyBedBadge(bedStatus); - }); - afterEach(() => { cy.saveLocalStorage(); }); diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index 608dd9c357b..e3639388620 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -337,6 +337,10 @@ class FacilityPage { cy.get("[data-testid='District']").should("contain", expectedText); } + verifyLocalBodyBadgeContent(expectedText: string) { + cy.get("[data-testid='Local Body']").should("contain", expectedText); + } + verifyFacilityTypeBadgeContent(expectedText: string) { cy.get("[data-testid='Facility type']").should("contain", expectedText); } diff --git a/cypress/pageobject/Facility/FacilityHome.ts b/cypress/pageobject/Facility/FacilityHome.ts index 04dfe94d002..03f2be9b19b 100644 --- a/cypress/pageobject/Facility/FacilityHome.ts +++ b/cypress/pageobject/Facility/FacilityHome.ts @@ -23,6 +23,49 @@ class FacilityHome { cy.intercept("GET", `**/api/v1/facility/?csv${queryParam}`).as(alias); } + clickViewCnsButton() { + cy.get("#view-cns-button").first().click(); + } + + verifyCnsUrl() { + cy.url().should("include", "/cns"); + } + + clickFacilityNotifyButton() { + cy.get("#facility-notify").first().click(); + } + + verifyFacilityDetailsUrl() { + cy.url().should("match", /\/facility\/[\w-]+/); + } + + verifyPatientListVisibility() { + cy.get("#patient-name-list").scrollIntoView(); + cy.get("#patient-name-list").should("be.visible"); + } + + verifyPatientListUrl() { + cy.url().should("match", /\/patients\?facility=.+/); + } + + verifyOccupancyBadgeVisibility() { + cy.get("#occupany-badge").should("be.visible"); + } + + verifyAndCloseNotifyModal() { + cy.get("#cancel").should("be.visible"); + cy.get("#cancel").click(); + } + + navigateBack() { + cy.go(-1); + } + + clickViewFacilityDetails() { + cy.get("#facility-details").should("be.visible"); + cy.get("#facility-details").first().click(); + } + verifyDownload(alias: string) { cy.wait(`@${alias}`).its("response.statusCode").should("eq", 200); } diff --git a/cypress/pageobject/Facility/FacilityLocation.ts b/cypress/pageobject/Facility/FacilityLocation.ts index d3eebdaf4ff..8924119510f 100644 --- a/cypress/pageobject/Facility/FacilityLocation.ts +++ b/cypress/pageobject/Facility/FacilityLocation.ts @@ -32,6 +32,10 @@ class FacilityLocation { cy.get("#location-type").contains(type); } + verifyNotification(message: string) { + cy.get(".pnotify-container").should("contain", message).and("be.visible"); + } + verifyLocationDescription(description: string) { cy.get("#view-location-description").contains(description); } @@ -49,7 +53,7 @@ class FacilityLocation { } enterBedName(name: string) { - cy.get("#bed-name").clear().click().type(name); + cy.get("#bed-name").click().clear().click().type(name); } enterBedDescription(description: string) { diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index d3f7e17e7d5..2d1ebbc14f0 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -103,6 +103,7 @@ export class ManageUserPage { } clickFacilityPatients() { + cy.get("#facility-patients").should("be.visible"); cy.get("#facility-patients").click(); } diff --git a/src/Components/Facility/FacilityCard.tsx b/src/Components/Facility/FacilityCard.tsx index c34fed49a6e..1424a657de4 100644 --- a/src/Components/Facility/FacilityCard.tsx +++ b/src/Components/Facility/FacilityCard.tsx @@ -98,6 +98,7 @@ export const FacilityCard = (props: { facility: any; userType: any }) => { {facility.name} </Link> <ButtonV2 + id="view-cns-button" href={`/facility/${facility.id}/cns`} border ghost @@ -160,6 +161,7 @@ export const FacilityCard = (props: { facility: any; userType: any }) => { <div className="flex w-full flex-wrap justify-between gap-2 py-2"> <div className="flex flex-wrap gap-2"> <div + id="occupany-badge" className={`tooltip button-size-default ml-auto flex w-fit items-center justify-center rounded-md px-2 ${ facility.patient_count / facility.bed_count > 0.85 ? "button-danger-border bg-red-500" From 283504d89f1171207e5529cc6e552fc706ba4b5a Mon Sep 17 00:00:00 2001 From: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:40:44 +0530 Subject: [PATCH 06/20] Replace all window.location.reload() with data refetch. (#6892) * replaced window.location.reload with appropriate fetchdata function in patient discharge * replaced window.location.reload with appropriate refetch while updating facility cover image * replaced window.location.reload with appropriate refetch in assetImportModal * replaced wimdow.location.reload with appropriate refecth in update app * Reverted changes made related to UpdatableApp * removed unnecessary data fetch calls --- src/Components/Assets/AssetImportModal.tsx | 5 +-- src/Components/Assets/AssetsList.tsx | 3 +- .../Facility/CoverImageEditModal.tsx | 2 -- src/Components/Facility/DischargeModal.tsx | 7 ++--- src/Components/Facility/FacilityHome.tsx | 31 ++++++++++--------- src/Components/Patient/PatientInfoCard.tsx | 8 ++++- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/src/Components/Assets/AssetImportModal.tsx b/src/Components/Assets/AssetImportModal.tsx index 02fe1d90265..adab83c744c 100644 --- a/src/Components/Assets/AssetImportModal.tsx +++ b/src/Components/Assets/AssetImportModal.tsx @@ -22,9 +22,10 @@ interface Props { open: boolean; onClose: (() => void) | undefined; facility: FacilityModel; + onUpdate?: (() => void) | undefined; } -const AssetImportModal = ({ open, onClose, facility }: Props) => { +const AssetImportModal = ({ open, onClose, facility, onUpdate }: Props) => { const [isImporting, setIsImporting] = useState(false); const [selectedFile, setSelectedFile] = useState<any>(); const [preview, setPreview] = @@ -170,7 +171,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { Notification.Success({ msg: "Assets imported successfully" }); await sleep(1000); setIsImporting(false); - window.location.reload(); + onUpdate?.(); } else { Notification.Error({ msg: "Error importing some assets" }); await sleep(1000); diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index 447bff1c1d0..3fc9c9f9f14 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -69,7 +69,7 @@ const AssetsList = () => { qParams.warranty_amc_end_of_validity_after || "", }; - const { loading } = useQuery(routes.listAssets, { + const { refetch: assetsFetch, loading } = useQuery(routes.listAssets, { query: params, onResponse: ({ res, data }) => { if (res?.status === 200 && data) { @@ -436,6 +436,7 @@ const AssetsList = () => { return f; }); }} + onUpdate={assetsFetch} facility={facility} /> )} diff --git a/src/Components/Facility/CoverImageEditModal.tsx b/src/Components/Facility/CoverImageEditModal.tsx index a1394c6441f..4d723214604 100644 --- a/src/Components/Facility/CoverImageEditModal.tsx +++ b/src/Components/Facility/CoverImageEditModal.tsx @@ -123,7 +123,6 @@ const CoverImageEditModal = ({ ); if (response.status === 200) { Success({ msg: "Cover image updated." }); - window.location.reload(); } else { Notification.Error({ msg: "Something went wrong!", @@ -148,7 +147,6 @@ const CoverImageEditModal = ({ const res = await dispatch(deleteFacilityCoverImage(facility.id as any)); if (res.statusCode === 204) { Success({ msg: "Cover image deleted" }); - window.location.reload(); } onDelete && onDelete(); diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx index 46bdf866e90..a15af4d3883 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -50,10 +50,7 @@ const DischargeModal = ({ show, onClose, consultationData, - afterSubmit = () => { - onClose(); - window.location.reload(); - }, + afterSubmit, discharge_reason = "", discharge_notes = "", discharge_date = dayjs().format("YYYY-MM-DDTHH:mm"), @@ -163,7 +160,7 @@ const DischargeModal = ({ msg: "Patient Discharged Successfully", }); - afterSubmit(); + afterSubmit?.(); } }; diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index b36a3a7b36d..2341d231675 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -54,19 +54,20 @@ export const FacilityHome = (props: any) => { useMessageListener((data) => console.log(data)); - const { data: facilityData, loading: isLoading } = useQuery( - routes.getPermittedFacility, - { - pathParams: { - id: facilityId, - }, - onResponse: ({ res }) => { - if (!res?.ok) { - navigate("/not-found"); - } - }, - } - ); + const { + data: facilityData, + loading: isLoading, + refetch: facilityFetch, + } = useQuery(routes.getPermittedFacility, { + pathParams: { + id: facilityId, + }, + onResponse: ({ res }) => { + if (!res?.ok) { + navigate("/not-found"); + } + }, + }); const handleDeleteClose = () => { setOpenDeleteDialog(false); @@ -139,10 +140,10 @@ export const FacilityHome = (props: any) => { onSave={() => facilityData?.read_cover_image_url ? setImageKey(Date.now()) - : window.location.reload() + : facilityFetch() } onClose={() => setEditCoverImage(false)} - onDelete={() => window.location.reload()} + onDelete={() => facilityFetch()} facility={facilityData ?? ({} as FacilityModel)} /> {hasCoverImage ? ( diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index b9d1d256566..c5087f13dda 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -141,7 +141,13 @@ export default function PatientInfoCard(props: { /> <DischargeModal show={openDischargeDialog} - onClose={() => setOpenDischargeDialog(false)} + onClose={() => { + setOpenDischargeDialog(false); + }} + afterSubmit={() => { + setOpenDischargeDialog(false); + props.fetchPatientData?.({ aborted: false }); + }} consultationData={consultation} /> </> From 3c008b28d616c50d930a9c00184cb6afc682f497 Mon Sep 17 00:00:00 2001 From: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:54:09 +0530 Subject: [PATCH 07/20] Replaced useDipatch with useQuery/request in FacilityCreate and HospitalList files (#6591) * Replaced useDipatch with useQuery/request in FacilityCreate and HospitalList files * fix failing api calls * replace function with request calls with useQuery * remove useState variables and used variables from useQuery * fix facility pincode issue * fix longitude and latitude for facility and optimized code * Fixed ward list bug * fix auto fill for pincode bug * remove duplicate request * fix total facility card loading state * fix clear filter badges bug --- src/Components/Facility/FacilityCreate.tsx | 313 +++++++++------------ src/Components/Facility/HospitalList.tsx | 153 +++------- src/Components/Facility/models.tsx | 23 +- src/Redux/api.tsx | 14 +- src/Utils/request/useQuery.ts | 4 +- 5 files changed, 210 insertions(+), 297 deletions(-) diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index e9547d5dddd..c9f40b5d1ef 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -5,9 +5,7 @@ import { CapacityModal, DistrictModel, DoctorModal, - LocalBodyModel, - StateModel, - WardModel, + FacilityRequest, } from "./models"; import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave.js"; import { @@ -20,19 +18,8 @@ import { SelectFormField, } from "../Form/FormFields/SelectFormField"; import { Popover, Transition } from "@headlessui/react"; -import { Fragment, lazy, useCallback, useState } from "react"; +import { Fragment, lazy, useState } from "react"; import Steps, { Step } from "../Common/Steps"; -import { - createFacility, - getDistrictByState, - getLocalbodyByDistrict, - getPermittedFacility, - getStates, - getWardByLocalBody, - listCapacity, - listDoctor, - updateFacility, -} from "../../Redux/actions"; import { getPincodeDetails, includesIgnoreCase, @@ -45,7 +32,6 @@ import { validateLongitude, validatePincode, } from "../../Common/validation"; -import { statusType, useAbortableEffect } from "../../Common/utils"; import { BedCapacity } from "./BedCapacity"; import BedTypeCard from "./BedTypeCard"; @@ -65,9 +51,12 @@ import TextFormField from "../Form/FormFields/TextFormField"; import { navigate } from "raviger"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; -import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; import { PhoneNumberValidator } from "../Form/FieldValidators.js"; +import request from "../../Utils/request/request.js"; +import routes from "../../Redux/api.js"; +import useQuery from "../../Utils/request/useQuery.js"; +import { RequestResult } from "../../Utils/request/types.js"; const Loading = lazy(() => import("../Common/Loading")); @@ -149,7 +138,6 @@ const facilityCreateReducer = (state = initialState, action: FormAction) => { export const FacilityCreate = (props: FacilityProps) => { const { t } = useTranslation(); const { gov_data_api_key, kasp_string, kasp_enabled } = useConfig(); - const dispatchAction: any = useDispatch(); const { facilityId } = props; const [state, dispatch] = useAutoSaveReducer<FacilityForm>( @@ -157,14 +145,6 @@ export const FacilityCreate = (props: FacilityProps) => { initialState ); const [isLoading, setIsLoading] = useState(false); - const [isStateLoading, setIsStateLoading] = useState(false); - const [isDistrictLoading, setIsDistrictLoading] = useState(false); - const [isLocalbodyLoading, setIsLocalbodyLoading] = useState(false); - const [isWardLoading, setIsWardLoading] = useState(false); - const [states, setStates] = useState<StateModel[]>([]); - const [districts, setDistricts] = useState<DistrictModel[]>([]); - const [localBodies, setLocalBodies] = useState<LocalBodyModel[]>([]); - const [ward, setWard] = useState<WardModel[]>([]); const [currentStep, setCurrentStep] = useState(1); const [createdFacilityId, setCreatedFacilityId] = useState(""); const [showAutoFilledPincode, setShowAutoFilledPincode] = useState(false); @@ -172,39 +152,32 @@ export const FacilityCreate = (props: FacilityProps) => { const [doctorData, setDoctorData] = useState<Array<DoctorModal>>([]); const [bedCapacityKey, setBedCapacityKey] = useState(0); const [docCapacityKey, setDocCapacityKey] = useState(0); + const [stateId, setStateId] = useState<number>(); + const [districtId, setDistrictId] = useState<number>(); + const [localBodyId, setLocalBodyId] = useState<number>(); const { goBack } = useAppHistory(); const headerText = !facilityId ? "Create Facility" : "Update Facility"; const buttonText = !facilityId ? "Save Facility" : "Update Facility"; - const fetchDistricts = useCallback( - async (id: number) => { - if (id > 0) { - setIsDistrictLoading(true); - const districtList = await dispatchAction(getDistrictByState({ id })); - if (districtList) { - setDistricts([...districtList.data]); - } - setIsDistrictLoading(false); - return districtList ? [...districtList.data] : []; - } - }, - [dispatchAction] - ); - - const fetchLocalBody = useCallback( - async (id: number) => { - if (id > 0) { - setIsLocalbodyLoading(true); - const localBodyList = await dispatchAction( - getLocalbodyByDistrict({ id }) - ); - setIsLocalbodyLoading(false); - if (localBodyList) { - setLocalBodies([...localBodyList.data]); - } - } + const { + data: districtData, + refetch: districtFetch, + loading: isDistrictLoading, + } = useQuery(routes.getDistrictByState, { + pathParams: { + id: String(stateId), }, - [dispatchAction] + prefetch: !!stateId, + }); + + const { data: localbodyData, loading: isLocalbodyLoading } = useQuery( + routes.getLocalbodyByDistrict, + { + pathParams: { + id: String(districtId), + }, + prefetch: !!districtId, + } ); const getSteps = (): Step[] => { @@ -244,89 +217,66 @@ export const FacilityCreate = (props: FacilityProps) => { ]; }; - const fetchWards = useCallback( - async (id: number) => { - if (id > 0) { - setIsWardLoading(true); - const wardList = await dispatchAction(getWardByLocalBody({ id })); - setIsWardLoading(false); - if (wardList) { - setWard([...wardList.data.results]); - } - } - }, - [dispatchAction] + const { data: wardData, loading: isWardLoading } = useQuery( + routes.getWardByLocalBody, + { + pathParams: { + id: String(localBodyId), + }, + prefetch: !!localBodyId, + } ); - const fetchData = useCallback( - async (status: statusType) => { + useQuery(routes.getPermittedFacility, { + pathParams: { + id: facilityId!, + }, + prefetch: !!facilityId, + onResponse: ({ res, data }) => { if (facilityId) { setIsLoading(true); - const res = await dispatchAction(getPermittedFacility(facilityId)); - if (!status.aborted && res.data) { + if (res?.ok && data) { const formData = { - facility_type: res.data.facility_type, - name: res.data.name, - state: res.data.state ? res.data.state : 0, - district: res.data.district ? res.data.district : 0, - local_body: res.data.local_body ? res.data.local_body : 0, - features: res.data.features || [], - ward: res.data.ward_object ? res.data.ward_object.id : 0, - kasp_empanelled: res.data.kasp_empanelled - ? String(res.data.kasp_empanelled) - : "false", - address: res.data.address, - pincode: res.data.pincode, - phone_number: - res.data.phone_number.length == 10 - ? "+91" + res.data.phone_number - : res.data.phone_number, - latitude: res.data.latitude || "", - longitude: res.data.longitude || "", - type_b_cylinders: res.data.type_b_cylinders, - type_c_cylinders: res.data.type_c_cylinders, - type_d_cylinders: res.data.type_d_cylinders, - expected_type_b_cylinders: res.data.expected_type_b_cylinders, - expected_type_c_cylinders: res.data.expected_type_c_cylinders, - expected_type_d_cylinders: res.data.expected_type_d_cylinders, - expected_oxygen_requirement: res.data.expected_oxygen_requirement, - oxygen_capacity: res.data.oxygen_capacity, + facility_type: data.facility_type ? data.facility_type : "", + name: data.name ? data.name : "", + state: data.state ? data.state : 0, + district: data.district ? data.district : 0, + local_body: data.local_body ? data.local_body : 0, + features: data.features || [], + ward: data.ward_object ? data.ward_object.id : 0, + kasp_empanelled: "", + address: data.address ? data.address : "", + pincode: data.pincode ? data.pincode : "", + phone_number: data.phone_number + ? data.phone_number.length == 10 + ? "+91" + data.phone_number + : data.phone_number + : "", + latitude: data ? String(data.latitude) : "", + longitude: data ? String(data.longitude) : "", + type_b_cylinders: data.type_b_cylinders, + type_c_cylinders: data.type_c_cylinders, + type_d_cylinders: data.type_d_cylinders, + expected_type_b_cylinders: data.expected_type_b_cylinders, + expected_type_c_cylinders: data.expected_type_c_cylinders, + expected_type_d_cylinders: data.expected_type_d_cylinders, + expected_oxygen_requirement: data.expected_oxygen_requirement, + oxygen_capacity: data.oxygen_capacity, }; dispatch({ type: "set_form", form: formData }); - Promise.all([ - fetchDistricts(res.data.state), - fetchLocalBody(res.data.district), - fetchWards(res.data.local_body), - ]); + setStateId(data.state); + setDistrictId(data.district); + setLocalBodyId(data.local_body); } else { navigate(`/facility/${facilityId}`); } setIsLoading(false); } }, - [dispatchAction, facilityId, fetchDistricts, fetchLocalBody, fetchWards] - ); - - const fetchStates = useCallback( - async (status: statusType) => { - setIsStateLoading(true); - const statesRes = await dispatchAction(getStates()); - if (!status.aborted && statesRes.data.results) { - setStates([...statesRes.data.results]); - } - setIsStateLoading(false); - }, - [dispatchAction] - ); + }); - useAbortableEffect( - (status: statusType) => { - if (facilityId) { - fetchData(status); - } - fetchStates(status); - }, - [dispatch, fetchData] + const { data: stateData, loading: isStateLoading } = useQuery( + routes.statesList ); const handleChange = (e: FieldChangeEvent<unknown>) => { @@ -357,12 +307,15 @@ export const FacilityCreate = (props: FacilityProps) => { const pincodeDetails = await getPincodeDetails(e.value, gov_data_api_key); if (!pincodeDetails) return; - const matchedState = states.find((state) => { + const matchedState = (stateData ? stateData.results : []).find((state) => { return includesIgnoreCase(state.name, pincodeDetails.statename); }); if (!matchedState) return; - const fetchedDistricts = await fetchDistricts(matchedState.id); + const newDistrictDataResult: RequestResult<DistrictModel[]> = + await districtFetch({ pathParams: { id: String(matchedState.id) } }); + const fetchedDistricts: DistrictModel[] = newDistrictDataResult.data || []; + if (!fetchedDistricts) return; const matchedDistrict = fetchedDistricts.find((district) => { @@ -380,7 +333,7 @@ export const FacilityCreate = (props: FacilityProps) => { }, }); - fetchLocalBody(matchedDistrict.id); + setDistrictId(matchedDistrict.id); setShowAutoFilledPincode(true); setTimeout(() => { setShowAutoFilledPincode(false); @@ -478,19 +431,18 @@ export const FacilityCreate = (props: FacilityProps) => { console.log(state.form); if (validated) { setIsLoading(true); - const data = { + const data: FacilityRequest = { facility_type: state.form.facility_type, name: state.form.name, district: state.form.district, state: state.form.state, address: state.form.address, - pincode: state.form.pincode, local_body: state.form.local_body, features: state.form.features, ward: state.form.ward, - kasp_empanelled: JSON.parse(state.form.kasp_empanelled), - latitude: state.form.latitude || null, - longitude: state.form.longitude || null, + pincode: state.form.pincode, + latitude: state.form.latitude, + longitude: state.form.longitude, phone_number: parsePhoneNumber(state.form.phone_number), oxygen_capacity: state.form.oxygen_capacity ? state.form.oxygen_capacity @@ -519,18 +471,26 @@ export const FacilityCreate = (props: FacilityProps) => { ? state.form.expected_type_d_cylinders : 0, }; - const res = await dispatchAction( - facilityId ? updateFacility(facilityId, data) : createFacility(data) - ); - if (res && (res.status === 200 || res.status === 201) && res.data) { - const id = res.data.id; + const { res, data: requestData } = facilityId + ? await request(routes.updateFacility, { + body: data, + pathParams: { + id: facilityId, + }, + }) + : await request(routes.createFacility, { + body: data, + }); + + if (res?.ok && requestData) { + const id = requestData.id; dispatch({ type: "set_form", form: initForm }); if (!facilityId) { Notification.Success({ msg: "Facility added successfully", }); - setCreatedFacilityId(id); + setCreatedFacilityId(String(id)); setCurrentStep(2); } else { Notification.Success({ @@ -538,11 +498,6 @@ export const FacilityCreate = (props: FacilityProps) => { }); navigate(`/facility/${facilityId}`); } - } else { - if (res?.data) - Notification.Error({ - msg: "Something went wrong: " + (res.data.detail || ""), - }); } setIsLoading(false); } @@ -605,11 +560,11 @@ export const FacilityCreate = (props: FacilityProps) => { lastUpdated={res.modified_date} removeBedType={removeCurrentBedType} handleUpdate={async () => { - const capacityRes = await dispatchAction( - listCapacity({}, { facilityId: createdFacilityId }) - ); - if (capacityRes && capacityRes.data) { - setCapacityData(capacityRes.data.results); + const { res, data } = await request(routes.getCapacity, { + pathParams: { facilityId: createdFacilityId }, + }); + if (res?.ok && data) { + setCapacityData(data.results); } }} /> @@ -642,11 +597,11 @@ export const FacilityCreate = (props: FacilityProps) => { facilityId={createdFacilityId || ""} key={`bed_${data.id}`} handleUpdate={async () => { - const doctorRes = await dispatchAction( - listDoctor({}, { facilityId: createdFacilityId }) - ); - if (doctorRes && doctorRes.data) { - setDoctorData(doctorRes.data.results); + const { res, data } = await request(routes.listDoctor, { + pathParams: { facilityId: createdFacilityId }, + }); + if (res?.ok && data) { + setDoctorData(data.results); } }} {...data} @@ -688,11 +643,11 @@ export const FacilityCreate = (props: FacilityProps) => { navigate(`/facility/${createdFacilityId}`); }} handleUpdate={async () => { - const doctorRes = await dispatchAction( - listDoctor({}, { facilityId: createdFacilityId }) - ); - if (doctorRes && doctorRes.data) { - setDoctorData(doctorRes.data.results); + const { res, data } = await request(routes.listDoctor, { + pathParams: { facilityId: createdFacilityId }, + }); + if (res?.ok && data) { + setDoctorData(data.results); } }} /> @@ -725,11 +680,11 @@ export const FacilityCreate = (props: FacilityProps) => { setCurrentStep(3); }} handleUpdate={async () => { - const capacityRes = await dispatchAction( - listCapacity({}, { facilityId: createdFacilityId }) - ); - if (capacityRes && capacityRes.data) { - setCapacityData(capacityRes.data.results); + const { res, data } = await request(routes.getCapacity, { + pathParams: { facilityId: createdFacilityId }, + }); + if (res?.ok && data) { + setCapacityData(data.results); } }} /> @@ -760,11 +715,9 @@ export const FacilityCreate = (props: FacilityProps) => { <DraftSection handleDraftSelect={(newState: any) => { dispatch({ type: "set_state", state: newState }); - Promise.all([ - fetchDistricts(newState.form.state), - fetchLocalBody(newState.form.district), - fetchWards(newState.form.local_body), - ]); + setStateId(newState.form.state); + setDistrictId(newState.form.district); + setLocalBodyId(newState.form.local_body); }} formData={state.form} /> @@ -809,13 +762,13 @@ export const FacilityCreate = (props: FacilityProps) => { placeholder="Choose State" className={isStateLoading ? "animate-pulse" : ""} disabled={isStateLoading} - options={states} + options={stateData ? stateData.results : []} optionLabel={(o) => o.name} optionValue={(o) => o.id} onChange={(event) => { handleChange(event); if (!event) return; - fetchDistricts(event.value); + setStateId(event.value); }} /> <SelectFormField @@ -824,13 +777,13 @@ export const FacilityCreate = (props: FacilityProps) => { required className={isDistrictLoading ? "animate-pulse" : ""} disabled={isDistrictLoading} - options={districts} + options={districtData ? districtData : []} optionLabel={(o) => o.name} optionValue={(o) => o.id} onChange={(event) => { handleChange(event); if (!event) return; - fetchLocalBody(event.value); + setDistrictId(event.value); }} /> <SelectFormField @@ -839,13 +792,13 @@ export const FacilityCreate = (props: FacilityProps) => { className={isLocalbodyLoading ? "animate-pulse" : ""} disabled={isLocalbodyLoading} placeholder="Choose Local Body" - options={localBodies} + options={localbodyData ? localbodyData : []} optionLabel={(o) => o.name} optionValue={(o) => o.id} onChange={(event) => { handleChange(event); if (!event) return; - fetchWards(event.value); + setLocalBodyId(event.value); }} /> <SelectFormField @@ -854,12 +807,14 @@ export const FacilityCreate = (props: FacilityProps) => { className={isWardLoading ? "animate-pulse" : ""} disabled={isWardLoading} placeholder="Choose Ward" - options={ward.sort(compareBy("number")).map((e) => { - return { - id: e.id, - name: e.number + ": " + e.name, - }; - })} + options={(wardData ? wardData.results : []) + .sort(compareBy("number")) + .map((e) => { + return { + id: e.id, + name: e.number + ": " + e.name, + }; + })} optionLabel={(o) => o.name} optionValue={(o) => o.id} /> diff --git a/src/Components/Facility/HospitalList.tsx b/src/Components/Facility/HospitalList.tsx index ffdc1f4ed12..075ad8de39e 100644 --- a/src/Components/Facility/HospitalList.tsx +++ b/src/Components/Facility/HospitalList.tsx @@ -3,14 +3,8 @@ import { downloadFacilityCapacity, downloadFacilityDoctors, downloadFacilityTriage, - getDistrict, - getLocalBody, - getPermittedFacilities, - getState, } from "../../Redux/actions"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { lazy, useCallback, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy } from "react"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import CountBlock from "../../CAREUI/display/Count"; import ExportMenu from "../Common/Export"; @@ -25,6 +19,8 @@ import { navigate } from "raviger"; import useFilters from "../../Common/hooks/useFilters"; import { useTranslation } from "react-i18next"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; const Loading = lazy(() => import("../Common/Loading")); @@ -39,21 +35,14 @@ export const HospitalList = () => { } = useFilters({ limit: 14, }); - const dispatchAction: any = useDispatch(); - const [data, setData] = useState<Array<FacilityModel>>([]); let manageFacilities: any = null; - const [isLoading, setIsLoading] = useState(false); - const [totalCount, setTotalCount] = useState(0); - const [stateName, setStateName] = useState(""); - const [districtName, setDistrictName] = useState(""); - const [localbodyName, setLocalbodyName] = useState(""); const { user_type } = useAuthUser(); const { t } = useTranslation(); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const params = { + const { data: permittedData, loading: isLoading } = useQuery( + routes.getPermittedFacilities, + { + query: { limit: resultsPerPage, page: qParams.page || 1, offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, @@ -63,92 +52,30 @@ export const HospitalList = () => { local_body: qParams.local_body, facility_type: qParams.facility_type, kasp_empanelled: qParams.kasp_empanelled, - }; - - const res = await dispatchAction(getPermittedFacilities(params)); - if (!status.aborted) { - if (res && res.data) { - setData(res.data.results); - setTotalCount(res.data.count); - } - setIsLoading(false); - } - }, - [ - qParams.page, - qParams.search, - qParams.state, - qParams.district, - qParams.local_body, - qParams.facility_type, - qParams.kasp_empanelled, - dispatchAction, - ] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] - ); - - const fetchStateName = useCallback( - async (status: statusType) => { - const res = - Number(qParams.state) && - (await dispatchAction(getState(qParams.state))); - if (!status.aborted) { - setStateName(res?.data?.name); - } - }, - [dispatchAction, qParams.state] - ); - - useAbortableEffect( - (status: statusType) => { - fetchStateName(status); - }, - [fetchStateName] - ); - - const fetchDistrictName = useCallback( - async (status: statusType) => { - const res = - Number(qParams.district) && - (await dispatchAction(getDistrict(qParams.district))); - if (!status.aborted) { - setDistrictName(res?.data?.name); - } - }, - [dispatchAction, qParams.district] + }, + } ); - useAbortableEffect( - (status: statusType) => { - fetchDistrictName(status); + const { data: stateData } = useQuery(routes.getState, { + pathParams: { + id: qParams.state, }, - [fetchDistrictName] - ); + prefetch: qParams.state !== undefined, + }); - const fetchLocalbodyName = useCallback( - async (status: statusType) => { - const res = - Number(qParams.local_body) && - (await dispatchAction(getLocalBody({ id: qParams.local_body }))); - if (!status.aborted) { - setLocalbodyName(res?.data?.name); - } + const { data: districtData } = useQuery(routes.getDistrict, { + pathParams: { + id: qParams.district, }, - [dispatchAction, qParams.local_body] - ); + prefetch: qParams.district !== undefined, + }); - useAbortableEffect( - (status: statusType) => { - fetchLocalbodyName(status); + const { data: localBodyData } = useQuery(routes.getLocalBody, { + pathParams: { + id: qParams.local_body, }, - [fetchLocalbodyName] - ); + prefetch: qParams.local_body !== undefined, + }); const findFacilityTypeById = (id: number) => { const facility_type = FACILITY_TYPES.find((type) => type.id == id); @@ -167,8 +94,8 @@ export const HospitalList = () => { }; let facilityList: JSX.Element[] = []; - if (data && data.length) { - facilityList = data.map((facility: FacilityModel) => ( + if (permittedData && permittedData.results.length) { + facilityList = permittedData.results.map((facility: FacilityModel) => ( <FacilityCard key={facility.id!} facility={facility} @@ -177,18 +104,18 @@ export const HospitalList = () => { )); } - if (isLoading || !data) { + if (isLoading || !permittedData) { manageFacilities = <Loading />; - } else if (data && data.length) { + } else if (permittedData.results && permittedData.results.length) { manageFacilities = ( <> <div className="grid gap-4 md:grid-cols-1 lg:grid-cols-2"> {facilityList} </div> - <Pagination totalCount={totalCount} /> + <Pagination totalCount={permittedData.count} /> </> ); - } else if (data && data.length === 0) { + } else if (permittedData.results && permittedData.results.length === 0) { manageFacilities = hasFiltersApplied(qParams) ? ( <div className="w-full rounded-lg bg-white p-3"> <div className="mt-4 flex w-full justify-center text-2xl font-bold text-gray-600"> @@ -246,7 +173,7 @@ export const HospitalList = () => { <div className="mt-4 gap-2 lg:flex"> <CountBlock text="Total Facilities" - count={totalCount} + count={permittedData ? permittedData.count : 0} loading={isLoading} icon="l-hospital" className="flex-1" @@ -266,9 +193,21 @@ export const HospitalList = () => { <FilterBadges badges={({ badge, value, kasp }) => [ badge("Facility/District Name", "search"), - value("State", "state", stateName), - value("District", "district", districtName), - value("Local Body", "local_body", localbodyName), + value( + "State", + "state", + qParams.state && stateData ? stateData.name : "" + ), + value( + "District", + "district", + qParams.district && districtData ? districtData.name : "" + ), + value( + "Local Body", + "local_body", + qParams.local_body && localBodyData ? localBodyData.name : "" + ), value( "Facility type", "facility_type", diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index c4a5196511d..90191e3321f 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -65,6 +65,7 @@ export interface FacilityModel { district?: number; local_body?: number; ward?: number; + pincode?: string; } export interface CapacityModal { @@ -241,6 +242,13 @@ export interface CurrentBed { meta: Record<string, any>; } +// Voluntarily made as `type` for it to achieve type-safety when used with +// `useAsyncOptions<ICD11DiagnosisModel>` +export type ICD11DiagnosisModel = { + id: string; + label: string; +}; + export type ABGPlotsFields = | "ph" | "pco2" @@ -447,13 +455,6 @@ export interface CreateBedBody { bed: string; } -// Voluntarily made as `type` for it to achieve type-safety when used with -// `useAsyncOptions<ICD11DiagnosisModel>` -export type ICD11DiagnosisModel = { - id: string; - label: string; -}; - // Patient Notes Model export interface BaseFacilityModel { id: string; @@ -513,3 +514,11 @@ export type IFacilityNotificationResponse = { export type IUserFacilityRequest = { facility: string; }; + +export type FacilityRequest = Omit<FacilityModel, "location"> & { + latitude?: string; + longitude?: string; + kasp_empanelled?: boolean; + patient_count?: string; + bed_count?: string; +}; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 9634036e6c4..80d5a630bea 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -41,10 +41,12 @@ import { IFacilityNotificationRequest, IFacilityNotificationResponse, IUserFacilityRequest, + LocalBodyModel, PatientStatsModel, + FacilityRequest, + StateModel, WardModel, LocationModel, - StateModel, PatientNotesModel, } from "../Components/Facility/models"; import { @@ -286,12 +288,14 @@ const routes = { createFacility: { path: "/api/v1/facility/", method: "POST", + TRes: Type<FacilityModel>(), + TBody: Type<FacilityRequest>(), }, getPermittedFacility: { path: "/api/v1/facility/{id}/", method: "GET", - TRes: Type<FacilityModel>(), + TRes: Type<FacilityRequest>(), }, getAnyFacility: { @@ -301,8 +305,10 @@ const routes = { }, updateFacility: { - path: "/api/v1/facility", + path: "/api/v1/facility/{id}/", method: "PUT", + TRes: Type<FacilityModel>(), + TBody: Type<FacilityRequest>(), }, partialUpdateFacility: { @@ -680,6 +686,7 @@ const routes = { getState: { path: "/api/v1/state/{id}/", + TRes: Type<StateModel>(), }, // Districts @@ -711,6 +718,7 @@ const routes = { // Local Body getLocalBody: { path: "/api/v1/local_body/{id}/", + TRes: Type<LocalBodyModel>(), }, getAllLocalBody: { path: "/api/v1/local_body/", diff --git a/src/Utils/request/useQuery.ts b/src/Utils/request/useQuery.ts index 2dab2910278..97d1b565f2f 100644 --- a/src/Utils/request/useQuery.ts +++ b/src/Utils/request/useQuery.ts @@ -31,8 +31,10 @@ export default function useQuery<TData>( : options; setLoading(true); - setResponse(await request(route, resolvedOptions)); + const response = await request(route, resolvedOptions); + setResponse(response); setLoading(false); + return response; }, [route, JSON.stringify(options)] ); From cfe001120d5d9c56c09c99f1f6c6430ebc42da67 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:55:04 +0530 Subject: [PATCH 08/20] Refactor middleware hostname usage in CameraFeed components (#6875) --- .../Assets/AssetType/HL7Monitor.tsx | 41 ++++++------------- .../Assets/AssetType/ONVIFCamera.tsx | 33 +++++---------- src/Components/Assets/AssetTypes.tsx | 6 +++ src/Components/CameraFeed/CameraFeed.tsx | 3 +- .../CameraFeed/CameraFeedWithBedPresets.tsx | 2 - .../CentralLiveMonitoring/index.tsx | 8 +--- src/Components/CameraFeed/utils.ts | 4 +- src/Components/Common/FilePreviewDialog.tsx | 2 +- .../Facility/CentralNursingStation.tsx | 3 +- .../Facility/Consultations/Feed.tsx | 33 +++++---------- 10 files changed, 46 insertions(+), 89 deletions(-) diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index b4fefbc90b2..86b9565e536 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -1,5 +1,5 @@ import { SyntheticEvent, useEffect, useState } from "react"; -import { AssetData } from "../AssetTypes"; +import { AssetData, ResolvedMiddleware } from "../AssetTypes"; import * as Notification from "../../../Utils/Notifications.js"; import MonitorConfigure from "../configure/MonitorConfigure"; import Loading from "../../Common/Loading"; @@ -13,7 +13,6 @@ import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatien import useAuthUser from "../../../Common/hooks/useAuthUser"; import request from "../../../Utils/request/request"; import routes from "../../../Redux/api"; -import useQuery from "../../../Utils/request/useQuery"; interface HL7MonitorProps { assetId: string; @@ -22,27 +21,20 @@ interface HL7MonitorProps { } const HL7Monitor = (props: HL7MonitorProps) => { - const { assetId, asset, facilityId } = props; + const { assetId, asset } = props; const [assetType, setAssetType] = useState(""); const [middlewareHostname, setMiddlewareHostname] = useState(""); - const [facilityMiddlewareHostname, setFacilityMiddlewareHostname] = - useState(""); + const [resolvedMiddleware, setResolvedMiddleware] = + useState<ResolvedMiddleware>(); const [isLoading, setIsLoading] = useState(true); const [localipAddress, setLocalIPAddress] = useState(""); const [ipadrdress_error, setIpAddress_error] = useState(""); const authUser = useAuthUser(); - const { data: facility, loading } = useQuery(routes.getPermittedFacility, { - pathParams: { id: facilityId }, - onResponse: ({ res, data }) => { - if (res?.status === 200 && data && data.middleware_address) { - setFacilityMiddlewareHostname(data.middleware_address); - } - }, - }); useEffect(() => { setAssetType(asset?.asset_class); setMiddlewareHostname(asset?.meta?.middleware_hostname); + setResolvedMiddleware(asset?.resolved_middleware); setLocalIPAddress(asset?.meta?.local_ip_address); setIsLoading(false); }, [asset]); @@ -76,10 +68,7 @@ const HL7Monitor = (props: HL7MonitorProps) => { } }; - const fallbackMiddleware = - asset?.location_object?.middleware_address || facilityMiddlewareHostname; - - if (isLoading || loading || !facility) return <Loading />; + if (isLoading) return <Loading />; return ( <div className="mx-auto flex w-full xl:mt-8"> <div className="mx-auto flex flex-col gap-4 xl:flex-row-reverse"> @@ -94,23 +83,21 @@ const HL7Monitor = (props: HL7MonitorProps) => { label={ <div className="flex flex-row gap-1"> <p>Middleware Hostname</p> - {!middlewareHostname && ( + {resolvedMiddleware?.source != "asset" && ( <div className="tooltip"> <CareIcon icon="l-info-circle" className="tooltip text-indigo-500 hover:text-indigo-600" /> <span className="tooltip-text w-56 whitespace-normal"> - Middleware hostname sourced from{" "} - {asset?.location_object?.middleware_address - ? "asset location" - : "asset facility"} + Middleware hostname sourced from asset{" "} + {resolvedMiddleware?.source} </span> </div> )} </div> } - placeholder={fallbackMiddleware} + placeholder={resolvedMiddleware?.hostname} value={middlewareHostname} onChange={(e) => setMiddlewareHostname(e.value)} errorClassName="hidden" @@ -140,16 +127,12 @@ const HL7Monitor = (props: HL7MonitorProps) => { {assetType === "HL7MONITOR" && ( <HL7PatientVitalsMonitor - socketUrl={`wss://${ - middlewareHostname || fallbackMiddleware - }/observations/${localipAddress}`} + socketUrl={`wss://${resolvedMiddleware?.hostname}/observations/${localipAddress}`} /> )} {assetType === "VENTILATOR" && ( <VentilatorPatientVitalsMonitor - socketUrl={`wss://${ - middlewareHostname || fallbackMiddleware - }/observations/${localipAddress}`} + socketUrl={`wss://${resolvedMiddleware?.hostname}/observations/${localipAddress}`} /> )} </div> diff --git a/src/Components/Assets/AssetType/ONVIFCamera.tsx b/src/Components/Assets/AssetType/ONVIFCamera.tsx index 44d4d372d73..86b7199cdac 100644 --- a/src/Components/Assets/AssetType/ONVIFCamera.tsx +++ b/src/Components/Assets/AssetType/ONVIFCamera.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import { AssetData } from "../AssetTypes"; +import { AssetData, ResolvedMiddleware } from "../AssetTypes"; import * as Notification from "../../../Utils/Notifications.js"; import { BedModel } from "../../Facility/models"; import axios from "axios"; @@ -29,8 +29,8 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const [isLoading, setIsLoading] = useState(true); const [assetType, setAssetType] = useState(""); const [middlewareHostname, setMiddlewareHostname] = useState(""); - const [facilityMiddlewareHostname, setFacilityMiddlewareHostname] = - useState(""); + const [resolvedMiddleware, setResolvedMiddleware] = + useState<ResolvedMiddleware>(); const [cameraAddress, setCameraAddress] = useState(""); const [ipadrdress_error, setIpAddress_error] = useState(""); const [username, setUsername] = useState(""); @@ -47,20 +47,11 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { pathParams: { id: facilityId }, }); const authUser = useAuthUser(); - useEffect(() => { - if (facility?.middleware_address) { - setFacilityMiddlewareHostname(facility.middleware_address); - } - }, [facility, facilityId]); - - const fallbackMiddleware = - asset?.location_object?.middleware_address || facilityMiddlewareHostname; - - const currentMiddleware = middlewareHostname || fallbackMiddleware; useEffect(() => { if (asset) { setAssetType(asset?.asset_class); + setResolvedMiddleware(asset?.resolved_middleware); const cameraConfig = getCameraConfig(asset); setMiddlewareHostname(cameraConfig.middleware_hostname); setCameraAddress(cameraConfig.hostname); @@ -79,7 +70,7 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const data = { meta: { asset_type: "CAMERA", - middleware_hostname: middlewareHostname, // TODO: remove this infavour of facility.middleware_address + middleware_hostname: middlewareHostname, local_ip_address: cameraAddress, camera_access_key: `${username}:${password}:${streamUuid}`, }, @@ -110,7 +101,7 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { try { setLoadingAddPreset(true); const presetData = await axios.get( - `https://${currentMiddleware}/status?hostname=${config.hostname}&port=${config.port}&username=${config.username}&password=${config.password}` + `https://${resolvedMiddleware?.hostname}/status?hostname=${config.hostname}&port=${config.port}&username=${config.username}&password=${config.password}` ); const { res } = await request(routes.createAssetBed, { @@ -151,23 +142,21 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { label={ <div className="flex flex-row gap-1"> <p>Middleware Hostname</p> - {!middlewareHostname && ( + {resolvedMiddleware?.source != "asset" && ( <div className="tooltip"> <CareIcon icon="l-info-circle" className="tooltip text-indigo-500 hover:text-indigo-600" /> <span className="tooltip-text w-56 whitespace-normal"> - Middleware hostname sourced from{" "} - {asset?.location_object?.middleware_address - ? "asset location" - : "asset facility"} + Middleware hostname sourced from asset{" "} + {resolvedMiddleware?.source} </span> </div> )} </div> } - placeholder={fallbackMiddleware} + placeholder={resolvedMiddleware?.hostname} value={middlewareHostname} onChange={({ value }) => setMiddlewareHostname(value)} /> @@ -225,7 +214,7 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { addPreset={addPreset} isLoading={loadingAddPreset} refreshPresetsHash={refreshPresetsHash} - facilityMiddlewareHostname={currentMiddleware} + facilityMiddlewareHostname={resolvedMiddleware?.hostname || ""} /> ) : null} </div> diff --git a/src/Components/Assets/AssetTypes.tsx b/src/Components/Assets/AssetTypes.tsx index 97334f6af49..436c9370dd8 100644 --- a/src/Components/Assets/AssetTypes.tsx +++ b/src/Components/Assets/AssetTypes.tsx @@ -72,6 +72,11 @@ export interface AssetService { note: string; } +export interface ResolvedMiddleware { + hostname: string; + source: "asset" | "location" | "facility"; +} + export interface AssetData { id: string; name: string; @@ -93,6 +98,7 @@ export interface AssetData { qr_code_id: string; manufacturer: string; warranty_amc_end_of_validity: string; + resolved_middleware?: ResolvedMiddleware; last_service: AssetService; meta?: { [key: string]: any; diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index 4ec039e4e70..6e2d8647c83 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -14,7 +14,6 @@ import Fullscreen from "../../CAREUI/misc/Fullscreen"; interface Props { children?: React.ReactNode; asset: AssetData; - fallbackMiddleware: string; // TODO: remove this in favour of `asset.resolved_middleware.hostname` once https://github.com/coronasafe/care/pull/1741 is merged preset?: PTZPayload; silent?: boolean; className?: string; @@ -29,7 +28,7 @@ interface Props { export default function CameraFeed(props: Props) { const playerRef = useRef<HTMLVideoElement | ReactPlayer | null>(null); - const streamUrl = getStreamUrl(props.asset, props.fallbackMiddleware); + const streamUrl = getStreamUrl(props.asset); const player = usePlayer(streamUrl, playerRef); const operate = useOperateCamera(props.asset.id, props.silent); diff --git a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx index b52071a8597..386b93325b0 100644 --- a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx +++ b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx @@ -5,7 +5,6 @@ import AssetBedSelect from "./AssetBedSelect"; interface Props { asset: AssetData; - fallbackMiddleware?: string; } export default function LocationFeedTile(props: Props) { @@ -14,7 +13,6 @@ export default function LocationFeedTile(props: Props) { return ( <CameraFeed asset={props.asset} - fallbackMiddleware={props.fallbackMiddleware as string} silent preset={preset?.meta.position} shortcutsDisabled diff --git a/src/Components/CameraFeed/CentralLiveMonitoring/index.tsx b/src/Components/CameraFeed/CentralLiveMonitoring/index.tsx index d83b01201b9..d2e7fd89494 100644 --- a/src/Components/CameraFeed/CentralLiveMonitoring/index.tsx +++ b/src/Components/CameraFeed/CentralLiveMonitoring/index.tsx @@ -67,13 +67,7 @@ export default function CentralLiveMonitoring(props: { facilityId: string }) { <div className="mt-1 grid grid-cols-1 place-content-center gap-1 lg:grid-cols-2 3xl:grid-cols-3"> {data.results.map((asset) => ( <div className="text-clip" key={asset.id}> - <LocationFeedTile - asset={asset} - fallbackMiddleware={ - asset.location_object.middleware_address || - facilityQuery.data?.middleware_address - } - /> + <LocationFeedTile asset={asset} /> </div> ))} </div> diff --git a/src/Components/CameraFeed/utils.ts b/src/Components/CameraFeed/utils.ts index b5b8920fd5a..e2793d76b41 100644 --- a/src/Components/CameraFeed/utils.ts +++ b/src/Components/CameraFeed/utils.ts @@ -17,9 +17,9 @@ export const calculateVideoDelay = ( return playedDuration - video.currentTime; }; -export const getStreamUrl = (asset: AssetData, fallbackMiddleware?: string) => { +export const getStreamUrl = (asset: AssetData) => { const config = getCameraConfig(asset); - const host = config.middleware_hostname || fallbackMiddleware; + const host = asset.resolved_middleware?.hostname; const uuid = config.accessKey; return isIOS diff --git a/src/Components/Common/FilePreviewDialog.tsx b/src/Components/Common/FilePreviewDialog.tsx index 90dde50a249..cb16337b238 100644 --- a/src/Components/Common/FilePreviewDialog.tsx +++ b/src/Components/Common/FilePreviewDialog.tsx @@ -192,7 +192,7 @@ const FilePreviewDialog = (props: FilePreviewProps) => { /> ) : ( <iframe - sandbox + sandbox="" title="Source Files" src={fileUrl} className="mx-auto h-5/6 w-5/6 border-2 border-black bg-white md:my-6 md:w-4/6" diff --git a/src/Components/Facility/CentralNursingStation.tsx b/src/Components/Facility/CentralNursingStation.tsx index 601f579e8d0..9e8febe88fd 100644 --- a/src/Components/Facility/CentralNursingStation.tsx +++ b/src/Components/Facility/CentralNursingStation.tsx @@ -86,8 +86,7 @@ export default function CentralNursingStation({ facilityId }: Props) { setTotalCount(res.data.count); setData( entries.map(({ patient, asset, bed }) => { - const middleware = - asset.meta?.middleware_hostname || facilityObj?.middleware_address; + const middleware = asset.resolved_middleware?.hostname; const local_ip_address = asset.meta?.local_ip_address; return { diff --git a/src/Components/Facility/Consultations/Feed.tsx b/src/Components/Facility/Consultations/Feed.tsx index fcd6d50360f..22953103089 100644 --- a/src/Components/Facility/Consultations/Feed.tsx +++ b/src/Components/Facility/Consultations/Feed.tsx @@ -27,6 +27,7 @@ import { triggerGoal } from "../../../Integrations/Plausible.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; import Spinner from "../../Common/Spinner.js"; import useQuery from "../../../Utils/request/useQuery.js"; +import { ResolvedMiddleware } from "../../Assets/AssetTypes.js"; interface IFeedProps { facilityId: string; @@ -35,7 +36,7 @@ interface IFeedProps { const PATIENT_DEFAULT_PRESET = "Patient View".trim().toLowerCase(); -export const Feed: React.FC<IFeedProps> = ({ consultationId, facilityId }) => { +export const Feed: React.FC<IFeedProps> = ({ consultationId }) => { const dispatch: any = useDispatch(); const videoWrapper = useRef<HTMLDivElement>(null); @@ -55,25 +56,10 @@ export const Feed: React.FC<IFeedProps> = ({ consultationId, facilityId }) => { const [isFullscreen, setFullscreen] = useFullscreen(); const [videoStartTime, setVideoStartTime] = useState<Date | null>(null); const [statusReported, setStatusReported] = useState(false); - const [facilityMiddlewareHostname, setFacilityMiddlewareHostname] = - useState(""); + const [resolvedMiddleware, setResolvedMiddleware] = + useState<ResolvedMiddleware>(); const authUser = useAuthUser(); - useQuery(routes.getPermittedFacility, { - pathParams: { id: facilityId || "" }, - onResponse: ({ res, data }) => { - if (res && res.status === 200 && data && data.middleware_address) { - setFacilityMiddlewareHostname(data.middleware_address); - } - }, - }); - - const fallbackMiddleware = - cameraAsset.location_middleware || facilityMiddlewareHostname; - - const currentMiddleware = - cameraAsset.middleware_address || fallbackMiddleware; - useEffect(() => { if (cameraState) { setCameraState({ @@ -136,6 +122,9 @@ export const Feed: React.FC<IFeedProps> = ({ consultationId, facilityId }) => { bedAssets.data.results[0].asset_object.location_object ?.middleware_address, }); + setResolvedMiddleware( + bedAssets.data.results[0].asset_object.resolved_middleware + ); setCameraConfig(bedAssets.data.results[0].meta); setCameraState({ ...bedAssets.data.results[0].meta.position, @@ -173,8 +162,8 @@ export const Feed: React.FC<IFeedProps> = ({ consultationId, facilityId }) => { ); const url = !isIOS - ? `wss://${currentMiddleware}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0` - : `https://${currentMiddleware}/stream/${cameraAsset?.accessKey}/channel/0/hls/live/index.m3u8?uuid=${cameraAsset?.accessKey}&channel=0`; + ? `wss://${resolvedMiddleware?.hostname}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0` + : `https://${resolvedMiddleware?.hostname}/stream/${cameraAsset?.accessKey}/channel/0/hls/live/index.m3u8?uuid=${cameraAsset?.accessKey}&channel=0`; const { startStream, @@ -185,7 +174,7 @@ export const Feed: React.FC<IFeedProps> = ({ consultationId, facilityId }) => { : // eslint-disable-next-line react-hooks/rules-of-hooks useMSEMediaPlayer({ config: { - middlewareHostname: currentMiddleware, + middlewareHostname: resolvedMiddleware?.hostname ?? "", ...cameraAsset, }, url, @@ -254,7 +243,7 @@ export const Feed: React.FC<IFeedProps> = ({ consultationId, facilityId }) => { }); getBedPresets(cameraAsset); } - }, [cameraAsset, currentMiddleware]); + }, [cameraAsset, resolvedMiddleware?.hostname]); useEffect(() => { let tId: any; From 988e383a6246695d4638e30f5ed813189a4755ae Mon Sep 17 00:00:00 2001 From: Onkar Jadhav <56870381+Omkar76@users.noreply.github.com> Date: Thu, 28 Dec 2023 06:58:49 +0530 Subject: [PATCH 09/20] Fix mispositioned active link indicator (#6880) * Fix mispositioned active link indicator * Make indicator height 26% of link element height. * Extract 'navItemCount' as variable --- src/Components/Common/Sidebar/Sidebar.tsx | 56 +++----- src/Components/Common/Sidebar/SidebarItem.tsx | 126 ++++++++++-------- 2 files changed, 85 insertions(+), 97 deletions(-) diff --git a/src/Components/Common/Sidebar/Sidebar.tsx b/src/Components/Common/Sidebar/Sidebar.tsx index 10b5c6d04fa..fa809571772 100644 --- a/src/Components/Common/Sidebar/Sidebar.tsx +++ b/src/Components/Common/Sidebar/Sidebar.tsx @@ -55,61 +55,36 @@ const StatelessSidebar = ({ const { dashboard_url } = useConfig(); const indicatorRef = useRef<HTMLDivElement>(null); + const activeLinkRef = useRef<HTMLAnchorElement>(null); const [lastIndicatorPosition, setLastIndicatorPosition] = useState(0); const [isOverflowVisible, setOverflowVisisble] = useState(false); useEffect(() => { if (!indicatorRef.current) return; const index = NavItems.findIndex((item) => item.to === activeLink); + const navItemCount = NavItems.length + 2; // +2 for notification and dashboard if (index !== -1) { // Haha math go brrrrrrrrr const e = indicatorRef.current; - - const itemHeight = 44; - const bottomItemOffset = 2; - - const indexDifference = index - lastIndicatorPosition; - e.style.display = "block"; - - if (indexDifference > 0) { - e.style.top = lastIndicatorPosition * itemHeight + 16 + "px"; - e.style.bottom = "auto"; + const itemHeight = activeLinkRef.current?.clientHeight || 0; + if (lastIndicatorPosition > index) { + e.style.top = `${itemHeight * (index + 0.37)}px`; + setTimeout(() => { + e.style.bottom = `${itemHeight * (navItemCount - 0.63 - index)}px`; + }, 50); } else { - e.style.bottom = - itemHeight * (NavItems.length + bottomItemOffset) - - lastIndicatorPosition * itemHeight - - 28 + - "px"; - e.style.top = "auto"; + e.style.bottom = `${itemHeight * (navItemCount - 0.63 - index)}px`; + setTimeout(() => { + e.style.top = `${itemHeight * (index + 0.37)}px`; + }, 50); } - - const variableHeight = Math.min( - Math.abs(indexDifference) * itemHeight, - 70 - ); - - e.style.height = `${variableHeight}px`; - setTimeout(() => { - if (!e) return; - if (indexDifference > 0) { - e.style.top = index * itemHeight + 16 + "px"; - e.style.bottom = "auto"; - } else { - e.style.bottom = - itemHeight * (NavItems.length + bottomItemOffset) - - index * itemHeight - - 28 + - "px"; - e.style.top = "auto"; - } - e.style.height = "0.75rem"; - setLastIndicatorPosition(index); - }, 300); + setLastIndicatorPosition(index); } else { indicatorRef.current.style.display = "none"; } - }, [activeLink]); + }, [activeLink, lastIndicatorPosition]); + const handleOverflow = (value: boolean) => { setOverflowVisisble(value); }; @@ -147,6 +122,7 @@ const StatelessSidebar = ({ {NavItems.map((i) => { return ( <Item + ref={i.to === activeLink ? activeLinkRef : undefined} key={i.text} {...i} icon={<CareIcon className={`${i.icon} h-5`} />} diff --git a/src/Components/Common/Sidebar/SidebarItem.tsx b/src/Components/Common/Sidebar/SidebarItem.tsx index e13a1452125..20ffec4217e 100644 --- a/src/Components/Common/Sidebar/SidebarItem.tsx +++ b/src/Components/Common/Sidebar/SidebarItem.tsx @@ -2,10 +2,12 @@ import { Link } from "raviger"; import { useTranslation } from "react-i18next"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import useAppHistory from "../../../Common/hooks/useAppHistory"; +import React, { forwardRef, Ref } from "react"; export type SidebarIcon = React.ReactNode; type SidebarItemProps = { + ref?: React.Ref<HTMLAnchorElement>; text: string; icon: SidebarIcon; external?: true | undefined; @@ -14,74 +16,84 @@ type SidebarItemProps = { handleOverflow?: any; } & ({ to: string; do?: undefined } | { to?: string; do: () => void }); -type SidebarItemBaseProps = SidebarItemProps & { shrinked?: boolean }; -const SidebarItemBase = ({ - shrinked, - external, - ...props -}: SidebarItemBaseProps) => { - const { t } = useTranslation(); - const { resetHistory } = useAppHistory(); +type SidebarItemBaseProps = SidebarItemProps & { + shrinked?: boolean; + ref: Ref<HTMLAnchorElement>; +}; + +const SidebarItemBase = forwardRef( + ( + { shrinked, external, ...props }: SidebarItemBaseProps, + ref: Ref<HTMLAnchorElement> + ) => { + const { t } = useTranslation(); + const { resetHistory } = useAppHistory(); - return ( - <Link - className={`tooltip relative ml-1 mr-3 h-full min-h-[40px] flex-1 cursor-pointer rounded-lg text-white transition-all duration-200 ease-in-out md:h-11 md:flex-none + return ( + <Link + ref={ref} + className={`tooltip relative ml-1 mr-3 h-full min-h-[40px] flex-1 cursor-pointer rounded-lg text-white transition-all duration-200 ease-in-out md:h-11 md:flex-none ${ props.selected ? "bg-primary-900 font-bold" : "bg-primary-800 font-normal hover:bg-primary-700" }`} - target={external && "_blank"} - rel={external && "noreferrer"} - href={props.to ?? ""} - onClick={props.do ?? resetHistory} - onMouseEnter={() => { - props.handleOverflow(true); - }} - onMouseLeave={() => { - props.handleOverflow(false); - }} - > - <span className={`tooltip-text tooltip-right ${!shrinked && "hidden"}`}> - {t(props.text)} - </span> - <div - className={`flex h-full items-center ${ - shrinked ? "justify-center" : "justify-start pl-5 pr-4" - } transition-all duration-200 ease-in-out`} + target={external && "_blank"} + rel={external && "noreferrer"} + href={props.to ?? ""} + onClick={props.do ?? resetHistory} + onMouseEnter={() => { + props.handleOverflow(true); + }} + onMouseLeave={() => { + props.handleOverflow(false); + }} > - <div className="flex-none text-lg">{props.icon}</div> - <span - className={`${ - shrinked ? "hidden" : "grow" - } flex w-full items-center pl-4 text-sm tracking-wide`} - > + <span className={`tooltip-text tooltip-right ${!shrinked && "hidden"}`}> {t(props.text)} </span> - {external && !shrinked && ( - <CareIcon className="care-l-external-link-alt text-lg" /> - )} - </div> - - {!!props.badgeCount && ( - <span - className={`absolute flex items-center justify-center bg-primary-500 font-semibold text-white ${ - shrinked - ? "right-3 top-0.5 h-4 w-5 rounded-md text-[9px]" - : "inset-y-0 right-4 my-auto h-6 rounded-md px-2 text-xs" - } z-10 animate-pulse transition-all duration-200 ease-in-out`} + <div + className={`flex h-full items-center ${ + shrinked ? "justify-center" : "justify-start pl-5 pr-4" + } transition-all duration-200 ease-in-out`} > - {props.badgeCount > 9 ? "9+" : props.badgeCount} - </span> - )} - </Link> - ); -}; + <div className="flex-none text-lg">{props.icon}</div> + <span + className={`${ + shrinked ? "hidden" : "grow" + } flex w-full items-center pl-4 text-sm tracking-wide`} + > + {t(props.text)} + </span> + {external && !shrinked && ( + <CareIcon className="care-l-external-link-alt text-lg" /> + )} + </div> + + {!!props.badgeCount && ( + <span + className={`absolute flex items-center justify-center bg-primary-500 font-semibold text-white ${ + shrinked + ? "right-3 top-0.5 h-4 w-5 rounded-md text-[9px]" + : "inset-y-0 right-4 my-auto h-6 rounded-md px-2 text-xs" + } z-10 animate-pulse transition-all duration-200 ease-in-out`} + > + {props.badgeCount > 9 ? "9+" : props.badgeCount} + </span> + )} + </Link> + ); + } +); -export const SidebarItem = (props: SidebarItemProps) => ( - <SidebarItemBase {...props} /> +export const SidebarItem = forwardRef( + (props: SidebarItemProps, ref: Ref<HTMLAnchorElement>) => ( + <SidebarItemBase {...props} ref={ref} /> + ) ); -export const ShrinkedSidebarItem = (props: SidebarItemProps) => ( - <SidebarItemBase shrinked {...props} /> +export const ShrinkedSidebarItem = forwardRef( + (props: SidebarItemProps, ref: Ref<HTMLAnchorElement>) => ( + <SidebarItemBase shrinked ref={ref} {...props} /> + ) ); From 8a9234feac7cfbe6eaed5fde410774308a20adfe Mon Sep 17 00:00:00 2001 From: Rithvik Nishad <mail@rithviknishad.dev> Date: Thu, 28 Dec 2023 14:01:12 +0530 Subject: [PATCH 10/20] Migrate Bed Management to `useQuery` and move pagination state to query params (#6909) * fixes #6906; migrate Bed Management to `useQuery` * uncommented related test * handle the auto-fill delay * handle the google map delay * revert vite --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Co-authored-by: Khavin Shankar <khavinshankar@gmail.com> --- cypress/e2e/facility_spec/locations.cy.ts | 34 ++--- .../pageobject/Facility/FacilityCreation.ts | 3 + src/Components/Facility/BedManagement.tsx | 127 +++++------------- src/Redux/api.tsx | 3 + 4 files changed, 58 insertions(+), 109 deletions(-) diff --git a/cypress/e2e/facility_spec/locations.cy.ts b/cypress/e2e/facility_spec/locations.cy.ts index b9d26ee3100..d6377ba97c3 100644 --- a/cypress/e2e/facility_spec/locations.cy.ts +++ b/cypress/e2e/facility_spec/locations.cy.ts @@ -3,16 +3,18 @@ import { AssetPage } from "../../pageobject/Asset/AssetCreation"; import { UserCreationPage } from "../../pageobject/Users/UserCreation"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityLocation from "../../pageobject/Facility/FacilityLocation"; +import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; -// import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; + describe("Location Management Section", () => { const assetPage = new AssetPage(); const userCreationPage = new UserCreationPage(); const facilityPage = new FacilityPage(); const facilityLocation = new FacilityLocation(); + const assetPagination = new AssetPagination(); const facilityHome = new FacilityHome(); - // const assetPagination = new AssetPagination(); + const EXPECTED_LOCATION_ERROR_MESSAGES = [ "Name is required", "Location Type is required", @@ -37,7 +39,7 @@ describe("Location Management Section", () => { const bedModifiedDescrption = "test modified description"; const bedModifiedType = "Isolation"; const numberOfBeds = 10; - // const numberOfModifiedBeds = 25; + const numberOfModifiedBeds = 25; before(() => { cy.loginByApi("devdistrictadmin", "Coronasafe@123"); @@ -144,19 +146,19 @@ describe("Location Management Section", () => { facilityLocation.deleteBedRequest(); }); - // it("Add Multiple Bed to a facility location and verify pagination", () => { - // // bed creation - // facilityLocation.clickManageBedButton(); - // facilityLocation.clickAddBedButton(); - // facilityLocation.enterBedName(bedModifiedName); - // facilityLocation.enterBedDescription(bedModifiedDescrption); - // facilityLocation.selectBedType(bedModifiedType); - // facilityLocation.setMultipleBeds(numberOfModifiedBeds); - // assetPage.clickassetupdatebutton(); - // // pagination - // assetPagination.navigateToNextPage(); - // assetPagination.navigateToPreviousPage(); - // }); need to be unblocked upon issue #6906 is solved + it("Add Multiple Bed to a facility location and verify pagination", () => { + // bed creation + facilityLocation.clickManageBedButton(); + facilityLocation.clickAddBedButton(); + facilityLocation.enterBedName(bedModifiedName); + facilityLocation.enterBedDescription(bedModifiedDescrption); + facilityLocation.selectBedType(bedModifiedType); + facilityLocation.setMultipleBeds(numberOfModifiedBeds); + assetPage.clickassetupdatebutton(); + // pagination + assetPagination.navigateToNextPage(); + assetPagination.navigateToPreviousPage(); + }); afterEach(() => { cy.saveLocalStorage(); diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index e3639388620..2d6aa9ff375 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -299,6 +299,7 @@ class FacilityPage { cy.intercept("https://maps.googleapis.com/maps/api/mapsjs/*").as("mapApi"); cy.wait("@mapApi").its("response.statusCode").should("eq", 200); cy.get("input#pac-input").type(location).type("{enter}"); + cy.wait(2000); cy.get("div#map-close").click(); } @@ -418,6 +419,7 @@ class FacilityPage { selectStateOnPincode(stateName) { this.getStateElement() .scrollIntoView() + .wait(2000) .should("be.visible") .then(($element) => { const text = $element.text(); @@ -431,6 +433,7 @@ class FacilityPage { selectDistrictOnPincode(districtName) { this.getDistrictElement() .scrollIntoView() + .wait(2000) .should("be.visible") .then(($element) => { const text = $element.text(); diff --git a/src/Components/Facility/BedManagement.tsx b/src/Components/Facility/BedManagement.tsx index 3c786bdce25..68e9fade43d 100644 --- a/src/Components/Facility/BedManagement.tsx +++ b/src/Components/Facility/BedManagement.tsx @@ -1,14 +1,4 @@ -import { lazy, useCallback, useState } from "react"; - -import { useDispatch } from "react-redux"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { - getAnyFacility, - getFacilityAssetLocation, - listFacilityBeds, - deleteFacilityBed, -} from "../../Redux/actions"; -import Pagination from "../Common/Pagination"; +import { lazy, useState } from "react"; import ButtonV2 from "../Common/components/ButtonV2"; import { BedModel } from "./models"; import { ReactElement } from "react"; @@ -18,6 +8,10 @@ import BedDeleteDialog from "./BedDeleteDialog"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import Page from "../Common/components/Page"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import useFilters from "../../Common/hooks/useFilters"; const Loading = lazy(() => import("../Common/Loading")); interface BedManagementProps { @@ -47,8 +41,6 @@ const BedRow = (props: BedRowProps) => { bedType, isOccupied, } = props; - - const dispatchAction: any = useDispatch(); const [bedData, setBedData] = useState<{ show: boolean; name: string; @@ -62,15 +54,11 @@ const BedRow = (props: BedRowProps) => { }; const handleDeleteConfirm = async () => { - const res = await dispatchAction(deleteFacilityBed(id)); - if (res?.status === 204) { - Notification.Success({ - msg: "Bed deleted successfully", - }); - } else { - Notification.Error({ - msg: "Error while deleting Bed: " + (res?.data?.detail || ""), - }); + const { res } = await request(routes.deleteFacilityBed, { + pathParams: { external_id: id }, + }); + if (res?.ok) { + Notification.Success({ msg: "Bed deleted successfully" }); } setBedData({ show: false, name: "" }); triggerRerender(); @@ -158,83 +146,41 @@ const BedRow = (props: BedRowProps) => { export const BedManagement = (props: BedManagementProps) => { const { facilityId, locationId } = props; - const dispatchAction: any = useDispatch(); - const [isLoading, setIsLoading] = useState(false); let bed: ReactElement | null = null; let BedList: ReactElement[] | ReactElement = []; - const [beds, setBeds] = useState<BedModel[]>([]); - const [offset, setOffset] = useState(0); - const [currentPage, setCurrentPage] = useState(1); - const [totalCount, setTotalCount] = useState(0); - const [rerender, setRerender] = useState(false); - const [facilityName, setFacilityName] = useState(""); - const [locationName, setLocationName] = useState(""); - const limit = 14; - - const triggerRerender = () => { - setRerender(!rerender); - }; - - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const facility = await dispatchAction(getAnyFacility(facilityId)); - - setFacilityName(facility?.data?.name || ""); + const { qParams, Pagination, resultsPerPage } = useFilters({}); - const location = await dispatchAction( - getFacilityAssetLocation(facilityId, locationId) - ); - - setLocationName(location?.data?.name || ""); - - const res = await dispatchAction( - listFacilityBeds({ - limit, - offset, - facility: facilityId, - location: locationId, - }) - ); - if (!status.aborted) { - if (res?.data) { - setBeds(res.data.results); - setTotalCount(res.data.count); - } - setIsLoading(false); - } + const { data: location } = useQuery(routes.getFacilityAssetLocation, { + pathParams: { + facility_external_id: facilityId, + external_id: locationId, }, - [dispatchAction, offset, rerender, facilityId, locationId] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(status); + }); + + const { loading, data, refetch } = useQuery(routes.listFacilityBeds, { + query: { + facility: facilityId, + location: locationId, + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, }, - [fetchData] - ); - - const handlePagination = (page: number, limit: number) => { - const offset = (page - 1) * limit; - setCurrentPage(page); - setOffset(offset); - }; + }); - if (beds && beds.length) { - BedList = beds.map((bedItem: BedModel) => ( + if (data?.results.length) { + BedList = data.results.map((bedItem: BedModel) => ( <BedRow id={bedItem.id ?? ""} facilityId={facilityId ?? ""} name={bedItem.name ?? ""} description={bedItem.description ?? ""} bedType={bedItem.bed_type ?? ""} - triggerRerender={triggerRerender} + triggerRerender={refetch} key={locationId ?? ""} locationId={locationId ?? ""} isOccupied={bedItem.is_occupied ?? false} /> )); - } else if (beds && beds.length === 0) { + } else if (data?.results.length === 0) { BedList = ( <p className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500"> No beds available in this location @@ -242,25 +188,20 @@ export const BedManagement = (props: BedManagementProps) => { ); } - if (beds) { + if (data?.results.length) { bed = ( <> <div className="mt-5 flex grow flex-wrap bg-white p-4">{BedList}</div> - {totalCount > limit && ( + {data.count && ( <div className="mt-4 flex w-full justify-center"> - <Pagination - cPage={currentPage} - defaultPerPage={limit} - data={{ totalCount }} - onChange={handlePagination} - /> + <Pagination totalCount={data.count} /> </div> )} </> ); } - if (isLoading || !beds) { + if (loading) { return <Loading />; } @@ -268,9 +209,9 @@ export const BedManagement = (props: BedManagementProps) => { <Page title="Bed Management" crumbsReplacements={{ - [facilityId]: { name: facilityName }, + [facilityId]: { name: location?.facility?.name }, [locationId]: { - name: locationName, + name: location?.name, uri: `/facility/${facilityId}/location`, }, }} diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 80d5a630bea..2b8d4f8f51b 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -48,6 +48,7 @@ import { WardModel, LocationModel, PatientNotesModel, + BedModel, } from "../Components/Facility/models"; import { IDeleteExternalResult, @@ -399,6 +400,7 @@ const routes = { listFacilityBeds: { path: "/api/v1/bed/", method: "GET", + TRes: Type<PaginatedResponse<BedModel>>(), }, createFacilityBed: { path: "/api/v1/bed/", @@ -415,6 +417,7 @@ const routes = { deleteFacilityBed: { path: "/api/v1/bed/{external_id}/", method: "DELETE", + TRes: Type<Record<string, never>>(), }, // Consultation beds From 36fa6e34968b7f0e19f63091f65c30eacbcf19f8 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:06:20 +0530 Subject: [PATCH 11/20] Fix latitude and longitude handling in FacilityCreate component (#6934) --- src/Components/Facility/FacilityCreate.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index c9f40b5d1ef..9fbe89035c4 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -252,8 +252,8 @@ export const FacilityCreate = (props: FacilityProps) => { ? "+91" + data.phone_number : data.phone_number : "", - latitude: data ? String(data.latitude) : "", - longitude: data ? String(data.longitude) : "", + latitude: data.latitude ? String(data.latitude) : "", + longitude: data.longitude ? String(data.longitude) : "", type_b_cylinders: data.type_b_cylinders, type_c_cylinders: data.type_c_cylinders, type_d_cylinders: data.type_d_cylinders, From 4a497688142a4a8f440b657e06bfd83bace147d4 Mon Sep 17 00:00:00 2001 From: Ashraf Mohammed <98876115+AshrafMd-1@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:04:46 +0530 Subject: [PATCH 12/20] Add OP number to treatment summary. (#6898) * add op * Add correct borders in treatment summary * Update TreatmentSummary.tsx * add space * add extra cols --- src/Components/Facility/TreatmentSummary.tsx | 34 +++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/src/Components/Facility/TreatmentSummary.tsx b/src/Components/Facility/TreatmentSummary.tsx index 8291e0e8ba4..8f1589cdf6f 100644 --- a/src/Components/Facility/TreatmentSummary.tsx +++ b/src/Components/Facility/TreatmentSummary.tsx @@ -122,22 +122,32 @@ const TreatmentSummary = (props: any) => { <div className="text-right font-bold">{formatDate(date)}</div> <div className="mb-5 mt-2 border border-gray-800"> - <div className="grid border-b-2 border-gray-800 print:grid-cols-3 sm:grid-cols-3"> + <div className="grid border-b-2 border-gray-800 print:grid-cols-3 sm:grid-cols-2 print:md:grid-cols-3"> <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 print:border-r-2 sm:border-b-0 sm:border-r-2"> <b>Name :</b> {patientData.name} </div> <div className="col-span-1 px-3 py-2"> - <b>Address :</b> {patientData.address} + <b>Address : </b> {patientData.address} </div> </div> - <div className="grid border-b-2 border-gray-800 print:grid-cols-3 sm:grid-cols-2 md:grid-cols-3"> - <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 print:border-r-2 sm:border-r-2 md:border-b-0 "> - <b>Age :</b>{" "} - {formatAge(patientData.age, patientData.date_of_birth, true)} + <div className="grid border-b-2 border-gray-800 print:grid-cols-3 sm:grid-cols-2 print:md:grid-cols-3"> + <div className="col-span-1 grid print:grid-cols-2 sm:grid-cols-2 "> + <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 print:border-r-2 sm:border-b-0 sm:border-r-2"> + <b>Age :</b>{" "} + {formatAge( + patientData.age, + patientData.date_of_birth, + true + )} + </div> + <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 print:border-r-2 sm:border-b-0 sm:border-r-2"> + <b>OP :</b> {consultationData.patient_no} + </div> </div> - <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 md:border-b-0 "> - <b>Date of admission :</b> + + <div className="col-span-1 px-3 py-2"> + <b>Date of admission : </b> <span> {consultationData.admitted ? formatDateTime(consultationData.encounter_date) @@ -146,13 +156,13 @@ const TreatmentSummary = (props: any) => { </div> </div> - <div className="grid border-b-2 border-gray-800 print:grid-cols-3 sm:grid-cols-2 md:grid-cols-3"> - <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 print:border-r-2 sm:border-r-2 md:border-b-0"> - <b>Gender :</b> + <div className="grid border-b-2 border-gray-800 print:grid-cols-3 sm:grid-cols-2 print:md:grid-cols-3"> + <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 print:border-r-2 sm:border-b-0 sm:border-r-2"> + <b>Gender : </b> {GENDER_TYPES.find((i) => i.id === patientData.gender)?.text} </div> - <div className="col-span-1 border-b-2 border-gray-800 px-3 py-2 print:border-b-0 md:border-b-0 "> + <div className="col-span-1 px-3 py-2"> <b>Contact person :</b> <span> {" "} From 22f36fd6ba73ee2d8754564f090ad0b16f689079 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Thu, 28 Dec 2023 17:07:31 +0530 Subject: [PATCH 13/20] Add consciousness level options for normal daily round type (#6935) --- src/Common/constants.tsx | 13 ++++++++++++ .../Form/FormFields/RadioFormField.tsx | 3 ++- .../Patient/DailyRoundListDetails.tsx | 16 +++++++++++++- src/Components/Patient/DailyRounds.tsx | 21 +++++++++++++++++-- 4 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 23c02e389f3..f472e8b193c 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -300,6 +300,19 @@ export const DISCHARGE_REASONS = [ { id: "LAMA", text: "LAMA" }, ]; +export const CONSCIOUSNESS_LEVEL = [ + { id: "UNRESPONSIVE", text: "Unresponsive" }, + { id: "RESPONDS_TO_PAIN", text: "Responds to Pain" }, + { id: "RESPONDS_TO_VOICE", text: "Responds to Voice" }, + { id: "ALERT", text: "Alert" }, + { id: "AGITATED_OR_CONFUSED", text: "Agitated or Confused" }, + { + id: "Onset of Agitation and Confusion", + text: "Onset of Agitation and Confusion", + }, + { id: "UNKNOWN", text: "Unknown" }, +]; + export const LINES_CATHETER_CHOICES: Array<OptionsType> = [ { id: 1, text: "CVP catheter " }, { id: 2, text: "Arterial Line" }, diff --git a/src/Components/Form/FormFields/RadioFormField.tsx b/src/Components/Form/FormFields/RadioFormField.tsx index 3d1a9b7d8ac..905986d62af 100644 --- a/src/Components/Form/FormFields/RadioFormField.tsx +++ b/src/Components/Form/FormFields/RadioFormField.tsx @@ -5,13 +5,14 @@ type Props<T> = FormFieldBaseProps<string> & { options: T[]; optionDisplay: (option: T) => React.ReactNode; optionValue: (option: T) => string; + containerClassName?: string; }; const RadioFormField = <T,>(props: Props<T>) => { const field = useFormFieldPropsResolver(props); return ( <FormField field={field}> - <div className="flex gap-4 p-4"> + <div className={props.containerClassName || "flex gap-4 p-4"}> {props.options.map((option, idx) => { const value = props.optionValue(option); const optionId = `${props.name}-${idx}`; diff --git a/src/Components/Patient/DailyRoundListDetails.tsx b/src/Components/Patient/DailyRoundListDetails.tsx index 9ac68a20ca7..8f313c0a51d 100644 --- a/src/Components/Patient/DailyRoundListDetails.tsx +++ b/src/Components/Patient/DailyRoundListDetails.tsx @@ -1,6 +1,10 @@ import { lazy, useCallback, useState } from "react"; import { useDispatch } from "react-redux"; -import { CURRENT_HEALTH_CHANGE, SYMPTOM_CHOICES } from "../../Common/constants"; +import { + CONSCIOUSNESS_LEVEL, + CURRENT_HEALTH_CHANGE, + SYMPTOM_CHOICES, +} from "../../Common/constants"; import { statusType, useAbortableEffect } from "../../Common/utils"; import { getConsultationDailyRoundsDetails } from "../../Redux/actions"; import { DailyRoundsModel } from "./models"; @@ -183,6 +187,16 @@ export const DailyRoundListDetails = (props: any) => { </span> {dailyRoundListDetailsData.rhythm_detail ?? "-"} </div> + <div className="md:col-span-2"> + <span className="font-semibold leading-relaxed"> + Level Of Consciousness:{" "} + </span> + {dailyRoundListDetailsData.consciousness_level + ? CONSCIOUSNESS_LEVEL.find( + (i) => i.id === dailyRoundListDetailsData.consciousness_level + )?.text + : "-"} + </div> <div> <span className="font-semibold leading-relaxed"> Recommend Discharge:{" "} diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index a9a31f2aa14..2b8bec1a75f 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -4,6 +4,7 @@ import dayjs from "dayjs"; import { lazy, useCallback, useEffect, useState } from "react"; import { useDispatch } from "react-redux"; import { + CONSCIOUSNESS_LEVEL, PATIENT_CATEGORIES, REVIEW_AT_CHOICES, RHYTHM_CHOICES, @@ -35,6 +36,7 @@ import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; import PatientCategorySelect from "./PatientCategorySelect"; +import RadioFormField from "../Form/FormFields/RadioFormField"; const Loading = lazy(() => import("../Common/Loading")); const initForm: any = { @@ -59,6 +61,7 @@ const initForm: any = { rhythm: "0", rhythm_detail: "", ventilator_spo2: null, + consciousness_level: "Unknown", // bed: null, }; @@ -129,6 +132,7 @@ export const DailyRounds = (props: any) => { "ventilator_spo2", "rhythm", "rhythm_detail", + "consciousness_level", ]; useEffect(() => { @@ -312,6 +316,7 @@ export const DailyRounds = (props: any) => { rhythm: Number(state.form.rhythm) || 0, rhythm_detail: state.form.rhythm_detail, ventilator_spo2: state.form.ventilator_spo2, + consciousness_level: state.form.consciousness_level, }; } } else { @@ -637,9 +642,21 @@ export const DailyRounds = (props: any) => { <TextAreaFormField {...field("rhythm_detail")} - className="md:col-span-2" + className="md:col-span-1" label="Rhythm Description" - rows={5} + rows={7} + /> + + <RadioFormField + label="Level Of Consciousness" + {...field("consciousness_level")} + options={CONSCIOUSNESS_LEVEL.map((level) => ({ + label: level.text, + value: level.id, + }))} + optionDisplay={(option) => option.label} + optionValue={(option) => option.value} + containerClassName="grid gap-1 grid-cols-1" /> </> )} From 8197f29b3109b653d09cd131614ebd7f442becdb Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:57:03 +0530 Subject: [PATCH 14/20] Fix empty state in LocationManagement and BedManagement (#6937) * Fix empty state in LocationManagement * Refactor BedManagement component --- src/CAREUI/misc/PaginatedList.tsx | 2 +- src/Components/Facility/BedManagement.tsx | 24 +++++++++---------- .../Facility/LocationManagement.tsx | 14 +++++------ 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx index 61c67f97ae2..1487d69e4fa 100644 --- a/src/CAREUI/misc/PaginatedList.tsx +++ b/src/CAREUI/misc/PaginatedList.tsx @@ -130,7 +130,7 @@ interface ItemsProps<TItem> { const Items = <TItem extends object>(props: ItemsProps<TItem>) => { const { loading, items } = useContextualized<TItem>(); - if (loading) { + if (loading || items.length === 0) { return null; } diff --git a/src/Components/Facility/BedManagement.tsx b/src/Components/Facility/BedManagement.tsx index 68e9fade43d..d8799f1fbc5 100644 --- a/src/Components/Facility/BedManagement.tsx +++ b/src/Components/Facility/BedManagement.tsx @@ -182,24 +182,22 @@ export const BedManagement = (props: BedManagementProps) => { )); } else if (data?.results.length === 0) { BedList = ( - <p className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500"> + <p className="flex w-full justify-center bg-white p-5 text-center text-2xl font-bold text-gray-500"> No beds available in this location </p> ); } - if (data?.results.length) { - bed = ( - <> - <div className="mt-5 flex grow flex-wrap bg-white p-4">{BedList}</div> - {data.count && ( - <div className="mt-4 flex w-full justify-center"> - <Pagination totalCount={data.count} /> - </div> - )} - </> - ); - } + bed = ( + <> + <div className="mt-5 flex grow flex-wrap bg-white p-4">{BedList}</div> + {Boolean(data?.count && data.count > 0) && ( + <div className="mt-4 flex w-full justify-center"> + <Pagination totalCount={data?.count ?? 0} /> + </div> + )} + </> + ); if (loading) { return <Loading />; diff --git a/src/Components/Facility/LocationManagement.tsx b/src/Components/Facility/LocationManagement.tsx index b7a758c5055..93a08794c82 100644 --- a/src/Components/Facility/LocationManagement.tsx +++ b/src/Components/Facility/LocationManagement.tsx @@ -46,14 +46,14 @@ export default function LocationManagement({ facilityId }: Props) { Add New Location </ButtonV2> </div> - <PaginatedList.WhenEmpty className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500"> - <span>No locations available</span> - </PaginatedList.WhenEmpty> - - <PaginatedList.WhenLoading> - <Loading /> - </PaginatedList.WhenLoading> <div className="w-full @container"> + <PaginatedList.WhenEmpty className="flex w-full justify-center border-b border-gray-200 bg-white p-5 text-center text-2xl font-bold text-gray-500"> + <span>No locations available</span> + </PaginatedList.WhenEmpty> + + <PaginatedList.WhenLoading> + <Loading /> + </PaginatedList.WhenLoading> <PaginatedList.Items<LocationModel> className="my-8 grid gap-3 @4xl:grid-cols-2 @6xl:grid-cols-3 @[100rem]:grid-cols-4 lg:mx-8"> {(item) => <Location {...item} />} </PaginatedList.Items> From 096905d645fa4c0b4fef9e14537961f1639e1502 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad <mail@rithviknishad.dev> Date: Fri, 29 Dec 2023 23:20:27 +0530 Subject: [PATCH 15/20] fixes not found errors in investigation and other type errors (#6948) --- src/Components/Facility/ConsultationDetails/index.tsx | 4 ++++ src/Components/Facility/models.tsx | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index abc7305a00b..c6cc9e02275 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -46,6 +46,8 @@ const symptomChoices = [...SYMPTOM_CHOICES]; export interface ConsultationTabProps { consultationId: string; + facilityId: string; + patientId: string; consultationData: ConsultationModel; patientData: PatientModel; } @@ -189,6 +191,8 @@ export const ConsultationDetails = (props: any) => { const consultationTabProps: ConsultationTabProps = { consultationId, consultationData, + patientId: consultationData.patient, + facilityId: consultationData.facility, patientData, }; diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 90191e3321f..c3ac910970c 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -109,12 +109,12 @@ export interface ConsultationModel { discharge_notes?: string; examination_details?: string; history_of_present_illness?: string; - facility?: number; + facility: string; facility_name?: string; id: string; modified_date?: string; other_symptoms?: string; - patient?: string; + patient: string; treatment_plan?: string; referred_to?: FacilityModel["id"]; referred_to_object?: FacilityModel; From 06a2afdbc875ab63e691900c23f039e8d64cc759 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Fri, 29 Dec 2023 23:20:57 +0530 Subject: [PATCH 16/20] Update consciousness level constants (#6947) --- src/Common/constants.tsx | 2 +- src/Components/Patient/DailyRounds.tsx | 103 +++++++++++-------------- 2 files changed, 45 insertions(+), 60 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index f472e8b193c..647c77ecf57 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -307,7 +307,7 @@ export const CONSCIOUSNESS_LEVEL = [ { id: "ALERT", text: "Alert" }, { id: "AGITATED_OR_CONFUSED", text: "Agitated or Confused" }, { - id: "Onset of Agitation and Confusion", + id: "ONSET_OF_AGITATION_AND_CONFUSION", text: "Onset of Agitation and Confusion", }, { id: "UNKNOWN", text: "Unknown" }, diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index 2b8bec1a75f..4def8ed74eb 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -1,7 +1,7 @@ import { navigate } from "raviger"; import dayjs from "dayjs"; -import { lazy, useCallback, useEffect, useState } from "react"; +import { lazy, useCallback, useState } from "react"; import { useDispatch } from "react-redux"; import { CONSCIOUSNESS_LEVEL, @@ -61,7 +61,7 @@ const initForm: any = { rhythm: "0", rhythm_detail: "", ventilator_spo2: null, - consciousness_level: "Unknown", + consciousness_level: "UNKNOWN", // bed: null, }; @@ -135,40 +135,6 @@ export const DailyRounds = (props: any) => { "consciousness_level", ]; - useEffect(() => { - (async () => { - if (patientId) { - const res = await dispatchAction(getPatient({ id: patientId })); - if (res.data) { - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); - setConsultationSuggestion(res.data.last_consultation?.suggestion); - setPreviousReviewInterval( - Number(res.data.last_consultation.review_interval) - ); - const getAction = - TELEMEDICINE_ACTIONS.find((action) => action.id === res.data.action) - ?.text || "NO_ACTION"; - setPreviousAction(getAction); - setInitialData({ - ...initialData, - action: getAction, - }); - dispatch({ - type: "set_form", - form: { - ...state.form, - action: getAction, - }, - }); - } - } else { - setPatientName(""); - setFacilityName(""); - } - })(); - }, [dispatchAction, patientId]); - const fetchRoundDetails = useCallback( async (status: statusType) => { setIsLoading(true); @@ -176,6 +142,8 @@ export const DailyRounds = (props: any) => { getConsultationDailyRoundsDetails({ consultationId, id }) ); + let formData: any = {}; + if (!status.aborted) { if (res?.data) { const data = { @@ -191,34 +159,41 @@ export const DailyRounds = (props: any) => { "0", admitted_to: res.data.admitted_to ? res.data.admitted_to : "Select", }; - dispatch({ type: "set_form", form: data }); - setInitialData(data); + formData = { ...formData, ...data }; } setIsLoading(false); } - }, - [consultationId, id, dispatchAction] - ); - useAbortableEffect( - (status: statusType) => { - if (id) { - fetchRoundDetails(status); + if (patientId) { + const res = await dispatchAction(getPatient({ id: patientId })); + if (res.data) { + setPatientName(res.data.name); + setFacilityName(res.data.facility_object.name); + setConsultationSuggestion(res.data.last_consultation?.suggestion); + setPreviousReviewInterval( + Number(res.data.last_consultation.review_interval) + ); + const getAction = + TELEMEDICINE_ACTIONS.find((action) => action.id === res.data.action) + ?.text || "NO_ACTION"; + setPreviousAction(getAction); + setInitialData({ + ...initialData, + action: getAction, + }); + formData = { ...formData, ...{ action: getAction } }; + } + } else { + setPatientName(""); + setFacilityName(""); } - }, - [dispatchAction, fetchRoundDetails] - ); - - useEffect(() => { - (async () => { if (consultationId && !id) { const res = await dispatchAction( getDailyReport({ limit: 1, offset: 0 }, { consultationId }) ); setHasPreviousLog(res.data.count > 0); - dispatch({ - type: "set_form", - form: { - ...state.form, + formData = { + ...formData, + ...{ patient_category: res.data.patient_category ? PATIENT_CATEGORIES.find( (i) => i.text === res.data.patient_category @@ -229,12 +204,22 @@ export const DailyRounds = (props: any) => { RHYTHM_CHOICES.find((i) => i.text === res.data.rhythm)?.id) || "0", temperature: parseFloat(res.data.temperature), - // clone_last: res.data.count > 0 ? true : false, }, - }); + }; + } + dispatch({ type: "set_form", form: formData }); + setInitialData(formData); + }, + [consultationId, id, dispatchAction, patientId] + ); + useAbortableEffect( + (status: statusType) => { + if (id) { + fetchRoundDetails(status); } - })(); - }, [dispatchAction, consultationId, id]); + }, + [dispatchAction, fetchRoundDetails] + ); const validateForm = () => { const errors = { ...initError }; From 1085e0f083125a466170df5470eb3ad33d2e4285 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad <mail@rithviknishad.dev> Date: Fri, 29 Dec 2023 23:47:55 +0530 Subject: [PATCH 17/20] fix resp. rate from being 0 instead of null when unset (#6949) * fix resp. rate from being 0 instead of null * fix relative time alignment --- src/CAREUI/display/RecordMeta.tsx | 8 +++++--- src/Components/Patient/DailyRounds.tsx | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/CAREUI/display/RecordMeta.tsx b/src/CAREUI/display/RecordMeta.tsx index d4d32e437c8..5e1e117f9d6 100644 --- a/src/CAREUI/display/RecordMeta.tsx +++ b/src/CAREUI/display/RecordMeta.tsx @@ -1,7 +1,8 @@ import CareIcon from "../icons/CareIcon"; import { - formatDateTime, + formatDate, formatName, + formatTime, isUserOnline, relativeTime, } from "../../Utils/utils"; @@ -37,8 +38,9 @@ const RecordMeta = ({ let child = ( <div className="tooltip"> <span className="underline">{relativeTime(time)}</span> - <span className="tooltip-text tooltip-left flex gap-1 text-xs font-medium tracking-wider"> - {formatDateTime(time)} + <span className="tooltip-text tooltip-bottom flex -translate-x-1/2 gap-1 text-xs font-medium tracking-wider"> + {formatTime(time)} <br /> + {formatDate(time)} {user && !inlineUser && ( <span className="flex items-center gap-1"> by diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index 4def8ed74eb..06431457b0e 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -296,9 +296,9 @@ export const DailyRounds = (props: any) => { } : undefined, pulse: state.form.pulse, - resp: Number(state.form.resp), + resp: state.form.resp, temperature: state.form.temperature, - rhythm: Number(state.form.rhythm) || 0, + rhythm: state.form.rhythm || 0, rhythm_detail: state.form.rhythm_detail, ventilator_spo2: state.form.ventilator_spo2, consciousness_level: state.form.consciousness_level, From 8aea0cc9139c507b83784986d9d2c4eec7a7baf6 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad <mail@rithviknishad.dev> Date: Sat, 30 Dec 2023 15:41:36 +0530 Subject: [PATCH 18/20] fix has previous log update not being filled (#6951) --- src/Components/Patient/DailyRounds.tsx | 51 +++++++++++++------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index 06431457b0e..ec2f070aa20 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -138,31 +138,34 @@ export const DailyRounds = (props: any) => { const fetchRoundDetails = useCallback( async (status: statusType) => { setIsLoading(true); - const res = await dispatchAction( - getConsultationDailyRoundsDetails({ consultationId, id }) - ); - - let formData: any = {}; + let formData: any = initialData; + if (id) { + const res = await dispatchAction( + getConsultationDailyRoundsDetails({ consultationId, id }) + ); - if (!status.aborted) { - if (res?.data) { - const data = { - ...res.data, - patient_category: res.data.patient_category - ? PATIENT_CATEGORIES.find( - (i) => i.text === res.data.patient_category - )?.id ?? "" - : "", - rhythm: - (res.data.rhythm && - RHYTHM_CHOICES.find((i) => i.text === res.data.rhythm)?.id) || - "0", - admitted_to: res.data.admitted_to ? res.data.admitted_to : "Select", - }; - formData = { ...formData, ...data }; + if (!status.aborted) { + if (res?.data) { + const data = { + ...res.data, + patient_category: res.data.patient_category + ? PATIENT_CATEGORIES.find( + (i) => i.text === res.data.patient_category + )?.id ?? "" + : "", + rhythm: + (res.data.rhythm && + RHYTHM_CHOICES.find((i) => i.text === res.data.rhythm)?.id) || + "0", + admitted_to: res.data.admitted_to + ? res.data.admitted_to + : "Select", + }; + formData = { ...formData, ...data }; + } } - setIsLoading(false); } + setIsLoading(false); if (patientId) { const res = await dispatchAction(getPatient({ id: patientId })); if (res.data) { @@ -214,9 +217,7 @@ export const DailyRounds = (props: any) => { ); useAbortableEffect( (status: statusType) => { - if (id) { - fetchRoundDetails(status); - } + fetchRoundDetails(status); }, [dispatchAction, fetchRoundDetails] ); From c5c86aa594e11e832b18cc6fa906ce04d7e041f7 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad <mail@rithviknishad.dev> Date: Sat, 30 Dec 2023 22:41:29 +0530 Subject: [PATCH 19/20] fix daily rounds vital fields not clearing (#6954) * fix daily rounds vital fields not clearing * Fix blood pressure form field and daily rounds component * Fix default blood pressure values in DailyRounds component * Fix handling of negative values in blood pressure --------- Co-authored-by: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> --- .../Common/BloodPressureFormField.tsx | 9 ++-- .../Facility/Consultations/Mews.tsx | 1 + .../Patient/DailyRoundListDetails.tsx | 10 +++- src/Components/Patient/DailyRounds.tsx | 50 +++++++++++++++---- 4 files changed, 53 insertions(+), 17 deletions(-) diff --git a/src/Components/Common/BloodPressureFormField.tsx b/src/Components/Common/BloodPressureFormField.tsx index ed0557ae8e3..3ff2774b900 100644 --- a/src/Components/Common/BloodPressureFormField.tsx +++ b/src/Components/Common/BloodPressureFormField.tsx @@ -21,7 +21,7 @@ export default function BloodPressureFormField(props: Props) { name: field.name, value: { ...field.value, - [event.name]: event.value, + [event.name]: event.value ?? -1, }, }); }; @@ -35,9 +35,10 @@ export default function BloodPressureFormField(props: Props) { <FormField field={{ ...field, - labelSuffix: map ? ( - <span className="font-medium">MAP: {map.toFixed(1)}</span> - ) : undefined, + labelSuffix: + map && map !== -1 ? ( + <span className="font-medium">MAP: {map.toFixed(1)}</span> + ) : undefined, }} > <div className="flex flex-row items-center"> diff --git a/src/Components/Facility/Consultations/Mews.tsx b/src/Components/Facility/Consultations/Mews.tsx index 6b8e7de806e..5160e42f9f2 100644 --- a/src/Components/Facility/Consultations/Mews.tsx +++ b/src/Components/Facility/Consultations/Mews.tsx @@ -26,6 +26,7 @@ const getHeartRateScore = (value?: number) => { const getSystolicBPScore = (value?: number) => { if (typeof value !== "number") return; + if (value === -1) return; if (value <= 70) return 3; if (value <= 80) return 2; diff --git a/src/Components/Patient/DailyRoundListDetails.tsx b/src/Components/Patient/DailyRoundListDetails.tsx index 8f313c0a51d..0a73607d688 100644 --- a/src/Components/Patient/DailyRoundListDetails.tsx +++ b/src/Components/Patient/DailyRoundListDetails.tsx @@ -158,14 +158,20 @@ export const DailyRoundListDetails = (props: any) => { <span className="font-semibold leading-relaxed"> Systolic:{" "} </span> - {dailyRoundListDetailsData.bp?.systolic ?? "-"} + {dailyRoundListDetailsData.bp?.systolic && + dailyRoundListDetailsData.bp?.systolic !== -1 + ? dailyRoundListDetailsData.bp?.systolic + : "-"} </div> <div className="flex"> {" "} <span className="font-semibold leading-relaxed"> Diastolic: </span> - {dailyRoundListDetailsData.bp?.diastolic ?? "-"} + {dailyRoundListDetailsData.bp?.diastolic && + dailyRoundListDetailsData.bp?.diastolic !== -1 + ? dailyRoundListDetailsData.bp?.diastolic + : "-"} </div> </div> </div> diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index ec2f070aa20..c12aea42bee 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -62,6 +62,11 @@ const initForm: any = { rhythm_detail: "", ventilator_spo2: null, consciousness_level: "UNKNOWN", + bp: { + systolic: -1, + diastolic: -1, + mean: -1, + }, // bed: null, }; @@ -242,6 +247,18 @@ export const DailyRounds = (props: any) => { invalidForm = true; } return; + case "bp": + if ( + (state.form.bp?.systolic && + state.form.bp?.diastolic && + state.form.bp.systolic !== -1 && + state.form.bp.diastolic === -1) || + (state.form.bp.systolic === -1 && state.form.bp.diastolic !== -1) + ) { + errors.bp = "Please enter both systolic and diastolic values"; + invalidForm = true; + } + return; default: return; } @@ -287,21 +304,32 @@ export const DailyRounds = (props: any) => { data = { ...data, bp: - state.form.bp && state.form.bp.systolic && state.form.bp.diastolic + state.form.bp?.systolic !== -1 && state.form.bp?.diastolic !== -1 ? { - systolic: Number(state.form.bp.systolic), - diastolic: Number(state.form.bp.diastolic), - mean: parseFloat( - meanArterialPressure(state.form.bp).toFixed(2) - ), + systolic: state.form.bp?.systolic + ? Number(state.form.bp?.systolic) + : -1, + diastolic: state.form.bp?.diastolic + ? Number(state.form.bp?.diastolic) + : -1, + mean: + state.form.bp?.systolic && state.form.bp?.diastolic + ? parseFloat( + meanArterialPressure(state.form.bp).toFixed(2) + ) + : -1, } - : undefined, - pulse: state.form.pulse, - resp: state.form.resp, - temperature: state.form.temperature, + : { + systolic: -1, + diastolic: -1, + mean: -1, + }, + pulse: state.form.pulse ?? null, + resp: state.form.resp ?? null, + temperature: state.form.temperature ?? null, rhythm: state.form.rhythm || 0, rhythm_detail: state.form.rhythm_detail, - ventilator_spo2: state.form.ventilator_spo2, + ventilator_spo2: state.form.ventilator_spo2 ?? null, consciousness_level: state.form.consciousness_level, }; } From f066a3659beb2ba899a226a465b06922b2207d69 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Sat, 30 Dec 2023 22:57:23 +0530 Subject: [PATCH 20/20] Fix conditional logic in PatientInfoCard (#6955) --- src/Components/Patient/PatientInfoCard.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index c5087f13dda..eb589667ece 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -490,6 +490,7 @@ export default function PatientInfoCard(props: { key={i} className="dropdown-item-primary pointer-events-auto m-2 flex cursor-pointer items-center justify-start gap-2 rounded border-0 p-2 text-sm font-normal transition-all duration-200 ease-in-out" href={ + action[1] !== "Treatment Summary" && consultation?.admitted && !consultation?.current_bed && i === 1 @@ -498,6 +499,7 @@ export default function PatientInfoCard(props: { } onClick={() => { if ( + action[1] !== "Treatment Summary" && consultation?.admitted && !consultation?.current_bed && i === 1