diff --git a/cypress/e2e/facility_spec/locations.cy.ts b/cypress/e2e/facility_spec/locations.cy.ts index d6377ba97c3..d39464ce14b 100644 --- a/cypress/e2e/facility_spec/locations.cy.ts +++ b/cypress/e2e/facility_spec/locations.cy.ts @@ -6,7 +6,6 @@ import FacilityLocation from "../../pageobject/Facility/FacilityLocation"; import { AssetPagination } from "../../pageobject/Asset/AssetPagination"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; - describe("Location Management Section", () => { const assetPage = new AssetPage(); const userCreationPage = new UserCreationPage(); diff --git a/cypress/e2e/patient_spec/patient_crud.cy.ts b/cypress/e2e/patient_spec/patient_crud.cy.ts index 06a27333b9e..66c8b35e236 100644 --- a/cypress/e2e/patient_spec/patient_crud.cy.ts +++ b/cypress/e2e/patient_spec/patient_crud.cy.ts @@ -8,7 +8,7 @@ import { emergency_phone_number, phone_number, } from "../../pageobject/constants"; -const yearOfBirth = "2023"; +const yearOfBirth = "2001"; const calculateAge = () => { const currentYear = new Date().getFullYear(); diff --git a/src/Components/CameraFeed/utils.ts b/src/Components/CameraFeed/utils.ts index e2793d76b41..fc940558aa0 100644 --- a/src/Components/CameraFeed/utils.ts +++ b/src/Components/CameraFeed/utils.ts @@ -1,5 +1,5 @@ import { MutableRefObject } from "react"; -import { AssetData } from "../Assets/AssetTypes"; +import { AssetClass, AssetData } from "../Assets/AssetTypes"; import { getCameraConfig } from "../../Utils/transformUtils"; import { isIOS } from "../../Utils/utils"; @@ -18,6 +18,10 @@ export const calculateVideoDelay = ( }; export const getStreamUrl = (asset: AssetData) => { + if (asset.asset_class !== AssetClass.ONVIF) { + throw "getStreamUrl can be invoked only for ONVIF Assets"; + } + const config = getCameraConfig(asset); const host = asset.resolved_middleware?.hostname; const uuid = config.accessKey; diff --git a/src/Components/ExternalResult/models.ts b/src/Components/ExternalResult/models.ts index 8ccaba04d05..7b136c76cef 100644 --- a/src/Components/ExternalResult/models.ts +++ b/src/Components/ExternalResult/models.ts @@ -53,6 +53,9 @@ export interface ILocalBodies { export interface IDeleteExternalResult { detail: string; } +export interface IDeleteBedCapacity { + detail: string; +} export interface IPartialUpdateExternalResult { address: string; diff --git a/src/Components/Facility/BedCapacity.tsx b/src/Components/Facility/BedCapacity.tsx index dba563e5876..9416f5981f2 100644 --- a/src/Components/Facility/BedCapacity.tsx +++ b/src/Components/Facility/BedCapacity.tsx @@ -1,11 +1,4 @@ -import { useCallback, useEffect, useReducer, useState } from "react"; -import { useDispatch } from "react-redux"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { - createCapacity, - listCapacity, - getCapacityBed, -} from "../../Redux/actions"; +import { useEffect, useReducer, useState } from "react"; import * as Notification from "../../Utils/Notifications.js"; import { CapacityModal, OptionsType } from "./models"; import TextFormField from "../Form/FormFields/TextFormField"; @@ -14,6 +7,8 @@ import { SelectFormField } from "../Form/FormFields/SelectFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; import useConfig from "../../Common/hooks/useConfig"; import { getBedTypes } from "../../Common/constants"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; interface BedCapacityProps extends CapacityModal { facilityId: string; @@ -55,7 +50,6 @@ const bedCountReducer = (state = initialState, action: any) => { export const BedCapacity = (props: BedCapacityProps) => { const config = useConfig(); - const dispatchAction: any = useDispatch(); const { facilityId, handleClose, handleUpdate, className, id } = props; const [state, dispatch] = useReducer(bedCountReducer, initialState); const [isLastOptionType, setIsLastOptionType] = useState(false); @@ -67,63 +61,53 @@ export const BedCapacity = (props: BedCapacityProps) => { ? `Save ${!isLastOptionType ? "& Add More" : "Bed Capacity"}` : "Update Bed Capacity"; - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - if (!id) { - // Add Form functionality - const capacityRes = await dispatchAction( - listCapacity({}, { facilityId }) - ); - if (!status.aborted) { - if (capacityRes && capacityRes.data) { - const existingData = capacityRes.data.results; - // if all options are diabled - if (existingData.length === getBedTypes(config).length) { - return; - } - // disable existing bed types - const updatedBedTypes = getBedTypes(config).map( - (type: OptionsType) => { - const isExisting = existingData.find( - (i: CapacityModal) => i.room_type === type.id - ); - return { - ...type, - disabled: !!isExisting, - }; - } - ); - setBedTypes(updatedBedTypes); - } - } - } else { - // Edit Form functionality - const res = await dispatchAction( - getCapacityBed({ facilityId: facilityId, bed_id: id }) - ); - if (res && res.data) { - dispatch({ - type: "set_form", - form: { - bedType: res.data.room_type, - totalCapacity: res.data.total_capacity, - currentOccupancy: res.data.current_capacity, - }, - }); + async function fetchCapacityBed() { + setIsLoading(true); + if (!id) { + // Add Form functionality + const capacityQuery = await request(routes.getCapacity, { + pathParams: { facilityId: props.facilityId }, + }); + if (capacityQuery?.data) { + const existingData = capacityQuery.data?.results; + // if all options are diabled + if (existingData.length === getBedTypes(config).length) { + return; } + // disable existing bed types + const updatedBedTypes = getBedTypes(config).map((type: OptionsType) => { + const isExisting = existingData.find( + (i: CapacityModal) => i.room_type === type.id + ); + return { + ...type, + disabled: !!isExisting, + }; + }); + setBedTypes(updatedBedTypes); } - setIsLoading(false); - }, - [dispatchAction, facilityId, id] - ); + } else { + // Edit Form functionality + const capacityQuery = await request(routes.getCapacityBed, { + pathParams: { facilityId: props.facilityId, bed_id: id.toString() }, + }); + if (capacityQuery.data) { + dispatch({ + type: "set_form", + form: { + bedType: capacityQuery.data.room_type, + totalCapacity: capacityQuery.data.total_capacity, + currentOccupancy: capacityQuery.data.current_capacity, + }, + }); + } + } + setIsLoading(false); + } - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [dispatch, fetchData, id] - ); + useEffect(() => { + fetchCapacityBed(); + }, []); useEffect(() => { const lastBedType = @@ -179,21 +163,24 @@ export const BedCapacity = (props: BedCapacityProps) => { const valid = validateData(); if (valid) { setIsLoading(true); - const data = { + const bodyData = { room_type: Number(state.form.bedType), total_capacity: Number(state.form.totalCapacity), current_capacity: Number(state.form.currentOccupancy), }; - const res = await dispatchAction( - createCapacity(id, data, { facilityId }) + const { data } = await request( + id ? routes.updateCapacity : routes.createCapacity, + { + pathParams: { facilityId, ...(id ? { bed_id: id.toString() } : {}) }, + body: bodyData, + } ); setIsLoading(false); - if (res && res.data) { - // disable last added bed type + if (data) { const updatedBedTypes = bedTypes.map((type: OptionsType) => { return { ...type, - disabled: res.data.room_type !== type.id ? type.disabled : true, + disabled: data.room_type !== type.id ? type.disabled : true, }; }); setBedTypes(updatedBedTypes); diff --git a/src/Components/Facility/BedTypeCard.tsx b/src/Components/Facility/BedTypeCard.tsx index 59e0661169d..67e22f31f42 100644 --- a/src/Components/Facility/BedTypeCard.tsx +++ b/src/Components/Facility/BedTypeCard.tsx @@ -1,8 +1,5 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import * as Notification from "../../Utils/Notifications"; -import { animated, config, useSpring } from "@react-spring/web"; -import { useDispatch } from "react-redux"; -import { deleteCapacity } from "../../Redux/actions"; import { BedCapacity } from "./BedCapacity"; import DialogModal from "../Common/Dialog"; import ButtonV2 from "../Common/components/ButtonV2"; @@ -10,7 +7,8 @@ import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import RecordMeta from "../../CAREUI/display/RecordMeta"; import ConfirmDialog from "../Common/ConfirmDialog"; -import { useTranslation } from "react-i18next"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; interface BedTypeCardProps { facilityId?: string; @@ -24,9 +22,6 @@ interface BedTypeCardProps { handleUpdate: () => void; } -const CIRCLE_PATH = - "M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831"; - export const BedTypeCard: React.FC = ({ facilityId, bedCapacityId, @@ -38,20 +33,19 @@ export const BedTypeCard: React.FC = ({ removeBedType, handleUpdate, }) => { - const { t } = useTranslation(); - const dispatchAction: any = useDispatch(); + const [isRefreshing, setIsRefreshing] = useState(false); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [open, setOpen] = useState(false); const [selectedId, setSelectedId] = useState(-1); const handleDeleteSubmit = async () => { if (room_type) { - const res = await dispatchAction( - deleteCapacity({ - facilityId: facilityId, - bed_id: room_type, - }) - ); - if (res && res.status == 204) { + const { res } = await request(routes.deleteCapacityBed, { + pathParams: { + facilityId: facilityId ?? "", + bed_id: room_type.toString(), + }, + }); + if (res?.status == 204) { Notification.Success({ msg: "Bed type deleted successfully", }); @@ -63,138 +57,88 @@ export const BedTypeCard: React.FC = ({ } }; - const _p = total ? Math.round((used / total) * 100) : 0; + useEffect(() => { + if (isRefreshing) { + setTimeout(() => { + setIsRefreshing(false); + }, 500); + } + }, [isRefreshing]); - const { occupied, totalCount, progress, innerProgress } = useSpring({ - from: { occupied: 0, totalCount: 0, progress: "0, 100", innerProgress: 0 }, - to: { - occupied: used, - totalCount: total, - progress: `${Number.isNaN(_p) ? 0 : _p}, 100`, - innerProgress: Number.isNaN(_p) ? 0 : _p, - }, - delay: 0, - config: config.slow, - }); + const usedPercent = total ? Math.round((used / total) * 100) : 0; return (
-
-

+

+
{label} -

+
+ + {usedPercent}% + +
+
+ {used} / {total} +
+
+
+
+
-
-
-
-
- - - - -
-
- - {innerProgress.to( - (x: number) => `${Math.round(x) || 0}%` - )} - -
- { -
- -
- } -
-
+
+ {" "} + Currently Occupied / Total Capacity{" "} +
+ {facilityId ? ( +
+
+ {lastUpdated && ( + + )}
-
-
-

- Used: - - {occupied.to((x: number) => Math.round(x))} - -

-
-
-

- Total: - - {totalCount.to((x: number) => Math.round(x))} - -

-
+
+ { + setSelectedId(room_type || 0); + setOpen(true); + }} + authorizeFor={NonReadOnlyUsers} + className="tooltip bg-opacity/20 flex aspect-square h-7 w-7 flex-col items-center justify-center rounded bg-gray-300 px-4 py-0" + variant="secondary" + ghost + > + + Edit + + + setOpenDeleteDialog(true)} + authorizeFor={NonReadOnlyUsers} + className=" tooltip bg-opacity/10 flex aspect-square h-7 w-7 flex-col items-center justify-center rounded bg-red-100 px-4 py-0 hover:bg-red-200" + variant="secondary" + ghost + > + + Delete +
- {facilityId && ( -
- {lastUpdated && ( - - )} -
- { - setSelectedId(room_type || 0); - setOpen(true); - }} - authorizeFor={NonReadOnlyUsers} - className="tooltip p-2" - variant="secondary" - ghost - > - - Edit - - setOpenDeleteDialog(true)} - authorizeFor={NonReadOnlyUsers} - className="tooltip p-2" - variant="danger" - ghost - > - - Delete - -
-
- )}
-

- No Data Available -

-
+ ) : ( +
+ )}
(); const [isFullscreen, setFullscreen] = useFullscreen(); - - const [facilityObject, setFacilityObject] = useState(); - const [data, setData] = - useState[0][]>(); - const [totalCount, setTotalCount] = useState(0); const { qParams, updateQuery, removeFilter, updatePage } = useFilters({ limit: PER_PAGE_LIMIT, }); + const query = useQuery(routes.listPatientAssetBeds, { + pathParams: { facility_external_id: facilityId }, + query: { + ...qParams, + page: qParams.page || 1, + limit: PER_PAGE_LIMIT, + offset: (qParams.page ? qParams.page - 1 : 0) * PER_PAGE_LIMIT, + asset_class: "HL7MONITOR", + ordering: qParams.ordering || "bed__name", + bed_is_occupied: qParams.bed_is_occupied ?? true, + }, + }); - useEffect(() => { - async function fetchFacilityOrObject() { - if (facilityObject) return facilityObject; - const res = await dispatch(getPermittedFacility(facilityId)); - if (res.status !== 200) return; - setFacilityObject(res.data); - return res.data as FacilityModel; - } - - async function fetchData() { - setData(undefined); - - const filters = { - ...qParams, - page: qParams.page || 1, - limit: PER_PAGE_LIMIT, - offset: (qParams.page ? qParams.page - 1 : 0) * PER_PAGE_LIMIT, - asset_class: "HL7MONITOR", - ordering: qParams.ordering || "bed__name", - bed_is_occupied: qParams.bed_is_occupied ?? true, - }; - - const [facilityObj, res] = await Promise.all([ - fetchFacilityOrObject(), - dispatch(listPatientAssetBeds(facilityId, filters)), - ]); - - if (!facilityObj || res.status !== 200) { - return; - } - - const entries = res.data.results as PatientAssetBed[]; - - setTotalCount(res.data.count); - setData( - entries.map(({ patient, asset, bed }) => { - const middleware = asset.resolved_middleware?.hostname; - const local_ip_address = asset.meta?.local_ip_address; - - return { - patientAssetBed: { patient, asset, bed }, - socketUrl: `wss://${middleware}/observations/${local_ip_address}`, - }; - }) - ); - } - fetchData(); - }, [ - dispatch, - facilityId, - qParams.page, - qParams.location, - qParams.ordering, - qParams.bed_is_occupied, - ]); + const totalCount = query.data?.count ?? 0; + const data = query.data?.results.map((obj) => ({ + patientAssetBed: obj, + socketUrl: getVitalsMonitorSocketUrl(obj.asset), + })); const { config, hash } = useVitalsAspectRatioConfig({ default: 6 / 11, diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index beac7f595a8..5e5b574dd07 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -2,8 +2,8 @@ import { lazy, useEffect, useState } from "react"; import { ConsultationTabProps } from "./index"; import { AssetBedModel, AssetClass, AssetData } from "../../Assets/AssetTypes"; import { useDispatch } from "react-redux"; -import { getPermittedFacility, listAssetBeds } from "../../../Redux/actions"; -import { BedModel, FacilityModel } from "../models"; +import { listAssetBeds } from "../../../Redux/actions"; +import { BedModel } from "../models"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; import useVitalsAspectRatioConfig from "../../VitalsMonitor/useVitalsAspectRatioConfig"; @@ -13,6 +13,7 @@ import Chip from "../../../CAREUI/display/Chip"; import { formatAge, formatDate, formatDateTime } from "../../../Utils/utils"; import ReadMore from "../../Common/components/Readmore"; import DailyRoundsList from "../Consultations/DailyRoundsList"; +import { getVitalsMonitorSocketUrl } from "../../VitalsMonitor/utils"; const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -40,32 +41,22 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { return; const fetchData = async () => { - const [facilityRes, assetBedRes] = await Promise.all([ - dispatch(getPermittedFacility(props.consultationData.facility as any)), - dispatch( - listAssetBeds({ - facility: props.consultationData.facility as any, - bed: props.consultationData.current_bed?.bed_object.id, - }) - ), - ]); - - const { middleware_address } = facilityRes.data as FacilityModel; + const assetBedRes = await dispatch( + listAssetBeds({ + facility: props.consultationData.facility as any, + bed: props.consultationData.current_bed?.bed_object.id, + }) + ); const assetBeds = assetBedRes?.data?.results as AssetBedModel[]; const monitorBedData = assetBeds?.find( (i) => i.asset_object?.asset_class === AssetClass.HL7MONITOR ); + setMonitorBedData(monitorBedData); - const assetDataForMonitor = monitorBedData?.asset_object; - const hl7Meta = assetDataForMonitor?.meta; - const hl7Middleware = - hl7Meta?.middleware_hostname || - assetDataForMonitor?.location_object?.middleware_address || - middleware_address; - if (hl7Middleware && hl7Meta?.local_ip_address) { + if (monitorBedData?.asset_object) { setHL7SocketUrl( - `wss://${hl7Middleware}/observations/${hl7Meta.local_ip_address}` + getVitalsMonitorSocketUrl(monitorBedData?.asset_object) ); } @@ -73,6 +64,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { props.consultationData?.current_bed?.assets_objects?.find( (i) => i.asset_class === AssetClass.VENTILATOR ); + let ventilatorBedData; if (consultationBedVentilator) { ventilatorBedData = { @@ -84,25 +76,13 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { (i) => i.asset_object.asset_class === AssetClass.VENTILATOR ); } + setVentilatorBedData(ventilatorBedData); - const ventilatorMeta = ventilatorBedData?.asset_object?.meta; - const ventilatorMiddleware = - ventilatorMeta?.middleware_hostname || - consultationBedVentilator?.location_object.middleware_address || - middleware_address; - if (ventilatorMiddleware && ventilatorMeta?.local_ip_address) { + if (ventilatorBedData?.asset_object) { setVentilatorSocketUrl( - `wss://${ventilatorMiddleware}/observations/${ventilatorMeta?.local_ip_address}` + getVitalsMonitorSocketUrl(ventilatorBedData?.asset_object) ); } - - if ( - !(hl7Middleware && hl7Meta?.local_ip_address) && - !(ventilatorMiddleware && ventilatorMeta?.local_ip_address) - ) { - setHL7SocketUrl(undefined); - setVentilatorSocketUrl(undefined); - } }; fetchData(); @@ -672,7 +652,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
-
+
diff --git a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx index 4cb90c900b1..34518afcb9b 100644 --- a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx @@ -32,12 +32,13 @@ const DefaultLogUpdateCard = ({ round, ...props }: Props) => { attributeKey="other_details" attributeValue={round.other_details} /> -
+
@@ -48,6 +49,7 @@ const DefaultLogUpdateCard = ({ round, ...props }: Props) => { border ghost size="small" + className="w-full" onClick={props.onUpdateLog} > diff --git a/src/Components/Facility/FacilityBedCapacity.tsx b/src/Components/Facility/FacilityBedCapacity.tsx index 0c05fb022cb..d33caf3f7cb 100644 --- a/src/Components/Facility/FacilityBedCapacity.tsx +++ b/src/Components/Facility/FacilityBedCapacity.tsx @@ -84,7 +84,7 @@ export const FacilityBedCapacity = (props: any) => { return (
-
+
Bed Capacity
{ USER_TYPES.findIndex((type) => type == authUser.user_type) >= StaffUserTypeIndex; + const hasPermissionToDeleteFacility = + authUser.user_type === "DistrictAdmin" || + authUser.user_type === "StateAdmin"; + const editCoverImageTooltip = hasPermissionToEditCoverImage && (
@@ -372,16 +376,17 @@ export const FacilityHome = (props: any) => { > View Users - setOpenDeleteDialog(true)} - className="flex items-center gap-3" - icon={} - authorizeFor={AuthorizeFor(["DistrictAdmin", "StateAdmin"])} - > - Delete Facility - + {hasPermissionToDeleteFacility && ( + setOpenDeleteDialog(true)} + className="flex items-center gap-3" + icon={} + > + Delete Facility + + )}
diff --git a/src/Components/Facility/LegacyFacilityCNS.tsx b/src/Components/Facility/LegacyFacilityCNS.tsx deleted file mode 100644 index 5b0005c9daf..00000000000 --- a/src/Components/Facility/LegacyFacilityCNS.tsx +++ /dev/null @@ -1,313 +0,0 @@ -import { navigate } from "raviger"; -import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { - getAllPatient, - getPermittedFacility, - listAssetBeds, -} from "../../Redux/actions"; -import { classNames } from "../../Utils/utils"; -import { AssetData, AssetLocationObject } from "../Assets/AssetTypes"; -import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; -import Page from "../Common/components/Page"; -import Loading from "../Common/Loading"; -import Pagination from "../Common/Pagination"; -import { PatientModel } from "../Patient/models"; -import { FacilityModel } from "./models"; -import AutocompleteFormField from "../Form/FormFields/Autocomplete"; -import { uniqBy } from "lodash-es"; -import DialogModal from "../Common/Dialog"; -import { LegacyMonitorCard } from "./LegacyMonitorCard"; - -interface Monitor { - patient: PatientModel; - asset: AssetData; - socketUrl: string; -} - -const PER_PAGE_LIMIT = 6; -const CNS_REFRESH_INTERVAL = 0.5 * 60e3; - -export default function LegacyFacilityCNS({ - facilityId, -}: { - facilityId: string; -}) { - const dispatch = useDispatch(); - const [isFullscreen, setIsFullscreen] = useState(false); - const [monitors, setMonitors] = useState(); - const [facility, setFacility] = useState(); - const [currentPage, setCurrentPage] = useState(1); - const [defaultShowAllLocation, setDefaultShowAllLocation] = useState(true); - const searchParams = new URLSearchParams(window.location.search); - - // this wil set ?page=1 param in url if it is not present - useEffect(() => { - if (!searchParams.get("page")) { - navigate(`/facility/${facilityId}/cns?page=1`); - } - }, []); - const [location, setLocation] = useState(); - const [showSelectLocation, setShowSelectLocation] = useState(false); - - useEffect(() => { - const onFullscreenChange = () => - setIsFullscreen(!!document.fullscreenElement); - document.addEventListener("fullscreenchange", onFullscreenChange); - return () => - document.removeEventListener("fullscreenchange", onFullscreenChange); - }, []); - - useEffect(() => { - async function fetchFacility() { - const res = await dispatch(getPermittedFacility(facilityId || "")); - if (res.status === 200) setFacility(res.data); - } - fetchFacility(); - }, [facilityId, dispatch]); - - useEffect(() => { - if (!facility) return; - const middlewareHostname = facility.middleware_address; - - async function fetchPatients() { - const res = await dispatch( - getAllPatient( - { facility: facilityId, is_active: "True" }, - "cns-patient-list" - ) - ); - if (res.status === 200) { - const patients = res.data.results as PatientModel[]; - return patients.filter( - (patient) => !!patient.last_consultation?.current_bed?.bed_object.id - ); - } - } - - async function fetchPatientMonitorAsset(patient: PatientModel) { - const res = await dispatch( - listAssetBeds( - { - bed: patient.last_consultation?.current_bed?.bed_object?.id, - }, - `asset-bed-${patient.id}` - ) - ); - - if (res.status !== 200) return; - - const asset = res.data.results.find( - (assetBed: any) => - assetBed.asset_object.meta?.asset_type === "HL7MONITOR" - )?.asset_object as AssetData | undefined; - - if (!asset) return; - - const socketUrl = `wss://${middlewareHostname}/observations/${asset.meta?.local_ip_address}`; - - return { patient, asset, socketUrl } as Monitor; - } - - async function fetchMonitors() { - const patients = await fetchPatients(); - if (!patients) return; - - const monitors = await Promise.all( - patients.map((patient) => fetchPatientMonitorAsset(patient)) - ); - return monitors.filter((monitor) => !!monitor) as Monitor[]; - } - - fetchMonitors().then((monitors) => { - setCurrentPage(Number(searchParams.get("page"))); - setMonitors(monitors); - }); - - const interval = setInterval(() => { - fetchMonitors().then(setMonitors); - }, CNS_REFRESH_INTERVAL); - - return () => clearInterval(interval); - }, [dispatch, facility, facilityId]); - - if (!monitors) return ; - return ( - - {monitors?.length > 0 ? ( - <> - setShowSelectLocation(true)} - > - - Change Location - - { - if (isFullscreen) { - document.exitFullscreen(); - } else { - document.documentElement.requestFullscreen(); - } - }} - className="tooltip !h-11" - > - - - {isFullscreen ? "Exit Fullscreen" : "Fullscreen"} - - - - ) : ( - <> - history.go(-2)} - > - Go Back - - - )} - - { - setCurrentPage(page); - navigate(`/facility/${facilityId}/cns?page=${page}`); - }} - data={{ - totalCount: defaultShowAllLocation - ? monitors.length - : monitors.filter( - (m) => m.asset.location_object.id === location?.id - ).length, - }} - defaultPerPage={PER_PAGE_LIMIT} - /> -
- } - > - setShowSelectLocation(false)} - className="w-full max-w-md" - > - {!monitors && } -
- setLocation(value)} - options={ - monitors - ? uniqBy( - monitors.map((m) => m.asset.location_object), - "id" - ) - : [] - } - isLoading={!monitors} - optionLabel={(location) => location.name} - optionDescription={(location) => - location.description + - " (" + - monitors.filter((m) => m.asset.location_object.id === location.id) - .length + - " patients)" - } - optionValue={(location) => location} - disabled={!monitors} - /> -
- { - setDefaultShowAllLocation(true); - setShowSelectLocation(false); - }} - > - Show All Locations - - { - setDefaultShowAllLocation(false); - setShowSelectLocation(false); - }} - className="my-2 mr-2" - label="Confirm" - /> - setShowSelectLocation(false)} - className="my-2 mr-2" - /> -
-
-
- {monitors.length === 0 && ( -
- No patients are currently monitored -
- )} -
- {defaultShowAllLocation - ? monitors - ?.slice( - (currentPage - 1) * PER_PAGE_LIMIT, - currentPage * PER_PAGE_LIMIT - ) - .map(({ patient, socketUrl, asset }) => ( - - )) - : monitors - ?.filter((m) => m.asset.location_object.id === location?.id) - ?.slice( - (currentPage - 1) * PER_PAGE_LIMIT, - currentPage * PER_PAGE_LIMIT - ) - .map(({ patient, socketUrl, asset }) => ( - - ))} -
- - ); -} diff --git a/src/Components/Facility/LegacyMonitorCard.tsx b/src/Components/Facility/LegacyMonitorCard.tsx deleted file mode 100644 index 61bff3d607b..00000000000 --- a/src/Components/Facility/LegacyMonitorCard.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { GENDER_TYPES } from "../../Common/constants"; -import { Link } from "raviger"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { PatientModel } from "../Patient/models"; -import LegacyPatientVitalsCard from "../Patient/LegacyPatientVitalsCard"; -import { AssetLocationObject } from "../Assets/AssetTypes"; -import { formatAge } from "../../Utils/utils"; - -interface MonitorCardProps { - facilityId: string; - patient: PatientModel; - socketUrl: string; - location: AssetLocationObject; -} - -export const LegacyMonitorCard = ({ - facilityId, - patient, - socketUrl, - location, -}: MonitorCardProps) => { - return ( -
-
- - {patient.name} - - - {formatAge(patient.age, patient.date_of_birth)} |{" "} - {GENDER_TYPES.find((g) => g.id === patient.gender)?.icon} - - - - {patient.last_consultation?.current_bed?.bed_object?.name} - - - - {location.name} - -
- -
- ); -}; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index 4279e3c1877..5ecf30cfcdf 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -23,7 +23,6 @@ interface PatientNotesProps { export default function PatientNotesSlideover(props: PatientNotesProps) { const [show, setShow] = useState(true); const [patientActive, setPatientActive] = useState(true); - const [noteField, setNoteField] = useState(""); const [reload, setReload] = useState(false); const [focused, setFocused] = useState(false); @@ -37,6 +36,11 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const { facilityId, patientId, consultationId, setShowPatientNotesPopup } = props; + const localStorageKey = `patientNotesNoteField_${consultationId}`; + const [noteField, setNoteField] = useState( + localStorage.getItem(localStorageKey) || "" + ); + const onAddNote = async () => { const payload = { note: noteField, @@ -127,6 +131,10 @@ export default function PatientNotesSlideover(props: PatientNotesProps) {
); + useEffect(() => { + localStorage.setItem(localStorageKey, noteField); + }, [noteField, localStorageKey]); + return (
{ - if (patientObservations) { - const vitalValues = patientObservations[vital]; - if (vitalValues) { - const returnValue = vitalValues?.value; - - if (returnValue !== undefined && returnValue !== null) { - return returnValue; - } - } - } - if (fallbackValue) { - return fallbackValue; - } - return ""; -}; - -export default function LegacyPatientVitalsCard({ - patient, - socketUrl, - facilityId, - shrinked, -}: IPatientVitalsCardProps) { - const wsClient = useRef(); - const [waveforms, setWaveForms] = useState(null); - const dispatch: any = useDispatch(); - const [middlewareHostname, setMiddlewareHostname] = useState(""); - const [wsUrl, setWsUrl] = useState(""); - const [patientObservations, setPatientObservations] = useState(); - const [stats, setStats] = useState(false); - - useEffect(() => { - const fetchFacility = async () => { - const res = await dispatch(getPermittedFacility(facilityId || "")); - - if (res.status === 200 && res.data) { - setMiddlewareHostname(res.data.middleware_address); - } - }; - - if (facilityId) fetchFacility(); - }, [dispatch, facilityId]); - - useEffect(() => { - const fetchAssetData = async () => { - let bedAssets = await dispatch( - listAssetBeds({ - bed: patient?.last_consultation?.current_bed?.bed_object?.id, - }) - ); - bedAssets = { - ...bedAssets, - data: { - ...bedAssets.data, - results: bedAssets.data.results.filter((assetBed: any) => - assetBed.asset_object.meta?.asset_type === "HL7MONITOR" - ? true - : false - ), - }, - }; - - if (bedAssets.data.results.length > 0) { - const asset: AssetData = bedAssets.data.results[0].asset_object; - if (asset?.meta?.local_ip_address) { - setWsUrl( - `wss://${middlewareHostname}/observations/${asset?.meta?.local_ip_address}` - ); - } - } - }; - - if (patient?.last_consultation?.current_bed?.bed_object?.id) - fetchAssetData(); - }, [ - dispatch, - middlewareHostname, - patient?.last_consultation?.current_bed?.bed_object?.id, - ]); - - const connectWs = (url: string) => { - wsClient.current = new WebSocket(url); - wsClient.current.addEventListener("message", (e) => { - const newObservations = JSON.parse(e.data || "{}"); - if (newObservations.length > 0) { - setWaveForms( - newObservations.filter((o: any) => o.observation_id === "waveform") - ); - const newObservationsMap = newObservations.reduce( - (acc: any, curr: { observation_id: any }) => ({ - ...acc, - [curr.observation_id]: curr, - }), - {} - ); - setPatientObservations(newObservationsMap); - } - }); - }; - - useEffect(() => { - if (socketUrl || wsUrl) connectWs(socketUrl || wsUrl); - - return () => { - wsClient.current?.close(); - }; - }, [wsUrl, socketUrl]); - - useEffect(() => { - return () => { - wsClient.current?.close(); - setWaveForms(null); - }; - }, [socketUrl, patient]); - - type VitalType = { - label: ReactNode; - liveKey: string; - vitalKey: string; - waveformKey?: string; - waveformColor?: string; - waveformName?: string; - waveformDefaultSpace?: boolean; - wavetype?: "STREAM" | "REFRESH"; - }; - - const vitals: VitalType[] = [ - { - label: shrinked ? "Pulse" : "Pulse Rate", - liveKey: "pulse-rate", - vitalKey: "pulse", - waveformKey: "II", - waveformColor: "limegreen", - waveformName: "ECG", - wavetype: "REFRESH", - }, - { - label: shrinked ? "BP" : "Blood Pressure", - liveKey: "bp", - vitalKey: "bp", - }, - { - label: ( - <> - SpO2 - - ), - liveKey: "SpO2", - vitalKey: "ventilator_spo2", - waveformKey: "Pleth", - waveformColor: "yellow", - }, - { - label: <>R. Rate, - liveKey: "respiratory-rate", - vitalKey: "resp", - waveformKey: "Respiration", - waveformColor: "cyan", - //waveformDefaultSpace: true - }, - { - label: shrinked ? "Temp. (°F)" : "Temperature (°F)", - liveKey: "body-temperature1", - vitalKey: "temperature", - }, - ]; - - return ( -
-
-
- {waveforms ? ( - <> - {vitals.map((v, i) => { - const waveform: any = waveforms.filter( - (w) => w["wave-name"] === v.waveformKey - )[0]; - return v.waveformKey && waveform ? ( - w["wave-name"] === v.waveformKey) - .map((w) => w.data) - .join(" "), - }} - title={v.waveformName || v.waveformKey} - color={v.waveformColor} - metrics={stats} - classes={"h-[150px]"} - defaultSpace={v.waveformDefaultSpace} - wavetype={v.wavetype || "STREAM"} - /> - ) : ( -
- ); - })} -
- - - -
- - ) : ( -
-
- -
No Live data at the moment!
-
-
- )} -
-
- {vitals.map((vital, i) => { - const liveReading = getVital(patientObservations, vital.liveKey); - return ( -
-

- {liveReading || - (vital.vitalKey === "bp" - ? `${ - patient?.last_consultation?.last_daily_round?.bp - .systolic || "--" - }/${ - patient?.last_consultation?.last_daily_round?.bp - .diastolic || "--" - }` - : patient?.last_consultation?.last_daily_round?.[ - vital.vitalKey || "" - ]) || - "--"} -

-
-
- {vital.label} -
-
- ); - })} -
-
-
- ); -} diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index dc400ddc79b..22edfb16192 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -311,20 +311,18 @@ export const PatientHome = (props: any) => { patientData.medical_history.length ) { const medHis = patientData.medical_history; - patientMedHis = medHis.map((item: any, idx: number) => ( -
- {item?.disease !== "NO" && ( - <> -
- {item.disease} -
-
- {item.details} -
- - )} -
- )); + patientMedHis = medHis + .filter((item) => item.disease !== "NO") + .map((item, idx) => ( +
+
+ {item.disease} +
+
+ {item.details} +
+
+ )); } let consultationList, sampleList; diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index eb589667ece..68555837d6e 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -320,7 +320,7 @@ export default function PatientInfoCard(props: { ); })}
- {!!consultation?.discharge_date && ( + {consultation?.discharge_date ? (
@@ -346,6 +346,23 @@ export default function PatientInfoCard(props: {
+ ) : ( +
+ + {consultation?.encounter_date && ( +
+ Admission on{" "} + {formatDateTime(consultation?.encounter_date)} +
+ )} + {consultation?.icu_admission_date && ( +
+ , ICU Admission on{" "} + {formatDateTime(consultation?.icu_admission_date)} +
+ )} +
+
)}
diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index 77088ef57e7..546d35cc487 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -544,6 +544,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { setFacilityName(""); } } + fetchFacilityName(); }, [dispatchAction, facilityId]); @@ -953,6 +954,14 @@ export const PatientRegister = (props: PatientRegisterProps) => { } else { values.splice(values.indexOf(id), 1); } + + if (id !== 1 && values.includes(1)) { + values.splice(values.indexOf(1), 1); + } else if (id === 1) { + values.length = 0; + values.push(1); + } + field("medical_history").onChange({ name: "medical_history", value: values, diff --git a/src/Components/VitalsMonitor/utils.ts b/src/Components/VitalsMonitor/utils.ts index 2e38f83b65b..21ba769dc83 100644 --- a/src/Components/VitalsMonitor/utils.ts +++ b/src/Components/VitalsMonitor/utils.ts @@ -1,3 +1,4 @@ +import { AssetClass, AssetData } from "../Assets/AssetTypes"; import { ChannelOptions, VitalsWaveformBase } from "./types"; /** @@ -73,3 +74,19 @@ export const getVitalsCanvasSizeAndDuration = ( duration: DEFAULT_DURATION * (ratio / DEFAULT_RATIO), }; }; + +export const getVitalsMonitorSocketUrl = (asset: AssetData) => { + if ( + asset.asset_class !== AssetClass.HL7MONITOR && + asset.asset_class !== AssetClass.VENTILATOR + ) { + throw "getVitalsMonitorSocketUrl can be invoked only for HL7MONITOR or VENTILATOR assets"; + } + + const middleware = asset.resolved_middleware?.hostname; + const ipAddress = asset.meta?.local_ip_address; + + if (middleware && ipAddress) { + return `wss://${middleware}/observations/${ipAddress}`; + } +}; diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 32af9609263..9d9ab59795b 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -28,10 +28,6 @@ export const getAllSkills = (params: object) => { return fireRequest("getAllSkills", [], params); }; -export const getPermittedFacility = (id: number | string, key?: string) => { - return fireRequest("getPermittedFacility", [], {}, { id: id }, key); -}; - export const getAnyFacility = (id: number | string, key?: string) => { return fireRequest("getAnyFacility", [], {}, { id: id }, key); }; @@ -109,11 +105,6 @@ export const deleteAssetBed = (asset_id: string) => } ); -export const listPatientAssetBeds = ( - facility_external_id: string, - params: object -) => fireRequest("listPatientAssetBeds", [], params, { facility_external_id }); - // Facility Beds export const listFacilityBeds = (params: object) => fireRequest("listFacilityBeds", [], params, {}); @@ -176,15 +167,6 @@ export const downloadFacilityTriage = () => { }; // Capacity/Triage/Doctor -export const createCapacity = ( - id: number | undefined, - params: object, - pathParam: object -) => { - return id - ? fireRequest("updateCapacity", [id], params, pathParam) - : fireRequest("createCapacity", [], params, pathParam); -}; export const createDoctor = ( id: number | undefined, params: object, @@ -206,26 +188,12 @@ export const getTriageInfo = (pathParam: object) => { export const getTriageDetails = (pathParam: object) => { return fireRequest("getTriageDetails", [], {}, pathParam); }; -export const listCapacity = (params: object, pathParam: object) => { - return fireRequest("getCapacity", [], params, pathParam); -}; export const listDoctor = (params: object, pathParam: object) => { return fireRequest("listDoctor", [], params, pathParam); }; -export const getCapacity = (id: number, pathParam: object) => { - return fireRequest("getCapacity", [id], {}, pathParam); -}; - -export const getCapacityBed = (pathParam: object) => { - return fireRequest("getCapacityBed", [], {}, pathParam); -}; - export const getDoctor = (pathParam: object) => { return fireRequest("getDoctor", [], {}, pathParam); }; -export const deleteCapacity = (pathParam: object) => { - return fireRequest("deleteCapacityBed", [], {}, pathParam); -}; //Patient export const searchPatient = (params: object) => { diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 2b8d4f8f51b..8f8fc76484d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -27,6 +27,7 @@ import { AssetServiceUpdate, AssetTransaction, AssetUpdate, + PatientAssetBed, } from "../Components/Assets/AssetTypes"; import { CapacityModal, @@ -51,6 +52,7 @@ import { BedModel, } from "../Components/Facility/models"; import { + IDeleteBedCapacity, IDeleteExternalResult, IExternalResult, IExternalResultCsv, @@ -394,6 +396,7 @@ const routes = { listPatientAssetBeds: { path: "/api/v1/facility/{facility_external_id}/patient_asset_beds/", method: "GET", + TRes: Type>(), }, // Facility Beds @@ -529,6 +532,7 @@ const routes = { createCapacity: { path: "/api/v1/facility/{facilityId}/capacity/", method: "POST", + TRes: Type(), }, createDoctor: { @@ -543,11 +547,13 @@ const routes = { getCapacityBed: { path: "/api/v1/facility/{facilityId}/capacity/{bed_id}/", + TRes: Type(), }, deleteCapacityBed: { path: "/api/v1/facility/{facilityId}/capacity/{bed_id}/", method: "DELETE", + TRes: Type(), }, listDoctor: { @@ -559,8 +565,9 @@ const routes = { }, updateCapacity: { - path: "/api/v1/facility/{facilityId}/capacity", + path: "/api/v1/facility/{facilityId}/capacity/{bed_id}/", method: "PUT", + TRes: Type(), }, updateDoctor: {