From df8c57b4f4abc29de89526fe31a76b00787df396 Mon Sep 17 00:00:00 2001 From: Gokulram A Date: Tue, 19 Mar 2024 12:17:11 +0530 Subject: [PATCH 01/12] Add titrated prescription dosage type (#6565) * feat: option to add titrated drug dose in prescription * Update prescription model and form to use dosage_type and base_dosage instead of is_prn and dosage * Update edit prescription form and fix dosage validation * Refactor administration dosage validation * Fix unit validation in PrescriptionFormValidator * Add dosage information to TimelineNode * Remove dosage from TimelineNode component * fix: renamed cypress dosage field selector to base_dosage * Merge branch 'develop' into fix-issue-6432 * update cypress to match new dosage field's id * fixed linting issue --------- Co-authored-by: Rithvik Nishad Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Co-authored-by: khavinshankar --- .../pageobject/Patient/PatientPrescription.ts | 2 +- .../Form/FormFields/DosageFormField.tsx | 14 +++ .../FormFields/NumericWithUnitsFormField.tsx | 2 + .../Medicine/AdministerMedicine.tsx | 52 ++++++++++- .../Medicine/CreatePrescriptionForm.tsx | 77 ++++++++++++---- .../Medicine/EditPrescriptionForm.tsx | 72 ++++++++++++--- .../Medicine/MedicineAdministration.tsx | 88 ++++++++++++++----- .../AdministrationTable.tsx | 6 +- .../AdministrationTableRow.tsx | 17 +++- .../MedicineAdministrationSheet/index.tsx | 6 +- .../Medicine/PrescriptionBuilder.tsx | 12 ++- .../Medicine/PrescriptionDetailCard.tsx | 71 ++++++++++++--- .../Medicine/PrescriptionsTable.tsx | 15 +++- .../Medicine/PrescrpitionTimeline.tsx | 5 +- src/Components/Medicine/models.ts | 12 ++- src/Components/Medicine/validators.ts | 48 ++++++++-- src/Locale/en/Medicine.json | 6 +- 17 files changed, 409 insertions(+), 96 deletions(-) create mode 100644 src/Components/Form/FormFields/DosageFormField.tsx diff --git a/cypress/pageobject/Patient/PatientPrescription.ts b/cypress/pageobject/Patient/PatientPrescription.ts index c20d32672d..e5934fcb95 100644 --- a/cypress/pageobject/Patient/PatientPrescription.ts +++ b/cypress/pageobject/Patient/PatientPrescription.ts @@ -27,7 +27,7 @@ export class PatientPrescription { } enterDosage(doseAmount: string) { - cy.get("#dosage").type(doseAmount, { force: true }); + cy.get("#base_dosage").type(doseAmount, { force: true }); } selectDosageFrequency(frequency: string) { diff --git a/src/Components/Form/FormFields/DosageFormField.tsx b/src/Components/Form/FormFields/DosageFormField.tsx new file mode 100644 index 0000000000..5f66970191 --- /dev/null +++ b/src/Components/Form/FormFields/DosageFormField.tsx @@ -0,0 +1,14 @@ +import { DOSAGE_UNITS } from "../../Medicine/models"; +import NumericWithUnitsFormField from "./NumericWithUnitsFormField"; +import { FormFieldBaseProps } from "./Utils"; + +type Props = FormFieldBaseProps & { + placeholder?: string; + autoComplete?: string; + min?: string | number; + max?: string | number; +}; + +export default function DosageFormField(props: Props) { + return ; +} diff --git a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx index d0dae7ddff..754079bb52 100644 --- a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx +++ b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx @@ -35,6 +35,7 @@ export default function NumericWithUnitsFormField(props: Props) { autoComplete={props.autoComplete} required={field.required} value={numValue} + disabled={props.disabled} onChange={(e) => field.handleChange(Number(e.target.value) + " " + unitValue) } @@ -48,6 +49,7 @@ export default function NumericWithUnitsFormField(props: Props) { onChange={(e) => field.handleChange(numValue + " " + e.target.value) } + disabled={props.disabled} > {props.units.map((unit) => ( diff --git a/src/Components/Medicine/AdministerMedicine.tsx b/src/Components/Medicine/AdministerMedicine.tsx index 8798b476b0..12b7be3e0d 100644 --- a/src/Components/Medicine/AdministerMedicine.tsx +++ b/src/Components/Medicine/AdministerMedicine.tsx @@ -13,6 +13,8 @@ import dayjs from "../../Utils/dayjs"; import useSlug from "../../Common/hooks/useSlug"; import request from "../../Utils/request/request"; import MedicineRoutes from "./routes"; +import DosageFormField from "../Form/FormFields/DosageFormField"; +import { AdministrationDosageValidator } from "./validators"; interface Props { prescription: Prescription; @@ -24,6 +26,8 @@ export default function AdministerMedicine({ prescription, ...props }: Props) { const consultation = useSlug("consultation"); const [isLoading, setIsLoading] = useState(false); const [notes, setNotes] = useState(""); + const [dosage, setDosage] = useState(); + const [error, setError] = useState(); const [isCustomTime, setIsCustomTime] = useState(false); const [customTime, setCustomTime] = useState( dayjs().format("YYYY-MM-DDTHH:mm") @@ -41,21 +45,45 @@ export default function AdministerMedicine({ prescription, ...props }: Props) { description={
Last administered - - {prescription.last_administered_on - ? formatDateTime(prescription.last_administered_on) + + {" "} + {prescription.last_administration?.administered_date + ? formatDateTime( + prescription.last_administration.administered_date + ) : t("never")} + {prescription.dosage_type === "TITRATED" && ( + + {t("dosage")} + {":"} {prescription.last_administration?.dosage ?? "NA"} + + )} + + Administered by:{" "} + {prescription.last_administration?.administered_by?.username ?? + "NA"} +
} show onClose={() => props.onClose(false)} onConfirm={async () => { + if (prescription.dosage_type === "TITRATED") { + const error = AdministrationDosageValidator( + prescription.base_dosage, + prescription.target_dosage + )(dosage); + setError(error); + if (error) return; + } + setIsLoading(true); const { res } = await request(MedicineRoutes.administerPrescription, { pathParams: { consultation, external_id: prescription.id }, body: { notes, + dosage, administered_date: isCustomTime ? customTime : undefined, }, }); @@ -70,6 +98,24 @@ export default function AdministerMedicine({ prescription, ...props }: Props) {
+ {prescription.dosage_type === "TITRATED" && ( + setDosage(value)} + required + min={prescription.base_dosage} + max={prescription.target_dosage} + disabled={isLoading} + error={error} + errorClassName={error ? "block" : "hidden"} + /> + )} +
-
+ {props.prescription.dosage_type !== "PRN" && ( + { + if (e.value) { + field("dosage_type").onChange({ + name: "dosage_type", + value: "TITRATED", + }); + } else { + field("dosage_type").onChange({ + name: "dosage_type", + value: "REGULAR", + }); + } + }} + /> + )} +
t("PRESCRIPTION_ROUTE_" + key)} optionValue={(key) => key} /> - + {field("dosage_type").value === "TITRATED" ? ( +
+ + +
+ ) : ( + + )}
- {props.prescription.is_prn ? ( + {props.prescription.dosage_type === "PRN" ? ( <> - @@ -130,6 +164,13 @@ export default function CreatePrescriptionForm(props: {
)} + {field("dosage_type").value === "TITRATED" && ( + + )} + )} diff --git a/src/Components/Medicine/EditPrescriptionForm.tsx b/src/Components/Medicine/EditPrescriptionForm.tsx index 2bd3805af7..972b918eed 100644 --- a/src/Components/Medicine/EditPrescriptionForm.tsx +++ b/src/Components/Medicine/EditPrescriptionForm.tsx @@ -1,13 +1,12 @@ import { useState } from "react"; import Form from "../Form/Form"; -import { DOSAGE_UNITS, Prescription } from "./models"; +import { Prescription } from "./models"; import request from "../../Utils/request/request"; import * as Notification from "../../Utils/Notifications"; import useSlug from "../../Common/hooks/useSlug"; import { RequiredFieldValidator } from "../Form/FieldValidators"; import { useTranslation } from "react-i18next"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormField"; import { PRESCRIPTION_FREQUENCIES, PRESCRIPTION_ROUTES, @@ -16,6 +15,8 @@ import TextFormField from "../Form/FormFields/TextFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import { EditPrescriptionFormValidator } from "./validators"; import MedicineRoutes from "./routes"; +import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; +import DosageFormField from "../Form/FormFields/DosageFormField"; interface Props { initial: Prescription; @@ -88,6 +89,27 @@ export default function EditPrescriptionForm(props: Props) { {...field("discontinued_reason")} /> + {props.initial.dosage_type !== "PRN" && ( + { + if (e.value) { + field("dosage_type").onChange({ + name: "dosage_type", + value: "TITRATED", + }); + } else { + field("dosage_type").onChange({ + name: "dosage_type", + value: "REGULAR", + }); + } + }} + /> + )} +
t("PRESCRIPTION_ROUTE_" + key)} optionValue={(key) => key} /> - + {field("dosage_type").value === "TITRATED" ? ( +
+ + +
+ ) : ( + + )}
- {props.initial.is_prn ? ( + {props.initial.dosage_type === "PRN" ? ( <> - @@ -154,6 +193,13 @@ export default function EditPrescriptionForm(props: Props) {
)} + {field("dosage_type").value === "TITRATED" && ( + + )} + )} diff --git a/src/Components/Medicine/MedicineAdministration.tsx b/src/Components/Medicine/MedicineAdministration.tsx index d899a3800f..23aef4b4dd 100644 --- a/src/Components/Medicine/MedicineAdministration.tsx +++ b/src/Components/Medicine/MedicineAdministration.tsx @@ -6,23 +6,31 @@ import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { Error, Success } from "../../Utils/Notifications"; -import { classNames, formatDateTime } from "../../Utils/utils"; +import { formatDateTime } from "../../Utils/utils"; import { useTranslation } from "react-i18next"; import dayjs from "../../Utils/dayjs"; import TextFormField from "../Form/FormFields/TextFormField"; import request from "../../Utils/request/request"; import MedicineRoutes from "./routes"; import useSlug from "../../Common/hooks/useSlug"; +import DosageFormField from "../Form/FormFields/DosageFormField"; +import { AdministrationDosageValidator } from "./validators"; interface Props { prescriptions: Prescription[]; onDone: () => void; } +type DosageField = { + dosage: MedicineAdministrationRecord["dosage"]; + error?: string; +}; + export default function MedicineAdministration(props: Props) { const { t } = useTranslation(); const consultation = useSlug("consultation"); const [shouldAdminister, setShouldAdminister] = useState([]); + const [dosages, setDosages] = useState([]); const [notes, setNotes] = useState( [] ); @@ -39,6 +47,7 @@ export default function MedicineAdministration(props: Props) { useEffect(() => { setShouldAdminister(Array(prescriptions.length).fill(false)); + setDosages(Array(prescriptions.length).fill({ dosage: undefined })); setNotes(Array(prescriptions.length).fill("")); setIsCustomTime(Array(prescriptions.length).fill(false)); setCustomTime( @@ -47,13 +56,31 @@ export default function MedicineAdministration(props: Props) { }, [props.prescriptions]); const handleSubmit = async () => { - const administrations = prescriptions - .map((prescription, i) => ({ - prescription, - notes: notes[i], - administered_date: isCustomTime[i] ? customTime[i] : undefined, - })) - .filter((_, i) => shouldAdminister[i]); + const administrations = []; + + for (let i = 0; i < prescriptions.length; i++) { + if (shouldAdminister[i]) { + if (prescriptions[i].dosage_type === "TITRATED") { + const error = AdministrationDosageValidator( + prescriptions[i].base_dosage, + prescriptions[i].target_dosage + )(dosages[i].dosage); + setDosages((dosages) => { + const newDosages = [...dosages]; + newDosages[i].error = error; + return newDosages; + }); + if (error) return; + } + const administration = { + prescription: prescriptions[i], + notes: notes[i], + dosage: dosages[i].dosage, + administered_date: isCustomTime[i] ? customTime[i] : undefined, + }; + administrations.push(administration); + } + } const ok = await Promise.all( administrations.map(({ prescription, ...body }) => @@ -74,7 +101,6 @@ export default function MedicineAdministration(props: Props) { }; const selectedCount = shouldAdminister.filter(Boolean).length; - const is_prn = prescriptions.some((obj) => obj.is_prn); return (
@@ -85,12 +111,7 @@ export default function MedicineAdministration(props: Props) { readonly selected={shouldAdminister[index]} > -
+
{" "} {t("last_administered")} - {obj.last_administered_on - ? formatDateTime(obj.last_administered_on) + {obj.last_administration?.administered_date + ? formatDateTime(obj.last_administration?.administered_date) : t("never")} + {obj.dosage_type === "TITRATED" && ( + + {t("dosage")} + {":"} {obj.last_administration?.dosage ?? "NA"} + + )}
-
+ {obj.dosage_type === "TITRATED" && ( + + setDosages((dosages) => { + const newDosages = [...dosages]; + newDosages[index].dosage = value; + return newDosages; + }) + } + required + min={obj.base_dosage} + max={obj.target_dosage} + disabled={!shouldAdminister[index]} + error={dosages[index]?.error} + errorClassName={dosages[index]?.error ? "block" : "hidden"} + /> )} - > {t("medicine")}

Dosage &

-

{!prescriptions[0]?.is_prn ? "Frequency" : "Indicator"}

+

+ {prescriptions[0]?.dosage_type !== "PRN" + ? "Frequency" + : "Indicator"} +

diff --git a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx index 4067564041..7a1542b43a 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/AdministrationTableRow.tsx @@ -49,7 +49,7 @@ export default function MedicineAdministrationTableRow({ ), archived: false, }, - key: `${prescription.last_administered_on}`, + key: `${prescription.last_administration?.administered_date}`, } ); @@ -141,7 +141,9 @@ export default function MedicineAdministrationTableRow({ onClose={() => setShowEdit(false)} show={showEdit} title={`${t("edit")} ${t( - prescription.is_prn ? "prn_prescription" : "prescription_medication" + prescription.dosage_type === "PRN" + ? "prn_prescription" + : "prescription_medication" )}: ${ prescription.medicine_object?.name ?? prescription.medicine_old }`} @@ -193,9 +195,16 @@ export default function MedicineAdministrationTableRow({
-

{prescription.dosage}

+ {prescription.dosage_type !== "TITRATED" ? ( +

{prescription.base_dosage}

+ ) : ( +

+ {prescription.base_dosage} - {prescription.target_dosage} +

+ )} +

- {!prescription.is_prn + {prescription.dosage_type !== "PRN" ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) : prescription.indicator}

diff --git a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx index dba0943db2..48c7e2aaff 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx @@ -27,7 +27,11 @@ const MedicineAdministrationSheet = ({ readonly, is_prn }: Props) => { const [showDiscontinued, setShowDiscontinued] = useState(false); - const filters = { is_prn, prescription_type: "REGULAR", limit: 100 }; + const filters = { + dosage_type: is_prn ? "PRN" : "REGULAR,TITRATED", + prescription_type: "REGULAR", + limit: 100, + }; const { data, loading, refetch } = useQuery( MedicineRoutes.listPrescriptions, diff --git a/src/Components/Medicine/PrescriptionBuilder.tsx b/src/Components/Medicine/PrescriptionBuilder.tsx index 39bf9b2f50..998b96f331 100644 --- a/src/Components/Medicine/PrescriptionBuilder.tsx +++ b/src/Components/Medicine/PrescriptionBuilder.tsx @@ -31,7 +31,11 @@ export default function PrescriptionBuilder({ const { data, refetch } = useQuery(MedicineRoutes.listPrescriptions, { pathParams: { consultation }, - query: { is_prn, prescription_type, limit: 100 }, + query: { + dosage_type: is_prn ? "PRN" : "REGULAR,TITRATED", + prescription_type, + limit: 100, + }, }); return ( @@ -118,5 +122,7 @@ export default function PrescriptionBuilder({ ); } -const DefaultPrescription: Partial = { is_prn: false }; -const DefaultPRNPrescription: Partial = { is_prn: true }; +const DefaultPrescription: Partial = { + dosage_type: "REGULAR", +}; +const DefaultPRNPrescription: Partial = { dosage_type: "PRN" }; diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx index 0e1f28e965..7112f75d59 100644 --- a/src/Components/Medicine/PrescriptionDetailCard.tsx +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -40,7 +40,11 @@ export default function PrescriptionDetailCard({ > {prescription.prescription_type === "DISCHARGE" && `${t("discharge")} `} - {t(prescription.is_prn ? "prn_prescription" : "prescription")} + {t( + prescription.dosage_type === "PRN" + ? "prn_prescription" + : "prescription" + )} {` #${prescription.id?.slice(-5)}`} {prescription.discontinued && ( @@ -82,37 +86,64 @@ export default function PrescriptionDetailCard({
-
- +
+ {prescription.medicine_object?.name ?? prescription.medicine_old} {prescription.route && t("PRESCRIPTION_ROUTE_" + prescription.route)} - - {prescription.dosage} - + {prescription.dosage_type === "TITRATED" ? ( + <> + + {prescription.base_dosage} + + + {prescription.target_dosage} + + + ) : ( + + {prescription.base_dosage} + + )} - {prescription.is_prn ? ( + {prescription.dosage_type === "PRN" ? ( <> {prescription.indicator} {prescription.max_dosage} {prescription.min_hours_between_doses && @@ -128,21 +159,33 @@ export default function PrescriptionDetailCard({ prescription.frequency.toUpperCase() )} - + {prescription.days} )} + {prescription.instruction_on_titration && ( + + + + )} + {prescription.notes && ( - + )} {prescription.discontinued && ( {prescription.discontinued_reason} diff --git a/src/Components/Medicine/PrescriptionsTable.tsx b/src/Components/Medicine/PrescriptionsTable.tsx index a1b039e71d..6cedc4ec29 100644 --- a/src/Components/Medicine/PrescriptionsTable.tsx +++ b/src/Components/Medicine/PrescriptionsTable.tsx @@ -38,7 +38,11 @@ export default function PrescriptionsTable({ const { data } = useQuery(MedicineRoutes.listPrescriptions, { pathParams: { consultation }, - query: { is_prn, prescription_type, limit: 100 }, + query: { + dosage_type: is_prn ? "PRN" : "REGULAR,TITRATED", + prescription_type, + limit: 100, + }, }); const lastModified = data?.results[0]?.modified_date; @@ -193,8 +197,11 @@ export default function PrescriptionsTable({ min_hours_between_doses__pretty: obj.min_hours_between_doses && obj.min_hours_between_doses + " hour(s)", - last_administered__pretty: obj.last_administered_on ? ( - + last_administered__pretty: obj.last_administration + ?.administered_date ? ( + ) : ( "never" ), @@ -275,7 +282,7 @@ export default function PrescriptionsTable({ const COMMON_TKEYS = { medicine: "medicine", route: "route__pretty", - dosage: "dosage", + base_dosage: "base_dosage", }; const REGULAR_NORMAL_TKEYS = { diff --git a/src/Components/Medicine/PrescrpitionTimeline.tsx b/src/Components/Medicine/PrescrpitionTimeline.tsx index 03b9f746bb..27f997e242 100644 --- a/src/Components/Medicine/PrescrpitionTimeline.tsx +++ b/src/Components/Medicine/PrescrpitionTimeline.tsx @@ -119,8 +119,9 @@ const MedicineAdministeredNode = ({ name="medicine" event={event} className={classNames(event.cancelled && "opacity-70")} - // TODO: to add administered dosage when Titrated Prescriptions are implemented - titleSuffix={`administered the medicine at ${formatTime( + titleSuffix={`administered ${ + event.administration.dosage + } dose of the medicine at ${formatTime( event.administration.administered_date )}.`} actions={ diff --git a/src/Components/Medicine/models.ts b/src/Components/Medicine/models.ts index b97e2c5225..1a5f6fe8a1 100644 --- a/src/Components/Medicine/models.ts +++ b/src/Components/Medicine/models.ts @@ -18,7 +18,10 @@ interface BasePrescription { medicine_object?: MedibaseMedicine; medicine_old?: string; route?: (typeof PRESCRIPTION_ROUTES)[number]; - dosage: DosageValue; + dosage_type?: "REGULAR" | "TITRATED" | "PRN"; + base_dosage?: DosageValue; + target_dosage?: DosageValue; + instruction_on_titration?: string; notes?: string; meta?: object; readonly prescription_type?: "DISCHARGE" | "REGULAR"; @@ -26,7 +29,7 @@ interface BasePrescription { discontinued_reason?: string; readonly prescribed_by: PerformedByModel; readonly discontinued_date: string; - readonly last_administered_on?: string; + readonly last_administration?: MedicineAdministrationRecord; readonly is_migrated: boolean; readonly created_date: string; readonly modified_date: string; @@ -44,7 +47,7 @@ export interface NormalPrescription extends BasePrescription { | "QOD" | "QWK"; days?: number; - is_prn: false; + dosage_type: "REGULAR" | "TITRATED"; indicator?: undefined; max_dosage?: undefined; min_hours_between_doses?: undefined; @@ -54,7 +57,7 @@ export interface PRNPrescription extends BasePrescription { indicator: string; max_dosage?: DosageValue; min_hours_between_doses?: number; - is_prn: true; + dosage_type: "PRN"; frequency?: undefined; days?: undefined; } @@ -65,6 +68,7 @@ export type MedicineAdministrationRecord = { readonly id: string; readonly prescription: Prescription; notes: string; + dosage?: string; administered_date?: string; readonly administered_by: PerformedByModel; readonly archived_by: PerformedByModel | undefined; diff --git a/src/Components/Medicine/validators.ts b/src/Components/Medicine/validators.ts index 40261646d0..5bc1335f2e 100644 --- a/src/Components/Medicine/validators.ts +++ b/src/Components/Medicine/validators.ts @@ -6,10 +6,21 @@ export const PrescriptionFormValidator = () => { return (form: Prescription): FormErrors => { const errors: Partial> = {}; errors.medicine_object = RequiredFieldValidator()(form.medicine_object); - errors.dosage = RequiredFieldValidator()(form.dosage); - if (form.is_prn) + if (form.dosage_type === "TITRATED") { + errors.base_dosage = RequiredFieldValidator()(form.base_dosage); + errors.target_dosage = RequiredFieldValidator()(form.target_dosage); + if ( + form.base_dosage && + form.target_dosage && + form.base_dosage.split(" ")[1] !== form.target_dosage.split(" ")[1] + ) { + errors.base_dosage = "Unit must be same as target dosage's unit"; + errors.target_dosage = "Unit must be same as base dosage's unit"; + } + } else errors.base_dosage = RequiredFieldValidator()(form.base_dosage); + if (form.dosage_type === "PRN") errors.indicator = RequiredFieldValidator()(form.indicator); - if (!form.is_prn) + if (form.dosage_type !== "PRN") errors.frequency = RequiredFieldValidator()(form.frequency); return errors; }; @@ -31,10 +42,10 @@ const PRESCRIPTION_COMPARE_FIELDS: (keyof Prescription)[] = [ "medicine", "days", "discontinued", - "dosage", + "base_dosage", "frequency", "indicator", - "is_prn", + "dosage_type", "max_dosage", "min_hours_between_doses", "prescription_type", @@ -47,3 +58,30 @@ export const comparePrescriptions = (a: Prescription, b: Prescription) => { a.medicine_object?.id === b.medicine_object?.id ); }; + +export const AdministrationDosageValidator = ( + base_dosage: Prescription["base_dosage"], + target_dosage: Prescription["target_dosage"] +) => { + return (value: Prescription["base_dosage"]) => { + const getDosageValue = (dosage: string | undefined) => { + return dosage ? Number(dosage.split(" ")[0]) : undefined; + }; + + const valueDosage = getDosageValue(value); + const baseDosage = getDosageValue(base_dosage); + const targetDosage = getDosageValue(target_dosage); + + if (!valueDosage) return "This field is required"; + + if (value?.split(" ")[1] !== base_dosage?.split(" ")[1]) + return "Unit must be the same as start and target dosage's unit"; + + if ( + baseDosage && + targetDosage && + (valueDosage < baseDosage || valueDosage > targetDosage) + ) + return "Dosage should be between start and target dosage"; + }; +}; diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json index f15bd7c802..95ddda7c1f 100644 --- a/src/Locale/en/Medicine.json +++ b/src/Locale/en/Medicine.json @@ -2,6 +2,10 @@ "medicine": "Medicine", "route": "Route", "dosage": "Dosage", + "start_dosage": "Start Dosage", + "target_dosage": "Target Dosage", + "instruction_on_titration": "Instruction on titration", + "titrate_dosage": "Titrate Dosage", "indicator": "Indicator", "inidcator_event": "Indicator Event", "max_dosage_24_hrs": "Max. dosage in 24 hrs.", @@ -54,4 +58,4 @@ "PRESCRIPTION_FREQUENCY_Q4H": "4th hourly", "PRESCRIPTION_FREQUENCY_QOD": "Alternate day", "PRESCRIPTION_FREQUENCY_QWK": "Once a week" -} \ No newline at end of file +} From 2d7abfb6032e5517a682da739f354634b42e4811 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Tue, 19 Mar 2024 15:20:52 +0530 Subject: [PATCH 02/12] New Cypress Test | Normal Log Update for Admission and Non- Admission Patients | Patient Consultation Module (#7400) * log update * log update * updated the additional symptoms * Normal log update modification part done * pre-final part * pre-final part * final change * flaky test * syntax error * added wait * added scrollinto view * added wait * fixing scroll * revert canvas verification --- .../patient_spec/patient_consultation.cy.ts | 1 - .../e2e/patient_spec/patient_logupdate.cy.ts | 156 ++++++++++++++++++ .../pageobject/Patient/PatientConsultation.ts | 1 + .../pageobject/Patient/PatientLogupdate.ts | 88 ++++++++++ cypress/support/commands.ts | 8 + cypress/support/index.ts | 4 + package-lock.json | 22 +-- package.json | 2 +- src/CAREUI/display/Timeline.tsx | 2 +- .../ConsultationUpdatesTab.tsx | 2 +- .../DailyRounds/DefaultLogUpdateCard.tsx | 5 +- .../Consultations/PrimaryParametersPlot.tsx | 2 +- .../Patient/DailyRoundListDetails.tsx | 5 +- src/Components/Patient/PatientInfoCard.tsx | 5 +- 14 files changed, 279 insertions(+), 24 deletions(-) create mode 100644 cypress/e2e/patient_spec/patient_logupdate.cy.ts create mode 100644 cypress/pageobject/Patient/PatientLogupdate.ts diff --git a/cypress/e2e/patient_spec/patient_consultation.cy.ts b/cypress/e2e/patient_spec/patient_consultation.cy.ts index 99565a24cc..36c3ebdcb4 100644 --- a/cypress/e2e/patient_spec/patient_consultation.cy.ts +++ b/cypress/e2e/patient_spec/patient_consultation.cy.ts @@ -376,7 +376,6 @@ describe("Patient Consultation in multiple combination", () => { it("Edit created consultation to existing patient", () => { patientPage.visitPatient("Dummy Patient 13"); patientConsultationPage.clickEditConsultationButton(); - cy.wait(5000); patientConsultationPage.typePatientIllnessHistory("editted"); patientConsultationPage.selectPatientDiagnosis( diagnosis5, diff --git a/cypress/e2e/patient_spec/patient_logupdate.cy.ts b/cypress/e2e/patient_spec/patient_logupdate.cy.ts new file mode 100644 index 0000000000..57f3198067 --- /dev/null +++ b/cypress/e2e/patient_spec/patient_logupdate.cy.ts @@ -0,0 +1,156 @@ +import { afterEach, before, beforeEach, cy, describe, it } from "local-cypress"; +import LoginPage from "../../pageobject/Login/LoginPage"; +import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; +import PatientLogupdate from "../../pageobject/Patient/PatientLogupdate"; + +describe("Patient Log Update in Normal, Critical and TeleIcu", () => { + const loginPage = new LoginPage(); + const patientConsultationPage = new PatientConsultationPage(); + const patientPage = new PatientPage(); + const patientLogupdate = new PatientLogupdate(); + const domicilaryPatient = "Dummy Patient 11"; + const patientCategory = "Abnormal"; + const additionalSymptoms = "ASYMPTOMATIC"; + const physicalExamination = "physical examination details"; + const otherExamination = "Other"; + const patientSystolic = "119"; + const patientDiastolic = "150"; + const patientModifiedSystolic = "120"; + const patientModifiedDiastolic = "145"; + const patientPulse = "152"; + const patientTemperature = "96.6"; + const patientRespiratory = "140"; + const patientSpo2 = "15"; + const patientRhythmType = "Regular"; + const patientRhythm = "Normal Rhythm"; + + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Create a new log teleicu update for a domicilary care patient and verify the copy previous value function", () => { + patientPage.visitPatient("Dummy Patient 11"); + patientConsultationPage.clickEditConsultationButton(); + patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); + cy.submitButton("Update Consultation"); + cy.verifyNotification("Consultation updated successfully"); + patientLogupdate.clickLogupdate(); + patientLogupdate.typePhysicalExamination(physicalExamination); + patientLogupdate.typeOtherDetails(otherExamination); + patientLogupdate.typeAdditionalSymptoms(additionalSymptoms); + patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.typeSystolic(patientSystolic); + patientLogupdate.typeDiastolic(patientDiastolic); + patientLogupdate.typePulse(patientPulse); + patientLogupdate.typeTemperature(patientTemperature); + patientLogupdate.typeRespiratory(patientRespiratory); + patientLogupdate.typeSpo2(patientSpo2); + patientLogupdate.selectRhythm(patientRhythmType); + patientLogupdate.typeRhythm(patientRhythm); + cy.get("#consciousness_level-2").click(); + cy.submitButton("Save"); + cy.verifyNotification("Consultation Updates details created successfully"); + // verify the copied previous value + cy.closeNotification(); + patientLogupdate.clickLogupdate(); + patientLogupdate.clickCopyPreviousValue(); + patientLogupdate.selectPatientCategory(patientCategory); + cy.submitButton("Save"); + cy.closeNotification(); + cy.verifyContentPresence("#physical_examination_info", [ + physicalExamination, + ]); + cy.verifyContentPresence("#rhythm_detail", [patientRhythm]); + cy.submitButton("Continue"); + cy.verifyNotification("Consultation Updates details updated successfully"); + }); + + it("Create a new log normal update for a domicilary care patient and edit it", () => { + patientPage.visitPatient(domicilaryPatient); + patientConsultationPage.clickEditConsultationButton(); + patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); + cy.submitButton("Update Consultation"); + cy.verifyNotification("Consultation updated successfully"); + patientLogupdate.clickLogupdate(); + patientLogupdate.typePhysicalExamination(physicalExamination); + patientLogupdate.typeOtherDetails(otherExamination); + patientLogupdate.typeAdditionalSymptoms(additionalSymptoms); + patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.typeSystolic(patientSystolic); + patientLogupdate.typeDiastolic(patientDiastolic); + patientLogupdate.typePulse(patientPulse); + patientLogupdate.typeTemperature(patientTemperature); + patientLogupdate.typeRespiratory(patientRespiratory); + patientLogupdate.typeSpo2(patientSpo2); + patientLogupdate.selectRhythm(patientRhythmType); + patientLogupdate.typeRhythm(patientRhythm); + cy.get("#consciousness_level-2").click(); + cy.submitButton("Save"); + cy.verifyNotification("Consultation Updates details created successfully"); + // edit the card and verify the data. + patientLogupdate.clickLogupdateCard("#dailyround-entry", patientCategory); + cy.verifyContentPresence("#consultation-preview", [ + patientCategory, + patientDiastolic, + patientSystolic, + physicalExamination, + otherExamination, + patientPulse, + patientTemperature, + patientRespiratory, + patientSpo2, + patientRhythm, + ]); + patientLogupdate.clickUpdateDetail(); + patientLogupdate.clickClearButtonInElement("#systolic"); + patientLogupdate.typeSystolic(patientModifiedSystolic); + patientLogupdate.clickClearButtonInElement("#diastolic"); + patientLogupdate.typeDiastolic(patientModifiedDiastolic); + cy.submitButton("Continue"); + cy.verifyNotification("Consultation Updates details updated successfully"); + patientLogupdate.clickLogupdateCard("#dailyround-entry", patientCategory); + cy.verifyContentPresence("#consultation-preview", [ + patientModifiedDiastolic, + patientModifiedSystolic, + ]); + }); + + it("Create a new log normal update for a admission patient and verify its reflection in cards", () => { + patientPage.visitPatient("Dummy Patient 13"); + patientLogupdate.clickLogupdate(); + cy.verifyNotification("Please assign a bed to the patient"); + patientLogupdate.selectBed("Dummy Bed 6"); + cy.closeNotification(); + patientLogupdate.clickLogupdate(); + patientLogupdate.typePhysicalExamination(physicalExamination); + patientLogupdate.typeOtherDetails(otherExamination); + patientLogupdate.typeAdditionalSymptoms(additionalSymptoms); + patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.typeSystolic(patientSystolic); + patientLogupdate.typeDiastolic(patientDiastolic); + patientLogupdate.typePulse(patientPulse); + patientLogupdate.typeTemperature(patientTemperature); + patientLogupdate.typeRespiratory(patientRespiratory); + patientLogupdate.typeSpo2(patientSpo2); + patientLogupdate.selectRhythm(patientRhythmType); + patientLogupdate.typeRhythm(patientRhythm); + cy.get("#consciousness_level-2").click(); + cy.submitButton("Save"); + cy.verifyNotification("Consultation Updates details created successfully"); + // Verify the card content + cy.get("#basic-information").scrollIntoView(); + cy.verifyContentPresence("#basic-information", [additionalSymptoms]); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index d88c79dafd..db72765444 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -120,6 +120,7 @@ export class PatientConsultationPage { "#consultation-buttons", "Edit Consultation Details" ); + cy.wait(3000); } visitShiftRequestPage() { diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts new file mode 100644 index 0000000000..8b15e4dd4f --- /dev/null +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -0,0 +1,88 @@ +class PatientLogupdate { + clickLogupdate() { + cy.get("#log-update").scrollIntoView(); + cy.verifyAndClickElement("#log-update", "Log Update"); + cy.wait(2000); + } + + selectBed(bed: string) { + cy.searchAndSelectOption("input[name='bed']", bed); + cy.submitButton("Move to bed"); + cy.wait(2000); + } + + selectPatientCategory(category: string) { + cy.clickAndSelectOption("#patient_category", category); + } + + typePhysicalExamination(examination: string) { + cy.get("#physical_examination_info").click().type(examination); + cy.get("#physical_examination_info").should("contain", examination); + } + + typeOtherDetails(details: string) { + cy.get("#other_details").click().type(details); + } + + typeAdditionalSymptoms(symptoms: string) { + cy.clickAndSelectOption("#additional_symptoms", symptoms); + } + + typeSystolic(systolic: string) { + cy.searchAndSelectOption("#systolic", systolic); + } + + typeDiastolic(diastolic: string) { + cy.searchAndSelectOption("#diastolic", diastolic); + } + + typePulse(pulse: string) { + cy.searchAndSelectOption("#pulse", pulse); + } + + typeTemperature(temperature: string) { + cy.searchAndSelectOption("#temperature", temperature); + } + + typeRespiratory(respiratory: string) { + cy.searchAndSelectOption("#resp", respiratory); + } + + typeSpo2(spo: string) { + cy.searchAndSelectOption("#ventilator_spo2", spo); + } + + selectRhythm(rhythm: string) { + cy.clickAndSelectOption("#rhythm", rhythm); + } + + typeRhythm(rhythm: string) { + cy.get("#rhythm_detail").click().type(rhythm); + } + + clickLogupdateCard(element, patientCategory) { + cy.get(element).scrollIntoView(); + cy.verifyContentPresence(element, [patientCategory]); + cy.get(element).first().contains("View Details").click(); + cy.wait(3000); + } + + clickUpdateDetail() { + cy.verifyAndClickElement("#consultation-preview", "Update Details"); + cy.wait(3000); + } + + clickClearButtonInElement(elementId) { + cy.get(elementId).find("#clear-button").click(); + } + + clickVitals() { + cy.get("#consultation_tab_nav").scrollIntoView(); + cy.verifyAndClickElement("#consultation_tab_nav", "Vitals"); + } + + clickCopyPreviousValue() { + cy.get("#clone_last").click(); + } +} +export default PatientLogupdate; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 5170635647..f4b7becdca 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -181,3 +181,11 @@ Cypress.Commands.add("closeNotification", () => { cy.wrap($div).click(); }); }); + +Cypress.Commands.add("verifyContentPresence", (selector, texts) => { + cy.get(selector).then(($el) => { + texts.forEach((text) => { + cy.wrap($el).should("contain", text); + }); + }); +}); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 46e695b650..fbaaa4f18e 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -34,6 +34,10 @@ declare global { ): Chainable; preventPrint(): Chainable; closeNotification(): Chainable; + verifyContentPresence( + selector: string, + texts: string[] + ): Chainable; } } } diff --git a/package-lock.json b/package-lock.json index 497be83b96..95740c9629 100644 --- a/package-lock.json +++ b/package-lock.json @@ -87,7 +87,7 @@ "@typescript-eslint/parser": "^5.61.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.14", - "cypress": "^13.5.0", + "cypress": "^13.7.0", "cypress-localstorage-commands": "^2.2.3", "cypress-split": "^1.20.1", "eslint": "^8.44.0", @@ -8849,21 +8849,20 @@ "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" }, "node_modules/cypress": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.5.0.tgz", - "integrity": "sha512-oh6U7h9w8wwHfzNDJQ6wVcAeXu31DlIYlNOBvfd6U4CcB8oe4akawQmH+QJVOMZlM42eBoCne015+svVqdwdRQ==", + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.7.0.tgz", + "integrity": "sha512-UimjRSJJYdTlvkChcdcfywKJ6tUYuwYuk/n1uMMglrvi+ZthNhoRYcxnWgTqUtkl17fXrPAsD5XT2rcQYN1xKA==", "dev": true, "hasInstallScript": true, "dependencies": { "@cypress/request": "^3.0.0", "@cypress/xvfb": "^1.2.4", - "@types/node": "^18.17.5", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", - "buffer": "^5.6.0", + "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "check-more-types": "^2.24.0", @@ -8881,7 +8880,7 @@ "figures": "^3.2.0", "fs-extra": "^9.1.0", "getos": "^3.2.1", - "is-ci": "^3.0.0", + "is-ci": "^3.0.1", "is-installed-globally": "~0.4.0", "lazy-ass": "^1.6.0", "listr2": "^3.8.3", @@ -8937,15 +8936,6 @@ "cypress-split-preview": "bin/preview.js" } }, - "node_modules/cypress/node_modules/@types/node": { - "version": "18.18.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.18.9.tgz", - "integrity": "sha512-0f5klcuImLnG4Qreu9hPj/rEfFq6YRc5n2mAjSsH+ec/mJL+3voBH0+8T7o8RpFjH7ovc+TRsL/c7OYIQsPTfQ==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, "node_modules/cypress/node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", diff --git a/package.json b/package.json index 7c5ab56133..244b6c125b 100644 --- a/package.json +++ b/package.json @@ -127,7 +127,7 @@ "@typescript-eslint/parser": "^5.61.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.14", - "cypress": "^13.5.0", + "cypress": "^13.7.0", "cypress-localstorage-commands": "^2.2.3", "cypress-split": "^1.20.1", "eslint": "^8.44.0", diff --git a/src/CAREUI/display/Timeline.tsx b/src/CAREUI/display/Timeline.tsx index 49ace78bd8..f8e7991d3b 100644 --- a/src/CAREUI/display/Timeline.tsx +++ b/src/CAREUI/display/Timeline.tsx @@ -23,7 +23,7 @@ const TimelineContext = createContext(""); export default function Timeline({ className, children, name }: TimelineProps) { return ( -
+
    {children} diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index fdbf484632..e8a64b5310 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -130,7 +130,7 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => { )}
    -
    +
    { const { t } = useTranslation(); return ( -
    +
    -
    +
    diff --git a/src/Components/Patient/DailyRoundListDetails.tsx b/src/Components/Patient/DailyRoundListDetails.tsx index 02ad9aa47f..0896cb5310 100644 --- a/src/Components/Patient/DailyRoundListDetails.tsx +++ b/src/Components/Patient/DailyRoundListDetails.tsx @@ -58,7 +58,10 @@ export const DailyRoundListDetails = (props: any) => { title={`Consultation Update #${id}`} backUrl={`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/daily-rounds`} > -
    +
    diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 1d9be149be..e91ce64562 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -492,7 +492,10 @@ export default function PatientInfoCard(props: { {patient.is_active && consultation?.id && !consultation?.discharge_date && ( -
    +
    Date: Tue, 19 Mar 2024 15:43:54 +0530 Subject: [PATCH 03/12] Track occupation of patient (#7152) * Add OCCUPATION_TYPES constant and update PatientHome and PatientRegister components * Add patient occupation field to patient registration cypress Test * convert existing code to use reusable component * Update grid layout in PatientHome component --------- Co-authored-by: Ashesh3 <3626859+Ashesh3@users.noreply.github.com> --- .../patient_spec/patient_registration.cy.ts | 8 +++-- cypress/pageobject/Patient/PatientCreation.ts | 29 +++++++----------- src/Common/constants.tsx | 12 ++++++++ src/Components/Patient/PatientHome.tsx | 15 +++++++++- src/Components/Patient/PatientRegister.tsx | 30 ++++++++++++++++++- src/Components/Patient/models.tsx | 8 +++++ 6 files changed, 80 insertions(+), 22 deletions(-) diff --git a/cypress/e2e/patient_spec/patient_registration.cy.ts b/cypress/e2e/patient_spec/patient_registration.cy.ts index eb6bfc079f..d0ec3d7548 100644 --- a/cypress/e2e/patient_spec/patient_registration.cy.ts +++ b/cypress/e2e/patient_spec/patient_registration.cy.ts @@ -59,6 +59,7 @@ describe("Patient Creation with consultation", () => { const patientTransferFacility = "Dummy Shifting Center"; const patientTransferName = "Dummy Patient 10"; const patientExternalName = "Patient 20"; + const patientOccupation = "Student"; before(() => { loginPage.loginAsDisctrictAdmin(); @@ -88,6 +89,7 @@ describe("Patient Creation with consultation", () => { facilityPage.selectDistrictOnPincode(patientOneDistrict); facilityPage.selectLocalBody(patientOneLocalbody); facilityPage.selectWard(patientOneWard); + patientPage.selectPatientOccupation(patientOccupation); // Patient Medical History patientMedicalHistory.typePatientPresentHealth(patientOnePresentHealth); patientMedicalHistory.typePatientOngoingMedication( @@ -115,7 +117,8 @@ describe("Patient Creation with consultation", () => { phone_number, emergency_phone_number, yearOfBirth, - patientOneBloodGroup + patientOneBloodGroup, + patientOccupation ); patientMedicalHistory.verifyPatientMedicalDetails( patientOnePresentHealth, @@ -202,7 +205,8 @@ describe("Patient Creation with consultation", () => { phone_number, emergency_phone_number, yearOfBirth, - patientOneUpdatedBloodGroup + patientOneUpdatedBloodGroup, + patientOccupation ); // Verify No medical history patientMedicalHistory.verifyNoSymptosPresent("Diabetes"); diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index 07d87e024a..833cd95a3f 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -21,13 +21,8 @@ export class PatientPage { } selectFacility(facilityName: string) { - cy.get("input[name='facilities']") - .type(facilityName) - .then(() => { - cy.get("[role='option']").contains(facilityName).click(); - }); - cy.get("button").should("contain", "Select"); - cy.get("button").get("#submit").click(); + cy.searchAndSelectOption("input[name='facilities']", facilityName); + cy.submitButton("Select"); } interceptCreatePatientAPI() { @@ -85,19 +80,15 @@ export class PatientPage { } selectPatientGender(gender: string) { - cy.get("[data-testid=Gender] button") - .click() - .then(() => { - cy.get("[role='option']").contains(gender).click(); - }); + cy.clickAndSelectOption("[data-testid=Gender] button", gender); } selectPatientBloodGroup(bloodgroup: string) { - cy.get("#blood_group") - .click() - .then(() => { - cy.get("[role='option']").contains(bloodgroup).click(); - }); + cy.clickAndSelectOption("#blood_group", bloodgroup); + } + + selectPatientOccupation(occupation: string) { + cy.clickAndSelectOption("#occupation", occupation); } clickCreatePatient() { @@ -146,7 +137,8 @@ export class PatientPage { phoneNumber, emergencyPhoneNumber, yearOfBirth, - bloodGroup + bloodGroup, + occupation ) { cy.url().should("include", "/facility/"); cy.get("[data-testid=patient-dashboard]").then(($dashboard) => { @@ -157,6 +149,7 @@ export class PatientPage { expect($dashboard).to.contain(emergencyPhoneNumber); expect($dashboard).to.contain(yearOfBirth); expect($dashboard).to.contain(bloodGroup); + expect($dashboard).to.contain(occupation); }); } diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index e452ed5abb..c5e8880cb5 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1172,3 +1172,15 @@ export const IN_LANDLINE_AREA_CODES = [ "891", "4822", ]; +export const OCCUPATION_TYPES = [ + { id: 1, text: "Student", value: "STUDENT" }, + { + id: 2, + text: "Businessman", + value: "BUSINESSMAN", + }, + { id: 3, text: "Healthcare Worker", value: "HEALTH_CARE_WORKER" }, + { id: 4, text: "Healthcare Lab Worker", value: "HEALTH_CARE_LAB_WORKER" }, + { id: 5, text: "Animal Handler", value: "ANIMAL_HANDLER" }, + { id: 6, text: "Others", value: "OTHERS" }, +]; diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 841a8d31dd..59ec27b3c3 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -5,6 +5,7 @@ import { DISCHARGE_REASONS, GENDER_TYPES, SAMPLE_TEST_STATUS, + OCCUPATION_TYPES, } from "../../Common/constants"; import * as Notification from "../../Utils/Notifications"; @@ -39,6 +40,10 @@ import PaginatedList from "../../CAREUI/misc/PaginatedList"; const Loading = lazy(() => import("../Common/Loading")); +export const parseOccupation = (occupation: string | undefined) => { + return OCCUPATION_TYPES.find((i) => i.value === occupation)?.text; +}; + export const PatientHome = (props: any) => { const { facilityId, id } = props; const [showShifts, setShowShifts] = useState(false); @@ -419,7 +424,7 @@ export const PatientHome = (props: any) => { {patientGender} | {patientData.blood_group || "-"}

    -
    +
    Date of Birth @@ -530,6 +535,14 @@ export const PatientHome = (props: any) => {
    )} +
    +
    + Occupation +
    +
    + {parseOccupation(patientData.meta_info?.occupation) || "-"} +
    +
    diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index efc269fa00..eb6c7104c0 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -5,6 +5,7 @@ import { DISEASE_STATUS, GENDER_TYPES, MEDICAL_HISTORY_CHOICES, + OCCUPATION_TYPES, TEST_TYPE, VACCINES, } from "../../Common/constants"; @@ -41,7 +42,7 @@ import { HCXPolicyModel } from "../HCX/models"; import HCXPolicyValidator from "../HCX/validators"; import InsuranceDetailsBuilder from "../HCX/InsuranceDetailsBuilder"; import LinkABHANumberModal from "../ABDM/LinkABHANumberModal"; -import { PatientModel } from "./models"; +import { PatientModel, Occupation } from "./models"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import RadioFormField from "../Form/FormFields/RadioFormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; @@ -84,6 +85,7 @@ const medicalHistoryChoices = MEDICAL_HISTORY_CHOICES.reduce( const genderTypes = GENDER_TYPES; const diseaseStatus = [...DISEASE_STATUS]; const bloodGroups = [...BLOOD_GROUPS]; +const occupationTypes = OCCUPATION_TYPES; const testType = [...TEST_TYPE]; const vaccines = ["Select", ...VACCINES]; @@ -168,6 +170,12 @@ const patientFormReducer = (state = initialState, action: any) => { return state; } }; +export const parseOccupationFromExt = (occupation: Occupation) => { + const occupationObject = OCCUPATION_TYPES.find( + (item) => item.value === occupation + ); + return occupationObject?.id; +}; export const PatientRegister = (props: PatientRegisterProps) => { const authUser = useAuthUser(); @@ -304,6 +312,10 @@ export const PatientRegister = (props: PatientRegisterProps) => { ? parseGenderFromExt(data.gender, state.form.gender) : state.form.gender, }); + field.onChange({ + name: "occupation", + value: data.meta_info?.occupation ?? state.form.occupation, + }); field.onChange({ name: "test_id", value: data.test_id ? data.test_id : state.form.test_id, @@ -421,6 +433,10 @@ export const PatientRegister = (props: PatientRegisterProps) => { data.instituion_of_health_care_worker ? data.instituion_of_health_care_worker : "", + meta_info: data.meta_info ?? {}, + occupation: data.meta_info?.occupation + ? parseOccupationFromExt(data.meta_info.occupation) + : null, number_of_primary_contacts: data.number_of_primary_contacts ? data.number_of_primary_contacts @@ -745,6 +761,10 @@ export const PatientRegister = (props: PatientRegisterProps) => { local_body: formData.nationality === "India" ? formData.local_body : undefined, ward: formData.ward, + meta_info: { + ...state.form?.meta_info, + occupation: formData.occupation ?? null, + }, village: formData.village, address: formData.address ? formData.address : undefined, permanent_address: formData.sameAddress @@ -1516,6 +1536,14 @@ export const PatientRegister = (props: PatientRegisterProps) => { /> )}
    + o.text} + optionValue={(o) => o.id} + /> ) : (
    diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 05eb780069..7648ea18c5 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -1,5 +1,6 @@ import { ConsultationModel, PatientCategory } from "../Facility/models"; import { PerformedByModel } from "../HCX/misc"; +import { OCCUPATION_TYPES } from "../../Common/constants"; export interface FlowModel { id?: number; @@ -127,6 +128,11 @@ export interface PatientModel { created_by?: PerformedByModel; assigned_to?: { first_name?: string; username?: string; last_name?: string }; assigned_to_object?: AssignedToObjectModel; + occupation?: Occupation; + meta_info?: { + id: number; + occupation: Occupation; + }; // ABDM related abha_number?: string; @@ -370,3 +376,5 @@ export interface FileUploadModel { archived_by?: PerformedByModel; archived_datetime?: string; } + +export type Occupation = (typeof OCCUPATION_TYPES)[number]["value"]; From a77ff8ba212bcbee3bf5cd97fd4612fa010b2cee Mon Sep 17 00:00:00 2001 From: Vedant Jain <129421822+jainvedant392@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:03:06 +0530 Subject: [PATCH 04/12] Refactored condition of Different Districts in External Result (#7339) * Refactored condition of Different Districts in External Result * minor corrections * Added permission for StateLabAdmin as well --- .../ExternalResult/ExternalResultUpload.tsx | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index 2f5337a242..792ba69144 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -2,16 +2,16 @@ import _ from "lodash-es"; import { navigate } from "raviger"; import { lazy, useEffect, useState } from "react"; import CSVReader from "react-csv-reader"; -import useConfig from "../../Common/hooks/useConfig"; -import * as Notification from "../../Utils/Notifications.js"; -const PageTitle = lazy(() => import("../Common/PageTitle")); import { useTranslation } from "react-i18next"; -import { Cancel, Submit } from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; import useAppHistory from "../../Common/hooks/useAppHistory"; -import request from "../../Utils/request/request"; +import useConfig from "../../Common/hooks/useConfig"; import routes from "../../Redux/api"; +import * as Notification from "../../Utils/Notifications.js"; +import request from "../../Utils/request/request"; +import { Cancel, Submit } from "../Common/components/ButtonV2"; import { IExternalResult } from "./models"; -import CareIcon from "../../CAREUI/icons/CareIcon"; +const PageTitle = lazy(() => import("../Common/PageTitle")); export default function ExternalResultUpload() { const { sample_format_external_result_import } = useConfig(); @@ -26,7 +26,12 @@ export default function ExternalResultUpload() { setValidationErrorCount( data.filter( (result: IExternalResult) => - result.district !== user.district_object.name + ((user.user_type === "StateAdmin" || + user.user_type === "StateLabAdmin") && + result.address.split(",").pop()?.trim() !== + user.state_object.name) || + (user.user_type !== "StateAdmin" && + result.district !== user.district_object.name) ).length ); }; @@ -67,7 +72,12 @@ export default function ExternalResultUpload() { sample_tests: validationErrorCount ? csvData.filter( (data: IExternalResult) => - data.district === user.district_object.name + ((user.user_type === "StateAdmin" || + user.user_type === "StateLabAdmin") && + data.address.split(",").pop()?.trim() !== + user.state_object.name) || + (user.user_type !== "StateAdmin" && + data.district !== user.district_object.name) ) : csvData, }, @@ -179,7 +189,13 @@ export default function ExternalResultUpload() { : null}
    - {data.district !== user.district_object.name && ( + {(((user.user_type === "StateAdmin" || + user.user_type === "StateLabAdmin") && + data.address.split(",").pop()?.trim() !== + user.state_object.name) || + (user.user_type !== "StateAdmin" && + user.user_type !== "StateLabAdmin" && + data.district !== user.district_object.name)) && (

    Different districts From b70c853927710d283e122b485a912ba85980c6a9 Mon Sep 17 00:00:00 2001 From: Zishan Ahmad Date: Tue, 19 Mar 2024 17:04:03 +0530 Subject: [PATCH 05/12] fix: added validation for doctor experience input. (#7408) * added validation for doctor experience * Update src/Components/Users/UserProfile.tsx suggested changes applied Co-authored-by: Ashesh <3626859+Ashesh3@users.noreply.github.com> --------- Co-authored-by: Ashesh <3626859+Ashesh3@users.noreply.github.com> --- src/Components/Users/UserProfile.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Components/Users/UserProfile.tsx b/src/Components/Users/UserProfile.tsx index 34452a367d..d1c23a9db6 100644 --- a/src/Components/Users/UserProfile.tsx +++ b/src/Components/Users/UserProfile.tsx @@ -249,10 +249,12 @@ export default function UserProfile() { errors[field] = "Field is required"; invalidForm = true; } else if ( - states.form.user_type === "Doctor" && - Number(states.form.doctor_experience_commenced_on) > 100 + (states.form.user_type === "Doctor" && + Number(states.form.doctor_experience_commenced_on) >= 100) || + Number(states.form.doctor_experience_commenced_on) < 0 ) { - errors[field] = "Doctor experience should be less than 100 years"; + errors[field] = + "Doctor experience should be at least 0 years and less than 100 years."; invalidForm = true; } return; From 1451d431ec4c996e8e728dc3596c48a5bc2003e9 Mon Sep 17 00:00:00 2001 From: Ashesh <3626859+Ashesh3@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:04:25 +0530 Subject: [PATCH 06/12] Update "connect with whatsapp" logic (#7396) --- .../Facility/DoctorVideoSlideover.tsx | 97 ++++++++++++------- 1 file changed, 62 insertions(+), 35 deletions(-) diff --git a/src/Components/Facility/DoctorVideoSlideover.tsx b/src/Components/Facility/DoctorVideoSlideover.tsx index 6457d4d42b..1ebbbca67a 100644 --- a/src/Components/Facility/DoctorVideoSlideover.tsx +++ b/src/Components/Facility/DoctorVideoSlideover.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useDispatch } from "react-redux"; import SlideOver from "../../CAREUI/interactive/SlideOver"; import { getFacilityUsers } from "../../Redux/actions"; @@ -8,6 +8,7 @@ import CareIcon, { IconName } from "../../CAREUI/icons/CareIcon"; import { relativeTime } from "../../Utils/utils"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { triggerGoal } from "../../Integrations/Plausible"; +import { Warn } from "../../Utils/Notifications"; export default function DoctorVideoSlideover(props: { show: boolean; @@ -112,6 +113,64 @@ function UserListItem(props: { user: UserAssignedModel }) { user.user_type === "Doctor" ? "l-user-md" : "l-user-nurse"; const authUser = useAuthUser(); + function connectOnWhatsApp(e: React.MouseEvent) { + e.stopPropagation(); + if (!user.alt_phone_number) return; + const phoneNumber = user.alt_phone_number; + const message = `Hey ${user.first_name} ${user.last_name}, I have a query regarding a patient.\n\nPatient Link: ${window.location.href}`; + const encodedMessage = encodeURIComponent(message); + const whatsappAppURL = `whatsapp://send?phone=${phoneNumber}&text=${encodedMessage}`; + const whatsappWebURL = `https://web.whatsapp.com/send?phone=${phoneNumber}&text=${encodedMessage}`; + + const userAgent = navigator.userAgent; + const isEdge = /edge\/\d+/i.test(userAgent); + const isMobileFirefoxOrSafari = + /iPhone|iPad|iPod|Android/i.test(userAgent) && + (/Firefox/i.test(userAgent) || + (/Safari/i.test(userAgent) && !/Chrome/i.test(userAgent))); + const isSafari = /Safari/i.test(userAgent) && !/Chrome/i.test(userAgent); + const isMobile = /iPhone|iPad|iPod|Android/i.test(userAgent); + + const openWhatsAppWebFallback = () => { + if (isMobile) { + Warn({ + msg: "Please install WhatsApp to connect with the doctor", + }); + } + window.open(whatsappWebURL, "_blank"); + }; + + if (isEdge) { + if (navigator.msLaunchUri) { + navigator.msLaunchUri(whatsappAppURL, null, openWhatsAppWebFallback); + } else { + openWhatsAppWebFallback(); + } + } else { + const attemptOpenWhatsApp = (url: string) => { + if (isMobileFirefoxOrSafari || isSafari) { + const iframe = document.createElement("iframe"); + iframe.style.display = "none"; + iframe.src = url; + document.body.appendChild(iframe); + } else { + window.location.href = url; + } + }; + + attemptOpenWhatsApp(whatsappAppURL); + + const fallbackTimeout = setTimeout(() => { + openWhatsAppWebFallback(); + }, 1250); + + // Listen for when the window loses focus, indicating app launch success + window.addEventListener("blur", () => { + clearTimeout(fallbackTimeout); + }); + } + } + return (

  1. - +
    { // Show online icon based on last_login @@ -178,26 +224,7 @@ function UserListItem(props: { user: UserAssignedModel }) {
    )} - { - triggerGoal("Doctor Connect Click", { - medium: "WhatsApp", - userId: authUser?.id, - targetUserType: user.user_type, - }); - }} - target="_blank" - rel="noopener noreferrer" - > +
    Connect on WhatsApp From 14a771d583051dd6c04fc37953bef521b5f90aaf Mon Sep 17 00:00:00 2001 From: Ashraf Mohammed <98876115+AshrafMd-1@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:04:50 +0530 Subject: [PATCH 07/12] fix ui (#7392) --- src/Components/Patient/FileUpload.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index 1b1ee8f67d..f0fe5cc55d 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -1410,10 +1410,10 @@ export const FileUpload = (props: FileUploadProps) => { Reason: {modalDetails?.reason}
    - Archived_by: {modalDetails?.userArchived} + Archived by: {modalDetails?.userArchived}
    - Time of Archive: + Time of Archive: {formatDateTime(modalDetails?.archiveTime)}
  2. @@ -1468,7 +1468,7 @@ export const FileUpload = (props: FileUploadProps) => {
    )} -
    +
    Date: Tue, 19 Mar 2024 17:06:19 +0530 Subject: [PATCH 08/12] Solved File Management Exceptions (#7390) * File Management Exceptions Solved * added return statement * change request done * change suggestion done * done * final commit --- src/Components/Patient/FileUpload.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index f0fe5cc55d..804dedcde1 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -905,8 +905,8 @@ export const FileUpload = (props: FileUploadProps) => { } const onFileChange = (e: ChangeEvent): any => { - if (e.target.files == null) { - throw new Error("Error finding e.target.files"); + if (!e.target.files?.length) { + return; } const f = e.target.files[0]; const fileName = f.name; From f00cd0249084c5103928433b40b9ea80592bf932 Mon Sep 17 00:00:00 2001 From: Nikhil Kumar <91010142+r-nikhilkumar@users.noreply.github.com> Date: Tue, 19 Mar 2024 17:09:21 +0530 Subject: [PATCH 09/12] Issue resolved "Handle the 0 Input in the Pressure sore and Pain Scale component" (#7391) * weight and height input columns length are now consistent * Pain 0 and PressureSore 0 error solved * Update src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res Co-authored-by: Rithvik Nishad --------- Co-authored-by: Rithvik Nishad --- .../Pain/CriticalCare__PainInputModal.res | 8 ++++++-- .../CriticalCare__PressureSoreInputModal.res | 12 ++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res b/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res index eb4fd2b237..5626adff45 100644 --- a/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res +++ b/src/Components/CriticalCareRecording/Pain/CriticalCare__PainInputModal.res @@ -138,8 +138,12 @@ let make = ( ?
    -
    +
    Date: Tue, 19 Mar 2024 17:10:20 +0530 Subject: [PATCH 10/12] Improves discharged tab switch behaviour (#7358) * Show Live/Discharged tab for certain conditions * Ask for facility for other cases * add notification * Update src/Components/Facility/DischargedPatientsList.tsx --- .../Common/components/SwitchTabs.tsx | 4 +- .../Facility/DischargedPatientsList.tsx | 34 ++++++--- src/Components/Patient/ManagePatients.tsx | 69 +++++++++++++------ src/Locale/en/Facility.json | 4 +- 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/src/Components/Common/components/SwitchTabs.tsx b/src/Components/Common/components/SwitchTabs.tsx index a7872d5a40..6bf510c085 100644 --- a/src/Components/Common/components/SwitchTabs.tsx +++ b/src/Components/Common/components/SwitchTabs.tsx @@ -4,8 +4,8 @@ import { classNames } from "../../../Utils/utils"; export default function SwitchTabs(props: { className?: string; isTab2Active: boolean; - onClickTab1: () => void; - onClickTab2: () => void; + onClickTab1?: () => void; + onClickTab2?: () => void; tab1: ReactNode; tab2: ReactNode; }) { diff --git a/src/Components/Facility/DischargedPatientsList.tsx b/src/Components/Facility/DischargedPatientsList.tsx index 7ce4e9dc21..e313717c2a 100644 --- a/src/Components/Facility/DischargedPatientsList.tsx +++ b/src/Components/Facility/DischargedPatientsList.tsx @@ -1,4 +1,4 @@ -import { Link, useQueryParams } from "raviger"; +import { Link, navigate, useQueryParams } from "raviger"; import routes from "../../Redux/api"; import Page from "../Common/components/Page"; import PaginatedList from "../../CAREUI/misc/PaginatedList"; @@ -11,12 +11,15 @@ import { formatAge } from "../../Utils/utils"; import { GENDER_TYPES } from "../../Common/constants"; import CareIcon from "../../CAREUI/icons/CareIcon"; import RecordMeta from "../../CAREUI/display/RecordMeta"; +import { useTranslation } from "react-i18next"; +import SwitchTabs from "../Common/components/SwitchTabs"; const DischargedPatientsList = ({ facility_external_id, }: { facility_external_id: string; }) => { + const { t } = useTranslation(); const facilityQuery = useQuery(routes.getAnyFacility, { pathParams: { id: facility_external_id }, }); @@ -25,18 +28,27 @@ const DischargedPatientsList = ({ return ( setSearch({ [e.name]: e.value }), 300)} - /> + <> + setSearch({ [e.name]: e.value }), 300)} + /> + navigate("/patients")} + isTab2Active + /> + } > {() => ( -
    +
    - No dischaged patients present in this facility + {t("discharged_patients_empty")} diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 220165e9e4..0109eebdef 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -103,7 +103,7 @@ export const PatientManager = () => { }); const authUser = useAuthUser(); const [diagnoses, setDiagnoses] = useState([]); - const [showDialog, setShowDialog] = useState(false); + const [showDialog, setShowDialog] = useState<"create" | "list-discharged">(); const [showDoctors, setShowDoctors] = useState(false); const [showDoctorConnect, setShowDoctorConnect] = useState(false); const [phone_number, setPhoneNumber] = useState(""); @@ -406,7 +406,9 @@ export const PatientManager = () => { const { data: permittedFacilities } = useQuery( routes.getPermittedFacilities, - {} + { + query: { limit: 1 }, + } ); const LastAdmittedToTypeBadges = () => { @@ -723,6 +725,9 @@ export const PatientManager = () => { }; }; + const onlyAccessibleFacility = + permittedFacilities?.count === 1 ? permittedFacilities.results[0] : null; + return ( { onClick={() => { if (qParams.facility) navigate(`/facility/${qParams.facility}/patient`); - else if (permittedFacilities?.results.length === 1) - navigate( - `/facility/${permittedFacilities?.results[0].id}/patient` - ); - else setShowDialog(true); + else if (onlyAccessibleFacility) + navigate(`/facility/${onlyAccessibleFacility.id}/patient`); + else setShowDialog("create"); }} className="w-full lg:w-fit" > @@ -751,16 +754,33 @@ export const PatientManager = () => {
    - {(authUser.user_type === "StateAdmin" || - authUser.user_type === "StateReadOnlyAdmin") && ( - updateQuery({ is_active: "True" })} - onClickTab2={() => updateQuery({ is_active: "False" })} - isTab2Active={tabValue ? true : false} - /> - )} + updateQuery({ is_active: "True" })} + onClickTab2={() => { + // Navigate to dedicated discharged list page if filtered by a facility or user has access only to one facility. + const id = qParams.facility || onlyAccessibleFacility?.id; + if (id) { + navigate(`facility/${id}/discharged-patients`); + return; + } + + if ( + authUser.user_type === "StateAdmin" || + authUser.user_type === "StateReadOnlyAdmin" + ) { + updateQuery({ is_active: "False" }); + return; + } + + Notification.Warn({ + msg: "Facility needs to be selected to view discharged patients.", + }); + setShowDialog("list-discharged"); + }} + isTab2Active={!!tabValue} + /> {showDoctorConnect && ( { } > setSelectedFacility(e)} selectedFacility={selectedFacility} - handleOk={() => navigate(`facility/${selectedFacility.id}/patient`)} + handleOk={() => { + switch (showDialog) { + case "create": + navigate(`facility/${selectedFacility.id}/patient`); + break; + case "list-discharged": + navigate(`facility/${selectedFacility.id}/discharged-patients`); + break; + } + }} handleCancel={() => { - setShowDialog(false); + setShowDialog(undefined); setSelectedFacility({ name: "" }); }} /> diff --git a/src/Locale/en/Facility.json b/src/Locale/en/Facility.json index aefc9c3b4a..3c22a9ab46 100644 --- a/src/Locale/en/Facility.json +++ b/src/Locale/en/Facility.json @@ -51,5 +51,7 @@ "last_serviced_on": "Last Serviced On", "notes": "Notes", "create_asset": "Create Asset", - "create_add_more": "Create & Add More" + "create_add_more": "Create & Add More", + "discharged_patients": "Discharged Patients", + "discharged_patients_empty": "No discharged patients present in this facility" } \ No newline at end of file From 906bb218b660a94a71e13c9e76786595392d1f29 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 22 Mar 2024 20:16:12 +0530 Subject: [PATCH 11/12] Removes support for exporting all patients (#7446) --- src/Components/Common/Export.tsx | 19 +++++++++++++++++++ src/Components/Patient/ManagePatients.tsx | 10 +--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/Components/Common/Export.tsx b/src/Components/Common/Export.tsx index 7ddaca115b..6714d8bdb0 100644 --- a/src/Components/Common/Export.tsx +++ b/src/Components/Common/Export.tsx @@ -39,6 +39,25 @@ export const ExportMenu = ({ }: ExportMenuProps) => { const { isExporting, exportFile } = useExport(); + if (exportItems.length === 1) { + const item = exportItems[0]; + + return ( + + exportFile(item.action, item.filePrefix, item.type, item.parse) + } + border + ghost + className="py-2.5" + > + + {isExporting ? "Exporting..." : label} + + ); + } + return (
    { disabled={!isExportAllowed} exportItems={[ { - label: - tabValue === 0 - ? "Live patients" - : "Discharged patients", + label: "Export Live patients", action: exportPatients(true), parse: preventDuplicatePatientsDuetoPolicyId, }, - { - label: "All patients", - action: exportPatients(false), - parse: preventDuplicatePatientsDuetoPolicyId, - }, ]} /> )} From 1e802a6d546f6e309ed9c8d207950b6ff09e12ff Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Mon, 25 Mar 2024 21:08:28 +0530 Subject: [PATCH 12/12] Fixes high cpu usage in medicines tab, pagination not working, incorrect last modified date and type errors (#7472) * Fixes component re-rendering issue due to change in reference fixes #7465; * fixes dependency on field `last_administered_on` that no longer exists fixes #7466 --- src/Common/hooks/useRangePagination.ts | 2 +- .../Medicine/MedicineAdministrationSheet/index.tsx | 2 +- .../Medicine/MedicineAdministrationSheet/utils.ts | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/Common/hooks/useRangePagination.ts b/src/Common/hooks/useRangePagination.ts index 7652ae546c..b7c8b8510a 100644 --- a/src/Common/hooks/useRangePagination.ts +++ b/src/Common/hooks/useRangePagination.ts @@ -19,7 +19,7 @@ const useRangePagination = ({ bounds, perPage, ...props }: Props) => { useEffect(() => { setCurrentRange(getInitialBounds(bounds, perPage, props.defaultEnd)); - }, [bounds, perPage, props.defaultEnd]); + }, [JSON.stringify(bounds), perPage, props.defaultEnd]); const next = () => { const { end } = currentRange; diff --git a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx index 48c7e2aaff..fd3d50272a 100644 --- a/src/Components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/Components/Medicine/MedicineAdministrationSheet/index.tsx @@ -83,7 +83,7 @@ const MedicineAdministrationSheet = ({ readonly, is_prn }: Props) => { prescription.last_administered_on) + .filter((prescription) => prescription.last_administration?.created_date) .reduce( (latest, curr) => - curr.last_administered_on && curr.last_administered_on > latest - ? curr.last_administered_on + curr.last_administration?.created_date && + curr.last_administration?.created_date > latest + ? curr.last_administration?.created_date : latest, prescriptions[0]?.created_date ?? new Date() )