Skip to content

Commit

Permalink
💊 Adds support for editing prescriptions + Adds useSlug hook (#6369)
Browse files Browse the repository at this point in the history
* Adds hook: `useSlug`

* bug fix: NumericWithUnits field not showing intial value

* Form: support for showing global errors

* Adds support for editing prescriptions (fixes #6340)
  • Loading branch information
rithviknishad authored Oct 4, 2023
1 parent a4a4671 commit c7c37c6
Show file tree
Hide file tree
Showing 11 changed files with 318 additions and 14 deletions.
45 changes: 45 additions & 0 deletions src/Common/hooks/useSlug.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { usePath } from "raviger";

/**
* Returns the slug from the current path.
* @param prefix The prefix of the slug.
* @returns The slug.
* @example
* // Current path: /consultation/94b9a
* const consultation = useSlug("consultation"); // consultation = "94b9a"
*/
export default function useSlug(prefix: string) {
const path = usePath() ?? "";
return findSlug(path.split("/"), prefix);
}

/**
* Returns the slugs from the current path.
* @param prefix The prefixes of the slug.
* @returns The slugs
* @example
* // Current path: /facility/5b0a/consultation/94b9a
* const [facility, consultation] = useSlug("facility", "consultation");
* // facility = "5b0a"
* // consultation = "94b9a"
*/
export const useSlugs = (...prefix: string[]) => {
const path = usePath() ?? "";
return prefix.map((p) => findSlug(path.split("/"), p));
};

const findSlug = (segments: string[], prefix: string) => {
const index = segments.findIndex((segment) => segment === prefix);
if (index === -1) {
throw new Error(
`Prefix "${prefix}" not found in path "${segments.join("/")}"`
);
}

const slug = segments[index + 1];
if (!slug) {
throw new Error(`Slug not found in path "${segments.join("/")}"`);
}

return slug;
};
5 changes: 5 additions & 0 deletions src/Components/Form/Form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FormContextValue, createFormContext } from "./FormContext";
import { FieldChangeEvent } from "./FormFields/Utils";
import { FormDetails, FormErrors, FormState, formReducer } from "./Utils";
import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave";
import * as Notification from "../../Utils/Notifications";

type Props<T extends FormDetails> = {
className?: string;
Expand Down Expand Up @@ -51,6 +52,10 @@ const Form = <T extends FormDetails>({

if (Object.keys(errors).length) {
dispatch({ type: "set_errors", errors });

if (errors.$all) {
Notification.Error({ msg: errors.$all });
}
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export default function NumericWithUnitsFormField(props: Props) {
max={props.max}
autoComplete={props.autoComplete}
required={field.required}
value={numValue}
onChange={(e) => field.handleChange(e.target.value + " " + unitValue)}
/>
<div className="absolute inset-y-0 right-0 flex items-center">
Expand Down
4 changes: 3 additions & 1 deletion src/Components/Form/Utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldError } from "./FieldValidators";

export type FormDetails = { [key: string]: any };
export type FormErrors<T = FormDetails> = Partial<Record<keyof T, FieldError>>;
export type FormErrors<T = FormDetails> = Partial<
Record<keyof T | "$all", FieldError>
>;
export type FormState<T = FormDetails> = { form: T; errors: FormErrors<T> };
export type FormAction<T = FormDetails> =
| { type: "set_form"; form: T }
Expand Down
14 changes: 3 additions & 11 deletions src/Components/Medicine/CreatePrescriptionForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators";
import { RequiredFieldValidator } from "../Form/FieldValidators";
import Form from "../Form/Form";
import { SelectFormField } from "../Form/FormFields/SelectFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
Expand All @@ -11,6 +11,7 @@ import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormFi
import { useTranslation } from "react-i18next";
import MedibaseAutocompleteFormField from "./MedibaseAutocompleteFormField";
import dayjs from "../../Utils/dayjs";
import { PrescriptionFormValidator } from "./validators";

export default function CreatePrescriptionForm(props: {
prescription: Prescription;
Expand Down Expand Up @@ -40,16 +41,7 @@ export default function CreatePrescriptionForm(props: {
}
}}
noPadding
validate={(form) => {
const errors: Partial<Record<keyof Prescription, FieldError>> = {};
errors.medicine_object = RequiredFieldValidator()(form.medicine_object);
errors.dosage = RequiredFieldValidator()(form.dosage);
if (form.is_prn)
errors.indicator = RequiredFieldValidator()(form.indicator);
if (!form.is_prn)
errors.frequency = RequiredFieldValidator()(form.frequency);
return errors;
}}
validate={PrescriptionFormValidator()}
className="max-w-3xl"
>
{(field) => (
Expand Down
158 changes: 158 additions & 0 deletions src/Components/Medicine/EditPrescriptionForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { useState } from "react";
import Form from "../Form/Form";
import { Prescription } from "./models";
import request from "../../Utils/request/request";
import routes from "../../Redux/api";
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,
} from "./CreatePrescriptionForm";
import TextFormField from "../Form/FormFields/TextFormField";
import TextAreaFormField from "../Form/FormFields/TextAreaFormField";
import { EditPrescriptionFormValidator } from "./validators";

interface Props {
initial: Prescription;
onDone: (created: boolean) => void;
}

const handleSubmit = async (
consultation_external_id: string,
oldObj: Prescription,
{ discontinued_reason, ...newObj }: Prescription
) => {
const discontinue = await request(routes.discontinuePrescription, {
pathParams: { consultation_external_id, external_id: oldObj.id },
body: {
discontinued_reason: discontinued_reason
? `Edit: ${discontinued_reason}`
: "Edited",
},
});

if (discontinue.res?.status !== 200) {
Notification.Error({
msg: "Failed to discontinue previous prescription",
});
return;
}

const { res } = await request(routes.createPrescription, {
pathParams: { consultation_external_id },
body: {
...newObj,
// Forcing the medicine to be the same as the old one
medicine: oldObj.medicine_object?.id,
medicine_old: oldObj.medicine_old,
},
});

return res?.status === 201;
};

export default function EditPrescriptionForm(props: Props) {
const consultation = useSlug("consultation");
const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();

return (
<Form<Prescription>
disabled={isLoading}
defaults={props.initial}
onCancel={() => props.onDone(false)}
onSubmit={async (obj) => {
setIsLoading(true);
const success = await handleSubmit(consultation, props.initial, obj);
setIsLoading(false);

if (success) {
props.onDone(true);
}
}}
noPadding
validate={EditPrescriptionFormValidator(props.initial)}
>
{(field) => (
<>
<TextAreaFormField
label={t("reason_for_edit")}
{...field("discontinued_reason")}
/>

<div className="flex items-center gap-4">
<SelectFormField
className="flex-1"
label={t("route")}
{...field("route")}
options={PRESCRIPTION_ROUTES}
optionLabel={(key) => t("PRESCRIPTION_ROUTE_" + key)}
optionValue={(key) => key}
/>
<NumericWithUnitsFormField
className="flex-1"
label={t("dosage")}
{...field("dosage", RequiredFieldValidator())}
required
units={["mg", "g", "ml", "drop(s)", "ampule(s)", "tsp"]}
min={0}
/>
</div>

{props.initial.is_prn ? (
<>
<TextFormField
label={t("indicator")}
{...field("indicator", RequiredFieldValidator())}
required
/>
<TextFormField
label={t("max_dosage_24_hrs")}
type="number"
min={0}
{...field("max_dosage")}
/>
<SelectFormField
label={t("min_time_bw_doses")}
{...field("min_hours_between_doses")}
options={[1, 2, 3, 6, 12, 24]}
optionLabel={(hours) => `${hours} hrs.`}
optionValue={(hours) => hours}
position="above"
/>
</>
) : (
<div className="flex items-center gap-4">
<SelectFormField
position="above"
className="flex-1"
label={t("frequency")}
{...field("frequency", RequiredFieldValidator())}
required
options={Object.entries(PRESCRIPTION_FREQUENCIES)}
optionLabel={([key]) =>
t("PRESCRIPTION_FREQUENCY_" + key.toUpperCase())
}
optionValue={([key]) => key}
/>
<TextFormField
className="flex-1"
label={t("days")}
type="number"
min={0}
{...field("days")}
/>
</div>
)}

<TextAreaFormField label={t("notes")} {...field("notes")} />
</>
)}
</Form>
);
}
45 changes: 45 additions & 0 deletions src/Components/Medicine/PrescriptionAdministrationsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
formatTime,
} from "../../Utils/utils";
import useRangePagination from "../../Common/hooks/useRangePagination";
import EditPrescriptionForm from "./EditPrescriptionForm";

interface DateRange {
start: Date;
Expand Down Expand Up @@ -254,6 +255,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => {
const { t } = useTranslation();
// const [showActions, setShowActions] = useState(false);
const [showDetails, setShowDetails] = useState(false);
const [showEdit, setShowEdit] = useState(false);
const [showAdminister, setShowAdminister] = useState(false);
const [showDiscontinue, setShowDiscontinue] = useState(false);
const [administrations, setAdministrations] =
Expand Down Expand Up @@ -342,6 +344,21 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => {
<CareIcon className="care-l-ban text-lg" />
{t("discontinue")}
</Submit>
<Submit
disabled={
prescription.discontinued ||
prescription.prescription_type === "DISCHARGE"
}
variant="secondary"
border
onClick={() => {
setShowDetails(false);
setShowEdit(true);
}}
>
<CareIcon icon="l-pen" className="text-lg" />
{t("edit")}
</Submit>
<Submit
disabled={
prescription.discontinued ||
Expand All @@ -356,6 +373,34 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => {
</div>
</DialogModal>
)}
{showEdit && (
<DialogModal
onClose={() => setShowEdit(false)}
show={showEdit}
title={`${t("edit")} ${t(
prescription.is_prn ? "prn_prescription" : "prescription_medication"
)}: ${
prescription.medicine_object?.name ?? prescription.medicine_old
}`}
description={
<div className="mt-2 flex w-full justify-start gap-2 text-warning-500">
<CareIcon icon="l-info-circle" className="text-base" />
<span>{t("edit_caution_note")}</span>
</div>
}
className="w-full max-w-3xl lg:min-w-[600px]"
>
<EditPrescriptionForm
initial={prescription}
onDone={(success) => {
setShowEdit(false);
if (success) {
props.refetch();
}
}}
/>
</DialogModal>
)}
<td
className="cursor-pointer py-3 pl-4 text-left"
onClick={() => setShowDetails(true)}
Expand Down
2 changes: 1 addition & 1 deletion src/Components/Medicine/models.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { PerformedByModel } from "../HCX/misc";

interface BasePrescription {
readonly id?: string;
readonly id: string;
medicine?: string;
medicine_object?: MedibaseMedicine;
medicine_old?: string;
Expand Down
Loading

0 comments on commit c7c37c6

Please sign in to comment.