From a5a61ca250c3ba3a2957b8f0af199f5ae8e2ac4b Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 26 Dec 2023 11:45:25 +0530 Subject: [PATCH 01/23] Revert "Disable critical care submit if onle basic editor is filled (#6860)" (#6916) This reverts commit 7c21b80c87f00eb50c81dda5cbf58537ea226ca8. --- .../Recording/CriticalCare__Recording.res | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res b/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res index b0ff4fd04cc..0316430dcd3 100644 --- a/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res +++ b/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res @@ -237,9 +237,7 @@ let make = (~id, ~facilityId, ~patientId, ~consultationId, ~dailyRound) => { From ce066ceb480dc3fb7c2579f92ff44c3eab84942b Mon Sep 17 00:00:00 2001 From: Shyam Prakash <106866225+shyamprakash123@users.noreply.github.com> Date: Wed, 27 Dec 2023 09:04:27 +0530 Subject: [PATCH 02/23] Hide Camera Feed button for patient with no bed assigned. (#6868) * Hide camera feed for no bed * updated type of asset --- .../Facility/ConsultationDetails/index.tsx | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index c0af068d66c..c438fa3fca7 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -38,6 +38,7 @@ import { ConsultationNeurologicalMonitoringTab } from "./ConsultationNeurologica import { ConsultationNutritionTab } from "./ConsultationNutritionTab"; import PatientNotesSlideover from "../PatientNotesSlideover"; import LegacyDiagnosesList from "../../Diagnosis/LegacyDiagnosesList"; +import { AssetBedModel } from "../../Assets/AssetTypes"; const Loading = lazy(() => import("../../Common/Loading")); const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -120,16 +121,19 @@ export const ConsultationDetails = (props: any) => { data.symptoms_text = symptoms.join(", "); } setConsultationData(data); - const assetRes = await dispatch( - listAssetBeds({ - bed: data?.current_bed?.bed_object?.id, - }) - ); - const isCameraAttachedRes = assetRes.data.results.some( - (asset: { asset_object: { asset_class: string } }) => { - return asset?.asset_object?.asset_class === "ONVIF"; - } - ); + const assetRes = data?.current_bed?.bed_object?.id + ? await dispatch( + listAssetBeds({ + bed: data?.current_bed?.bed_object?.id, + }) + ) + : null; + const isCameraAttachedRes = + assetRes != null + ? assetRes.data.results.some((asset: AssetBedModel) => { + return asset?.asset_object?.asset_class === "ONVIF"; + }) + : false; setIsCameraAttached(isCameraAttachedRes); const id = res.data.patient; const patientRes = await dispatch(getPatient({ id })); From 279a67d1574d33a72e921f2fab87752fe095924e Mon Sep 17 00:00:00 2001 From: khavinshankar Date: Wed, 27 Dec 2023 13:32:55 +0530 Subject: [PATCH 03/23] fix deploy workflow --- .github/workflows/deploy.yaml | 77 +++++++++++++++++++---------------- 1 file changed, 42 insertions(+), 35 deletions(-) diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml index b90adb20138..6e760362cd6 100644 --- a/.github/workflows/deploy.yaml +++ b/.github/workflows/deploy.yaml @@ -188,9 +188,6 @@ jobs: needs: build-staging name: Deploy to staging GCP cluster runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Staging-GCP url: https://care-staging.ohc.network/ @@ -202,15 +199,19 @@ jobs: token: ${{ secrets.GIT_ACCESS_TOKEN }} path: kube ref: main + + - uses: actions/setup-node@v3 + with: + node-version: '20' # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} @@ -233,9 +234,6 @@ jobs: needs: build-production name: Deploy to GKE Manipur runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Production-Manipur url: https://care.mn.gov.in @@ -248,14 +246,18 @@ jobs: path: kube ref: main + - uses: actions/setup-node@v3 + with: + node-version: '20' + # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} @@ -278,9 +280,6 @@ jobs: needs: build-production name: Deploy to GKE Karnataka runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Production-Karnataka url: https://karnataka.care @@ -293,14 +292,18 @@ jobs: path: kube ref: main + - uses: actions/setup-node@v3 + with: + node-version: '20' + # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} @@ -323,9 +326,6 @@ jobs: needs: build-production name: Deploy to GKE Sikkim runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Production-Sikkim url: https://care.sikkim.gov.in @@ -338,14 +338,18 @@ jobs: path: kube ref: main + - uses: actions/setup-node@v3 + with: + node-version: '20' + # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} @@ -368,9 +372,6 @@ jobs: needs: build-production name: Deploy to GKE Assam runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Production-Assam url: https://care.assam.gov.in @@ -383,14 +384,18 @@ jobs: path: kube ref: main + - uses: actions/setup-node@v3 + with: + node-version: '20' + # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} @@ -413,9 +418,6 @@ jobs: needs: build-production name: Deploy to GKE Nagaland runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Production - Nagaland url: https://care.nagaland.gov.in @@ -427,15 +429,19 @@ jobs: token: ${{ secrets.GIT_ACCESS_TOKEN }} path: kube ref: main + + - uses: actions/setup-node@v3 + with: + node-version: '20' # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials, so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} @@ -458,9 +464,6 @@ jobs: needs: build-production name: Deploy to GKE Meghalaya runs-on: ubuntu-latest - - uses: actions/setup-node@v3 - with: - node-version: '20' environment: name: Production-Meghalaya url: https://care.meghealth.gov.in @@ -472,15 +475,19 @@ jobs: token: ${{ secrets.GIT_ACCESS_TOKEN }} path: kube ref: main + + - uses: actions/setup-node@v3 + with: + node-version: '20' # Setup gcloud CLI - - uses: google-github-actions/setup-gcloud@v2 + - uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 with: service_account_key: ${{ secrets.GKE_SA_KEY }} project_id: ${{ secrets.GKE_PROJECT }} # Get the GKE credentials, so we can deploy to the cluster - - uses: google-github-actions/get-gke-credentials@v2 + - uses: google-github-actions/get-gke-credentials@fb08709ba27618c31c09e014e1d8364b02e5042e with: cluster_name: ${{ secrets.GKE_CLUSTER }} location: ${{ secrets.GKE_ZONE }} From 6b1ebc66c3b6fde4c1ae6045974decff80b49a30 Mon Sep 17 00:00:00 2001 From: Gokulram A Date: Thu, 28 Dec 2023 06:35:29 +0530 Subject: [PATCH 04/23] 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 ( { errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} /> { + if (focused) { + onAddNote(); + } + }, + { + ignoreInputFields: false, + } + ); + const notesActionIcons = (
{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)} /> Date: Thu, 28 Dec 2023 06:35:53 +0530 Subject: [PATCH 05/23] 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 06/23] 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 ( ), + "/consultation/:consultationId": ({ consultationId }: any) => ( + + ), "/facility/:facilityId/patient/:patientId/consultation/:consultationId/treatment-summary": ({ facilityId, patientId, consultationId }: any) => ( Date: Thu, 28 Dec 2023 06:38:25 +0530 Subject: [PATCH 07/23] Timeline style view for Daily Rounds (#6808) * make template timeline design * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad * Update src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx Co-authored-by: Rithvik Nishad * Update src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx Co-authored-by: Rithvik Nishad * Update src/Components/Facility/Consultations/DailyRoundsList.tsx Co-authored-by: Rithvik Nishad * 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 Co-authored-by: Aakash Singh --- 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 = (
{relativeTime(time)} - + {formatDateTime(time)} {user && !inlineUser && ( 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 || ( -

- {props.event.by && ( - - {formatName(props.event.by)}{" "} - - )} - {props.titleSuffix - ? props.titleSuffix - : `${props.event.type} the ${props.name || name}.`} -

- {props.actions && ( - {props.actions} - )} - +
+

+ {props.event.by && ( + + {formatName(props.event.by)}{" "} + {props.event.by.user_type && + `(${props.event.by.user_type}) `} + + )} + {props.titleSuffix + ? props.titleSuffix + : `${props.event.type} the ${props.name || name}.`} +

+
+ {props.actions && ( + {props.actions} + )} + +
+
)}
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 { const Items = (props: ItemsProps) => { const { loading, items } = useContextualized(); + if (loading) { + return null; + } + return ( -
    - {loading && props.shimmer - ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( -
  • - {props.shimmer} -
  • - )) - : items.map((item, index, items) => ( -
  • - {props.children(item, items)} -
  • - ))} -
+ +
    + {loading && props.shimmer + ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( +
  • + {props.shimmer} +
  • + )) + : items.map((item, index, items) => ( +
  • + {props.children(item, items)} +
  • + ))} +
+
); }; @@ -153,8 +160,16 @@ interface PaginatorProps { hideIfSinglePage?: boolean; } -const Paginator = ({ className, hideIfSinglePage }: PaginatorProps) => { +const Paginator = ({ + className, + hideIfSinglePage, +}: PaginatorProps) => { const { data, perPage, currentPage, setPage } = useContextualized(); + const { loading } = useContextualized(); + + 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 ( -
-
-
-
- -
- - {`${by?.first_name} ${by?.last_name}`} - - ({by?.user_type}) - - -
- - {t("created")} - -
-
- {!telemedicine_doctor_update && round?.last_edited_by && ( - - )} - - - - - -
- - - {t("view_details")} - - {props.onUpdateLog && ( - - - {t("update_log")} - - )} -
+
+ + + + +
+ + + {t("view_details")} + + + + {t("update_log")} +
); 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 ( -
-
-
-
- -
- - {t("virtual_nursing_assistant")} - -
- - {t("created")} - -
+
{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) { - <> - {Array.from({ length: 3 }).map((_, i) => ( - - ))} - + className="flex grow flex-col gap-3"> {(item, items) => { if (item.rounds_type === "AUTOMATED") { return ( - + + + ); } @@ -65,12 +80,30 @@ export default function DailyRoundsList({ consultation }: Props) { : `${consultationUrl}/daily_rounds/${item.id}`; return ( - navigate(itemUrl)} - onUpdateLog={() => navigate(`${itemUrl}/update`)} - /> + + navigate(itemUrl)} + onUpdateLog={() => navigate(`${itemUrl}/update`)} + /> + ); }} 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 08/23] 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} {
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 09/23] 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(); 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: { /> 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 10/23] 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( @@ -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([]); - const [districts, setDistricts] = useState([]); - const [localBodies, setLocalBodies] = useState([]); - const [ward, setWard] = useState([]); 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>([]); const [bedCapacityKey, setBedCapacityKey] = useState(0); const [docCapacityKey, setDocCapacityKey] = useState(0); + const [stateId, setStateId] = useState(); + const [districtId, setDistrictId] = useState(); + const [localBodyId, setLocalBodyId] = useState(); 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) => { @@ -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 = + 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) => { { 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); }} /> { 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); }} /> { 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); }} /> { 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>([]); 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) => ( { )); } - if (isLoading || !data) { + if (isLoading || !permittedData) { manageFacilities = ; - } else if (data && data.length) { + } else if (permittedData.results && permittedData.results.length) { manageFacilities = ( <>
{facilityList}
- + ); - } else if (data && data.length === 0) { + } else if (permittedData.results && permittedData.results.length === 0) { manageFacilities = hasFiltersApplied(qParams) ? (
@@ -246,7 +173,7 @@ export const HospitalList = () => {
{ [ 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; } +// Voluntarily made as `type` for it to achieve type-safety when used with +// `useAsyncOptions` +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` -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 & { + 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(), + TBody: Type(), }, getPermittedFacility: { path: "/api/v1/facility/{id}/", method: "GET", - TRes: Type(), + TRes: Type(), }, getAnyFacility: { @@ -301,8 +305,10 @@ const routes = { }, updateFacility: { - path: "/api/v1/facility", + path: "/api/v1/facility/{id}/", method: "PUT", + TRes: Type(), + TBody: Type(), }, partialUpdateFacility: { @@ -680,6 +686,7 @@ const routes = { getState: { path: "/api/v1/state/{id}/", + TRes: Type(), }, // Districts @@ -711,6 +718,7 @@ const routes = { // Local Body getLocalBody: { path: "/api/v1/local_body/{id}/", + TRes: Type(), }, 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( : 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 11/23] 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(); 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 ; + if (isLoading) return ; return (
@@ -94,23 +83,21 @@ const HL7Monitor = (props: HL7MonitorProps) => { label={

Middleware Hostname

- {!middlewareHostname && ( + {resolvedMiddleware?.source != "asset" && (
- Middleware hostname sourced from{" "} - {asset?.location_object?.middleware_address - ? "asset location" - : "asset facility"} + Middleware hostname sourced from asset{" "} + {resolvedMiddleware?.source}
)}
} - placeholder={fallbackMiddleware} + placeholder={resolvedMiddleware?.hostname} value={middlewareHostname} onChange={(e) => setMiddlewareHostname(e.value)} errorClassName="hidden" @@ -140,16 +127,12 @@ const HL7Monitor = (props: HL7MonitorProps) => { {assetType === "HL7MONITOR" && ( )} {assetType === "VENTILATOR" && ( )}
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(); 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={

Middleware Hostname

- {!middlewareHostname && ( + {resolvedMiddleware?.source != "asset" && (
- Middleware hostname sourced from{" "} - {asset?.location_object?.middleware_address - ? "asset location" - : "asset facility"} + Middleware hostname sourced from asset{" "} + {resolvedMiddleware?.source}
)}
} - 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}
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(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 ( {data.results.map((asset) => (
- +
))}
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) => { /> ) : (