diff --git a/SECURITY.md b/SECURITY.md
index 0b3730ed9ad..03d8dec4837 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -23,4 +23,4 @@
## Reporting a Vulnerability
-Please create a ticket at https://support.coronasafe.network
+Please create an issue at https://github.com/coronasafe/care_fe/issues/new
diff --git a/cypress/e2e/patient_spec/patient_consultation.cy.ts b/cypress/e2e/patient_spec/patient_consultation.cy.ts
index 8caea111ca1..eef98a5e3ce 100644
--- a/cypress/e2e/patient_spec/patient_consultation.cy.ts
+++ b/cypress/e2e/patient_spec/patient_consultation.cy.ts
@@ -59,14 +59,14 @@ describe("Patient Consultation in multiple combination", () => {
patientConsultationPage.selectConsultationStatus(
"Outpatient/Emergency Room",
);
- cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC");
+ cy.get("#is_asymptomatic").click();
patientConsultationPage.typePatientIllnessHistory(patientIllnessHistory);
patientConsultationPage.typePatientExaminationHistory(
patientExaminationHistory,
);
patientConsultationPage.typePatientWeight(patientWeight);
patientConsultationPage.typePatientHeight(patientHeight);
- patientConsultationPage.selectPatientCategory("Stable");
+ patientConsultationPage.selectPatientCategory("Mild");
// icd 11 - 4 diagnosis with one principal
patientConsultationPage.selectPatientDiagnosis(
diagnosis1,
@@ -175,7 +175,7 @@ describe("Patient Consultation in multiple combination", () => {
"Outpatient/Emergency Room",
);
// Asymptomatic
- cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC");
+ cy.get("#is_asymptomatic").click();
// CRITICAL category
patientConsultationPage.selectPatientCategory("Critical");
patientConsultationPage.selectPatientSuggestion("Declare Death");
@@ -234,9 +234,9 @@ describe("Patient Consultation in multiple combination", () => {
);
patientConsultationPage.selectPatientWard("Dummy Location 1");
// Asymptomatic
- cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC");
+ cy.get("#is_asymptomatic").click();
// Abnormal category
- patientConsultationPage.selectPatientCategory("Abnormal");
+ patientConsultationPage.selectPatientCategory("Moderate");
patientConsultationPage.selectPatientSuggestion("Domiciliary Care");
// one ICD-11 diagnosis
patientConsultationPage.selectPatientDiagnosis(
@@ -293,18 +293,14 @@ describe("Patient Consultation in multiple combination", () => {
);
// verify the free text in referring facility name
patientConsultationPage.typeReferringFacility("Life Care Hospital");
- // Vomiting and Nausea symptoms
+ patientConsultationPage.selectSymptomsDate("01012024");
patientConsultationPage.typeAndMultiSelectSymptoms("s", [
- "SPUTUM",
- "SORE THROAT",
+ "Sore throat",
+ "Sputum",
]);
+ patientConsultationPage.clickAddSymptom();
// Stable category
- patientConsultationPage.selectPatientCategory("Stable");
- // Date of symptoms
- patientConsultationPage.selectSymptomsDate(
- "#symptoms_onset_date",
- "01012024",
- );
+ patientConsultationPage.selectPatientCategory("Mild");
// OP Consultation
patientConsultationPage.selectPatientSuggestion("OP Consultation");
// one ICD-11 and no principal
@@ -341,18 +337,16 @@ describe("Patient Consultation in multiple combination", () => {
patientConsultationPage.selectConsultationStatus(
"Outpatient/Emergency Room",
);
- // Select the Symptoms - Sore throat and fever symptoms
+ // Select the Symptoms - Breathlessness and Bleeding symptoms
+ patientConsultationPage.selectSymptomsDate("01012024");
patientConsultationPage.typeAndMultiSelectSymptoms("b", [
- "BREATHLESSNESS",
- "BLEEDING",
+ "Breathlessness",
+ "Bleeding",
]);
+ patientConsultationPage.clickAddSymptom();
// Comfort Care category
patientConsultationPage.selectPatientCategory("Comfort Care");
// Date of symptoms
- patientConsultationPage.selectSymptomsDate(
- "#symptoms_onset_date",
- "01012024",
- );
// Decision after consultation - Referred to Facility
patientConsultationPage.selectPatientSuggestion(
"Refer to another Hospital",
diff --git a/cypress/e2e/patient_spec/patient_logupdate.cy.ts b/cypress/e2e/patient_spec/patient_logupdate.cy.ts
index 8cf83b1c5a8..562e430a9ad 100644
--- a/cypress/e2e/patient_spec/patient_logupdate.cy.ts
+++ b/cypress/e2e/patient_spec/patient_logupdate.cy.ts
@@ -10,8 +10,8 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
const patientPage = new PatientPage();
const patientLogupdate = new PatientLogupdate();
const domicilaryPatient = "Dummy Patient 11";
- const patientCategory = "Abnormal";
- const additionalSymptoms = "ASYMPTOMATIC";
+ const patientCategory = "Moderate";
+ const additionalSymptoms = "Fever";
const physicalExamination = "physical examination details";
const otherExamination = "Other";
const patientSystolic = "119";
@@ -59,9 +59,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
patientLogupdate.typeRhythm(patientRhythm);
cy.get("#consciousness_level-2").click();
cy.submitButton("Save");
- cy.verifyNotification(
- "Telemedicine Log Updates details created successfully",
- );
+ cy.verifyNotification("Tele-medicine log update created successfully");
});
it("Create a new log normal update for a domicilary care patient and edit it", () => {
@@ -86,7 +84,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
patientLogupdate.typeRhythm(patientRhythm);
cy.get("#consciousness_level-2").click();
cy.submitButton("Save");
- cy.verifyNotification("Normal Log Updates details created successfully");
+ cy.verifyNotification("Normal log update created successfully");
cy.closeNotification();
// edit the card and verify the data.
cy.contains("Daily Rounds").click();
@@ -109,7 +107,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
patientLogupdate.clickClearButtonInElement("#diastolic");
patientLogupdate.typeDiastolic(patientModifiedDiastolic);
cy.submitButton("Continue");
- cy.verifyNotification("Normal Log Updates details updated successfully");
+ cy.verifyNotification("Normal log update details updated successfully");
cy.contains("Daily Rounds").click();
patientLogupdate.clickLogupdateCard("#dailyround-entry", patientCategory);
cy.verifyContentPresence("#consultation-preview", [
@@ -127,7 +125,9 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
patientLogupdate.clickLogupdate();
patientLogupdate.typePhysicalExamination(physicalExamination);
patientLogupdate.typeOtherDetails(otherExamination);
- patientLogupdate.typeAdditionalSymptoms(additionalSymptoms);
+ patientLogupdate.selectSymptomsDate("01012024");
+ patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]);
+ patientLogupdate.clickAddSymptom();
patientLogupdate.selectPatientCategory(patientCategory);
patientLogupdate.typeSystolic(patientSystolic);
patientLogupdate.typeDiastolic(patientDiastolic);
@@ -140,10 +140,10 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
cy.get("#consciousness_level-2").click();
cy.submitButton("Save");
cy.wait(2000);
- cy.verifyNotification("Normal Log Updates details created successfully");
+ cy.verifyNotification("Normal log update created successfully");
// Verify the card content
cy.get("#basic-information").scrollIntoView();
- cy.verifyContentPresence("#basic-information", [additionalSymptoms]);
+ cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]);
});
it("Create a normal log update to verify MEWS Score Functionality", () => {
@@ -163,7 +163,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
patientLogupdate.typeRespiratory(patientRespiratory);
cy.get("#consciousness_level-2").click();
cy.submitButton("Save");
- cy.verifyNotification("Normal Log Updates details created successfully");
+ cy.verifyNotification("Normal log update created successfully");
cy.closeNotification();
cy.verifyContentPresence("#consultation-buttons", ["9"]);
// Verify the Incomplete data will give blank info
@@ -173,7 +173,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => {
patientLogupdate.typeDiastolic(patientDiastolic);
patientLogupdate.typePulse(patientPulse);
cy.submitButton("Save");
- cy.verifyNotification("Normal Log Updates details created successfully");
+ cy.verifyNotification("Normal log update created successfully");
cy.closeNotification();
cy.verifyContentPresence("#consultation-buttons", ["-"]);
});
diff --git a/cypress/e2e/patient_spec/patient_prescription.cy.ts b/cypress/e2e/patient_spec/patient_prescription.cy.ts
index 3cf5a1ba5aa..d9a7bf7a080 100644
--- a/cypress/e2e/patient_spec/patient_prescription.cy.ts
+++ b/cypress/e2e/patient_spec/patient_prescription.cy.ts
@@ -5,6 +5,11 @@ import { PatientPage } from "../../pageobject/Patient/PatientCreation";
const patientPrescription = new PatientPrescription();
const loginPage = new LoginPage();
const patientPage = new PatientPage();
+const medicineName = "DOLO";
+const medicineBaseDosage = "4";
+const medicineTargetDosage = "9";
+const medicineFrequency = "Twice daily";
+const medicineAdministerNote = "Medicine Administration Note";
describe("Patient Medicine Administration", () => {
before(() => {
@@ -18,15 +23,54 @@ describe("Patient Medicine Administration", () => {
cy.awaitUrl("/patients");
});
+ it("Add a new titrated medicine for a patient | Individual Administeration |", () => {
+ patientPage.visitPatient("Dummy Patient 5");
+ patientPrescription.visitMedicineTab();
+ patientPrescription.visitEditPrescription();
+ patientPrescription.clickAddPrescription();
+ patientPrescription.interceptMedibase();
+ patientPrescription.selectMedicinebox();
+ patientPrescription.selectMedicine(medicineName);
+ patientPrescription.clickTitratedDosage();
+ patientPrescription.enterDosage(medicineBaseDosage);
+ patientPrescription.enterTargetDosage(medicineTargetDosage);
+ patientPrescription.selectDosageFrequency(medicineFrequency);
+ cy.submitButton("Submit");
+ cy.verifyNotification("Medicine prescribed");
+ cy.closeNotification();
+ // Administer the medicine in edit form
+ patientPrescription.clickAdministerButton();
+ patientPrescription.enterAdministerDosage(medicineBaseDosage);
+ patientPrescription.enterAdministerNotes(medicineAdministerNote);
+ cy.submitButton("Administer Medicine");
+ cy.verifyNotification("Medicine(s) administered");
+ cy.closeNotification();
+ // Verify the Reflection on the Medicine
+ cy.verifyContentPresence("#medicine-preview", [
+ medicineName,
+ medicineBaseDosage,
+ medicineTargetDosage,
+ ]);
+ patientPrescription.clickReturnToDashboard();
+ // Go to medicine tab and administer it again
+ patientPrescription.visitMedicineTab();
+ cy.verifyAndClickElement("#0", medicineName);
+ cy.submitButton("Administer");
+ patientPrescription.enterAdministerDosage(medicineBaseDosage);
+ cy.submitButton("Administer Medicine");
+ cy.verifyNotification("Medicine(s) administered");
+ });
+
it("Add a new medicine for a patient and verify the duplicate medicine validation", () => {
patientPage.visitPatient("Dummy Patient 4");
patientPrescription.visitMedicineTab();
+ patientPrescription.visitEditPrescription();
patientPrescription.clickAddPrescription();
patientPrescription.interceptMedibase();
patientPrescription.selectMedicinebox();
- patientPrescription.selectMedicine("DOLO");
- patientPrescription.enterDosage("4");
- patientPrescription.selectDosageFrequency("Twice daily");
+ patientPrescription.selectMedicine(medicineName);
+ patientPrescription.enterDosage(medicineBaseDosage);
+ patientPrescription.selectDosageFrequency(medicineFrequency);
cy.submitButton("Submit");
cy.verifyNotification("Medicine prescribed");
cy.closeNotification();
@@ -34,9 +78,9 @@ describe("Patient Medicine Administration", () => {
patientPrescription.clickAddPrescription();
patientPrescription.interceptMedibase();
patientPrescription.selectMedicinebox();
- patientPrescription.selectMedicine("DOLO");
- patientPrescription.enterDosage("4");
- patientPrescription.selectDosageFrequency("Twice daily");
+ patientPrescription.selectMedicine(medicineName);
+ patientPrescription.enterDosage(medicineBaseDosage);
+ patientPrescription.selectDosageFrequency(medicineFrequency);
cy.submitButton("Submit");
cy.verifyNotification(
"Medicine - This medicine is already prescribed to this patient. Please discontinue the existing prescription to prescribe again.",
diff --git a/cypress/e2e/shifting_spec/filter.cy.ts b/cypress/e2e/shifting_spec/filter.cy.ts
index a142a657013..82fad9d99e4 100644
--- a/cypress/e2e/shifting_spec/filter.cy.ts
+++ b/cypress/e2e/shifting_spec/filter.cy.ts
@@ -20,7 +20,7 @@ describe("Shifting section filter", () => {
shiftingPage.filterByFacility(
"Dummy Shifting",
"Dummy Shifting",
- "District"
+ "District",
);
shiftingPage.facilityAssignedBadge().should("exist");
@@ -32,13 +32,10 @@ describe("Shifting section filter", () => {
"ASC Created Date",
"yes",
"yes",
- "POSITIVE",
"no",
"MODERATE",
- "9999999999"
+ "9999999999",
);
-
- shiftingPage.diseaseStatusBadge().should("exist");
shiftingPage.orderingBadge().should("exist");
shiftingPage.breathlessnessLevelBadge().should("exist");
shiftingPage.phoneNumberBadge().should("exist");
diff --git a/cypress/e2e/users_spec/user_homepage.cy.ts b/cypress/e2e/users_spec/user_homepage.cy.ts
index a006fe77569..3ac07dd9d9c 100644
--- a/cypress/e2e/users_spec/user_homepage.cy.ts
+++ b/cypress/e2e/users_spec/user_homepage.cy.ts
@@ -32,6 +32,7 @@ describe("User Homepage", () => {
userPage.selectDistrict("Ernakulam");
userPage.typeInPhoneNumber(phone_number);
userPage.typeInAltPhoneNumber(alt_phone_number);
+ userPage.selectHomeFacility("Dummy Facility 40");
userPage.applyFilter();
userPage.verifyUrlafteradvancefilter();
userPage.checkUsernameText(usernameToTest);
@@ -46,6 +47,10 @@ describe("User Homepage", () => {
"WhatsApp no.: +919876543219",
);
userPage.verifyDataTestIdText("Role", "Role: Doctor");
+ userPage.verifyDataTestIdText(
+ "Home Facility",
+ "Home Facility: Dummy Facility 40",
+ );
userPage.verifyDataTestIdText("District", "District: Ernakulam");
userPage.clearFilters();
userPage.verifyDataTestIdNotVisible("First Name");
@@ -53,6 +58,7 @@ describe("User Homepage", () => {
userPage.verifyDataTestIdNotVisible("Phone Number");
userPage.verifyDataTestIdNotVisible("WhatsApp no.");
userPage.verifyDataTestIdNotVisible("Role");
+ userPage.verifyDataTestIdNotVisible("Home Facility");
userPage.verifyDataTestIdNotVisible("District");
});
diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts
index 4400d9a524c..31b1fd6cb68 100644
--- a/cypress/pageobject/Patient/PatientConsultation.ts
+++ b/cypress/pageobject/Patient/PatientConsultation.ts
@@ -6,14 +6,14 @@ export class PatientConsultationPage {
cy.clickAndSelectOption("#route_to_facility", status);
}
- selectSymptoms(symptoms) {
- cy.clickAndMultiSelectOption("#symptoms", symptoms);
- }
typeAndMultiSelectSymptoms(input, symptoms) {
- cy.typeAndMultiSelectOption("#symptoms", input, symptoms);
+ cy.typeAndMultiSelectOption("#additional_symptoms", input, symptoms);
+ }
+ selectSymptomsDate(date: string) {
+ cy.clickAndTypeDate("#symptoms_onset_date", date);
}
- selectSymptomsDate(selector: string, date: string) {
- cy.clickAndTypeDate(selector, date);
+ clickAddSymptom() {
+ cy.get("#add-symptom").click();
}
verifyConsultationPatientName(patientName: string) {
diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts
index 3511f0241bb..92ea02a1417 100644
--- a/cypress/pageobject/Patient/PatientLogupdate.ts
+++ b/cypress/pageobject/Patient/PatientLogupdate.ts
@@ -32,6 +32,16 @@ class PatientLogupdate {
cy.searchAndSelectOption("#additional_symptoms", symptoms);
}
+ typeAndMultiSelectSymptoms(input, symptoms) {
+ cy.typeAndMultiSelectOption("#additional_symptoms", input, symptoms);
+ }
+ selectSymptomsDate(date: string) {
+ cy.clickAndTypeDate("#symptoms_onset_date", date);
+ }
+ clickAddSymptom() {
+ cy.get("#add-symptom").click();
+ }
+
typeSystolic(systolic: string) {
cy.searchAndSelectOption("#systolic", systolic);
}
diff --git a/cypress/pageobject/Patient/PatientPrescription.ts b/cypress/pageobject/Patient/PatientPrescription.ts
index dc4163e4823..a4b92b0a5fa 100644
--- a/cypress/pageobject/Patient/PatientPrescription.ts
+++ b/cypress/pageobject/Patient/PatientPrescription.ts
@@ -1,8 +1,10 @@
export class PatientPrescription {
clickAddPrescription() {
- cy.contains("button", "Add Prescription Medication")
- .should("be.visible")
- .click();
+ cy.get("#add-prescription").scrollIntoView();
+ cy.verifyAndClickElement(
+ "#add-prescription",
+ "Add Prescription Medication",
+ );
}
interceptMedibase() {
@@ -16,6 +18,15 @@ export class PatientPrescription {
);
}
+ clickTitratedDosage() {
+ cy.get("#titrated-dosage").click();
+ }
+
+ clickAdministerButton() {
+ cy.get("#administer-medicine").should("be.visible");
+ cy.verifyAndClickElement("#administer-medicine", "Administer");
+ }
+
selectMedicinebox() {
cy.get(
"div#medicine_object input[placeholder='Select'][role='combobox']",
@@ -30,6 +41,18 @@ export class PatientPrescription {
cy.get("#base_dosage").type(doseAmount, { force: true });
}
+ enterAdministerDosage(dosage: string) {
+ cy.get("#dosage").type(dosage);
+ }
+
+ enterAdministerNotes(notes: string) {
+ cy.get("#administration_notes").type(notes);
+ }
+
+ enterTargetDosage(targetDosage: string) {
+ cy.get("#target_dosage").type(targetDosage, { force: true });
+ }
+
selectDosageFrequency(frequency: string) {
cy.clickAndSelectOption("#frequency", frequency);
}
@@ -54,7 +77,10 @@ export class PatientPrescription {
visitMedicineTab() {
cy.get("#consultation_tab_nav").scrollIntoView();
cy.get("#consultation_tab_nav").contains("Medicines").click();
- cy.get("a[href='prescriptions']").first().click();
+ }
+
+ visitEditPrescription() {
+ cy.get("#edit-prescription").click();
}
}
export default PatientPrescription;
diff --git a/cypress/pageobject/Shift/ShiftFilters.ts b/cypress/pageobject/Shift/ShiftFilters.ts
index 87d3f33c37e..1f824cebbb3 100644
--- a/cypress/pageobject/Shift/ShiftFilters.ts
+++ b/cypress/pageobject/Shift/ShiftFilters.ts
@@ -27,10 +27,6 @@ class ShiftingPage {
return cy.get("#is-up-shift");
}
- diseaseStatusInput() {
- return cy.get("#disease-status");
- }
-
isAntenatalInput() {
return cy.get("#is-antenatal");
}
@@ -63,10 +59,6 @@ class ShiftingPage {
return cy.get("[data-testid='Current facility']");
}
- diseaseStatusBadge() {
- return cy.get("[data-testid='Disease status']");
- }
-
orderingBadge() {
return cy.get("[data-testid='Ordering']");
}
@@ -98,7 +90,7 @@ class ShiftingPage {
filterByFacility(
origin_facility: string,
assigned_facility: string,
- assigned_to: string
+ assigned_to: string,
) {
this.originFacilityInput().click().type(origin_facility);
cy.get("[role='option']").contains(origin_facility).click();
@@ -116,10 +108,9 @@ class ShiftingPage {
ordering: string,
emergency: string,
is_up_shift: string,
- disease_status: string,
is_antenatal: string,
breathlessness_level: string,
- patient_phone_number: string
+ patient_phone_number: string,
) {
this.orderingInput().click();
cy.get("[role='option']").contains(ordering).click();
@@ -130,9 +121,6 @@ class ShiftingPage {
this.isUpShiftInput().click();
cy.get("[role='option']").contains(is_up_shift).click();
- this.diseaseStatusInput().click();
- cy.get("[role='option']").contains(disease_status).click();
-
this.isAntenatalInput().click();
cy.get("[role='option']").contains(is_antenatal).click();
@@ -148,7 +136,7 @@ class ShiftingPage {
created_date_start: string,
created_date_end: string,
modified_date_start: string,
- modified_date_end: string
+ modified_date_end: string,
) {
this.createdDateStartInput().click();
cy.get("[id^='headlessui-popover-panel-'] .care-l-angle-left-b")
diff --git a/cypress/pageobject/Users/UserSearch.ts b/cypress/pageobject/Users/UserSearch.ts
index 7d85563d62c..56d1a81395d 100644
--- a/cypress/pageobject/Users/UserSearch.ts
+++ b/cypress/pageobject/Users/UserSearch.ts
@@ -78,6 +78,10 @@ export class UserPage {
cy.get("#alt_phone_number").click().type(altPhone);
}
+ selectHomeFacility(facility: string) {
+ cy.searchAndSelectOption("input[name='home_facility']", facility);
+ }
+
applyFilter() {
cy.get("#apply-filter").click();
}
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/misc/AuthorizedChild.tsx b/src/CAREUI/misc/AuthorizedChild.tsx
index ce9ec69d546..935f0c51f3c 100644
--- a/src/CAREUI/misc/AuthorizedChild.tsx
+++ b/src/CAREUI/misc/AuthorizedChild.tsx
@@ -1,4 +1,7 @@
+import { ReactNode } from "react";
+import useAuthUser from "../../Common/hooks/useAuthUser";
import { useIsAuthorized } from "../../Common/hooks/useIsAuthorized";
+import useSlug from "../../Common/hooks/useSlug";
import { AuthorizedForCB } from "../../Utils/AuthorizeFor";
interface Props {
@@ -12,3 +15,20 @@ const AuthorizedChild = (props: Props) => {
};
export default AuthorizedChild;
+
+export const AuthorizedForConsultationRelatedActions = (props: {
+ children: ReactNode;
+}) => {
+ const me = useAuthUser();
+ const facilityId = useSlug("facility");
+
+ if (
+ me.home_facility_object?.id === facilityId ||
+ me.user_type === "DistrictAdmin" ||
+ me.user_type === "StateAdmin"
+ ) {
+ return props.children;
+ }
+
+ return null;
+};
diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx
index db68c6f042c..ec270bf8134 100644
--- a/src/CAREUI/misc/PaginatedList.tsx
+++ b/src/CAREUI/misc/PaginatedList.tsx
@@ -1,4 +1,4 @@
-import { createContext, useContext, useState } from "react";
+import { createContext, useContext, useEffect, useState } from "react";
import { PaginatedResponse, QueryRoute } from "../../Utils/request/types";
import useQuery, { QueryOptions } from "../../Utils/request/useQuery";
import ButtonV2, {
@@ -33,6 +33,9 @@ function useContextualized() {
interface Props extends QueryOptions> {
route: QueryRoute>;
perPage?: number;
+ queryCB?: (
+ query: ReturnType>>,
+ ) => void;
children: (
ctx: PaginatedListContext,
query: ReturnType>>,
@@ -43,6 +46,7 @@ export default function PaginatedList({
children,
route,
perPage = DEFAULT_PER_PAGE_LIMIT,
+ queryCB,
...queryOptions
}: Props) {
const [currentPage, setPage] = useState(1);
@@ -57,6 +61,12 @@ export default function PaginatedList({
const items = query.data?.results ?? [];
+ useEffect(() => {
+ if (queryCB) {
+ queryCB(query);
+ }
+ }, [query]);
+
return (
= [
{ id: 30 * 24 * 60, text: "1 month" },
];
-export const SYMPTOM_CHOICES = [
- { id: 1, text: "ASYMPTOMATIC", isSingleSelect: true },
- { id: 2, text: "FEVER" },
- { id: 3, text: "SORE THROAT" },
- { id: 4, text: "COUGH" },
- { id: 5, text: "BREATHLESSNESS" },
- { id: 6, text: "MYALGIA" },
- { id: 7, text: "ABDOMINAL DISCOMFORT" },
- { id: 8, text: "VOMITING" },
- { id: 11, text: "SPUTUM" },
- { id: 12, text: "NAUSEA" },
- { id: 13, text: "CHEST PAIN" },
- { id: 14, text: "HEMOPTYSIS" },
- { id: 15, text: "NASAL DISCHARGE" },
- { id: 16, text: "BODY ACHE" },
- { id: 17, text: "DIARRHOEA" },
- { id: 18, text: "PAIN" },
- { id: 19, text: "PEDAL EDEMA" },
- { id: 20, text: "WOUND" },
- { id: 21, text: "CONSTIPATION" },
- { id: 22, text: "HEAD ACHE" },
- { id: 23, text: "BLEEDING" },
- { id: 24, text: "DIZZINESS" },
- { id: 25, text: "CHILLS" },
- { id: 26, text: "GENERAL WEAKNESS" },
- { id: 27, text: "IRRITABILITY" },
- { id: 28, text: "CONFUSION" },
- { id: 29, text: "ABDOMINAL PAIN" },
- { id: 30, text: "JOINT PAIN" },
- { id: 31, text: "REDNESS OF EYES" },
- { id: 32, text: "ANOREXIA" },
- { id: 33, text: "NEW LOSS OF TASTE" },
- { id: 34, text: "NEW LOSS OF SMELL" },
- { id: 9, text: "OTHERS" },
-];
-
export const DISCHARGE_REASONS = [
{ id: 1, text: "Recovered" },
{ id: 2, text: "Referred" },
@@ -406,10 +370,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 =
@@ -438,8 +402,8 @@ export const PATIENT_CATEGORIES: {
twClass: string;
}[] = [
{ id: "Comfort", text: "Comfort Care", twClass: "patient-comfort" },
- { id: "Stable", text: "Stable", twClass: "patient-stable" },
- { id: "Moderate", text: "Abnormal", twClass: "patient-abnormal" },
+ { id: "Stable", text: "Mild", twClass: "patient-stable" },
+ { id: "Moderate", text: "Moderate", twClass: "patient-abnormal" },
{ id: "Critical", text: "Critical", twClass: "patient-critical" },
];
@@ -477,13 +441,6 @@ export const SAMPLE_FLOW_RULES = {
RECEIVED_AT_LAB: ["COMPLETED"],
};
-export const DISEASE_STATUS = [
- "POSITIVE",
- "SUSPECTED",
- "NEGATIVE",
- "RECOVERED",
-];
-
export const TEST_TYPE = [
"UNK",
"ANTIGEN",
@@ -1322,7 +1279,7 @@ export const CONSENT_PATIENT_CODE_STATUS_CHOICES = [
{ id: 1, text: "Do Not Hospitalise (DNH)" },
{ id: 2, text: "Do Not Resuscitate (DNR)" },
{ id: 3, text: "Comfort Care Only" },
- { id: 4, text: "Active treatment (Default)" },
+ { id: 4, text: "Active treatment" },
];
export const OCCUPATION_TYPES = [
{
@@ -1418,3 +1375,5 @@ export const PATIENT_NOTES_THREADS = {
Doctors: 10,
Nurses: 20,
} as const;
+
+export const RATION_CARD_CATEGORY = ["BPL", "APL", "NO_CARD"] as const;
diff --git a/src/Common/hooks/useMSEplayer.ts b/src/Common/hooks/useMSEplayer.ts
index 482f991bc25..39e4d356910 100644
--- a/src/Common/hooks/useMSEplayer.ts
+++ b/src/Common/hooks/useMSEplayer.ts
@@ -149,7 +149,6 @@ export const useMSEMediaPlayer = ({
const ws = wsRef.current;
ws.binaryType = "arraybuffer";
ws.onopen = function (_event) {
- console.log("Connected to ws");
onSuccess && onSuccess(undefined);
};
ws.onmessage = function (event) {
diff --git a/src/Common/hooks/useNotificationSubscriptionState.ts b/src/Common/hooks/useNotificationSubscriptionState.ts
new file mode 100644
index 00000000000..b7927e0cc70
--- /dev/null
+++ b/src/Common/hooks/useNotificationSubscriptionState.ts
@@ -0,0 +1,55 @@
+import { useEffect, useState } from "react";
+import routes from "../../Redux/api";
+import request from "../../Utils/request/request";
+import useAuthUser from "./useAuthUser";
+import * as Sentry from "@sentry/browser";
+
+export type NotificationSubscriptionState =
+ | "unsubscribed"
+ | "subscribed"
+ | "subscribed_on_other_device"
+ | "subscribed"
+ | "pending"
+ | "error";
+
+/**
+ * This is a temporary hook and will be removed in the future.
+ *
+ * This hook is used to get the initial notification subscription state of the user.
+ * @returns NotificationSubscriptionState
+ */
+export default function useNotificationSubscriptionState(
+ dependencies: any[] = [],
+) {
+ const { username } = useAuthUser();
+ const [subscriptionState, setSubscriptionState] =
+ useState("pending");
+
+ const getSubscriptionState = async () => {
+ try {
+ const res = await request(routes.getUserPnconfig, {
+ pathParams: { username },
+ });
+
+ const reg = await navigator.serviceWorker.ready;
+ const subscription = await reg.pushManager.getSubscription();
+
+ if (!subscription && !res.data?.pf_endpoint) {
+ setSubscriptionState("unsubscribed");
+ } else if (subscription?.endpoint !== res.data?.pf_endpoint) {
+ setSubscriptionState("subscribed_on_other_device");
+ } else {
+ setSubscriptionState("subscribed");
+ }
+ } catch (error) {
+ setSubscriptionState("error");
+ Sentry.captureException(error);
+ }
+ };
+
+ useEffect(() => {
+ getSubscriptionState();
+ }, [username, ...dependencies]);
+
+ return subscriptionState;
+}
diff --git a/src/Components/ABDM/ABDMFacilityRecords.tsx b/src/Components/ABDM/ABDMFacilityRecords.tsx
index 8178e037a7f..96040905d6c 100644
--- a/src/Components/ABDM/ABDMFacilityRecords.tsx
+++ b/src/Components/ABDM/ABDMFacilityRecords.tsx
@@ -4,6 +4,8 @@ import useQuery from "../../Utils/request/useQuery";
import { formatDateTime } from "../../Utils/utils";
import Loading from "../Common/Loading";
import Page from "../Common/components/Page";
+import CareIcon from "../../CAREUI/icons/CareIcon";
+import ButtonV2 from "../Common/components/ButtonV2";
interface IProps {
facilityId: string;
@@ -21,7 +23,11 @@ const TableHeads = [
];
export default function ABDMFacilityRecords({ facilityId }: IProps) {
- const { data: consentsResult, loading } = useQuery(routes.abha.listConsents, {
+ const {
+ data: consentsResult,
+ loading,
+ refetch,
+ } = useQuery(routes.abha.listConsents, {
query: { facility: facilityId, ordering: "-created_date" },
});
@@ -53,6 +59,13 @@ export default function ABDMFacilityRecords({ facilityId }: IProps) {
scope="col"
className="sticky right-0 top-0 py-3.5 pl-3 pr-4 sm:pr-6"
>
+ refetch()}
+ ghost
+ className="max-w-2xl text-sm text-gray-700 hover:text-gray-900"
+ >
+ Refresh
+
View
diff --git a/src/Components/ABDM/FetchRecordsModal.tsx b/src/Components/ABDM/FetchRecordsModal.tsx
index 8273a9763f1..950b69611c4 100644
--- a/src/Components/ABDM/FetchRecordsModal.tsx
+++ b/src/Components/ABDM/FetchRecordsModal.tsx
@@ -21,6 +21,7 @@ import CircularProgress from "../Common/components/CircularProgress.js";
import CareIcon from "../../CAREUI/icons/CareIcon.js";
import { classNames } from "../../Utils/utils.js";
import { ConsentHIType, ConsentPurpose } from "./types/consent.js";
+import useNotificationSubscriptionState from "../../Common/hooks/useNotificationSubscriptionState.js";
const getDate = (value: any) =>
value && dayjs(value).isValid() && dayjs(value).toDate();
@@ -46,6 +47,9 @@ export default function FetchRecordsModal({ patient, show, onClose }: IProps) {
dayjs().add(30, "day").toDate(),
);
const [errors, setErrors] = useState({});
+ const notificationSubscriptionState = useNotificationSubscriptionState([
+ show,
+ ]);
useMessageListener((data) => {
if (data.type === "MESSAGE" && data.from === "patients/on_find") {
@@ -62,7 +66,23 @@ export default function FetchRecordsModal({ patient, show, onClose }: IProps) {
});
return (
-
+
+ {["unsubscribed", "subscribed_on_other_device"].includes(
+ notificationSubscriptionState,
+ ) && (
+
+ {" "}
+ Notifications needs to be enabled on this device to verify the
+ patient.
+
+ )}
+
Request Consent
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/Assets/configure/MonitorConfigure.tsx b/src/Components/Assets/configure/MonitorConfigure.tsx
index 5053b5b7d02..785b82873de 100644
--- a/src/Components/Assets/configure/MonitorConfigure.tsx
+++ b/src/Components/Assets/configure/MonitorConfigure.tsx
@@ -1,7 +1,7 @@
import { useState } from "react";
import { BedSelect } from "../../Common/BedSelect";
import { BedModel } from "../../Facility/models";
-import { AssetData } from "../AssetTypes";
+import { AssetClass, AssetData } from "../AssetTypes";
import * as Notification from "../../../Utils/Notifications.js";
import { Submit } from "../../Common/components/ButtonV2";
import { FieldLabel } from "../../Form/FormFields/FormField";
@@ -73,6 +73,7 @@ export default function MonitorConfigure({ asset }: { asset: AssetData }) {
multiple={false}
location={asset?.location_object?.id}
facility={asset?.location_object?.facility?.id}
+ not_occupied_by_asset_type={AssetClass.HL7MONITOR}
className="w-full"
/>
diff --git a/src/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx
index 17701dccbde..56def3d41c9 100644
--- a/src/Components/CameraFeed/AssetBedSelect.tsx
+++ b/src/Components/CameraFeed/AssetBedSelect.tsx
@@ -1,42 +1,76 @@
import { Fragment } from "react";
-import useSlug from "../../Common/hooks/useSlug";
-import routes from "../../Redux/api";
-import useQuery from "../../Utils/request/useQuery";
-import { AssetBedModel, AssetData } from "../Assets/AssetTypes";
-import { BedModel } from "../Facility/models";
+import { AssetBedModel } from "../Assets/AssetTypes";
import { Listbox, Transition } from "@headlessui/react";
import CareIcon from "../../CAREUI/icons/CareIcon";
+import { classNames } from "../../Utils/utils";
interface Props {
- asset?: AssetData;
- bed?: BedModel;
+ options: AssetBedModel[];
value?: AssetBedModel;
+ label?: (value: AssetBedModel) => string;
onChange?: (value: AssetBedModel) => void;
}
-export default function AssetBedSelect(props: Props) {
- const facility = useSlug("facility");
-
- const { data, loading } = useQuery(routes.listAssetBeds, {
- query: {
- limit: 100,
- facility,
- asset: props.asset?.id,
- bed: props.bed?.id,
- },
- });
+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) => {
const selected = props.value;
+ const options = props.options.filter(({ meta }) => meta.type !== "boundary");
+
+ const label = props.label ?? defaultLabel;
+
return (
-
-
-
+
+
+
- {selected?.bed_object.name ?? "No Preset"}
+ {selected ? label(selected) : "Select preset"}
-
-
+
+
-
- {data?.results.map((obj) => (
+
+ {options?.map((obj) => (
@@ -59,11 +93,11 @@ export default function AssetBedSelect(props: Props) {
{({ selected }) => (
<>
- {obj.bed_object.name}: {obj.meta.preset_name}
+ {label(obj)}
>
)}
@@ -74,4 +108,8 @@ export default function AssetBedSelect(props: Props) {
);
-}
+};
+
+const defaultLabel = ({ bed_object, meta }: AssetBedModel) => {
+ return `${bed_object.name}: ${meta.preset_name}`;
+};
diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx
index d5ea120e126..a5f0a3d80e0 100644
--- a/src/Components/CameraFeed/CameraFeed.tsx
+++ b/src/Components/CameraFeed/CameraFeed.tsx
@@ -10,6 +10,8 @@ import FeedNetworkSignal from "./FeedNetworkSignal";
import NoFeedAvailable from "./NoFeedAvailable";
import FeedControls from "./FeedControls";
import Fullscreen from "../../CAREUI/misc/Fullscreen";
+import FeedWatermark from "./FeedWatermark";
+import CareIcon from "../../CAREUI/icons/CareIcon";
interface Props {
children?: React.ReactNode;
@@ -24,6 +26,7 @@ interface Props {
// Controls
constrolsDisabled?: boolean;
shortcutsDisabled?: boolean;
+ onMove?: () => void;
}
export default function CameraFeed(props: Props) {
@@ -76,7 +79,7 @@ export default function CameraFeed(props: Props) {
},
onError: props.onStreamError,
});
- }, [player.initializeStream, props.onStreamSuccess, props.onStreamError]);
+ }, [player.initializeStream]);
// Start stream on mount
useEffect(() => initializeStream(), [initializeStream]);
@@ -85,18 +88,22 @@ export default function CameraFeed(props: Props) {
setState("loading");
initializeStream();
};
-
return (
setFullscreen(false)}>
-
-
-
+
+ {props.children}
+
+
+
{props.asset.name}
@@ -108,12 +115,12 @@ export default function CameraFeed(props: Props) {
/>
- {props.children}
{/* Notifications */}
+ {player.status === "playing" && }
{/* No Feed informations */}
{state === "host_unreachable" && (
@@ -144,6 +151,7 @@ export default function CameraFeed(props: Props) {
url={streamUrl}
ref={playerRef.current as LegacyRef}
controls={false}
+ pip={false}
playsinline
playing
muted
@@ -161,10 +169,12 @@ export default function CameraFeed(props: Props) {
) : (