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..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/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 ( 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/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) { 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/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
-
+
- {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} 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"}
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