From 24491373be15e465fa35166107b5989bdb85fb50 Mon Sep 17 00:00:00 2001 From: Onkar Jadhav <56870381+Omkar76@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:02:59 +0530 Subject: [PATCH 01/31] Improve resolution images uploaded via camera (#7160) * Improve resolution of webcam images * replicate changes in cover image * revert: replicate changes in cover image --------- Co-authored-by: Ashesh <3626859+Ashesh3@users.noreply.github.com> Co-authored-by: rithviknishad --- src/Components/Patient/FileUpload.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index d4b8b0387dc..77c67abeadd 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -172,8 +172,8 @@ export const FileUpload = (props: FileUploadProps) => { const [previewImage, setPreviewImage] = useState(null); const [facingMode, setFacingMode] = useState(FACING_MODE_USER); const videoConstraints = { - width: 1280, - height: 720, + width: { ideal: 4096 }, + height: { ideal: 2160 }, facingMode: "user", }; const { width } = useWindowDimensions(); @@ -1164,10 +1164,10 @@ export const FileUpload = (props: FileUploadProps) => { {!previewImage ? (
From eeb4359ceb96eebe52b2ebc36bd704a9c9384991 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:03:45 +0530 Subject: [PATCH 02/31] Show Facility Notify button for District/State Admins only (#7117) --- src/Components/Facility/FacilityCard.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Components/Facility/FacilityCard.tsx b/src/Components/Facility/FacilityCard.tsx index 1424a657de4..080e7992950 100644 --- a/src/Components/Facility/FacilityCard.tsx +++ b/src/Components/Facility/FacilityCard.tsx @@ -225,7 +225,7 @@ export const FacilityCard = (props: { facility: any; userType: any }) => {
- {userType !== "Staff" ? ( + {["DistrictAdmin", "StateAdmin"].includes(userType) && ( { Notify - ) : ( - <> )} Date: Tue, 6 Feb 2024 20:37:16 +0100 Subject: [PATCH 03/31] fix: disable add counsultations button (#7133) --- src/Components/Facility/ConsultationCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Components/Facility/ConsultationCard.tsx b/src/Components/Facility/ConsultationCard.tsx index c402853f366..1c6ec77afde 100644 --- a/src/Components/Facility/ConsultationCard.tsx +++ b/src/Components/Facility/ConsultationCard.tsx @@ -163,6 +163,7 @@ export const ConsultationCard = (props: ConsultationProps) => { `/facility/${itemData.facility}/patient/${itemData.patient}/consultation/${itemData.id}/daily-rounds` ) } + disabled={itemData.discharge_date} authorizeFor={NonReadOnlyUsers} > Add Consultation Updates From f70b827ad8e9329501c0dcb2ff866813b81ce018 Mon Sep 17 00:00:00 2001 From: Ashraf Mohammed <98876115+AshrafMd-1@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:07:37 +0530 Subject: [PATCH 04/31] Redesign the facility card image to 1:1 (#7137) * redesign the facility card * add rounded corners for images --- src/Components/Facility/FacilityCard.tsx | 166 ++++++++++++----------- 1 file changed, 84 insertions(+), 82 deletions(-) diff --git a/src/Components/Facility/FacilityCard.tsx b/src/Components/Facility/FacilityCard.tsx index 080e7992950..8e8dd6da601 100644 --- a/src/Components/Facility/FacilityCard.tsx +++ b/src/Components/Facility/FacilityCard.tsx @@ -49,24 +49,10 @@ export const FacilityCard = (props: { facility: any; userType: any }) => {
- - {(facility.read_cover_image_url && ( - {facility.name} - )) || ( - - )} -
{(facility.read_cover_image_url && ( {
-
- {facility.kasp_empanelled && ( -
- {kasp_string} -
- )} -
+ - - {facility.name} - - - - View CNS - -
-
- - {facility.features?.map( - (feature: number) => - FACILITY_FEATURE_TYPES.some( - (f) => f.id === feature - ) && ( - f.id === feature - )[0]?.name - } - size="small" - startIcon={ - FACILITY_FEATURE_TYPES.filter( - (f) => f.id === feature - )[0]?.icon - } - /> - ) + {(facility.read_cover_image_url && ( + {facility.name} + )) || ( + )} -
+ +
+ {facility.kasp_empanelled && ( +
+ {kasp_string} +
+ )} +
+ + {facility.name} + + + + View CNS + +
+
+ + {facility.features?.map( + (feature: number) => + FACILITY_FEATURE_TYPES.some( + (f) => f.id === feature + ) && ( + f.id === feature + )[0]?.name + } + size="small" + startIcon={ + FACILITY_FEATURE_TYPES.filter( + (f) => f.id === feature + )[0]?.icon + } + /> + ) + )} +
-
From db35822a6d30a7c9af85fea0f55083a110729226 Mon Sep 17 00:00:00 2001 From: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:07:55 +0530 Subject: [PATCH 05/31] Add max validation for doctor experience years in add user form (#7161) * Add validation for doctor experience * add auto scroll function to error * add max validation for doctor experience in user profile page --- src/Components/Users/UserAdd.tsx | 18 +++++++++++++++++- src/Components/Users/UserProfile.tsx | 13 ++++++++++++- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/Components/Users/UserAdd.tsx b/src/Components/Users/UserAdd.tsx index 2c5319e67f3..b3fc9b830f8 100644 --- a/src/Components/Users/UserAdd.tsx +++ b/src/Components/Users/UserAdd.tsx @@ -19,6 +19,7 @@ import { classNames, dateQueryString, parsePhoneNumber, + scrollTo, } from "../../Utils/utils"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; @@ -357,8 +358,19 @@ export const UserAdd = (props: UserProps) => { invalidForm = true; } return; - case "doctor_qualification": case "doctor_experience_commenced_on": + if (state.form.user_type === "Doctor" && !state.form[field]) { + errors[field] = "Field is required"; + invalidForm = true; + } else if ( + state.form.user_type === "Doctor" && + Number(state.form.doctor_experience_commenced_on) > 100 + ) { + errors[field] = "Doctor experience should be less than 100 years"; + invalidForm = true; + } + return; + case "doctor_qualification": case "doctor_medical_council_registration": if (state.form.user_type === "Doctor" && !state.form[field]) { errors[field] = "Field is required"; @@ -491,6 +503,10 @@ export const UserAdd = (props: UserProps) => { }); if (invalidForm) { dispatch({ type: "set_errors", errors }); + const firstError = Object.keys(errors).find((e) => errors[e]); + if (firstError) { + scrollTo(firstError); + } return false; } dispatch({ type: "set_errors", errors }); diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx index 170de558e41..767fc9b73b8 100644 --- a/src/Components/Users/UserProfile.tsx +++ b/src/Components/Users/UserProfile.tsx @@ -244,8 +244,19 @@ export default function UserProfile() { invalidForm = true; } return; - case "doctor_qualification": case "doctor_experience_commenced_on": + if (states.form.user_type === "Doctor" && !states.form[field]) { + errors[field] = "Field is required"; + invalidForm = true; + } else if ( + states.form.user_type === "Doctor" && + Number(states.form.doctor_experience_commenced_on) > 100 + ) { + errors[field] = "Doctor experience should be less than 100 years"; + invalidForm = true; + } + return; + case "doctor_qualification": case "doctor_medical_council_registration": if (states.form.user_type === "Doctor" && !states.form[field]) { errors[field] = "Field is required"; From 1773a15a5c5aa85b522f41727a1bacb7eb60b739 Mon Sep 17 00:00:00 2001 From: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:08:13 +0530 Subject: [PATCH 06/31] Replaced useDispatch with useQuery/request in Treatment Summary and Transfer Patient. (#7084) * replace useDispatch with useQuery/request in TreatmentSummary page * replace useDispatch with useQuery/request in TransferPatientDialog * remove unused fire request --------- Co-authored-by: Rithvik Nishad --- .../Facility/TransferPatientDialog.tsx | 27 +- src/Components/Facility/TreatmentSummary.tsx | 622 ++++++++---------- src/Components/Facility/models.tsx | 7 + src/Redux/actions.tsx | 4 +- src/Redux/api.tsx | 4 + 5 files changed, 302 insertions(+), 362 deletions(-) diff --git a/src/Components/Facility/TransferPatientDialog.tsx b/src/Components/Facility/TransferPatientDialog.tsx index 259d497f684..4c38965c235 100644 --- a/src/Components/Facility/TransferPatientDialog.tsx +++ b/src/Components/Facility/TransferPatientDialog.tsx @@ -9,10 +9,10 @@ import { FieldLabel } from "../Form/FormFields/FormField"; import { OptionsType } from "../../Common/constants"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import { navigate } from "raviger"; -import { transferPatient } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { dateQueryString } from "../../Utils/utils.js"; import dayjs from "dayjs"; +import request from "../../Utils/request/request.js"; +import routes from "../../Redux/api.js"; interface Props { patientList: Array; @@ -60,7 +60,6 @@ const patientFormReducer = (state = initialState, action: any) => { const TransferPatientDialog = (props: Props) => { const { patientList, handleOk, handleCancel, facilityId } = props; - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [state, dispatch] = useReducer(patientFormReducer, initialState); const patientOptions: Array = patientList.map((patient) => { @@ -114,15 +113,17 @@ const TransferPatientDialog = (props: Props) => { const validForm = validateForm(); if (validForm) { setIsLoading(true); - const data = { - date_of_birth: dateQueryString(state.form.date_of_birth), - facility: facilityId, - }; - const res = await dispatchAction( - transferPatient(data, { id: state.form.patient }) - ); + const { res, data } = await request(routes.transferPatient, { + body: { + facility: facilityId, + date_of_birth: dateQueryString(state.form.date_of_birth), + }, + pathParams: { + id: state.form.patient, + }, + }); setIsLoading(false); - if (res && res.data && res.status === 200) { + if (res?.ok && data) { dispatch({ type: "set_form", form: initForm }); handleOk(); @@ -132,10 +133,10 @@ const TransferPatientDialog = (props: Props) => { msg: `Patient ${patientName} transferred successfully`, }); const newFacilityId = - res.data && res.data.facility_object && res.data.facility_object.id; + data && data.facility_object && data.facility_object.id; if (newFacilityId) { navigate( - `/facility/${newFacilityId}/patient/${res.data.patient}/consultation` + `/facility/${newFacilityId}/patient/${data.patient}/consultation` ); } else { navigate("/facility"); diff --git a/src/Components/Facility/TreatmentSummary.tsx b/src/Components/Facility/TreatmentSummary.tsx index f41f540a12d..b6caebc750e 100644 --- a/src/Components/Facility/TreatmentSummary.tsx +++ b/src/Components/Facility/TreatmentSummary.tsx @@ -1,402 +1,332 @@ -import { lazy, useCallback, useState } from "react"; -import { useDispatch } from "react-redux"; -import { - getPatient, - getInvestigation, - getConsultation, -} from "../../Redux/actions"; -import { ConsultationModel } from "./models"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { PatientModel } from "../Patient/models"; - import { GENDER_TYPES } from "../../Common/constants"; import { formatAge, formatDate, formatDateTime } from "../../Utils/utils"; import useSlug from "../../Common/hooks/useSlug"; import useAppHistory from "../../Common/hooks/useAppHistory"; - -const Loading = lazy(() => import("../Common/Loading")); +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; const TreatmentSummary = (props: any) => { const { consultationId, patientId } = props; const date = new Date(); - const dispatch: any = useDispatch(); - const [patientData, setPatientData] = useState({}); - const [consultationData, setConsultationData] = useState( - {} - ); - const [isLoading, setIsLoading] = useState(false); - const [investigations, setInvestigations] = useState>([]); - const [dailyRounds, setDailyRounds] = useState({}); const facilityId = useSlug("facility"); const { goBack } = useAppHistory(); const url = `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}`; - const fetchPatientData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch(getPatient({ id: patientId })); - if (!status.aborted) { - if (res?.data) { - setPatientData(res.data); - } else { - setPatientData({}); - } - } - setIsLoading(false); - }, - [patientId, dispatch] - ); - - const fetchInvestigationData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch(getInvestigation({}, consultationId)); - - if (!status.aborted) { - if (res?.data?.results) { - const valueMap = res.data.results.reduce( - (acc: any, cur: { id: any }) => ({ ...acc, [cur.id]: cur }), - {} - ); - setInvestigations(valueMap); - } else { - setInvestigations([]); - } - } - setIsLoading(false); - }, - [consultationId, dispatch] - ); - - const fetchConsultation = useCallback( - async (status: statusType) => { - setIsLoading(true); - const [res] = await Promise.all([ - dispatch(getConsultation(consultationId)), - ]); - if (!status.aborted) { - if (res?.data) { - setConsultationData(res.data); - if (res.data.last_daily_round) { - setDailyRounds(res.data.last_daily_round); - } - } else { - setConsultationData({}); - } - } - setIsLoading(false); - }, - [consultationId, dispatch] - ); + const { data: patientData } = useQuery(routes.getPatient, { + pathParams: { id: patientId }, + prefetch: patientId !== undefined, + }); - useAbortableEffect((status: statusType) => { - fetchPatientData(status); - fetchInvestigationData(status); + const { data: investigations } = useQuery(routes.getInvestigation, { + pathParams: { consultation_external_id: consultationId }, + prefetch: consultationId !== undefined, + }); - fetchConsultation(status); - }, []); + const { data: consultationData } = useQuery(routes.getConsultation, { + pathParams: { id: consultationId }, + prefetch: consultationId !== undefined, + }); return (
- {isLoading ? ( - - ) : ( -
-
- - -
+
+
+ + +
+ +
+

+ {consultationData?.facility_name ?? ""} +

-
-

- {consultationData.facility_name} -

+

INTERIM TREATMENT SUMMARY

-

INTERIM TREATMENT SUMMARY

+
{formatDate(date)}
-
{formatDate(date)}
+
+
+
+ Name : {patientData?.name ?? ""} +
+
+ Address : {patientData?.address ?? ""} +
+
-
-
+
+
- Name : {patientData.name} + Age :{" "} + {formatAge( + patientData?.age ?? 0, + patientData?.date_of_birth ?? "", + true + )}
-
- Address : {patientData.address} +
+ OP : {consultationData?.patient_no ?? ""}
-
-
-
- Age :{" "} - {formatAge( - patientData.age, - patientData.date_of_birth, - true - )} -
-
- OP : {consultationData.patient_no} -
-
- -
- Date of admission : - - {consultationData.admitted - ? formatDateTime(consultationData.encounter_date) - : " --/--/----"} - -
+
+ Date of admission : + + {consultationData?.admitted + ? formatDateTime(consultationData.encounter_date) + : " --/--/----"} +
+
-
-
- Gender : - {GENDER_TYPES.find((i) => i.id === patientData.gender)?.text} -
+
+
+ Gender : + {GENDER_TYPES.find((i) => i.id === patientData?.gender)?.text} +
-
- Contact person : - - {" "} - {patientData.emergency_phone_number - ? patientData.emergency_phone_number - : " -"} - -
+
+ Contact person : + + {" "} + {patientData?.emergency_phone_number + ? patientData.emergency_phone_number + : " -"} +
+
-
- Comorbidities : -
- - +
+ Comorbidities : +
+
+ + + + + + + + {patientData?.medical_history && + patientData.medical_history.length > 0 ? ( + patientData.medical_history.map( + (obj: any, index: number) => { + return ( + + + + + ); + } + ) + ) : ( - - + + - - - {patientData.medical_history && - patientData.medical_history.length > 0 ? ( - patientData.medical_history.map( - (obj: any, index: number) => { - return ( - - - - - ); - } - ) - ) : ( - - - - - )} - -
DiseaseDetails
+ {obj["disease"]} + + {obj["details"] ? obj["details"] : "---"} +
DiseaseDetails + --- + + --- +
- {obj["disease"]} - - {obj["details"] ? obj["details"] : "---"} -
- --- - - --- -
-
+ )} + +
+
-
- Diagnosis : -
-
- History of present illness : - {consultationData.history_of_present_illness - ? consultationData.history_of_present_illness - : " ---"} -
+
+ Diagnosis : +
+
+ History of present illness : + {consultationData?.history_of_present_illness + ? consultationData.history_of_present_illness + : " ---"} +
-
- Examination details and clinical conditions : - {consultationData.examination_details - ? consultationData.examination_details - : " ---"} -
+
+ Examination details and clinical conditions : + {consultationData?.examination_details + ? consultationData.examination_details + : " ---"} +
-
- Physical Examination info : - {dailyRounds.physical_examination_info - ? dailyRounds.physical_examination_info - : " ---"} -
+
+ Physical Examination info : + {consultationData?.last_daily_round?.physical_examination_info + ? consultationData.last_daily_round + ?.physical_examination_info + : " ---"}
+
-
- General Instructions : - {patientData?.last_consultation?.consultation_notes ? ( -
- {patientData.last_consultation.consultation_notes} -
- ) : ( - " ---" - )} -
+
+ General Instructions : + {patientData?.last_consultation?.consultation_notes ? ( +
+ {patientData.last_consultation.consultation_notes} +
+ ) : ( + " ---" + )} +
+ +
+ Relevant investigations : -
- Relevant investigations : +
+ + + + + + + + + + + -
-
+ Date + + Name + + Result + + Ideal value + + values range + + unit +
- + + {investigations && investigations.results.length > 0 ? ( + investigations.results.map( + (value: any, index: number) => { + return ( + + + + + + + + + ); + } + ) + ) : ( - - - - - - + + + + + + - - - - {Object.values(investigations).length > 0 ? ( - Object.values(investigations).map( - (value: any, index: number) => { - return ( - - - - - - - - - ); - } - ) - ) : ( - - - - - - - - - )} - -
+ {formatDate( + value["session_object"][ + "session_created_date" + ] + )} + + {value["investigation_object"]["name"]} + + {value["notes"] || value["value"]} + + {value["investigation_object"]["ideal_value"] || + "-"} + + {value["investigation_object"]["min_value"]} -{" "} + {value["investigation_object"]["max_value"]} + + {value["investigation_object"]["unit"] || "-"} +
- Date - - Name - - Result - - Ideal value - - values range - - unit - + --- + + --- + + --- + + --- + + --- + + --- +
- {formatDate( - value["session_object"][ - "session_created_date" - ] - )} - - {value["investigation_object"]["name"]} - - {value["notes"] || value["value"]} - - {value["investigation_object"][ - "ideal_value" - ] || "-"} - - {value["investigation_object"]["min_value"]} -{" "} - {value["investigation_object"]["max_value"]} - - {value["investigation_object"]["unit"] || "-"} -
- --- - - --- - - --- - - --- - - --- - - --- -
-
+ )} + +
+
-
- Treatment : - {consultationData.treatment_plan ? ( -

{consultationData.treatment_plan}

- ) : ( -

---

- )} - Treatment summary/Treament Plan : +
+ Treatment : + {consultationData?.treatment_plan ? ( +

{consultationData.treatment_plan}

+ ) : ( +

---

+ )} + Treatment summary/Treament Plan : -
- - +
+
+ + + + + + + + + + {consultationData?.last_daily_round ? ( - - - + + + - - - - {dailyRounds ? ( - - - - - - ) : ( - - - - - - )} - -
DateSpo2Temperature
DateSpo2Temperature + {formatDateTime( + consultationData.last_daily_round.modified_date + )} + + {consultationData.last_daily_round.ventilator_spo2 || + "-"} + + {consultationData.last_daily_round.temperature || "-"} +
- {formatDateTime(dailyRounds.modified_date)} - - {dailyRounds.ventilator_spo2 || "-"} - - {dailyRounds.temperature || "-"} -
- --- - - --- - - --- -
-
+ ) : ( + + + --- + + + --- + + + --- + + + )} + +
- )} +
); }; diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 999c54a2e3a..de035f9ffea 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -572,3 +572,10 @@ export type InventoryLogResponse = InventorySummaryResponse & { unit: number; created_by: number; }; + +export type PatientTransferResponse = { + id: string; + patient: string; + date_of_birth: string; + facility_object: BaseFacilityModel; +}; diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 8e3ae382547..280abbce5cc 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -102,9 +102,7 @@ export const updatePatient = (params: object, pathParam: object) => { export const patchPatient = (params: object, pathParam: object) => { return fireRequest("patchPatient", [], params, pathParam); }; -export const transferPatient = (params: object, pathParam: object) => { - return fireRequest("transferPatient", [], params, pathParam); -}; + export const getStates = () => { return fireRequest("statesList", []); }; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 6425ff8eab0..eed17ad3b4b 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -55,6 +55,7 @@ import { InventorySummaryResponse, InventoryLogResponse, InventoryItemsModel, + PatientTransferResponse, } from "../Components/Facility/models"; import { IDeleteBedCapacity, @@ -86,6 +87,7 @@ import { InvestigationGroup, InvestigationType, } from "../Components/Facility/Investigations"; +import { Investigation } from "../Components/Facility/Investigations/Reports/types"; import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; /** @@ -657,6 +659,7 @@ const routes = { transferPatient: { path: "/api/v1/patient/{id}/transfer/", method: "POST", + TRes: Type(), }, getPatientNotes: { path: "/api/v1/patient/{patientId}/notes/", @@ -998,6 +1001,7 @@ const routes = { getInvestigation: { path: "/api/v1/consultation/{consultation_external_id}/investigation/", method: "GET", + TRes: Type>(), }, getPatientInvestigation: { path: "/api/v1/patient/{patient_external_id}/investigation/", From 1dd0ada4312dd3c4d443e6907c2e239dfa39b84b Mon Sep 17 00:00:00 2001 From: Kshitij Verma <101321276+kshitijv256@users.noreply.github.com> Date: Wed, 7 Feb 2024 01:08:37 +0530 Subject: [PATCH 07/31] only loading discontinued prescriptions instead of all prescriptions (#7071) * only loading discontinued prescriptions instead of all prescriptions * removed useEffect hook --- .../MedicineAdministrationSheet/index.tsx | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx index e94d740c0db..dba0943db20 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx @@ -33,7 +33,7 @@ const MedicineAdministrationSheet = ({ readonly, is_prn }: Props) => { MedicineRoutes.listPrescriptions, { pathParams: { consultation }, - query: { ...filters, discontinued: showDiscontinued ? undefined : false }, + query: { ...filters, discontinued: false }, } ); @@ -41,7 +41,7 @@ const MedicineAdministrationSheet = ({ readonly, is_prn }: Props) => { pathParams: { consultation }, query: { ...filters, - limit: showDiscontinued ? 100 : 1, + limit: 100, discontinued: true, }, prefetch: !showDiscontinued, @@ -49,16 +49,21 @@ const MedicineAdministrationSheet = ({ readonly, is_prn }: Props) => { const discontinuedCount = discontinuedPrescriptions.data?.count; + const prescriptionList = [ + ...(data?.results ?? []), + ...(showDiscontinued ? discontinuedPrescriptions.data?.results ?? [] : []), + ]; + const { activityTimelineBounds, prescriptions } = useMemo( () => ({ - prescriptions: data?.results?.sort( + prescriptions: prescriptionList.sort( (a, b) => +a.discontinued - +b.discontinued ), - activityTimelineBounds: data - ? computeActivityBounds(data.results) + activityTimelineBounds: prescriptionList + ? computeActivityBounds(prescriptionList) : undefined, }), - [data] + [prescriptionList] ); const daysPerPage = useBreakpoints({ default: 1, "2xl": 2 }); From e9f3cc5c1dc6e9447eead50e0d006c6ced231673 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:13:26 +0530 Subject: [PATCH 08/31] Redesign Patient Info Card (#7077) * Redesign Patient Info Card * Responsive view * Fix null reference exception in LegacyDiagnosesList.tsx * Fix Cypress * Update click selector for "Edit Consultation Details" button * Remove unnecessary center alignment in PatientInfoCard * Refactor LegacyDiagnosesList component and remove unused imports * Redesign diagnosis list * Refactor to use tailwind classes * Make hide diagnoses less intrusive * Add "Manage Patient" button click in clickEditConsultationButton method --- .../pageobject/Patient/PatientConsultation.ts | 1 + .../Common/RelativeDateUserMention.tsx | 5 +- .../Common/components/AccordionV2.tsx | 12 +- .../Diagnosis/DiagnosesListAccordion.tsx | 102 ++++ .../Diagnosis/LegacyDiagnosesList.tsx | 87 ---- .../ConsultationUpdatesTab.tsx | 107 ++-- .../Facility/ConsultationDetails/index.tsx | 87 +--- .../Facility/Consultations/Mews.tsx | 66 ++- src/Components/Patient/PatientInfoCard.tsx | 490 ++++++++++-------- src/Utils/utils.ts | 6 +- 10 files changed, 508 insertions(+), 455 deletions(-) create mode 100644 src/Components/Diagnosis/DiagnosesListAccordion.tsx delete mode 100644 src/Components/Diagnosis/LegacyDiagnosesList.tsx diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index 76724d85017..bc2e8f330a3 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -151,6 +151,7 @@ export class PatientConsultationPage { clickEditConsultationButton() { cy.get("#consultation-buttons").scrollIntoView(); + cy.get("button").contains("Manage Patient").click(); cy.get("#consultation-buttons") .contains("Edit Consultation Details") .click(); diff --git a/src/Components/Common/RelativeDateUserMention.tsx b/src/Components/Common/RelativeDateUserMention.tsx index 18fb431574f..6c5a78c7f09 100644 --- a/src/Components/Common/RelativeDateUserMention.tsx +++ b/src/Components/Common/RelativeDateUserMention.tsx @@ -6,6 +6,7 @@ function RelativeDateUserMention(props: { actionDate?: string; user?: PerformedByModel; tooltipPosition?: "top" | "bottom" | "left" | "right"; + withoutSuffix?: boolean; }) { return (
@@ -15,7 +16,9 @@ function RelativeDateUserMention(props: { > {props.actionDate ? formatDateTime(props.actionDate) : "--:--"} - {props.actionDate ? relativeDate(props.actionDate) : "--:--"} + {props.actionDate + ? relativeDate(props.actionDate, props.withoutSuffix ?? false) + : "--:--"}
{props.user && (
diff --git a/src/Components/Common/components/AccordionV2.tsx b/src/Components/Common/components/AccordionV2.tsx index ef4948d8ae3..b8fa441d277 100644 --- a/src/Components/Common/components/AccordionV2.tsx +++ b/src/Components/Common/components/AccordionV2.tsx @@ -1,5 +1,4 @@ import { useRef, useState } from "react"; -import { classNames } from "../../../Utils/utils"; export default function AccordionV2(props: { children: JSX.Element | JSX.Element[]; @@ -55,15 +54,10 @@ export default function AccordionV2(props: {
{props.children}
diff --git a/src/Components/Diagnosis/DiagnosesListAccordion.tsx b/src/Components/Diagnosis/DiagnosesListAccordion.tsx new file mode 100644 index 00000000000..5f339cabc16 --- /dev/null +++ b/src/Components/Diagnosis/DiagnosesListAccordion.tsx @@ -0,0 +1,102 @@ +import { + ActiveConditionVerificationStatuses, + ConditionVerificationStatus, + ConsultationDiagnosis, +} from "./types"; +import { useTranslation } from "react-i18next"; +import { compareBy } from "../../Utils/utils"; +import { useState } from "react"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ButtonV2 from "../Common/components/ButtonV2"; + +interface Props { + diagnoses: ConsultationDiagnosis[]; +} + +type GroupedDiagnoses = Record< + ConditionVerificationStatus, + ConsultationDiagnosis[] +>; + +function groupDiagnoses(diagnoses: ConsultationDiagnosis[]) { + const groupedDiagnoses = {} as GroupedDiagnoses; + + for (const status of ActiveConditionVerificationStatuses) { + groupedDiagnoses[status] = diagnoses + .filter((d) => d.verification_status === status) + .sort(compareBy("is_principal")); + } + + return groupedDiagnoses; +} + +export default function DiagnosesListAccordion(props: Props) { + const [isVisible, setIsVisible] = useState(true); + const diagnoses = groupDiagnoses(props.diagnoses); + + return ( +
+
+ {!isVisible && ( + { + setIsVisible((prev) => !prev); + }} + > + + Expand Diagnoses + + )} +
+
+

+ Diagnoses +

+
+ {Object.entries(diagnoses).map( + ([status, diagnoses]) => + !!diagnoses.length && ( + + ) + )} +
+ { + setIsVisible(false); + }} + > + + Hide Diagnoses + +
+
+ ); +} + +const DiagnosesOfStatus = ({ diagnoses }: Props) => { + const { t } = useTranslation(); + + return ( +
+

+ {t(diagnoses[0].verification_status)} {t("diagnoses")}{" "} + ({t("icd11_as_recommended")}) +

+
    + {diagnoses.map((diagnosis) => ( +
  • + {diagnosis.diagnosis_object?.label} +
  • + ))} +
+
+ ); +}; diff --git a/src/Components/Diagnosis/LegacyDiagnosesList.tsx b/src/Components/Diagnosis/LegacyDiagnosesList.tsx deleted file mode 100644 index 408bee7b52a..00000000000 --- a/src/Components/Diagnosis/LegacyDiagnosesList.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { useState } from "react"; -import { - ActiveConditionVerificationStatuses, - ConditionVerificationStatus, - ConsultationDiagnosis, -} from "./types"; -import { useTranslation } from "react-i18next"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { compareBy } from "../../Utils/utils"; - -interface Props { - diagnoses: ConsultationDiagnosis[]; -} - -type GroupedDiagnoses = Record< - ConditionVerificationStatus, - ConsultationDiagnosis[] ->; - -function groupDiagnoses(diagnoses: ConsultationDiagnosis[]) { - const groupedDiagnoses = {} as GroupedDiagnoses; - - for (const status of ActiveConditionVerificationStatuses) { - groupedDiagnoses[status] = diagnoses - .filter((d) => d.verification_status === status) - .sort(compareBy("is_principal")); - } - - return groupedDiagnoses; -} - -export default function LegacyDiagnosesList(props: Props) { - const diagnoses = groupDiagnoses(props.diagnoses); - - return ( -
- {Object.entries(diagnoses).map( - ([status, diagnoses]) => - !!diagnoses.length && ( - - ) - )} -
- ); -} - -const DefaultShowLimit = 3; - -const DiagnosesOfStatus = ({ diagnoses }: Props) => { - const { t } = useTranslation(); - const [showMore, setShowMore] = useState(false); - - const queryset = showMore ? diagnoses : diagnoses.slice(0, DefaultShowLimit); - - return ( -
-

- {t(queryset[0].verification_status)} {t("diagnoses")}{" "} - ({t("icd11_as_recommended")}) -

-
    - {queryset.map((diagnosis) => ( -
  • - {diagnosis.diagnosis_object.label} - {diagnosis.is_principal && ( - - - {t("principal")} - - )} -
  • - ))} -
- - {diagnoses.length > DefaultShowLimit && ( - setShowMore(!showMore)} - className="cursor-pointer text-sm text-blue-600 hover:text-blue-300" - > - {showMore - ? t("hide") - : `... and ${diagnoses.length - queryset.length} more.`} - - )} -
- ); -}; diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index 4158d7f6609..323715ccfb0 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -342,7 +342,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { from{" "} {formatDate( - props.consultationData.last_daily_round.created_at + props.consultationData.last_daily_round.taken_at )} @@ -602,58 +602,59 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
)} - -
-
-

- Body Details -

-
-
- Gender {" - "} - - {props.patientData.gender ?? "-"} - -
-
- Age {" - "} - - {props.patientData.age !== undefined // 0 is a valid age, so we need to check for undefined - ? formatAge( - props.patientData.age, - props.patientData.date_of_birth - ) - : "-"} - -
-
- Weight {" - "} - - {props.consultationData.weight ?? "-"} Kg - -
-
- Height {" - "} - - {props.consultationData.height ?? "-"} cm - -
-
- Body Surface Area {" - "} - - {Math.sqrt( - (Number(props.consultationData.weight) * - Number(props.consultationData.height)) / - 3600 - ).toFixed(2)}{" "} - m2 - -
-
- Blood Group {" - "} - - {props.patientData.blood_group ?? "-"} - +
+
+
+

+ Body Details +

+
+
+ Gender {" - "} + + {props.patientData.gender ?? "-"} + +
+
+ Age {" - "} + + {props.patientData.age !== undefined // 0 is a valid age, so we need to check for undefined + ? formatAge( + props.patientData.age, + props.patientData.date_of_birth + ) + : "-"} + +
+
+ Weight {" - "} + + {props.consultationData.weight ?? "-"} Kg + +
+
+ Height {" - "} + + {props.consultationData.height ?? "-"} cm + +
+
+ Body Surface Area {" - "} + + {Math.sqrt( + (Number(props.consultationData.weight) * + Number(props.consultationData.height)) / + 3600 + ).toFixed(2)}{" "} + m2 + +
+
+ Blood Group {" - "} + + {props.patientData.blood_group ?? "-"} + +
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index c6cc9e02275..071e7bfc1af 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -15,7 +15,6 @@ import { statusType, useAbortableEffect } from "../../../Common/utils"; import { lazy, useCallback, useState } from "react"; import DoctorVideoSlideover from "../DoctorVideoSlideover"; import { make as Link } from "../../Common/components/Link.bs"; -import PatientInfoCard from "../../Patient/PatientInfoCard"; import { PatientModel } from "../../Patient/models"; import { formatDateTime, relativeTime } from "../../../Utils/utils"; @@ -37,8 +36,10 @@ import { ConsultationDialysisTab } from "./ConsultationDialysisTab"; import { ConsultationNeurologicalMonitoringTab } from "./ConsultationNeurologicalMonitoringTab"; import { ConsultationNutritionTab } from "./ConsultationNutritionTab"; import PatientNotesSlideover from "../PatientNotesSlideover"; -import LegacyDiagnosesList from "../../Diagnosis/LegacyDiagnosesList"; import { AssetBedModel } from "../../Assets/AssetTypes"; +import PatientInfoCard from "../../Patient/PatientInfoCard"; +import RelativeDateUserMention from "../../Common/RelativeDateUserMention"; +import DiagnosesListAccordion from "../../Diagnosis/DiagnosesListAccordion"; const Loading = lazy(() => import("../../Common/Loading")); const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -343,7 +344,7 @@ export const ConsultationDetails = (props: any) => { showAbhaProfile={qParams["show-abha-profile"] === "true"} /> -
+
{consultationData.admitted_to && (
@@ -374,71 +375,39 @@ export const ConsultationDetails = (props: any) => {
)}
- -
-
- {/*consultationData.other_symptoms && ( -
- - Other Symptoms:{" "} - - {consultationData.other_symptoms} -
- )*/} - - - - {(consultationData.treating_physician_object || - consultationData.deprecated_verified_by) && ( -
- - Treating Physician:{" "} - - {consultationData.treating_physician_object - ? `${consultationData.treating_physician_object.first_name} ${consultationData.treating_physician_object.last_name}` - : consultationData.deprecated_verified_by} - -
- )} -
-
-
+
-
- Created: - {consultationData.created_date - ? formatDateTime(consultationData.created_date) - : "--:--"}{" "} - | +
+ Created:   +
- {consultationData.created_by && ( -
- {` ${consultationData.created_by.first_name} ${consultationData.created_by.last_name} `} - {`@${consultationData.created_by.username} (${consultationData.created_by.user_type})`} -
- )}
-
- Last Modified: - {consultationData.modified_date - ? formatDateTime(consultationData.modified_date) - : "--:--"}{" "} - | +
+ Last Modified:   +
- {consultationData.last_edited_by && ( -
- {` ${consultationData.last_edited_by.first_name} ${consultationData.last_edited_by.last_name} `} - {`@${consultationData.last_edited_by.username} (${consultationData.last_edited_by.user_type})`} -
- )}
- +
+
+ +
+
diff --git a/src/Components/Facility/Consultations/Mews.tsx b/src/Components/Facility/Consultations/Mews.tsx index 14e7d7f9e63..8e109410958 100644 --- a/src/Components/Facility/Consultations/Mews.tsx +++ b/src/Components/Facility/Consultations/Mews.tsx @@ -1,6 +1,5 @@ import { DailyRoundsModel } from "../../Patient/models"; -import RecordMeta from "../../../CAREUI/display/RecordMeta"; -import { classNames } from "../../../Utils/utils"; +import { formatDateTime } from "../../../Utils/utils"; const getRespScore = (value?: number) => { if (typeof value !== "number") return; @@ -60,35 +59,45 @@ const getLOCRange = (value?: DailyRoundsModel["consciousness_level"]) => { }[value]; }; +const getBorderColor = (score: number) => { + if (score === undefined) return "border-gray-700"; + if (score <= 2) return "border-primary-500"; + if (score <= 3) return "border-yellow-300"; + if (score <= 5) return "border-warning-600"; + return "border-danger-500"; +}; + export const Mews = ({ dailyRound }: { dailyRound: DailyRoundsModel }) => { const mewsCard = (isMissing: boolean, data: string[] | number) => { if (isMissing) { return ( <> -
-

N/A

+
+
+ - +
+ MEWS
{(data as string[]).join(", ")}{" "} data is missing from the last log update. +
Last Updated: {formatDateTime(dailyRound.modified_date)}
-
- -
-
+
); } else { - const value = Number(data); return ( <> -
-

{data}

+
+
+ {data} +
+ MEWS

Resp. Rate: {dailyRound.resp} @@ -113,25 +122,10 @@ export const Mews = ({ dailyRound }: { dailyRound: DailyRoundsModel }) => { .toLowerCase()}

+ Last Updated: {formatDateTime(dailyRound.modified_date)}
-
- -
6 && "bg-danger-500" - )} - >
-
+
); } @@ -149,8 +143,7 @@ export const Mews = ({ dailyRound }: { dailyRound: DailyRoundsModel }) => { if (Object.values(scores).some((value) => value === undefined)) { return ( -
-

MEWS Score

+
{mewsCard( true, Object.entries(scores) @@ -162,8 +155,7 @@ export const Mews = ({ dailyRound }: { dailyRound: DailyRoundsModel }) => { } return ( -
-

MEWS Score

+
{mewsCard( false, Object.values(scores as Record).reduce((p, v) => p + v) diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index c9d4aac916d..8cbb24c61a9 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -6,29 +6,29 @@ import { PATIENT_CATEGORIES, RESPIRATORY_SUPPORT, TELEMEDICINE_ACTIONS, -} from "../../Common/constants"; -import { ConsultationModel, PatientCategory } from "../Facility/models"; +} from "../../Common/constants.js"; +import { ConsultationModel, PatientCategory } from "../Facility/models.js"; import { Switch, Menu } from "@headlessui/react"; import { Link, navigate } from "raviger"; import { useState } from "react"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import useConfig from "../../Common/hooks/useConfig"; -import dayjs from "../../Utils/dayjs"; +import CareIcon from "../../CAREUI/icons/CareIcon.js"; +import useConfig from "../../Common/hooks/useConfig.js"; +import dayjs from "../../Utils/dayjs.js"; import { classNames, formatDate, formatDateTime } from "../../Utils/utils.js"; -import ABHAProfileModal from "../ABDM/ABHAProfileModal"; -import LinkABHANumberModal from "../ABDM/LinkABHANumberModal"; -import LinkCareContextModal from "../ABDM/LinkCareContextModal"; -import DialogModal from "../Common/Dialog"; -import ButtonV2 from "../Common/components/ButtonV2"; -import Beds from "../Facility/Consultations/Beds"; -import { PatientModel } from "./models"; +import ABHAProfileModal from "../ABDM/ABHAProfileModal.js"; +import LinkABHANumberModal from "../ABDM/LinkABHANumberModal.js"; +import LinkCareContextModal from "../ABDM/LinkCareContextModal.js"; +import DialogModal from "../Common/Dialog.js"; +import ButtonV2 from "../Common/components/ButtonV2.js"; +import Beds from "../Facility/Consultations/Beds.js"; +import { PatientModel } from "./models.js"; import request from "../../Utils/request/request.js"; import routes from "../../Redux/api.js"; import DropdownMenu from "../Common/components/Menu.js"; import { triggerGoal } from "../../Integrations/Plausible.js"; -import useAuthUser from "../../Common/hooks/useAuthUser"; +import useAuthUser from "../../Common/hooks/useAuthUser.js"; import { Mews } from "../Facility/Consultations/Mews.js"; import DischargeSummaryModal from "../Facility/DischargeSummaryModal.js"; import DischargeModal from "../Facility/DischargeModal.js"; @@ -153,83 +153,86 @@ export default function PatientInfoCard(props: { )} -
-
+
+
{/* Can support for patient picture in the future */} -
-
- {consultation?.current_bed && - consultation?.discharge_date === null ? ( -
-

- { - consultation?.current_bed?.bed_object?.location_object - ?.name - } -

-

- {consultation?.current_bed?.bed_object.name} -

-
- +
+
+
+ {consultation?.current_bed && + consultation?.discharge_date === null ? ( +
+

{ consultation?.current_bed?.bed_object?.location_object ?.name } - - {consultation?.current_bed?.bed_object.name} +

+

+ {consultation?.current_bed?.bed_object.name} +

+
+ + { + consultation?.current_bed?.bed_object?.location_object + ?.name + } + + {consultation?.current_bed?.bed_object.name} +
-
- ) : ( -
- + ) : ( +
+ +
+ )} +
+ {category && ( +
+ {category.toUpperCase()}
)} + setOpen(true)} + className="mt-1 px-[10px] py-1" + > + {bedDialogTitle} +
- {category && ( +
- {category.toUpperCase()} -
- )} - setOpen(true)} className="mt-1"> - {bedDialogTitle} - -
-
-
- {patient.name} -
-
- {patient.review_time && - !consultation?.discharge_date && - Number(consultation?.review_interval) > 0 && ( -
+ {patient.age} years • {patient.gender} +
+
+ - - {(dayjs().isBefore(patient.review_time) - ? "Review before: " - : "Review Missed: ") + - formatDateTime(patient.review_time)} -
- )} + + {consultation?.facility_name} + +
+
-
+
+
+
- {consultation?.patient_no && ( - - - {`${consultation?.suggestion === "A" ? "IP" : "OP"}: ${ - consultation?.patient_no - }`} - - - )} {medicoLegalCase && ( - + MLC )}
- {!!consultation?.discharge_date && ( -

- Discharged from CARE -

- )} -
-
- {patient.action && patient.action != 10 && ( -
-
- - {" "} - { - TELEMEDICINE_ACTIONS.find( - (i) => i.id === patient.action - )?.desc - } - -
-
- )} -
-
- Age: {patient.age} years -
-
-
-
- Gender: {patient.gender} -
+
+
+ {patient.name} +
+ {patient.age} years • {patient.gender}
- {consultation?.suggestion === "DC" && ( -
+
+
+
+ {consultation?.patient_no && ( + + + {`${consultation?.suggestion === "A" ? "IP" : "OP"}: ${ + consultation?.patient_no + }`} + + + )} + {patient.action && patient.action != 10 && (
-
- Domiciliary Care - +
+ + {" "} + { + TELEMEDICINE_ACTIONS.find( + (i) => i.id === patient.action + )?.desc + } +
+ )} +
+ {patient.blood_group && ( +
+ Blood Group: {patient.blood_group} +
+ )}
- )} -
-
-
- {[ - [ - "Respiratory Support", - RESPIRATORY_SUPPORT.find( - (resp) => - resp.text === - consultation?.last_daily_round?.ventilator_interface - )?.id ?? "UNKNOWN", - consultation?.last_daily_round?.ventilator_interface, - ], - ].map((stat, i) => { - return stat[2] && stat[1] !== "NONE" ? ( -
- {stat[0]} : {stat[1]} -
- ) : ( - "" - ); - })} -
- {consultation?.discharge_date ? ( -
-
- - { - CONSULTATION_SUGGESTION.find( - (suggestion) => - suggestion.id === consultation?.suggestion - )?.text - }{" "} - on {formatDateTime(consultation.encounter_date)}, - {consultation?.new_discharge_reason === - DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id ? ( - - {" "} - Expired on {formatDate(consultation?.death_datetime)} - - ) : ( - - {" "} - Discharged on{" "} - {formatDateTime(consultation?.discharge_date)} - + {patient.review_time && + !consultation?.discharge_date && + Number(consultation?.review_interval) > 0 && ( +
+
+ + {dayjs().isBefore(patient.review_time) + ? "Review before: " + : "Review Missed: "} + {formatDateTime(patient.review_time)} +
+
)} -
-
-
- ) : ( -
- - {consultation?.encounter_date && ( + {consultation?.suggestion === "DC" && (
- Admission on{" "} - {formatDateTime(consultation?.encounter_date)} +
+
+ + Domiciliary Care +
+
)} - {consultation?.icu_admission_date && ( -
- , ICU Admission on{" "} - {formatDateTime(consultation?.icu_admission_date)} + {!!consultation?.discharge_date && ( +

+ Discharged from CARE +

+ )} + {[ + [ + "Respiratory Support", + RESPIRATORY_SUPPORT.find( + (resp) => + resp.text === + consultation?.last_daily_round?.ventilator_interface + )?.id ?? "UNKNOWN", + consultation?.last_daily_round?.ventilator_interface, + ], + ].map((stat, i) => { + return stat[2] && stat[1] !== "NONE" ? ( +
+
+ {stat[0]} : {stat[1]} +
+
+ ) : ( + "" + ); + })} + {consultation?.discharge_date ? ( +
+
+ + + { + CONSULTATION_SUGGESTION.find( + (suggestion) => + suggestion.id === consultation?.suggestion + )?.text + } + {" "} + on {formatDateTime(consultation.encounter_date)}, + {consultation?.new_discharge_reason === "EXP" ? ( + + {" "} + Expired on{" "} + {formatDate(consultation?.death_datetime)} + + ) : ( + + {" "} + Discharged on{" "} + {formatDateTime(consultation?.discharge_date)} + + )} + +
+
+ ) : ( +
+ + {consultation?.encounter_date && ( +
+ Admission on:{" "} + {formatDateTime(consultation?.encounter_date)} +
+ )} + {consultation?.icu_admission_date && ( +
+ , ICU Admission on:{" "} + {formatDateTime(consultation?.icu_admission_date)} +
+ )} +
)} - +
+
+
+
+
+ {consultation?.diagnoses?.length + ? (() => { + const principal_diagnosis = consultation.diagnoses.find( + (diagnosis) => diagnosis.is_principal + ); + return principal_diagnosis ? ( +
+
+ Principal Diagnosis: +
+
+ {principal_diagnosis.diagnosis_object.label}{" "} + + +

+ {principal_diagnosis.verification_status} +

+
+
+
+ ) : null; + })() + : null} + {(consultation?.treating_physician_object || + consultation?.deprecated_verified_by) && ( +
+ + Treating Physician:{" "} + + {consultation?.treating_physician_object + ? `${consultation?.treating_physician_object.first_name} ${consultation?.treating_physician_object.last_name}` + : consultation?.deprecated_verified_by} + +
+ )}
- )} +
- {consultation?.last_daily_round && ( -
- -
- )} -
+ {consultation?.last_daily_round && ( +
+ +
+ )} {!!consultation?.discharge_date && ( -
+
Discharge Reason
-
+
{!consultation?.new_discharge_reason ? ( {consultation.suggestion === "OP" @@ -401,14 +472,6 @@ export default function PatientInfoCard(props: {
)} {[ - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/update`, - "Edit Consultation Details", - "pen", - patient.is_active && - consultation?.id && - !consultation?.discharge_date, - ], [ `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/daily-rounds`, "Log Update", @@ -431,7 +494,10 @@ export default function PatientInfoCard(props: { ].map( (action: any, i) => action[3] && ( -
+
- +

{action[1]}

{action?.[4]?.[0] && ( <> -

+

{action[4][1]}

@@ -474,11 +540,20 @@ export default function PatientInfoCard(props: { } + title={"Manage Patient"} + icon={} + containerClassName="w-full lg:w-auto mt-2 2xl:mt-0" >
{[ + [ + `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/update`, + "Edit Consultation Details", + "pen", + patient.is_active && + consultation?.id && + !consultation?.discharge_date, + ], [ `/patient/${patient.id}/investigation_reports`, "Investigation Summary", @@ -554,6 +629,7 @@ export default function PatientInfoCard(props: { ) )}
+
{enable_abdm && (patient.abha_number ? ( diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index b890672a6c1..916d32f5ba3 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -99,9 +99,11 @@ export const formatDate = (date: DateLike, format = DATE_FORMAT) => export const formatTime = (date: DateLike, format = TIME_FORMAT) => formatDateTime(date, format); -export const relativeDate = (date: DateLike) => { +export const relativeDate = (date: DateLike, withoutSuffix = false) => { const obj = dayjs(date); - return `${obj.fromNow()} at ${obj.format(TIME_FORMAT)}`; + return `${obj.fromNow(withoutSuffix)} ${ + withoutSuffix && "ago" + } at ${obj.format(TIME_FORMAT)}`; }; export const formatName = (user: { first_name: string; last_name: string }) => { From 8fb7d5946230f5b1ae563af70dce07eaffac6468 Mon Sep 17 00:00:00 2001 From: Gampa Sri Harsh <114745442+sriharsh05@users.noreply.github.com> Date: Wed, 7 Feb 2024 11:17:30 +0530 Subject: [PATCH 09/31] limited the decimal point of location picker to 7 (#7162) --- src/Components/Facility/FacilityCreate.tsx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index 9fbe89035c4..69f02aa91ee 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -252,8 +252,10 @@ export const FacilityCreate = (props: FacilityProps) => { ? "+91" + data.phone_number : data.phone_number : "", - latitude: data.latitude ? String(data.latitude) : "", - longitude: data.longitude ? String(data.longitude) : "", + latitude: data.latitude ? parseFloat(data.latitude).toFixed(7) : "", + longitude: data.longitude + ? parseFloat(data.longitude).toFixed(7) + : "", type_b_cylinders: data.type_b_cylinders, type_c_cylinders: data.type_c_cylinders, type_d_cylinders: data.type_d_cylinders, @@ -292,8 +294,8 @@ export const FacilityCreate = (props: FacilityProps) => { type: "set_form", form: { ...state.form, - latitude: location.lat().toString(), - longitude: location.lng().toString(), + latitude: location.lat().toFixed(7), + longitude: location.lng().toFixed(7), }, }); } From fd53e5e3918c033d4d6717931b0e3b5244961d5b Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 7 Feb 2024 11:19:06 +0530 Subject: [PATCH 10/31] fixes #7145; live refresh cns on middleware hostname change (#7149) --- src/Components/Assets/AssetType/HL7Monitor.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index 86b9565e536..a7be23baad8 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -69,6 +69,11 @@ const HL7Monitor = (props: HL7MonitorProps) => { }; if (isLoading) return ; + + const socketUrl = `wss://${ + middlewareHostname || resolvedMiddleware?.hostname + }/observations/${localipAddress}`; + return (
@@ -126,13 +131,12 @@ const HL7Monitor = (props: HL7MonitorProps) => { )} {assetType === "HL7MONITOR" && ( - + )} {assetType === "VENTILATOR" && ( )}
From cab3e97bac2d327894e1b8afa66d4a559608ca20 Mon Sep 17 00:00:00 2001 From: konavivekramakrishna Date: Wed, 7 Feb 2024 11:27:49 +0530 Subject: [PATCH 11/31] Removed 0 in Link Facility sidebar (#7144) * minor fix * draft * Revert "draft" This reverts commit 4ed54c2672a8f470c55fec4032ae653e31cfb457. --- src/Components/Users/ManageUsers.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx index 597ddefba19..6f5de125600 100644 --- a/src/Components/Users/ManageUsers.tsx +++ b/src/Components/Users/ManageUsers.tsx @@ -715,7 +715,7 @@ function UserFacilities(props: { user: any }) { )} {/* Linked Facilities section */} - {userFacilities?.length && ( + {!!userFacilities?.length && (
Linked Facilities From d4ac20460b4c83b38114c96cf0e6375a682cd5da Mon Sep 17 00:00:00 2001 From: konavivekramakrishna Date: Wed, 7 Feb 2024 16:11:13 +0530 Subject: [PATCH 12/31] Fixes responsiveness in Facilty Home (#6960) * fixed styling in faciltyhome * added flex-wrap * fix ui in sm * fix ui in sm * rm un-necessary classname * Update src/Components/Facility/FacilityHome.tsx --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Co-authored-by: Rithvik Nishad --- src/Components/Facility/FacilityHome.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index 6f42dab00a6..4b4754b894c 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -222,7 +222,7 @@ export const FacilityHome = (props: any) => {
-
+

@@ -242,7 +242,7 @@ export const FacilityHome = (props: any) => {

-
+

Local Body @@ -251,7 +251,7 @@ export const FacilityHome = (props: any) => { {facilityData?.local_body_object?.name}

-
+

Ward @@ -392,7 +392,7 @@ export const FacilityHome = (props: any) => { )}

-
+
Date: Wed, 7 Feb 2024 17:11:27 +0530 Subject: [PATCH 13/31] Make Update Log buttons responsive (#7185) --- .../Facility/ConsultationDetails/ConsultationUpdatesTab.tsx | 6 +++++- .../Consultations/DailyRounds/DefaultLogUpdateCard.tsx | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index 323715ccfb0..56aadcadbdb 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -126,7 +126,11 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { )}
- +
{!props.consultationData.discharge_date && ((hl7SocketUrl && !ventilatorSocketUrl) || diff --git a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx index 34518afcb9b..bb080d43179 100644 --- a/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/DefaultLogUpdateCard.tsx @@ -32,7 +32,7 @@ const DefaultLogUpdateCard = ({ round, ...props }: Props) => { attributeKey="other_details" attributeValue={round.other_details} /> -
+
Date: Wed, 7 Feb 2024 22:17:43 +0530 Subject: [PATCH 14/31] Make log update button responsive (#7191) * Make log update button responsive * Update PatientInfoCard component styling --- src/Components/Patient/PatientInfoCard.tsx | 603 +++++++++++---------- 1 file changed, 303 insertions(+), 300 deletions(-) diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 8cbb24c61a9..c954438e6c2 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -471,347 +471,350 @@ export default function PatientInfoCard(props: {
)} - {[ - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/daily-rounds`, - "Log Update", - "plus", - patient.is_active && - consultation?.id && - !consultation?.discharge_date, +
+ {[ [ - !(consultation?.facility !== patient.facility) && - !(consultation?.discharge_date ?? !patient.is_active) && - dayjs(consultation?.modified_date).isBefore( - dayjs().subtract(1, "day") - ), -
- No update - filed in the last 24 hours -
, + `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/daily-rounds`, + "Log Update", + "plus", + patient.is_active && + consultation?.id && + !consultation?.discharge_date, + [ + !(consultation?.facility !== patient.facility) && + !(consultation?.discharge_date ?? !patient.is_active) && + dayjs(consultation?.modified_date).isBefore( + dayjs().subtract(1, "day") + ), +
+ No + update filed in the last 24 hours +
, + ], ], - ], - ].map( - (action: any, i) => - action[3] && ( -
- + action[3] && ( +
{ - if ( + > + - - -

{action[1]}

-
-
- {action?.[4]?.[0] && ( - <> -

- {action[4][1]} -

- - )} -
- ) - )} - } - containerClassName="w-full lg:w-auto mt-2 2xl:mt-0" - > -
- {[ - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/update`, - "Edit Consultation Details", - "pen", - patient.is_active && - consultation?.id && - !consultation?.discharge_date, - ], - [ - `/patient/${patient.id}/investigation_reports`, - "Investigation Summary", - "align-alt", - true, - ], - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/treatment-summary`, - "Treatment Summary", - "file-medical", - consultation?.id, - ], - ] - .concat( - enable_hcx - ? [ - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/claims`, - "Claims", - "copy-landscape", - consultation?.id, - ], - ] - : [] + onClick={() => { + if ( + consultation?.admitted && + !consultation?.current_bed && + i === 1 + ) { + Notification.Error({ + msg: "Please assign a bed to the patient", + }); + setOpen(true); + } + }} + className="w-full" + > + + +

{action[1]}

+
+ + {action?.[4]?.[0] && ( + <> +

+ {action[4][1]} +

+ + )} +
) - .map( - (action: any, i) => - action[3] && ( -
- { - if ( + )} + } + className="xl:justify-center" + containerClassName="w-full lg:w-auto mt-2 2xl:mt-0 flex justify-center" + > +
+ {[ + [ + `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/update`, + "Edit Consultation Details", + "pen", + patient.is_active && + consultation?.id && + !consultation?.discharge_date, + ], + [ + `/patient/${patient.id}/investigation_reports`, + "Investigation Summary", + "align-alt", + true, + ], + [ + `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/treatment-summary`, + "Treatment Summary", + "file-medical", + consultation?.id, + ], + ] + .concat( + enable_hcx + ? [ + [ + `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/claims`, + "Claims", + "copy-landscape", + consultation?.id, + ], + ] + : [] + ) + .map( + (action: any, i) => + action[3] && ( +
+ - - {action[1]} - - {action?.[4]?.[0] && ( + onClick={() => { + if ( + action[1] !== "Treatment Summary" && + consultation?.admitted && + !consultation?.current_bed && + i === 1 + ) { + Notification.Error({ + msg: "Please assign a bed to the patient", + }); + setOpen(true); + } + triggerGoal("Patient Card Button Clicked", { + buttonName: action[1], + consultationId: consultation?.id, + userId: authUser?.id, + }); + }} + > + + {action[1]} + + {action?.[4]?.[0] && ( + <> +

+ {action[4][1]} +

+ + )} +
+ ) + )} +
+ +
+ {enable_abdm && + (patient.abha_number ? ( + <> + + {({ close }) => ( <> -

- {action[4][1]} -

+
{ + close(); + setShowABHAProfile(true); + triggerGoal("Patient Card Button Clicked", { + buttonName: "Show ABHA Profile", + consultationId: consultation?.id, + userId: authUser?.id, + }); + }} + > + + Show ABHA Profile +
+
{ + triggerGoal("Patient Card Button Clicked", { + buttonName: "Link Care Context", + consultationId: consultation?.id, + userId: authUser?.id, + }); + close(); + setShowLinkCareContext(true); + }} + > + + Link Care Context +
)} -
- ) - )} -
- -
- {enable_abdm && - (patient.abha_number ? ( - <> + + + ) : ( {({ close }) => ( - <> +
{ + close(); + setShowLinkABHANumber(true); + }} + > + + +

Link ABHA Number

+
+
+ )} +
+ ))} +
+
+ {!consultation?.discharge_date && ( + + {({ close }) => ( + <> + {hasActiveShiftingRequest() ? (
{ close(); - setShowABHAProfile(true); - triggerGoal("Patient Card Button Clicked", { - buttonName: "Show ABHA Profile", - consultationId: consultation?.id, - userId: authUser?.id, - }); + navigate( + `/shifting/${ + activeShiftingData[ + activeShiftingData.length - 1 + ].id + }` + ); }} > - - Show ABHA Profile + + +

Track Shifting

+
+ ) : (
{ - triggerGoal("Patient Card Button Clicked", { - buttonName: "Link Care Context", - consultationId: consultation?.id, - userId: authUser?.id, - }); close(); - setShowLinkCareContext(true); + navigate( + `/facility/${patient.facility}/patient/${patient.id}/shift/new` + ); }} > - - Link Care Context + + +

Shift Patient

+
- - )} -
- - ) : ( - - {({ close }) => ( -
{ - close(); - setShowLinkABHANumber(true); - }} - > - - -

Link ABHA Number

-
-
+ )} + )}
- ))} -
-
- {!consultation?.discharge_date && ( + )} {({ close }) => ( - <> - {hasActiveShiftingRequest() ? ( -
{ - close(); - navigate( - `/shifting/${ - activeShiftingData[ - activeShiftingData.length - 1 - ].id - }` - ); - }} - > - - -

Track Shifting

-
-
- ) : ( -
{ - close(); - navigate( - `/facility/${patient.facility}/patient/${patient.id}/shift/new` - ); - }} - > - - -

Shift Patient

-
-
- )} - +
{ + close(); + setOpenDischargeSummaryDialog(true); + }} + > + + +

{t("discharge_summary")}

+
+
)}
- )} - - {({ close }) => ( -
{ - close(); - setOpenDischargeSummaryDialog(true); - }} - > - - -

{t("discharge_summary")}

-
-
- )} -
- - {({ close }) => ( -
{ - if (!consultation?.discharge_date) { - close(); - setOpenDischargeDialog(true); - } - }} - > - - -

{t("discharge_from_care")}

-
-
- )} -
-
-
- - { - triggerGoal("Patient Card Button Clicked", { - buttonName: "Medico Legal Case", - consultationId: consultation?.id, - userId: authUser?.id, - }); - setMedicoLegalCase(checked); - switchMedicoLegalCase(checked); - }} - className={classNames( - medicoLegalCase ? "bg-primary" : "bg-gray-200", - "relative inline-flex h-4 w-8 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none " + + {({ close }) => ( +
{ + if (!consultation?.discharge_date) { + close(); + setOpenDischargeDialog(true); + } + }} + > + + +

{t("discharge_from_care")}

+
+
)} - > -
+
+ + { + triggerGoal("Patient Card Button Clicked", { + buttonName: "Medico Legal Case", + consultationId: consultation?.id, + userId: authUser?.id, + }); + setMedicoLegalCase(checked); + switchMedicoLegalCase(checked); + }} className={classNames( - medicoLegalCase ? "translate-x-4" : "translate-x-0", - "pointer-events-none inline-block h-3 w-3 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out" + medicoLegalCase ? "bg-primary" : "bg-gray-200", + "relative inline-flex h-4 w-8 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none " )} - /> - - - - Medico-Legal Case - {" "} - - -
-
+ > +
+ +
Date: Wed, 7 Feb 2024 22:23:18 +0530 Subject: [PATCH 15/31] fixes improper string interpolation in relative dates causing 'false' to be present when withoutSuffix is disabled (#7189) * fixes improper string interpolation in realtive dates causing 'false' to be present when withoutSuffix is disabled * Update src/Utils/utils.ts --------- Co-authored-by: Ashesh <3626859+Ashesh3@users.noreply.github.com> --- src/Utils/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 916d32f5ba3..4e1d1626a59 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -101,8 +101,8 @@ export const formatTime = (date: DateLike, format = TIME_FORMAT) => export const relativeDate = (date: DateLike, withoutSuffix = false) => { const obj = dayjs(date); - return `${obj.fromNow(withoutSuffix)} ${ - withoutSuffix && "ago" + return `${obj.fromNow(withoutSuffix)}${ + withoutSuffix ? " ago " : "" } at ${obj.format(TIME_FORMAT)}`; }; From 4fa053384bb97b626ed46c9bc40289ce84404b3a Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Thu, 8 Feb 2024 19:07:58 +0530 Subject: [PATCH 16/31] show notification when health id registration fails (#7114) --- .../e2e/facility_spec/facility_manage.cy.ts | 27 +++++----- cypress/pageobject/Facility/FacilityManage.ts | 10 ++-- .../ABDM/ConfigureHealthFacility.tsx | 49 ++++++------------- 3 files changed, 37 insertions(+), 49 deletions(-) diff --git a/cypress/e2e/facility_spec/facility_manage.cy.ts b/cypress/e2e/facility_spec/facility_manage.cy.ts index 11c75949af1..612a95001d6 100644 --- a/cypress/e2e/facility_spec/facility_manage.cy.ts +++ b/cypress/e2e/facility_spec/facility_manage.cy.ts @@ -13,11 +13,11 @@ describe("Facility Manage Functions", () => { const facilityUpdatedMiddleware = "updated.coronasafe.live"; const facilityMiddlewareSuccessfullNotification = "Facility updated successfully"; - const facilityHrfidUpdateButton = "Link Health Facility"; - const facilityHrfidSuccessfullNotification = - "Health Facility config updated successfully"; - const facilityHrfId = uuidv4(); - const facilityUpdatedHrfId = uuidv4(); + const facilityHfridUpdateButton = "Link Health Facility"; + const facilityHfridToastNotificationText = + /Health Facility config updated successfully|Health ID registration failed/; + const facilityHfrId = "IN180000018"; + const facilityUpdatedHfrId = uuidv4(); const doctorCapacity = "5"; const doctorModifiedCapacity = "7"; const totalCapacity = "100"; @@ -80,28 +80,29 @@ describe("Facility Manage Functions", () => { facilityPage.clickManageFacilityDropdown(); facilityManage.clickFacilityConfigureButton(); // verify mandatory field error message - facilityManage.clickButtonWithText(facilityHrfidUpdateButton); + facilityManage.clearHfrId(); + facilityManage.clickButtonWithText(facilityHfridUpdateButton); facilityManage.checkErrorMessageVisibility( "Health Facility Id is required" ); // add facility health ID and verify notification - facilityManage.typeHrfId(facilityHrfId); - facilityManage.clickButtonWithText(facilityHrfidUpdateButton); + facilityManage.typeHfrId(facilityHfrId); + facilityManage.clickButtonWithText(facilityHfridUpdateButton); facilityManage.verifySuccessMessageVisibilityAndContent( - facilityHrfidSuccessfullNotification + facilityHfridToastNotificationText ); // update the existing middleware facilityPage.clickManageFacilityDropdown(); facilityManage.clickFacilityConfigureButton(); - facilityManage.typeHrfId(facilityUpdatedHrfId); - facilityManage.clickButtonWithText(facilityHrfidUpdateButton); + facilityManage.typeHfrId(facilityUpdatedHfrId); + facilityManage.clickButtonWithText(facilityHfridUpdateButton); facilityManage.verifySuccessMessageVisibilityAndContent( - facilityHrfidSuccessfullNotification + facilityHfridToastNotificationText ); // verify its reflection facilityPage.clickManageFacilityDropdown(); facilityManage.clickFacilityConfigureButton(); - facilityManage.verifyHrfIdValue(facilityUpdatedHrfId); + facilityManage.verifyHfrIdValue(facilityUpdatedHfrId); }); it("Modify doctor capacity in Facility detail page", () => { diff --git a/cypress/pageobject/Facility/FacilityManage.ts b/cypress/pageobject/Facility/FacilityManage.ts index 69cdaf669e2..df379a53012 100644 --- a/cypress/pageobject/Facility/FacilityManage.ts +++ b/cypress/pageobject/Facility/FacilityManage.ts @@ -63,19 +63,23 @@ class FacilityManage { cy.get("#middleware_address").click().clear().click().type(address); } - typeHrfId(address) { + clearHfrId() { + cy.get("#hf_id").click().clear(); + } + + typeHfrId(address) { cy.get("#hf_id").click().clear().click().type(address); } verifySuccessMessageVisibilityAndContent(text) { - cy.get(".pnotify-text").should("be.visible").and("contain", text); + cy.get(".pnotify-text").should("be.visible").contains(text); } verifyMiddlewareAddressValue(expectedValue) { cy.get("#middleware_address").should("have.value", expectedValue); } - verifyHrfIdValue(expectedValue) { + verifyHfrIdValue(expectedValue) { cy.get("#hf_id").should("have.value", expectedValue); } diff --git a/src/Components/ABDM/ConfigureHealthFacility.tsx b/src/Components/ABDM/ConfigureHealthFacility.tsx index bccf58e47c2..e64841464a1 100644 --- a/src/Components/ABDM/ConfigureHealthFacility.tsx +++ b/src/Components/ABDM/ConfigureHealthFacility.tsx @@ -75,53 +75,31 @@ export const ConfigureHealthFacility = (props: any) => { let response = null; let responseData = null; - if (state.form.health_facility) { + if (state.form.hf_id === state.form.health_facility?.hf_id) { const { res, data } = await request( - routes.abha.partialUpdateHealthFacility, + routes.abha.registerHealthFacilityAsService, { pathParams: { facility_id: facilityId, }, - body: { - hf_id: state.form.hf_id, - }, } ); response = res; responseData = data; - } else if (state.form.hf_id === state.form.health_facility?.hf_id) { + } else if (state.form.health_facility) { const { res, data } = await request( - routes.abha.registerHealthFacilityAsService, + routes.abha.partialUpdateHealthFacility, { pathParams: { facility_id: facilityId, }, + body: { + hf_id: state.form.hf_id, + }, } ); response = res; responseData = data; - - if (response?.status === 200 && responseData) { - if (responseData?.registered) { - dispatch({ - type: "set_form", - form: { - ...state.form, - health_facility: { - ...state.form?.health_facility, - registered: responseData.registered, - }, - }, - }); - - return; - } - } - - Notification.Error({ - msg: "Service registration failed, please try again later", - }); - return; } else { const { res, data } = await request(routes.abha.createHealthFacility, { body: { @@ -133,17 +111,22 @@ export const ConfigureHealthFacility = (props: any) => { responseData = data; } - setIsLoading(false); - if (response && responseData) { + if (response?.ok && responseData?.registered) { Notification.Success({ msg: "Health Facility config updated successfully", }); navigate(`/facility/${facilityId}`); } else { - if (responseData) + if (responseData?.registered === false) { + Notification.Warn({ + msg: responseData?.detail || "Health ID registration failed", + }); + navigate(`/facility/${facilityId}`); + } else { Notification.Error({ - msg: "Something went wrong: " + (responseData.detail || ""), + msg: responseData?.detail || "Health Facility config update failed", }); + } } setIsLoading(false); }; From 75685a1558d0d73d11f41577808c54728c166a65 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Mon, 12 Feb 2024 09:15:53 +0530 Subject: [PATCH 17/31] Fix bed check for log update button (#7202) * Fix bed check for log update button * Update username length in user creation test --- cypress/e2e/users_spec/user_creation.cy.ts | 2 +- src/Components/Patient/PatientInfoCard.tsx | 103 +++++++++------------ 2 files changed, 46 insertions(+), 59 deletions(-) diff --git a/cypress/e2e/users_spec/user_creation.cy.ts b/cypress/e2e/users_spec/user_creation.cy.ts index 72d2e7f15f4..b8e0dfe63eb 100644 --- a/cypress/e2e/users_spec/user_creation.cy.ts +++ b/cypress/e2e/users_spec/user_creation.cy.ts @@ -25,7 +25,7 @@ describe("User Creation", () => { } return result; }; - const username = makeid(25); + const username = makeid(8); const alreadylinkedusersviews = [ "devdoctor", "devstaff2", diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index c954438e6c2..67df8a2dd29 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -472,72 +472,59 @@ export default function PatientInfoCard(props: {
)}
- {[ - [ - `/facility/${patient.facility}/patient/${patient.id}/consultation/${consultation?.id}/daily-rounds`, - "Log Update", - "plus", - patient.is_active && - consultation?.id && - !consultation?.discharge_date, - [ - !(consultation?.facility !== patient.facility) && + {patient.is_active && + consultation?.id && + !consultation?.discharge_date && ( +
+ { + if ( + consultation?.admitted && + !consultation?.current_bed + ) { + Notification.Error({ + msg: "Please assign a bed to the patient", + }); + setOpen(true); + } + }} + className="w-full" + > + + +

Log Update

+
+
+ {!(consultation?.facility !== patient.facility) && !(consultation?.discharge_date ?? !patient.is_active) && dayjs(consultation?.modified_date).isBefore( dayjs().subtract(1, "day") - ), -
- No - update filed in the last 24 hours -
, - ], - ], - ].map( - (action: any, i) => - action[3] && ( -
- { - if ( - consultation?.admitted && - !consultation?.current_bed && - i === 1 - ) { - Notification.Error({ - msg: "Please assign a bed to the patient", - }); - setOpen(true); - } - }} - className="w-full" - > - - -

{action[1]}

-
-
- {action?.[4]?.[0] && ( + ) && ( <>

- {action[4][1]} +

+ {" "} + No update filed in the last 24 hours +

)} -
- ) - )} +
+ )} Date: Mon, 12 Feb 2024 10:01:58 +0530 Subject: [PATCH 18/31] Update username validation on useradd (#7134) * update username validation on useradd * update username length --- src/Common/validation.tsx | 2 +- src/Components/Users/UserAdd.tsx | 30 ++++++++++++++++++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Common/validation.tsx b/src/Common/validation.tsx index 54ad399c6e2..ef60a40bd79 100644 --- a/src/Common/validation.tsx +++ b/src/Common/validation.tsx @@ -43,7 +43,7 @@ export const validateName = (name: string) => { }; export const validateUsername = (username: string) => { - const pattern = /^[\w.@+-]+[^.@+_-]$/; + const pattern = /^(?!.*[._-]{2})[a-z0-9](?:[a-z0-9._-]{2,14}[a-z0-9])$/s; return pattern.test(username); }; diff --git a/src/Components/Users/UserAdd.tsx b/src/Components/Users/UserAdd.tsx index b3fc9b830f8..3f7fcb9d56a 100644 --- a/src/Components/Users/UserAdd.tsx +++ b/src/Components/Users/UserAdd.tsx @@ -211,11 +211,7 @@ export const UserAdd = (props: UserProps) => { useEffect(() => { setUsernameExists(userExistsEnums.idle); - if ( - usernameInput.length > 1 && - !(state.form.username?.length < 2) && - /[^.@+_-]/.test(state.form.username[state.form.username?.length - 1]) - ) { + if (validateUsername(usernameInput)) { const timeout = setTimeout(() => { check_username(usernameInput); }, 500); @@ -403,7 +399,7 @@ export const UserAdd = (props: UserProps) => { invalidForm = true; } else if (!validateUsername(state.form[field])) { errors[field] = - "Please enter letters, digits and @ . + - _ only and username should not end with @, ., +, - or _"; + "Please enter a 4-16 characters long username with lowercase letters, digits and . _ - only and it should not start or end with . _ -"; invalidForm = true; } else if (usernameExists !== userExistsEnums.available) { errors[field] = "This username already exists"; @@ -757,16 +753,26 @@ export const UserAdd = (props: UserProps) => {
{validateRule( - state.form.username?.length >= 2, - "Username should be atleast 2 characters long" + usernameInput.length >= 4 && usernameInput.length <= 16, + "Username should be 4-16 characters long" )}
{validateRule( - /[^.@+_-]/.test( - state.form.username[state.form.username?.length - 1] - ), - "Username can't end with ^ . @ + _ -" + /^[a-z0-9._-]*$/.test(usernameInput), + "Username can only contain lowercase letters, numbers, and . _ -" + )} +
+
+ {validateRule( + /^[a-z0-9].*[a-z0-9]$/i.test(usernameInput), + "Username must start and end with a letter or number" + )} +
+
+ {validateRule( + !/(?:[._-]{2,})/.test(usernameInput), + "Username can't contain consecutive special characters . _ -" )}
From fcc276aa70f9abb42fd7d21bb3e5da1437a77eb4 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:42:33 +0530 Subject: [PATCH 19/31] Fix update log filters on page change (#7204) * Fix update log filters on page change * Clear unnecessary refetch --- .../Facility/Consultations/DailyRoundsList.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/Components/Facility/Consultations/DailyRoundsList.tsx b/src/Components/Facility/Consultations/DailyRoundsList.tsx index 828a41c8c24..d438ee098c7 100644 --- a/src/Components/Facility/Consultations/DailyRoundsList.tsx +++ b/src/Components/Facility/Consultations/DailyRoundsList.tsx @@ -11,6 +11,8 @@ import DailyRoundsFilter from "./DailyRoundsFilter"; import { ConsultationModel } from "../models"; import { useSlugs } from "../../../Common/hooks/useSlug"; import { TimelineNode } from "../../../CAREUI/display/Timeline"; +import { useState } from "react"; +import { QueryParams } from "../../../Utils/request/types"; interface Props { consultation: ConsultationModel; @@ -19,6 +21,7 @@ interface Props { export default function DailyRoundsList({ consultation }: Props) { const [consultationId] = useSlugs("consultation"); const { t } = useTranslation(); + const [query, setQuery] = useState(); const consultationUrl = `/facility/${consultation.facility}/patient/${consultation.patient}/consultation/${consultation.id}`; @@ -26,12 +29,17 @@ export default function DailyRoundsList({ consultation }: Props) { - {({ refetch }) => ( + {() => ( <>
- refetch({ query })} /> + { + setQuery(query); + }} + />
From 2a7c2615103529f28c702b1afcfacdfe7d51e875 Mon Sep 17 00:00:00 2001 From: Pranshu Aggarwal <70687348+Pranshu1902@users.noreply.github.com> Date: Mon, 12 Feb 2024 12:22:34 +0530 Subject: [PATCH 20/31] Show Asset Downtime on assets lists page (#6952) * Show Asset Downtime on assets lists page * fetch downtime from asset api * update icon * update to new api variable --- src/Components/Assets/AssetTypes.tsx | 1 + src/Components/Assets/AssetsList.tsx | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/Components/Assets/AssetTypes.tsx b/src/Components/Assets/AssetTypes.tsx index dc70d246e0b..6186f3c4ee9 100644 --- a/src/Components/Assets/AssetTypes.tsx +++ b/src/Components/Assets/AssetTypes.tsx @@ -99,6 +99,7 @@ export interface AssetData { manufacturer: string; warranty_amc_end_of_validity: string; resolved_middleware?: ResolvedMiddleware; + latest_status: string; last_service: AssetService; meta?: { [key: string]: any; diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index bc0abbd4c2a..946618fd970 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -240,6 +240,13 @@ const AssetsList = () => { )} {warrantyAmcValidityChip(asset.warranty_amc_end_of_validity)} + {asset?.latest_status === "Down" && ( + + )}{" "}
From d63ef9f4a8fe77ac6933c1741b72fc8c5c397a5b Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Mon, 12 Feb 2024 16:31:37 +0530 Subject: [PATCH 21/31] Add min_encounter_date validation (#7207) * Add min_encounter_date validation * Add check for minimum encounter date in ConsultationForm --- public/config.json | 3 ++- src/Common/hooks/useConfig.ts | 5 +++++ src/Components/Facility/ConsultationForm.tsx | 16 ++++++++++++++-- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/public/config.json b/public/config.json index 69d898af544..d02bd135cfe 100644 --- a/public/config.json +++ b/public/config.json @@ -22,5 +22,6 @@ "sample_format_asset_import": "https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=11JaEhNHdyCHth4YQs_44YaRlP77Rrqe81VSEfg1glko&exportFormat=xlsx", "sample_format_external_result_import": "/External-Results-Template.csv", "enable_abdm": true, - "enable_hcx": false + "enable_hcx": false, + "min_encounter_date": "2020-01-01" } diff --git a/src/Common/hooks/useConfig.ts b/src/Common/hooks/useConfig.ts index 79addd71c8b..4e1cb806e3f 100644 --- a/src/Common/hooks/useConfig.ts +++ b/src/Common/hooks/useConfig.ts @@ -69,6 +69,11 @@ export interface IConfig { */ wartime_shifting: boolean; jwt_token_refresh_interval?: number; + + /* + * Minimum date for a possible consultation encounter. + */ + min_encounter_date: string; } const useConfig = () => { diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index d7bc5f67a70..344e24c7c31 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -263,6 +263,8 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { const [bedStatusVisible, bedStatusRef] = useVisibility(-300); const [disabledFields, setDisabledFields] = useState([]); + const { min_encounter_date } = useConfig(); + const sections = { "Consultation Details": { iconClass: "care-l-medkit", @@ -504,8 +506,13 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { errors[field] = "Field is required"; invalidForm = true; } - if (dayjs(state.form.encounter_date).isBefore(dayjs("2000-01-01"))) { - errors[field] = "Admission date cannot be before 01/01/2000"; + if ( + min_encounter_date && + dayjs(state.form.encounter_date).isBefore(dayjs(min_encounter_date)) + ) { + errors[ + field + ] = `Admission date cannot be before ${min_encounter_date}`; invalidForm = true; } return; @@ -1238,6 +1245,11 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { "YYYY-MM-DDTHH:mm" )} max={dayjs().format("YYYY-MM-DDTHH:mm")} + min={ + min_encounter_date + ? dayjs(min_encounter_date).format("YYYY-MM-DDTHH:mm") + : undefined + } />
From 000138b0cf322a1f8eb5887253081769e38c787b Mon Sep 17 00:00:00 2001 From: konavivekramakrishna Date: Wed, 14 Feb 2024 10:15:30 +0530 Subject: [PATCH 22/31] Add Search Box to Resource (#7199) * add search for resource * fix styling --- src/Components/Resource/BadgesList.tsx | 1 + src/Components/Resource/Commons.tsx | 2 + src/Components/Resource/ListView.tsx | 81 ++++++++++++------- src/Components/Resource/ResourceBoard.tsx | 3 +- src/Components/Resource/ResourceBoardView.tsx | 17 +++- src/Locale/en/Resource.json | 3 +- 6 files changed, 70 insertions(+), 37 deletions(-) diff --git a/src/Components/Resource/BadgesList.tsx b/src/Components/Resource/BadgesList.tsx index 4ab4f3cc8e4..9fa1859fdd4 100644 --- a/src/Components/Resource/BadgesList.tsx +++ b/src/Components/Resource/BadgesList.tsx @@ -31,6 +31,7 @@ export default function BadgesList(props: any) { getDescShiftingFilterOrder(appliedFilters.ordering) ), badge("Status", "status"), + badge("Title", "title"), boolean("Emergency", "emergency", { trueValue: "yes", falseValue: "no", diff --git a/src/Components/Resource/Commons.tsx b/src/Components/Resource/Commons.tsx index 0162f34d02c..9ba522f763b 100644 --- a/src/Components/Resource/Commons.tsx +++ b/src/Components/Resource/Commons.tsx @@ -12,6 +12,7 @@ export const initialFilterData = { modified_date_after: null, offset: 0, ordering: null, + title: "", }; export const formatFilter = (params: any) => { @@ -35,5 +36,6 @@ export const formatFilter = (params: any) => { modified_date_before: filter.modified_date_before || undefined, modified_date_after: filter.modified_date_after || undefined, ordering: filter.ordering || undefined, + title: filter.title || undefined, }; }; diff --git a/src/Components/Resource/ListView.tsx b/src/Components/Resource/ListView.tsx index 5eb5a93fb8f..05b2ce2f4bb 100644 --- a/src/Components/Resource/ListView.tsx +++ b/src/Components/Resource/ListView.tsx @@ -14,13 +14,21 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import dayjs from "../../Utils/dayjs"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; +import Page from "../Common/components/Page"; +import SearchInput from "../Form/SearchInput"; const Loading = lazy(() => import("../Common/Loading")); -const PageTitle = lazy(() => import("../Common/PageTitle")); export default function ListView() { - const { qParams, Pagination, FilterBadges, advancedFilter, resultsPerPage } = - useFilters({}); + const { + qParams, + Pagination, + FilterBadges, + advancedFilter, + resultsPerPage, + updateQuery, + } = useFilters({ cacheBlacklist: ["title"] }); + const { t } = useTranslation(); const onBoardViewBtnClick = () => @@ -148,33 +156,42 @@ export default function ListView() { }; return ( -
-
- - downloadResourceRequests({ ...appliedFilters, csv: 1 }) - } - filenamePrefix="resource_requests" - /> - } - breadcrumbs={false} + downloadResourceRequests({ ...appliedFilters, csv: 1 })} + filenamePrefix="resource_requests" /> + } + breadcrumbs={false} + options={ + <> +
+ updateQuery({ [e.name]: e.value })} + placeholder={t("search_resource")} + /> +
+
+ {/* dummy div to align space as per board view */} +
+
+ + + {t("board_view")} + -
-
- - - {t("board_view")} - - - advancedFilter.setShow(true)} /> -
-
- + advancedFilter.setShow(true)} + /> +
+ + } + >
@@ -188,14 +205,16 @@ export default function ListView() { onClick={() => refetch()} > - Refresh List + {t("refresh_list")}
{data?.results && showResourceCardList(data?.results)}
- +
+ +
)}
@@ -204,6 +223,6 @@ export default function ListView() { showResourceStatus={true} key={window.location.search} /> -
+ ); } diff --git a/src/Components/Resource/ResourceBoard.tsx b/src/Components/Resource/ResourceBoard.tsx index d57c7a0d36f..0f16391b9ab 100644 --- a/src/Components/Resource/ResourceBoard.tsx +++ b/src/Components/Resource/ResourceBoard.tsx @@ -171,6 +171,7 @@ export default function ResourceBoard({ setIsLoading((loading) => reduceLoading("BOARD", loading)); }, [ board, + filterProp.title, filterProp.facility, filterProp.origin_facility, filterProp.approving_facility, @@ -231,7 +232,7 @@ export default function ResourceBoard({
diff --git a/src/Components/Resource/ResourceBoardView.tsx b/src/Components/Resource/ResourceBoardView.tsx index 17fb70c662f..558cd77c1f0 100644 --- a/src/Components/Resource/ResourceBoardView.tsx +++ b/src/Components/Resource/ResourceBoardView.tsx @@ -14,6 +14,7 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { useTranslation } from "react-i18next"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import SearchInput from "../Form/SearchInput"; const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); @@ -24,7 +25,10 @@ const COMPLETED = ["COMPLETED", "REJECTED"]; const ACTIVE = resourceStatusOptions.filter((o) => !COMPLETED.includes(o)); export default function BoardView() { - const { qParams, FilterBadges, advancedFilter } = useFilters({ limit: -1 }); + const { qParams, FilterBadges, advancedFilter, updateQuery } = useFilters({ + limit: -1, + cacheBlacklist: ["title"], + }); const [boardFilter, setBoardFilter] = useState(ACTIVE); // eslint-disable-next-line const [isLoading, setIsLoading] = useState(false); @@ -37,7 +41,7 @@ export default function BoardView() { }; return ( -
+
-
-
+
+ updateQuery({ [e.name]: e.value })} + placeholder={t("search_resource")} + /> Date: Wed, 14 Feb 2024 10:16:14 +0530 Subject: [PATCH 23/31] fix: hide the diagonses card if it's empty (#7196) * fix(consultation): fixes #7187 hide diagnoses card if empty * fix(consultation): resolved suggestion * Update src/Components/Facility/ConsultationDetails/index.tsx --------- Co-authored-by: Rithvik Nishad --- .../Facility/ConsultationDetails/index.tsx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 071e7bfc1af..808a2c90de9 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -401,13 +401,15 @@ export const ConsultationDetails = (props: any) => {
-
-
- + {!!consultationData.diagnoses?.length && ( +
+
+ +
-
+ )}
From 77fa4de78c1dc00a8256b007af258a7a356d9b5e Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:16:41 +0530 Subject: [PATCH 24/31] Add facility name to ReportTable component (#7190) * Add facility name to ReportTable component * Refactor ReportTable component to remove facilityName prop --- .../Facility/Investigations/Reports/ReportTable.tsx | 12 ++++++++++-- .../Facility/Investigations/Reports/types.ts | 5 ++++- .../Facility/Investigations/Reports/utils.tsx | 10 +++++++--- src/Components/Patient/PatientInfoCard.tsx | 2 +- 4 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/Components/Facility/Investigations/Reports/ReportTable.tsx b/src/Components/Facility/Investigations/Reports/ReportTable.tsx index 1f20ec94180..c15eec4e33b 100644 --- a/src/Components/Facility/Investigations/Reports/ReportTable.tsx +++ b/src/Components/Facility/Investigations/Reports/ReportTable.tsx @@ -124,9 +124,17 @@ const ReportTable: FC = ({ - {formatDateTime(session.session_created_date)} +
+ {formatDateTime(session.session_created_date)} + + {session.facility_name} + +
))} { const sessions = _.chain(data) - .map((value) => value.session_object) + .map((value) => { + return { + ...value.session_object, + facility_name: value.consultation_object?.facility_name, + facility_id: value.consultation_object?.facility, + }; + }) .uniqBy("session_external_id") .orderBy("session_created_date", "desc") .value(); @@ -28,7 +34,6 @@ export const transformData = _.memoize((data: InvestigationResponse) => { } }); const { - consultation, group, group_object, id, @@ -40,7 +45,6 @@ export const transformData = _.memoize((data: InvestigationResponse) => { } = value[0]; return { - consultation, group, group_object, id, diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 67df8a2dd29..25683e1a461 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -411,7 +411,7 @@ export default function PatientInfoCard(props: { Principal Diagnosis:
- {principal_diagnosis.diagnosis_object.label}{" "} + {principal_diagnosis.diagnosis_object?.label ?? "-"}{" "}

From 7956c90dd4862a50071b610bc85f15e2c7cbe34a Mon Sep 17 00:00:00 2001 From: Shyam Prakash <106866225+shyamprakash123@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:17:25 +0530 Subject: [PATCH 25/31] Hide Asset Type (#7180) * update * Revert "update" This reverts commit 137539925bfd73e9163e39eb5a90760bf6b24c69. * hideAssetType * update * Revert "update" This reverts commit 137539925bfd73e9163e39eb5a90760bf6b24c69. --------- Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- cypress/e2e/assets_spec/asset_homepage.cy.ts | 2 - cypress/e2e/assets_spec/assets_creation.cy.ts | 4 - cypress/e2e/sample_test_spec/filter.cy.ts | 7 -- cypress/pageobject/Asset/AssetCreation.ts | 15 --- cypress/pageobject/Asset/AssetFilters.ts | 9 -- src/Components/Assets/AssetFilter.tsx | 16 --- src/Components/Assets/AssetManage.tsx | 5 - src/Components/Assets/AssetsList.tsx | 7 -- src/Components/Facility/AssetCreate.tsx | 101 +++++------------- 9 files changed, 28 insertions(+), 138 deletions(-) diff --git a/cypress/e2e/assets_spec/asset_homepage.cy.ts b/cypress/e2e/assets_spec/asset_homepage.cy.ts index 388f19424a4..5710df08e83 100644 --- a/cypress/e2e/assets_spec/asset_homepage.cy.ts +++ b/cypress/e2e/assets_spec/asset_homepage.cy.ts @@ -63,7 +63,6 @@ describe("Asset Tab", () => { it("Filter Asset", () => { assetFilters.filterAssets( "Dummy Facility 40", - "INTERNAL", "ACTIVE", "ONVIF Camera", "Camera Loc" @@ -71,7 +70,6 @@ describe("Asset Tab", () => { assetFilters.clickadvancefilter(); assetFilters.clickslideoverbackbutton(); // to verify the back button doesn't clear applied filters assetFilters.assertFacilityText("Dummy Facility 40"); - assetFilters.assertAssetTypeText("INTERNAL"); assetFilters.assertAssetClassText("ONVIF"); assetFilters.assertStatusText("ACTIVE"); assetFilters.assertLocationText("Camera Loc"); diff --git a/cypress/e2e/assets_spec/assets_creation.cy.ts b/cypress/e2e/assets_spec/assets_creation.cy.ts index 63f8bee50c8..f8edc2bb172 100644 --- a/cypress/e2e/assets_spec/assets_creation.cy.ts +++ b/cypress/e2e/assets_spec/assets_creation.cy.ts @@ -29,7 +29,6 @@ describe("Asset", () => { assetPage.clickCreateAsset(); assetPage.verifyEmptyAssetNameError(); - assetPage.verifyEmptyAssetTypeError(); assetPage.verifyEmptyLocationError(); assetPage.verifyEmptyStatusError(); assetPage.verifyEmptyPhoneError(); @@ -41,7 +40,6 @@ describe("Asset", () => { assetPage.createAsset(); assetPage.selectFacility("Dummy Facility 40"); assetPage.selectLocation("Camera Loc"); - assetPage.selectAssetType("Internal"); assetPage.selectAssetClass("ONVIF Camera"); const qr_id_1 = uuidv4(); @@ -68,7 +66,6 @@ describe("Asset", () => { const qr_id_2 = uuidv4(); assetPage.selectLocation("Camera Loc"); - assetPage.selectAssetType("Internal"); assetPage.selectAssetClass("ONVIF Camera"); assetPage.enterAssetDetails( "New Test Asset 2", @@ -141,7 +138,6 @@ describe("Asset", () => { assetPage.createAsset(); assetPage.selectFacility("Dummy Facility 40"); assetPage.selectLocation("Camera Loc"); - assetPage.selectAssetType("Internal"); assetPage.selectAssetClass("HL7 Vitals Monitor"); const qr_id_1 = uuidv4(); diff --git a/cypress/e2e/sample_test_spec/filter.cy.ts b/cypress/e2e/sample_test_spec/filter.cy.ts index a015d1ba7c5..df934c641bb 100644 --- a/cypress/e2e/sample_test_spec/filter.cy.ts +++ b/cypress/e2e/sample_test_spec/filter.cy.ts @@ -20,13 +20,6 @@ describe("Sample Filter", () => { .click(); }); - it("Filter by Asset Type", () => { - cy.get("#result").click(); - cy.get("li[role='option']") - .contains(/^POSITIVE$/) - .click(); - }); - it("Filter by sample type", () => { cy.get("#sample_type").click(); cy.get("li[role='option']") diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index 6932b5ed15e..8f611e97d92 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -24,14 +24,6 @@ export class AssetPage { }); } - selectAssetType(assetType: string) { - cy.get("[data-testid=asset-type-input] button") - .click() - .then(() => { - cy.get("[role='option']").contains(assetType).click(); - }); - } - selectAssetClass(assetClass: string) { cy.get("[data-testid=asset-class-input] button") .click() @@ -205,13 +197,6 @@ export class AssetPage { ); } - verifyEmptyAssetTypeError() { - cy.get("[data-testid=asset-type-input] span").should( - "contain", - "Select an asset type" - ); - } - verifyEmptyStatusError() { cy.get("[data-testid=asset-working-status-input] span").should( "contain", diff --git a/cypress/pageobject/Asset/AssetFilters.ts b/cypress/pageobject/Asset/AssetFilters.ts index 5ded59f4f63..33363f2d161 100644 --- a/cypress/pageobject/Asset/AssetFilters.ts +++ b/cypress/pageobject/Asset/AssetFilters.ts @@ -1,7 +1,6 @@ export class AssetFilters { filterAssets( facilityName: string, - assetType: string, assetStatus: string, assetClass: string, assetLocation: string @@ -13,11 +12,6 @@ export class AssetFilters { .then(() => { cy.get("[role='option']").contains(facilityName).click(); }); - cy.get("#asset-type") - .click() - .then(() => { - cy.get("[role='option']").contains(assetType).click(); - }); cy.get("#asset-status") .click() .then(() => { @@ -65,9 +59,6 @@ export class AssetFilters { assertFacilityText(text) { cy.get("[data-testid=Facility]").should("contain", text); } - assertAssetTypeText(text) { - cy.get("[data-testid='Asset Type']").should("contain", text); - } assertAssetClassText(text) { cy.get("[data-testid='Asset Class']").should("contain", text); } diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx index 3edde4ab0cd..1fca1475269 100644 --- a/src/Components/Assets/AssetFilter.tsx +++ b/src/Components/Assets/AssetFilter.tsx @@ -21,9 +21,6 @@ const getDate = (value: any) => function AssetFilter(props: any) { const { filter, onChange, closeFilter, removeFilters } = props; const [facility, setFacility] = useState(null); - const [asset_type, setAssetType] = useState( - filter.asset_type ? filter.asset_type : "" - ); const [asset_status, setAssetStatus] = useState(filter.status || ""); const [asset_class, setAssetClass] = useState( filter.asset_class || "" @@ -61,7 +58,6 @@ function AssetFilter(props: any) { const applyFilter = () => { const data = { facility: facilityId, - asset_type: asset_type ?? "", asset_class: asset_class ?? "", status: asset_status ?? "", location: locationId ?? "", @@ -125,18 +121,6 @@ function AssetFilter(props: any) {

)} - o} - optionValue={(o) => o} - value={asset_type} - onChange={({ value }) => setAssetType(value)} - /> - { {asset?.description}
- {asset?.asset_type === "INTERNAL" ? ( - - ) : ( - - )} {asset?.status === "ACTIVE" ? ( ) : ( diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index 946618fd970..f210bf04535 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -45,7 +45,6 @@ const AssetsList = () => { const [isScannerActive, setIsScannerActive] = useState(false); const [totalCount, setTotalCount] = useState(0); const [facility, setFacility] = useState(); - const [asset_type, setAssetType] = useState(); const [status, setStatus] = useState(); const [asset_class, setAssetClass] = useState(); const [importAssetModalOpen, setImportAssetModalOpen] = useState(false); @@ -60,7 +59,6 @@ const AssetsList = () => { offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, search_text: qParams.search || "", facility: qParams.facility || "", - asset_type: qParams.asset_type || "", asset_class: qParams.asset_class || "", location: qParams.facility ? qParams.location || "" : "", status: qParams.status || "", @@ -91,10 +89,6 @@ const AssetsList = () => { prefetch: !!qParams.facility, }); - useEffect(() => { - setAssetType(qParams.asset_type); - }, [qParams.asset_type]); - useEffect(() => { setStatus(qParams.status); }, [qParams.status]); @@ -387,7 +381,6 @@ const AssetsList = () => { qParams.facility && facilityObject?.name ), badge("Name/Serial No./QR ID", "search"), - value("Asset Type", "asset_type", asset_type ?? ""), value("Asset Class", "asset_class", asset_class ?? ""), value("Status", "status", status?.replace(/_/g, " ") ?? ""), value( diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx index 784174cd2f7..0c422d1f646 100644 --- a/src/Components/Facility/AssetCreate.tsx +++ b/src/Components/Facility/AssetCreate.tsx @@ -40,7 +40,6 @@ const Loading = lazy(() => import("../Common/Loading")); const formErrorKeys = [ "name", - "asset_type", "asset_class", "description", "is_working", @@ -103,12 +102,10 @@ const AssetCreate = (props: AssetProps) => { const { goBack } = useAppHistory(); const { facilityId, assetId } = props; - let assetTypeInitial: AssetType; let assetClassInitial: AssetClass; const [state, dispatch] = useReducer(asset_create_reducer, initialState); const [name, setName] = useState(""); - const [asset_type, setAssetType] = useState(); const [asset_class, setAssetClass] = useState(); const [not_working_reason, setNotWorkingReason] = useState(""); const [description, setDescription] = useState(""); @@ -177,7 +174,6 @@ const AssetCreate = (props: AssetProps) => { setName(asset.name); setDescription(asset.description); setLocation(asset.location_object.id!); - setAssetType(asset.asset_type); setAssetClass(asset.asset_class); setIsWorking(String(asset.is_working)); setNotWorkingReason(asset.not_working_reason); @@ -219,12 +215,6 @@ const AssetCreate = (props: AssetProps) => { invalidForm = true; } return; - case "asset_type": - if (!asset_type || asset_type == "NONE") { - errors[field] = "Select an asset type"; - invalidForm = true; - } - return; case "support_phone": { if (!support_phone) { errors[field] = "Field is required"; @@ -282,7 +272,6 @@ const AssetCreate = (props: AssetProps) => { setName(""); setDescription(""); setLocation(""); - setAssetType(assetTypeInitial); setAssetClass(assetClassInitial); setIsWorking(undefined); setNotWorkingReason(""); @@ -307,7 +296,7 @@ const AssetCreate = (props: AssetProps) => { setIsLoading(true); const data: any = { name: name, - asset_type: asset_type, + asset_type: AssetType.INTERNAL, asset_class: asset_class || "", description: description, is_working: is_working, @@ -547,68 +536,34 @@ const AssetCreate = (props: AssetProps) => { errors={state.errors.location} />
- {/* Asset Type */} -
-
- title} - optionDescription={({ description }) => description} - optionValue={({ value }) => value} - onChange={({ value }) => setAssetType(value)} - error={state.errors.asset_type} - /> -
- {/* Asset Class */} -
- title} - optionValue={({ value }) => value} - onChange={({ value }) => setAssetClass(value)} - error={state.errors.asset_class} - /> -
+ {/* Asset Class */} +
+ title} + optionValue={({ value }) => value} + onChange={({ value }) => setAssetClass(value)} + error={state.errors.asset_class} + />
{/* Description */}
Date: Wed, 14 Feb 2024 10:17:54 +0530 Subject: [PATCH 26/31] Increase the Add button height in skills slideover. (#7163) * Fixed btn height * updated height, width, font-size according to the requested changes. * update * Revert "update" This reverts commit 137539925bfd73e9163e39eb5a90760bf6b24c69. --- src/Components/Users/ManageUsers.tsx | 2 +- src/Components/Users/SkillsSlideOver.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx index 6f5de125600..15f4ba4ed81 100644 --- a/src/Components/Users/ManageUsers.tsx +++ b/src/Components/Users/ManageUsers.tsx @@ -671,7 +671,7 @@ function UserFacilities(props: { user: any }) { addFacility(username, facility)} > Add diff --git a/src/Components/Users/SkillsSlideOver.tsx b/src/Components/Users/SkillsSlideOver.tsx index 63353479f0d..616dbb6404d 100644 --- a/src/Components/Users/SkillsSlideOver.tsx +++ b/src/Components/Users/SkillsSlideOver.tsx @@ -125,7 +125,7 @@ export default ({ show, setShow, username }: IProps) => { id="add-skill-button" disabled={!authorizeForAddSkill} onClick={() => addSkill(username, selectedSkill)} - className="w-6rem" + className="mt-1 h-[45px] w-[74px] text-base" > {t("add")} From bba3b5c7d2172ff01cc481d8f75d18df6403f12d Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 14 Feb 2024 10:18:25 +0530 Subject: [PATCH 27/31] Set Form Drafts to expire after 24 hours (#7125) --- src/Utils/AutoSave.tsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/Utils/AutoSave.tsx b/src/Utils/AutoSave.tsx index 5cdf118650f..0c929f371e5 100644 --- a/src/Utils/AutoSave.tsx +++ b/src/Utils/AutoSave.tsx @@ -104,6 +104,23 @@ export function DraftSection(props: { }; }, []); + // Remove drafts older than 24 hours + useEffect(() => { + const keys = Object.keys(localStorage); + const now = Date.now(); + keys.forEach((key) => { + if (key.startsWith("form_draft_")) { + const savedDrafts = localStorage.getItem(key); + const drafts = savedDrafts ? JSON.parse(savedDrafts) : []; + const newDrafts = drafts.filter( + (draft: Draft) => now - draft.timestamp < 24 * 60 * 60 * 1000 + ); + localStorage.setItem(key, JSON.stringify(newDrafts)); + if (newDrafts.length === 0) localStorage.removeItem(key); + } + }); + }, []); + return ( <> {drafts && ( From 2cc66f08e2f49d9dadf21fd038e724e9bfadbd5d Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:51:53 +0530 Subject: [PATCH 28/31] Update Uptime endpoint for AssetUptime (#7210) * Track uptime for Locations * Refactor Uptime component to accept route and params * Remove unused import in api.tsx --- src/Components/Assets/AssetManage.tsx | 7 ++++- src/Components/Assets/AssetTypes.tsx | 11 ++----- src/Components/Common/Uptime.tsx | 41 ++++++++++++++------------- src/Redux/api.tsx | 25 +++++++--------- 4 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 0da432eccb6..77bb88da3a8 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -498,7 +498,12 @@ const AssetManage = (props: AssetManageProps) => {
{asset?.id && asset?.asset_class && - asset?.asset_class != AssetClass.NONE && } + asset?.asset_class != AssetClass.NONE && ( + + )}
Service History
>; + params?: Record; + }> +) { const [summary, setSummary] = useState<{ - [key: number]: AssetUptimeRecord[]; + [key: number]: AvailabilityRecord[]; }>([]); - const { data, loading } = useQuery(routes.listAssetAvailability, { - query: { external_id: props.assetId }, + const { data, loading } = useQuery(props.route, { + pathParams: props.params, onResponse: ({ data }) => setUptimeRecord(data?.results.reverse() ?? []), }); const availabilityData = data?.results ?? []; @@ -186,8 +191,8 @@ export default function Uptime(props: { assetId: string }) { setNumDays(Math.min(newNumDays, 100)); }; - const setUptimeRecord = (records: AssetUptimeRecord[]): void => { - const recordsByDayBefore: { [key: number]: AssetUptimeRecord[] } = {}; + const setUptimeRecord = (records: AvailabilityRecord[]): void => { + const recordsByDayBefore: { [key: number]: AvailabilityRecord[] } = {}; records.forEach((record) => { const timestamp = dayjs(record.timestamp).startOf("day"); @@ -207,10 +212,8 @@ export default function Uptime(props: { assetId: string }) { recordsByDayBefore[i] = []; if (statusToCarryOver) { recordsByDayBefore[i].push({ - id: "", - asset: { id: "", name: "" }, - created_date: "", - modified_date: "", + linked_id: "", + linked_model: "", status: statusToCarryOver, timestamp: dayjs() .subtract(i, "days") @@ -225,10 +228,8 @@ export default function Uptime(props: { assetId: string }) { ).length === 0 ) { recordsByDayBefore[i].unshift({ - id: "", - asset: { id: "", name: "" }, - created_date: "", - modified_date: "", + linked_id: "", + linked_model: "", status: statusToCarryOver, timestamp: dayjs() .subtract(i, "days") @@ -284,7 +285,7 @@ export default function Uptime(props: { assetId: string }) { const statusColors: (typeof STATUS_COLORS)[keyof typeof STATUS_COLORS][] = []; let dayUptimeScore = 0; - const recordsInPeriodCache: { [key: number]: AssetUptimeRecord[] } = {}; + const recordsInPeriodCache: { [key: number]: AvailabilityRecord[] } = {}; for (let i = 0; i < 3; i++) { const start = i * 8; const end = (i + 1) * 8; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index eed17ad3b4b..40c842adc07 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -27,7 +27,7 @@ import { AssetService, AssetServiceUpdate, AssetTransaction, - AssetUptimeRecord, + AvailabilityRecord, PatientAssetBed, } from "../Components/Assets/AssetTypes"; import { @@ -371,6 +371,11 @@ const routes = { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", method: "PATCH", }, + getFacilityAssetLocationAvailability: { + path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/availability/", + method: "GET", + TRes: Type>(), + }, // Asset bed listAssetBeds: { @@ -1116,6 +1121,11 @@ const routes = { TRes: Type(), TBody: Type>(), }, + listAssetAvailability: { + path: "/api/v1/asset/{external_id}/availability/", + method: "GET", + TRes: Type>(), + }, // Asset transaction endpoints @@ -1290,19 +1300,6 @@ const routes = { }, }, - // Asset Availability endpoints - - listAssetAvailability: { - path: "/api/v1/asset_availability/", - method: "GET", - TRes: Type>(), - }, - getAssetAvailability: { - path: "/api/v1/asset_availability/{id}", - method: "GET", - TRes: Type(), - }, - // Prescription endpoints listPrescriptions: { From 5e0727d88f2801c059d051a80d9ed25f124f6606 Mon Sep 17 00:00:00 2001 From: Kshitij Verma <101321276+kshitijv256@users.noreply.github.com> Date: Wed, 14 Feb 2024 12:54:23 +0530 Subject: [PATCH 29/31] Added support for multi-line text input for Doctor Notes (#6977) * Changed TextFormField to TextAreaFormField * made doctor notes input size dynamic * fixed max height of textareafield * made max height variable * replaced ids with ref * removed id prop * added required comments * added required comments * lineheight computed dynamically --- .../ConsultationDoctorNotes/index.tsx | 8 +-- .../Facility/PatientNotesSlideover.tsx | 7 +-- .../AutoExpandingTextInputFormField.tsx | 30 +++++++++++ .../Form/FormFields/TextAreaFormField.tsx | 53 +++++++++++-------- 4 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx diff --git a/src/Components/Facility/ConsultationDoctorNotes/index.tsx b/src/Components/Facility/ConsultationDoctorNotes/index.tsx index 8e39ee04e4e..c17ee3fb2db 100644 --- a/src/Components/Facility/ConsultationDoctorNotes/index.tsx +++ b/src/Components/Facility/ConsultationDoctorNotes/index.tsx @@ -1,7 +1,6 @@ import { useState } from "react"; import * as Notification from "../../../Utils/Notifications.js"; import Page from "../../Common/components/Page"; -import TextFormField from "../../Form/FormFields/TextFormField"; import ButtonV2 from "../../Common/components/ButtonV2"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import { NonReadOnlyUsers } from "../../../Utils/AuthorizeFor"; @@ -13,6 +12,7 @@ import request from "../../../Utils/request/request.js"; import useQuery from "../../../Utils/request/useQuery.js"; import useKeyboardShortcut from "use-keyboard-shortcut"; import { isAppleDevice } from "../../../Utils/utils.js"; +import AutoExpandingTextInputFormField from "../../Form/FormFields/AutoExpandingTextInputFormField.js"; interface ConsultationDoctorNotesProps { patientId: string; @@ -120,12 +120,14 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { />
- setNoteField(e.value)} className="grow" - type="text" errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index 5ecf30cfcdf..2e231576eb2 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -3,7 +3,6 @@ import * as Notification from "../../Utils/Notifications.js"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { classNames, isAppleDevice } from "../../Utils/utils"; -import TextFormField from "../Form/FormFields/TextFormField"; import ButtonV2 from "../Common/components/ButtonV2"; import { make as Link } from "../Common/components/Link.bs"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; @@ -12,6 +11,7 @@ import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import { PatientNoteStateType } from "./models"; import useKeyboardShortcut from "use-keyboard-shortcut"; +import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; interface PatientNotesProps { patientId: string; @@ -169,13 +169,14 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setReload={setReload} />
- setNoteField(e.value)} className="grow" - type="text" errorClassName="hidden" placeholder="Type your Note" disabled={!patientActive} diff --git a/src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx b/src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx new file mode 100644 index 00000000000..22707f133bc --- /dev/null +++ b/src/Components/Form/FormFields/AutoExpandingTextInputFormField.tsx @@ -0,0 +1,30 @@ +import React, { useEffect, useRef } from "react"; +import TextAreaFormField, { TextAreaFormFieldProps } from "./TextAreaFormField"; + +type AutoExpandingTextInputFormFieldProps = TextAreaFormFieldProps & { + maxHeight?: number; +}; + +const AutoExpandingTextInputFormField = ( + props: AutoExpandingTextInputFormFieldProps +) => { + const myref = useRef(null); + useEffect(() => { + if (myref.current == null) return; + const text = myref.current.textContent?.split("\n"); + const len = text?.length || 1; + // 46 is height of the textarea when there is only 1 line + // getting line height from window + const lineHeight = + window.getComputedStyle(myref.current).lineHeight.slice(0, -2) || "20"; + // added 26 for padding (20+26 = 46) + const height = + Math.min(len * parseInt(lineHeight), (props.maxHeight || 160) - 26) + 26; + // 160 is the max height of the textarea if not specified + myref.current.style.cssText = "height:" + height + "px"; + }); + + return ; +}; + +export default AutoExpandingTextInputFormField; diff --git a/src/Components/Form/FormFields/TextAreaFormField.tsx b/src/Components/Form/FormFields/TextAreaFormField.tsx index 23a7d025938..5c23bfec764 100644 --- a/src/Components/Form/FormFields/TextAreaFormField.tsx +++ b/src/Components/Form/FormFields/TextAreaFormField.tsx @@ -1,33 +1,44 @@ +import { forwardRef } from "react"; import FormField from "./FormField"; import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils"; -type TextAreaFormFieldProps = FormFieldBaseProps & { +export type TextAreaFormFieldProps = FormFieldBaseProps & { placeholder?: string; value?: string | number; rows?: number; // prefixIcon?: React.ReactNode; // suffixIcon?: React.ReactNode; + onFocus?: (event: React.FocusEvent) => void; + onBlur?: (event: React.FocusEvent) => void; }; -const TextAreaFormField = ({ rows = 3, ...props }: TextAreaFormFieldProps) => { - const field = useFormFieldPropsResolver(props as any); - return ( - - +
+ { + setIsEditing(false); + setNoteField(note.note); + }} + id="cancel-update-note-button" + > + + Cancel + + + + Update Note + +
+
+ ) : ( +
{noteField}
+ )} +
+ }
-
+ {showEditHistory && ( + setShowEditHistory(false)} + title={t("edit_history")} + > +
+
+

+ Edit History for note + {note.id} +

+
+
+ {editHistory.length === 0 && ( +
+ +
+ )} + {editHistory?.map((edit, index) => { + const isLast = index === editHistory.length - 1; + return ( +
+
+
+

+ {isLast ? "Created" : "Edited"} On +

+

+ {formatDateTime(edit.edited_date)} +

+
+
+
+

Note

+

{edit.note}

+
+
+ ); + })} +
+
+ { + setShowEditHistory(false); + }} + > + {t("close")} + +
+
+
+ )} + ); }; diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 96f9dcad871..a36762072b9 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -74,7 +74,9 @@ const PatientNotesList = (props: PatientNotesProps) => { ); } - return ; + return ( + + ); }; export default PatientNotesList; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index 2e231576eb2..8943d7fe21a 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -30,6 +30,8 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { notes: [], cPage: 1, totalPages: 1, + patientId: props.patientId, + facilityId: props.facilityId, }; const [state, setState] = useState(initialData); @@ -163,10 +165,9 @@ export default function PatientNotesSlideover(props: PatientNotesProps) {
& { leadingPadding?: string | undefined; min?: string | number; max?: string | number; + onKeyDown?: (event: React.KeyboardEvent) => void; onFocus?: (event: React.FocusEvent) => void; onBlur?: (event: React.FocusEvent) => void; }; @@ -62,6 +63,7 @@ const TextFormField = forwardRef((props: TextFormFieldProps, ref) => { onFocus={props.onFocus} onBlur={props.onBlur} onChange={(e) => field.handleChange(e.target.value)} + onKeyDown={props.onKeyDown} /> ); diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 40c842adc07..4893bfbd33f 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -50,6 +50,7 @@ import { WardModel, LocationModel, PatientNotesModel, + PatientNotesEditModel, BedModel, MinimumQuantityItemResponse, InventorySummaryResponse, @@ -677,6 +678,16 @@ const routes = { method: "POST", TRes: Type(), }, + updatePatientNote: { + path: "/api/v1/patient/{patientId}/notes/{noteId}/", + method: "PUT", + TRes: Type(), + }, + getPatientNoteEditHistory: { + path: "/api/v1/patient/{patientId}/notes/{noteId}/edits/", + method: "GET", + TRes: Type>(), + }, sampleTestList: { path: "/api/v1/patient/{patientId}/test_sample/", }, From d36c0f24a60e09adaee68bc74b0ebc04dda6967f Mon Sep 17 00:00:00 2001 From: konavivekramakrishna Date: Wed, 14 Feb 2024 13:48:40 +0530 Subject: [PATCH 31/31] Replace useDispatch in Patient Files (src/Components/Patient/) (#7078) * replaced useDispatch in patientHome * replace usedispatch in patientfiler and managepatinet * add patientfilter * replace useDispatch in dailyroundslistdetials * replaced useDispatch in sampleDetails * replace useDispatch in sampleFilters * replace useDispatch in samplePreview * sampleTestCard * replace useDispatch in sampleTest * replace useDispatch in sampleViewAdmin * replace useDispatch in shiftCreate * fix * fix * revert managePatients and patientFilter useDispatch * replace useDispatch in managePatients * fix * fix * replace useDispatch in PatientFilter * fix prefetch * minor fix * add trailing slashes to api's * fix based on review * Update package-lock.json * Update package-lock.json * replace reload * implement paginated list * fix types * rm Timeline from paginatedList * Update button styling in CommentsSection component * Fix API paths in Redux file * Update API paths * remove unused actions * fix eslint * fix daily rounds and filters * fix sample view admin * fix shifting * fix based on review * minor fix * fix lint --------- Co-authored-by: rithviknishad Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- src/CAREUI/display/Timeline.tsx | 2 +- src/CAREUI/misc/PaginatedList.tsx | 29 +- .../Consultations/DailyRoundsList.tsx | 95 ++-- .../Patient/DailyRoundListDetails.tsx | 78 ++-- src/Components/Patient/ManagePatients.tsx | 229 +++------- src/Components/Patient/PatientHome.tsx | 422 ++++++------------ src/Components/Patient/SampleDetails.tsx | 91 ++-- src/Components/Patient/SampleFilters.tsx | 32 +- src/Components/Patient/SamplePreview.tsx | 41 +- src/Components/Patient/SampleTest.tsx | 61 ++- src/Components/Patient/SampleTestCard.tsx | 33 +- src/Components/Patient/SampleViewAdmin.tsx | 133 +++--- src/Components/Patient/ShiftCreate.tsx | 76 ++-- src/Redux/actions.tsx | 63 +-- src/Redux/api.tsx | 39 +- 15 files changed, 540 insertions(+), 884 deletions(-) diff --git a/src/CAREUI/display/Timeline.tsx b/src/CAREUI/display/Timeline.tsx index 276c437056c..49ace78bd88 100644 --- a/src/CAREUI/display/Timeline.tsx +++ b/src/CAREUI/display/Timeline.tsx @@ -14,7 +14,7 @@ export interface TimelineEvent { } interface TimelineProps { - className: string; + className?: string; children: React.ReactNode | React.ReactNode[]; name: string; } diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx index 1487d69e4fa..363e657f253 100644 --- a/src/CAREUI/misc/PaginatedList.tsx +++ b/src/CAREUI/misc/PaginatedList.tsx @@ -7,7 +7,6 @@ import ButtonV2, { import CareIcon from "../icons/CareIcon"; import { classNames } from "../../Utils/utils"; import Pagination from "../../Components/Common/Pagination"; -import Timeline from "../display/Timeline"; const DEFAULT_PER_PAGE_LIMIT = 14; @@ -135,21 +134,19 @@ const Items = (props: ItemsProps) => { } return ( - -
    - {loading && props.shimmer - ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( -
  • - {props.shimmer} -
  • - )) - : items.map((item, index, items) => ( -
  • - {props.children(item, items)} -
  • - ))} -
-
+
    + {loading && props.shimmer + ? Array.from({ length: props.shimmerCount ?? 8 }).map((_, i) => ( +
  • + {props.shimmer} +
  • + )) + : items.map((item, index, items) => ( +
  • + {props.children(item, items)} +
  • + ))} +
); }; diff --git a/src/Components/Facility/Consultations/DailyRoundsList.tsx b/src/Components/Facility/Consultations/DailyRoundsList.tsx index d438ee098c7..0d569eade41 100644 --- a/src/Components/Facility/Consultations/DailyRoundsList.tsx +++ b/src/Components/Facility/Consultations/DailyRoundsList.tsx @@ -10,7 +10,8 @@ import PageTitle from "../../Common/PageTitle"; import DailyRoundsFilter from "./DailyRoundsFilter"; import { ConsultationModel } from "../models"; import { useSlugs } from "../../../Common/hooks/useSlug"; -import { TimelineNode } from "../../../CAREUI/display/Timeline"; + +import Timeline, { TimelineNode } from "../../../CAREUI/display/Timeline"; import { useState } from "react"; import { QueryParams } from "../../../Utils/request/types"; @@ -52,69 +53,71 @@ export default function DailyRoundsList({ consultation }: Props) { - className="flex grow flex-col gap-3"> - {(item, items) => { - if (item.rounds_type === "AUTOMATED") { + + className="flex grow flex-col gap-3 rounded-lg bg-white p-2 shadow"> + {(item, items) => { + if (item.rounds_type === "AUTOMATED") { + return ( + + + + ); + } + + const itemUrl = ["NORMAL", "TELEMEDICINE"].includes( + item.rounds_type as string + ) + ? `${consultationUrl}/daily-rounds/${item.id}` + : `${consultationUrl}/daily_rounds/${item.id}`; + return ( - navigate(itemUrl)} + onUpdateLog={() => navigate(`${itemUrl}/update`)} /> ); - } - - const itemUrl = ["NORMAL", "TELEMEDICINE"].includes( - item.rounds_type - ) - ? `${consultationUrl}/daily-rounds/${item.id}` - : `${consultationUrl}/daily_rounds/${item.id}`; - - return ( - - navigate(itemUrl)} - onUpdateLog={() => navigate(`${itemUrl}/update`)} - /> - - ); - }} - + }} + +
diff --git a/src/Components/Patient/DailyRoundListDetails.tsx b/src/Components/Patient/DailyRoundListDetails.tsx index 8f313c0a51d..02ad9aa47fa 100644 --- a/src/Components/Patient/DailyRoundListDetails.tsx +++ b/src/Components/Patient/DailyRoundListDetails.tsx @@ -1,75 +1,53 @@ -import { lazy, useCallback, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy, useState } from "react"; import { CONSCIOUSNESS_LEVEL, CURRENT_HEALTH_CHANGE, SYMPTOM_CHOICES, } from "../../Common/constants"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { getConsultationDailyRoundsDetails } from "../../Redux/actions"; import { DailyRoundsModel } from "./models"; import Page from "../Common/components/Page"; import ButtonV2 from "../Common/components/ButtonV2"; import { formatDateTime } from "../../Utils/utils"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; const Loading = lazy(() => import("../Common/Loading")); const symptomChoices = [...SYMPTOM_CHOICES]; const currentHealthChoices = [...CURRENT_HEALTH_CHANGE]; export const DailyRoundListDetails = (props: any) => { const { facilityId, patientId, consultationId, id } = props; - const dispatch: any = useDispatch(); const [dailyRoundListDetailsData, setDailyRoundListDetails] = useState({}); - const [isLoading, setIsLoading] = useState(false); - const fetchpatient = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch( - getConsultationDailyRoundsDetails({ consultationId, id }) - ); - if (!status.aborted) { - if (res && res.data) { - const currentHealth = currentHealthChoices.find( - (i) => i.text === res.data.current_health - ); + const { loading: isLoading } = useQuery(routes.getDailyReport, { + pathParams: { consultationId, id }, + onResponse: ({ res, data }) => { + if (res && data) { + const currentHealth = currentHealthChoices.find( + (i) => i.text === data.current_health + ); - const data: DailyRoundsModel = { - ...res.data, - temperature: Number(res.data.temperature) - ? res.data.temperature - : "", - additional_symptoms_text: "", - medication_given: - Object.keys(res.data.medication_given).length === 0 - ? [] - : res.data.medication_given, - current_health: currentHealth - ? currentHealth.desc - : res.data.current_health, - }; - if (res.data.additional_symptoms?.length) { - const symptoms = res.data.additional_symptoms.map( - (symptom: number) => { - const option = symptomChoices.find((i) => i.id === symptom); - return option ? option.text.toLowerCase() : symptom; - } - ); - data.additional_symptoms_text = symptoms.join(", "); - } - setDailyRoundListDetails(data); + const tdata: DailyRoundsModel = { + ...data, + temperature: Number(data.temperature) ? data.temperature : "", + additional_symptoms_text: "", + medication_given: data.medication_given ?? [], + + current_health: currentHealth + ? currentHealth.desc + : data.current_health, + }; + if (data.additional_symptoms?.length) { + const symptoms = data.additional_symptoms.map((symptom: number) => { + const option = symptomChoices.find((i) => i.id === symptom); + return option ? option.text.toLowerCase() : symptom; + }); + tdata.additional_symptoms_text = symptoms.join(", "); } - setIsLoading(false); + setDailyRoundListDetails(tdata); } }, - [consultationId, dispatch, id] - ); - useAbortableEffect( - (status: statusType) => { - fetchpatient(status); - }, - [dispatch, fetchpatient] - ); + }); if (isLoading) { return ; diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index ce8475e4c66..5e8afaf2d5c 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -11,19 +11,9 @@ import { } from "../../Common/constants"; import { FacilityModel, PatientCategory } from "../Facility/models"; import { Link, navigate } from "raviger"; -import { ReactNode, lazy, useCallback, useEffect, useState } from "react"; -import { - getAllPatient, - getAnyFacility, - getDistrict, - getFacilityAssetLocation, - getLocalBody, -} from "../../Redux/actions"; -import { - statusType, - useAbortableEffect, - parseOptionId, -} from "../../Common/utils"; +import { ReactNode, lazy, useEffect, useState } from "react"; +import { getAllPatient } from "../../Redux/actions"; +import { parseOptionId } from "../../Common/utils"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import ButtonV2 from "../Common/components/ButtonV2"; @@ -42,7 +32,6 @@ import SearchInput from "../Form/SearchInput"; import SortDropdownMenu from "../Common/SortDropdown"; import SwitchTabs from "../Common/components/SwitchTabs"; import { formatAge, parsePhoneNumber } from "../../Utils/utils.js"; -import { useDispatch } from "react-redux"; import useFilters from "../../Common/hooks/useFilters"; import { useTranslation } from "react-i18next"; import Page from "../Common/components/Page.js"; @@ -93,10 +82,6 @@ const PatientCategoryDisplayText: Record = { export const PatientManager = () => { const { t } = useTranslation(); - const dispatch: any = useDispatch(); - const [data, setData] = useState(); - const [isLoading, setIsLoading] = useState(false); - const [totalCount, setTotalCount] = useState(0); const { qParams, updateQuery, @@ -121,10 +106,6 @@ export const PatientManager = () => { const [showDialog, setShowDialog] = useState(false); const [showDoctors, setShowDoctors] = useState(false); const [showDoctorConnect, setShowDoctorConnect] = useState(false); - const [districtName, setDistrictName] = useState(""); - const [localbodyName, setLocalbodyName] = useState(""); - const [facilityBadgeName, setFacilityBadge] = useState(""); - const [locationBadgeName, setLocationBadge] = useState(""); const [phone_number, setPhoneNumber] = useState(""); const [phoneNumberError, setPhoneNumberError] = useState(""); const [emergency_phone_number, setEmergencyPhoneNumber] = useState(""); @@ -367,73 +348,17 @@ export const PatientManager = () => { return cleanedData; }; - useEffect(() => { - setIsLoading(true); - if (!params.phone_number) { - setPhoneNumber("+91"); - } - if (!params.emergency_phone_number) { - setEmergencyPhoneNumber("+91"); - } - dispatch(getAllPatient(params, "listPatients")).then((res: any) => { - if (res && res.data) { - setData(res.data.results); - setTotalCount(res.data.count); - setIsLoading(false); + const { loading: isLoading, data } = useQuery(routes.patientList, { + query: params, + onResponse: () => { + if (!params.phone_number) { + setPhoneNumber("+91"); } - }); - }, [ - dispatch, - qParams.last_consultation_medico_legal_case, - qParams.last_consultation_encounter_date_before, - qParams.last_consultation_encounter_date_after, - qParams.last_consultation_discharge_date_before, - qParams.last_consultation_discharge_date_after, - qParams.age_max, - qParams.age_min, - qParams.last_consultation_admitted_bed_type_list, - qParams.last_consultation__new_discharge_reason, - qParams.last_consultation_current_bed__location, - qParams.facility, - qParams.facility_type, - qParams.district, - qParams.category, - qParams.gender, - qParams.ordering, - qParams.created_date_before, - qParams.created_date_after, - qParams.modified_date_before, - qParams.modified_date_after, - qParams.is_active, - qParams.disease_status, - qParams.name, - qParams.patient_no, - qParams.page, - qParams.phone_number, - qParams.emergency_phone_number, - qParams.srf_id, - qParams.covin_id, - qParams.number_of_doses, - qParams.lsgBody, - qParams.is_kasp, - qParams.is_declared_positive, - qParams.date_declared_positive_before, - qParams.date_declared_positive_after, - qParams.date_of_result_before, - qParams.date_of_result_after, - qParams.last_consultation_symptoms_onset_date_before, - qParams.last_consultation_symptoms_onset_date_after, - qParams.last_vaccinated_date_before, - qParams.last_vaccinated_date_after, - qParams.last_consultation_is_telemedicine, - qParams.is_antenatal, - qParams.ventilator_interface, - qParams.diagnoses, - qParams.diagnoses_confirmed, - qParams.diagnoses_provisional, - qParams.diagnoses_unconfirmed, - qParams.diagnoses_differential, - ]); + if (!params.emergency_phone_number) { + setEmergencyPhoneNumber("+91"); + } + }, + }); const getTheCategoryFromId = () => { let category_name; @@ -448,80 +373,35 @@ export const PatientManager = () => { } }; - const fetchDistrictName = useCallback( - async (status: statusType) => { - const res = - Number(qParams.district) && - (await dispatch(getDistrict(qParams.district))); - if (!status.aborted) { - setDistrictName(res?.data?.name); - } - }, - [dispatch, qParams.district] - ); - - useAbortableEffect( - (status: statusType) => { - fetchDistrictName(status); - }, - [fetchDistrictName] - ); - - const fetchLocalbodyName = useCallback( - async (status: statusType) => { - const res = - Number(qParams.lsgBody) && - (await dispatch(getLocalBody({ id: qParams.lsgBody }))); - if (!status.aborted) { - setLocalbodyName(res?.data?.name); - } - }, - [dispatch, qParams.lsgBody] - ); - - useAbortableEffect( - (status: statusType) => { - fetchLocalbodyName(status); + const { data: districtData } = useQuery(routes.getDistrict, { + pathParams: { + id: qParams.district, }, - [fetchLocalbodyName] - ); - - const fetchFacilityBadgeName = useCallback( - async (status: statusType) => { - const res = - qParams.facility && (await dispatch(getAnyFacility(qParams.facility))); + prefetch: !!Number(qParams.district), + }); - if (!status.aborted) { - setFacilityBadge(res?.data?.name); - } + const { data: LocalBodyData } = useQuery(routes.getLocalBody, { + pathParams: { + id: qParams.lsgBody, }, - [dispatch, qParams.facility] - ); - - const fetchLocationBadgeName = useCallback( - async (status: statusType) => { - const res = - qParams.last_consultation_current_bed__location && - (await dispatch( - getFacilityAssetLocation( - qParams.facility, - qParams.last_consultation_current_bed__location - ) - )); - - if (!status.aborted) { - setLocationBadge(res?.data?.name); - } - }, - [dispatch, qParams.last_consultation_current_bed__location] - ); + prefetch: !!Number(qParams.lsgBody), + }); - useAbortableEffect( - (status: statusType) => { - fetchFacilityBadgeName(status); - fetchLocationBadgeName(status); + const { data: facilityData } = useQuery(routes.getAnyFacility, { + pathParams: { + id: qParams.facility, }, - [fetchFacilityBadgeName, fetchLocationBadgeName] + prefetch: !!qParams.facility, + }); + const { data: facilityAssetLocationData } = useQuery( + routes.getFacilityAssetLocation, + { + pathParams: { + facility_external_id: qParams.facility, + external_id: qParams.last_consultation_current_bed__location, + }, + prefetch: !!qParams.last_consultation_current_bed__location, + } ); const { data: permittedFacilities } = useQuery( @@ -564,8 +444,8 @@ export const PatientManager = () => { }; let patientList: ReactNode[] = []; - if (data && data.length) { - patientList = data.map((patient: any) => { + if (data?.count) { + patientList = data.results.map((patient: any) => { let patientUrl = ""; if ( patient.last_consultation && @@ -814,16 +694,16 @@ export const PatientManager = () => {
); - } else if (data?.length) { + } else if (data?.count) { managePatients = ( <>
{patientList}
- + ); - } else if (data && data.length === 0) { + } else if (data && data.count === 0) { managePatients = (

No Patients Found

@@ -967,7 +847,7 @@ export const PatientManager = () => {
{ "Is Medico-Legal Case", "last_consultation_medico_legal_case" ), - value("Facility", "facility", facilityBadgeName), + value( + "Facility", + "facility", + qParams.facility ? facilityData?.name || "" : "" + ), value( "Location", "last_consultation_current_bed__location", - locationBadgeName + qParams.last_consultation_current_bed__location + ? facilityAssetLocationData?.name || + qParams.last_consultation_current_bed__locations + : "" ), badge("Facility Type", "facility_type"), - value("District", "district", districtName), + value( + "District", + "district", + qParams.district ? districtData?.name || "" : "" + ), ordering(), value("Category", "category", getTheCategoryFromId()), badge("Disease Status", "disease_status"), @@ -1067,7 +958,11 @@ export const PatientManager = () => { }, ...range("Age", "age"), badge("SRF ID", "srf_id"), - { name: "LSG Body", value: localbodyName, paramKey: "lsgBody" }, + { + name: "LSG Body", + value: qParams.lsgBody ? LocalBodyData?.name || "" : "", + paramKey: "lsgBody", + }, ...FILTER_BY_DIAGNOSES_KEYS.map((key) => value( DIAGNOSES_FILTER_LABELS[key], diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 9b719ba31dd..22dc86117f4 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -1,23 +1,13 @@ import { navigate } from "raviger"; -import { lazy, useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy, useEffect, useState } from "react"; + import { DISCHARGE_REASONS, GENDER_TYPES, SAMPLE_TEST_STATUS, } from "../../Common/constants"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { - getConsultationList, - listShiftRequests, - getPatient, - getSampleTestList, - patchSample, - patchPatient, - completeTransfer, -} from "../../Redux/actions"; + import * as Notification from "../../Utils/Notifications"; -import Pagination from "../Common/Pagination"; import { ConsultationCard } from "../Facility/ConsultationCard"; import { ConsultationModel } from "../Facility/models"; import { PatientModel, SampleTestModel } from "./models"; @@ -44,35 +34,18 @@ import useAuthUser from "../../Common/hooks/useAuthUser"; import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; import { InsuranceDetialsCard } from "./InsuranceDetailsCard"; +import request from "../../Utils/request/request"; +import PaginatedList from "../../CAREUI/misc/PaginatedList"; const Loading = lazy(() => import("../Common/Loading")); export const PatientHome = (props: any) => { const { facilityId, id } = props; - const dispatch: any = useDispatch(); const [showShifts, setShowShifts] = useState(false); const [isShiftClicked, setIsShiftClicked] = useState(false); - const [isShiftDataLoaded, setIsShiftDataLoaded] = useState(false); const [patientData, setPatientData] = useState({}); - const [consultationListData, setConsultationListData] = useState< - Array - >([]); - const [sampleListData, setSampleListData] = useState>( - [] - ); - const [activeShiftingData, setActiveShiftingData] = useState>([]); const [assignedVolunteerObject, setAssignedVolunteerObject] = useState(null); - const [isLoading, setIsLoading] = useState(false); - const [totalConsultationCount, setTotalConsultationCount] = useState(0); - const [currentConsultationPage, setCurrentConsultationPage] = useState(1); - const [consultationOffset, setConsultationOffset] = useState(0); - const [totalSampleListCount, setTotalSampleListCount] = useState(0); - const [currentSampleListPage, setCurrentSampleListPage] = useState(1); - const [sampleListOffset, setSampleListOffset] = useState(0); - const [isConsultationLoading, setIsConsultationLoading] = useState(false); - const [isSampleLoading, setIsSampleLoading] = useState(false); - const [sampleFlag, callSampleList] = useState(false); const authUser = useAuthUser(); const { t } = useTranslation(); const [selectedStatus, setSelectedStatus] = useState<{ @@ -94,13 +67,16 @@ export const PatientHome = (props: any) => { setAssignedVolunteerObject(patientData.assigned_to_object); }, [patientData.assigned_to_object]); - const handleTransferComplete = (shift: any) => { + const handleTransferComplete = async (shift: any) => { setModalFor({ ...modalFor, loading: true }); - dispatch(completeTransfer({ externalId: modalFor })).then(() => { - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation` - ); + await request(routes.completeTransfer, { + pathParams: { + id: modalFor.externalId ?? "", + }, }); + navigate( + `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation` + ); }; const { data: insuranceDetials } = useQuery(routes.listHCXPolicies, { @@ -110,52 +86,28 @@ export const PatientHome = (props: any) => { }, }); - const handleAssignedVolunteer = () => { - dispatch( - patchPatient( - { - assigned_to: assignedVolunteerObject - ? assignedVolunteerObject.id - : null, - }, - { id: patientData.id } - ) - ).then((response: any) => { - if ((response || {}).status === 200) { - const dummyPatientData = Object.assign({}, patientData); - dummyPatientData["assigned_to"] = assignedVolunteerObject; - setPatientData(dummyPatientData); - if (assignedVolunteerObject) - Notification.Success({ - msg: "Volunteer assigned successfully.", - }); - else - Notification.Success({ - msg: "Volunteer unassigned successfully.", - }); - document.location.reload(); - } - }); - setOpenAssignVolunteerDialog(false); - if (errors["assignedVolunteer"]) delete errors["assignedVolunteer"]; - }; - - const handlePatientTransfer = (value: boolean) => { + const handlePatientTransfer = async (value: boolean) => { const dummyPatientData = Object.assign({}, patientData); dummyPatientData["allow_transfer"] = value; - dispatch( - patchPatient({ allow_transfer: value }, { id: patientData.id }) - ).then((response: any) => { - if ((response || {}).status === 200) { - const dummyPatientData = Object.assign({}, patientData); - dummyPatientData["allow_transfer"] = value; - setPatientData(dummyPatientData); + await request(routes.patchPatient, { + pathParams: { + id: patientData.id as string, + }, - Notification.Success({ - msg: "Transfer status updated.", - }); - } + body: { allow_transfer: value }, + + onResponse: ({ res }) => { + if ((res || {}).status === 200) { + const dummyPatientData = Object.assign({}, patientData); + dummyPatientData["allow_transfer"] = value; + setPatientData(dummyPatientData); + + Notification.Success({ + msg: "Transfer status updated.", + }); + } + }, }); }; @@ -163,122 +115,58 @@ export const PatientHome = (props: any) => { setAssignedVolunteerObject(volunteer.value); }; - const limit = 5; - - const fetchpatient = useCallback( - async (status: statusType) => { - setIsLoading(true); - const patientRes = await dispatch(getPatient({ id })); - if (!status.aborted) { - if (patientRes && patientRes.data) { - setPatientData(patientRes.data); - } - setIsLoading(false); - } + const { loading: isLoading, refetch } = useQuery(routes.getPatient, { + pathParams: { + id, }, - [dispatch, id] - ); - - const fetchConsultation = useCallback( - async (status: statusType) => { - setIsConsultationLoading(true); - const consultationRes = await dispatch( - getConsultationList({ patient: id, limit, offset: consultationOffset }) - ); - if (!status.aborted) { - if ( - consultationRes && - consultationRes.data && - consultationRes.data.results - ) { - setConsultationListData(consultationRes.data.results); - setTotalConsultationCount(consultationRes.data.count); - } - setIsConsultationLoading(false); - } - }, - [dispatch, id, consultationOffset] - ); - - const fetchSampleTest = useCallback( - async (status: statusType) => { - setIsSampleLoading(true); - const sampleRes = await dispatch( - getSampleTestList( - { limit, offset: sampleListOffset }, - { patientId: id } - ) - ); - if (!status.aborted) { - if (sampleRes && sampleRes.data && sampleRes.data.results) { - setSampleListData(sampleRes.data.results); - setTotalSampleListCount(sampleRes.data.count); - } - setIsSampleLoading(false); + onResponse: ({ res, data }) => { + if (res?.ok && data) { + setPatientData(data); } - }, - [dispatch, id, sampleListOffset] - ); - - const fetchActiveShiftingData = useCallback( - async (status: statusType) => { - const shiftingRes = isShiftClicked - ? await dispatch(listShiftRequests({ patient: id }, "shift-list-call")) - : activeShiftingData; - setIsShiftDataLoaded(isShiftClicked); - if (!status.aborted) { - if (shiftingRes && shiftingRes.data && shiftingRes.data.results) { - const activeShiftingRes: any[] = shiftingRes.data.results; - setActiveShiftingData(activeShiftingRes); - } - } - }, - [dispatch, id, isShiftClicked] - ); - - useAbortableEffect( - (status: statusType) => { - fetchpatient(status); triggerGoal("Patient Profile Viewed", { facilityId: facilityId, userId: authUser.id, }); }, - [dispatch, fetchpatient] - ); - - useAbortableEffect( - (status: statusType) => { - fetchConsultation(status); - }, - [dispatch, fetchConsultation] - ); - - useAbortableEffect( - (status: statusType) => { - fetchSampleTest(status); - }, - [dispatch, fetchSampleTest, sampleFlag] - ); - - useAbortableEffect( - (status: statusType) => { - fetchActiveShiftingData(status); - }, - [dispatch, fetchActiveShiftingData] - ); + }); - const handleConsultationPagination = (page: number, limit: number) => { - const offset = (page - 1) * limit; - setCurrentConsultationPage(page); - setConsultationOffset(offset); + const handleAssignedVolunteer = async () => { + const { res, data } = await request(routes.patchPatient, { + pathParams: { + id: patientData.id as string, + }, + body: { + assigned_to: assignedVolunteerObject + ? assignedVolunteerObject.id + : null, + }, + }); + if (res?.ok && data) { + setPatientData(data); + if (assignedVolunteerObject) { + Notification.Success({ + msg: "Volunteer assigned successfully.", + }); + } else { + Notification.Success({ + msg: "Volunteer unassigned successfully.", + }); + } + refetch(); + } + setOpenAssignVolunteerDialog(false); + if (errors["assignedVolunteer"]) delete errors["assignedVolunteer"]; }; - const handleSampleListPagination = (page: number, limit: number) => { - const offset = (page - 1) * limit; - setCurrentSampleListPage(page); - setSampleListOffset(offset); - }; + const { loading: isShiftDataLoading, data: activeShiftingData } = useQuery( + routes.listShiftRequests, + { + query: { + patient: id, + }, + prefetch: isShiftClicked, + } + ); const confirmApproval = (status: number, sample: any) => { setSelectedStatus({ status, sample }); @@ -289,20 +177,25 @@ export const PatientHome = (props: any) => { const { status, sample } = selectedStatus; const sampleData = { id: sample.id, - status, + status: status.toString(), consultation: sample.consultation, }; const statusName = SAMPLE_TEST_STATUS.find((i) => i.id === status)?.desc; - const res = await dispatch(patchSample(sampleData, { id: sample.id })); - if (res && (res.status === 201 || res.status === 200)) { - Notification.Success({ - msg: `Request ${statusName}`, - }); - callSampleList(!sampleFlag); - } - - setShowAlertMessage(false); + await request(routes.patchSample, { + body: sampleData, + pathParams: { + id: sample.id, + }, + onResponse: ({ res }) => { + if (res?.ok) { + Notification.Success({ + msg: `Request ${statusName}`, + }); + } + setShowAlertMessage(false); + }, + }); }; if (isLoading) { @@ -334,57 +227,7 @@ export const PatientHome = (props: any) => { )); } - let consultationList, sampleList; - - if (isConsultationLoading) { - consultationList = ; - } else if (consultationListData.length === 0) { - consultationList = ( -
-
-
- No Data Found -
-
- ); - } else if (consultationListData.length > 0) { - consultationList = consultationListData.map((itemData, idx) => ( - - )); - } - - if (isSampleLoading) { - sampleList = ; - } else if (sampleListData.length === 0) { - sampleList = ( -
-
-
- No Data Found -
-
- ); - } else if (sampleListData.length > 0) { - sampleList = ( -
- {sampleListData.map((itemData, idx) => ( - - ))} -
- ); - } - - const isPatientInactive = (patientData: PatientModel, facilityId: number) => { + const isPatientInactive = (patientData: PatientModel, facilityId: string) => { return ( !patientData.is_active || !(patientData?.last_consultation?.facility === facilityId) @@ -798,8 +641,7 @@ export const PatientHome = (props: any) => { id="patient-allow-transfer" className="mt-4 w-full" disabled={ - !consultationListData || - !consultationListData.length || + !patientData.last_consultation?.id || !patientData.is_active } onClick={() => @@ -835,14 +677,14 @@ export const PatientHome = (props: any) => {
- {activeShiftingData.length ? ( - activeShiftingData.map((shift: any) => ( + {activeShiftingData?.count ? ( + activeShiftingData.results.map((shift: any) => (
{ )) ) : (
- {isShiftDataLoaded ? "No Shifting Records!" : "Loading..."} + {isShiftDataLoading ? "Loading..." : "No Shifting Records!"}
)}
@@ -1511,34 +1353,66 @@ export const PatientHome = (props: any) => {

Consultation History

- {consultationList} - {!isConsultationLoading && totalConsultationCount > limit && ( -
- -
- )} + + + {(_) => ( +
+ + + + > + {(item) => ( + + )} + +
+ +
+
+ )} +

Sample Test History

- {sampleList} - {!isSampleLoading && totalSampleListCount > limit && ( -
- -
- )} + + {(_, query) => ( +
+ + + + > + {(item) => ( + + )} + +
+ +
+
+ )} +
); diff --git a/src/Components/Patient/SampleDetails.tsx b/src/Components/Patient/SampleDetails.tsx index 9b08395a233..bcaf170cc4f 100644 --- a/src/Components/Patient/SampleDetails.tsx +++ b/src/Components/Patient/SampleDetails.tsx @@ -1,7 +1,6 @@ -import { FlowModel, SampleTestModel } from "./models"; +import { FlowModel } from "./models"; import { GENDER_TYPES, TEST_TYPE_CHOICES } from "../../Common/constants"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { lazy, useCallback, useState } from "react"; +import { lazy } from "react"; import ButtonV2 from "../Common/components/ButtonV2"; import Card from "../../CAREUI/display/Card"; @@ -9,41 +8,29 @@ import { FileUpload } from "./FileUpload"; import Page from "../Common/components/Page"; import _ from "lodash-es"; import { formatAge, formatDateTime } from "../../Utils/utils"; -import { getTestSample } from "../../Redux/actions"; import { navigate } from "raviger"; -import { useDispatch } from "react-redux"; import { DetailRoute } from "../../Routers/types"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; const Loading = lazy(() => import("../Common/Loading")); export const SampleDetails = ({ id }: DetailRoute) => { - const dispatch: any = useDispatch(); - const [isLoading, setIsLoading] = useState(false); - const [sampleDetails, setSampleDetails] = useState({}); - - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch(getTestSample(id)); - if (!status.aborted) { - if (res && res.data) { - setSampleDetails(res.data); - } else { + const { loading: isLoading, data: sampleDetails } = useQuery( + routes.getTestSample, + { + pathParams: { + id, + }, + onResponse: ({ res, data }) => { + if (!(res?.ok && data)) { navigate("/not-found"); } - setIsLoading(false); - } - }, - [dispatch, id] + }, + } ); - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [dispatch, fetchData, id] - ); const yesOrNoBadge = (param: any) => param ? ( Yes @@ -287,7 +274,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { title="Sample Test Details" backUrl="/sample" options={ - sampleDetails.patient && ( + sampleDetails?.patient && (
{ Status:{" "} - {sampleDetails.status} + {sampleDetails?.status}
Result:{" "} - {sampleDetails.result} + {sampleDetails?.result}
Patient: - {sampleDetails.patient_name} + {sampleDetails?.patient_name}
- {sampleDetails.facility_object && ( + {sampleDetails?.facility_object && (
Facility: - {sampleDetails.facility_object.name} + {sampleDetails?.facility_object.name}
)}
Tested on: - {sampleDetails.date_of_result + {sampleDetails?.date_of_result ? formatDateTime(sampleDetails.date_of_result) : "-"}
Result on: - {sampleDetails.date_of_result + {sampleDetails?.date_of_result ? formatDateTime(sampleDetails.date_of_result) : "-"}
- {sampleDetails.fast_track && ( + {sampleDetails?.fast_track && (
Fast track testing reason:{" "} @@ -342,7 +329,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails.fast_track}
)} - {sampleDetails.doctor_name && ( + {sampleDetails?.doctor_name && (
Doctor's Name:{" "} @@ -350,21 +337,21 @@ export const SampleDetails = ({ id }: DetailRoute) => { {_.startCase(_.camelCase(sampleDetails.doctor_name))}
)} - {sampleDetails.diagnosis && ( + {sampleDetails?.diagnosis && (
Diagnosis: {sampleDetails.diagnosis}
)} - {sampleDetails.diff_diagnosis && ( + {sampleDetails?.diff_diagnosis && (
Differential diagnosis:{" "} - {sampleDetails.diff_diagnosis} + {sampleDetails?.diff_diagnosis}
)} - {sampleDetails.etiology_identified && ( + {sampleDetails?.etiology_identified && (
Etiology identified:{" "} @@ -376,15 +363,15 @@ export const SampleDetails = ({ id }: DetailRoute) => { Is Atypical presentation{" "} - {yesOrNoBadge(sampleDetails.is_atypical_presentation)} + {yesOrNoBadge(sampleDetails?.is_atypical_presentation)}
Is unusual course{" "} - {yesOrNoBadge(sampleDetails.is_unusual_course)} + {yesOrNoBadge(sampleDetails?.is_unusual_course)}
- {sampleDetails.atypical_presentation && ( + {sampleDetails?.atypical_presentation && (
Atypical presentation details:{" "} @@ -396,27 +383,27 @@ export const SampleDetails = ({ id }: DetailRoute) => { SARI - Severe Acute Respiratory illness{" "} - {yesOrNoBadge(sampleDetails.has_sari)} + {yesOrNoBadge(sampleDetails?.has_sari)}
ARI - Acute Respiratory illness{" "} - {yesOrNoBadge(sampleDetails.has_ari)} + {yesOrNoBadge(sampleDetails?.has_ari)}
Contact with confirmed carrier{" "} - {yesOrNoBadge(sampleDetails.patient_has_confirmed_contact)} + {yesOrNoBadge(sampleDetails?.patient_has_confirmed_contact)}
Contact with suspected carrier{" "} - {yesOrNoBadge(sampleDetails.patient_has_suspected_contact)} + {yesOrNoBadge(sampleDetails?.patient_has_suspected_contact)}
- {sampleDetails.patient_travel_history && + {sampleDetails?.patient_travel_history && sampleDetails.patient_travel_history.length !== 0 && (
@@ -425,7 +412,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails.patient_travel_history.join(", ")}
)} - {sampleDetails.sample_type && ( + {sampleDetails?.sample_type && (
Sample Type:{" "} @@ -438,12 +425,12 @@ export const SampleDetails = ({ id }: DetailRoute) => {

Details of patient

- {showPatientCard(sampleDetails.patient_object)} + {showPatientCard(sampleDetails?.patient_object)}

Sample Test History

- {sampleDetails.flow && + {sampleDetails?.flow && sampleDetails.flow.map((flow: FlowModel) => renderFlow(flow))}
diff --git a/src/Components/Patient/SampleFilters.tsx b/src/Components/Patient/SampleFilters.tsx index 8bebd135e0b..2a72f54204f 100644 --- a/src/Components/Patient/SampleFilters.tsx +++ b/src/Components/Patient/SampleFilters.tsx @@ -1,4 +1,3 @@ -import { useState, useEffect } from "react"; import { SAMPLE_TEST_STATUS, SAMPLE_TEST_RESULT, @@ -6,14 +5,14 @@ import { } from "../../Common/constants"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FacilityModel } from "../Facility/models"; -import { getAnyFacility } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import useMergeState from "../../Common/hooks/useMergeState"; import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; import CircularProgress from "../Common/components/CircularProgress"; import { FieldLabel } from "../Form/FormFields/FormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; export default function UserFilter(props: any) { const { filter, onChange, closeFilter, removeFilters } = props; @@ -26,9 +25,6 @@ export default function UserFilter(props: any) { sample_type: filter.sample_type || "", }); - const [isFacilityLoading, setFacilityLoading] = useState(false); - const dispatch: any = useDispatch(); - const handleChange = ({ name, value }: FieldChangeEvent) => { setFilterState({ ...filterState, [name]: value }); }; @@ -44,21 +40,15 @@ export default function UserFilter(props: any) { onChange(data); }; - useEffect(() => { - async function fetchData() { - if (filter.facility) { - setFacilityLoading(true); - const { data: facilityData } = await dispatch( - getAnyFacility(filter.facility, "facility") - ); - setFilterState({ ...filterState, facility_ref: facilityData }); - setFacilityLoading(false); - } - } - fetchData(); - }, [dispatch]); - - console.log(filterState.sample_type); + const { loading: isFacilityLoading } = useQuery(routes.getAnyFacility, { + pathParams: { + id: filter.facility, + }, + prefetch: !!filter.facility, + onResponse: ({ data }) => { + setFilterState({ ...filterState, facility_ref: data }); + }, + }); return ( import("../Common/Loading")); @@ -56,34 +54,19 @@ function SampleReportSection({ title, fields }: ISampleReportSectionProps) { } export default function SampleReport(props: ISamplePreviewProps) { - const dispatch: any = useDispatch(); const { id, sampleId } = props; - const [isLoading, setIsLoading] = useState(false); - const [sampleData, setSampleData] = useState({}); let report: JSX.Element = <>; let reportData: JSX.Element = <>; - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res: any = await dispatch(sampleReport(id, sampleId)); - - if (!status.aborted) { - if (res && res.data) { - setSampleData(res.data); - } - } - setIsLoading(false); - }, - [dispatch, id] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] + const { loading: isLoading, data: sampleData } = useQuery( + routes.sampleReport, + { + pathParams: { + id, + sampleId, + }, + } ); if (sampleData) { diff --git a/src/Components/Patient/SampleTest.tsx b/src/Components/Patient/SampleTest.tsx index 625e2104d0d..21a308fa393 100644 --- a/src/Components/Patient/SampleTest.tsx +++ b/src/Components/Patient/SampleTest.tsx @@ -1,9 +1,7 @@ import { navigate } from "raviger"; -import { useReducer, useState, useEffect, lazy } from "react"; -import { useDispatch } from "react-redux"; +import { useReducer, useState, lazy } from "react"; import { SAMPLE_TYPE_CHOICES, ICMR_CATEGORY } from "../../Common/constants"; -import { createSampleTest, getPatient } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; import { SampleTestModel } from "./models"; import { Cancel, Submit } from "../Common/components/ButtonV2"; @@ -16,6 +14,9 @@ import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; import Page from "../Common/components/Page"; import { FacilitySelect } from "../Common/FacilitySelect"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; const Loading = lazy(() => import("../Common/Loading")); const initForm: SampleTestModel = { @@ -68,30 +69,18 @@ const sampleTestFormReducer = (state = initialState, action: any) => { export const SampleTest = ({ facilityId, patientId }: any) => { const { goBack } = useAppHistory(); - const dispatchAction: any = useDispatch(); const [state, dispatch] = useReducer(sampleTestFormReducer, initialState); const [isLoading, setIsLoading] = useState(false); - const [facilityName, setFacilityName] = useState(""); - const [patientName, setPatientName] = useState(""); const headerText = "Request Sample"; const buttonText = "Confirm your request to send sample for testing"; - useEffect(() => { - async function fetchPatientName() { - if (patientId) { - const res = await dispatchAction(getPatient({ id: patientId })); - if (res.data) { - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); - } - } else { - setPatientName(""); - setFacilityName(""); - } - } - fetchPatientName(); - }, [dispatchAction, patientId]); + const { data } = useQuery(routes.getPatient, { + pathParams: { + id: patientId, + }, + prefetch: !!patientId, + }); const validateForm = () => { const errors = { ...initError }; @@ -170,15 +159,23 @@ export const SampleTest = ({ facilityId, patientId }: any) => { ? state.form.sample_type_other : undefined, }; - const res = await dispatchAction(createSampleTest(data, { patientId })); - setIsLoading(false); - if (res && res.data) { - dispatch({ type: "set_form", form: initForm }); - Notification.Success({ - msg: "Sample test created successfully", - }); - navigate(`/facility/${facilityId}/patient/${patientId}`); - } + + await request(routes.createSampleTest, { + pathParams: { + patientId, + }, + body: data, + onResponse: ({ res, data }) => { + setIsLoading(false); + if (res?.ok && data) { + dispatch({ type: "set_form", form: initForm }); + Notification.Success({ + msg: "Sample test created successfully", + }); + navigate(`/facility/${facilityId}/patient/${patientId}`); + } + }, + }); } }; @@ -201,8 +198,8 @@ export const SampleTest = ({ facilityId, patientId }: any) => { diff --git a/src/Components/Patient/SampleTestCard.tsx b/src/Components/Patient/SampleTestCard.tsx index 1ae1ff8671b..f2cc928bf50 100644 --- a/src/Components/Patient/SampleTestCard.tsx +++ b/src/Components/Patient/SampleTestCard.tsx @@ -1,9 +1,7 @@ import { navigate } from "raviger"; import { useState } from "react"; import { SampleTestModel } from "./models"; -import { useDispatch } from "react-redux"; import { SAMPLE_TEST_STATUS } from "../../Common/constants"; -import { patchSample } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import UpdateStatusDialog from "./UpdateStatusDialog"; import _ from "lodash-es"; @@ -11,17 +9,19 @@ import { formatDateTime } from "../../Utils/utils"; import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import RelativeDateUserMention from "../Common/RelativeDateUserMention"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; interface SampleDetailsProps { facilityId: number; patientId: number; itemData: SampleTestModel; + refetch: () => void; handleApproval: (status: number, sample: SampleTestModel) => void; } export const SampleTestCard = (props: SampleDetailsProps) => { - const { itemData, handleApproval, facilityId, patientId } = props; - const dispatch: any = useDispatch(); + const { itemData, handleApproval, facilityId, patientId, refetch } = props; const [statusDialog, setStatusDialog] = useState<{ show: boolean; @@ -43,15 +43,21 @@ export const SampleTestCard = (props: SampleDetailsProps) => { sampleData.date_of_result = new Date().toISOString(); } const statusName = SAMPLE_TEST_STATUS.find((i) => i.id === status)?.desc; - - const res = await dispatch(patchSample(sampleData, { id: sample.id })); - if (res && (res.status === 201 || res.status === 200)) { - window.location.reload(); - Notification.Success({ - msg: `Success - ${statusName}`, - }); - } - dismissUpdateStatus(); + await request(routes.patchSample, { + pathParams: { + id: sample.id!, + }, + body: sampleData, + onResponse: ({ res }) => { + if (res?.ok) { + refetch(); + Notification.Success({ + msg: `Success - ${statusName}`, + }); + } + dismissUpdateStatus(); + }, + }); }; const showUpdateStatus = (sample: SampleTestModel) => { @@ -78,6 +84,7 @@ export const SampleTestCard = (props: SampleDetailsProps) => { : "bg-white hover:border-primary-500" } mt-4 block cursor-pointer rounded-lg border bg-white p-4 text-black shadow`} > +
test card this
navigate( diff --git a/src/Components/Patient/SampleViewAdmin.tsx b/src/Components/Patient/SampleViewAdmin.tsx index 5aca767e480..122f82aa7e2 100644 --- a/src/Components/Patient/SampleViewAdmin.tsx +++ b/src/Components/Patient/SampleViewAdmin.tsx @@ -1,20 +1,13 @@ import SampleFilter from "./SampleFilters"; import { navigate } from "raviger"; -import { useCallback, useState, useEffect, lazy } from "react"; -import { useDispatch } from "react-redux"; +import { useState, lazy } from "react"; import { SAMPLE_TEST_STATUS, SAMPLE_TEST_RESULT, SAMPLE_FLOW_RULES, SAMPLE_TYPE_CHOICES, } from "../../Common/constants"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { - getTestList, - patchSample, - downloadSampleTests, - getAnyFacility, -} from "../../Redux/actions"; +import { downloadSampleTests } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import { SampleTestModel } from "./models"; import UpdateStatusDialog from "./UpdateStatusDialog"; @@ -26,6 +19,9 @@ import CountBlock from "../../CAREUI/display/Count"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import Page from "../Common/components/Page"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; const Loading = lazy(() => import("../Common/Loading")); @@ -41,70 +37,35 @@ export default function SampleViewAdmin() { limit: 10, cacheBlacklist: ["patient_name", "district_name"], }); - const dispatch: any = useDispatch(); - const initialData: any[] = []; let manageSamples: any = null; - const [sample, setSample] = useState>(initialData); - const [isLoading, setIsLoading] = useState(false); - const [totalCount, setTotalCount] = useState(0); - const [fetchFlag, callFetchData] = useState(false); - const [facilityName, setFacilityName] = useState(""); const [statusDialog, setStatusDialog] = useState<{ show: boolean; sample: SampleTestModel; }>({ show: false, sample: {} }); - useEffect(() => { - async function fetchData() { - if (!qParams.facility) return setFacilityName(""); - const res = await dispatch(getAnyFacility(qParams.facility)); - setFacilityName(res?.data?.name); - } - - fetchData(); - }, [dispatch, qParams.facility]); - - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch( - getTestList({ - limit: resultsPerPage, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - patient_name: qParams.patient_name || undefined, - district_name: qParams.district_name || undefined, - status: qParams.status || undefined, - result: qParams.result || undefined, - facility: qParams.facility || "", - sample_type: qParams.sample_type || undefined, - }) - ); - if (!status.aborted) { - if (res && res.data) { - setSample(res.data.results); - setTotalCount(res.data.count); - } - setIsLoading(false); - } + const { data: facilityData } = useQuery(routes.getAnyFacility, { + pathParams: { + id: qParams.facility, }, - [ - dispatch, - qParams.page, - qParams.patient_name, - qParams.district_name, - qParams.status, - qParams.result, - qParams.facility, - qParams.sample_type, - ] - ); + prefetch: !!qParams.facility, + }); - useAbortableEffect( - (status: statusType) => { - fetchData(status); + const { + loading: isLoading, + data: sampeleData, + refetch, + } = useQuery(routes.getTestSampleList, { + query: { + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + patient_name: qParams.patient_name || undefined, + district_name: qParams.district_name || undefined, + status: qParams.status || undefined, + result: qParams.result || undefined, + facility: qParams.facility || undefined, + sample_type: qParams.sample_type || undefined, }, - [fetchData, fetchFlag] - ); + }); const handleApproval = async ( sample: SampleTestModel, @@ -121,14 +82,22 @@ export default function SampleViewAdmin() { sampleData.date_of_result = new Date().toISOString(); } const statusName = SAMPLE_TEST_STATUS.find((i) => i.id === status)?.desc; - const res = await dispatch(patchSample(sampleData, { id: sample.id })); - if (res && (res.status === 201 || res.status === 200)) { - Notification.Success({ - msg: `Success - ${statusName}`, - }); - callFetchData(!fetchFlag); - } - dismissUpdateStatus(); + + await request(routes.patchSample, { + pathParams: { + id: sample.id || 0, + }, + body: sampleData, + onResponse: ({ res }) => { + if (res?.ok) { + Notification.Success({ + msg: `Success - ${statusName}`, + }); + refetch(); + } + dismissUpdateStatus(); + }, + }); }; const showUpdateStatus = (sample: SampleTestModel) => { @@ -163,8 +132,8 @@ export default function SampleViewAdmin() { .join("\n"); let sampleList: any[] = []; - if (sample && sample.length) { - sampleList = sample.map((item) => { + if (sampeleData?.count) { + sampleList = sampeleData.results.map((item) => { const status = String(item.status) as keyof typeof SAMPLE_FLOW_RULES; const statusText = SAMPLE_TEST_STATUS.find( (i) => i.text === status @@ -303,20 +272,20 @@ export default function SampleViewAdmin() { }); } - if (isLoading || !sample) { + if (isLoading || !sampeleData) { manageSamples = (
); - } else if (sample && sample.length) { + } else if (sampeleData?.count) { manageSamples = ( <> {sampleList} - + ); - } else if (sample && sample.length === 0) { + } else if (sampeleData?.count === 0) { manageSamples = (
@@ -351,7 +320,7 @@ export default function SampleViewAdmin() {
type.id === qParams.sample_type )?.text || "" ), - value("Facility", "facility", facilityName), + value( + "Facility", + "facility", + qParams.facility ? facilityData?.name || "" : "" + ), ]} />
diff --git a/src/Components/Patient/ShiftCreate.tsx b/src/Components/Patient/ShiftCreate.tsx index fb15039a2ea..0323ddb7c4b 100644 --- a/src/Components/Patient/ShiftCreate.tsx +++ b/src/Components/Patient/ShiftCreate.tsx @@ -7,8 +7,7 @@ import { SHIFTING_VEHICLE_CHOICES, } from "../../Common/constants"; import { Cancel, Submit } from "../Common/components/ButtonV2"; -import { createShift, getPatient } from "../../Redux/actions"; -import { lazy, useEffect, useReducer, useState } from "react"; +import { lazy, useReducer, useState } from "react"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; @@ -22,28 +21,27 @@ import { parsePhoneNumber } from "../../Utils/utils.js"; import { phonePreg } from "../../Common/validation"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; -import { useDispatch } from "react-redux"; import { useTranslation } from "react-i18next"; import Page from "../Common/components/Page.js"; import Card from "../../CAREUI/display/Card.js"; import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField.js"; import { SelectFormField } from "../Form/FormFields/SelectFormField.js"; import { PhoneNumberValidator } from "../Form/FieldValidators.js"; +import useQuery from "../../Utils/request/useQuery.js"; +import routes from "../../Redux/api.js"; +import request from "../../Utils/request/request.js"; const Loading = lazy(() => import("../Common/Loading")); interface patientShiftProps { - facilityId: number; - patientId: number; + facilityId: string; + patientId: string; } export const ShiftCreate = (props: patientShiftProps) => { const { goBack } = useAppHistory(); const { facilityId, patientId } = props; - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); - const [facilityName, setFacilityName] = useState(""); - const [patientName, setPatientName] = useState(""); const [patientCategory, setPatientCategory] = useState(); const { t } = useTranslation(); const { wartime_shifting } = useConfig(); @@ -109,27 +107,22 @@ export const ShiftCreate = (props: patientShiftProps) => { errors: { ...initError }, }; - useEffect(() => { - async function fetchPatientName() { - if (patientId) { - const res = await dispatchAction(getPatient({ id: patientId })); - if (res.data) { - const patient_category = - res.data.last_consultation?.last_daily_round?.patient_category ?? - res.data.last_consultation?.category; - setPatientCategory( - PATIENT_CATEGORIES.find((c) => c.text === patient_category)?.id - ); - setPatientName(res.data.name); - setFacilityName(res.data.facility_object.name); - } - } else { - setPatientName(""); - setFacilityName(""); + const { data: patientData } = useQuery(routes.getPatient, { + pathParams: { + id: patientId, + }, + prefetch: !!patientId, + onResponse: ({ data }) => { + if (data) { + const patient_category = + data.last_consultation?.last_daily_round?.patient_category ?? + data.last_consultation?.category; + setPatientCategory( + PATIENT_CATEGORIES.find((c) => c.text === patient_category)?.id + ); } - } - fetchPatientName(); - }, [dispatchAction, patientId]); + }, + }); const shiftFormReducer = (state = initialState, action: any) => { switch (action.type) { @@ -242,17 +235,22 @@ export const ShiftCreate = (props: patientShiftProps) => { ambulance_number: state.form.ambulance_number, }; - const res = await dispatchAction(createShift(data)); - setIsLoading(false); + await request(routes.createShift, { + body: data, - if (res && res.data && (res.status == 201 || res.status == 200)) { - await dispatch({ type: "set_form", form: initForm }); - Notification.Success({ - msg: "Shift request created successfully", - }); + onResponse: ({ res, data }) => { + setIsLoading(false); - navigate(`/shifting/${res.data.id}`); - } + if (res?.ok && data) { + dispatch({ type: "set_form", form: initForm }); + Notification.Success({ + msg: "Shift request created successfully", + }); + + navigate(`/shifting/${data.id}`); + } + }, + }); } }; @@ -271,8 +269,8 @@ export const ShiftCreate = (props: patientShiftProps) => { diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 280abbce5cc..cfe298bda82 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -20,31 +20,9 @@ export const getFacilityUsers = (id: string, params?: object) => { ); }; -export const getFacilityAssetLocation = ( - facility_external_id: string, - external_id: string -) => - fireRequest( - "getFacilityAssetLocation", - [], - {}, - { facility_external_id, external_id } - ); - // asset bed export const listAssetBeds = (params: object, altKey?: string) => fireRequest("listAssetBeds", [], params, {}, altKey); -export const createAssetBed = ( - params: object, - asset_id: string, - bed_id: string -) => - fireRequest( - "createAssetBed", - [], - { ...params, asset: asset_id, bed: bed_id }, - {} - ); export const partialUpdateAssetBed = (params: object, asset_id: string) => fireRequest( @@ -99,6 +77,11 @@ export const getPatient = (pathParam: object) => { export const updatePatient = (params: object, pathParam: object) => { return fireRequest("updatePatient", [], params, pathParam); }; + +export const transferPatient = (params: object, pathParam: object) => { + return fireRequest("transferPatient", [], params, pathParam); +}; + export const patchPatient = (params: object, pathParam: object) => { return fireRequest("patchPatient", [], params, pathParam); }; @@ -114,9 +97,6 @@ export const getDistrictByState = (pathParam: object) => { export const getDistrictByName = (params: object) => { return fireRequest("getDistrictByName", [], params, null); }; -export const getDistrict = (id: number, key?: string) => { - return fireRequest("getDistrict", [], {}, { id: id }, key); -}; export const getLocalbodyByDistrict = (pathParam: object) => { return fireRequest("getLocalbodyByDistrict", [], {}, pathParam); @@ -126,30 +106,7 @@ export const getWardByLocalBody = (pathParam: object) => { return fireRequest("getWardByLocalBody", [], {}, pathParam); }; -// Local Body -export const getLocalBody = (pathParam: object) => { - return fireRequest("getLocalBody", [], {}, pathParam); -}; - // Sample Test -export const getSampleTestList = (params: object, pathParam: object) => { - return fireRequest("sampleTestList", [], params, pathParam); -}; -export const createSampleTest = (params: object, pathParam: object) => { - return fireRequest("createSampleTest", [], params, pathParam); -}; -export const sampleReport = (id: string, sampleId: string) => { - return fireRequest("sampleReport", [], {}, { id, sampleId }); -}; -export const getTestList = (params: object) => { - return fireRequest("getTestSampleList", [], params); -}; -export const getTestSample = (id: string) => { - return fireRequest("getTestSample", [id], {}); -}; -export const patchSample = (params: object, pathParam: object) => { - return fireRequest("patchSample", [], params, pathParam); -}; export const downloadSampleTests = (params: object) => { return fireRequest("getTestSampleList", [], { ...params, csv: 1 }); }; @@ -173,9 +130,6 @@ export const getConsultationDailyRoundsDetails = (pathParam: object) => { export const createConsultation = (params: object) => { return fireRequest("createConsultation", [], params); }; -export const getConsultationList = (params: object) => { - return fireRequest("getConsultationList", [], params); -}; export const getConsultation = (id: string) => { return fireRequest("getConsultation", [], {}, { id: id }); }; @@ -204,17 +158,10 @@ export const dischargePatient = (params: object, pathParams: object) => { }; //Shift -export const createShift = (params: object) => { - return fireRequest("createShift", [], params); -}; - export const listShiftRequests = (params: object, key: string) => { return fireRequest("listShiftRequests", [], params, null, key); }; -export const completeTransfer = (pathParams: object) => { - return fireRequest("completeTransfer", [], {}, pathParams); -}; export const downloadShiftRequests = (params: object) => { return fireRequest("downloadShiftRequests", [], params); }; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 4893bfbd33f..8b2a3ae08c2 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -74,7 +74,12 @@ import { UserModel, } from "../Components/Users/models"; import { Prescription } from "../Components/Medicine/models"; -import { DailyRoundsModel, PatientModel } from "../Components/Patient/models"; +import { + DailyRoundsModel, + PatientModel, + SampleReportModel, + SampleTestModel, +} from "../Components/Patient/models"; import { PaginatedResponse } from "../Utils/request/types"; import { NotificationData, @@ -505,6 +510,8 @@ const routes = { }, getConsultationList: { path: "/api/v1/consultation/", + method: "GET", + TRes: Type>(), }, createConsultation: { path: "/api/v1/consultation/", @@ -549,6 +556,8 @@ const routes = { getDailyReport: { path: "/api/v1/consultation/{consultationId}/daily_rounds/{id}/", + method: "GET", + TRes: Type(), }, dailyRoundsAnalyse: { path: "/api/v1/consultation/{consultationId}/daily_rounds/analyse/", @@ -644,6 +653,8 @@ const routes = { }, patientList: { path: "/api/v1/patient/", + method: "GET", + TRes: Type>(), }, addPatient: { path: "/api/v1/patient/", @@ -651,7 +662,7 @@ const routes = { }, getPatient: { path: "/api/v1/patient/{id}/", - TBody: Type(), + method: "GET", TRes: Type(), }, updatePatient: { @@ -661,6 +672,8 @@ const routes = { patchPatient: { path: "/api/v1/patient/{id}/", method: "PATCH", + TBody: Type>(), + TRes: Type(), }, transferPatient: { path: "/api/v1/patient/{id}/transfer/", @@ -690,13 +703,19 @@ const routes = { }, sampleTestList: { path: "/api/v1/patient/{patientId}/test_sample/", + method: "GET", + TRes: Type>(), }, createSampleTest: { path: "/api/v1/patient/{patientId}/test_sample/", method: "POST", + TRes: Type(), + TBody: Type(), }, sampleReport: { - path: "/api/v1/patient/{id}/test_sample/{sampleId}/icmr_sample", + path: "/api/v1/patient/{id}/test_sample/{sampleId}/icmr_sample/", + method: "GET", + TRes: Type(), }, // External Results @@ -803,13 +822,19 @@ const routes = { // Sample Test getTestSampleList: { path: "/api/v1/test_sample/", + method: "GET", + TRes: Type>(), }, getTestSample: { - path: "/api/v1/test_sample", + path: "/api/v1/test_sample/{id}/", + method: "GET", + TRes: Type(), }, patchSample: { path: "/api/v1/test_sample/{id}/", method: "PATCH", + TBody: Type(), + TRes: Type(), }, //inventory @@ -905,15 +930,17 @@ const routes = { createShift: { path: "/api/v1/shift/", method: "POST", + TBody: Type>(), + TRes: Type(), }, updateShift: { - path: "/api/v1/shift/{id}", + path: "/api/v1/shift/{id}/", method: "PUT", TBody: Type(), TRes: Type(), }, deleteShiftRecord: { - path: "/api/v1/shift/{id}", + path: "/api/v1/shift/{id}/", method: "DELETE", TRes: Type<{ detail: string }>(), },