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 01/15] 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 02/15] 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 03/15] 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 04/15] 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 05/15] 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 06/15] 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 07/15] 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 08/15] 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 09/15] 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 10/15] 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 11/15] 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 12/15] 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 13/15] 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 14/15] 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 15/15] 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" ? (