From a0d29b197fa65c38dfcbd7fc54a2727c9196db36 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 22 May 2024 10:05:34 +0530 Subject: [PATCH 01/12] Updated discharged patients page (#7853) * updated discharged patients page * fixed filter badges * updated filters * fixed responsiveness and removed geo filters * changed per page limit * switched to 12 * fixes * fixed filters * fix phone number field * changes to filters * removed admitted to --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- src/CAREUI/misc/PaginatedList.tsx | 12 +- .../Facility/DischargedPatientsList.tsx | 358 +++++++++++++++++- src/Components/Patient/PatientFilter.tsx | 134 ++++--- 3 files changed, 428 insertions(+), 76 deletions(-) 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 ( { + if (!qParams.phone_number && phone_number.length >= 13) { + setPhoneNumber("+91"); + } + if ( + !qParams.emergency_phone_number && + emergency_phone_number.length >= 13 + ) { + setEmergencyPhoneNumber("+91"); + } + }, [qParams]); + + const { data: districtData } = useQuery(routes.getDistrict, { + pathParams: { + id: qParams.district, + }, + prefetch: !!Number(qParams.district), + }); + + const { data: LocalBodyData } = useQuery(routes.getLocalBody, { + pathParams: { + id: qParams.lsgBody, + }, + prefetch: !!Number(qParams.lsgBody), + }); + + const { data: facilityAssetLocationData } = useQuery( + routes.getFacilityAssetLocation, + { + pathParams: { + facility_external_id: qParams.facility, + external_id: qParams.last_consultation_current_bed__location, + }, + prefetch: !!qParams.last_consultation_current_bed__location, + }, + ); + + const getTheCategoryFromId = () => { + let category_name; + if (qParams.category) { + category_name = PATIENT_CATEGORIES.find( + (item: any) => qParams.category === item.id, + )?.text; + + return String(category_name); + } else { + return ""; + } + }; + + const getDiagnosisFilterValue = (key: DiagnosesFilterKey) => { + const ids: string[] = (qParams[key] ?? "").split(","); + return ids.map((id) => diagnoses.find((obj) => obj.id == id)?.label ?? id); + }; + + useEffect(() => { + const ids: string[] = []; + FILTER_BY_DIAGNOSES_KEYS.forEach((key) => { + ids.push(...(qParams[key] ?? "").split(",").filter(Boolean)); + }); + const existing = diagnoses.filter(({ id }) => ids.includes(id)); + const objIds = existing.map((o) => o.id); + const diagnosesToBeFetched = ids.filter((id) => !objIds.includes(id)); + getDiagnosesByIds(diagnosesToBeFetched).then((data) => { + const retrieved = data.filter(Boolean) as ICD11DiagnosisModel[]; + setDiagnoses([...existing, ...retrieved]); + }); + }, [ + qParams.diagnoses, + qParams.diagnoses_confirmed, + qParams.diagnoses_provisional, + qParams.diagnoses_unconfirmed, + qParams.diagnoses_differential, + ]); + + const LastAdmittedToTypeBadges = () => { + const badge = (key: string, value: any, id: string) => { + return ( + value && ( + { + const lcat = qParams.last_consultation_admitted_bed_type_list + .split(",") + .filter((x: string) => x != id) + .join(","); + updateQuery({ + ...qParams, + last_consultation_admitted_bed_type_list: lcat, + }); + }} + /> + ) + ); + }; + return qParams.last_consultation_admitted_bed_type_list + .split(",") + .map((id: string) => { + const text = ADMITTED_TO.find((obj) => obj.id == id)?.text; + return badge("Bed Type", text, id); + }); + }; + + const queryField = (name: string, defaultValue?: T) => { + return { + name, + value: qParams[name] || defaultValue, + onChange: (e: FieldChangeEvent) => updateQuery({ [e.name]: e.value }), + className: "grow w-full mb-2", + }; + }; + const [diagnoses, setDiagnoses] = useState([]); + const [phone_number, setPhoneNumber] = useState(""); + const [phoneNumberError, setPhoneNumberError] = useState(""); + const [emergency_phone_number, setEmergencyPhoneNumber] = useState(""); + const [emergencyPhoneNumberError, setEmergencyPhoneNumberError] = + useState(""); + const [count, setCount] = useState(0); + + const setPhoneNum = (phone_number: string) => { + setPhoneNumber(phone_number); + if (phone_number.length >= 13) { + setPhoneNumberError(""); + updateQuery({ phone_number }); + return; + } + + if (phone_number === "+91" || phone_number === "") { + setPhoneNumberError(""); + qParams.phone_number && updateQuery({ phone_number: null }); + return; + } + + setPhoneNumberError("Enter a valid number"); + }; + + const setEmergencyPhoneNum = (emergency_phone_number: string) => { + setEmergencyPhoneNumber(emergency_phone_number); + if (emergency_phone_number.length >= 13) { + setEmergencyPhoneNumberError(""); + updateQuery({ emergency_phone_number }); + return; + } + + if (emergency_phone_number === "+91" || emergency_phone_number === "") { + setEmergencyPhoneNumberError(""); + qParams.emergency_phone_number && + updateQuery({ emergency_phone_number: null }); + return; + } + + setEmergencyPhoneNumberError("Enter a valid number"); + }; return ( - updateQuery({ name: e.value }))} - /> -
+
navigate("/patients")} isTab2Active /> + advancedFilter.setShow(true)} + /> } > +
+
+
+ +
+
+
+
+
+ + +
+
+ setPhoneNum(e.value)} + error={phoneNumberError} + types={["mobile", "landline"]} + /> + setEmergencyPhoneNum(e.value)} + error={emergencyPhoneNumberError} + types={["mobile", "landline"]} + /> +
+
+
+
- [ordering()]} /> + [ + phoneNumber("Primary number", "phone_number"), + phoneNumber("Emergency number", "emergency_phone_number"), + badge("Patient name", "name"), + badge("IP/OP number", "patient_no"), + ...dateRange("Modified", "modified_date"), + ...dateRange("Created", "created_date"), + ...dateRange("Admitted", "last_consultation_encounter_date"), + ...dateRange("Discharged", "last_consultation_discharge_date"), + // Admitted to type badges + badge("No. of vaccination doses", "number_of_doses"), + kasp(), + badge("COWIN ID", "covin_id"), + badge("Is Antenatal", "is_antenatal"), + badge("Review Missed", "review_missed"), + badge( + "Is Medico-Legal Case", + "last_consultation_medico_legal_case", + ), + value( + "Location", + "last_consultation_current_bed__location", + qParams.last_consultation_current_bed__location + ? facilityAssetLocationData?.name || + qParams.last_consultation_current_bed__locations + : "", + ), + badge("Facility Type", "facility_type"), + value( + "District", + "district", + qParams.district ? districtData?.name || "" : "", + ), + ordering(), + value("Category", "category", getTheCategoryFromId()), + badge("Disease Status", "disease_status"), + value( + "Respiratory Support", + "ventilator_interface", + qParams.ventilator_interface && + t(`RESPIRATORY_SUPPORT_${qParams.ventilator_interface}`), + ), + value( + "Gender", + "gender", + parseOptionId(GENDER_TYPES, qParams.gender) || "", + ), + { + name: "Admitted to", + value: ADMITTED_TO[qParams.last_consultation_admitted_to], + paramKey: "last_consultation_admitted_to", + }, + ...range("Age", "age"), + badge("SRF ID", "srf_id"), + { + name: "LSG Body", + value: qParams.lsgBody ? LocalBodyData?.name || "" : "", + paramKey: "lsgBody", + }, + ...FILTER_BY_DIAGNOSES_KEYS.map((key) => + value( + DIAGNOSES_FILTER_LABELS[key], + key, + getDiagnosisFilterValue(key).join(", "), + ), + ), + badge("Declared Status", "is_declared_positive"), + ...dateRange("Result", "date_of_result"), + ...dateRange("Declared positive", "date_declared_positive"), + ...dateRange( + "Symptoms onset", + "last_consultation_symptoms_onset_date", + ), + ...dateRange("Last vaccinated", "last_vaccinated_date"), + { + name: "Telemedicine", + paramKey: "last_consultation_is_telemedicine", + }, + value( + "Discharge Reason", + "last_consultation__new_discharge_reason", + parseOptionId( + DISCHARGE_REASONS, + qParams.last_consultation__new_discharge_reason, + ) || "", + ), + ]} + children={ + qParams.last_consultation_admitted_bed_type_list && + LastAdmittedToTypeBadges() + } + />
{ + setCount(query.data?.count || 0); + console.log(query.data?.count); + }} > {() => ( -
+
{t("discharged_patients_empty")} @@ -99,6 +428,11 @@ const DischargedPatientsList = ({
)} + ); }; diff --git a/src/Components/Patient/PatientFilter.tsx b/src/Components/Patient/PatientFilter.tsx index fe7a9f4ee06..0d8ebaae2b0 100644 --- a/src/Components/Patient/PatientFilter.tsx +++ b/src/Components/Patient/PatientFilter.tsx @@ -345,26 +345,31 @@ export default function PatientFilter(props: any) { />
-
- Admitted to (Bed Types) - o.id} - optionLabel={(o) => o.text} - onChange={(o) => - setFilterState({ - ...filterState, - last_consultation_admitted_bed_type_list: o, - }) - } - /> -
- {["StateAdmin", "StateReadOnlyAdmin"].includes( - authUser.user_type, - ) && ( + {props.dischargePage || ( +
+ + {props.dischargePage && "Last "}Admitted to (Bed Types) + + o.id} + optionLabel={(o) => o.text} + onChange={(o) => + setFilterState({ + ...filterState, + last_consultation_admitted_bed_type_list: o, + }) + } + /> +
+ )} + {(props.dischargePage || + ["StateAdmin", "StateReadOnlyAdmin"].includes( + authUser.user_type, + )) && (
Discharge Reason
-
+ {/*
Is Antenatal o === "true" ? "Antenatal" : "Non-antenatal" } - optionDescription={(o) => - o === "true" - ? "i.e., last menstruation start date is within the last 9 months" - : undefined - } value={filterState.is_antenatal} onChange={(v) => setFilterState({ ...filterState, is_antenatal: v }) } /> -
+
*/}
- Review Missed - (o === "true" ? "Yes" : "No")} - value={filterState.review_missed} - onChange={(v) => - setFilterState({ ...filterState, review_missed: v }) - } - /> + {props.dischargePage || ( + <> + Review Missed + (o === "true" ? "Yes" : "No")} + value={filterState.review_missed} + onChange={(v) => + setFilterState({ ...filterState, review_missed: v }) + } + /> + + )}
Is Medico-Legal Case @@ -568,16 +572,18 @@ export default function PatientFilter(props: any) { className="rounded-md" >
-
- Facility - setFilterWithRef("facility", obj)} - /> -
+ {!props.dischargePage && ( +
+ Facility + setFilterWithRef("facility", obj)} + /> +
+ )} {filterState.facility && (
Location @@ -596,22 +602,24 @@ export default function PatientFilter(props: any) { />
)} -
- Facility type - o.text} - optionValue={(o) => o.text} - value={filterState.facility_type} - onChange={(v) => - setFilterState({ ...filterState, facility_type: v }) - } - optionIcon={() => ( - - )} - /> -
+ {!props.dischargePage && ( +
+ Facility type + o.text} + optionValue={(o) => o.text} + value={filterState.facility_type} + onChange={(v) => + setFilterState({ ...filterState, facility_type: v }) + } + optionIcon={() => ( + + )} + /> +
+ )}
LSG Body
From 25f51d4ad98b8e01fbf6c7c948e9eebe28430e3b Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 22 May 2024 10:06:11 +0530 Subject: [PATCH 02/12] Update Security Policy to redirect to issue page (#7292) * Update Security Policy to redirect to issue page * change url --- SECURITY.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 1fe6eb33177f1bf6cac576276a36ee3be462eb99 Mon Sep 17 00:00:00 2001 From: Ankur Prabhu <85862184+AnkurPrabhu@users.noreply.github.com> Date: Wed, 22 May 2024 10:06:35 +0530 Subject: [PATCH 03/12] Disable the Bed List of Already occupied Beds (front-end change) (#7699) * front-end change * adding suggested changes --- src/Components/Assets/configure/MonitorConfigure.tsx | 3 ++- src/Components/Common/BedSelect.tsx | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) 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/Common/BedSelect.tsx b/src/Components/Common/BedSelect.tsx index 2e7bf09072e..5c31055a3a4 100644 --- a/src/Components/Common/BedSelect.tsx +++ b/src/Components/Common/BedSelect.tsx @@ -4,6 +4,7 @@ import AutoCompleteAsync from "../Form/AutoCompleteAsync"; import { useTranslation } from "react-i18next"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; +import { AssetClass } from "../Assets/AssetTypes"; interface BedSelectProps { name: string; @@ -17,6 +18,7 @@ interface BedSelectProps { showAll?: boolean; showNOptions?: number; selected: BedModel | BedModel[] | null; + not_occupied_by_asset_type?: AssetClass; setSelected: (selected: BedModel | BedModel[] | null) => void; } @@ -33,6 +35,7 @@ export const BedSelect = (props: BedSelectProps) => { facility, location, showNOptions = 20, + not_occupied_by_asset_type, } = props; const { t } = useTranslation(); @@ -45,8 +48,8 @@ export const BedSelect = (props: BedSelectProps) => { all: searchAll, facility, location, + not_occupied_by_asset_type, }; - const { data } = await request(routes.listFacilityBeds, { query }); if (unoccupiedOnly) { From 599359e30296efa457596595627253da3898161d Mon Sep 17 00:00:00 2001 From: Ankur Prabhu <85862184+AnkurPrabhu@users.noreply.github.com> Date: Wed, 22 May 2024 10:07:56 +0530 Subject: [PATCH 04/12] Allow only admins to delete a location or a bed (#7671) * allow only admins to delete * adding size change for tooltip * add suggested changes --- src/Components/Facility/BedManagement.tsx | 22 ++++++++++++++----- .../Facility/LocationManagement.tsx | 15 +++++++++++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/Components/Facility/BedManagement.tsx b/src/Components/Facility/BedManagement.tsx index 27100bf992b..10624675b5e 100644 --- a/src/Components/Facility/BedManagement.tsx +++ b/src/Components/Facility/BedManagement.tsx @@ -5,13 +5,14 @@ import { ReactElement } from "react"; import * as Notification from "../../Utils/Notifications.js"; import { LOCATION_BED_TYPES } from "../../Common/constants"; import BedDeleteDialog from "./BedDeleteDialog"; -import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import AuthorizeFor, { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import Page from "../Common/components/Page"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import useQuery from "../../Utils/request/useQuery"; import useFilters from "../../Common/hooks/useFilters"; +import useAuthUser from "../../Common/hooks/useAuthUser"; const Loading = lazy(() => import("../Common/Loading")); interface BedManagementProps { @@ -45,7 +46,7 @@ const BedRow = (props: BedRowProps) => { show: boolean; name: string; }>({ show: false, name: "" }); - + const authUser = useAuthUser(); const handleDelete = (name: string, _id: string) => { setBedData({ show: true, @@ -53,6 +54,10 @@ const BedRow = (props: BedRowProps) => { }); }; + const allowedUser = ["DistrictAdmin", "StateAdmin"].includes( + authUser.user_type, + ); + const handleDeleteConfirm = async () => { const { res } = await request(routes.deleteFacilityBed, { pathParams: { external_id: id }, @@ -126,14 +131,19 @@ const BedRow = (props: BedRowProps) => { handleDelete(name, id)} - authorizeFor={NonReadOnlyUsers} + authorizeFor={AuthorizeFor(["DistrictAdmin", "StateAdmin"])} variant="danger" border ghost className="w-full lg:w-auto" - disabled={isOccupied} - tooltip={isOccupied ? "Bed is occupied" : undefined} - tooltipClassName="w-full lg:w-auto" + tooltip={ + !allowedUser + ? "Contact your admin to delete the bed" + : isOccupied + ? "Bed is occupied" + : undefined + } + tooltipClassName=" text-xs w-full lg:w-auto" > Delete diff --git a/src/Components/Facility/LocationManagement.tsx b/src/Components/Facility/LocationManagement.tsx index 06e0de28d62..18f3af60eb5 100644 --- a/src/Components/Facility/LocationManagement.tsx +++ b/src/Components/Facility/LocationManagement.tsx @@ -1,6 +1,6 @@ import { lazy, useState } from "react"; import ButtonV2, { Cancel } from "../Common/components/ButtonV2"; -import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import AuthorizeFor, { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import Page from "../Common/components/Page"; import routes from "../../Redux/api"; @@ -12,6 +12,7 @@ import * as Notification from "../../Utils/Notifications.js"; import ConfirmDialog from "../Common/ConfirmDialog"; import DialogModal from "../Common/Dialog"; import Uptime from "../Common/Uptime"; +import useAuthUser from "../../Common/hooks/useAuthUser"; const Loading = lazy(() => import("../Common/Loading")); @@ -21,10 +22,12 @@ interface Props { interface LocationProps extends LocationModel { facilityId: string; + disabled: boolean; setShowDeletePopup: (e: { open: boolean; name: string; id: string }) => void; } export default function LocationManagement({ facilityId }: Props) { + const authUser = useAuthUser(); const [showDeleteFailModal, setShowDeleteFailModal] = useState({ open: false, id: "", @@ -115,6 +118,11 @@ export default function LocationManagement({ facilityId }: Props) { setShowDeletePopup={setShowDeletePopup} facilityId={facilityId} {...item} + disabled={ + ["DistrictAdmin", "StateAdmin"].includes(authUser.user_type) + ? false + : true + } /> )} @@ -213,6 +221,7 @@ const Location = ({ created_date, modified_date, id, + disabled, setShowDeletePopup, facilityId, }: LocationProps) => ( @@ -286,14 +295,16 @@ const Location = ({
setShowDeletePopup({ open: true, name: name ?? "", id: id ?? "" }) } - authorizeFor={NonReadOnlyUsers} > Delete From 1554ef2fc6af55333199140659468e894b220dda Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Wed, 22 May 2024 14:02:17 +0530 Subject: [PATCH 05/12] Show standardized labels for patient categorization (#7875) --- cypress/e2e/patient_spec/patient_consultation.cy.ts | 6 +++--- cypress/e2e/patient_spec/patient_logupdate.cy.ts | 2 +- src/Common/constants.tsx | 4 ++-- src/Components/Facility/models.tsx | 6 +----- src/Components/Patient/ManagePatients.tsx | 9 +-------- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/cypress/e2e/patient_spec/patient_consultation.cy.ts b/cypress/e2e/patient_spec/patient_consultation.cy.ts index 8caea111ca1..d5732a6619d 100644 --- a/cypress/e2e/patient_spec/patient_consultation.cy.ts +++ b/cypress/e2e/patient_spec/patient_consultation.cy.ts @@ -66,7 +66,7 @@ describe("Patient Consultation in multiple combination", () => { ); patientConsultationPage.typePatientWeight(patientWeight); patientConsultationPage.typePatientHeight(patientHeight); - patientConsultationPage.selectPatientCategory("Stable"); + patientConsultationPage.selectPatientCategory("Mild"); // icd 11 - 4 diagnosis with one principal patientConsultationPage.selectPatientDiagnosis( diagnosis1, @@ -236,7 +236,7 @@ describe("Patient Consultation in multiple combination", () => { // Asymptomatic cy.searchAndSelectOption("#symptoms", "ASYMPTOMATIC"); // Abnormal category - patientConsultationPage.selectPatientCategory("Abnormal"); + patientConsultationPage.selectPatientCategory("Moderate"); patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); // one ICD-11 diagnosis patientConsultationPage.selectPatientDiagnosis( @@ -299,7 +299,7 @@ describe("Patient Consultation in multiple combination", () => { "SORE THROAT", ]); // Stable category - patientConsultationPage.selectPatientCategory("Stable"); + patientConsultationPage.selectPatientCategory("Mild"); // Date of symptoms patientConsultationPage.selectSymptomsDate( "#symptoms_onset_date", diff --git a/cypress/e2e/patient_spec/patient_logupdate.cy.ts b/cypress/e2e/patient_spec/patient_logupdate.cy.ts index 8cf83b1c5a8..a55b86e464b 100644 --- a/cypress/e2e/patient_spec/patient_logupdate.cy.ts +++ b/cypress/e2e/patient_spec/patient_logupdate.cy.ts @@ -10,7 +10,7 @@ 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 patientCategory = "Moderate"; const additionalSymptoms = "ASYMPTOMATIC"; const physicalExamination = "physical examination details"; const otherExamination = "Other"; diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 10cdbdbcc7f..de0ab305f48 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -431,8 +431,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" }, ]; diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index d61d5a9f129..49a2f1346af 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -95,11 +95,7 @@ export interface OptionsType { disabled?: boolean; } -export type PatientCategory = - | "Comfort Care" - | "Stable" - | "Abnormal" - | "Critical"; +export type PatientCategory = "Comfort Care" | "Mild" | "Moderate" | "Critical"; export interface ConsultationModel { encounter_date: string; diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index e0a3b2d9ee5..ac716f7bf45 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -77,13 +77,6 @@ function TabPanel(props: TabPanelProps) { ); } -const PatientCategoryDisplayText: Record = { - "Comfort Care": "COMFORT CARE", - Stable: "STABLE", - Abnormal: "ABNORMAL", - Critical: "CRITICAL", -}; - export const PatientManager = () => { const { t } = useTranslation(); const { @@ -473,7 +466,7 @@ export const PatientManager = () => { className={`absolute inset-y-0 left-0 flex h-full w-1 items-center rounded-l-lg transition-all duration-200 ease-in-out group-hover:w-5 ${categoryClass}`} > - {category ? PatientCategoryDisplayText[category] : "UNKNOWN"} + {category || "UNKNOWN"}
From b39b28f3026feced05535bee2e9f1caa337591f9 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 22 May 2024 14:13:19 +0530 Subject: [PATCH 06/12] Fixes procedures not working in Edit Consultation form (#7879) fixes #7878 --- src/Components/Facility/ConsultationForm.tsx | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index ba248b8db6f..7e6fe345f7c 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -125,7 +125,6 @@ type FormDetails = { death_datetime: string; death_confirmed_doctor: string; InvestigationAdvice: InvestigationType[]; - procedures: ProcedureType[]; consent_records: ConsentRecord[]; }; @@ -176,7 +175,6 @@ const initForm: FormDetails = { death_datetime: "", death_confirmed_doctor: "", InvestigationAdvice: [], - procedures: [], consent_records: [], }; @@ -375,7 +373,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { (Array.isArray(data.investigation) && data.investigation) || [], }); handleFormFieldChange({ - name: "procedures", + name: "procedure", value: (Array.isArray(data.procedure) && data.procedure) || [], }); if (data.suggestion === "R") { @@ -593,7 +591,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { } return; case "procedure": { - for (const p of state.form.procedures) { + for (const p of state.form.procedure) { if (!p.procedure?.replace(/\s/g, "").length) { errors[field] = "Procedure field can not be empty"; invalidForm = true; @@ -725,7 +723,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { create_diagnoses: isUpdate ? undefined : state.form.create_diagnoses, treating_physician: state.form.treating_physician, investigation: state.form.InvestigationAdvice, - procedure: state.form.procedures, + procedure: state.form.procedure, patient: patientId, facility: facilityId, referred_to: @@ -1433,11 +1431,11 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { > Procedures { + procedures={state.form.procedure} + setProcedures={(procedure) => { handleFormFieldChange({ - name: "procedures", - value: procedures, + name: "procedure", + value: procedure, }); }} /> From 91940910a05502998bbeb558ebd1f214875f2273 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 22 May 2024 15:30:53 +0530 Subject: [PATCH 07/12] Migrate Consultation Camera Feed to use newer camera feed components (#7654) * Migrate consultation feed to use newer camera feed components * remove console logs * adds plausible tracking * remove unused code * Revert "remove unused code" This reverts commit ab47c1c1d02ab9fc9912703d5d0898e3540c22e3. * fix responsiveness and full screen issues * Adds support for updating presets in consultation camera feed * disable update preset if camera not moved * fix loading state issue * revert unintended changes * fix responsiveness of tooltip --- src/Common/hooks/useMSEplayer.ts | 1 - src/Components/CameraFeed/AssetBedSelect.tsx | 37 ++-- src/Components/CameraFeed/CameraFeed.tsx | 6 +- .../CameraFeed/CameraFeedWithBedPresets.tsx | 22 ++- src/Components/CameraFeed/FeedControls.tsx | 16 +- .../CameraFeed/FeedNetworkSignal.tsx | 2 +- src/Components/Common/PageTitle.tsx | 2 +- .../ConsultationFeedTab.tsx | 172 +++++++++++++++++- .../Facility/ConsultationDetails/index.tsx | 51 ------ 9 files changed, 206 insertions(+), 103 deletions(-) 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/Components/CameraFeed/AssetBedSelect.tsx b/src/Components/CameraFeed/AssetBedSelect.tsx index 17701dccbde..c8a2d5451bc 100644 --- a/src/Components/CameraFeed/AssetBedSelect.tsx +++ b/src/Components/CameraFeed/AssetBedSelect.tsx @@ -1,39 +1,28 @@ 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"; 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 selected = props.value; - const { data, loading } = useQuery(routes.listAssetBeds, { - query: { - limit: 100, - facility, - asset: props.asset?.id, - bed: props.bed?.id, - }, - }); + const options = props.options.filter(({ meta }) => meta.type !== "boundary"); - const selected = props.value; + const label = props.label ?? defaultLabel; return ( - -
+ +
- {selected?.bed_object.name ?? "No Preset"} + {selected ? label(selected) : "No Preset"} @@ -46,7 +35,7 @@ export default function AssetBedSelect(props: Props) { leaveTo="opacity-0" > - {data?.results.map((obj) => ( + {options?.map((obj) => ( @@ -63,7 +52,7 @@ export default function AssetBedSelect(props: Props) { selected ? "font-bold text-white" : "font-normal" }`} > - {obj.bed_object.name}: {obj.meta.preset_name} + {label(obj)} )} @@ -75,3 +64,7 @@ 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..8f7659cf730 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -24,6 +24,7 @@ interface Props { // Controls constrolsDisabled?: boolean; shortcutsDisabled?: boolean; + onMove?: () => void; } export default function CameraFeed(props: Props) { @@ -76,7 +77,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]); @@ -90,7 +91,7 @@ export default function CameraFeed(props: Props) { setFullscreen(false)}>
@@ -180,6 +181,7 @@ export default function CameraFeed(props: Props) { setFullscreen={setFullscreen} onReset={resetStream} onMove={async (data) => { + props.onMove?.(); setState("moving"); const { res } = await operate({ type: "relative_move", data }); setTimeout(() => { diff --git a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx index 386b93325b0..4c205c0e9c6 100644 --- a/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx +++ b/src/Components/CameraFeed/CameraFeedWithBedPresets.tsx @@ -2,14 +2,22 @@ import { useState } from "react"; import { AssetBedModel, AssetData } from "../Assets/AssetTypes"; import CameraFeed from "./CameraFeed"; import AssetBedSelect from "./AssetBedSelect"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import useSlug from "../../Common/hooks/useSlug"; interface Props { asset: AssetData; } export default function LocationFeedTile(props: Props) { + const facility = useSlug("facility"); const [preset, setPreset] = useState(); + const { data, loading } = useQuery(routes.listAssetBeds, { + query: { limit: 100, facility, asset: props.asset?.id }, + }); + return (
- + {loading ? ( + loading presets... + ) : ( + + )}
); diff --git a/src/Components/CameraFeed/FeedControls.tsx b/src/Components/CameraFeed/FeedControls.tsx index 3a5afb76209..46258114fc0 100644 --- a/src/Components/CameraFeed/FeedControls.tsx +++ b/src/Components/CameraFeed/FeedControls.tsx @@ -87,6 +87,7 @@ export default function FeedControls({ shortcutsDisabled, ...props }: Props) { helpText="Up" shortcut={Shortcuts.MoveUp} shortcutsDisabled={shortcutsDisabled} + tooltipClassName="-translate-y-20 md:translate-y-0" > @@ -102,6 +103,7 @@ export default function FeedControls({ shortcutsDisabled, ...props }: Props) { helpText="Left" shortcut={Shortcuts.MoveLeft} shortcutsDisabled={shortcutsDisabled} + tooltipClassName="-translate-y-20 -translate-x-1 md:translate-x-0 md:translate-y-0" > @@ -114,6 +116,7 @@ export default function FeedControls({ shortcutsDisabled, ...props }: Props) { shortcut={Shortcuts.TogglePrecision} className="font-bold" shortcutsDisabled={shortcutsDisabled} + tooltipClassName="-translate-y-20 -translate-x-20 md:translate-x-0 md:translate-y-0" > {precision}x @@ -125,6 +128,7 @@ export default function FeedControls({ shortcutsDisabled, ...props }: Props) { helpText="Right" shortcut={Shortcuts.MoveRight} shortcutsDisabled={shortcutsDisabled} + tooltipClassName="-translate-y-20 -translate-x-2 md:translate-x-0 md:translate-y-0" > @@ -142,7 +146,7 @@ export default function FeedControls({ shortcutsDisabled, ...props }: Props) { helpText="Down" shortcut={Shortcuts.MoveDown} shortcutsDisabled={shortcutsDisabled} - tooltipClassName="tooltip-top" + tooltipClassName="-translate-y-20 -translate-x-2 md:-translate-x-14" > @@ -185,16 +189,6 @@ export default function FeedControls({ shortcutsDisabled, ...props }: Props) { > - {/* TODO: implement this when this is used in where presets can be saved. */} - {/* console.error("Not implemented")} - shortcutsDisabled={shortcutsDisabled} - > - - */} delay || delay > 5) { props.onReset(); } - }, 500); + }, 1000); return () => { clearInterval(interval); diff --git a/src/Components/Common/PageTitle.tsx b/src/Components/Common/PageTitle.tsx index 6fb38a36f08..990dc7b72cc 100644 --- a/src/Components/Common/PageTitle.tsx +++ b/src/Components/Common/PageTitle.tsx @@ -47,7 +47,7 @@ export default function PageTitle({ useEffect(() => { if (divRef.current && focusOnLoad) { - divRef.current.scrollIntoView(); + divRef.current.scrollIntoView({ behavior: "smooth" }); } }, [divRef, focusOnLoad]); diff --git a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx index f4fbc08331c..64ba39ae5aa 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationFeedTab.tsx @@ -1,22 +1,176 @@ -import { lazy } from "react"; -import { Feed } from "../Consultations/Feed"; +import { useEffect, useRef, useState } from "react"; import { ConsultationTabProps } from "./index"; - -const PageTitle = lazy(() => import("../../Common/PageTitle")); +import { AssetBedModel, AssetData } from "../../Assets/AssetTypes"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; +import CameraFeed from "../../CameraFeed/CameraFeed"; +import Loading from "../../Common/Loading"; +import AssetBedSelect from "../../CameraFeed/AssetBedSelect"; +import { triggerGoal } from "../../../Integrations/Plausible"; +import useAuthUser from "../../../Common/hooks/useAuthUser"; +import PageTitle from "../../Common/PageTitle"; +import useSlug from "../../../Common/hooks/useSlug"; +import CareIcon from "../../../CAREUI/icons/CareIcon"; +import ButtonV2 from "../../Common/components/ButtonV2"; +import useOperateCamera, { + PTZPayload, +} from "../../CameraFeed/useOperateCamera"; +import request from "../../../Utils/request/request"; +import { classNames } from "../../../Utils/utils"; export const ConsultationFeedTab = (props: ConsultationTabProps) => { + const authUser = useAuthUser(); + const facility = useSlug("facility"); + const bed = props.consultationData.current_bed?.bed_object; + + const [asset, setAsset] = useState(); + const [preset, setPreset] = useState(); + const [isUpdatingPreset, setIsUpdatingPreset] = useState(false); + const [hasMoved, setHasMoved] = useState(false); + const divRef = useRef(); + + const operate = useOperateCamera(asset?.id ?? "", true); + + const { data, loading, refetch } = useQuery(routes.listAssetBeds, { + query: { limit: 100, facility, bed: bed?.id, asset: asset?.id }, + prefetch: !!bed, + onResponse: ({ data }) => { + if (!data) { + return; + } + + const preset = data.results.find( + (obj) => + obj.asset_object.meta?.asset_type === "CAMERA" && + obj.meta.type !== "boundary", + ); + + if (preset) { + setPreset(preset); + setAsset(preset.asset_object); + } + }, + }); + + const presets = data?.results; + + const handleUpdatePreset = async () => { + if (!preset) return; + + setIsUpdatingPreset(true); + + const { data } = await operate({ type: "get_status" }); + const { position } = (data as { result: { position: PTZPayload } }).result; + + const { data: updated } = await request(routes.partialUpdateAssetBed, { + pathParams: { external_id: preset.id }, + body: { + asset: preset.asset_object.id, + bed: preset.bed_object.id, + meta: { ...preset.meta, position }, + }, + }); + + await refetch(); + + setPreset(updated); + setHasMoved(false); + setIsUpdatingPreset(false); + }; + + useEffect(() => { + if (divRef.current) { + divRef.current.scrollIntoView({ behavior: "smooth" }); + } + }, [!!bed, loading, !!asset, divRef.current]); + + if (loading) { + return ; + } + + if (!bed || !asset) { + return No bed/asset linked allocated; + } + return (
- +
+ setHasMoved(true)} + onStreamError={() => { + triggerGoal("Camera Feed Viewed", { + consultationId: props.consultationId, + userId: authUser.id, + result: "error", + }); + }} + onStreamSuccess={() => { + triggerGoal("Camera Feed Viewed", { + consultationId: props.consultationId, + userId: authUser.id, + result: "success", + }); + }} + > +
+ {presets ? ( + <> + obj.meta.preset_name} + value={preset} + onChange={(value) => { + triggerGoal("Camera Preset Clicked", { + presetName: preset?.meta?.preset_name, + consultationId: props.consultationId, + userId: authUser.id, + result: "success", + }); + setHasMoved(false); + setPreset(value); + }} + /> + {isUpdatingPreset ? ( + + ) : ( + + + + )} + + ) : ( + loading presets... + )} +
+
+
); }; diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 4d393752888..38882c66f33 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -210,57 +210,6 @@ export const ConsultationDetails = (props: any) => { selected === true ? "border-primary-500 text-primary-600 border-b-2" : "" }`; - // const ShowDiagnosis = ({ - // diagnoses = [], - // label = "Diagnosis", - // nshow = 2, - // }: { - // diagnoses: ICD11DiagnosisModel[] | undefined; - // label: string; - // nshow?: number; - // }) => { - // const [showMore, setShowMore] = useState(false); - - // return diagnoses.length ? ( - //
- //

{label}

- // {diagnoses.slice(0, !showMore ? nshow : undefined).map((diagnosis) => - // diagnosis.id === consultationData.icd11_principal_diagnosis ? ( - //
- //

{diagnosis.label}

- //
- // - // - // - //
- //
- // ) : ( - //

{diagnosis.label}

- // ) - // )} - // {diagnoses.length > nshow && ( - // <> - // {!showMore ? ( - // setShowMore(true)} - // className="cursor-pointer text-sm text-blue-600 hover:text-blue-300" - // > - // show more - // - // ) : ( - // setShowMore(false)} - // className="cursor-pointer text-sm text-blue-600 hover:text-blue-300" - // > - // show less - // - // )} - // - // )} - //
- // ) : null; - // }; - return (
From 48838b8f6c74fff5e9154e4bdc00d4a408bfe2bb Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 22 May 2024 17:27:43 +0530 Subject: [PATCH 08/12] CNS: Skip filtering by `bed_occupied` if "hide monitors without patient" is unchecked (#7881) --- src/Components/Facility/CentralNursingStation.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Components/Facility/CentralNursingStation.tsx b/src/Components/Facility/CentralNursingStation.tsx index 30005be0be6..2f31b619776 100644 --- a/src/Components/Facility/CentralNursingStation.tsx +++ b/src/Components/Facility/CentralNursingStation.tsx @@ -48,7 +48,8 @@ export default function CentralNursingStation({ facilityId }: Props) { asset_class: "HL7MONITOR", ordering: qParams.ordering || "bed__name", bed_is_occupied: - (qParams.hide_monitors_without_patient ?? "true") === "true", + (qParams.hide_monitors_without_patient ?? "true") === "true" || + undefined, }, }); From 374d11cd5f011de87cbd8ffe4d400ba9cfa77151 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 22 May 2024 17:30:15 +0530 Subject: [PATCH 09/12] Hides prescription related action buttons for non-home facility users + Adds reusable component to hide child component if authzn. requirements not met (#7880) * remove logically never used code * Adds reusable component: `AuthorizedForConsultationRelatedActions` to hide buttons based on home facility access * Disallow unauthorized view for button related to prescriptions * wrap archive action with authzn. wrapper --- src/CAREUI/misc/AuthorizedChild.tsx | 20 +++ .../ConsultationUpdatesTab.tsx | 2 - .../AdministrationTableRow.tsx | 109 ++++++------ .../MedicineAdministrationSheet/index.tsx | 5 +- .../Medicine/PrescriptionBuilder.tsx | 36 ++-- .../Medicine/PrescriptionDetailCard.tsx | 57 ++++--- .../Medicine/PrescriptionsTable.tsx | 155 +----------------- .../Medicine/PrescrpitionTimeline.tsx | 21 ++- 8 files changed, 145 insertions(+), 260 deletions(-) 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/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index e9e658cee4f..a7e52257e28 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -249,7 +249,6 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
@@ -257,7 +256,6 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
diff --git a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx index 4fc7a97f56b..13f5191f429 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx @@ -14,6 +14,7 @@ import CareIcon from "../../../CAREUI/icons/CareIcon"; import EditPrescriptionForm from "../EditPrescriptionForm"; import AdministrationEventSeperator from "./AdministrationEventSeperator"; import AdministrationEventCell from "./AdministrationEventCell"; +import { AuthorizedForConsultationRelatedActions } from "../../../CAREUI/misc/AuthorizedChild"; interface Props { prescription: Prescription; @@ -93,46 +94,48 @@ export default function MedicineAdministrationTableRow({ onClick={() => setShowDetails(false)} label={t("close")} /> - {!props.readonly && ( - <> - setShowDiscontinue(true)} - > - - {t("discontinue")} - - { - setShowDetails(false); - setShowEdit(true); - }} - > - - {t("edit")} - - setShowAdminister(true)} - > - - {t("administer")} - - - )} + + {!props.readonly && ( + <> + setShowDiscontinue(true)} + > + + {t("discontinue")} + + { + setShowDetails(false); + setShowEdit(true); + }} + > + + {t("edit")} + + setShowAdminister(true)} + > + + {t("administer")} + + + )} +
@@ -252,18 +255,20 @@ export default function MedicineAdministrationTableRow({ {/* Action Buttons */} - {!props.readonly && ( - setShowAdminister(true)} - > - {t("administer")} - - )} + + {!props.readonly && ( + setShowAdminister(true)} + > + {t("administer")} + + )} + diff --git a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx index c1917cef41d..59d3976d763 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx @@ -13,6 +13,7 @@ import useRangePagination from "../../../Common/hooks/useRangePagination"; import MedicineAdministrationTable from "./AdministrationTable"; import Loading from "../../Common/Loading"; import ScrollOverlay from "../../../CAREUI/interactive/ScrollOverlay"; +import { AuthorizedForConsultationRelatedActions } from "../../../CAREUI/misc/AuthorizedChild"; interface Props { readonly?: boolean; @@ -89,7 +90,7 @@ const MedicineAdministrationSheet = ({ readonly, is_prn }: Props) => { options={ !readonly && !!data?.results && ( - <> + { prescriptions={data.results} onDone={() => refetch()} /> - + ) } /> diff --git a/src/Components/Medicine/PrescriptionBuilder.tsx b/src/Components/Medicine/PrescriptionBuilder.tsx index d5388dfa44a..972a74159d2 100644 --- a/src/Components/Medicine/PrescriptionBuilder.tsx +++ b/src/Components/Medicine/PrescriptionBuilder.tsx @@ -11,6 +11,7 @@ import { useTranslation } from "react-i18next"; import useQuery from "../../Utils/request/useQuery"; import MedicineRoutes from "./routes"; import useSlug from "../../Common/hooks/useSlug"; +import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; interface Props { prescription_type?: Prescription["prescription_type"]; @@ -75,20 +76,27 @@ export default function PrescriptionBuilder({ /> ))}
- setShowCreate(true)} - variant="secondary" - className="mt-4 w-full bg-gray-200 text-gray-700 hover:bg-gray-300 hover:text-gray-900 focus:bg-gray-100 focus:text-gray-900" - disabled={disabled} - > -
- - - {t(is_prn ? "add_prn_prescription" : "add_prescription_medication")} - -
-
+ + setShowCreate(true)} + variant="secondary" + className="mt-4 w-full bg-gray-200 text-gray-700 hover:bg-gray-300 hover:text-gray-900 focus:bg-gray-100 focus:text-gray-900" + disabled={disabled} + > +
+ + + {t( + is_prn ? "add_prn_prescription" : "add_prescription_medication", + )} + +
+
+
{showCreate && ( setShowCreate(false)} diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx index 166408e6292..630ab324662 100644 --- a/src/Components/Medicine/PrescriptionDetailCard.tsx +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -5,6 +5,7 @@ import ReadMore from "../Common/components/Readmore"; import ButtonV2 from "../Common/components/ButtonV2"; import { useTranslation } from "react-i18next"; import RecordMeta from "../../CAREUI/display/RecordMeta"; +import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; export default function PrescriptionDetailCard({ prescription, @@ -56,33 +57,35 @@ export default function PrescriptionDetailCard({ {!props.readonly && prescription.prescription_type !== "DISCHARGE" && ( -
- - - {t("administer")} - - - - {t("discontinue")} - -
+ +
+ + + {t("administer")} + + + + {t("discontinue")} + +
+
)}
diff --git a/src/Components/Medicine/PrescriptionsTable.tsx b/src/Components/Medicine/PrescriptionsTable.tsx index d61275c42c1..f842ccc654b 100644 --- a/src/Components/Medicine/PrescriptionsTable.tsx +++ b/src/Components/Medicine/PrescriptionsTable.tsx @@ -3,12 +3,8 @@ import ResponsiveMedicineTable from "./ResponsiveMedicineTables"; import { formatDateTime } from "../../Utils/utils"; import { Prescription } from "./models"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; -import SlideOver from "../../CAREUI/interactive/SlideOver"; -import MedicineAdministration from "./MedicineAdministration"; -import DiscontinuePrescription from "./DiscontinuePrescription"; +import { Cancel } from "../Common/components/ButtonV2"; import RecordMeta from "../../CAREUI/display/RecordMeta"; -import AdministerMedicine from "./AdministerMedicine"; import DialogModal from "../Common/Dialog"; import PrescriptionDetailCard from "./PrescriptionDetailCard"; import { useTranslation } from "react-i18next"; @@ -19,21 +15,14 @@ import MedicineRoutes from "./routes"; interface Props { is_prn?: boolean; prescription_type?: Prescription["prescription_type"]; - onChange?: () => void; - readonly?: boolean; } export default function PrescriptionsTable({ is_prn = false, prescription_type = "REGULAR", - onChange, - readonly, }: Props) { const consultation = useSlug("consultation"); const { t } = useTranslation(); - const [showBulkAdminister, setShowBulkAdminister] = useState(false); - const [showDiscontinueFor, setShowDiscontinueFor] = useState(); - const [showAdministerFor, setShowAdministerFor] = useState(); const [detailedViewFor, setDetailedViewFor] = useState(); const { data } = useQuery(MedicineRoutes.listPrescriptions, { @@ -57,42 +46,6 @@ export default function PrescriptionsTable({ return (
- {data?.results && ( - - { - setShowBulkAdminister(false); - onChange?.(); - }} - /> - - )} - {showDiscontinueFor && ( - { - setShowDiscontinueFor(undefined); - if (success) onChange?.(); - }} - key={showDiscontinueFor.id} - /> - )} - {showAdministerFor && ( - { - setShowAdministerFor(undefined); - if (success) onChange?.(); - }} - key={showAdministerFor.id} - /> - )} {detailedViewFor && ( setDetailedViewFor(undefined)} @@ -111,27 +64,6 @@ export default function PrescriptionsTable({ onClick={() => setDetailedViewFor(undefined)} label={t("close")} /> - setShowDiscontinueFor(detailedViewFor)} - > - - {t("discontinue")} - - setShowAdministerFor(detailedViewFor)} - > - - {t("administer")} -
@@ -148,34 +80,6 @@ export default function PrescriptionsTable({
- {prescription_type === "REGULAR" && ( -
- - - {t("edit_prescriptions")} - {t("edit")} - - setShowBulkAdminister(true)} - className="w-full lg:w-auto" - > - - - {t("administer_medicines")} - - {t("administer")} - -
- )}
@@ -209,63 +113,6 @@ export default function PrescriptionsTable({ } objectKeys={Object.values(tkeys)} fieldsToDisplay={[2, 3]} - actions={ - !readonly - ? (med: Prescription) => { - if (med.prescription_type === "DISCHARGE") { - return ( -
- - {t("discharge_prescription")} - -
- ); - } - - if (med.discontinued) { - return ( -
- - {t("discontinued")} -
- ); - } - - return ( -
- { - e.stopPropagation(); - setShowAdministerFor(med); - }} - > - - {t("administer")} - - { - e.stopPropagation(); - setShowDiscontinueFor(med); - }} - > - - {t("discontinue")} - -
- ); - } - : undefined - } /> {data?.results.length === 0 && (
diff --git a/src/Components/Medicine/PrescrpitionTimeline.tsx b/src/Components/Medicine/PrescrpitionTimeline.tsx index 9422d349b3a..f26957f63c6 100644 --- a/src/Components/Medicine/PrescrpitionTimeline.tsx +++ b/src/Components/Medicine/PrescrpitionTimeline.tsx @@ -15,6 +15,7 @@ import ConfirmDialog from "../Common/ConfirmDialog"; import request from "../../Utils/request/request"; import RecordMeta from "../../CAREUI/display/RecordMeta"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { AuthorizedForConsultationRelatedActions } from "../../CAREUI/misc/AuthorizedChild"; interface MedicineAdministeredEvent extends TimelineEvent<"administered"> { administration: MedicineAdministrationRecord; @@ -131,15 +132,17 @@ const MedicineAdministeredNode = ({ actions={ !event.cancelled && !hideArchive && ( - setShowArchiveConfirmation(true)} - > - Archive - + + setShowArchiveConfirmation(true)} + > + Archive + + ) } isLast={isLastNode} From 8582bbd1f323a1d97d9f8f4a3df9ce5ca55d623a Mon Sep 17 00:00:00 2001 From: Sunny Thakurwar <87633236+sunny-thakurwar@users.noreply.github.com> Date: Wed, 22 May 2024 17:31:04 +0530 Subject: [PATCH 10/12] Show loading state when switching tabs in Discussion Notes (#7819) * Show loading state when switching tabs in Discussion Notes * fixed patient note list loading --- src/Components/Facility/PatientConsultationNotesList.tsx | 3 ++- src/Components/Facility/PatientNotesList.tsx | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Components/Facility/PatientConsultationNotesList.tsx b/src/Components/Facility/PatientConsultationNotesList.tsx index e7e0bb25504..f81ef122f6c 100644 --- a/src/Components/Facility/PatientConsultationNotesList.tsx +++ b/src/Components/Facility/PatientConsultationNotesList.tsx @@ -26,6 +26,7 @@ const PatientConsultationNotesList = (props: PatientNotesProps) => { const fetchNotes = async () => { setIsLoading(true); + const { data } = await request(routes.getPatientNotes, { pathParams: { patientId: props.state.patientId || "", @@ -80,7 +81,7 @@ const PatientConsultationNotesList = (props: PatientNotesProps) => { } }; - if (isLoading && !state.notes.length) { + if (isLoading) { return (
diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 9585ace5db7..4e1d0a5a7a6 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -74,7 +74,7 @@ const PatientNotesList = (props: PatientNotesProps) => { } }; - if (isLoading && !state.notes.length) { + if (isLoading) { return (
From 729211544780091925e878a7eb0effdadc1c8680 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar <91010142+r-nikhilkumar@users.noreply.github.com> Date: Wed, 22 May 2024 17:31:50 +0530 Subject: [PATCH 11/12] [FIXED] Enhancement of pain scale value and badge position (#7795) * issue resolved * string interpolation issue solved * string interpolation issue solved using className utility * box width reduced and everything else is centered aligned --- .../CriticalCareRecording/components/Slider.res | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Components/CriticalCareRecording/components/Slider.res b/src/Components/CriticalCareRecording/components/Slider.res index d3ab996683e..9dc8dfd2d2c 100644 --- a/src/Components/CriticalCareRecording/components/Slider.res +++ b/src/Components/CriticalCareRecording/components/Slider.res @@ -47,6 +47,9 @@ let make = ( let (text, setText) = React.useState(() => "Normal") let (precision, setPrecision) = React.useState(() => 1) let (displayValue, setDisplayValue) = React.useState(() => value) + let justifyContentClassName = title != "" ? "justify-between" : "justify-center" + let alignItemClassName = title != "" ? "items-end" : "items-center" + let boxWidthClassName = title != "" ? "" : "w-16" React.useEffect1(() => { let (text, color) = getLabel(value->Belt.Float.fromString->Belt.Option.getWithDefault(0.0)) @@ -76,11 +79,11 @@ let make = ( <>
-
+

{title->str}

titleNeighbour
-
+