Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

💊 Adds support for editing prescriptions + Adds useSlug hook #6369

Merged
merged 5 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading