@@ -270,6 +270,7 @@ export default function InventoryLog(props: any) {
as accident.
removeLastInventoryLog(inventory[0].item_object.id)
diff --git a/src/Components/Facility/MinQuantityList.tsx b/src/Components/Facility/MinQuantityList.tsx
index 631f432a9dc..941722bd810 100644
--- a/src/Components/Facility/MinQuantityList.tsx
+++ b/src/Components/Facility/MinQuantityList.tsx
@@ -126,6 +126,7 @@ export default function MinQuantityList(props: any) {
{inventoryItem.item_object?.default_unit?.name}
{
/>
-
+
Update
From cc3886007e46c41f908567e0a32438785eedcfd0 Mon Sep 17 00:00:00 2001
From: Aditya Pachauri <103623274+AdityyaX@users.noreply.github.com>
Date: Wed, 10 Jan 2024 13:25:44 +0530
Subject: [PATCH 22/36] Updated DOB Date Format in Patient details page (#6950)
* date format changed
* Update src/Components/Patient/PatientHome.tsx
Co-authored-by: Rithvik Nishad
* Update src/Components/Patient/PatientHome.tsx
Co-authored-by: Rithvik Nishad
* fix imports
---------
Co-authored-by: Rithvik Nishad
Co-authored-by: rithviknishad
---
src/Components/Patient/PatientHome.tsx | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx
index 22edfb16192..32118e5951e 100644
--- a/src/Components/Patient/PatientHome.tsx
+++ b/src/Components/Patient/PatientHome.tsx
@@ -19,7 +19,12 @@ import { ConsultationModel } from "../Facility/models";
import { PatientModel, SampleTestModel } from "./models";
import { SampleTestCard } from "./SampleTestCard";
import Chip from "../../CAREUI/display/Chip";
-import { classNames, formatAge, formatDateTime } from "../../Utils/utils";
+import {
+ classNames,
+ formatAge,
+ formatDate,
+ formatDateTime,
+} from "../../Utils/utils";
import ButtonV2 from "../Common/components/ButtonV2";
import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
import RelativeDateUserMention from "../Common/RelativeDateUserMention";
@@ -570,7 +575,7 @@ export const PatientHome = (props: any) => {
Date of Birth
- {patientData?.date_of_birth}
+ {formatDate(patientData?.date_of_birth)}
From 197cb39f42ea578922db0f45983e842613454ce0 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Wed, 10 Jan 2024 13:28:10 +0530
Subject: [PATCH 23/36] Migrate `useDispatch` with `useQuery/request` in
`src/Components/Common/**` (#6889)
* Migrate to `useQuery` in `AssetSelect`
* Migrate `useQuery` in `BedSelect`
* Migrate `useQuery` in `DistrictAutocompleteFormField`
* Migrate `useQuery` in `StateAutocompleteFormField`
* Migrate `useQuery` in `LocalBodyAutocompleteFormField`
* Migrate `useQuery` in `FacilitySelect`
* Migrate `useQuery` in `LocationSelect`
* Migrate `useQuery` in `SkillSelect`
* Migrate `useQuery` in `InvestigationBuilder`
* Migrate `useQuery` in `Uptime` (Asset)
* fix location select not working for single select
* uptime: fix issue with reverse
---
src/Components/Common/AssetSelect.tsx | 28 ++----
src/Components/Common/BedSelect.tsx | 24 +++--
.../Common/DistrictAutocompleteFormField.tsx | 46 +++-------
src/Components/Common/FacilitySelect.tsx | 20 ++---
.../Common/LocalBodyAutocompleteFormField.tsx | 48 +++-------
src/Components/Common/LocationSelect.tsx | 89 +++++++------------
src/Components/Common/SkillSelect.tsx | 25 ++----
.../Common/StateAutocompleteFormField.tsx | 36 ++------
src/Components/Common/Uptime.tsx | 42 +++------
.../InvestigationBuilder.tsx | 37 +++-----
src/Components/Facility/AssetCreate.tsx | 2 +-
src/Components/Facility/ConsultationForm.tsx | 13 ++-
.../Facility/Investigations/Reports/index.tsx | 4 +-
.../Facility/Investigations/index.tsx | 12 +--
src/Components/Facility/models.tsx | 10 ++-
src/Components/Users/SkillsSlideOver.tsx | 2 -
src/Redux/actions.tsx | 15 +---
src/Redux/api.tsx | 12 ++-
18 files changed, 156 insertions(+), 309 deletions(-)
diff --git a/src/Components/Common/AssetSelect.tsx b/src/Components/Common/AssetSelect.tsx
index 0e47c7cfbfd..3f4f928032d 100644
--- a/src/Components/Common/AssetSelect.tsx
+++ b/src/Components/Common/AssetSelect.tsx
@@ -1,7 +1,7 @@
import { useCallback } from "react";
-import { useDispatch } from "react-redux";
-import { listAssets } from "../../Redux/actions";
import AutoCompleteAsync from "../Form/AutoCompleteAsync";
+import routes from "../../Redux/api";
+import request from "../../Utils/request/request";
interface AssetSelectProps {
name: string;
@@ -16,7 +16,6 @@ interface AssetSelectProps {
asset_class?: string;
showAll?: boolean;
showNOptions?: number;
- freeText?: boolean;
selected: any;
setSelected: (selected: any) => void;
}
@@ -33,21 +32,13 @@ export const AssetSelect = (props: AssetSelectProps) => {
is_permanent = null,
showNOptions = 10,
className = "",
- freeText = false,
errors = "",
asset_class = "",
} = props;
- const dispatchAction: any = useDispatch();
-
const AssetSearch = useCallback(
async (text: string) => {
- const params: Partial & {
- limit: number;
- offset: number;
- search_text: string;
- asset_class: string;
- } = {
+ const query = {
limit: 50,
offset: 0,
search_text: text,
@@ -56,17 +47,12 @@ export const AssetSelect = (props: AssetSelectProps) => {
in_use_by_consultation,
is_permanent,
asset_class,
- };
+ } as const;
- const res = await dispatchAction(listAssets(params));
- if (freeText)
- res?.data?.results?.push({
- id: -1,
- name: text,
- });
- return res?.data?.results;
+ const { data } = await request(routes.listAssets, { query });
+ return data?.results;
},
- [dispatchAction]
+ [asset_class, facility, in_use_by_consultation, is_permanent, is_working]
);
return (
diff --git a/src/Components/Common/BedSelect.tsx b/src/Components/Common/BedSelect.tsx
index 94caded12a3..a548b7a9eb5 100644
--- a/src/Components/Common/BedSelect.tsx
+++ b/src/Components/Common/BedSelect.tsx
@@ -1,9 +1,9 @@
import { useCallback } from "react";
-import { useDispatch } from "react-redux";
-import { listFacilityBeds } from "../../Redux/actions";
import { BedModel } from "../Facility/models";
import AutoCompleteAsync from "../Form/AutoCompleteAsync";
import { useTranslation } from "react-i18next";
+import request from "../../Utils/request/request";
+import routes from "../../Redux/api";
interface BedSelectProps {
name: string;
@@ -34,13 +34,11 @@ export const BedSelect = (props: BedSelectProps) => {
location,
showNOptions = 20,
} = props;
-
- const dispatchAction: any = useDispatch();
const { t } = useTranslation();
const onBedSearch = useCallback(
async (text: string) => {
- const params = {
+ const query = {
limit: 50,
offset: 0,
search_text: text,
@@ -49,17 +47,17 @@ export const BedSelect = (props: BedSelectProps) => {
location,
};
- const res = await dispatchAction(listFacilityBeds(params));
- if (res && res.data) {
- let beds = res.data.results;
- if (unoccupiedOnly) {
- beds = beds.filter((bed: BedModel) => bed?.is_occupied === false);
- }
+ const { data } = await request(routes.listFacilityBeds, { query });
- return beds;
+ if (unoccupiedOnly) {
+ return data?.results?.filter(
+ (bed: BedModel) => bed?.is_occupied === false
+ );
}
+
+ return data?.results;
},
- [dispatchAction, facility, location, searchAll, unoccupiedOnly]
+ [facility, location, searchAll, unoccupiedOnly]
);
return (
diff --git a/src/Components/Common/DistrictAutocompleteFormField.tsx b/src/Components/Common/DistrictAutocompleteFormField.tsx
index 122b576a2fc..dbee6c9f08d 100644
--- a/src/Components/Common/DistrictAutocompleteFormField.tsx
+++ b/src/Components/Common/DistrictAutocompleteFormField.tsx
@@ -1,51 +1,27 @@
-import { useDispatch } from "react-redux";
import { FormFieldBaseProps } from "../Form/FormFields/Utils";
-import { IState } from "./StateAutocompleteFormField";
import AutocompleteFormField from "../Form/FormFields/Autocomplete";
-import { statusType, useAbortableEffect } from "../../Common/utils";
-import { useCallback, useState } from "react";
-import { getDistrictByState } from "../../Redux/actions";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
+import { DistrictModel, StateModel } from "../Facility/models";
-export type IDistrict = {
- id: number;
- name: string;
-};
-
-type Props = FormFieldBaseProps & {
+type Props = FormFieldBaseProps & {
placeholder?: string;
- state?: IState["id"];
+ state?: StateModel["id"];
};
export default function DistrictAutocompleteFormField(props: Props) {
- const dispatch = useDispatch();
- const [districts, setDistricts] = useState();
-
- const fetchDistricts = useCallback(
- async (status: any) => {
- setDistricts(undefined);
- if (!props.state) {
- return;
- }
- const res = await dispatch(getDistrictByState({ id: props.state }));
- if (!status.aborted && res.data) {
- setDistricts(res.data);
- }
- },
- [dispatch, props.state]
- );
-
- useAbortableEffect(
- (status: statusType) => fetchDistricts(status),
- [props.state]
- );
+ const { data, loading } = useQuery(routes.getDistrictByState, {
+ pathParams: { id: props.state?.toString() ?? "" },
+ prefetch: props.state !== undefined,
+ });
return (
option.name}
optionValue={(option) => option.id}
- isLoading={!!(props.state && districts === undefined)}
+ isLoading={loading}
disabled={!props.state}
/>
);
diff --git a/src/Components/Common/FacilitySelect.tsx b/src/Components/Common/FacilitySelect.tsx
index 1aabc36013b..17f67a7def1 100644
--- a/src/Components/Common/FacilitySelect.tsx
+++ b/src/Components/Common/FacilitySelect.tsx
@@ -1,8 +1,8 @@
import { useCallback } from "react";
-import { useDispatch } from "react-redux";
-import { getAllFacilities, getPermittedFacilities } from "../../Redux/actions";
import AutoCompleteAsync from "../Form/AutoCompleteAsync";
import { FacilityModel } from "../Facility/models";
+import request from "../../Utils/request/request";
+import routes from "../../Redux/api";
interface FacilitySelectProps {
name: string;
@@ -37,11 +37,9 @@ export const FacilitySelect = (props: FacilitySelectProps) => {
errors = "",
} = props;
- const dispatchAction: any = useDispatch();
-
const facilitySearch = useCallback(
async (text: string) => {
- const params = {
+ const query = {
limit: 50,
offset: 0,
search_text: text,
@@ -51,17 +49,19 @@ export const FacilitySelect = (props: FacilitySelectProps) => {
district,
};
- const res = await dispatchAction(
- showAll ? getAllFacilities(params) : getPermittedFacilities(params)
+ const { data } = await request(
+ showAll ? routes.getAllFacilities : routes.getPermittedFacilities,
+ { query }
);
+
if (freeText)
- res?.data?.results?.push({
+ data?.results?.push({
id: -1,
name: text,
});
- return res?.data?.results;
+ return data?.results;
},
- [dispatchAction, searchAll, showAll, facilityType, district]
+ [searchAll, showAll, facilityType, district, exclude_user, freeText]
);
return (
diff --git a/src/Components/Common/LocalBodyAutocompleteFormField.tsx b/src/Components/Common/LocalBodyAutocompleteFormField.tsx
index eacfbc35b89..32023fa559f 100644
--- a/src/Components/Common/LocalBodyAutocompleteFormField.tsx
+++ b/src/Components/Common/LocalBodyAutocompleteFormField.tsx
@@ -1,53 +1,27 @@
-import { useDispatch } from "react-redux";
import { FormFieldBaseProps } from "../Form/FormFields/Utils";
import AutocompleteFormField from "../Form/FormFields/Autocomplete";
-import { statusType, useAbortableEffect } from "../../Common/utils";
-import { useCallback, useState } from "react";
-import { getLocalbodyByDistrict } from "../../Redux/actions";
-import { IDistrict } from "./DistrictAutocompleteFormField";
+import { DistrictModel, LocalBodyModel } from "../Facility/models";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
-export type ILocalBody = {
- id: number;
- name: string;
-};
-
-type Props = FormFieldBaseProps & {
+type Props = FormFieldBaseProps & {
placeholder?: string;
- district?: IDistrict["id"];
+ district?: DistrictModel["id"];
};
export default function LocalBodyAutocompleteFormField(props: Props) {
- const dispatch = useDispatch();
- const [localBodies, setLocalBodies] = useState();
-
- const fetchLocalBodies = useCallback(
- async (status: any) => {
- setLocalBodies(undefined);
- if (!props.district) {
- return;
- }
- const res = await dispatch(
- getLocalbodyByDistrict({ id: props.district })
- );
- if (!status.aborted && res && res.data) {
- setLocalBodies(res.data);
- }
- },
- [dispatch, props.district]
- );
-
- useAbortableEffect(
- (status: statusType) => fetchLocalBodies(status),
- [props.district]
- );
+ const { data, loading } = useQuery(routes.getLocalbodyByDistrict, {
+ pathParams: { id: props.district?.toString() ?? "" },
+ prefetch: props.district !== undefined,
+ });
return (
option.name}
optionValue={(option) => option.id}
- isLoading={!!(props.district && localBodies === undefined)}
+ isLoading={loading}
disabled={!props.district}
/>
);
diff --git a/src/Components/Common/LocationSelect.tsx b/src/Components/Common/LocationSelect.tsx
index d20c843fad8..dcade42a596 100644
--- a/src/Components/Common/LocationSelect.tsx
+++ b/src/Components/Common/LocationSelect.tsx
@@ -1,8 +1,7 @@
-import { useEffect, useState } from "react";
-import { useDispatch } from "react-redux";
-import { listFacilityAssetLocation } from "../../Redux/actions";
import AutocompleteFormField from "../Form/FormFields/Autocomplete";
import AutocompleteMultiSelectFormField from "../Form/FormFields/AutocompleteMultiselect";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
interface LocationSelectProps {
name: string;
disabled?: boolean;
@@ -11,7 +10,7 @@ interface LocationSelectProps {
className?: string;
searchAll?: boolean;
multiple?: boolean;
- facilityId: number | string;
+ facilityId: string;
showAll?: boolean;
selected: string | string[] | null;
setSelected: (selected: string | string[] | null) => void;
@@ -19,76 +18,48 @@ interface LocationSelectProps {
}
export const LocationSelect = (props: LocationSelectProps) => {
- const {
- name,
- multiple,
- selected,
- setSelected,
- errors,
- className = "",
- facilityId,
- disabled = false,
- } = props;
- const [locations, setLocations] = useState<{ name: string; id: string }[]>(
- []
+ const { data, loading, refetch } = useQuery(
+ routes.listFacilityAssetLocation,
+ {
+ query: {
+ limit: 14,
+ },
+ pathParams: {
+ facility_external_id: props.facilityId,
+ },
+ prefetch: props.facilityId !== undefined,
+ }
);
- const [query, setQuery] = useState("");
- const [loading, setLoading] = useState(false);
- const dispatchAction: any = useDispatch();
-
- const handleValueChange = (current: string[]) => {
- if (multiple) setSelected(current);
- else setSelected(current ? current[0] : "");
- };
-
- useEffect(() => {
- if (!facilityId) return;
- const params = {
- limit: 14,
- search_text: query,
- };
- setLoading(true);
- dispatchAction(
- listFacilityAssetLocation(params, { facility_external_id: facilityId })
- ).then(({ data }: any) => {
- setLocations(data.results);
- setLoading(false);
- });
- }, [query, facilityId]);
return props.multiple ? (
handleValueChange(value as unknown as string[])}
- onQuery={(query) => {
- setQuery(query);
- }}
+ name={props.name}
+ disabled={props.disabled}
+ value={props.selected as unknown as string[]}
+ options={data?.results ?? []}
+ onChange={({ value }) => props.setSelected(value)}
+ onQuery={(search_text) => refetch({ query: { search_text } })}
placeholder="Search by location name"
optionLabel={(option) => option.name}
optionValue={(option) => option.id}
- error={errors}
- className={className}
+ error={props.errors}
+ className={props.className}
errorClassName={props.errorClassName}
/>
) : (
handleValueChange([value])}
- onQuery={(query) => {
- setQuery(query);
- }}
+ name={props.name}
+ disabled={props.disabled}
+ value={props.selected as string}
+ options={data?.results ?? []}
+ onChange={({ value }) => props.setSelected(value)}
+ onQuery={(search_text) => refetch({ query: { search_text } })}
isLoading={loading}
placeholder="Search by location name"
optionLabel={(option) => option.name}
optionValue={(option) => option.id}
- error={errors}
- className={className}
+ error={props.errors}
+ className={props.className}
errorClassName={props.errorClassName}
/>
);
diff --git a/src/Components/Common/SkillSelect.tsx b/src/Components/Common/SkillSelect.tsx
index 941c29790d1..e0d9aafdb13 100644
--- a/src/Components/Common/SkillSelect.tsx
+++ b/src/Components/Common/SkillSelect.tsx
@@ -1,8 +1,8 @@
import { useCallback } from "react";
-import { useDispatch } from "react-redux";
-import { getAllSkills } from "../../Redux/actions";
import AutoCompleteAsync from "../Form/AutoCompleteAsync";
import { SkillModel, SkillObjectModel } from "../Users/models";
+import request from "../../Utils/request/request";
+import routes from "../../Redux/api";
interface SkillSelectProps {
id?: string;
@@ -11,12 +11,10 @@ interface SkillSelectProps {
className?: string;
searchAll?: boolean;
multiple?: boolean;
- showAll?: boolean;
showNOptions?: number;
disabled?: boolean;
selected: SkillObjectModel | SkillObjectModel[] | null;
setSelected: (selected: SkillObjectModel) => void;
- username?: string;
userSkills?: SkillModel[];
}
@@ -28,37 +26,28 @@ export const SkillSelect = (props: SkillSelectProps) => {
selected,
setSelected,
searchAll,
- showAll = true,
showNOptions = 10,
disabled = false,
className = "",
errors = "",
- //username,
userSkills,
} = props;
- const dispatchAction: any = useDispatch();
-
const skillSearch = useCallback(
async (text: string) => {
- const params = {
+ const query = {
limit: 50,
offset: 0,
search_text: text,
all: searchAll,
};
- const res = await dispatchAction(getAllSkills(params));
- const skillsID: string[] = [];
- userSkills?.map((skill: SkillModel) =>
- skillsID.push(skill.skill_object.id)
- );
- const skills = res?.data?.results.filter(
- (skill: any) => !skillsID.includes(skill.id)
+ const { data } = await request(routes.getAllSkills, { query });
+ return data?.results.filter(
+ (skill) => !userSkills?.some((userSkill) => userSkill.id === skill.id)
);
- return skills;
},
- [dispatchAction, searchAll, userSkills, showAll]
+ [searchAll, userSkills]
);
return (
diff --git a/src/Components/Common/StateAutocompleteFormField.tsx b/src/Components/Common/StateAutocompleteFormField.tsx
index 384f46c3a7c..77d52768198 100644
--- a/src/Components/Common/StateAutocompleteFormField.tsx
+++ b/src/Components/Common/StateAutocompleteFormField.tsx
@@ -1,45 +1,23 @@
-import { useCallback, useState } from "react";
import AutocompleteFormField from "../Form/FormFields/Autocomplete";
import { FormFieldBaseProps } from "../Form/FormFields/Utils";
-import { statusType, useAbortableEffect } from "../../Common/utils";
-import { useDispatch } from "react-redux";
-import { getStates } from "../../Redux/actions";
+import { StateModel } from "../Facility/models";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
-export type IState = {
- id: number;
- name: string;
-};
-
-type Props = FormFieldBaseProps & {
+type Props = FormFieldBaseProps & {
placeholder?: string;
};
export default function StateAutocompleteFormField(props: Props) {
- const dispatch = useDispatch();
- const [states, setStates] = useState();
-
- const fetchStates = useCallback(
- async (status: any) => {
- setStates(undefined);
- const res = await dispatch(getStates());
- if (!status.aborted && res && res.data) {
- setStates(res.data.results);
- }
- },
- [dispatch]
- );
-
- useAbortableEffect((status: statusType) => {
- fetchStates(status);
- }, []);
+ const { data, loading } = useQuery(routes.statesList);
return (
option.name}
optionValue={(option) => option.id}
- isLoading={states === undefined}
+ isLoading={loading}
/>
);
}
diff --git a/src/Components/Common/Uptime.tsx b/src/Components/Common/Uptime.tsx
index 6f6966b9053..e3c7dd7a439 100644
--- a/src/Components/Common/Uptime.tsx
+++ b/src/Components/Common/Uptime.tsx
@@ -1,12 +1,10 @@
import { Popover } from "@headlessui/react";
-import { useCallback, useEffect, useRef, useState } from "react";
-import { listAssetAvailability } from "../../Redux/actions";
-import { useDispatch } from "react-redux";
-import * as Notification from "../../Utils/Notifications.js";
+import { useEffect, useRef, useState } from "react";
import { AssetStatus, AssetUptimeRecord } from "../Assets/AssetTypes";
-import { reverse } from "lodash-es";
import { classNames } from "../../Utils/utils";
import dayjs from "../../Utils/dayjs";
+import useQuery from "../../Utils/request/useQuery.js";
+import routes from "../../Redux/api.js";
const STATUS_COLORS = {
Operational: "bg-green-500",
@@ -63,7 +61,7 @@ function UptimeInfo({
<>
Status Updates
- {reverse(incidents)?.map((incident, index) => {
+ {incidents.reverse().map((incident, index) => {
const prevIncident = incidents[index - 1];
let endTimestamp;
let ongoing = false;
@@ -171,16 +169,16 @@ export default function Uptime(props: { assetId: string }) {
const [summary, setSummary] = useState<{
[key: number]: AssetUptimeRecord[];
}>([]);
- const [availabilityData, setAvailabilityData] = useState (
- []
- );
- const [loading, setLoading] = useState(true);
+ const { data, loading } = useQuery(routes.listAssetAvailability, {
+ query: { external_id: props.assetId },
+ onResponse: ({ data }) => setUptimeRecord(data?.results.reverse() ?? []),
+ });
+ const availabilityData = data?.results ?? [];
const graphElem = useRef(null);
const [numDays, setNumDays] = useState(
Math.floor((window.innerWidth - 1024) / 20)
);
const [hoveredDay, setHoveredDay] = useState(-1);
- const dispatch = useDispatch();
const handleResize = () => {
const containerWidth = graphElem.current?.clientWidth ?? window.innerWidth;
@@ -268,25 +266,6 @@ export default function Uptime(props: { assetId: string }) {
return Math.round((upStatus / (days * 3)) * 100);
}
- const fetchData = useCallback(async () => {
- setLoading(true);
- setLoading(false);
-
- const availabilityData = await dispatch(
- listAssetAvailability({
- external_id: props.assetId,
- })
- );
- if (availabilityData?.data) {
- setAvailabilityData(availabilityData.data.results);
- setUptimeRecord(reverse(availabilityData.data.results));
- } else {
- Notification.Error({
- msg: "Error fetching availability history",
- });
- }
- }, [dispatch, props.assetId]);
-
useEffect(() => {
setTimeout(() => {
handleResize();
@@ -297,8 +276,7 @@ export default function Uptime(props: { assetId: string }) {
useEffect(() => {
handleResize();
- fetchData();
- }, [props.assetId, fetchData]);
+ }, []);
const getStatusColor = (day: number) => {
if (summary[day]) {
diff --git a/src/Components/Common/prescription-builder/InvestigationBuilder.tsx b/src/Components/Common/prescription-builder/InvestigationBuilder.tsx
index 67ac412c2e0..cb0bbac1f66 100644
--- a/src/Components/Common/prescription-builder/InvestigationBuilder.tsx
+++ b/src/Components/Common/prescription-builder/InvestigationBuilder.tsx
@@ -1,12 +1,10 @@
import { useEffect, useState } from "react";
-import { useDispatch } from "react-redux";
-import {
- listInvestigationGroups,
- listInvestigations,
-} from "../../../Redux/actions";
import { PrescriptionDropdown } from "./PrescriptionDropdown";
import { PrescriptionMultiDropdown } from "./PrescriptionMultiselect";
import CareIcon from "../../../CAREUI/icons/CareIcon";
+import request from "../../../Utils/request/request";
+import routes from "../../../Redux/api";
+
export type InvestigationType = {
type?: string[];
repetitive?: boolean;
@@ -35,7 +33,6 @@ export default function InvestigationBuilder(
) {
const { investigations, setInvestigations } = props;
const [investigationsList, setInvestigationsList] = useState([]);
- const dispatch: any = useDispatch();
const [activeIdx, setActiveIdx] = useState(null);
const additionalInvestigations = [
["Vitals", ["Temp", "Blood Pressure", "Respiratory Rate", "Pulse Rate"]],
@@ -85,26 +82,20 @@ export default function InvestigationBuilder(
};
const fetchInvestigations = async () => {
- const res = await dispatch(listInvestigations({}));
- if (res && res.data) {
- return res.data.results.map(
- (investigation: any) =>
- investigation.name +
- " -- " +
- investigation.groups
- .map((group: any) => " ( " + group.name + " ) ")
- .join(", ")
- );
- }
- return [];
+ const { data } = await request(routes.listInvestigations);
+ return (
+ data?.results.map(
+ (investigation) =>
+ `${investigation.name} -- ${investigation.groups
+ .map((group) => ` ( ${group.name} ) `)
+ .join(", ")}`
+ ) ?? []
+ );
};
const fetchInvestigationGroups = async () => {
- const res = await dispatch(listInvestigationGroups({}));
- if (res && res.data) {
- return res.data.results.map((group: any) => group.name + " (GROUP)");
- }
- return [];
+ const { data } = await request(routes.listInvestigationGroups);
+ return data?.results.map((group) => `${group.name} (GROUP)`) ?? [];
};
return (
diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx
index a1e06a8c7ab..35f71a746fd 100644
--- a/src/Components/Facility/AssetCreate.tsx
+++ b/src/Components/Facility/AssetCreate.tsx
@@ -563,7 +563,7 @@ const AssetCreate = (props: AssetProps) => {
selected={location}
showAll={false}
multiple={false}
- facilityId={facilityId as unknown as number}
+ facilityId={facilityId}
errors={state.errors.location}
/>
diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx
index 77962cc7703..63e18645e13 100644
--- a/src/Components/Facility/ConsultationForm.tsx
+++ b/src/Components/Facility/ConsultationForm.tsx
@@ -229,11 +229,16 @@ type ConsultationFormSection =
| "Treatment Plan"
| "Bed Status";
-export const ConsultationForm = (props: any) => {
+type Props = {
+ facilityId: string;
+ patientId: string;
+ id?: string;
+};
+
+export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
const { goBack } = useAppHistory();
const { kasp_enabled, kasp_string } = useConfig();
const dispatchAction: any = useDispatch();
- const { facilityId, patientId, id } = props;
const [state, dispatch] = useAutoSaveReducer(
consultationFormReducer,
initialState
@@ -344,7 +349,7 @@ export const ConsultationForm = (props: any) => {
const fetchData = useCallback(
async (status: statusType) => {
if (!patientId) setIsLoading(true);
- const res = await dispatchAction(getConsultation(id));
+ const res = await dispatchAction(getConsultation(id!));
handleFormFieldChange({
name: "InvestigationAdvice",
value: !Array.isArray(res.data.investigation)
@@ -743,7 +748,7 @@ export const ConsultationForm = (props: any) => {
};
const res = await dispatchAction(
- id ? updateConsultation(id, data) : createConsultation(data)
+ id ? updateConsultation(id!, data) : createConsultation(data)
);
setIsLoading(false);
if (res?.data && res.status !== 400) {
diff --git a/src/Components/Facility/Investigations/Reports/index.tsx b/src/Components/Facility/Investigations/Reports/index.tsx
index 9b3c29d40f8..b868e8e8af0 100644
--- a/src/Components/Facility/Investigations/Reports/index.tsx
+++ b/src/Components/Facility/Investigations/Reports/index.tsx
@@ -1,6 +1,6 @@
import * as Notification from "../../../../Utils/Notifications";
import _ from "lodash-es";
-import { Group, InvestigationType } from "..";
+import { InvestigationGroup, InvestigationType } from "..";
import {
getPatient,
getPatientInvestigation,
@@ -22,7 +22,7 @@ import { useRef } from "react";
const RESULT_PER_PAGE = 14;
interface InitialState {
- investigationGroups: Group[];
+ investigationGroups: InvestigationGroup[];
selectedGroup: string[];
investigations: InvestigationType[];
selectedInvestigations: any[];
diff --git a/src/Components/Facility/Investigations/index.tsx b/src/Components/Facility/Investigations/index.tsx
index 91836b6bbec..95147ec4c89 100644
--- a/src/Components/Facility/Investigations/index.tsx
+++ b/src/Components/Facility/Investigations/index.tsx
@@ -22,7 +22,7 @@ const initialState = {
form: {},
};
-export interface Group {
+export interface InvestigationGroup {
external_id: string;
name: string;
}
@@ -38,9 +38,9 @@ export interface InvestigationType {
unit?: string;
choices?: string;
ideal_value?: string;
- groups: Group[];
+ groups: InvestigationGroup[];
}
-type SearchItem = Group | InvestigationType;
+type SearchItem = InvestigationGroup | InvestigationType;
function isInvestigation(e: SearchItem): e is InvestigationType {
return (e as InvestigationType).groups !== undefined;
}
@@ -67,7 +67,7 @@ const listOfInvestigations = (
);
};
-const findGroup = (group_id: string, groups: Group[]) => {
+const findGroup = (group_id: string, groups: InvestigationGroup[]) => {
return groups.find((g) => g.external_id === group_id);
};
@@ -106,7 +106,9 @@ const Investigation = (props: {
const [selectedGroup, setSelectedGroup] = useState([]);
const [state, setState] = useReducer(testFormReducer, initialState);
const [investigations, setInvestigations] = useState([]);
- const [investigationGroups, setInvestigationGroups] = useState([]);
+ const [investigationGroups, setInvestigationGroups] = useState<
+ InvestigationGroup[]
+ >([]);
const [selectedInvestigations, setSelectedInvestigations] = useState<
InvestigationType[]
>([]);
diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx
index c3ac910970c..f26f458d4ca 100644
--- a/src/Components/Facility/models.tsx
+++ b/src/Components/Facility/models.tsx
@@ -203,11 +203,11 @@ export interface InventoryItemsModel {
}
export interface LocationModel {
- id?: string;
- name?: string;
+ id: string;
+ name: string;
description?: string;
middleware_address?: string;
- location_type?: AssetLocationType;
+ location_type: AssetLocationType;
facility?: {
name: string;
};
@@ -218,8 +218,8 @@ export interface LocationModel {
export interface BedModel {
id?: string;
bed_type?: string;
- description?: string;
name?: string;
+ description?: string;
facility?: string;
location_object?: {
name: string;
@@ -227,6 +227,8 @@ export interface BedModel {
};
location?: string;
is_occupied?: boolean;
+ created_date?: string;
+ modified_date?: string;
}
export interface CurrentBed {
diff --git a/src/Components/Users/SkillsSlideOver.tsx b/src/Components/Users/SkillsSlideOver.tsx
index 1819333451d..63353479f0d 100644
--- a/src/Components/Users/SkillsSlideOver.tsx
+++ b/src/Components/Users/SkillsSlideOver.tsx
@@ -115,12 +115,10 @@ export default ({ show, setShow, username }: IProps) => {
multiple={false}
name="skill"
disabled={!authorizeForAddSkill}
- showAll={true}
showNOptions={Infinity}
selected={selectedSkill}
setSelected={setSelectedSkill}
errors=""
- username={username}
userSkills={skills?.results || []}
/>
{
return fireRequest("getPermittedFacilities", [], params);
};
-export const getAllFacilities = (params: object) => {
- return fireRequest("getAllFacilities", [], params);
-};
-
-export const getAllSkills = (params: object) => {
- return fireRequest("getAllSkills", [], params);
-};
-
export const getAnyFacility = (id: number | string, key?: string) => {
return fireRequest("getAnyFacility", [], {}, { id: id }, key);
};
@@ -296,10 +288,10 @@ export const createConsultation = (params: object) => {
export const getConsultationList = (params: object) => {
return fireRequest("getConsultationList", [], params);
};
-export const getConsultation = (id: number) => {
+export const getConsultation = (id: string) => {
return fireRequest("getConsultation", [], {}, { id: id });
};
-export const updateConsultation = (id: number, params: object) => {
+export const updateConsultation = (id: string, params: object) => {
return fireRequest("updateConsultation", [], params, { id: id });
};
//Inventory
@@ -506,9 +498,6 @@ export const updateAsset = (id: string, params: object) =>
export const operateAsset = (id: string, params: object) =>
fireRequest("operateAsset", [], params, { external_id: id });
-export const listAssetAvailability = (params: object) =>
- fireRequest("listAssetAvailability", [], params);
-
export const listPMJYPackages = (query?: string) =>
fireRequest("listPMJYPackages", [], { query });
diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx
index 8f8fc76484d..db049b1ed71 100644
--- a/src/Redux/api.tsx
+++ b/src/Redux/api.tsx
@@ -27,6 +27,7 @@ import {
AssetServiceUpdate,
AssetTransaction,
AssetUpdate,
+ AssetUptimeRecord,
PatientAssetBed,
} from "../Components/Assets/AssetTypes";
import {
@@ -77,6 +78,10 @@ import {
import { IComment, IResource } from "../Components/Resource/models";
import { IShift } from "../Components/Shifting/models";
import { HCXPolicyModel } from "../Components/HCX/models";
+import {
+ InvestigationGroup,
+ InvestigationType,
+} from "../Components/Facility/Investigations";
/**
* A fake function that returns an empty object casted to type T
@@ -286,6 +291,7 @@ const routes = {
getAllFacilities: {
path: "/api/v1/getallfacilities",
+ TRes: Type>(),
},
createFacility: {
@@ -933,10 +939,12 @@ const routes = {
listInvestigations: {
path: "/api/v1/investigation/",
method: "GET",
+ TRes: Type>(),
},
listInvestigationGroups: {
- path: "/api/v1/investigation/group",
+ path: "/api/v1/investigation/group/",
method: "GET",
+ TRes: Type>(),
},
createInvestigation: {
path: "/api/v1/consultation/{consultation_external_id}/investigation/",
@@ -1232,10 +1240,12 @@ const routes = {
listAssetAvailability: {
path: "/api/v1/asset_availability/",
method: "GET",
+ TRes: Type>(),
},
getAssetAvailability: {
path: "/api/v1/asset_availability/{id}",
method: "GET",
+ TRes: Type(),
},
// Prescription endpoints
From f4f9b1e79edb5a8ed8b9eecddc92176a97f9fdb3 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Wed, 10 Jan 2024 19:25:50 +0530
Subject: [PATCH 24/36] Migrate to useQuery for Bed Management (#6975)
---
src/Components/Facility/AddBedForm.tsx | 187 +++++++++++--------------
src/Locale/en/Bed.json | 10 +-
src/Locale/en/Common.json | 3 +-
src/Locale/en/Facility.json | 3 +-
src/Redux/actions.tsx | 29 ----
src/Redux/api.tsx | 5 +
6 files changed, 100 insertions(+), 137 deletions(-)
diff --git a/src/Components/Facility/AddBedForm.tsx b/src/Components/Facility/AddBedForm.tsx
index 931a95da6a2..379a9a532b8 100644
--- a/src/Components/Facility/AddBedForm.tsx
+++ b/src/Components/Facility/AddBedForm.tsx
@@ -1,44 +1,41 @@
import Card from "../../CAREUI/display/Card";
-import { useState, useEffect, lazy, SyntheticEvent } from "react";
-import { useDispatch } from "react-redux";
-import {
- createFacilityBed,
- getAnyFacility,
- getFacilityAssetLocation,
- getFacilityBed,
- updateFacilityBed,
-} from "../../Redux/actions";
+import { useState, lazy, SyntheticEvent } from "react";
import * as Notification from "../../Utils/Notifications.js";
import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
import { SelectFormField } from "../Form/FormFields/SelectFormField";
import { LOCATION_BED_TYPES } from "../../Common/constants";
-import { navigate } from "raviger";
import { Cancel, Submit } from "../Common/components/ButtonV2";
import TextFormField from "../Form/FormFields/TextFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import Page from "../Common/components/Page";
+import useQuery from "../../Utils/request/useQuery";
+import routes from "../../Redux/api";
+import useAppHistory from "../../Common/hooks/useAppHistory";
+import request from "../../Utils/request/request";
+import { useTranslation } from "react-i18next";
const Loading = lazy(() => import("../Common/Loading"));
-interface BedFormProps {
+interface Props {
facilityId: string;
locationId: string;
bedId?: string;
}
-export const AddBedForm = (props: BedFormProps) => {
- const { facilityId, locationId, bedId } = props;
- const dispatchAction: any = useDispatch();
- const [isLoading, setIsLoading] = useState(false);
- const [name, setName] = useState("");
- const [description, setDescription] = useState("");
- const [bedType, setBedType] = useState("");
- const [facilityName, setFacilityName] = useState("");
- const [locationName, setLocationName] = useState("");
- const [bedName, setBedName] = useState("");
+export const AddBedForm = ({ facilityId, locationId, bedId }: Props) => {
+ const { t } = useTranslation();
+ const { goBack } = useAppHistory();
+ const [state, setState] = useState({
+ name: "",
+ description: "",
+ bed_type: "",
+ facility: facilityId,
+ location: locationId,
+ });
+
const [multipleBeds, setMultipleBeds] = useState(false);
- const [numberOfBeds, setNumberOfBeds] = useState(1); //default = 1
+ const [numberOfBeds, setNumberOfBeds] = useState(1);
const [errors, setErrors] = useState({
name: "",
description: "",
@@ -46,43 +43,32 @@ export const AddBedForm = (props: BedFormProps) => {
numberOfBeds: "",
});
- const headerText = !bedId ? "Add Bed" : "Update Bed";
- const buttonText = !bedId ? "Add Bed(s)" : "Update Bed";
-
- useEffect(() => {
- async function fetchFacilityLocationAndBed() {
- setIsLoading(true);
- if (facilityId) {
- const res = await dispatchAction(getAnyFacility(facilityId));
- setFacilityName(res?.data?.name || "");
- }
- if (facilityId && locationId) {
- const res = await dispatchAction(
- getFacilityAssetLocation(facilityId, locationId)
- );
- setLocationName(res?.data?.name || "");
- }
- if (facilityId && locationId && bedId) {
- const res = await dispatchAction(
- getFacilityBed(facilityId, locationId, bedId)
- );
- setName(res?.data?.name || "");
- setBedName(res?.data?.name || "");
- setDescription(res?.data?.description || "");
- setBedType(res?.data?.bed_type || "");
- setNumberOfBeds(res?.data?.number_of_beds || "");
- }
- setIsLoading(false);
- }
+ const { data: location } = useQuery(routes.getFacilityAssetLocation, {
+ pathParams: {
+ facility_external_id: facilityId,
+ external_id: locationId,
+ },
+ });
- fetchFacilityLocationAndBed();
- }, [dispatchAction, facilityId, locationId]);
+ const { data, loading } = useQuery(routes.getFacilityBed, {
+ pathParams: { external_id: bedId ?? "" },
+ prefetch: !!bedId,
+ onResponse: ({ data }) => {
+ setState({
+ name: data?.name ?? "",
+ description: data?.description ?? "",
+ bed_type: data?.bed_type ?? "",
+ facility: facilityId,
+ location: locationId,
+ });
+ },
+ });
const validateInputs = (data: {
name: string;
description: string;
bed_type: string;
- number_of_beds: number;
+ number_of_beds?: number;
}) => {
let isValid = true;
if (!data.name) {
@@ -96,7 +82,7 @@ export const AddBedForm = (props: BedFormProps) => {
if (multipleBeds === false) {
setNumberOfBeds(1);
}
- if (data.number_of_beds < 1) {
+ if (data.number_of_beds !== undefined && data.number_of_beds < 1) {
isValid = false;
setErrors((prev) => ({
...prev,
@@ -115,63 +101,56 @@ export const AddBedForm = (props: BedFormProps) => {
return isValid;
};
- const handleCancel = () =>
- navigate(`/facility/${facilityId}/location/${locationId}/beds`, {
- replace: true,
- });
-
const handleSubmit = async (e: SyntheticEvent) => {
e.preventDefault();
- const data = {
- name,
- description,
- bed_type: bedType,
- number_of_beds: bedId ? 1 : numberOfBeds,
- };
+ const data = bedId
+ ? { ...state }
+ : { ...state, number_of_beds: numberOfBeds };
if (!validateInputs(data)) return;
- setIsLoading(true);
-
- const res = await dispatchAction(
- bedId
- ? updateFacilityBed(data, facilityId, bedId, locationId)
- : createFacilityBed(data, facilityId, locationId)
- );
- setIsLoading(false);
- if (res && (res.status === 201 || res.status === 200)) {
- const notificationMessage = bedId
- ? "Bed updated successfully"
- : "Bed(s) created successfully";
+ const onSuccess = (msg: string) => {
+ Notification.Success({ msg });
+ goBack();
+ };
- navigate(`/facility/${facilityId}/location/${locationId}/beds`, {
- replace: true,
+ if (bedId) {
+ // Update
+ const { res } = await request(routes.updateFacilityBed, {
+ pathParams: { external_id: bedId },
+ body: { ...data, facility: facilityId, location: locationId },
});
- Notification.Success({
- msg: notificationMessage,
+ res?.ok && onSuccess("Bed updated successfully");
+ } else {
+ // Create
+ const { res } = await request(routes.createFacilityBed, {
+ body: { ...data, facility: facilityId, location: locationId },
});
+ res?.ok && onSuccess("Bed(s) created successfully");
}
};
- if (isLoading) {
+ if (loading) {
return ;
}
+ const action = t(!bedId ? "add_beds" : "update_bed");
+
return (
{
setName(e.value)}
+ value={state.name}
+ onChange={(e) => setState((p) => ({ ...p, [e.name]: e.value }))}
error={errors.name}
/>
setDescription(e.value)}
+ value={state.description}
+ onChange={(e) => setState((p) => ({ ...p, [e.name]: e.value }))}
error={errors.description}
/>
@@ -203,13 +182,13 @@ export const AddBedForm = (props: BedFormProps) => {
id="bed-type"
className="w-full"
name="bed_type"
- label="Bed Type"
+ label={t("bed_type")}
required
options={LOCATION_BED_TYPES}
optionLabel={(option) => option.name}
optionValue={(option) => option.id}
- value={bedType}
- onChange={(e) => setBedType(e.value)}
+ value={state.bed_type}
+ onChange={(e) => setState((p) => ({ ...p, [e.name]: e.value }))}
error={errors.bedType}
/>
@@ -217,10 +196,12 @@ export const AddBedForm = (props: BedFormProps) => {
<>
{
- if (multipleBeds) setNumberOfBeds(1);
- setMultipleBeds(!multipleBeds);
+ label={t("make_multiple_beds_label")}
+ onChange={({ value }) => {
+ setMultipleBeds(value);
+ if (value) {
+ setNumberOfBeds(1);
+ }
}}
name={"multipleBeds"}
/>
@@ -228,7 +209,7 @@ export const AddBedForm = (props: BedFormProps) => {
id="numberofbed"
name="number_of_beds"
disabled={!multipleBeds}
- label="Number of beds"
+ label={t("number_of_beds")}
type="number"
value={numberOfBeds.toString()}
min={1}
@@ -236,17 +217,17 @@ export const AddBedForm = (props: BedFormProps) => {
onChange={(e) => setNumberOfBeds(Number(e.value))}
error={
numberOfBeds > 100
- ? "Number of beds cannot be greater than 100"
+ ? t("number_of_beds_out_of_range_error")
: undefined
}
/>
>
)}
-
+ goBack()} />
100}
/>
diff --git a/src/Locale/en/Bed.json b/src/Locale/en/Bed.json
index 7b839c2a384..b410293959b 100644
--- a/src/Locale/en/Bed.json
+++ b/src/Locale/en/Bed.json
@@ -3,5 +3,11 @@
"BED_WITH_OXYGEN_SUPPORT": "Bed with Oxygen Support",
"REGULAR": "Regular",
"ICU": "ICU",
- "ISOLATION": "Isolation"
-}
+ "ISOLATION": "Isolation",
+ "add_beds": "Add Bed(s)",
+ "update_bed": "Update Bed",
+ "bed_type": "Bed Type",
+ "make_multiple_beds_label": "Do you want to make multiple beds?",
+ "number_of_beds": "Number of beds",
+ "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100"
+}
\ No newline at end of file
diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json
index 31528afa390..99adb77451c 100644
--- a/src/Locale/en/Common.json
+++ b/src/Locale/en/Common.json
@@ -84,6 +84,7 @@
"age": "Age",
"is": "Is",
"reason": "Reason",
+ "description": "Description",
"name": "Name",
"address": "Address",
"phone": "Phone",
@@ -160,4 +161,4 @@
"select_date": "Select date",
"DD/MM/YYYY": "DD/MM/YYYY",
"clear_all_filters": "Clear All Filters"
-}
+}
\ No newline at end of file
diff --git a/src/Locale/en/Facility.json b/src/Locale/en/Facility.json
index 5e69f8108af..aefc9c3b4a8 100644
--- a/src/Locale/en/Facility.json
+++ b/src/Locale/en/Facility.json
@@ -33,7 +33,6 @@
"asset_location": "Asset Location",
"asset_type": "Asset Type",
"asset_class": "Asset Class",
- "description": "Description",
"details_about_the_equipment": "Details about the equipment",
"working_status": "Working Status",
"why_the_asset_is_not_working": "Why the asset is not working?",
@@ -53,4 +52,4 @@
"notes": "Notes",
"create_asset": "Create Asset",
"create_add_more": "Create & Add More"
-}
+}
\ No newline at end of file
diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx
index e517d7df3ce..d6de3c3b960 100644
--- a/src/Redux/actions.tsx
+++ b/src/Redux/actions.tsx
@@ -112,35 +112,6 @@ export const createFacilityBed = (
{}
);
-export const getFacilityBed = (
- facility_external_id: string,
- location_id: string,
- external_id: string
-) =>
- fireRequest(
- "getFacilityBed",
- [],
- { facility: facility_external_id, location: location_id },
- { external_id }
- );
-export const updateFacilityBed = (
- params: object,
- facility_external_id: string,
- external_id: string,
- location_id: string
-) =>
- fireRequest(
- "updateFacilityBed",
- [],
- { ...params, facility: facility_external_id, location: location_id },
- {
- external_id,
- }
- );
-export const deleteFacilityBed = (external_id: string) => {
- return fireRequest("deleteFacilityBed", [], {}, { external_id });
-};
-
// Download Actions
export const downloadFacility = () => {
return fireRequest("downloadFacility");
diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx
index db049b1ed71..ee8c8f86ccf 100644
--- a/src/Redux/api.tsx
+++ b/src/Redux/api.tsx
@@ -414,14 +414,19 @@ const routes = {
createFacilityBed: {
path: "/api/v1/bed/",
method: "POST",
+ TBody: Type(),
+ TRes: Type(),
},
getFacilityBed: {
path: "/api/v1/bed/{external_id}/",
method: "GET",
+ TRes: Type(),
},
updateFacilityBed: {
path: "/api/v1/bed/{external_id}/",
method: "PUT",
+ TBody: Type(),
+ TRes: Type(),
},
deleteFacilityBed: {
path: "/api/v1/bed/{external_id}/",
From 5cf75e8e71268e854ffc96627086bb2007657453 Mon Sep 17 00:00:00 2001
From: Ashesh <3626859+Ashesh3@users.noreply.github.com>
Date: Wed, 10 Jan 2024 19:35:41 +0530
Subject: [PATCH 25/36] Add video connect link and Cypress Tests for User
Profile (#6924)
* Add video connect link to user profile
* Add useAuthUser hook and triggerGoal function
* Refactor DoctorVideoSlideover to filter doctors with alt_phone_number or video_connect_link
* Add user profile tests
* Update input fields with click before clearing and typing
---
cypress/e2e/users_spec/user_profile.cy.ts | 85 +++++++++++++++++++
cypress/pageobject/Login/LoginPage.ts | 4 +
cypress/pageobject/Users/ManageUserPage.ts | 12 +++
cypress/pageobject/Users/UserProfilePage.ts | 84 ++++++++++++++++++
.../Facility/DoctorVideoSlideover.tsx | 44 +++++++++-
src/Components/Users/UserProfile.tsx | 43 +++++++++-
src/Components/Users/models.tsx | 2 +
src/Utils/utils.ts | 9 ++
8 files changed, 280 insertions(+), 3 deletions(-)
create mode 100644 cypress/e2e/users_spec/user_profile.cy.ts
create mode 100644 cypress/pageobject/Users/UserProfilePage.ts
diff --git a/cypress/e2e/users_spec/user_profile.cy.ts b/cypress/e2e/users_spec/user_profile.cy.ts
new file mode 100644
index 00000000000..b6fe64b2455
--- /dev/null
+++ b/cypress/e2e/users_spec/user_profile.cy.ts
@@ -0,0 +1,85 @@
+import { cy, describe, before, beforeEach, it, afterEach } from "local-cypress";
+import LoginPage from "../../pageobject/Login/LoginPage";
+import UserProfilePage from "../../pageobject/Users/UserProfilePage";
+import ManageUserPage from "../../pageobject/Users/ManageUserPage";
+
+describe("Manage User Profile", () => {
+ const loginPage = new LoginPage();
+ const userProfilePage = new UserProfilePage();
+ const manageUserPage = new ManageUserPage();
+
+ const age = "30";
+ const gender = "Male";
+ const email = "test@example.com";
+ const phone = "+918899887788";
+ const workinghours = "8";
+ const doctorQualification = "MBBS";
+ const doctorYoE = "10";
+ const medicalCouncilRegistration = "1234567890";
+
+ const facilitySearch = "Dummy Facility 1";
+
+ before(() => {
+ loginPage.loginAsDevDoctor();
+ cy.saveLocalStorage();
+ });
+
+ beforeEach(() => {
+ cy.restoreLocalStorage();
+ console.log(localStorage);
+ cy.clearLocalStorage(/filters--.+/);
+ console.log(localStorage);
+ cy.awaitUrl("/user/profile");
+ });
+
+ it("Set Age, Gender, Email, Phone and Working Hours for a user and verify its reflection in user profile", () => {
+ userProfilePage.clickEditProfileButton();
+
+ userProfilePage.typeAge(age);
+ userProfilePage.selectGender(gender);
+ userProfilePage.typeEmail(email);
+ userProfilePage.typePhone(phone);
+ userProfilePage.typeWhatsApp(phone);
+ userProfilePage.typeWorkingHours(workinghours);
+ userProfilePage.typeDoctorQualification(doctorQualification);
+ userProfilePage.typeDoctorYoE(doctorYoE);
+ userProfilePage.typeMedicalCouncilRegistration(medicalCouncilRegistration);
+
+ userProfilePage.clickUpdateButton();
+
+ cy.verifyNotification("Details updated successfully");
+
+ userProfilePage.assertAge(age);
+ userProfilePage.assertGender(gender);
+ userProfilePage.assertEmail(email);
+ userProfilePage.assertPhone(phone);
+ userProfilePage.assertWhatsApp(phone);
+ userProfilePage.assertWorkingHours(workinghours);
+ });
+
+ it("Adding video connect link for a user and verify its reflection in user profile and doctor connect", () => {
+ // verify the user doesn't have any video connect link
+ userProfilePage.assertVideoConnectLink("-");
+ // Link a new video connect link and ensure it is under video connect link
+ userProfilePage.clickEditProfileButton();
+ userProfilePage.typeVideoConnectLink("https://www.example.com");
+ userProfilePage.clickUpdateButton();
+ userProfilePage.assertVideoConnectLink("https://www.example.com");
+ // Edit the video connect link and ensure it is updated
+ userProfilePage.clickEditProfileButton();
+ userProfilePage.typeVideoConnectLink("https://www.test.com");
+ userProfilePage.clickUpdateButton();
+ userProfilePage.assertVideoConnectLink("https://www.test.com");
+ // Go to particular facility doctor connect and verify the video connect link is present
+ manageUserPage.navigateToFacility();
+ manageUserPage.typeFacilitySearch(facilitySearch);
+ manageUserPage.assertFacilityInCard(facilitySearch);
+ manageUserPage.clickFacilityPatients();
+ manageUserPage.clickDoctorConnectButton();
+ manageUserPage.assertVideoConnectLink("Dev Doctor", "https://www.test.com");
+ });
+
+ afterEach(() => {
+ cy.saveLocalStorage();
+ });
+});
diff --git a/cypress/pageobject/Login/LoginPage.ts b/cypress/pageobject/Login/LoginPage.ts
index f691d5f9e15..f4f188f11d6 100644
--- a/cypress/pageobject/Login/LoginPage.ts
+++ b/cypress/pageobject/Login/LoginPage.ts
@@ -6,6 +6,10 @@ class LoginPage {
cy.loginByApi("devdistrictadmin", "Coronasafe@123");
}
+ loginAsDevDoctor(): void {
+ cy.loginByApi("devdoctor", "Coronasafe@123");
+ }
+
login(username: string, password: string): void {
cy.loginByApi(username, password);
}
diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts
index 2d1ebbc14f0..622229745f7 100644
--- a/cypress/pageobject/Users/ManageUserPage.ts
+++ b/cypress/pageobject/Users/ManageUserPage.ts
@@ -149,6 +149,18 @@ export class ManageUserPage {
cy.get("#doctor-connect-home-doctor").should("contain.text", realName);
cy.get("#doctor-connect-remote-doctor").should("contain.text", realName);
}
+
+ assertVideoConnectLink(docName: string, link: string) {
+ cy.get("ul#options")
+ .find("li")
+ .contains(docName)
+ .within(() => {
+ cy.get("a").should(($a) => {
+ const hrefs = $a.map((i, el) => Cypress.$(el).attr("href")).get();
+ expect(hrefs).to.include(link);
+ });
+ });
+ }
}
export default ManageUserPage;
diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts
new file mode 100644
index 00000000000..3f71a29181b
--- /dev/null
+++ b/cypress/pageobject/Users/UserProfilePage.ts
@@ -0,0 +1,84 @@
+export default class UserProfilePage {
+ assertVideoConnectLink(link: string) {
+ cy.get("#videoconnectlink-profile-details").should("contain.text", link);
+ }
+
+ clickEditProfileButton() {
+ cy.get("#edit-cancel-profile-button").click();
+ }
+
+ typeVideoConnectLink(link: string) {
+ cy.get("#video_connect_link").click().clear().type(link);
+ }
+
+ clickUpdateButton() {
+ cy.get("#submit").click();
+ }
+
+ typeAge(age: string) {
+ cy.get("#age").click().clear().type(age);
+ }
+
+ selectGender(gender: string) {
+ cy.get("#gender").click();
+ cy.get("#gender-option-" + gender).click();
+ }
+
+ typeEmail(email: string) {
+ cy.get("#email").click().clear().type(email);
+ }
+
+ typePhone(phone: string) {
+ cy.get("#phoneNumber").click().clear().type(phone);
+ }
+
+ typeWhatsApp(phone: string) {
+ cy.get("#altPhoneNumber").click().clear().type(phone);
+ }
+
+ typeWorkingHours(workinghours: string) {
+ cy.get("#weekly_working_hours").click().clear().type(workinghours);
+ }
+
+ typeDoctorQualification = (doctorQualification: string) => {
+ cy.get("#doctor_qualification").click().clear().type(doctorQualification);
+ };
+
+ typeDoctorYoE = (doctorYoE: string) => {
+ cy.get("#doctor_experience_commenced_on").click().clear().type(doctorYoE);
+ };
+
+ typeMedicalCouncilRegistration = (medicalCouncilRegistration: string) => {
+ cy.get("#doctor_medical_council_registration")
+ .click()
+ .clear()
+ .type(medicalCouncilRegistration);
+ };
+
+ assertAge(age: string) {
+ cy.get("#age-profile-details").should("contain.text", age);
+ }
+
+ assertGender(gender: string) {
+ cy.get("#gender-profile-details").should("contain.text", gender);
+ }
+
+ assertEmail(email: string) {
+ cy.get("#emailid-profile-details").should("contain.text", email);
+ }
+
+ assertPhone(phone: string) {
+ cy.get("#contactno-profile-details").should("contain.text", phone);
+ }
+
+ assertWhatsApp(phone: string) {
+ cy.get("#whatsapp-profile-details").should("contain.text", phone);
+ }
+
+ assertWorkingHours(workinghours: string) {
+ cy.get("#averageworkinghour-profile-details").should(
+ "contain.text",
+ workinghours
+ );
+ }
+}
diff --git a/src/Components/Facility/DoctorVideoSlideover.tsx b/src/Components/Facility/DoctorVideoSlideover.tsx
index 5302f7f9d53..f16b7ba1811 100644
--- a/src/Components/Facility/DoctorVideoSlideover.tsx
+++ b/src/Components/Facility/DoctorVideoSlideover.tsx
@@ -6,6 +6,8 @@ import { UserAssignedModel } from "../Users/models";
import { SkillObjectModel } from "../Users/models";
import CareIcon from "../../CAREUI/icons/CareIcon";
import { relativeTime } from "../../Utils/utils";
+import useAuthUser from "../../Common/hooks/useAuthUser";
+import { triggerGoal } from "../../Integrations/Plausible";
export default function DoctorVideoSlideover(props: {
show: boolean;
@@ -22,10 +24,12 @@ export default function DoctorVideoSlideover(props: {
const res = await dispatchAction(
getFacilityUsers(facilityId, { limit: 50 })
);
- if (res && res.data) {
+ if (res?.data) {
setDoctors(
res.data.results
- .filter((user: any) => user.alt_phone_number)
+ .filter(
+ (user: any) => user.alt_phone_number || user.video_connect_link
+ )
.sort((a: any, b: any) => {
return Number(a.last_login) - Number(b.last_login);
})
@@ -106,6 +110,7 @@ function UserListItem(props: { user: UserAssignedModel }) {
const user = props.user;
const icon =
user.user_type === "Doctor" ? "fa-user-doctor " : " fa-user-nurse";
+ const authUser = useAuthUser();
return (
@@ -152,6 +157,27 @@ function UserListItem(props: { user: UserAssignedModel }) {
{user.first_name} {user.last_name}
+ {user.video_connect_link && (
+ {
+ triggerGoal("Doctor Connect Click", {
+ medium: "Video Call",
+ userId: authUser?.id,
+ targetUserType: user.user_type,
+ });
+ }}
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+
+
+ Connect on a Video Call
+
+
+
+
+ )}
{
+ triggerGoal("Doctor Connect Click", {
+ medium: "WhatsApp",
+ userId: authUser?.id,
+ targetUserType: user.user_type,
+ });
+ }}
target="_blank"
rel="noopener noreferrer"
>
@@ -176,6 +209,13 @@ function UserListItem(props: { user: UserAssignedModel }) {
href={
user.alt_phone_number ? `tel:${user.alt_phone_number}` : "#"
}
+ onClick={() => {
+ triggerGoal("Doctor Connect Click", {
+ medium: "Phone Call",
+ userId: authUser?.id,
+ targetUserType: user.user_type,
+ });
+ }}
>
diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx
index 2dc75d33613..1fcc8fb385e 100644
--- a/src/Components/Users/UserProfile.tsx
+++ b/src/Components/Users/UserProfile.tsx
@@ -5,7 +5,7 @@ import * as Notification from "../../Utils/Notifications.js";
import LanguageSelector from "../../Components/Common/LanguageSelector";
import TextFormField from "../Form/FormFields/TextFormField";
import ButtonV2, { Submit } from "../Common/components/ButtonV2";
-import { classNames, parsePhoneNumber } from "../../Utils/utils";
+import { classNames, isValidUrl, parsePhoneNumber } from "../../Utils/utils";
import CareIcon from "../../CAREUI/icons/CareIcon";
import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField";
import { FieldChangeEvent } from "../Form/FormFields/Utils";
@@ -27,6 +27,7 @@ type EditForm = {
age: string;
gender: GenderType;
email: string;
+ video_connect_link: string | undefined;
phoneNumber: string;
altPhoneNumber: string;
user_type: string | undefined;
@@ -41,6 +42,7 @@ type ErrorForm = {
age: string;
gender: string;
email: string;
+ video_connect_link: string | undefined;
phoneNumber: string;
altPhoneNumber: string;
user_type: string | undefined;
@@ -62,6 +64,7 @@ const initForm: EditForm = {
lastName: "",
age: "",
gender: "Male",
+ video_connect_link: "",
email: "",
phoneNumber: "",
altPhoneNumber: "",
@@ -145,6 +148,7 @@ export default function UserProfile() {
age: result.data.age?.toString() || "",
gender: result.data.gender || "Male",
email: result.data.email,
+ video_connect_link: result.data.video_connect_link,
phoneNumber: result.data.phone_number?.toString() || "",
altPhoneNumber: result.data.alt_phone_number?.toString() || "",
user_type: result.data.user_type,
@@ -262,6 +266,14 @@ export default function UserProfile() {
invalidForm = true;
}
return;
+ case "video_connect_link":
+ if (states.form[field]) {
+ if (isValidUrl(states.form[field]) === false) {
+ errors[field] = "Please enter a valid url";
+ invalidForm = true;
+ }
+ }
+ return;
}
});
dispatch({ type: "set_error", errors });
@@ -294,6 +306,7 @@ export default function UserProfile() {
first_name: states.form.firstName,
last_name: states.form.lastName,
email: states.form.email,
+ video_connect_link: states.form.video_connect_link,
phone_number: parsePhoneNumber(states.form.phoneNumber) ?? "",
alt_phone_number: parsePhoneNumber(states.form.altPhoneNumber) ?? "",
gender: states.form.gender,
@@ -578,6 +591,28 @@ export default function UserProfile() {
{userData?.weekly_working_hours || "-"}
+
)}
@@ -679,6 +714,12 @@ export default function UserProfile() {
min={0}
max={168}
/>
+
diff --git a/src/Components/Users/models.tsx b/src/Components/Users/models.tsx
index 6fa0bfb3e42..a6cd0d7c8ff 100644
--- a/src/Components/Users/models.tsx
+++ b/src/Components/Users/models.tsx
@@ -28,6 +28,7 @@ export type UserModel = UserBareMinimum & {
local_body?: number;
district?: number;
state?: number;
+ video_connect_link: string;
phone_number?: string;
alt_phone_number?: string;
gender?: GenderType;
@@ -62,6 +63,7 @@ export interface UserAssignedModel extends UserBareMinimum {
state?: number;
phone_number?: string;
alt_phone_number?: string;
+ video_connect_link: string;
gender?: number;
age?: number;
is_superuser?: boolean;
diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts
index df6bd46d131..c518bcffe7d 100644
--- a/src/Utils/utils.ts
+++ b/src/Utils/utils.ts
@@ -462,3 +462,12 @@ export const compareBy = (key: keyof T) => {
return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : 0;
};
};
+
+export const isValidUrl = (url?: string) => {
+ try {
+ new URL(url ?? "");
+ return true;
+ } catch {
+ return false;
+ }
+};
From 901f619562694d602dfb6e574fb81bd7d0e2d8f7 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Thu, 11 Jan 2024 19:32:24 +0530
Subject: [PATCH 26/36] Fixes buggy prefetch condition for local body and
district autocompletes (#7006)
* Fixes #7005; buggy prefetch condition for local body and district autocompletes
* fix not found
---
src/Components/Common/DistrictAutocompleteFormField.tsx | 4 ++--
src/Components/Common/LocalBodyAutocompleteFormField.tsx | 4 ++--
src/Utils/request/types.ts | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Components/Common/DistrictAutocompleteFormField.tsx b/src/Components/Common/DistrictAutocompleteFormField.tsx
index dbee6c9f08d..25fef151f2a 100644
--- a/src/Components/Common/DistrictAutocompleteFormField.tsx
+++ b/src/Components/Common/DistrictAutocompleteFormField.tsx
@@ -11,8 +11,8 @@ type Props = FormFieldBaseProps & {
export default function DistrictAutocompleteFormField(props: Props) {
const { data, loading } = useQuery(routes.getDistrictByState, {
- pathParams: { id: props.state?.toString() ?? "" },
- prefetch: props.state !== undefined,
+ pathParams: { id: props.state! },
+ prefetch: !!props.state,
});
return (
diff --git a/src/Components/Common/LocalBodyAutocompleteFormField.tsx b/src/Components/Common/LocalBodyAutocompleteFormField.tsx
index 32023fa559f..8d0821cb43c 100644
--- a/src/Components/Common/LocalBodyAutocompleteFormField.tsx
+++ b/src/Components/Common/LocalBodyAutocompleteFormField.tsx
@@ -11,8 +11,8 @@ type Props = FormFieldBaseProps & {
export default function LocalBodyAutocompleteFormField(props: Props) {
const { data, loading } = useQuery(routes.getLocalbodyByDistrict, {
- pathParams: { id: props.district?.toString() ?? "" },
- prefetch: props.district !== undefined,
+ pathParams: { id: props.district! },
+ prefetch: !!props.district,
});
return (
diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts
index e22d37f3090..aed066ac81c 100644
--- a/src/Utils/request/types.ts
+++ b/src/Utils/request/types.ts
@@ -30,7 +30,7 @@ export interface RequestResult {
export interface RequestOptions {
query?: QueryParams;
body?: TBody;
- pathParams?: Record;
+ pathParams?: Record;
onResponse?: (res: RequestResult) => void;
silent?: boolean;
reattempts?: number;
From f62ea5dc636d29f6892e5540370c9502841523a8 Mon Sep 17 00:00:00 2001
From: Ashesh <3626859+Ashesh3@users.noreply.github.com>
Date: Thu, 11 Jan 2024 19:33:16 +0530
Subject: [PATCH 27/36] Refactor discharge_reason data type to Integer (#6807)
* Refactor discharge_reason data type to Integer
* Update discharge_reason to new_discharge_reason
* Revert API target in vite.config.ts
* Fix discharge reason and discharge notes validation
---
src/Common/constants.tsx | 8 +--
.../ConsultationUpdatesTab.tsx | 21 ++++---
src/Components/Facility/ConsultationForm.tsx | 11 ++--
src/Components/Facility/DischargeModal.tsx | 55 +++++++++++--------
src/Components/Facility/models.tsx | 2 +-
src/Components/Patient/ManagePatients.tsx | 15 ++---
src/Components/Patient/PatientFilter.tsx | 16 +++---
src/Components/Patient/PatientHome.tsx | 9 ++-
src/Components/Patient/PatientInfoCard.tsx | 10 ++--
.../Shifting/ShiftDetailsUpdate.tsx | 5 +-
10 files changed, 92 insertions(+), 60 deletions(-)
diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx
index 647c77ecf57..55a35bef9ab 100644
--- a/src/Common/constants.tsx
+++ b/src/Common/constants.tsx
@@ -294,10 +294,10 @@ export const SYMPTOM_CHOICES = [
];
export const DISCHARGE_REASONS = [
- { id: "REC", text: "Recovered" },
- { id: "EXP", text: "Expired" },
- { id: "REF", text: "Referred" },
- { id: "LAMA", text: "LAMA" },
+ { id: 1, text: "Recovered" },
+ { id: 2, text: "Referred" },
+ { id: 3, text: "Expired" },
+ { id: 4, text: "LAMA" },
];
export const CONSCIOUSNESS_LEVEL = [
diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx
index 5e5b574dd07..4158d7f6609 100644
--- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx
+++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx
@@ -176,7 +176,8 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
{props.consultationData.discharge_date && (
i.text == "Recovered")?.id &&
"lg:col-span-2"
}`}
>
@@ -190,11 +191,13 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
{DISCHARGE_REASONS.find(
(d) =>
- d.id === props.consultationData.discharge_reason
+ d.id === props.consultationData.new_discharge_reason
)?.text ?? "--"}
- {props.consultationData.discharge_reason === "REF" && (
+ {props.consultationData.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Referred")
+ ?.id && (
Referred Facility {" - "}
@@ -204,7 +207,9 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
)}
- {props.consultationData.discharge_reason === "REC" && (
+ {props.consultationData.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Recovered")
+ ?.id && (
Discharge Date {" - "}
@@ -239,7 +244,9 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
)}
- {props.consultationData.discharge_reason === "EXP" && (
+ {props.consultationData.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")
+ ?.id && (
Date of Death {" - "}
@@ -266,8 +273,8 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
)}
- {["REF", "LAMA"].includes(
- props.consultationData.discharge_reason ?? ""
+ {[2, 4].includes(
+ props.consultationData.new_discharge_reason ?? 0
) && (
diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx
index 63e18645e13..a493d354be2 100644
--- a/src/Components/Facility/ConsultationForm.tsx
+++ b/src/Components/Facility/ConsultationForm.tsx
@@ -3,6 +3,7 @@ import * as Notification from "../../Utils/Notifications.js";
import { BedModel, FacilityModel } from "./models";
import {
CONSULTATION_SUGGESTION,
+ DISCHARGE_REASONS,
ConsultationSuggestionValue,
PATIENT_CATEGORIES,
REVIEW_AT_CHOICES,
@@ -121,7 +122,7 @@ type FormDetails = {
weight: string;
height: string;
bed: BedModel | null;
- discharge_reason: string;
+ new_discharge_reason: number | null;
cause_of_death: string;
death_datetime: string;
death_confirmed_doctor: string;
@@ -171,7 +172,7 @@ const initForm: FormDetails = {
weight: "",
height: "",
bed: null,
- discharge_reason: "",
+ new_discharge_reason: null,
cause_of_death: "",
death_datetime: "",
death_confirmed_doctor: "",
@@ -402,7 +403,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
weight: res.data.weight ? res.data.weight : "",
height: res.data.height ? res.data.height : "",
bed: res.data?.current_bed?.bed_object || null,
- discharge_reason: res.data?.discharge_reason || "",
+ new_discharge_reason: res.data?.new_discharge_reason || null,
cause_of_death: res.data?.discharge_notes || "",
death_datetime: res.data?.death_datetime || "",
death_confirmed_doctor: res.data?.death_confirmed_doctor || "",
@@ -653,7 +654,9 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => {
const dischargeResponse = await dispatchAction(
dischargePatient(
{
- discharge_reason: "EXP",
+ new_discharge_reason: DISCHARGE_REASONS.find(
+ (i) => i.text === "Expired"
+ )?.id,
discharge_notes: cause_of_death,
death_datetime: death_datetime,
death_confirmed_doctor: death_confirmed_doctor,
diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx
index a15af4d3883..892be2e916b 100644
--- a/src/Components/Facility/DischargeModal.tsx
+++ b/src/Components/Facility/DischargeModal.tsx
@@ -26,7 +26,7 @@ import { FacilityModel } from "./models";
import dayjs from "../../Utils/dayjs";
interface PreDischargeFormInterface {
- discharge_reason: string;
+ new_discharge_reason: number | null;
discharge_notes: string;
discharge_date?: string;
death_datetime?: string;
@@ -40,7 +40,7 @@ interface IProps {
onClose: () => void;
consultationData: ConsultationModel;
afterSubmit?: () => void;
- discharge_reason?: string;
+ new_discharge_reason?: number | null;
discharge_notes?: string;
discharge_date?: string;
death_datetime?: string;
@@ -51,7 +51,7 @@ const DischargeModal = ({
onClose,
consultationData,
afterSubmit,
- discharge_reason = "",
+ new_discharge_reason = null,
discharge_notes = "",
discharge_date = dayjs().format("YYYY-MM-DDTHH:mm"),
death_datetime = dayjs().format("YYYY-MM-DDTHH:mm"),
@@ -60,7 +60,7 @@ const DischargeModal = ({
const dispatch: any = useDispatch();
const [preDischargeForm, setPreDischargeForm] =
useState ({
- discharge_reason,
+ new_discharge_reason,
discharge_notes,
discharge_date,
death_datetime,
@@ -110,17 +110,18 @@ const DischargeModal = ({
const handlePatientDischarge = async (value: boolean) => {
setIsSendingDischargeApi(true);
- if (!preDischargeForm.discharge_reason) {
+ if (!preDischargeForm.new_discharge_reason) {
setErrors({
...errors,
- discharge_reason: "Please select a reason for discharge",
+ new_discharge_reason: "Please select a reason for discharge",
});
setIsSendingDischargeApi(false);
return;
}
if (
- preDischargeForm.discharge_reason == "EXP" &&
+ preDischargeForm.new_discharge_reason ==
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id &&
!preDischargeForm.discharge_notes.trim()
) {
setErrors({
@@ -196,20 +197,21 @@ const DischargeModal = ({
label="Reason"
name="discharge_reason"
id="discharge_reason"
- value={preDischargeForm.discharge_reason}
- disabled={!!discharge_reason}
+ value={preDischargeForm.new_discharge_reason}
+ disabled={!!new_discharge_reason}
options={DISCHARGE_REASONS}
optionValue={({ id }) => id}
optionLabel={({ text }) => text}
onChange={(e) =>
setPreDischargeForm((prev) => ({
...prev,
- discharge_reason: e.value,
+ new_discharge_reason: e.value,
}))
}
- error={errors?.discharge_reason}
+ error={errors?.new_discharge_reason}
/>
- {preDischargeForm.discharge_reason === "REF" && (
+ {preDischargeForm.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Referred")?.id && (
<>
Referred to
)}
i.text == "Expired")?.id
+ }
label={
{
- EXP: "Cause of death",
- REC: "Discharged Advice",
- }[preDischargeForm.discharge_reason] ?? "Notes"
+ "3": "Cause of death",
+ "1": "Discharged Advice",
+ }[preDischargeForm.new_discharge_reason ?? 0] ?? "Notes"
}
name="discharge_notes"
value={preDischargeForm.discharge_notes}
@@ -246,19 +251,22 @@ const DischargeModal = ({
/>
i.text == "Expired")?.id
? "death_datetime"
: "discharge_date"
}
label={
- preDischargeForm.discharge_reason === "EXP"
+ preDischargeForm.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
? "Date of Death"
: "Date and Time of Discharge"
}
type="datetime-local"
value={
preDischargeForm[
- preDischargeForm.discharge_reason === "EXP"
+ preDischargeForm.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
? "death_datetime"
: "discharge_date"
]
@@ -277,13 +285,15 @@ const DischargeModal = ({
)}
max={dayjs().format("YYYY-MM-DDTHH:mm")}
error={
- preDischargeForm.discharge_reason === "EXP"
+ preDischargeForm.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
? errors?.death_datetime
: errors?.discharge_date
}
/>
- {preDischargeForm.discharge_reason === "REC" && (
+ {preDischargeForm.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Recovered")?.id && (
<>
Discharge Prescription Medications
@@ -295,7 +305,8 @@ const DischargeModal = ({
>
)}
- {preDischargeForm.discharge_reason === "EXP" && (
+ {preDischargeForm.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id && (
{
};
const tabValue =
- qParams.last_consultation_discharge_reason || qParams.is_active === "False"
+ qParams.last_consultation__new_discharge_reason ||
+ qParams.is_active === "False"
? 1
: 0;
@@ -163,7 +164,7 @@ export const PatientManager = () => {
name: qParams.name || undefined,
patient_no: qParams.patient_no || undefined,
is_active:
- !qParams.last_consultation_discharge_reason &&
+ !qParams.last_consultation__new_discharge_reason &&
(qParams.is_active || "True"),
disease_status: qParams.disease_status || undefined,
phone_number: qParams.phone_number
@@ -204,8 +205,8 @@ export const PatientManager = () => {
qParams.last_consultation_discharge_date_after || undefined,
last_consultation_admitted_bed_type_list:
qParams.last_consultation_admitted_bed_type_list || undefined,
- last_consultation_discharge_reason:
- qParams.last_consultation_discharge_reason || undefined,
+ last_consultation__new_discharge_reason:
+ qParams.last_consultation__new_discharge_reason || undefined,
last_consultation_current_bed__location:
qParams.last_consultation_current_bed__location || undefined,
srf_id: qParams.srf_id || undefined,
@@ -352,7 +353,7 @@ export const PatientManager = () => {
qParams.age_max,
qParams.age_min,
qParams.last_consultation_admitted_bed_type_list,
- qParams.last_consultation_discharge_reason,
+ qParams.last_consultation__new_discharge_reason,
qParams.last_consultation_current_bed__location,
qParams.facility,
qParams.facility_type,
@@ -1022,10 +1023,10 @@ export const PatientManager = () => {
},
value(
"Discharge Reason",
- "last_consultation_discharge_reason",
+ "last_consultation__new_discharge_reason",
parseOptionId(
DISCHARGE_REASONS,
- qParams.last_consultation_discharge_reason
+ qParams.last_consultation__new_discharge_reason
) || ""
),
]}
diff --git a/src/Components/Patient/PatientFilter.tsx b/src/Components/Patient/PatientFilter.tsx
index 481d2dcc67b..4488aa755e1 100644
--- a/src/Components/Patient/PatientFilter.tsx
+++ b/src/Components/Patient/PatientFilter.tsx
@@ -81,8 +81,8 @@ export default function PatientFilter(props: any) {
: [],
last_consultation_current_bed__location:
filter.last_consultation_current_bed__location || "",
- last_consultation_discharge_reason:
- filter.last_consultation_discharge_reason || null,
+ last_consultation__new_discharge_reason:
+ filter.last_consultation__new_discharge_reason || null,
srf_id: filter.srf_id || null,
number_of_doses: filter.number_of_doses || null,
covin_id: filter.covin_id || null,
@@ -241,7 +241,7 @@ export default function PatientFilter(props: any) {
last_consultation_discharge_date_before,
last_consultation_discharge_date_after,
last_consultation_admitted_bed_type_list,
- last_consultation_discharge_reason,
+ last_consultation__new_discharge_reason,
last_consultation_current_bed__location,
number_of_doses,
covin_id,
@@ -298,8 +298,8 @@ export default function PatientFilter(props: any) {
age_max: age_max || "",
last_consultation_admitted_bed_type_list:
last_consultation_admitted_bed_type_list || [],
- last_consultation_discharge_reason:
- last_consultation_discharge_reason || "",
+ last_consultation__new_discharge_reason:
+ last_consultation__new_discharge_reason || "",
srf_id: srf_id || "",
number_of_doses: number_of_doses || "",
covin_id: covin_id || "",
@@ -424,16 +424,16 @@ export default function PatientFilter(props: any) {
Discharge Reason
o.id}
optionLabel={(o) => o.text}
onChange={(o) =>
setFilterState({
...filterState,
- last_consultation_discharge_reason: o,
+ last_consultation__new_discharge_reason: o,
})
}
/>
diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx
index 32118e5951e..22cc35a170e 100644
--- a/src/Components/Patient/PatientHome.tsx
+++ b/src/Components/Patient/PatientHome.tsx
@@ -1,7 +1,11 @@
import { navigate } from "raviger";
import { lazy, useCallback, useEffect, useState } from "react";
import { useDispatch } from "react-redux";
-import { GENDER_TYPES, SAMPLE_TEST_STATUS } from "../../Common/constants";
+import {
+ DISCHARGE_REASONS,
+ GENDER_TYPES,
+ SAMPLE_TEST_STATUS,
+} from "../../Common/constants";
import { statusType, useAbortableEffect } from "../../Common/utils";
import {
getConsultationList,
@@ -761,7 +765,8 @@ export const PatientHome = (props: any) => {
- {patientData.last_consultation?.discharge_reason === "EXP" && (
+ {patientData.last_consultation?.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id && (
i.text == "Expired")?.id ? (
{" "}
Expired on {formatDate(consultation?.death_datetime)}
@@ -379,17 +380,18 @@ export default function PatientInfoCard(props: {
Discharge Reason
- {!consultation?.discharge_reason ? (
+ {!consultation?.new_discharge_reason ? (
{consultation.suggestion === "OP"
? "OP file closed"
: "UNKNOWN"}
- ) : consultation?.discharge_reason === "EXP" ? (
+ ) : consultation?.new_discharge_reason ===
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id ? (
EXPIRED
) : (
DISCHARGE_REASONS.find(
- (reason) => reason.id === consultation?.discharge_reason
+ (reason) => reason.id === consultation?.new_discharge_reason
)?.text
)}
diff --git a/src/Components/Shifting/ShiftDetailsUpdate.tsx b/src/Components/Shifting/ShiftDetailsUpdate.tsx
index 36d2d120585..a726150c9e1 100644
--- a/src/Components/Shifting/ShiftDetailsUpdate.tsx
+++ b/src/Components/Shifting/ShiftDetailsUpdate.tsx
@@ -2,6 +2,7 @@ import * as Notification from "../../Utils/Notifications.js";
import {
BREATHLESSNESS_LEVEL,
+ DISCHARGE_REASONS,
FACILITY_TYPES,
PATIENT_CATEGORIES,
SHIFTING_CHOICES_PEACETIME,
@@ -282,7 +283,9 @@ export const ShiftDetailsUpdate = (props: patientShiftProps) => {
show={showDischargeModal}
onClose={() => setShowDischargeModal(false)}
consultationData={consultationData}
- discharge_reason="EXP"
+ new_discharge_reason={
+ DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id
+ }
afterSubmit={() => {
handleSubmit(true);
}}
From e1879773a86644acfacd3f03ed17f9a7ac54eb45 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Thu, 11 Jan 2024 22:03:02 +0530
Subject: [PATCH 28/36] Adds user type: Nurse and Nurse (readonly) (#6464)
* adds nurse user type, fixes #6240
* Nurse/Staff can now create doctors
* Staff user: disallow link in patient list
* fixes #7002; restrict access to external results for Nurse and Staff
* Hide External Results tab and pages from Staff and Nurse
---------
Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com>
Co-authored-by: Aakash Singh
---
README.md | 2 +-
src/Common/constants.tsx | 22 ++--------
src/Components/Common/Sidebar/Sidebar.tsx | 41 +++++++++++--------
src/Components/ExternalResult/ResultList.tsx | 26 ++++++++----
.../Facility/DoctorVideoSlideover.tsx | 4 +-
src/Components/Patient/ManagePatients.tsx | 22 +++++++---
src/Components/Patient/PatientRegister.tsx | 6 +++
src/Components/Users/UserAdd.tsx | 31 +++++++++-----
src/Routers/AppRouter.tsx | 11 ++++-
9 files changed, 103 insertions(+), 62 deletions(-)
diff --git a/README.md b/README.md
index 7f2c3e926bd..c1dd7f37564 100644
--- a/README.md
+++ b/README.md
@@ -56,7 +56,7 @@ Authenticate to staging API with any of the following credentials
- username: staffdev
password: Coronasafe@123
- role: Staff
+ role: Nurse
- username: doctordev
password: Coronasafe@123
diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx
index 55a35bef9ab..3cb972641b5 100644
--- a/src/Common/constants.tsx
+++ b/src/Common/constants.tsx
@@ -28,6 +28,8 @@ export type UserRole =
| "Volunteer"
| "StaffReadOnly"
| "Staff"
+ | "NurseReadOnly"
+ | "Nurse"
| "Doctor"
| "WardAdmin"
| "LocalBodyAdmin"
@@ -47,6 +49,8 @@ export const USER_TYPE_OPTIONS: {
{ id: "Volunteer", role: "Volunteer", readOnly: false },
{ id: "StaffReadOnly", role: "Staff", readOnly: true },
{ id: "Staff", role: "Staff", readOnly: false },
+ { id: "NurseReadOnly", role: "Nurse", readOnly: true },
+ { id: "Nurse", role: "Nurse", readOnly: false },
{ id: "Doctor", role: "Doctor", readOnly: false },
{ id: "WardAdmin", role: "Ward Admin", readOnly: false },
{ id: "LocalBodyAdmin", role: "Local Body Admin", readOnly: false },
@@ -418,24 +422,6 @@ export const SAMPLE_FLOW_RULES = {
RECEIVED_AT_LAB: ["COMPLETED"],
};
-export const ROLE_STATUS_MAP = {
- Staff: ["SENT_TO_COLLECTON_CENTRE"],
- DistrictAdmin: [
- "APPROVED",
- "DENIED",
- "SENT_TO_COLLECTON_CENTRE",
- "RECEIVED_AND_FORWARED",
- ],
- StateLabAdmin: [
- "APPROVED",
- "DENIED",
- "SENT_TO_COLLECTON_CENTRE",
- "RECEIVED_AND_FORWARED",
- "RECEIVED_AT_LAB",
- "COMPLETED",
- ],
-};
-
export const DISEASE_STATUS = [
"POSITIVE",
"SUSPECTED",
diff --git a/src/Components/Common/Sidebar/Sidebar.tsx b/src/Components/Common/Sidebar/Sidebar.tsx
index fa809571772..9d378df4267 100644
--- a/src/Components/Common/Sidebar/Sidebar.tsx
+++ b/src/Components/Common/Sidebar/Sidebar.tsx
@@ -8,6 +8,7 @@ import useConfig from "../../../Common/hooks/useConfig";
import SlideOver from "../../../CAREUI/interactive/SlideOver";
import { classNames } from "../../../Utils/utils";
import { Link } from "raviger";
+import useAuthUser from "../../../Common/hooks/useAuthUser";
export const SIDEBAR_SHRINK_PREFERENCE_KEY = "sidebarShrinkPreference";
@@ -27,28 +28,36 @@ type StatelessSidebarProps =
onItemClick: (open: boolean) => void;
};
-const NavItems = [
- { text: "Facilities", to: "/facility", icon: "care-l-hospital" },
- { text: "Patients", to: "/patients", icon: "care-l-user-injured" },
- { text: "Assets", to: "/assets", icon: "care-l-shopping-cart-alt" },
- { text: "Sample Test", to: "/sample", icon: "care-l-medkit" },
- { text: "Shifting", to: "/shifting", icon: "care-l-ambulance" },
- { text: "Resource", to: "/resource", icon: "care-l-heart-medical" },
- {
- text: "External Results",
- to: "/external_results",
- icon: "care-l-clipboard-notes",
- },
- { text: "Users", to: "/users", icon: "care-l-users-alt" },
- { text: "Notice Board", to: "/notice_board", icon: "care-l-meeting-board" },
-];
-
const StatelessSidebar = ({
shrinkable = false,
shrinked = false,
setShrinked,
onItemClick,
}: StatelessSidebarProps) => {
+ const authUser = useAuthUser();
+
+ const NavItems = [
+ { text: "Facilities", to: "/facility", icon: "care-l-hospital" },
+ { text: "Patients", to: "/patients", icon: "care-l-user-injured" },
+ { text: "Assets", to: "/assets", icon: "care-l-shopping-cart-alt" },
+ { text: "Sample Test", to: "/sample", icon: "care-l-medkit" },
+ { text: "Shifting", to: "/shifting", icon: "care-l-ambulance" },
+ { text: "Resource", to: "/resource", icon: "care-l-heart-medical" },
+ ...(!["Nurse", "NurseReadOnly", "Staff", "StaffReadOnly"].includes(
+ authUser.user_type
+ )
+ ? [
+ {
+ text: "External Results",
+ to: "/external_results",
+ icon: "care-l-clipboard-notes",
+ },
+ ]
+ : []),
+ { text: "Users", to: "/users", icon: "care-l-users-alt" },
+ { text: "Notice Board", to: "/notice_board", icon: "care-l-meeting-board" },
+ ];
+
const { main_logo } = useConfig();
const activeLink = useActiveLink();
const Item = shrinked ? ShrinkedSidebarItem : SidebarItem;
diff --git a/src/Components/ExternalResult/ResultList.tsx b/src/Components/ExternalResult/ResultList.tsx
index 8fddc7cadc9..9a04fcf38b6 100644
--- a/src/Components/ExternalResult/ResultList.tsx
+++ b/src/Components/ExternalResult/ResultList.tsx
@@ -16,9 +16,12 @@ import Page from "../Common/components/Page";
import routes from "../../Redux/api";
import useQuery from "../../Utils/request/useQuery";
import { parsePhoneNumber } from "../../Utils/utils";
+import useAuthUser from "../../Common/hooks/useAuthUser";
+import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor";
const Loading = lazy(() => import("../Common/Loading"));
export default function ResultList() {
+ const authUser = useAuthUser();
const {
qParams,
updateQuery,
@@ -165,6 +168,10 @@ export default function ResultList() {
{
@@ -226,13 +233,18 @@ export default function ResultList() {
navigate("/external_results/upload"),
- options: {
- icon: ,
- },
- },
+ ...(authUser.user_type !== "Nurse" &&
+ authUser.user_type !== "Staff"
+ ? [
+ {
+ label: "Import Results",
+ action: () => navigate("/external_results/upload"),
+ options: {
+ icon: ,
+ },
+ },
+ ]
+ : []),
{
label: "Export Results",
action: () =>
diff --git a/src/Components/Facility/DoctorVideoSlideover.tsx b/src/Components/Facility/DoctorVideoSlideover.tsx
index f16b7ba1811..c2cd94e0ded 100644
--- a/src/Components/Facility/DoctorVideoSlideover.tsx
+++ b/src/Components/Facility/DoctorVideoSlideover.tsx
@@ -62,8 +62,8 @@ export default function DoctorVideoSlideover(props: {
home: true,
},
{
- title: "Staff",
- user_type: "Staff",
+ title: "Nurse",
+ user_type: "Nurse",
home: true,
},
{
diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx
index af5c5398cb0..0f9a6656f67 100644
--- a/src/Components/Patient/ManagePatients.tsx
+++ b/src/Components/Patient/ManagePatients.tsx
@@ -537,12 +537,9 @@ export const PatientManager = () => {
? PATIENT_CATEGORIES.find((c) => c.text === category)?.twClass
: "patient-unknown";
- return (
-
{
)}
+
+ );
+
+ if (
+ authUser.user_type === "Staff" ||
+ authUser.user_type === "StaffReadOnly"
+ ) {
+ return children;
+ }
+
+ return (
+
+ {children}
);
});
diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx
index 546d35cc487..ce63d5019de 100644
--- a/src/Components/Patient/PatientRegister.tsx
+++ b/src/Components/Patient/PatientRegister.tsx
@@ -70,6 +70,7 @@ import useConfig from "../../Common/hooks/useConfig";
import { useDispatch } from "react-redux";
import { validatePincode } from "../../Common/validation";
import { FormContextValue } from "../Form/FormContext.js";
+import useAuthUser from "../../Common/hooks/useAuthUser.js";
const Loading = lazy(() => import("../Common/Loading"));
const PageTitle = lazy(() => import("../Common/PageTitle"));
@@ -180,6 +181,7 @@ const patientFormReducer = (state = initialState, action: any) => {
};
export const PatientRegister = (props: PatientRegisterProps) => {
+ const authUser = useAuthUser();
const { goBack } = useAppHistory();
const { gov_data_api_key, enable_hcx, enable_abdm } = useConfig();
const dispatchAction: any = useDispatch();
@@ -1173,6 +1175,10 @@ export const PatientRegister = (props: PatientRegisterProps) => {
{
setShowImport({
show: true,
diff --git a/src/Components/Users/UserAdd.tsx b/src/Components/Users/UserAdd.tsx
index 26ba9e44b4b..2c5319e67f3 100644
--- a/src/Components/Users/UserAdd.tsx
+++ b/src/Components/Users/UserAdd.tsx
@@ -99,6 +99,13 @@ const initForm: UserForm = {
doctor_medical_council_registration: undefined,
};
+const STAFF_OR_NURSE_USER = [
+ "Staff",
+ "StaffReadOnly",
+ "Nurse",
+ "NurseReadOnly",
+];
+
const initError = Object.assign(
{},
...Object.keys(initForm).map((k) => ({ [k]: "" }))
@@ -234,15 +241,19 @@ export const UserAdd = (props: UserProps) => {
: // Exception to allow Staff to Create Doctors
defaultAllowedUserTypes;
+ // TODO: refactor lines 227 through 248 to be more readable. This is messy.
+ if (authUser.user_type === "Nurse" || authUser.user_type === "Staff") {
+ userTypes.push(USER_TYPE_OPTIONS[6]); // Temperorily allows creation of users with elevated permissions due to introduction of new roles.
+ }
+
const headerText = !userId ? "Add User" : "Update User";
const buttonText = !userId ? "Save User" : "Update Details";
- const showLocalbody = !(
- state.form.user_type === "Pharmacist" ||
- state.form.user_type === "Volunteer" ||
- state.form.user_type === "Doctor" ||
- state.form.user_type === "Staff" ||
- state.form.user_type === "StaffReadOnly"
- );
+ const showLocalbody = ![
+ "Pharmacist",
+ "Volunteer",
+ "Doctor",
+ ...STAFF_OR_NURSE_USER,
+ ].includes(state.form.user_type);
const { loading: isDistrictLoading } = useQuery(routes.getDistrictByState, {
prefetch: !!(selectedStateId > 0),
@@ -332,10 +343,8 @@ export const UserAdd = (props: UserProps) => {
case "facilities":
if (
state.form[field].length === 0 &&
- (authUser.user_type === "Staff" ||
- authUser.user_type === "StaffReadOnly") &&
- (state.form["user_type"] === "Staff" ||
- state.form["user_type"] === "StaffReadOnly")
+ STAFF_OR_NURSE_USER.includes(authUser.user_type) &&
+ STAFF_OR_NURSE_USER.includes(state.form.user_type)
) {
errors[field] =
"Please select atleast one of the facilities you are linked to";
diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx
index 5e238627bc8..56d8b31573b 100644
--- a/src/Routers/AppRouter.tsx
+++ b/src/Routers/AppRouter.tsx
@@ -25,13 +25,13 @@ import AssetRoutes from "./routes/AssetRoutes";
import ResourceRoutes from "./routes/ResourceRoutes";
import ExternalResultRoutes from "./routes/ExternalResultRoutes";
import { DetailRoute } from "./types";
+import useAuthUser from "../Common/hooks/useAuthUser";
const Routes = {
"/": () => ,
...AssetRoutes,
...ConsultationRoutes,
- ...ExternalResultRoutes,
...FacilityRoutes,
...PatientRoutes,
...ResourceRoutes,
@@ -49,6 +49,7 @@ const Routes = {
};
export default function AppRouter() {
+ const authUser = useAuthUser();
const { main_logo, enable_hcx } = useConfig();
let routes = Routes;
@@ -57,6 +58,14 @@ export default function AppRouter() {
routes = { ...routes, ...HCXRoutes };
}
+ if (
+ !["Nurse", "NurseReadOnly", "Staff", "StaffReadOnly"].includes(
+ authUser.user_type
+ )
+ ) {
+ routes = { ...routes, ...ExternalResultRoutes };
+ }
+
useRedirect("/user", "/users");
const pages = useRoutes(routes) || ;
const path = usePath();
From dfe99238be335f9dbd86644416fbf358d6e43e05 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Fri, 12 Jan 2024 11:58:56 +0530
Subject: [PATCH 29/36] Fixes incorrect loading state when login form is
invalid (no credentials provided) (#7011)
* Fix incorrect loading state when login form is invalid
* fix bugs
---
src/Components/Auth/Login.tsx | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/src/Components/Auth/Login.tsx b/src/Components/Auth/Login.tsx
index c2acb88cf0c..371a3510859 100644
--- a/src/Components/Auth/Login.tsx
+++ b/src/Components/Auth/Login.tsx
@@ -92,15 +92,14 @@ export const Login = (props: { forgot?: boolean }) => {
const handleSubmit = async (e: any) => {
e.preventDefault();
-
setLoading(true);
invalidateFiltersCache();
-
const validated = validateData();
- if (!validated) return;
-
+ if (!validated) {
+ setLoading(false);
+ return;
+ }
const { res } = await signIn(validated);
-
setCaptcha(res?.status === 429);
setLoading(false);
};
From 066789417ea846ffd5e96a8bc3de7b8be8907d8e Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Fri, 12 Jan 2024 12:10:33 +0530
Subject: [PATCH 30/36] prevent resetting the filters cache on page load
(#7009)
* fixes #7008; fix resetting the filters on page visit
* fix existing filters not being cleared
* fix
---
src/Common/hooks/useFilters.tsx | 14 ++++++++++----
1 file changed, 10 insertions(+), 4 deletions(-)
diff --git a/src/Common/hooks/useFilters.tsx b/src/Common/hooks/useFilters.tsx
index 5781eaca54f..117a14ba418 100644
--- a/src/Common/hooks/useFilters.tsx
+++ b/src/Common/hooks/useFilters.tsx
@@ -1,4 +1,4 @@
-import { useQueryParams } from "raviger";
+import { QueryParam, setQueryParamsOptions, useQueryParams } from "raviger";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import GenericFilterBadge from "../../CAREUI/display/FilterBadge";
@@ -23,7 +23,15 @@ export default function useFilters({ limit = 14 }: { limit?: number }) {
const { kasp_string } = useConfig();
const hasPagination = limit > 0;
const [showFilters, setShowFilters] = useState(false);
- const [qParams, setQueryParams] = useQueryParams();
+ const [qParams, _setQueryParams] = useQueryParams();
+
+ const setQueryParams = (
+ query: QueryParam,
+ options?: setQueryParamsOptions
+ ) => {
+ _setQueryParams(query, options);
+ updateFiltersCache(query);
+ };
const updateQuery = (filter: FilterState) => {
filter = hasPagination ? { page: 1, limit, ...filter } : filter;
@@ -38,8 +46,6 @@ export default function useFilters({ limit = 14 }: { limit?: number }) {
};
const removeFilter = (param: string) => removeFilters([param]);
- useEffect(() => updateFiltersCache(qParams), [qParams]);
-
useEffect(() => {
const cache = getFiltersCache();
const qParamKeys = Object.keys(qParams);
From 2fbdb85847e8d4657a916e2a7666a4bc9043838c Mon Sep 17 00:00:00 2001
From: Ashesh <3626859+Ashesh3@users.noreply.github.com>
Date: Fri, 12 Jan 2024 17:31:12 +0530
Subject: [PATCH 31/36] Update weekly_working_hours to allow null value (#7015)
---
src/Components/Users/UserProfile.tsx | 23 ++++++++++++-----------
src/Components/Users/models.tsx | 2 +-
2 files changed, 13 insertions(+), 12 deletions(-)
diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx
index 1fcc8fb385e..170de558e41 100644
--- a/src/Components/Users/UserProfile.tsx
+++ b/src/Components/Users/UserProfile.tsx
@@ -34,7 +34,7 @@ type EditForm = {
doctor_qualification: string | undefined;
doctor_experience_commenced_on: number | string | undefined;
doctor_medical_council_registration: string | undefined;
- weekly_working_hours: string | undefined;
+ weekly_working_hours: string | null;
};
type ErrorForm = {
firstName: string;
@@ -253,13 +253,11 @@ export default function UserProfile() {
}
return;
case "weekly_working_hours":
- if (!states.form[field]) {
- errors[field] = "This field is required";
- invalidForm = true;
- } else if (
- Number(states.form[field]) < 0 ||
- Number(states.form[field]) > 168 ||
- !/^\d+$/.test(states.form[field] ?? "")
+ if (
+ states.form[field] &&
+ (Number(states.form[field]) < 0 ||
+ Number(states.form[field]) > 168 ||
+ !/^\d+$/.test(states.form[field] ?? ""))
) {
errors[field] =
"Average weekly working hours must be a number between 0 and 168";
@@ -331,7 +329,11 @@ export default function UserProfile() {
states.form.user_type === "Doctor"
? states.form.doctor_medical_council_registration
: undefined,
- weekly_working_hours: states.form.weekly_working_hours,
+ weekly_working_hours:
+ states.form.weekly_working_hours &&
+ states.form.weekly_working_hours !== ""
+ ? states.form.weekly_working_hours
+ : null,
};
const { res } = await request(routes.partialUpdateUser, {
pathParams: { username: authUser.username },
@@ -588,7 +590,7 @@ export default function UserProfile() {
Average weekly working hours
- {userData?.weekly_working_hours || "-"}
+ {userData?.weekly_working_hours ?? "-"}
Date: Fri, 12 Jan 2024 17:31:44 +0530
Subject: [PATCH 32/36] Update AssetFilter component to handle null facility
value (#7014)
---
src/Components/Assets/AssetFilter.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx
index 9aeac85e734..5d2ad9e30ae 100644
--- a/src/Components/Assets/AssetFilter.tsx
+++ b/src/Components/Assets/AssetFilter.tsx
@@ -20,7 +20,7 @@ const getDate = (value: any) =>
function AssetFilter(props: any) {
const { filter, onChange, closeFilter, removeFilters } = props;
- const [facility, setFacility] = useState({ name: "" });
+ const [facility, setFacility] = useState(null);
const [asset_type, setAssetType] = useState(
filter.asset_type ? filter.asset_type : ""
);
@@ -51,7 +51,7 @@ function AssetFilter(props: any) {
setLocationId(
facility?.id === qParams.facility ? qParams.location ?? "" : ""
);
- }, [facility.id, qParams.facility, qParams.location]);
+ }, [facility?.id, qParams.facility, qParams.location]);
const clearFilter = useCallback(() => {
removeFilters([
@@ -81,8 +81,8 @@ function AssetFilter(props: any) {
onChange(data);
};
- const handleFacilitySelect = (selected: FacilityModel) => {
- setFacility(selected ? selected : facility);
+ const handleFacilitySelect = (selected: FacilityModel | null) => {
+ setFacility(selected);
handleLocationSelect("");
};
const handleLocationSelect = (selectedId: string) => {
@@ -107,7 +107,7 @@ function AssetFilter(props: any) {
- handleFacilitySelect(selected as FacilityModel)
+ handleFacilitySelect(selected as FacilityModel | null)
}
selected={facility}
errors=""
From b88233173d751b4ee181d12ca070e1ad13c1e208 Mon Sep 17 00:00:00 2001
From: Rithvik Nishad
Date: Fri, 12 Jan 2024 20:14:14 +0530
Subject: [PATCH 33/36] fixes #7018; nurse undefined in doctor notes (#7020)
---
src/Common/constants.tsx | 4 +++-
src/Components/Facility/models.tsx | 4 ++--
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx
index 3cb972641b5..00ce057784b 100644
--- a/src/Common/constants.tsx
+++ b/src/Common/constants.tsx
@@ -1028,6 +1028,8 @@ export const USER_TYPES_MAP = {
StaffReadOnly: "Staff",
Staff: "Staff",
Doctor: "Doctor",
+ Nurse: "Nurse",
+ NurseReadOnly: "Nurse",
WardAdmin: "Ward Admin",
LocalBodyAdmin: "Local Body Admin",
DistrictLabAdmin: "District Lab Admin",
@@ -1037,7 +1039,7 @@ export const USER_TYPES_MAP = {
StateReadOnlyAdmin: "State Admin",
StateAdmin: "State Admin",
RemoteSpecialist: "Remote Specialist",
-};
+} as const;
export const AREACODES: Record = {
CA: [
diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx
index f14a3a102ee..99d91c6cbae 100644
--- a/src/Components/Facility/models.tsx
+++ b/src/Components/Facility/models.tsx
@@ -5,7 +5,7 @@ import { AssetData, AssetLocationType } from "../Assets/AssetTypes";
import { UserBareMinimum } from "../Users/models";
import { RouteToFacility } from "../Common/RouteToFacilitySelect";
import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types";
-import { ConsultationSuggestionValue } from "../../Common/constants";
+import { ConsultationSuggestionValue, UserRole } from "../../Common/constants";
export interface LocalBodyModel {
id: number;
@@ -494,7 +494,7 @@ export interface PatientNotesModel {
note: string;
facility: BaseFacilityModel;
created_by_object: BaseUserModel;
- user_type?: string;
+ user_type?: UserRole | "RemoteSpecialist";
created_date: string;
}
From 33f913c1d77502b844efc10ff89db47548a7cc9b Mon Sep 17 00:00:00 2001
From: Ashesh <3626859+Ashesh3@users.noreply.github.com>
Date: Fri, 12 Jan 2024 20:24:52 +0530
Subject: [PATCH 34/36] Reset preview image and close modal for camera (#7024)
---
src/Components/Patient/FileUpload.tsx | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx
index e1ea368af72..d1dedc5e2ff 100644
--- a/src/Components/Patient/FileUpload.tsx
+++ b/src/Components/Patient/FileUpload.tsx
@@ -276,6 +276,7 @@ export const FileUpload = (props: FileUploadProps) => {
const handleClose = () => {
setDownloadURL("");
+ setPreviewImage(null);
setFileState({
...file_state,
open: false,
@@ -1212,6 +1213,7 @@ export const FileUpload = (props: FileUploadProps) => {
{
+ setPreviewImage(null);
setModalOpenForCamera(false);
}}
className="m-2"
@@ -1272,6 +1274,7 @@ export const FileUpload = (props: FileUploadProps) => {
{
setModalOpenForCamera(false);
+ setPreviewImage(null);
}}
>
{t("submit")}
From 27b524a22c7c6e09f7828fae7486c90129b9b910 Mon Sep 17 00:00:00 2001
From: Ashesh <3626859+Ashesh3@users.noreply.github.com>
Date: Fri, 12 Jan 2024 23:55:20 +0530
Subject: [PATCH 35/36] Refactor user delete button in FacilityUsers and
ManageUsers components (#7027)
---
src/Components/Facility/FacilityUsers.tsx | 14 ++++++++------
src/Components/Users/ManageUsers.tsx | 16 ++++++++--------
2 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/src/Components/Facility/FacilityUsers.tsx b/src/Components/Facility/FacilityUsers.tsx
index ca2e1d0e363..f18b9578192 100644
--- a/src/Components/Facility/FacilityUsers.tsx
+++ b/src/Components/Facility/FacilityUsers.tsx
@@ -296,7 +296,10 @@ export default function FacilityUsers(props: any) {
-
+
{`${user.first_name} ${user.last_name}`}
{user.last_login && isUserOnline(user) ? (
@@ -306,13 +309,12 @@ export default function FacilityUsers(props: any) {
>
) : null}
{showUserDelete(authUser, user) && (
-
+
+
)}
diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx
index 750603ccf6f..300f9846a89 100644
--- a/src/Components/Users/ManageUsers.tsx
+++ b/src/Components/Users/ManageUsers.tsx
@@ -218,7 +218,10 @@ export default function ManageUsers() {
)}
-
+
{`${user.first_name} ${user.last_name}`}
{user.last_login && cur_online ? (
@@ -228,15 +231,12 @@ export default function ManageUsers() {
>
) : null}
{showUserDelete(authUser, user) && (
- handleDelete(user)}
>
- Delete
-
+
+
)}
From 51b02c26666877f2b0b481f0476a9b34acba6743 Mon Sep 17 00:00:00 2001
From: Ashesh <3626859+Ashesh3@users.noreply.github.com>
Date: Sat, 13 Jan 2024 00:10:40 +0530
Subject: [PATCH 36/36] Fix InvestigationTable alignment (#7028)
---
src/Components/Facility/Investigations/InvestigationTable.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Components/Facility/Investigations/InvestigationTable.tsx b/src/Components/Facility/Investigations/InvestigationTable.tsx
index 54e7e50bdc7..d322698779d 100644
--- a/src/Components/Facility/Investigations/InvestigationTable.tsx
+++ b/src/Components/Facility/Investigations/InvestigationTable.tsx
@@ -17,7 +17,7 @@ const TestRow = ({ data, i, onChange, showForm, value, isChanged }: any) => {
{data?.investigation_object?.name || "---"}
|
-
+ |
{showForm ? (
data?.investigation_object?.investigation_type === "Choice" ? (
| | |