diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index 68990e64416..d4be388b8f4 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -62,7 +62,7 @@ export const FacilityHome = (props: any) => { const { t } = useTranslation(); const { facilityId } = props; const dispatch: any = useDispatch(); - const [facilityData, setFacilityData] = useState({}); + const [facilityData, setFacilityData] = useState({}); const [capacityData, setCapacityData] = useState>([]); const [doctorData, setDoctorData] = useState>([]); const [isLoading, setIsLoading] = useState(false); @@ -495,7 +495,7 @@ export const FacilityHome = (props: any) => {
- {facilityData.features?.some((feature) => + {facilityData.features?.some((feature: any) => FACILITY_FEATURE_TYPES.some((f) => f.id === feature) ) && (

Available features

@@ -617,6 +617,16 @@ export const FacilityHome = (props: any) => { Central Nursing Station + navigate(`/facility/${facilityId}/livefeed`)} + > + + Live Monitoring + void +) => { + const orderData = localStorage.getItem("live-feed-order"); + if (orderData) { + const order = JSON.parse(orderData); + const orderValue = order.find((item: any) => item.facility === facilityId); + setOrdering(orderValue.order); + } +}; + +const setOrderingList = async (facilityId: string, order: string) => { + const orderData = localStorage.getItem("live-feed-order") || "[]"; + const orderList = JSON.parse(orderData); + const index = orderList.findIndex( + (item: any) => item.facility === facilityId + ); + if (index !== -1) { + orderList[index].order = order; + } else { + orderList.push({ facility: facilityId, order }); + } + localStorage.setItem("live-feed-order", JSON.stringify(orderList)); +}; + +export default function LiveFeedScreen({ facilityId }: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [isFullscreen, setFullscreen] = useFullscreen(); + const sidebar = useContext(SidebarShrinkContext); + + const [facility, setFacility] = useState(); + const [assets, setAssets] = useState(); + const [totalCount, setTotalCount] = useState(0); + const { qParams, updateQuery, removeFilter, updatePage } = useFilters({ + limit: PER_PAGE_LIMIT, + }); + const [ordering, setOrdering] = useState("bed__name"); + + const [refresh_presets_hash, setRefreshPresetsHash] = useState( + Number(new Date()) + ); + + // To automatically collapse sidebar. + useEffect(() => { + sidebar.setShrinked(true); + + return () => { + sidebar.setShrinked(sidebar.shrinked); + }; + }, []); + + useEffect(() => { + getOrderingList(facilityId, setOrdering); + }, [facilityId]); + + useEffect(() => { + async function fetchFacilityOrObject() { + if (facility) return facility; + const res = await dispatch(getPermittedFacility(facilityId)); + if (res.status !== 200) return; + setFacility(res.data); + return res.data as FacilityModel; + } + + async function fetchData() { + setAssets(undefined); + + const filters = { + ...qParams, + page: qParams.page || 1, + limit: PER_PAGE_LIMIT, + offset: (qParams.page ? qParams.page - 1 : 0) * PER_PAGE_LIMIT, + asset_class: "ONVIF", + facility: facilityId || "", + location: qParams.location, + ordering: qParams.ordering || ordering, + bed_is_occupied: qParams.bed_is_occupied, + }; + + const [facilityObj, res] = await Promise.all([ + fetchFacilityOrObject(), + dispatch(listAssets(filters)), + ]); + + if (!facilityObj || res.status !== 200) { + return; + } + console.log(facilityObj, res.data); + const entries = res.data.results; + + setTotalCount(entries.length); + setAssets(entries); + } + fetchData(); + setRefreshPresetsHash(Number(new Date())); + }, [ + dispatch, + facilityId, + qParams.page, + qParams.location, + qParams.ordering, + qParams.bed_is_occupied, + ]); + + return ( + + + + + + Settings and Filters + + + + +
+
+
+ + {totalCount} Camera + present + +
+
+
+
+ + Filter by Location + +
+ updateQuery({ location })} + selected={qParams.location} + showAll={false} + multiple={false} + facilityId={facilityId} + errors="" + errorClassName="hidden" + /> + {qParams.location && ( + removeFilter("location")} + > + Clear + + )} +
+
+ { + updateQuery({ ordering: value }); + setOrderingList(facilityId, value); + }} + options={SORT_OPTIONS} + optionLabel={({ value }) => t("SortOptions." + value)} + optionIcon={({ isAscending }) => ( + + )} + optionValue={({ value }) => value} + labelClassName="text-sm" + errorClassName="hidden" + /> + { + if (value) { + updateQuery({ [name]: value }); + } else { + removeFilter(name); + } + }} + labelClassName="text-sm" + errorClassName="hidden" + /> + setFullscreen(!isFullscreen)} + className="tooltip !h-11" + > + + {isFullscreen ? "Exit Fullscreen" : "Fullscreen"} + +
+
+
+
+
+ + updatePage(page)} + /> +
+ } + > + {assets === undefined ? ( + + ) : assets.length === 0 ? ( +
+ No Camera present in this location or facility. +
+ ) : ( +
+ {assets.map((asset, idx) => ( +
+ {/* */} + +
+ ))} +
+ )} + + ); +} diff --git a/src/Components/Facility/LiveFeedTile.tsx b/src/Components/Facility/LiveFeedTile.tsx new file mode 100644 index 00000000000..945240b9c6c --- /dev/null +++ b/src/Components/Facility/LiveFeedTile.tsx @@ -0,0 +1,1045 @@ +// import axios from "axios"; +// import React, { useEffect, useState, useRef, useCallback } from "react"; +// import * as Notification from "../../Utils/Notifications.js"; +// import { useDispatch } from "react-redux"; +// import ReactPlayer from "react-player"; +// import { getAsset, listAssetBeds } from "../../Redux/actions"; +// import { statusType, useAbortableEffect } from "../../Common/utils"; +// import { useTranslation } from "react-i18next"; +// import useFullscreen from "../../Common/hooks/useFullscreen.js"; +// interface LiveFeedTileProps { +// assetId: string; +// } + +// interface CameraPosition { +// x: number; +// y: number; +// zoom: number; +// } + +// // string:string dictionary +// interface CameraPreset { +// [key: string]: string; +// } + +// export default function LiveFeedTile(props: LiveFeedTileProps) { +// const dispatch: any = useDispatch(); +// const { assetId } = props; +// const [sourceUrl, setSourceUrl] = useState(); +// const [asset, setAsset] = useState(); +// const [presets, setPresets] = useState([]); +// const [bedPresets, setBedPresets] = useState([]); +// const [loading, setLoading] = useState(true); +// // const [showControls, setShowControls] = useState(false); +// const [showDefaultPresets, setShowDefaultPresets] = useState(false); +// const [position, setPosition] = useState({ +// x: 0, +// y: 0, +// zoom: 0, +// }); +// const { t } = useTranslation(); +// const [_isFullscreen, setFullscreen] = useFullscreen(); +// // const [toggle, setToggle] = useState(false); + +// useEffect(() => { +// let loadingTimeout: any; +// if (loading === true) +// loadingTimeout = setTimeout(() => { +// setLoading(false); +// }, 6000); +// return () => { +// if (loadingTimeout) clearTimeout(loadingTimeout); +// }; +// }, [loading]); + +// const fetchData = useCallback( +// async (status: statusType) => { +// setLoading(true); +// console.log("fetching asset"); +// const assetData: any = await dispatch(getAsset(assetId)); +// if (!status.aborted) { +// // setLoading(false); +// if (!assetData.data) +// Notification.Error({ +// msg: t("something_went_wrong"), +// }); +// else { +// setAsset(assetData.data); +// } +// } +// }, +// [dispatch, assetId] +// ); + +// useAbortableEffect( +// (status: statusType) => fetchData(status), +// [dispatch, fetchData] +// ); +// const requestStream = () => { +// axios +// .post(`https://${asset.meta.middleware_hostname}/start`, { +// uri: "rtsp://remote:qwerty123@192.168.1.64:554/", +// }) +// .then((resp: any) => { +// setSourceUrl( +// `https://${asset.meta.middleware_hostname}${resp.data.uri}` +// ); +// }) +// .catch((_ex: any) => { +// // console.error('Error while refreshing',ex); +// }); +// }; +// const stopStream = (url: string | undefined) => { +// console.log("stop", url); +// if (url) { +// const urlSegments = url.split("/"); +// const id = urlSegments?.pop(); +// axios +// .post(`https://${asset.meta.middleware_hostname}/stop`, { +// id, +// }) +// .then((resp: any) => { +// console.log(resp); +// // setSourceUrl(`https://${middlewareHostname}${resp.data.uri}`); +// }) +// .catch((_ex: any) => { +// // console.error('Error while refreshing',ex); +// }); +// } +// }; +// const getCameraStatus = (asset: any) => { +// axios +// .get( +// `https://${asset.meta.middleware_hostname}/status?hostname=${asset.hostname}&port=${asset.port}&username=${asset.username}&password=${asset.password}` +// ) +// .then((resp: any) => { +// setPosition(resp.data.position); +// }) +// .catch((_ex: any) => { +// // console.error('Error while refreshing',ex); +// }); +// }; +// const getPresets = (asset: any) => { +// const url = `https://${asset.meta.middleware_hostname}/presets?hostname=${asset.hostname}&port=${asset.port}&username=${asset.username}&password=${asset.password}`; +// axios +// .get(url) +// .then((resp: any) => { +// setPresets(resp.data); +// }) +// .catch((_ex: any) => { +// // console.error("Error while refreshing", ex); +// }); +// }; +// const getBedPresets = async (_asset: any) => { +// const bedAssets = await dispatch(listAssetBeds({ asset: props.assetId })); +// setBedPresets(bedAssets.data.results); +// }; +// const gotoBedPreset = (preset: any) => { +// absoluteMove(preset.meta.position); +// }; +// const gotoPreset = (preset: number) => { +// axios +// .post(`https://${asset.meta.middleware_hostname}/gotoPreset`, { +// ...asset, +// preset, +// }) +// .then((resp: any) => { +// console.log(resp.data); +// }) +// .catch((_ex: any) => { +// // console.error('Error while refreshing',ex); +// }); +// }; +// const requestPTZ = (action: string) => { +// setLoading(true); +// if (!position) { +// getCameraStatus(asset); +// } else { +// const data = { +// x: 0, +// y: 0, +// zoom: 0, +// } as any; +// console.log(action); +// // Relative X Y Coordinates +// switch (action) { +// case "up": +// data.y = 0.05; +// break; +// case "down": +// data.y = -0.05; +// break; +// case "left": +// data.x = -0.05; +// break; +// case "right": +// data.x = 0.05; +// break; +// case "zoomIn": +// data.zoom = 0.05; +// break; +// case "zoomOut": +// data.zoom = -0.05; +// break; +// case "stop": +// stopStream(sourceUrl); +// setSourceUrl(undefined); +// return; +// case "reset": +// setSourceUrl(undefined); +// requestStream(); +// return; +// default: +// break; +// } +// axios +// .post(`https://${asset.meta.middleware_hostname}/relativeMove`, { +// ...data, +// ...asset, +// }) +// .then((resp: any) => { +// console.log(resp.data); +// getCameraStatus(asset); +// }) +// .catch((_ex: any) => { +// // console.error('Error while refreshing',ex); +// }); +// } +// }; + +// const absoluteMove = (data: any) => { +// setLoading(true); +// axios +// .post(`https://${asset.meta.middleware_hostname}/absoluteMove`, { +// ...data, +// ...asset, +// }) +// .then((_resp: any) => { +// getCameraStatus(asset); +// }) +// .catch((ex: any) => { +// console.error("Error while absolute move", ex); +// }); +// }; + +// useEffect(() => { +// if (asset) { +// getPresets(asset); +// getBedPresets(asset); +// requestStream(); +// } +// }, [asset]); + +// useEffect(() => { +// if (bedPresets.length > 0) absoluteMove(bedPresets[0].meta.position); +// }, [bedPresets]); + +// // useEffect(() => { +// // const timer = setTimeout(() => { +// // setShowControls(toggle); +// // }, 300); +// // return () => clearTimeout(timer); +// // }, [toggle]); + +// const liveFeedPlayerRef = useRef(null); +// const handleClickFullscreen = () => { +// if (liveFeedPlayerRef.current) { +// setFullscreen(true, liveFeedPlayerRef.current.wrapper); +// } +// }; + +// const viewOptions = presets +// ? Object.entries(presets) +// .map(([key, value]) => ({ label: key, value })) +// .slice(0, 10) +// : Array.from(Array(10), (_, i) => ({ +// label: t("monitor") + (i + 1), +// value: i + 1, +// })); + +// const cameraPTZ = [ +// { icon: "fa fa-arrow-up", label: t("up"), action: "up" }, +// { icon: "fa fa-arrow-down", label: t("down"), action: "down" }, +// { icon: "fa fa-arrow-left", label: t("left"), action: "left" }, +// { icon: "fa fa-arrow-right", label: t("right"), action: "right" }, +// { icon: "fa fa-search-plus", label: t("zoom_in"), action: "zoomIn" }, +// { icon: "fa fa-search-minus", label: t("zoom_out"), action: "zoomOut" }, +// { icon: "fa fa-stop", label: t("stop"), action: "stop" }, +// { icon: "fa fa-undo", label: t("reset"), action: "reset" }, +// ]; + +// return ( +//
+//
+//
+//
+// {sourceUrl ? ( +//
+// { +// // requestStream(); +// console.log("Error", e); +// console.log("Data", data); +// console.log("HLS Instance", hlsInstance); +// console.log("HLS Global", hlsGlobal); +// if (e === "hlsError") { +// const recovered = hlsInstance.recoverMediaError(); +// console.log(recovered); +// } +// }} +// /> +//
+// ) : ( +//
+//

+// STATUS: OFFLINE +//

+//

+// {t("feed_is_currently_not_live")} +//

+//
+// )} +//
+//
+//
+//
+//
+// {cameraPTZ.map((option: any) => ( +//
{ +// // console.log(option.action); +// requestPTZ(option.action); +// }} +// > +// +//
+// ))} +// +//
+// {/*
+// +//
*/} +//
+//
+//
+//
+// {/* div with "Loading" at the center */} +//
+// +// +// +// +//
{t("moving_camera")}
+//
+//
+//
+//
+// +// {showDefaultPresets +// ? viewOptions.map((option: any) => ( +//
{ +// setLoading(true); +// gotoPreset(option.value); +// }} +// > +// +//
+// )) +// : bedPresets.map((preset: any, index: number) => ( +//
{ +// setLoading(true); +// gotoBedPreset(preset); +// }} +// key={preset.id} +// > +// +//
+// ))} +//
+//
+//
+// ); +// } + +import { useEffect, useState, useRef } from "react"; +import { useDispatch } from "react-redux"; +import useKeyboardShortcut from "use-keyboard-shortcut"; +import { + listAssetBeds, + partialUpdateAssetBed, + deleteAssetBed, +} from "../../Redux/actions"; +import { getCameraPTZ } from "../../Common/constants"; +import { + StreamStatus, + useMSEMediaPlayer, +} from "../../Common/hooks/useMSEplayer"; +import { useFeedPTZ } from "../../Common/hooks/useFeedPTZ"; +import * as Notification from "../../Utils/Notifications.js"; +import { AxiosError } from "axios"; +import { BedSelect } from "../Common/BedSelect"; +import { BedModel } from "./models"; +import useWindowDimensions from "../../Common/hooks/useWindowDimensions"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ConfirmDialog from "../Common/ConfirmDialog"; +import { FieldLabel } from "../Form/FormFields/FormField"; +import useFullscreen from "../../Common/hooks/useFullscreen"; +import { FeedCameraPTZHelpButton } from "./Consultations/Feed"; + +const LiveFeed = (props: any) => { + const middlewareHostname = + props.middlewareHostname || "dev_middleware.coronasafe.live"; + const [presetsPage, setPresetsPage] = useState(0); + const cameraAsset = props.asset; + const [presets, setPresets] = useState([]); + const [bedPresets, setBedPresets] = useState([]); + const [showDefaultPresets, setShowDefaultPresets] = useState(false); + const [precision, setPrecision] = useState(1); + const [streamStatus, setStreamStatus] = useState( + StreamStatus.Offline + ); + const [videoStartTime, setVideoStartTime] = useState(null); + const [bed, setBed] = useState({}); + const [preset, setNewPreset] = useState(""); + const [loading, setLoading] = useState(); + const dispatch: any = useDispatch(); + const [page, setPage] = useState({ + count: 0, + limit: 8, + offset: 0, + }); + const [toDelete, setToDelete] = useState(null); + const [toUpdate, setToUpdate] = useState(null); + const [_isFullscreen, setFullscreen] = useFullscreen(); + + const { width } = useWindowDimensions(); + const extremeSmallScreenBreakpoint = 320; + const isExtremeSmallScreen = + width <= extremeSmallScreenBreakpoint ? true : false; + const liveFeedPlayerRef = useRef(null); + + const videoEl = liveFeedPlayerRef.current as HTMLVideoElement; + + const url = `wss://${middlewareHostname}/stream/${cameraAsset?.accessKey}/channel/0/mse?uuid=${cameraAsset?.accessKey}&channel=0`; + + const { startStream } = useMSEMediaPlayer({ + config: { + middlewareHostname, + ...cameraAsset, + }, + url, + videoEl, + }); + + const refreshPresetsHash = props.refreshPresetsHash; + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const [currentPreset, setCurrentPreset] = useState(); + const { + absoluteMove, + getCameraStatus, + getPTZPayload, + getPresets, + gotoPreset, + relativeMove, + } = useFeedPTZ({ + config: { + middlewareHostname, + ...cameraAsset, + }, + dispatch, + }); + + const fetchCameraPresets = () => + getPresets({ + onSuccess: (resp) => { + setPresets(resp); + }, + onError: (resp) => { + resp instanceof AxiosError && + Notification.Error({ + msg: "Camera is offline", + }); + }, + }); + + const calculateVideoLiveDelay = () => { + const video = liveFeedPlayerRef.current as HTMLVideoElement; + if (!video || !videoStartTime) return 0; + + const timeDifference = + (new Date().getTime() - videoStartTime.getTime()) / 1000; + + return timeDifference - video.currentTime; + }; + + const getBedPresets = async (id: any) => { + const bedAssets = await dispatch( + listAssetBeds({ + asset: id, + limit: page.limit, + offset: page.offset, + }) + ); + setBedPresets(bedAssets?.data?.results); + setPage({ + ...page, + count: bedAssets?.data?.count, + }); + }; + + const deletePreset = async (id: any) => { + const res = await dispatch(deleteAssetBed(id)); + if (res?.status === 204) { + Notification.Success({ msg: "Preset deleted successfully" }); + getBedPresets(cameraAsset.id); + } else { + Notification.Error({ + msg: "Error while deleting Preset: " + (res?.data?.detail || ""), + }); + } + setToDelete(null); + }; + + const updatePreset = async (currentPreset: any) => { + const data = { + bed_id: bed.id, + preset_name: preset, + }; + const response = await dispatch( + partialUpdateAssetBed( + { + asset: currentPreset.asset_object.id, + bed: bed.id, + meta: { + ...currentPreset.meta, + ...data, + }, + }, + currentPreset?.id + ) + ); + if (response && response.status === 200) { + Notification.Success({ msg: "Preset Updated" }); + } else { + Notification.Error({ msg: "Something Went Wrong" }); + } + getBedPresets(cameraAsset?.id); + fetchCameraPresets(); + setToUpdate(null); + }; + + const gotoBedPreset = (preset: any) => { + setLoading("Moving"); + absoluteMove(preset.meta.position, { + onSuccess: () => setLoading(undefined), + }); + }; + + useEffect(() => { + if (cameraAsset?.hostname) { + fetchCameraPresets(); + } + }, []); + + useEffect(() => { + setNewPreset(toUpdate?.meta?.preset_name); + setBed(toUpdate?.bed_object); + }, [toUpdate]); + + useEffect(() => { + getBedPresets(cameraAsset.id); + if (bedPresets?.[0]?.position) { + absoluteMove(bedPresets[0]?.position, {}); + } + }, [page.offset, cameraAsset.id, refreshPresetsHash]); + + const viewOptions = (page: number) => { + return presets + ? Object.entries(presets) + .map(([key, value]) => ({ label: key, value })) + .slice(page, page + 10) + : Array.from(Array(10), (_, i) => ({ + label: "Monitor " + (i + 1), + value: i + 1, + })); + }; + useEffect(() => { + let tId: any; + if (streamStatus !== StreamStatus.Playing) { + setStreamStatus(StreamStatus.Loading); + tId = setTimeout(() => { + startStream({ + onSuccess: () => setStreamStatus(StreamStatus.Playing), + onError: () => setStreamStatus(StreamStatus.Offline), + }); + }, 500); + } + + return () => { + clearTimeout(tId); + }; + }, [startStream, streamStatus]); + + const handlePagination = (cOffset: number) => { + setPage({ + ...page, + offset: cOffset, + }); + }; + + const cameraPTZActionCBs: { [key: string]: (option: any) => void } = { + precision: () => { + setPrecision((precision: number) => + precision === 16 ? 1 : precision * 2 + ); + }, + reset: () => { + setStreamStatus(StreamStatus.Loading); + setVideoStartTime(null); + startStream({ + onSuccess: () => setStreamStatus(StreamStatus.Playing), + onError: () => setStreamStatus(StreamStatus.Offline), + }); + }, + fullScreen: () => { + if (!liveFeedPlayerRef.current) return; + setFullscreen(true, liveFeedPlayerRef.current); + }, + updatePreset: (option) => { + getCameraStatus({ + onSuccess: async (data) => { + console.log({ currentPreset, data }); + if (currentPreset?.asset_object?.id && data?.position) { + setLoading(option.loadingLabel); + console.log("Updating Preset"); + const response = await dispatch( + partialUpdateAssetBed( + { + asset: currentPreset.asset_object.id, + bed: currentPreset.bed_object.id, + meta: { + ...currentPreset.meta, + position: data?.position, + }, + }, + currentPreset?.id + ) + ); + if (response && response.status === 200) { + Notification.Success({ msg: "Preset Updated" }); + getBedPresets(cameraAsset?.id); + fetchCameraPresets(); + } + setLoading(undefined); + } + }, + }); + }, + other: (option) => { + setLoading(option.loadingLabel); + relativeMove(getPTZPayload(option.action, precision), { + onSuccess: () => setLoading(undefined), + }); + }, + }; + + const cameraPTZ = getCameraPTZ(precision).map((option) => { + const cb = + cameraPTZActionCBs[ + cameraPTZActionCBs[option.action] ? option.action : "other" + ]; + return { ...option, callback: () => cb(option) }; + }); + + // Voluntarily disabling eslint, since length of `cameraPTZ` is constant and + // hence shall not cause issues. (https://news.ycombinator.com/item?id=24363703) + for (const option of cameraPTZ) { + if (!option.shortcutKey) continue; + // eslint-disable-next-line react-hooks/rules-of-hooks + useKeyboardShortcut(option.shortcutKey, option.callback); + } + + return ( +
+ {toDelete && ( + +

+ Preset: {toDelete.meta.preset_name} +

+

+ Bed: {toDelete.bed_object.name} +

+ + } + action="Delete" + variant="danger" + onClose={() => setToDelete(null)} + onConfirm={() => deletePreset(toDelete.id)} + /> + )} + {toUpdate && ( + setToUpdate(null)} + onConfirm={() => updatePreset(toUpdate)} + > +
+ Bed + setBed(selected as BedModel)} + selected={bed} + error="" + multiple={false} + location={cameraAsset.location_id} + facility={cameraAsset.facility_id} + /> +
+
+ )} +
+
+
+ {/* ADD VIDEO PLAYER HERE */} +
+ + + {streamStatus === StreamStatus.Playing && + calculateVideoLiveDelay() > 3 && ( +
+ + Slow Network Detected +
+ )} + + {loading && ( +
+
+
+

{loading}

+
+
+ )} + {/* { streamStatus > 0 && */} +
+ {streamStatus === StreamStatus.Offline && ( +
+

+ STATUS: OFFLINE +

+

+ Feed is currently not live. +

+

+ Click refresh button to try again. +

+
+ )} + {streamStatus === StreamStatus.Stop && ( +
+

+ STATUS: STOPPED +

+

Feed is Stooped.

+

+ Click refresh button to start feed. +

+
+ )} + {streamStatus === StreamStatus.Loading && ( +
+

+ STATUS: LOADING +

+

+ Fetching latest feed. +

+
+ )} +
+
+
+ {cameraPTZ.map((option) => { + const shortcutKeyDescription = + option.shortcutKey && + option.shortcutKey + .join(" + ") + .replace("Control", "Ctrl") + .replace("ArrowUp", "↑") + .replace("ArrowDown", "↓") + .replace("ArrowLeft", "←") + .replace("ArrowRight", "→"); + + return ( + + ); + })} +
+ +
+
+
+ +
+ +
+
+ {showDefaultPresets ? ( + <> + {viewOptions(presetsPage)?.map((option: any, i) => ( + + ))} + + ) : ( + <> + {bedPresets?.map((preset: any, index: number) => ( +
+ +
+ + +
+
+ ))} + + )} +
+ {/* Page Number Next and Prev buttons */} + {showDefaultPresets ? ( +
+ + +
+ ) : ( +
+ + +
+ )} + {props?.showRefreshButton && ( + + )} +
+
+
+
+
+ ); +}; + +export default LiveFeed; diff --git a/src/Routers/routes/FacilityRoutes.tsx b/src/Routers/routes/FacilityRoutes.tsx index 77247df9189..be52ef4e72c 100644 --- a/src/Routers/routes/FacilityRoutes.tsx +++ b/src/Routers/routes/FacilityRoutes.tsx @@ -8,6 +8,7 @@ import ResourceCreate from "../../Components/Resource/ResourceCreate"; import CentralNursingStation from "../../Components/Facility/CentralNursingStation"; import FacilityLocationRoutes from "./FacilityLocationRoutes"; import FacilityInventoryRoutes from "./FacilityInventoryRoutes"; +import LiveFeedScreen from "../../Components/Facility/LiveFeedScreen"; export default { "/facility": () => , @@ -21,6 +22,9 @@ export default { "/facility/:facilityId/cns": ({ facilityId }: any) => ( ), + "/facility/:facilityId/livefeed": ({ facilityId }: any) => ( + + ), "/facility/:facilityId": ({ facilityId }: any) => ( ),