Skip to content

Commit

Permalink
Add titrated prescription dosage type (#6565)
Browse files Browse the repository at this point in the history
* 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 <mail@rithviknishad.dev>
Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com>
Co-authored-by: khavinshankar <khavinshankar@gmail.com>
  • Loading branch information
4 people authored Mar 19, 2024
1 parent 921fb4c commit df8c57b
Show file tree
Hide file tree
Showing 17 changed files with 409 additions and 96 deletions.
2 changes: 1 addition & 1 deletion cypress/pageobject/Patient/PatientPrescription.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
14 changes: 14 additions & 0 deletions src/Components/Form/FormFields/DosageFormField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { DOSAGE_UNITS } from "../../Medicine/models";
import NumericWithUnitsFormField from "./NumericWithUnitsFormField";
import { FormFieldBaseProps } from "./Utils";

type Props = FormFieldBaseProps<string> & {
placeholder?: string;
autoComplete?: string;
min?: string | number;
max?: string | number;
};

export default function DosageFormField(props: Props) {
return <NumericWithUnitsFormField {...props} units={DOSAGE_UNITS} />;
}
2 changes: 2 additions & 0 deletions src/Components/Form/FormFields/NumericWithUnitsFormField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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) => (
<option key={unit}>{unit}</option>
Expand Down
52 changes: 49 additions & 3 deletions src/Components/Medicine/AdministerMedicine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -24,6 +26,8 @@ export default function AdministerMedicine({ prescription, ...props }: Props) {
const consultation = useSlug("consultation");
const [isLoading, setIsLoading] = useState(false);
const [notes, setNotes] = useState<string>("");
const [dosage, setDosage] = useState<string | undefined>();
const [error, setError] = useState<string>();
const [isCustomTime, setIsCustomTime] = useState(false);
const [customTime, setCustomTime] = useState<string>(
dayjs().format("YYYY-MM-DDTHH:mm")
Expand All @@ -41,21 +45,45 @@ export default function AdministerMedicine({ prescription, ...props }: Props) {
description={
<div className="text-sm font-semibold leading-relaxed text-gray-600">
<CareIcon className="care-l-history-alt pr-1" /> Last administered
<span className="pl-1">
{prescription.last_administered_on
? formatDateTime(prescription.last_administered_on)
<span className="whitespace-nowrap pl-2">
<CareIcon className="care-l-clock" />{" "}
{prescription.last_administration?.administered_date
? formatDateTime(
prescription.last_administration.administered_date
)
: t("never")}
</span>
{prescription.dosage_type === "TITRATED" && (
<span className="whitespace-nowrap pl-2">
<CareIcon className="care-l-syringe" /> {t("dosage")}
{":"} {prescription.last_administration?.dosage ?? "NA"}
</span>
)}
<span className="whitespace-nowrap pl-2">
<CareIcon className="care-l-user" /> Administered by:{" "}
{prescription.last_administration?.administered_by?.username ??
"NA"}
</span>
</div>
}
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,
},
});
Expand All @@ -70,6 +98,24 @@ export default function AdministerMedicine({ prescription, ...props }: Props) {
<div className="mt-4 flex flex-col gap-8">
<PrescriptionDetailCard prescription={prescription} readonly />

{prescription.dosage_type === "TITRATED" && (
<DosageFormField
name="dosage"
label={
t("dosage") +
` (${prescription.base_dosage} - ${prescription.target_dosage})`
}
value={dosage}
onChange={({ value }) => setDosage(value)}
required
min={prescription.base_dosage}
max={prescription.target_dosage}
disabled={isLoading}
error={error}
errorClassName={error ? "block" : "hidden"}
/>
)}

<div className="flex flex-col gap-4 lg:flex-row lg:gap-6">
<TextAreaFormField
label={t("administration_notes")}
Expand Down
77 changes: 59 additions & 18 deletions src/Components/Medicine/CreatePrescriptionForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,18 @@ import Form from "../Form/Form";
import { SelectFormField } from "../Form/FormFields/SelectFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import TextFormField from "../Form/FormFields/TextFormField";
import {
DOSAGE_UNITS,
MedicineAdministrationRecord,
Prescription,
} from "./models";
import { MedicineAdministrationRecord, Prescription } from "./models";
import { useState } from "react";
import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormField";
import { useTranslation } from "react-i18next";
import MedibaseAutocompleteFormField from "./MedibaseAutocompleteFormField";
import dayjs from "../../Utils/dayjs";
import { PrescriptionFormValidator } from "./validators";
import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
import MedicineRoutes from "./routes";
import request from "../../Utils/request/request";
import useSlug from "../../Common/hooks/useSlug";
import { Success } from "../../Utils/Notifications";
import DosageFormField from "../Form/FormFields/DosageFormField";

export default function CreatePrescriptionForm(props: {
prescription: Prescription;
Expand Down Expand Up @@ -64,7 +61,27 @@ export default function CreatePrescriptionForm(props: {
{...field("medicine_object", RequiredFieldValidator())}
required
/>
<div className="flex items-center gap-4">
{props.prescription.dosage_type !== "PRN" && (
<CheckBoxFormField
label={t("titrate_dosage")}
name="Titrate Dosage"
value={field("dosage_type").value === "TITRATED"}
onChange={(e) => {
if (e.value) {
field("dosage_type").onChange({
name: "dosage_type",
value: "TITRATED",
});
} else {
field("dosage_type").onChange({
name: "dosage_type",
value: "REGULAR",
});
}
}}
/>
)}
<div className="flex flex-wrap items-center gap-x-4">
<SelectFormField
className="flex-1"
label={t("route")}
Expand All @@ -73,27 +90,44 @@ export default function CreatePrescriptionForm(props: {
optionLabel={(key) => t("PRESCRIPTION_ROUTE_" + key)}
optionValue={(key) => key}
/>
<NumericWithUnitsFormField
className="flex-1"
label={t("dosage")}
{...field("dosage", RequiredFieldValidator())}
required
units={DOSAGE_UNITS}
min={0}
/>
{field("dosage_type").value === "TITRATED" ? (
<div className="flex w-full gap-4">
<DosageFormField
className="flex-1"
label={t("start_dosage")}
{...field("base_dosage", RequiredFieldValidator())}
required
min={0}
/>
<DosageFormField
className="flex-1"
label={t("target_dosage")}
{...field("target_dosage", RequiredFieldValidator())}
required
min={0}
/>
</div>
) : (
<DosageFormField
className="flex-1"
label={t("dosage")}
{...field("base_dosage", RequiredFieldValidator())}
required={field("dosage_type").value !== "TITRATED"}
min={0}
/>
)}
</div>

{props.prescription.is_prn ? (
{props.prescription.dosage_type === "PRN" ? (
<>
<TextFormField
label={t("indicator")}
{...field("indicator", RequiredFieldValidator())}
required
/>
<NumericWithUnitsFormField
<DosageFormField
className="flex-1"
label={t("max_dosage_24_hrs")}
units={DOSAGE_UNITS}
min={0}
{...field("max_dosage")}
/>
Expand Down Expand Up @@ -130,6 +164,13 @@ export default function CreatePrescriptionForm(props: {
</div>
)}

{field("dosage_type").value === "TITRATED" && (
<TextAreaFormField
label={t("instruction_on_titration")}
{...field("instruction_on_titration")}
/>
)}

<TextAreaFormField label={t("notes")} {...field("notes")} />
</>
)}
Expand Down
72 changes: 59 additions & 13 deletions src/Components/Medicine/EditPrescriptionForm.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -88,6 +89,27 @@ export default function EditPrescriptionForm(props: Props) {
{...field("discontinued_reason")}
/>

{props.initial.dosage_type !== "PRN" && (
<CheckBoxFormField
label={t("titrate_dosage")}
name="Titrate Dosage"
value={field("dosage_type").value === "TITRATED"}
onChange={(e) => {
if (e.value) {
field("dosage_type").onChange({
name: "dosage_type",
value: "TITRATED",
});
} else {
field("dosage_type").onChange({
name: "dosage_type",
value: "REGULAR",
});
}
}}
/>
)}

<div className="flex items-center gap-4">
<SelectFormField
className="flex-1"
Expand All @@ -97,27 +119,44 @@ export default function EditPrescriptionForm(props: Props) {
optionLabel={(key) => t("PRESCRIPTION_ROUTE_" + key)}
optionValue={(key) => key}
/>
<NumericWithUnitsFormField
className="flex-1"
label={t("dosage")}
{...field("dosage", RequiredFieldValidator())}
required
units={DOSAGE_UNITS}
min={0}
/>
{field("dosage_type").value === "TITRATED" ? (
<div className="flex w-full gap-4">
<DosageFormField
className="flex-1"
label={t("start_dosage")}
{...field("base_dosage", RequiredFieldValidator())}
required
min={0}
/>
<DosageFormField
className="flex-1"
label={t("target_dosage")}
{...field("target_dosage", RequiredFieldValidator())}
required
min={0}
/>
</div>
) : (
<DosageFormField
className="flex-1"
label={t("dosage")}
{...field("base_dosage", RequiredFieldValidator())}
required={field("dosage_type").value !== "TITRATED"}
min={0}
/>
)}
</div>

{props.initial.is_prn ? (
{props.initial.dosage_type === "PRN" ? (
<>
<TextFormField
label={t("indicator")}
{...field("indicator", RequiredFieldValidator())}
required
/>
<NumericWithUnitsFormField
<DosageFormField
className="flex-1"
label={t("max_dosage_24_hrs")}
units={DOSAGE_UNITS}
min={0}
{...field("max_dosage")}
/>
Expand Down Expand Up @@ -154,6 +193,13 @@ export default function EditPrescriptionForm(props: Props) {
</div>
)}

{field("dosage_type").value === "TITRATED" && (
<TextAreaFormField
label={t("instruction_on_titration")}
{...field("instruction_on_titration")}
/>
)}

<TextAreaFormField label={t("notes")} {...field("notes")} />
</>
)}
Expand Down
Loading

0 comments on commit df8c57b

Please sign in to comment.