diff --git a/cypress/pageobject/Patient/PatientInvestigation.ts b/cypress/pageobject/Patient/PatientInvestigation.ts
index c226c358676..8f73cf908bc 100644
--- a/cypress/pageobject/Patient/PatientInvestigation.ts
+++ b/cypress/pageobject/Patient/PatientInvestigation.ts
@@ -10,7 +10,7 @@ class PatientInvestigation {
}
selectInvestigation(investigation: string) {
- cy.get("#search-patient-investigation").click();
+ cy.get("#search-patient-investigation").type(investigation);
cy.verifyAndClickElement("#investigation-group", investigation);
cy.verifyAndClickElement("#investigation", "Investigation No. 1");
}
diff --git a/src/CAREUI/display/NetworkSignal.tsx b/src/CAREUI/display/NetworkSignal.tsx
index 2bcd2744acb..1d5f2d49623 100644
--- a/src/CAREUI/display/NetworkSignal.tsx
+++ b/src/CAREUI/display/NetworkSignal.tsx
@@ -19,7 +19,7 @@ export default function NetworkSignal({ strength, children }: Props) {
return (
))
)}
+ {!!strength && strength < 2 && (
+
+ )}
{children}
diff --git a/src/CAREUI/display/RecordMeta.tsx b/src/CAREUI/display/RecordMeta.tsx
index 662c61fd73f..2363ddbd551 100644
--- a/src/CAREUI/display/RecordMeta.tsx
+++ b/src/CAREUI/display/RecordMeta.tsx
@@ -1,8 +1,7 @@
import CareIcon from "../icons/CareIcon";
import {
- formatDate,
+ formatDateTime,
formatName,
- formatTime,
isUserOnline,
relativeTime,
} from "../../Utils/utils";
@@ -39,8 +38,9 @@ const RecordMeta = ({
{relativeTime(time)}
- {formatTime(time)}
- {formatDate(time)}
+
+ {formatDateTime(time).replace(";", "")}
+
{user && !inlineUser && (
by
diff --git a/src/CAREUI/interactive/FiltersSlideover.tsx b/src/CAREUI/interactive/FiltersSlideover.tsx
index 83f92e2bd90..496f1b3e516 100644
--- a/src/CAREUI/interactive/FiltersSlideover.tsx
+++ b/src/CAREUI/interactive/FiltersSlideover.tsx
@@ -58,7 +58,7 @@ export const AdvancedFilterButton = ({ onClick }: { onClick: () => void }) => {
diff --git a/src/CAREUI/misc/Fullscreen.tsx b/src/CAREUI/misc/Fullscreen.tsx
index 5cfa7865128..82c6d9e91ed 100644
--- a/src/CAREUI/misc/Fullscreen.tsx
+++ b/src/CAREUI/misc/Fullscreen.tsx
@@ -5,17 +5,25 @@ interface Props {
fullscreenClassName?: string;
children: React.ReactNode;
fullscreen: boolean;
- onExit: () => void;
+ onExit: (reason?: "DEVICE_UNSUPPORTED") => void;
}
export default function Fullscreen(props: Props) {
const ref = useRef(null);
useEffect(() => {
+ if (!ref.current) {
+ return;
+ }
+
if (props.fullscreen) {
- ref.current?.requestFullscreen();
+ if (ref.current.requestFullscreen) {
+ ref.current.requestFullscreen();
+ } else {
+ props.onExit("DEVICE_UNSUPPORTED");
+ }
} else {
- document.exitFullscreen();
+ document.exitFullscreen?.();
}
}, [props.fullscreen]);
@@ -27,6 +35,7 @@ export default function Fullscreen(props: Props) {
};
document.addEventListener("fullscreenchange", listener);
+
return () => {
document.removeEventListener("fullscreenchange", listener);
};
diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx
index d63c3bebf31..c2ef605f5ae 100644
--- a/src/Common/constants.tsx
+++ b/src/Common/constants.tsx
@@ -388,10 +388,10 @@ export const SAMPLE_TEST_RESULT = [
export const CONSULTATION_SUGGESTION = [
{ id: "HI", text: "Home Isolation", deprecated: true }, // # Deprecated. Preserving option for backward compatibility (use only for readonly operations)
{ id: "A", text: "Admission" },
- { id: "R", text: "Refer to another Hospital" },
+ { id: "R", text: "Refer to another Hospital", editDisabled: true },
{ id: "OP", text: "OP Consultation" },
{ id: "DC", text: "Domiciliary Care" },
- { id: "DD", text: "Declare Death" },
+ { id: "DD", text: "Declare Death", editDisabled: true },
] as const;
export type ConsultationSuggestionValue =
diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx
index 796e532a41e..f6f83e7030f 100644
--- a/src/Components/Assets/AssetsList.tsx
+++ b/src/Components/Assets/AssetsList.tsx
@@ -105,40 +105,63 @@ const AssetsList = () => {
prefetch: !!(qParams.facility && qParams.location),
});
- const getAssetIdFromQR = async (assetUrl: string) => {
+ function isValidURL(url: string) {
+ try {
+ new URL(url);
+ return true;
+ } catch (_) {
+ return false;
+ }
+ }
+
+ const accessAssetIdFromQR = async (assetURL: string) => {
try {
setIsLoading(true);
setIsScannerActive(false);
- const params = parseQueryParams(assetUrl);
+ if (!isValidURL(assetURL)) {
+ setIsLoading(false);
+ Notification.Error({
+ msg: "Invalid QR code scanned !!!",
+ });
+ return;
+ }
+ const params = parseQueryParams(assetURL);
// QR Maybe searchParams "asset" or "assetQR"
+ // If no params found, then use assetText
const assetId = params.asset || params.assetQR;
+
if (assetId) {
- const { data } = await request(routes.listAssets, {
- query: { qr_code_id: assetId },
+ const { data } = await request(routes.listAssetQR, {
+ pathParams: { qr_code_id: assetId },
+ });
+ if (!data) {
+ setIsLoading(false);
+ Notification.Error({
+ msg: "Invalid QR code scanned !!!",
+ });
+ return;
+ }
+ const { data: assetData } = await request(routes.listAssets, {
+ query: { qr_code_id: assetId, limit: 1 },
+ });
+ if (assetData?.results.length === 1) {
+ navigate(
+ `/facility/${assetData.results[0].location_object.facility?.id}/assets/${assetData.results[0].id}`,
+ );
+ } else {
+ setIsLoading(false);
+ Notification.Error({
+ msg: "Asset not found !!!",
+ });
+ }
+ } else {
+ setIsLoading(false);
+ Notification.Error({
+ msg: "Invalid QR code scanned !!!",
});
- return data?.results[0].id;
- }
- } catch (err) {
- console.log(err);
- }
- };
-
- const checkValidAssetId = async (assetId: string) => {
- const { data: assetData } = await request(routes.getAsset, {
- pathParams: { external_id: assetId },
- });
- try {
- if (assetData) {
- navigate(
- `/facility/${assetData.location_object.facility?.id}/assets/${assetId}`,
- );
}
} catch (err) {
console.log(err);
- setIsLoading(false);
- Notification.Error({
- msg: "Invalid QR code scanned !!!",
- });
}
};
@@ -159,8 +182,7 @@ const AssetsList = () => {
{
if (text) {
- const assetId = await getAssetIdFromQR(text);
- checkValidAssetId(assetId ?? text);
+ await accessAssetIdFromQR(text);
}
}}
onError={(e) => {
diff --git a/src/Components/Assets/configure/CameraConfigure.tsx b/src/Components/Assets/configure/CameraConfigure.tsx
index c3ba434ef3f..5a8ccd5c184 100644
--- a/src/Components/Assets/configure/CameraConfigure.tsx
+++ b/src/Components/Assets/configure/CameraConfigure.tsx
@@ -7,6 +7,7 @@ import { getCameraConfig } from "../../../Utils/transformUtils";
import { Submit } from "../../Common/components/ButtonV2";
import TextFormField from "../../Form/FormFields/TextFormField";
import Card from "../../../CAREUI/display/Card";
+import { FieldErrorText } from "../../Form/FormFields/FormField";
interface CameraConfigureProps {
asset: AssetData;
@@ -59,8 +60,14 @@ export default function CameraConfigure(props: CameraConfigureProps) {
value={newPreset}
className="mt-1"
onChange={(e) => setNewPreset(e.value)}
- error=""
+ errorClassName="hidden"
/>
+ {newPreset.length > 12 && (
+
+ )}
diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx
index 715c326c35d..f970a920abc 100644
--- a/src/Components/CameraFeed/AssetBedSelect.tsx
+++ b/src/Components/CameraFeed/AssetBedSelect.tsx
@@ -15,17 +15,17 @@ export default function CameraPresetSelect(props: Props) {
const label = props.label ?? defaultLabel;
return (
<>
-
+
{/* Desktop View */}
{props.options
.slice(0, props.options.length > 5 ? 4 : 5)
.map((option) => (
))}
{props.options.length > 5 && (
-
+ o.id === props.value?.id)}
+ />
)}
-
+
{/* Mobile View */}
-
+
>
);
}
-export const CameraPresetDropdown = (props: Props) => {
+export const CameraPresetDropdown = (
+ props: Props & { placeholder: string },
+) => {
const selected = props.value;
const options = props.options.filter(({ meta }) => meta.type !== "boundary");
@@ -52,19 +59,29 @@ export const CameraPresetDropdown = (props: Props) => {
const label = props.label ?? defaultLabel;
return (
-
+
-
-
- {selected ? label(selected) : "Select preset"}
+
+
+ {options.length === 0
+ ? "No presets"
+ : selected
+ ? label(selected)
+ : props.placeholder}
-
-
+
+
{
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
-
+
{options?.map((obj) => (
{
{({ selected }) => (
<>
diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx
index 81b526363b9..1c6781ee51b 100644
--- a/src/Components/CameraFeed/CameraFeed.tsx
+++ b/src/Components/CameraFeed/CameraFeed.tsx
@@ -4,7 +4,7 @@ import useOperateCamera, { PTZPayload } from "./useOperateCamera";
import usePlayer from "./usePlayer";
import { getStreamUrl } from "./utils";
import ReactPlayer from "react-player";
-import { classNames, isIOS } from "../../Utils/utils";
+import { classNames, isAppleDevice, isIOS } from "../../Utils/utils";
import FeedAlert, { FeedAlertState } from "./FeedAlert";
import FeedNetworkSignal from "./FeedNetworkSignal";
import NoFeedAvailable from "./NoFeedAvailable";
@@ -12,6 +12,7 @@ import FeedControls from "./FeedControls";
import Fullscreen from "../../CAREUI/misc/Fullscreen";
import FeedWatermark from "./FeedWatermark";
import CareIcon from "../../CAREUI/icons/CareIcon";
+import { Error } from "../../Utils/Notifications";
interface Props {
children?: React.ReactNode;
@@ -27,6 +28,7 @@ interface Props {
constrolsDisabled?: boolean;
shortcutsDisabled?: boolean;
onMove?: () => void;
+ onReset?: () => void;
}
export default function CameraFeed(props: Props) {
@@ -86,34 +88,51 @@ export default function CameraFeed(props: Props) {
const resetStream = () => {
setState("loading");
+ props.onReset?.();
initializeStream();
};
return (
- setFullscreen(false)}>
+ {
+ setFullscreen(false);
+
+ if (reason === "DEVICE_UNSUPPORTED") {
+ // iOS webkit allows only video/iframe elements to call full-screen
+ // APIs. But we need to show controls too, not just the video element.
+ Error({
+ msg: "This device does not support viewing this content in full-screen.",
+ });
+ }
+ }}
+ >
-
+
{props.children}
-
+
{props.asset.name}
-
-
-
+ {!isIOS && (
+
+
+
+ )}
@@ -170,7 +189,7 @@ export default function CameraFeed(props: Props) {
) : (
diff --git a/src/Components/CameraFeed/FeedAlert.tsx b/src/Components/CameraFeed/FeedAlert.tsx
index a4f8a3beb18..138af509c8c 100644
--- a/src/Components/CameraFeed/FeedAlert.tsx
+++ b/src/Components/CameraFeed/FeedAlert.tsx
@@ -15,12 +15,12 @@ interface Props {
state?: FeedAlertState;
}
-const ALERT_ICON_MAP: Record
= {
+const ALERT_ICON_MAP: Partial> = {
playing: "l-play-circle",
stop: "l-stop-circle",
offline: "l-exclamation-triangle",
loading: "l-spinner",
- moving: "l-expand-from-corner",
+ // moving: "l-expand-from-corner",
zooming: "l-search",
saving_preset: "l-save",
host_unreachable: "l-exclamation-triangle",
@@ -53,14 +53,14 @@ export default function FeedAlert({ state }: Props) {
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 -translate-y-5"
>
-
- {state && (
+
+ {state && ALERT_ICON_MAP[state] && (
)}
diff --git a/src/Components/CameraFeed/FeedWatermark.tsx b/src/Components/CameraFeed/FeedWatermark.tsx
index e80c756ba3f..90ca2d15863 100644
--- a/src/Components/CameraFeed/FeedWatermark.tsx
+++ b/src/Components/CameraFeed/FeedWatermark.tsx
@@ -8,12 +8,12 @@ export default function FeedWatermark() {
{me.username}
-
+ {/*
{me.username}
{me.username}
-
+ */}
{me.username}
@@ -47,7 +47,7 @@ const Watermark = (props: { children: string; className: string }) => {
return (
{props.children}
diff --git a/src/Components/Common/SortDropdown.tsx b/src/Components/Common/SortDropdown.tsx
index b29662de0a7..8ffd09ba269 100644
--- a/src/Components/Common/SortDropdown.tsx
+++ b/src/Components/Common/SortDropdown.tsx
@@ -23,8 +23,9 @@ export default function SortDropdownMenu(props: Props) {
}
+ containerClassName="w-full md:w-auto"
>
{props.options.map(({ isAscending, value }) => (
d?.id === selected?.id);
- const { data, loading, refetch } = useQuery(routes.listICD11Diagnosis);
+ const { res, data, loading, refetch } = useQuery(routes.listICD11Diagnosis, {
+ silent: true,
+ });
+
+ useEffect(() => {
+ if (res?.status === 500) {
+ Error({ msg: "ICD-11 Diagnosis functionality is facing issues." });
+ }
+ }, [res?.status]);
const handleAdd = async (status: CreateDiagnosis["verification_status"]) => {
if (!selected) return;
diff --git a/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisEntry.tsx b/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisEntry.tsx
index c2354503bea..d431890cd17 100644
--- a/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisEntry.tsx
+++ b/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisEntry.tsx
@@ -80,9 +80,11 @@ export default function ConsultationDiagnosisEntry(props: Props) {
? "font-semibold text-primary-500"
: "font-normal",
!isActive && "text-gray-500 line-through",
+ !object.diagnosis_object?.label && "italic text-gray-500",
)}
>
- {object.diagnosis_object?.label}
+ {object.diagnosis_object?.label ||
+ "Unable to retrieve this ICD-11 diagnosis at the moment"}
diff --git a/src/Components/Diagnosis/DiagnosesListAccordion.tsx b/src/Components/Diagnosis/DiagnosesListAccordion.tsx
index 0e6d62774c7..ec641d720b5 100644
--- a/src/Components/Diagnosis/DiagnosesListAccordion.tsx
+++ b/src/Components/Diagnosis/DiagnosesListAccordion.tsx
@@ -4,7 +4,7 @@ import {
ConsultationDiagnosis,
} from "./types";
import { useTranslation } from "react-i18next";
-import { compareBy } from "../../Utils/utils";
+import { classNames, compareBy } from "../../Utils/utils";
import { useState } from "react";
import CareIcon from "../../CAREUI/icons/CareIcon";
import ButtonV2 from "../Common/components/ButtonV2";
@@ -96,7 +96,14 @@ const DiagnosesOfStatus = ({ diagnoses }: Props) => {
{diagnoses.map((diagnosis) => (
-
- {diagnosis.diagnosis_object?.label}
+
+ {diagnosis.diagnosis_object?.label ||
+ "Unable to resolve ICD-11 diagnosis at the moment"}
+
))}
diff --git a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx
index 40e5ffdc610..91cd86c606e 100644
--- a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx
+++ b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx
@@ -16,7 +16,7 @@ import useOperateCamera, {
PTZPayload,
} from "../../CameraFeed/useOperateCamera";
import request from "../../../Utils/request/request";
-import { classNames } from "../../../Utils/utils";
+import { classNames, isIOS } from "../../../Utils/utils";
export const ConsultationFeedTab = (props: ConsultationTabProps) => {
const authUser = useAuthUser();
@@ -27,6 +27,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
const [preset, setPreset] = useState
();
const [isUpdatingPreset, setIsUpdatingPreset] = useState(false);
const [hasMoved, setHasMoved] = useState(false);
+ const [key, setKey] = useState(0);
const divRef = useRef();
const operate = useOperateCamera(asset?.id ?? "", true);
@@ -100,11 +101,21 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
hideBack={true}
focusOnLoad={false}
/>
+
+
+ For better experience, rotate your device.
+
setHasMoved(true)}
+ onReset={() => {
+ if (isIOS) {
+ setKey(key + 1);
+ }
+ }}
onStreamError={() => {
triggerGoal("Camera Feed Viewed", {
consultationId: props.consultationId,
@@ -161,7 +172,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => {
diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx
index be2751f0033..7a4080f53dd 100644
--- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx
+++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx
@@ -311,59 +311,6 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
)}
- {((props.patientData.is_antenatal &&
- isAntenatal(props.patientData.last_menstruation_start_date)) ||
- isPostPartum(props.patientData.date_of_delivery)) && (
-
-
- Perinatal Status
-
-
-
- {props.patientData.is_antenatal &&
- isAntenatal(
- props.patientData.last_menstruation_start_date,
- ) && (
-
- )}
- {isPostPartum(props.patientData.date_of_delivery) && (
-
- )}
-
-
- {props.patientData.last_menstruation_start_date && (
-
-
- Last Menstruation:
-
- {formatDate(
- props.patientData.last_menstruation_start_date,
- )}
-
-
- )}
-
- {props.patientData.date_of_delivery && (
-
-
- Date of Delivery:
-
- {formatDate(props.patientData.date_of_delivery)}
-
-
- )}
-
- )}
@@ -637,6 +584,59 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
+ {((props.patientData.is_antenatal &&
+ isAntenatal(props.patientData.last_menstruation_start_date)) ||
+ isPostPartum(props.patientData.date_of_delivery)) && (
+
+
+ Perinatal Status
+
+
+
+ {props.patientData.is_antenatal &&
+ isAntenatal(
+ props.patientData.last_menstruation_start_date,
+ ) && (
+
+ )}
+ {isPostPartum(props.patientData.date_of_delivery) && (
+
+ )}
+
+
+ {props.patientData.last_menstruation_start_date && (
+
+
+ Last Menstruation:
+
+ {formatDate(
+ props.patientData.last_menstruation_start_date,
+ )}
+
+
+ )}
+
+ {props.patientData.date_of_delivery && (
+
+
+ Date of Delivery:
+
+ {formatDate(props.patientData.date_of_delivery)}
+
+
+ )}
+
+ )}
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx
index ee1f7bda7ff..e46db70189a 100644
--- a/src/Components/Facility/ConsultationDetails/index.tsx
+++ b/src/Components/Facility/ConsultationDetails/index.tsx
@@ -37,6 +37,7 @@ import PatientInfoCard from "../../Patient/PatientInfoCard";
import RelativeDateUserMention from "../../Common/RelativeDateUserMention";
import DiagnosesListAccordion from "../../Diagnosis/DiagnosesListAccordion";
import { CameraFeedPermittedUserTypes } from "../../../Utils/permissions";
+import Error404 from "../../ErrorPages/404";
const Loading = lazy(() => import("../../Common/Loading"));
const PageTitle = lazy(() => import("../../Common/PageTitle"));
@@ -68,7 +69,10 @@ const TABS = {
export const ConsultationDetails = (props: any) => {
const { facilityId, patientId, consultationId } = props;
- const tab = props.tab.toUpperCase() as keyof typeof TABS;
+ let tab = undefined;
+ if (Object.keys(TABS).includes(props.tab.toUpperCase())) {
+ tab = props.tab.toUpperCase() as keyof typeof TABS;
+ }
const dispatch: any = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [showDoctors, setShowDoctors] = useState(false);
@@ -194,6 +198,10 @@ export const ConsultationDetails = (props: any) => {
patientData,
};
+ if (!tab) {
+ return ;
+ }
+
const SelectedTab = TABS[tab];
if (isLoading) {
diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx
index d362abe1ecc..6e1a976de35 100644
--- a/src/Components/Facility/ConsultationForm.tsx
+++ b/src/Components/Facility/ConsultationForm.tsx
@@ -1148,6 +1148,14 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
options={CONSULTATION_SUGGESTION.filter(
(option) => !("deprecated" in option),
)}
+ optionDisabled={(option) =>
+ isUpdate && "editDisabled" in option
+ }
+ optionDescription={(option) =>
+ isUpdate && "editDisabled" in option
+ ? t("encounter_suggestion_edit_disallowed")
+ : undefined
+ }
/>
@@ -1351,7 +1359,11 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
>
Procedures
{
handleFormFieldChange({
name: "procedure",
diff --git a/src/Components/Facility/DoctorVideoSlideover.tsx b/src/Components/Facility/DoctorVideoSlideover.tsx
index a77a1017b6b..140de6985c1 100644
--- a/src/Components/Facility/DoctorVideoSlideover.tsx
+++ b/src/Components/Facility/DoctorVideoSlideover.tsx
@@ -172,11 +172,11 @@ function UserListItem({ user }: { user: UserAnnotatedWithGroup }) {
function connectOnWhatsApp(e: React.MouseEvent) {
e.stopPropagation();
if (!user.alt_phone_number) return;
- const phoneNumber = user.alt_phone_number;
+ const phoneNumber = user.alt_phone_number?.replace(/\D+/g, "");
const message = `${courtesyTitle(user)} ${user.first_name} ${user.last_name}, I have a query regarding a patient.\n\nPatient Link: ${window.location.href}`;
const encodedMessage = encodeURIComponent(message);
const whatsappAppURL = `whatsapp://send?phone=${phoneNumber}&text=${encodedMessage}`;
- const whatsappWebURL = `https://web.whatsapp.com/send?phone=${phoneNumber}&text=${encodedMessage}`;
+ const whatsappWebURL = `https://wa.me/${phoneNumber}?text=${encodedMessage}`;
const userAgent = navigator.userAgent;
const isEdge = /edge\/\d+/i.test(userAgent);
diff --git a/src/Components/Form/FormFields/Autocomplete.tsx b/src/Components/Form/FormFields/Autocomplete.tsx
index e414fda233a..67b1224cddf 100644
--- a/src/Components/Form/FormFields/Autocomplete.tsx
+++ b/src/Components/Form/FormFields/Autocomplete.tsx
@@ -17,6 +17,7 @@ type AutocompleteFormFieldProps = FormFieldBaseProps & {
optionValue?: OptionCallback;
optionDescription?: OptionCallback;
optionIcon?: OptionCallback;
+ optionDisabled?: OptionCallback;
onQuery?: (query: string) => void;
dropdownIcon?: React.ReactNode | undefined;
isLoading?: boolean;
@@ -43,6 +44,7 @@ const AutocompleteFormField = (
optionIcon={props.optionIcon}
optionValue={props.optionValue}
optionDescription={props.optionDescription}
+ optionDisabled={props.optionDisabled}
onQuery={props.onQuery}
isLoading={props.isLoading}
allowRawInput={props.allowRawInput}
@@ -65,6 +67,7 @@ type AutocompleteProps = {
optionIcon?: OptionCallback;
optionValue?: OptionCallback;
optionDescription?: OptionCallback;
+ optionDisabled?: OptionCallback;
className?: string;
onQuery?: (query: string) => void;
requiredError?: boolean;
@@ -105,6 +108,7 @@ export const Autocomplete = (props: AutocompleteProps) => {
search: label.toLowerCase(),
icon: props.optionIcon?.(option),
value: props.optionValue ? props.optionValue(option) : option,
+ disabled: props.optionDisabled?.(option),
};
});
@@ -123,6 +127,7 @@ export const Autocomplete = (props: AutocompleteProps) => {
search: query.toLowerCase(),
icon: ,
value: query,
+ disabled: undefined,
},
...mappedOptions,
];
@@ -204,6 +209,7 @@ export const Autocomplete = (props: AutocompleteProps) => {
key={`${props.id}-option-${option.label}-value-${index}`}
className={dropdownOptionClassNames}
value={option}
+ disabled={option.disabled}
>
{({ active }) => (
@@ -214,8 +220,12 @@ export const Autocomplete =
(props: AutocompleteProps) => {
{option.description && (
{option.description}
diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx
index 3bdbffdc6cb..7857e96889d 100644
--- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx
+++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx
@@ -17,6 +17,7 @@ type AutocompleteMultiSelectFormFieldProps
= FormFieldBaseProps & {
options: T[];
optionLabel: OptionCallback;
optionValue?: OptionCallback;
+ optionDisabled?: OptionCallback;
onQuery?: (query: string) => void;
dropdownIcon?: React.ReactNode | undefined;
isLoading?: boolean;
@@ -50,6 +51,7 @@ type AutocompleteMutliSelectProps = {
optionDescription?: OptionCallback;
optionLabel: OptionCallback;
optionValue?: OptionCallback;
+ optionDisabled?: OptionCallback;
className?: string;
onChange: OptionCallback;
onQuery?: (query: string) => void;
@@ -87,6 +89,7 @@ export const AutocompleteMutliSelect = (
description: props.optionDescription?.(option),
search: label.toLowerCase(),
value: (props.optionValue ? props.optionValue(option) : option) as V,
+ disabled: props.optionDisabled?.(option),
};
});
@@ -187,8 +190,9 @@ export const AutocompleteMutliSelect = (
onClick={() => {
handleSingleSelect(option);
}}
+ disabled={option.disabled}
>
- {({ selected }) => (
+ {({ active, selected }) => (
<>
{option.label}
@@ -198,9 +202,14 @@ export const AutocompleteMutliSelect = (
{option.description && (
{option.description}
diff --git a/src/Components/Form/FormFields/SelectFormField.tsx b/src/Components/Form/FormFields/SelectFormField.tsx
index 42ba82fa5de..47c28777368 100644
--- a/src/Components/Form/FormFields/SelectFormField.tsx
+++ b/src/Components/Form/FormFields/SelectFormField.tsx
@@ -14,6 +14,7 @@ type SelectFormFieldProps = FormFieldBaseProps & {
optionDescription?: OptionCallback;
optionIcon?: OptionCallback;
optionValue?: OptionCallback;
+ optionDisabled?: OptionCallback;
};
export const SelectFormField = (props: SelectFormFieldProps) => {
@@ -34,6 +35,7 @@ export const SelectFormField = (props: SelectFormFieldProps) => {
optionDescription={props.optionDescription}
optionIcon={props.optionIcon}
optionValue={props.optionValue}
+ optionDisabled={props.optionDisabled}
requiredError={field.error ? props.required : false}
/>
@@ -48,6 +50,7 @@ type MultiSelectFormFieldProps = FormFieldBaseProps & {
optionDescription?: OptionCallback;
optionIcon?: OptionCallback;
optionValue?: OptionCallback;
+ optionDisabled?: OptionCallback;
};
export const MultiSelectFormField = (
@@ -67,6 +70,7 @@ export const MultiSelectFormField = (
optionSelectedLabel={props.optionSelectedLabel}
optionDescription={props.optionDescription}
optionIcon={props.optionIcon}
+ optionDisabled={props.optionDisabled}
optionValue={props.optionValue}
/>
diff --git a/src/Components/Form/MultiSelectMenuV2.tsx b/src/Components/Form/MultiSelectMenuV2.tsx
index 1568c3f6e84..419664f98e9 100644
--- a/src/Components/Form/MultiSelectMenuV2.tsx
+++ b/src/Components/Form/MultiSelectMenuV2.tsx
@@ -16,6 +16,7 @@ type Props = {
optionDescription?: OptionCallback;
optionIcon?: OptionCallback;
optionValue?: OptionCallback;
+ optionDisabled?: OptionCallback;
className?: string;
disabled?: boolean;
renderSelectedOptions?: OptionCallback;
@@ -42,9 +43,10 @@ const MultiSelectMenuV2 = (props: Props) => {
option,
label,
selectedLabel,
- description: props.optionDescription && props.optionDescription(option),
- icon: props.optionIcon && props.optionIcon(option),
+ description: props.optionDescription?.(option),
+ icon: props.optionIcon?.(option),
value,
+ disabled: props.optionDisabled?.(option),
isSelected: props.value?.includes(value as any) ?? false,
displayChip: (
@@ -138,6 +140,7 @@ const MultiSelectMenuV2 =
(props: Props) => {
className={dropdownOptionClassNames}
value={option}
onClick={() => handleSingleSelect(option)}
+ disabled={option.disabled}
>
{({ active }) => (
@@ -152,9 +155,14 @@ const MultiSelectMenuV2 = (props: Props) => {
{option.description && (
{option.description}
@@ -205,17 +213,20 @@ export const MultiSelectOptionChip = ({
interface OptionRenderPropArg {
active: boolean;
selected: boolean;
+ disabled: boolean;
}
export const dropdownOptionClassNames = ({
active,
selected,
+ disabled,
}: OptionRenderPropArg) => {
return classNames(
"group/option relative w-full cursor-default select-none p-4 text-sm transition-colors duration-75 ease-in-out",
- active && "bg-primary-500 text-white",
- !active && selected && "text-primary-500",
- !active && !selected && "text-gray-900",
+ !disabled && active && "bg-primary-500 text-white",
+ !disabled && !active && selected && "text-primary-500",
+ !disabled && !active && !selected && "text-gray-900",
+ disabled && "cursor-not-allowed text-gray-800",
selected ? "font-semibold" : "font-normal",
);
};
diff --git a/src/Components/Form/SelectMenuV2.tsx b/src/Components/Form/SelectMenuV2.tsx
index 03e0312c602..4483c92fe4c 100644
--- a/src/Components/Form/SelectMenuV2.tsx
+++ b/src/Components/Form/SelectMenuV2.tsx
@@ -19,6 +19,7 @@ type SelectMenuProps = {
optionDescription?: OptionCallback;
optionIcon?: OptionCallback;
optionValue?: OptionCallback;
+ optionDisabled?: OptionCallback;
showIconWhenSelected?: boolean;
showChevronIcon?: boolean;
className?: string;
@@ -51,9 +52,10 @@ const SelectMenuV2 = (props: SelectMenuProps) => {
selectedLabel: props.optionSelectedLabel
? props.optionSelectedLabel(option)
: label,
- description: props.optionDescription && props.optionDescription(option),
- icon: props.optionIcon && props.optionIcon(option),
+ description: props.optionDescription?.(option),
+ icon: props.optionIcon?.(option),
value: props.optionValue ? props.optionValue(option) : option,
+ disabled: props.optionDisabled?.(option),
};
});
@@ -67,6 +69,7 @@ const SelectMenuV2 = (props: SelectMenuProps) => {
description: undefined,
icon: undefined,
value: undefined,
+ disabled: undefined,
};
const options = props.required
@@ -128,6 +131,7 @@ const SelectMenuV2 = (props: SelectMenuProps) => {
key={index}
className={dropdownOptionClassNames}
value={option}
+ disabled={option.disabled}
>
{({ active, selected }) => (
@@ -144,9 +148,14 @@ const SelectMenuV2 = (props: SelectMenuProps) => {
{option.description && (
{option.description}
diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx
index d0b2e321898..35e9828f9d1 100644
--- a/src/Components/Patient/DailyRounds.tsx
+++ b/src/Components/Patient/DailyRounds.tsx
@@ -502,6 +502,11 @@ export const DailyRounds = (props: any) => {
+
+ Symptoms
+
+
+
{
rows={5}
/>
-
- Symptoms
-
-
-
{state.form.rounds_type !== "DOCTORS_LOG" && (
<>
{
{state.form.rounds_type === "DOCTORS_LOG" && (
<>
+
+
+ {t("diagnosis")}
+
+ {/* */}
+ {diagnoses ? (
+
+ ) : (
+
+ Fetching existing diagnosis of patient...
+
+ )}
+
{t("investigations")}
@@ -701,19 +714,6 @@ export const DailyRounds = (props: any) => {
-
-
- {t("diagnosis")}
-
- {/* */}
- {diagnoses ? (
-
- ) : (
-
- Fetching existing diagnosis of patient...
-
- )}
-
>
)}
diff --git a/src/Components/Patient/DiagnosesFilter.tsx b/src/Components/Patient/DiagnosesFilter.tsx
index e8bd1afb722..c4c4872fdda 100644
--- a/src/Components/Patient/DiagnosesFilter.tsx
+++ b/src/Components/Patient/DiagnosesFilter.tsx
@@ -7,6 +7,7 @@ import useQuery from "../../Utils/request/useQuery";
import routes from "../../Redux/api";
import { mergeQueryOptions } from "../../Utils/utils";
import { debounce } from "lodash-es";
+import { Error } from "../../Utils/Notifications";
export const FILTER_BY_DIAGNOSES_KEYS = [
"diagnoses",
@@ -34,7 +35,15 @@ interface Props {
export default function DiagnosesFilter(props: Props) {
const { t } = useTranslation();
const [diagnoses, setDiagnoses] = useState([]);
- const { data, loading, refetch } = useQuery(routes.listICD11Diagnosis);
+ const { res, data, loading, refetch } = useQuery(routes.listICD11Diagnosis, {
+ silent: true,
+ });
+
+ useEffect(() => {
+ if (res?.status === 500) {
+ Error({ msg: "ICD-11 Diagnosis functionality is facing issues." });
+ }
+ }, [res?.status]);
useEffect(() => {
if (!props.value) {
diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx
index a99d12c09fc..ae227bbbb6b 100644
--- a/src/Components/Patient/ManagePatients.tsx
+++ b/src/Components/Patient/ManagePatients.tsx
@@ -821,7 +821,7 @@ export const PatientManager = () => {
selected={qParams.ordering}
onSelect={updateQuery}
/>
-
+
{!isExportAllowed ? (
{
diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx
index bdf71648e5e..630d0823309 100644
--- a/src/Components/Patient/PatientHome.tsx
+++ b/src/Components/Patient/PatientHome.tsx
@@ -297,7 +297,7 @@ export const PatientHome = (props: any) => {
{patientData?.last_consultation?.assigned_to_object
.alt_phone_number && (
@@ -443,7 +443,7 @@ export const PatientHome = (props: any) => {
= ({ fields, onFormUpdate }) => {
const [open, setOpen] = useState(false);
const [_progress, setProgress] = useState(0);
const [stage, setStage] = useState("start");
- const [editableTranscript, setEditableTranscript] = useState("");
+ const [_editableTranscript, setEditableTranscript] = useState("");
const [errors, setErrors] = useState([]);
const [isTranscribing, setIsTranscribing] = useState(false);
const [formFields, setFormFields] = useState<{
@@ -70,6 +70,13 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
const [isAudioUploading, setIsAudioUploading] = useState(false);
const [updatedTranscript, setUpdatedTranscript] = useState("");
const [scribeID, setScribeID] = useState("");
+ const stageRef = useRef(stage);
+
+ useEffect(() => {
+ if (stageRef.current === "cancelled") {
+ setStage("start");
+ }
+ }, [stage]);
const {
isRecording,
@@ -81,6 +88,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
const uploadAudioData = (response: any, audioBlob: Blob) => {
return new Promise((resolve, reject) => {
+ if (stageRef.current === "cancelled") resolve();
const url = response.data.signed_url;
const internal_name = response.data.internal_name;
const f = audioBlob;
@@ -109,6 +117,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
const uploadAudio = async (audioBlob: Blob, associatingId: string) => {
return new Promise((resolve, reject) => {
+ if (stageRef.current === "cancelled") resolve({});
const category = "AUDIO";
const name = "audio.mp3";
const filename = Date.now().toString();
@@ -127,6 +136,8 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
uploadAudioData(response, audioBlob)
.then(() => {
if (!response?.data?.id) throw Error("Error uploading audio");
+
+ if (stageRef.current === "cancelled") resolve({});
markUploadComplete(response?.data.id, associatingId).then(() => {
resolve(response.data);
});
@@ -145,12 +156,14 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
startRecording();
setProgress(25);
setStage("recording");
+ stageRef.current = "recording";
};
const handleStopRecordingClick = () => {
stopRecording();
setProgress(50);
setStage("recording-review");
+ stageRef.current = "recording-end";
};
const handleEditChange = (e: {
@@ -161,6 +174,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
const waitForTranscript = async (externalId: string): Promise => {
return new Promise((resolve, reject) => {
+ if (stageRef.current === "cancelled") resolve("");
const interval = setInterval(async () => {
try {
const res = await request(routes.getScribe, {
@@ -191,9 +205,29 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
});
};
+ const reProcessTranscript = async () => {
+ setErrors([]);
+ setEditableTranscript(updatedTranscript);
+ setStage("review");
+ const res = await request(routes.updateScribe, {
+ body: {
+ status: "READY",
+ transcript: updatedTranscript,
+ ai_response: null,
+ },
+ pathParams: {
+ external_id: scribeID,
+ },
+ });
+ if (res.error || !res.data) throw Error("Error updating scribe instance");
+ setStage("review");
+ await handleSubmitTranscript(res.data.external_id);
+ };
+
const waitForFormData = async (externalId: string): Promise => {
return new Promise((resolve, reject) => {
const interval = setInterval(async () => {
+ if (stageRef.current === "cancelled") resolve("");
try {
const res = await request(routes.getScribe, {
pathParams: {
@@ -270,12 +304,14 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
};
const handleSubmitTranscript = async (external_id: string) => {
+ if (stageRef.current === "cancelled") return;
setProgress(75);
setIsGPTProcessing(true);
try {
const updatedFieldsResponse = await waitForFormData(external_id);
setProgress(100);
const parsedFormData = JSON.parse(updatedFieldsResponse ?? "{}");
+ if (stageRef.current === "cancelled") return;
setFormFields(parsedFormData);
onFormUpdate(parsedFormData);
setStage("final-review");
@@ -286,6 +322,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
};
const startTranscription = async (scribeID: string) => {
+ if (stageRef.current === "cancelled") return;
setIsTranscribing(true);
const errors = [];
try {
@@ -302,6 +339,7 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
//poll for transcript
const transcriptResponse = await waitForTranscript(res.data.external_id);
+ if (stageRef.current === "cancelled") return;
setEditableTranscript(transcriptResponse);
setUpdatedTranscript(transcriptResponse);
setStage("review");
@@ -329,6 +367,10 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
setFormFields({});
setEditableTranscript("");
setUpdatedTranscript("");
+ setIsAudioUploading(false);
+ setScribeID("");
+ setIsHoveringCancelRecord(false);
+ stageRef.current = "cancelled";
};
const processFormField = (
@@ -485,44 +527,47 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
src={URL.createObjectURL(blob)}
/>
))}
+ {
+ handleRerecordClick();
+ }}
+ className="w-full"
+ >
+ Restart Recording
+
)}
{(stage === "review" || stage === "final-review") && (
-
- Transcript
-
+
+
+ Transcript
+
+
+ (Edit if needed)
+
+
- {updatedTranscript !== editableTranscript && (
-
{
- setErrors([]);
- setEditableTranscript(updatedTranscript);
- setStage("review");
- const res = await request(routes.updateScribe, {
- body: {
- status: "READY",
- transcript: updatedTranscript,
- },
- pathParams: {
- external_id: scribeID,
- },
- });
- if (res.error || !res.data)
- throw Error("Error updating scribe instance");
- setStage("review");
- await handleSubmitTranscript(res.data.external_id);
- }}
- className="my-2 w-full"
- >
- Process Transcript
-
- )}
+
+
+ Process Transcript
+
+
{
+ setStage("recording-review");
+ }}
+ className="mb-2 w-full"
+ >
+ Regenerate Transcript
+
{stage === "review" && (
{getStageMessage(stage)}
@@ -568,10 +613,18 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
)}
{stage === "final-review" && (
-
+
{getStageMessage(stage)}
)}
+ {stage === "final-review" && !isGPTProcessing && (
+
+ Reobtain Form Data
+
+ )}
)}
diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx
index 9669032f14c..99a88b20600 100644
--- a/src/Components/Users/ManageUsers.tsx
+++ b/src/Components/Users/ManageUsers.tsx
@@ -447,7 +447,11 @@ export default function ManageUsers() {
} else if (userListData?.results && userListData?.results.length === 0) {
manageUsers = (
-
{t("no_users_found")}
+
);
}
diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json
index 42f31433495..fc1b5ae5f7d 100644
--- a/src/Locale/en/Consultation.json
+++ b/src/Locale/en/Consultation.json
@@ -34,5 +34,6 @@
"generate_report": "Generate Report",
"prev_sessions": "Prev Sessions",
"next_sessions": "Next Sessions",
- "no_changes": "No changes"
+ "no_changes": "No changes",
+ "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation"
}
\ No newline at end of file
diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx
index f4a9732a40c..439b122ca09 100644
--- a/src/Redux/api.tsx
+++ b/src/Redux/api.tsx
@@ -1286,6 +1286,11 @@ const routes = {
method: "GET",
TRes: Type
>(),
},
+ listAssetQR: {
+ path: "/api/v1/public/asset_qr/{qr_code_id}/",
+ method: "GET",
+ TRes: Type(),
+ },
// Asset transaction endpoints