diff --git a/src/Components/Common/ConfirmDialogV2.tsx b/src/Components/Common/ConfirmDialogV2.tsx index ba99423be21..47633201458 100644 --- a/src/Components/Common/ConfirmDialogV2.tsx +++ b/src/Components/Common/ConfirmDialogV2.tsx @@ -1,12 +1,13 @@ import DialogModal from "./Dialog"; -import ButtonV2, { ButtonVariant, Cancel } from "./components/ButtonV2"; +import { ButtonVariant, Cancel, Submit } from "./components/ButtonV2"; type ConfirmDialogV2Props = { + className?: string; title: React.ReactNode; description?: React.ReactNode; disabled?: boolean; show: boolean; - action: string; + action: React.ReactNode; variant?: ButtonVariant; onClose: () => void; onConfirm: () => void; @@ -15,34 +16,22 @@ type ConfirmDialogV2Props = { }; const ConfirmDialogV2 = ({ - title, - description, - show, disabled, variant, action, - onClose, onConfirm, cancelLabel, children, + ...props }: ConfirmDialogV2Props) => { return ( - - {description} - - } - show={show} - > + {children}
- - + + {action} - +
); diff --git a/src/Components/Common/PrescriptionBuilder.res b/src/Components/Common/PrescriptionBuilder.res index 8d2adcb9826..913ac7d06da 100644 --- a/src/Components/Common/PrescriptionBuilder.res +++ b/src/Components/Common/PrescriptionBuilder.res @@ -1,5 +1,5 @@ let select = (setPrescription, prescription) => setPrescription(_ => prescription) -@react.component -export make = (~prescriptions, ~setPrescriptions) => +@genType @react.component +let make = (~prescriptions, ~setPrescriptions) =>
diff --git a/src/Components/Common/components/ResponsiveMedicineTables.tsx b/src/Components/Common/components/ResponsiveMedicineTables.tsx index a589f0ae426..f3dc2599ec9 100644 --- a/src/Components/Common/components/ResponsiveMedicineTables.tsx +++ b/src/Components/Common/components/ResponsiveMedicineTables.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import AccordionV2 from "./AccordionV2"; +import { classNames } from "../../../Utils/utils"; function getWindowSize() { const { innerWidth, innerHeight } = window; @@ -11,6 +12,10 @@ export default function ResponsiveMedicineTable(props: { list: Array; objectKeys: Array; fieldsToDisplay: Array; + actions?: (item: any) => JSX.Element; + actionLabel?: string; + maxWidthColumn?: number; + onClick?: (item: any) => void; }) { const [windowSize, setWindowSize] = useState(getWindowSize()); useEffect(() => { @@ -32,30 +37,49 @@ export default function ResponsiveMedicineTable(props: { {props.theads.map((item) => { return ( - + {item} ); })} + {props.actions && ( + + {props.actionLabel || ""} + + )} {props.list?.map?.((med: any, index: number) => ( - + props.onClick && props.onClick(med)} + > {props.objectKeys.map((key, idx) => { - if (idx === 0) + if ( + props.maxWidthColumn !== undefined && + idx === props.maxWidthColumn + ) { return ( - - {med[key]} - - ); - else - return ( - + {med[key]} ); + } + + return ( + + {med[key]} + + ); })} + {props.actions && ( + {props.actions(med)} + )} ))} diff --git a/src/Components/Common/prescription-builder/PRNPrescriptionBuilder.tsx b/src/Components/Common/prescription-builder/PRNPrescriptionBuilder.tsx deleted file mode 100644 index 57a14fef792..00000000000 --- a/src/Components/Common/prescription-builder/PRNPrescriptionBuilder.tsx +++ /dev/null @@ -1,322 +0,0 @@ -import { useState } from "react"; -import AutoCompleteAsync from "../../Form/AutoCompleteAsync"; -import SelectMenuV2 from "../../Form/SelectMenuV2"; -import { medicines, routes, units } from "./PrescriptionBuilder"; -import CareIcon from "../../../CAREUI/icons/CareIcon"; - -export type PRNPrescriptionType = { - medicine?: string; - route?: string; - dosage?: string; - indicator?: string; - max_dosage?: string; - min_time?: number; -}; - -export const PRNEmptyValues = { - medicine: "", - route: "", - dosage: "0 mg", - max_dosage: "0 mg", - indicator: "", - min_time: 0, -}; - -const DOSAGE_HRS = [1, 2, 3, 6, 12, 24]; - -export interface PrescriptionBuilderProps { - prescriptions: T[]; - setPrescriptions: React.Dispatch>; -} - -export default function PRNPrescriptionBuilder( - props: PrescriptionBuilderProps -) { - const { prescriptions, setPrescriptions } = props; - - const setItem = (object: PRNPrescriptionType, i: number) => { - setPrescriptions( - prescriptions.map((prescription, index) => - index === i ? object : prescription - ) - ); - }; - - const [activeIdx, setActiveIdx] = useState(null); - - return ( -
- {prescriptions.map((prescription, i) => { - const setMedicine = (medicine: string) => { - setItem( - { - ...prescription, - medicine, - }, - i - ); - }; - - const setRoute = (route: string) => { - setItem( - { - ...prescription, - route, - }, - i - ); - }; - - const setDosageUnit = (unit: string) => { - setItem( - { - ...prescription, - dosage: prescription.dosage - ? prescription.dosage.split(" ")[0] + " " + unit - : "0 mg", - }, - i - ); - }; - - const setMaxDosageUnit = (unit: string) => { - setItem( - { - ...prescription, - max_dosage: prescription.max_dosage - ? prescription.max_dosage.split(" ")[0] + " " + unit - : "0 mg", - }, - i - ); - }; - - const setMinTime = (min_time: number) => { - setItem( - { - ...prescription, - min_time, - }, - i - ); - }; - - return ( -
-
-

- Prescription No. {i + 1} -

- -
-
-
-
- Medicine - {" *"} -
- { - return Promise.resolve( - medicines.filter((medicine: string) => - medicine.toLowerCase().includes(search.toLowerCase()) - ) - ); - }} - optionLabel={(option) => option} - onChange={setMedicine} - showNOptions={medicines.length} - className="-mt-1" - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
-
Route
- setRoute(route || "")} - optionLabel={(option) => option} - required={false} - showChevronIcon={false} - className="mt-[2px]" - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
-
-
Dosage
-
- { - let value = parseFloat(e.target.value); - if (value < 0) { - value = 0; - } - setItem( - { - ...prescription, - dosage: - value + - " " + - (prescription.dosage?.split(" ")[1] || "mg"), - }, - i - ); - }} - required - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
- setDosageUnit(dosage || "")} - optionLabel={(option) => option} - required={false} - showChevronIcon={false} - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
-
-
-
-
-
-
-
- Indicator - {" *"} -
- { - setItem( - { - ...prescription, - indicator: e.target.value, - }, - i - ); - }} - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
-
Max Dosage in 24 hrs.
- -
- { - let value = parseFloat(e.target.value); - if (value < 0) { - value = 0; - } - setItem( - { - ...prescription, - max_dosage: - value + - " " + - (prescription.max_dosage?.split(" ")[1] || "mg"), - }, - i - ); - }} - required - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
- setMaxDosageUnit(dosage || "")} - optionLabel={(option) => option} - required={false} - showChevronIcon={false} - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
-
-
-
Min. time btwn. 2 doses
-
- - min_time && (min_time > 0 ? setMinTime(min_time) : 0) - } - optionLabel={(option) => option} - required={false} - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
Hrs.
-
-
-
-
- ); - })} - -
- ); -} diff --git a/src/Components/Common/prescription-builder/PrescriptionBuilder.tsx b/src/Components/Common/prescription-builder/PrescriptionBuilder.tsx deleted file mode 100644 index 68159cb444e..00000000000 --- a/src/Components/Common/prescription-builder/PrescriptionBuilder.tsx +++ /dev/null @@ -1,309 +0,0 @@ -import AutoCompleteAsync from "../../Form/AutoCompleteAsync"; -import SelectMenuV2 from "../../Form/SelectMenuV2"; -import { PrescriptionDropdown } from "./PrescriptionDropdown"; -import { PrescriptionBuilderProps } from "./PRNPrescriptionBuilder"; -import CareIcon from "../../../CAREUI/icons/CareIcon"; -import medicines_list from "./assets/medicines.json"; -import ToolTip from "../utils/Tooltip"; -import { useState } from "react"; - -export const medicines = medicines_list; - -const frequency = ["Stat", "od", "hs", "bd", "tid", "qid", "q4h", "qod", "qwk"]; -const frequencyTips = { - Stat: "Immediately", - od: "once daily", - hs: "Night only", - bd: "Twice daily", - tid: "8th hourly", - qid: "6th hourly", - q4h: "4th hourly", - qod: "Alternate day", - qwk: "Once a week", -}; -export const routes = ["Oral", "IV", "IM", "S/C"]; -export const units = ["mg", "g", "ml", "drops", "ampule", "tsp"]; - -export type PrescriptionType = { - medicine?: string; - route?: string; - dosage?: string; // is now frequency - dosage_new?: string; - days?: number; - notes?: string; -}; - -export const emptyValues = { - medicine: "", - route: "", - dosage: "", - dosage_new: "0 mg", - days: 0, - notes: "", -}; - -export default function PrescriptionBuilder( - props: PrescriptionBuilderProps -) { - const { prescriptions, setPrescriptions } = props; - - const setItem = (object: PrescriptionType, i: number) => { - setPrescriptions( - prescriptions.map((prescription, index) => - index === i ? object : prescription - ) - ); - }; - - const [activeIdx, setActiveIdx] = useState(null); - - return ( -
- {prescriptions.map((prescription, i) => { - const setMedicine = (medicine: string) => { - setItem( - { - ...prescription, - medicine, - }, - i - ); - }; - - const setRoute = (route: string) => { - setItem( - { - ...prescription, - route, - }, - i - ); - }; - - const setFrequency = (frequency: string) => { - setItem( - { - ...prescription, - dosage: frequency, - }, - i - ); - }; - - const setDosageUnit = (unit: string) => { - setItem( - { - ...prescription, - dosage_new: prescription.dosage_new - ? prescription.dosage_new.split(" ")[0] + " " + unit - : "0 mg", - }, - i - ); - }; - - return ( -
-
-

- Prescription No. {i + 1} -

- -
-
-
-
- Medicine * -
- setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - placeholder="Medicine" - selected={prescription.medicine} - fetchData={(search) => { - return Promise.resolve( - medicines.filter((medicine: string) => - medicine.toLowerCase().includes(search.toLowerCase()) - ) - ); - }} - optionLabel={(option) => option} - onChange={setMedicine} - showNOptions={medicines.length} - /> -
-
-
-
Route
- setRoute(route || "")} - optionLabel={(option) => option} - required={false} - className="mt-[6px]" - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
- Frequency * -
- setFrequency(freq || "")} - optionLabel={(option) => option} - optionIcon={(option) => ( - - { - frequencyTips[ - option as keyof typeof frequencyTips - ] - } - - } - > - - - )} - showIconWhenSelected={false} - required={false} - className="mt-[6px] w-[150px]" - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
-
-
-
-
Dosage
-
- { - let value = parseFloat(e.target.value); - if (value < 0) { - value = 0; - } - setItem( - { - ...prescription, - dosage_new: - value + - " " + - (prescription.dosage_new?.split(" ")[1] || "mg"), - }, - i - ); - }} - required - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
- setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
- -
-
Days
- { - let value = parseInt(e.target.value); - if (value < 0) { - value = 0; - } - setItem( - { - ...prescription, - days: value, - }, - i - ); - }} - required - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
- -
-
Notes
- { - setItem( - { - ...prescription, - notes: e.target.value, - }, - i - ); - }} - onFocus={() => setActiveIdx(i)} - onBlur={() => setActiveIdx(null)} - /> -
-
-
- ); - })} - -
- ); -} diff --git a/src/Components/Common/prescription-builder/PrescriptionBuilderTS.res b/src/Components/Common/prescription-builder/PrescriptionBuilderTS.res deleted file mode 100644 index 6f52af48b94..00000000000 --- a/src/Components/Common/prescription-builder/PrescriptionBuilderTS.res +++ /dev/null @@ -1,12 +0,0 @@ -type reactClass -module PrescriptionBuilder = { - @module("./PrescriptionBuilder.tsx") @react.component - external make: ( - ~prescriptions: array, - ~setPrescriptions: array => unit, - ) => React.element = "default" -} - -@react.component -let make = (~prescriptions, ~setPrescriptions) => - diff --git a/src/Components/CriticalCareRecording/CriticalCare__MedicineEditor.res b/src/Components/CriticalCareRecording/CriticalCare__MedicineEditor.res deleted file mode 100644 index 5f5d8a00f4f..00000000000 --- a/src/Components/CriticalCareRecording/CriticalCare__MedicineEditor.res +++ /dev/null @@ -1,149 +0,0 @@ -let str = React.string - -@module("./CriticalCare__API") -external updateDailyRound: (string, string, Js.Json.t, _ => unit, _ => unit) => unit = - "updateDailyRound" - -type prescriptionType = { - medicine: string, - route: string, - dosage: string, // is now frequency - dosage_new: string, - days: int, - notes: string, -} - -type state = { - medicines: array, - saving: bool, - dirty: bool, -} - -type action = - | SetMedicines(array) - | SetSaving - | ClearSaving - -let reducer = (state, action) => { - switch action { - | SetMedicines(medicines) => { - ...state, - medicines: medicines, - dirty: true, - } - | SetSaving => { - ...state, - saving: true, - } - | ClearSaving => { - ...state, - saving: false, - } - } -} - -let makeField = p => { - let payload = Js.Dict.empty() - Js.Dict.set(payload, "medicine", Js.Json.string(Prescription__Prescription.medicine(p))) - Js.Dict.set(payload, "dosage", Js.Json.string(Prescription__Prescription.dosage(p))) - Js.Dict.set(payload, "days", Js.Json.number(float_of_int(Prescription__Prescription.days(p)))) - Js.Dict.set(payload, "route", Js.Json.string(Prescription__Prescription.route(p))) - Js.Dict.set(payload, "notes", Js.Json.string(Prescription__Prescription.notes(p))) - Js.Dict.set(payload, "dosage_new", Js.Json.string(Prescription__Prescription.dosage_new(p))) - payload -} - -let makePayload = state => { - Js.Dict.fromArray([ - ("medication_given", Js.Json.objectArray(Js.Array.map(makeField, state.medicines))), - ]) -} - -let successCB = (send, updateCB, data) => { - send(ClearSaving) - updateCB(CriticalCare__DailyRound.makeFromJs(data)) -} - -let errorCB = (send, _error) => { - send(ClearSaving) -} - -let validateMedicines = (prescriptions: array) => { - let error = prescriptions |> Js.Array.find(prescription => { - let medicine = prescription |> Prescription__Prescription.medicine - let dosage = prescription |> Prescription__Prescription.dosage - let dosage_new = prescription |> Prescription__Prescription.dosage_new |> Js.String.split(" ") - let dosage_invalid = switch dosage_new |> Js.Array.length == 2 { - | true => { - let dosage_value = - dosage_new->Js.Array.unsafe_get(0) |> Js.String.replaceByRe(%re("/\D/g"), "") - let dosage_unit = dosage_new->Js.Array.unsafe_get(1) - dosage_value == "" || dosage_unit == "" - } - | false => true - } - let invalid = - medicine == "" || medicine == " " || dosage == "" || dosage == " " || dosage_invalid - switch invalid { - | true => { - Notifications.error({ - msg: "Medicine, Dosage and Frequency are mandatory for Prescriptions.", - }) - true - } - | false => false - } - }) - switch error { - | None => true - | Some(_) => false - } -} - -let saveData = (id, consultationId, state, send, updateCB) => { - switch state.medicines |> validateMedicines { - | true => { - send(SetSaving) - updateDailyRound( - consultationId, - id, - Js.Json.object_(makePayload(state)), - successCB(send, updateCB), - errorCB(send), - ) - } - | false => Js_console.log("Validation failed") - } -} -let initialState = medicines => { - { - medicines: medicines, - saving: false, - dirty: false, - } -} - -let getFieldValue = event => { - ReactEvent.Form.target(event)["value"] -} - -@react.component -let make = (~medicines, ~updateCB, ~id, ~consultationId) => { - let (state, send) = React.useReducer(reducer, initialState(medicines)) - -
- -
- send(SetMedicines(medicines))} - /> -
- -
-} diff --git a/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res b/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res index 7db1405c490..0316430dcd3 100644 --- a/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res +++ b/src/Components/CriticalCareRecording/Recording/CriticalCare__Recording.res @@ -10,7 +10,6 @@ type editor = | DialysisEditor | PressureSoreEditor | NursingCareEditor - | MedicineEditor type state = { visibleEditor: option, @@ -53,7 +52,6 @@ let editorNameToString = editor => { | DialysisEditor => "Dialysis" | PressureSoreEditor => "Pressure Sore" | NursingCareEditor => "Nursing Care" - | MedicineEditor => "Medicine" } } @@ -212,13 +210,6 @@ let make = (~id, ~facilityId, ~patientId, ~consultationId, ~dailyRound) => { id consultationId /> - | MedicineEditor => - }} @@ -239,7 +230,6 @@ let make = (~id, ~facilityId, ~patientId, ~consultationId, ~dailyRound) => { DialysisEditor, PressureSoreEditor, NursingCareEditor, - MedicineEditor, ])->React.array} import("../Common/Loading")); const PageTitle = loadable(() => import("../Common/PageTitle")); const symptomChoices = [...SYMPTOM_CHOICES]; export const ConsultationDetails = (props: any) => { + const [medicinesKey, setMedicinesKey] = useState(0); const { t } = useTranslation(); const { facilityId, patientId, consultationId } = props; const tab = props.tab.toUpperCase(); const dispatch: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [showDoctors, setShowDoctors] = useState(false); - const state: any = useSelector((state) => state); - const { currentUser } = state; const [consultationData, setConsultationData] = useState( {} as ConsultationModel ); const [patientData, setPatientData] = useState({}); - const [open, setOpen] = useState(false); + const [openDischargeSummaryDialog, setOpenDischargeSummaryDialog] = + useState(false); const [openDischargeDialog, setOpenDischargeDialog] = useState(false); - - const initDischargeSummaryForm: { email: string } = { - email: "", - }; - const [dischargeSummaryState, setDischargeSummaryForm] = useState( - initDischargeSummaryForm - ); - const [errors, setErrors] = useState({}); const [showAutomatedRounds, setShowAutomatedRounds] = useState(true); - const handleClickOpen = () => { - setOpen(true); - }; - - const handleDischageClickOpen = () => { - setOpenDischargeDialog(true); - }; - - const handleClose = () => { - setOpen(false); - }; - - const handleDischargeClose = () => { - setOpenDischargeDialog(false); - }; - - const handleDischargeSummarySubmit = () => { - if (!dischargeSummaryState.email) { - const errorField = Object.assign({}, errors); - errorField["dischargeSummaryForm"] = "email field can not be blank."; - setErrors(errorField); - } else if (!validateEmailAddress(dischargeSummaryState.email)) { - const errorField = Object.assign({}, errors); - errorField["dischargeSummaryForm"] = "Please Enter a Valid Email Address"; - setErrors(errorField); - } else { - dispatch( - discharge( - { email: dischargeSummaryState.email }, - { external_id: patientData.id } - ) - ).then((response: any) => { - if ((response || {}).status === 200) { - Notification.Success({ - msg: "We will be sending an email shortly. Please check your inbox.", - }); - } - }); - setOpen(false); - } - }; - - const handleDischargeSummary = (e: any) => { - e.preventDefault(); - setOpen(false); - }; - const getPatientGender = (patientData: any) => GENDER_TYPES.find((i) => i.id === patientData.gender)?.text; @@ -148,26 +83,6 @@ export const ConsultationDetails = (props: any) => { } }; - const handleDischargeSummaryFormChange = ( - e: React.ChangeEvent - ) => { - const { value } = e.target; - - const errorField = Object.assign({}, errors); - errorField["dischargeSummaryForm"] = null; - setErrors(errorField); - - setDischargeSummaryForm({ email: value }); - }; - - const dischargeSummaryFormSetUserEmail = () => { - if (!currentUser.data.email.trim()) - return Notification.Error({ - msg: "Email not provided! Please update profile", - }); - setDischargeSummaryForm({ email: currentUser.data.email }); - }; - const fetchData = useCallback( async (status: statusType) => { setIsLoading(true); @@ -186,13 +101,6 @@ export const ConsultationDetails = (props: any) => { return option ? option.text.toLowerCase() : symptom; }); data.symptoms_text = symptoms.join(", "); - data.discharge_advice = - Object.keys(res.data.discharge_advice).length === 0 - ? [] - : res.data.discharge_advice; - } - if (!Array.isArray(res.data.prn_prescription)) { - data.prn_prescription = []; } setConsultationData(data); const id = res.data.patient; @@ -280,60 +188,15 @@ export const ConsultationDetails = (props: any) => { return (
- - - Download Discharge Summary - - - - Please enter your email id to receive the discharge summary. - Disclaimer: This is an automatically Generated email using your info - Captured in Care System. -
- - Please check your email id before continuing. We cannot deliver - the email if the email id is invalid - -
-
- - -
- - - - -
+ setOpenDischargeSummaryDialog(false)} + /> setOpenDischargeDialog(false)} consultationData={consultationData} /> @@ -487,14 +350,17 @@ export const ConsultationDetails = (props: any) => { )}
-
-
-
- Prescription -
-
-
- -
-
{" "} +
+

-
-
- PRN Prescription -
-
- -
+
+
)} @@ -1141,98 +963,29 @@ export const ConsultationDetails = (props: any) => { )} {tab === "MEDICINES" && (
- {consultationData.discharge_advice && ( -
-
- Prescription -
- - {consultationData.modified_date && - formatDate(consultationData.modified_date)} -
-
-
-
-
- - {consultationData.discharge_advice.length === 0 && ( -
- No data found -
- )} -
-
-
-
- )} - {consultationData.prn_prescription && ( -
-
- PRN Prescription -
- - {consultationData.modified_date && - formatDate(consultationData.modified_date)} -
-
-
-
-
- - {consultationData.prn_prescription.length === 0 && ( -
- No data found -
- )} -
-
-
-
- )} - - +
+ setMedicinesKey((k) => k + 1)} + readonly={!!consultationData.discharge_date} + /> +
+
+ setMedicinesKey((k) => k + 1)} + readonly={!!consultationData.discharge_date} + /> +
+
+ +
)} {tab === "FILES" && ( diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 18df8d86738..8585340b974 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -33,12 +33,6 @@ import { UserModel } from "../Users/models"; import { BedSelect } from "../Common/BedSelect"; import { dischargePatient } from "../../Redux/actions"; import Beds from "./Consultations/Beds"; -import PrescriptionBuilder, { - PrescriptionType, -} from "../Common/prescription-builder/PrescriptionBuilder"; -import PRNPrescriptionBuilder, { - PRNPrescriptionType, -} from "../Common/prescription-builder/PRNPrescriptionBuilder"; import InvestigationBuilder, { InvestigationType, } from "../Common/prescription-builder/InvestigationBuilder"; @@ -94,8 +88,6 @@ type FormDetails = { ip_no: string; op_no: string; procedure: ProcedureType[]; - discharge_advice: PrescriptionType[]; - prn_prescription: PRNPrescriptionType[]; investigation: InvestigationType[]; is_telemedicine: BooleanStrings; action?: string; @@ -144,8 +136,6 @@ const initForm: FormDetails = { ip_no: "", op_no: "", procedure: [], - discharge_advice: [], - prn_prescription: [], investigation: [], is_telemedicine: "false", action: "NO_ACTION", @@ -223,10 +213,6 @@ export const ConsultationForm = (props: any) => { const { facilityId, patientId, id } = props; const [state, dispatch] = useReducer(consultationFormReducer, initialState); const [bed, setBed] = useState(null); - const [dischargeAdvice, setDischargeAdvice] = useState( - [] - ); - const [PRNAdvice, setPRNAdvice] = useState([]); const [InvestigationAdvice, setInvestigationAdvice] = useState< InvestigationType[] >([]); @@ -305,12 +291,6 @@ export const ConsultationForm = (props: any) => { async (status: statusType) => { setIsLoading(true); const res = await dispatchAction(getConsultation(id)); - setDischargeAdvice(res && res.data && res.data.discharge_advice); - setPRNAdvice( - !Array.isArray(res.data.prn_prescription) - ? [] - : res.data.prn_prescription - ); setInvestigationAdvice( !Array.isArray(res.data.investigation) ? [] : res.data.investigation ); @@ -368,7 +348,7 @@ export const ConsultationForm = (props: any) => { fetchData(status); } }, - [dispatch, fetchData] + [fetchData, id] ); if (isLoading) return ; @@ -489,27 +469,6 @@ export const ConsultationForm = (props: any) => { invalidForm = true; } return; - case "discharge_advice": { - let invalid = false; - let errorMsg = ""; - for (const f of dischargeAdvice) { - if (!f.medicine?.replace(/\s/g, "").length) { - invalid = true; - errorMsg = "Prescription Medicine field can not be empty"; - break; - } - if (!f.dosage?.replace(/\s/g, "").length) { - invalid = true; - errorMsg = "Prescription Frequency field can not be empty"; - break; - } - } - if (invalid) { - errors[field] = errorMsg; - invalidForm = true; - } - return; - } case "procedure": { for (const p of procedures) { if (!p.procedure?.replace(/\s/g, "").length) { @@ -530,21 +489,6 @@ export const ConsultationForm = (props: any) => { } return; } - case "prn_prescription": { - for (const f of PRNAdvice) { - if (!f.medicine?.replace(/\s/g, "").length) { - errors[field] = "Medicine field can not be empty"; - invalidForm = true; - break; - } - if (!f.indicator?.replace(/\s/g, "").length) { - errors[field] = "Indicator field can not be empty"; - invalidForm = true; - break; - } - } - return; - } case "investigation": { for (const i of InvestigationAdvice) { @@ -617,7 +561,7 @@ export const ConsultationForm = (props: any) => { death_datetime: death_datetime, death_confirmed_doctor: death_confirmed_doctor, }, - { id: patientId } + { id } ) ); @@ -661,8 +605,6 @@ export const ConsultationForm = (props: any) => { icd11_provisional_diagnoses: state.form.icd11_provisional_diagnoses_object.map((o) => o.id), verified_by: state.form.verified_by, - discharge_advice: dischargeAdvice, - prn_prescription: PRNAdvice, investigation: InvestigationAdvice, procedure: procedures, patient: patientId, @@ -713,6 +655,10 @@ export const ConsultationForm = (props: any) => { if (data.suggestion === "R") { navigate(`/facility/${facilityId}/patient/${patientId}/shift/new`); return; + } else if (!id) { + navigate( + `/facility/${facilityId}/patient/${patientId}/consultation/${res.data.id}/prescriptions` + ); } } } @@ -1176,37 +1122,6 @@ export const ConsultationForm = (props: any) => { error={state.errors.investigation} />
- -
- Prescription Medication - - -
- -
- PRN Prescription - - -
-
{ - const { facilityId, patientId, consultationId } = props; - const dispatch: any = useDispatch(); - const [isLoading, setIsLoading] = useState(false); - const [results, setResults] = useState({}); - const [currentPage, setCurrentPage] = useState(1); - const [totalCount, setTotalCount] = useState(0); - - const fetchDailyRounds = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch( - dailyRoundsAnalyse( - { - page: currentPage, - fields: ["medication_given"], - }, - { consultationId } - ) - ); - if (!status.aborted) { - if (res && res.data) { - setResults(res.data.results); - setTotalCount(res.data.count); - } - setIsLoading(false); - } - }, - [consultationId, dispatch, currentPage] - ); - - useAbortableEffect( - (status: statusType) => { - fetchDailyRounds(status); - }, - [currentPage] - ); - - const handlePagination = (page: number) => { - setCurrentPage(page); - }; - - const areFieldsEmpty = () => { - let emptyFieldCount = 0; - Object.keys(results).forEach((k: any) => { - if (Object.keys(results[k].medication_given).length === 0) { - emptyFieldCount++; - } - }); - if (emptyFieldCount === Object.keys(results).length) return true; - else return false; - }; - - const noDataFound = Object.keys(results).length === 0 || areFieldsEmpty(); - - return ( -
- {results && ( -
-
Consultation Updates
- {noDataFound && ( -
- No Consultation Updates Found -
- )} - {Object.keys(results).map((k: any, indx: number) => ( -
- {Object.keys(results[k].medication_given).length !== 0 && ( -
-
-
- {formatDate(k)} -
-
-
-
- -
-
-
-
-
- )} -
- ))} -
- )} - - {totalCount > PAGINATION_LIMIT && ( -
- -
- )} -
- ); -}; diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx index 2cd717fe7a2..6815f917c35 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -1,12 +1,6 @@ import * as Notification from "../../Utils/Notifications"; import { Cancel, Submit } from "../Common/components/ButtonV2"; -import PRNPrescriptionBuilder, { - PRNPrescriptionType, -} from "../Common/prescription-builder/PRNPrescriptionBuilder"; -import PrescriptionBuilder, { - PrescriptionType, -} from "../Common/prescription-builder/PrescriptionBuilder"; import { useCallback, useEffect, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -19,7 +13,7 @@ import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "../Common/Dialog"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; import { FieldLabel } from "../Form/FormFields/FormField"; -import { HCXActions } from "../../Redux/actions"; +import { HCXActions, PrescriptionActions } from "../../Redux/actions"; import { HCXClaimModel } from "../HCX/models"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; @@ -29,6 +23,7 @@ import moment from "moment"; import useConfig from "../../Common/hooks/useConfig"; import { useDispatch } from "react-redux"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; +import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; interface PreDischargeFormInterface { discharge_reason: string; @@ -36,8 +31,6 @@ interface PreDischargeFormInterface { discharge_date: string | null; death_datetime: string | null; death_confirmed_doctor: string | null; - discharge_prescription: PrescriptionType[]; - discharge_prn_prescription: PRNPrescriptionType[]; } interface IProps { @@ -73,15 +66,7 @@ const DischargeModal = ({ discharge_date, death_datetime, death_confirmed_doctor: null, - discharge_prescription: [], - discharge_prn_prescription: [], }); - const [dischargePrescription, setDischargePrescription] = useState< - PrescriptionType[] - >([]); - const [dischargePRNPrescription, setDischargePRNPrescription] = useState< - PRNPrescriptionType[] - >([]); const [latestClaim, setLatestClaim] = useState(); const [isCreateClaimLoading, setIsCreateClaimLoading] = useState(false); const [isSendingDischargeApi, setIsSendingDischargeApi] = useState(false); @@ -153,10 +138,8 @@ const DischargeModal = ({ discharge_date: moment(preDischargeForm.discharge_date).toISOString( true ), - discharge_prescription: dischargePrescription, - discharge_prn_prescription: dischargePRNPrescription, }, - { id: consultationData.patient } + { id: consultationData.id } ) ); @@ -184,6 +167,8 @@ const DischargeModal = ({ }); }; + const prescriptionActions = PrescriptionActions(consultationData.id); + return ( - Discharge Prescription -
+ +
+ Discharge Prescription Medications
-
- Discharge PRN Prescription - + Discharge PRN Prescriptions +
diff --git a/src/Components/Facility/DischargeSummaryModal.tsx b/src/Components/Facility/DischargeSummaryModal.tsx new file mode 100644 index 00000000000..2925fafb6c5 --- /dev/null +++ b/src/Components/Facility/DischargeSummaryModal.tsx @@ -0,0 +1,159 @@ +import { useState } from "react"; +import DialogModal from "../Common/Dialog"; +import TextFormField from "../Form/FormFields/TextFormField"; +import { ConsultationModel } from "./models"; +import { Cancel, Submit } from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { + EmailValidator, + MultiValidator, + RequiredFieldValidator, +} from "../Form/FieldValidators"; +import { useDispatch } from "react-redux"; +import { + emailDischargeSummary, + generateDischargeSummary, +} from "../../Redux/actions"; +import { Error, Success } from "../../Utils/Notifications"; +import { previewDischargeSummary } from "../../Redux/actions"; +import { useTranslation } from "react-i18next"; + +interface Props { + show: boolean; + onClose: () => void; + consultation: ConsultationModel; +} + +export default function DischargeSummaryModal(props: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [email, setEmail] = useState(""); + const [emailError, setEmailError] = useState(""); + const [emailing, setEmailing] = useState(false); + const [downloading, setDownloading] = useState(false); + const [generating, setGenerating] = useState(false); + + const handleDownload = async () => { + setDownloading(true); + + if (props.consultation.discharge_date) { + const res = await dispatch( + previewDischargeSummary({ external_id: props.consultation.id }) + ); + + if (res.status === 200) { + window.open(res.data.read_signed_url, "_blank"); + setDownloading(false); + props.onClose(); + return; + } + } + + await dispatch( + generateDischargeSummary({ external_id: props.consultation.id }) + ); + + setGenerating(true); + Success({ msg: t("generating_discharge_summary") + "..." }); + + setTimeout(async () => { + setGenerating(false); + + const res = await dispatch( + previewDischargeSummary({ external_id: props.consultation.id }) + ); + + if (res.status === 200) { + window.open(res.data.read_signed_url, "_blank"); + setDownloading(false); + props.onClose(); + return; + } + + Error({ + msg: t("discharge_summary_not_ready") + " " + t("try_again_later"), + }); + setDownloading(false); + }, 5000); + }; + + const handleEmail = async () => { + setEmailing(true); + + const emailError = MultiValidator([ + RequiredFieldValidator(), + EmailValidator(), + ])(email); + + if (emailError) { + setEmailError(emailError); + setEmailing(false); + return; + } + + const res = await dispatch( + emailDischargeSummary({ email }, { external_id: props.consultation.id }) + ); + + if (res.status === 200) { + Success({ msg: t("email_success") }); + props.onClose(); + } + + setEmailing(false); + }; + + return ( + +
+
+ + {t("email_discharge_summary_description")} + + + + {`${t("disclaimer")}: ${t("generated_summary_caution")}`} + +
+ setEmail(e.value)} + error={emailError} + /> +
+ + + {downloading ? ( + + ) : ( + + )} + + {generating + ? t("generating") + "..." + : downloading + ? t("downloading") + "..." + : t("download")} + + + + {emailing ? ( + + ) : ( + + )} + {t("send_email")} + +
+
+
+ ); +} diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 88973fde3a0..52fdc73e860 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -1,6 +1,6 @@ import { AssignedToObjectModel } from "../Patient/models"; -import { PRNPrescriptionType } from "../Common/prescription-builder/PRNPrescriptionBuilder"; import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; +import { NormalPrescription, PRNPrescription } from "../Medicine/models"; export interface LocalBodyModel { name: string; @@ -88,8 +88,8 @@ export interface ConsultationModel { created_date?: string; discharge_date?: string; discharge_reason?: string; - discharge_prescription: any; - discharge_prn_prescription: any; + discharge_prescription: NormalPrescription; + discharge_prn_prescription: PRNPrescription; discharge_notes?: string; examination_details?: string; history_of_present_illness?: string; @@ -117,8 +117,6 @@ export interface ConsultationModel { symptoms_onset_date?: string; consultation_notes?: string; is_telemedicine?: boolean; - discharge_advice?: any; - prn_prescription?: PRNPrescriptionType[]; procedure?: ProcedureType[]; assigned_to_object?: AssignedToObjectModel; created_by?: any; diff --git a/src/Components/Form/FieldValidators.ts b/src/Components/Form/FieldValidators.tsx similarity index 64% rename from src/Components/Form/FieldValidators.ts rename to src/Components/Form/FieldValidators.tsx index c144e421791..7a7bf72d067 100644 --- a/src/Components/Form/FieldValidators.ts +++ b/src/Components/Form/FieldValidators.tsx @@ -17,7 +17,7 @@ export type FieldValidator = (value: T, ...args: any) => FieldError; * @param validators List of `FieldValidator`s. * @returns `FieldError` */ -export const MultiValidator = ( +export const MultiValidator = ( validators: FieldValidator[] ): FieldValidator => { const validator = (value: T) => { @@ -30,8 +30,16 @@ export const MultiValidator = ( }; export const RequiredFieldValidator = (message = "Field is required") => { - return (value: T): FieldError => { + return (value: T): FieldError => { if (!value) return message; if (Array.isArray(value) && value.length === 0) return message; }; }; + +export const EmailValidator = (message = "Invalid email address") => { + return (value: string): FieldError => { + const pattern = + /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + return pattern.test(value) ? undefined : message; + }; +}; diff --git a/src/Components/Form/Form.tsx b/src/Components/Form/Form.tsx index d3bb41500dd..ed5ac1a889b 100644 --- a/src/Components/Form/Form.tsx +++ b/src/Components/Form/Form.tsx @@ -43,6 +43,7 @@ const Form = ({ const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); + event.stopPropagation(); if (validate) { const errors = omitBy(validate(state.form), isEmpty) as FormErrors; @@ -97,7 +98,7 @@ const Form = ({ {props.children} ) : ( <> -
+
{props.children}
diff --git a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx new file mode 100644 index 00000000000..3d728dfab54 --- /dev/null +++ b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx @@ -0,0 +1,57 @@ +import FormField from "./FormField"; +import { FormFieldBaseProps, useFormFieldPropsResolver } from "./Utils"; +import { classNames } from "../../../Utils/utils"; + +type Props = FormFieldBaseProps & { + placeholder?: string; + value?: string; + autoComplete?: string; + className?: string | undefined; + min?: string | number; + max?: string | number; + units: string[]; +}; + +export default function NumericWithUnitsFormField(props: Props) { + const field = useFormFieldPropsResolver(props); + + const [numValue, unitValue] = field.value?.split(" ") ?? ["", props.units[0]]; + + return ( + +
+ field.handleChange(e.target.value + " " + unitValue)} + /> +
+ +
+
+
+ ); +} diff --git a/src/Components/Form/FormFields/Utils.ts b/src/Components/Form/FormFields/Utils.ts index ccf714a97d3..117e2631eac 100644 --- a/src/Components/Form/FormFields/Utils.ts +++ b/src/Components/Form/FormFields/Utils.ts @@ -86,7 +86,7 @@ export const useFormFieldPropsResolver = < return { ...props, - id: props.id, + id: props.id ?? props.name, name: props.name, onChange: props.onChange, value: props.value, diff --git a/src/Components/Form/Utils.ts b/src/Components/Form/Utils.ts index 8f9f2c0d3ec..c6bf46e6d68 100644 --- a/src/Components/Form/Utils.ts +++ b/src/Components/Form/Utils.ts @@ -1,6 +1,6 @@ import { FieldError } from "./FieldValidators"; -export type FormDetails = Record; +export type FormDetails = { [key: string]: any }; export type FormErrors = Partial>; export type FormState = { form: T; errors: FormErrors }; export type FormAction = diff --git a/src/Components/Medicine/AdministerMedicine.tsx b/src/Components/Medicine/AdministerMedicine.tsx new file mode 100644 index 00000000000..180e3b1c9fb --- /dev/null +++ b/src/Components/Medicine/AdministerMedicine.tsx @@ -0,0 +1,76 @@ +import { useState } from "react"; +import { PrescriptionActions } from "../../Redux/actions"; +import ConfirmDialogV2 from "../Common/ConfirmDialogV2"; +import { Prescription } from "./models"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { Success } from "../../Utils/Notifications"; +import { useDispatch } from "react-redux"; +import PrescriptionDetailCard from "./PrescriptionDetailCard"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { formatDate } from "../../Utils/utils"; +import { useTranslation } from "react-i18next"; + +interface Props { + prescription: Prescription; + actions: ReturnType["prescription"]>; + onClose: (success: boolean) => void; +} + +export default function AdministerMedicine({ prescription, ...props }: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [isLoading, setIsLoading] = useState(false); + const [notes, setNotes] = useState(""); + + return ( + + + {t("administer_medicine")} + + } + title={t("administer_medicine")} + description={ +
+ Last administered + + {prescription.last_administered_on + ? formatDate(prescription.last_administered_on) + : t("never")} + +
+ } + show + onClose={() => props.onClose(false)} + // variant="primary" + onConfirm={async () => { + setIsLoading(true); + const res = await dispatch(props.actions.administer({ notes })); + if (res.status === 201) { + Success({ msg: t("medicines_administered") }); + } + setIsLoading(false); + props.onClose(true); + }} + className="max-w-4xl w-full" + > +
+ + setNotes(value)} + errorClassName="hidden" + disabled={isLoading} + /> +
+
+ ); +} diff --git a/src/Components/Medicine/CreatePrescriptionForm.tsx b/src/Components/Medicine/CreatePrescriptionForm.tsx new file mode 100644 index 00000000000..dc252bce701 --- /dev/null +++ b/src/Components/Medicine/CreatePrescriptionForm.tsx @@ -0,0 +1,216 @@ +import moment from "moment"; +import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import Form from "../Form/Form"; +import { createFormContext } from "../Form/FormContext"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import TextFormField from "../Form/FormFields/TextFormField"; +import { MedicineAdministrationRecord, Prescription } from "./models"; +import { PrescriptionActions } from "../../Redux/actions"; +import { useDispatch } from "react-redux"; +import { useState } from "react"; +import AutocompleteFormField from "../Form/FormFields/Autocomplete"; +import medicines_list from "../Common/prescription-builder/assets/medicines.json"; +import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormField"; +import { useTranslation } from "react-i18next"; + +export const medicines = medicines_list; + +const prescriptionFormContext = createFormContext(); + +export default function CreatePrescriptionForm(props: { + prescription: Prescription; + create: ReturnType["create"]; + onDone: () => void; +}) { + const dispatch = useDispatch(); + const [isCreating, setIsCreating] = useState(false); + const { t } = useTranslation(); + + return ( +
{ + setIsCreating(true); + const res = await dispatch(props.create(obj)); + setIsCreating(false); + if (res.status !== 201) { + return res.data; + } else { + props.onDone(); + } + }} + noPadding + validate={(form) => { + const errors: Partial> = {}; + errors.medicine = RequiredFieldValidator()(form.medicine); + 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; + }} + > + {(field) => ( + <> + medicine} + optionValue={(medicine) => medicine} + /> +
+ t("PRESCRIPTION_ROUTE_" + key)} + optionValue={(key) => key} + /> + +
+ + {props.prescription.is_prn ? ( + <> + + + `${hours} hrs.`} + optionValue={(hours) => hours} + position="above" + /> + + ) : ( +
+ + t("PRESCRIPTION_FREQUENCY_" + key.toUpperCase()) + } + optionValue={([key]) => key} + /> + +
+ )} + + + + )} + + ); +} + +export const PRESCRIPTION_ROUTES = ["ORAL", "IV", "IM", "SC"]; +export const PRESCRIPTION_FREQUENCIES = { + STAT: { + slots: 1, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => administration), + }, + OD: { + slots: 1, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "day") + ), + }, + HS: { + slots: 1, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "day") + ), + }, + BD: { + slots: 2, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "day") + ), + }, + TID: { + slots: 3, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "day") + ), + }, + QID: { + slots: 4, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "day") + ), + }, + Q4H: { + slots: 6, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "day") + ), + }, + QOD: { + slots: 1, + completed: (administrations: MedicineAdministrationRecord[]) => { + const lastAdministration = administrations[0]; + if (!lastAdministration) { + return []; + } + if ( + moment(lastAdministration.administered_date).isSame(moment(), "day") || + moment(lastAdministration.administered_date).isSame( + moment().subtract(1, "day"), + "day" + ) + ) { + return [lastAdministration]; + } else { + return [] as MedicineAdministrationRecord[]; + } + }, + }, + QWK: { + slots: 1, + completed: (administrations: MedicineAdministrationRecord[]) => + administrations.filter((administration) => + moment(administration.administered_date).isSame(moment(), "week") + ), + }, +}; diff --git a/src/Components/Medicine/DiscontinuePrescription.tsx b/src/Components/Medicine/DiscontinuePrescription.tsx new file mode 100644 index 00000000000..e02dd41787c --- /dev/null +++ b/src/Components/Medicine/DiscontinuePrescription.tsx @@ -0,0 +1,60 @@ +import { useState } from "react"; +import { PrescriptionActions } from "../../Redux/actions"; +import ConfirmDialogV2 from "../Common/ConfirmDialogV2"; +import { Prescription } from "./models"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { Success } from "../../Utils/Notifications"; +import { useDispatch } from "react-redux"; +import PrescriptionDetailCard from "./PrescriptionDetailCard"; +import { useTranslation } from "react-i18next"; + +interface Props { + prescription: Prescription; + actions: ReturnType["prescription"]>; + onClose: (discontinued: boolean) => void; +} + +export default function DiscontinuePrescription(props: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [isDiscontinuing, setIsDiscontinuing] = useState(false); + const [discontinuedReason, setDiscontinuedReason] = useState(""); + + return ( + props.onClose(false)} + variant="danger" + onConfirm={async () => { + setIsDiscontinuing(true); + const res = await dispatch( + props.actions.discontinue(discontinuedReason) + ); + if (res.status === 201) { + Success({ msg: t("prescription_discontinued") }); + } + setIsDiscontinuing(false); + props.onClose(true); + }} + className="max-w-4xl w-full" + > +
+ + setDiscontinuedReason(value)} + disabled={isDiscontinuing} + /> +
+
+ ); +} diff --git a/src/Components/Medicine/ManagePrescriptions.tsx b/src/Components/Medicine/ManagePrescriptions.tsx new file mode 100644 index 00000000000..8b9bc6bd990 --- /dev/null +++ b/src/Components/Medicine/ManagePrescriptions.tsx @@ -0,0 +1,48 @@ +import { useTranslation } from "react-i18next"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import useAppHistory from "../../Common/hooks/useAppHistory"; +import { PrescriptionActions } from "../../Redux/actions"; +import ButtonV2 from "../Common/components/ButtonV2"; +import Page from "../Common/components/Page"; +import PrescriptionBuilder from "./PrescriptionBuilder"; + +interface Props { + consultationId: string; +} + +export default function ManagePrescriptions({ consultationId }: Props) { + const actions = PrescriptionActions(consultationId); + const { t } = useTranslation(); + const { goBack } = useAppHistory(); + + return ( + +
+
+
+

+ {t("prescription_medications")} +

+ +
+
+

+ {t("prn_prescriptions")} +

+ +
+
+
+ goBack()}> + + {t("return_to_patient_dashboard")} + + + + {t("all_changes_have_been_saved")} + +
+
+
+ ); +} diff --git a/src/Components/Medicine/MedicineAdministration.tsx b/src/Components/Medicine/MedicineAdministration.tsx new file mode 100644 index 00000000000..978d3231d8d --- /dev/null +++ b/src/Components/Medicine/MedicineAdministration.tsx @@ -0,0 +1,126 @@ +import { useEffect, useMemo, useState } from "react"; +import { PrescriptionActions } from "../../Redux/actions"; +import PrescriptionDetailCard from "./PrescriptionDetailCard"; +import { MedicineAdministrationRecord, Prescription } from "./models"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; +import ButtonV2 from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { useDispatch } from "react-redux"; +import { Error, Success } from "../../Utils/Notifications"; +import { formatDate } from "../../Utils/utils"; +import { useTranslation } from "react-i18next"; + +interface Props { + prescriptions: Prescription[]; + action: ReturnType["prescription"]; + onDone: () => void; +} + +export default function MedicineAdministration(props: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [shouldAdminister, setShouldAdminister] = useState([]); + const [notes, setNotes] = useState( + [] + ); + + const prescriptions = useMemo( + () => + props.prescriptions.filter( + (obj) => !obj.discontinued && obj.prescription_type !== "DISCHARGE" + ), + [props.prescriptions] + ); + + useEffect(() => { + setShouldAdminister(Array(prescriptions.length).fill(false)); + setNotes(Array(prescriptions.length).fill("")); + }, [props.prescriptions]); + + const handleSubmit = () => { + const records: MedicineAdministrationRecord[] = []; + prescriptions.forEach((prescription, i) => { + if (shouldAdminister[i]) { + records.push({ prescription, notes: notes[i] }); + } + }); + + Promise.all( + records.map(async ({ prescription, ...record }) => { + const res = await dispatch( + props.action(prescription!.id!).administer(record) + ); + if (res.status !== 201) { + Error({ msg: t("medicines_administered_error") }); + } + }) + ).then(() => { + Success({ msg: t("medicines_administered") }); + props.onDone(); + }); + }; + + const selectedCount = shouldAdminister.filter(Boolean).length; + + return ( +
+ {prescriptions.map((obj, index) => ( + +
+ + setShouldAdminister((shouldAdminister) => { + const newShouldAdminister = [...shouldAdminister]; + newShouldAdminister[index] = event.value; + return newShouldAdminister; + }) + } + errorClassName="hidden" + /> +
+ {" "} + {t("last_administered")} + + {obj.last_administered_on + ? formatDate(obj.last_administered_on) + : t("never")} + +
+ + setNotes((notes) => { + const newNotes = [...notes]; + newNotes[index] = event.value; + return newNotes; + }) + } + errorClassName="hidden" + /> +
+
+ ))} +
+ + + {t("administer_selected_medicines")}{" "} + {selectedCount > 0 && `(${selectedCount})`} + +
+
+ ); +} diff --git a/src/Components/Medicine/MedicineAdministrationsTable.tsx b/src/Components/Medicine/MedicineAdministrationsTable.tsx new file mode 100644 index 00000000000..718f9d037dd --- /dev/null +++ b/src/Components/Medicine/MedicineAdministrationsTable.tsx @@ -0,0 +1,86 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import ResponsiveMedicineTable from "../Common/components/ResponsiveMedicineTables"; +import { formatDate } from "../../Utils/utils"; +import { PrescriptionActions } from "../../Redux/actions"; +import { useDispatch } from "react-redux"; +import { MedicineAdministrationRecord } from "./models"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; +import { useTranslation } from "react-i18next"; + +interface Props { + consultation_id: string; +} + +export default function MedicineAdministrationsTable({ + consultation_id, +}: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [items, setItems] = useState(); + + const { listAdministrations } = useMemo( + () => PrescriptionActions(consultation_id), + [consultation_id] + ); + + const fetchItems = useCallback(() => { + dispatch(listAdministrations()).then((res: any) => + setItems(res.data.results) + ); + }, [consultation_id]); + + useEffect(() => { + fetchItems(); + }, [consultation_id]); + + const lastModified = items?.[0]?.modified_date; + + return ( +
+
+
+ + {t("medicine_administration_history")} + +
+ + + {lastModified && formatDate(lastModified)} + +
+
+
+
+
+
+ t(_))} + list={ + items?.map((obj) => ({ + ...obj, + medicine: obj.prescription?.medicine, + created_date__pretty: ( + + by{" "} + {obj.administered_by?.first_name}{" "} + {obj.administered_by?.last_name} + + ), + ...obj, + })) || [] + } + objectKeys={["medicine", "notes", "created_date__pretty"]} + fieldsToDisplay={[2, 3]} + /> + {items?.length === 0 && ( +
+ {t("no_data_found")} +
+ )} +
+
+
+
+ ); +} diff --git a/src/Components/Medicine/PrescriptionBuilder.tsx b/src/Components/Medicine/PrescriptionBuilder.tsx new file mode 100644 index 00000000000..44c3b47d9a3 --- /dev/null +++ b/src/Components/Medicine/PrescriptionBuilder.tsx @@ -0,0 +1,130 @@ +import { useCallback, useEffect, useState } from "react"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ButtonV2 from "../Common/components/ButtonV2"; +import { NormalPrescription, Prescription } from "./models"; +import DialogModal from "../Common/Dialog"; +import { PRNPrescription } from "./models"; +import CreatePrescriptionForm from "./CreatePrescriptionForm"; +import PrescriptionDetailCard from "./PrescriptionDetailCard"; +import { PrescriptionActions } from "../../Redux/actions"; +import { useDispatch } from "react-redux"; +import DiscontinuePrescription from "./DiscontinuePrescription"; +import AdministerMedicine from "./AdministerMedicine"; +import { useTranslation } from "react-i18next"; + +interface Props { + prescription_type?: Prescription["prescription_type"]; + actions: ReturnType; + is_prn?: boolean; + disabled?: boolean; +} + +export default function PrescriptionBuilder({ + prescription_type, + actions, + is_prn = false, + disabled, +}: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + + const [prescriptions, setPrescriptions] = useState(); + const [showCreate, setShowCreate] = useState(false); + const [showDiscontinueFor, setShowDiscontinueFor] = useState(); + const [showAdministerFor, setShowAdministerFor] = useState(); + + const fetchPrescriptions = useCallback(() => { + dispatch(actions.list({ is_prn, prescription_type })).then((res: any) => + setPrescriptions(res.data.results) + ); + }, [dispatch, is_prn]); + + useEffect(() => { + fetchPrescriptions(); + }, []); + + return ( +
+ {showDiscontinueFor && ( + { + setShowDiscontinueFor(undefined); + if (success) fetchPrescriptions(); + }} + key={showDiscontinueFor.id} + /> + )} + {showAdministerFor && ( + { + setShowAdministerFor(undefined); + if (success) fetchPrescriptions(); + }} + key={showAdministerFor.id} + /> + )} +
+ {prescriptions?.map((obj, index) => ( + setShowDiscontinueFor(obj)} + onAdministerClick={() => setShowAdministerFor(obj)} + readonly={disabled} + /> + ))} +
+ setShowCreate(true)} + variant="secondary" + className="mt-4 bg-gray-200 text-gray-700 hover:bg-gray-300 hover:text-gray-900 w-full focus:bg-gray-100 focus:text-gray-900" + align="start" + disabled={disabled} + > + + + {t(is_prn ? "add_prn_prescription" : "add_prescription_medication")} + + + {showCreate && ( + setShowCreate(false)} + show={showCreate} + title={t( + is_prn ? "add_prn_prescription" : "add_prescription_medication" + )} + description={ +
+ + {t("modification_caution_note")} +
+ } + className="max-w-3xl w-full" + > + { + setShowCreate(false); + fetchPrescriptions(); + }} + /> +
+ )} +
+ ); +} + +const DefaultPrescription: Partial = { is_prn: false }; +const DefaultPRNPrescription: Partial = { is_prn: true }; diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx new file mode 100644 index 00000000000..d6be22ad1c1 --- /dev/null +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -0,0 +1,174 @@ +import { Prescription } from "./models"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { classNames } from "../../Utils/utils"; +import ReadMore from "../Common/components/Readmore"; +import ButtonV2 from "../Common/components/ButtonV2"; +import { PrescriptionActions } from "../../Redux/actions"; +import { useTranslation } from "react-i18next"; + +export default function PrescriptionDetailCard({ + prescription, + ...props +}: { + prescription: Prescription; + readonly?: boolean; + children?: React.ReactNode; + actions: ReturnType["prescription"]>; + onDiscontinueClick?: () => void; + onAdministerClick?: () => void; + selected?: boolean; +}) { + const { t } = useTranslation(); + return ( +
+
+
+
+
+

+ {prescription.prescription_type === "DISCHARGE" && + `${t("discharge")} `} + {t(prescription.is_prn ? "prn_prescription" : "prescription")} + {` #${prescription.id?.slice(-5)}`} +

+ {prescription.discontinued && ( + + {t("discontinued")} + + )} +
+ + {!props.readonly && + prescription.prescription_type !== "DISCHARGE" && ( +
+ + + {t("administer")} + + + + {t("discontinue")} + +
+ )} +
+
+ +
+ + {prescription.medicine} + + + {prescription.route && + t("PRESCRIPTION_ROUTE_" + prescription.route)} + + + {prescription.dosage} + + + {prescription.is_prn ? ( + <> + + {prescription.indicator} + + + {prescription.max_dosage} + + + {prescription.max_dosage} + + + ) : ( + <> + + {prescription.frequency && + t( + "PRESCRIPTION_FREQUENCY_" + + prescription.frequency.toUpperCase() + )} + + + {prescription.days} + + + )} + + {prescription.notes && ( + + + + )} + + {prescription.discontinued && ( + + {prescription.discontinued_reason} + + )} +
+
+ + {props.children} +
+ ); +} + +const Detail = (props: { + className?: string; + label: string; + children?: React.ReactNode; +}) => { + const { t } = useTranslation(); + return ( +
+ +
+ {props.children ? ( + {props.children} + ) : ( + {t("not_specified")} + )} +
+
+ ); +}; diff --git a/src/Components/Medicine/PrescriptionsTable.tsx b/src/Components/Medicine/PrescriptionsTable.tsx new file mode 100644 index 00000000000..7b6272675b9 --- /dev/null +++ b/src/Components/Medicine/PrescriptionsTable.tsx @@ -0,0 +1,327 @@ +import { useCallback, useEffect, useMemo, useState } from "react"; +import ResponsiveMedicineTable from "../Common/components/ResponsiveMedicineTables"; +import { formatDate } from "../../Utils/utils"; +import { PrescriptionActions } from "../../Redux/actions"; +import { useDispatch } from "react-redux"; +import { Prescription } from "./models"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; +import SlideOver from "../../CAREUI/interactive/SlideOver"; +import MedicineAdministration from "./MedicineAdministration"; +import DiscontinuePrescription from "./DiscontinuePrescription"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; +import AdministerMedicine from "./AdministerMedicine"; +import DialogModal from "../Common/Dialog"; +import PrescriptionDetailCard from "./PrescriptionDetailCard"; +import { useTranslation } from "react-i18next"; + +interface Props { + is_prn?: boolean; + prescription_type?: Prescription["prescription_type"]; + consultation_id: string; + onChange?: () => void; + readonly?: boolean; +} + +export default function PrescriptionsTable({ + is_prn = false, + prescription_type = "REGULAR", + consultation_id, + onChange, + readonly, +}: Props) { + const dispatch = useDispatch(); + const { t } = useTranslation(); + + const [prescriptions, setPrescriptions] = useState(); + const [showBulkAdminister, setShowBulkAdminister] = useState(false); + const [showDiscontinueFor, setShowDiscontinueFor] = useState(); + const [showAdministerFor, setShowAdministerFor] = useState(); + const [detailedViewFor, setDetailedViewFor] = useState(); + + const { list, prescription } = useMemo( + () => PrescriptionActions(consultation_id), + [consultation_id] + ); + + const fetchPrescriptions = useCallback(() => { + dispatch(list({ is_prn, prescription_type })).then((res: any) => + setPrescriptions(res.data.results) + ); + }, [consultation_id]); + + useEffect(() => { + fetchPrescriptions(); + }, [consultation_id]); + + const lastModified = prescriptions?.[0]?.modified_date; + const tkeys = + prescription_type === "REGULAR" + ? is_prn + ? REGULAR_PRN_TKEYS + : REGULAR_NORMAL_TKEYS + : is_prn + ? DISCHARGE_PRN_TKEYS + : DISCHARGE_NORMAL_TKEYS; + + return ( +
+ {prescriptions && ( + + { + setShowBulkAdminister(false); + onChange?.(); + }} + /> + + )} + {showDiscontinueFor && ( + { + setShowDiscontinueFor(undefined); + if (success) onChange?.(); + }} + key={showDiscontinueFor.id} + /> + )} + {showAdministerFor && ( + { + setShowAdministerFor(undefined); + if (success) onChange?.(); + }} + key={showAdministerFor.id} + /> + )} + {detailedViewFor && ( + setDetailedViewFor(undefined)} + title={t("prescription_details")} + className="md:max-w-4xl w-full" + show + > +
+ +
+ setDetailedViewFor(undefined)} + label={t("close")} + /> + setShowDiscontinueFor(detailedViewFor)} + > + + {t("discontinue")} + + setShowAdministerFor(detailedViewFor)} + > + + {t("administer")} + +
+
+
+ )} +
+
+ + {is_prn ? "PRN Prescriptions" : "Prescriptions"} + +
+ + + {lastModified && formatDate(lastModified)} + +
+
+ {prescription_type === "REGULAR" && ( +
+ + + {t("edit_prescriptions")} + {t("edit")} + + setShowBulkAdminister(true)} + className="w-full lg:w-auto" + > + + + {t("administer_medicines")} + + {t("administer")} + +
+ )} +
+
+
+
+ t(_))} + list={ + prescriptions?.map((obj) => ({ + ...obj, + route__pretty: + obj.route && t("PRESCRIPTION_ROUTE_" + obj.route), + frequency__pretty: + obj.frequency && + t("PRESCRIPTION_FREQUENCY_" + obj.frequency.toUpperCase()), + days__pretty: obj.days && obj.days + " day(s)", + min_hours_between_doses__pretty: + obj.min_hours_between_doses && + obj.min_hours_between_doses + " hour(s)", + last_administered__pretty: obj.last_administered_on ? ( + + ) : ( + "never" + ), + })) || [] + } + objectKeys={Object.values(tkeys)} + fieldsToDisplay={[2, 3]} + actions={ + !readonly + ? (med: Prescription) => { + if (med.prescription_type === "DISCHARGE") { + return ( +
+ + {t("discharge_prescription")} + +
+ ); + } + + if (med.discontinued) { + return ( +
+ + {t("discontinued")} +
+ ); + } + + return ( +
+ { + e.stopPropagation(); + setShowAdministerFor(med); + }} + > + + {t("administer")} + + { + e.stopPropagation(); + setShowDiscontinueFor(med); + }} + > + + {t("discontinue")} + +
+ ); + } + : undefined + } + /> + {prescriptions?.length === 0 && ( +
+ {t("no_data_found")} +
+ )} +
+
+
+
+ ); +} + +const COMMON_TKEYS = { + medicine: "medicine", + route: "route__pretty", + dosage: "dosage", +}; + +const REGULAR_NORMAL_TKEYS = { + ...COMMON_TKEYS, + frequency: "frequency__pretty", + days: "days__pretty", + notes: "notes", + last_administered: "last_administered__pretty", +}; + +const REGULAR_PRN_TKEYS = { + ...COMMON_TKEYS, + indicator: "indicator", + max_dosage_24_hrs: "max_dosage", + min_time_bw_doses: "min_hours_between_doses__pretty", + notes: "notes", + last_administered: "last_administered__pretty", +}; + +const DISCHARGE_NORMAL_TKEYS = { + ...COMMON_TKEYS, + frequency: "frequency__pretty", + days: "days__pretty", + notes: "notes", +}; + +const DISCHARGE_PRN_TKEYS = { + ...COMMON_TKEYS, + indicator: "indicator", + max_dosage_24_hrs: "max_dosage", + min_time_bw_doses: "min_hours_between_doses__pretty", + notes: "notes", +}; diff --git a/src/Components/Medicine/models.ts b/src/Components/Medicine/models.ts new file mode 100644 index 00000000000..2f02e6a84b2 --- /dev/null +++ b/src/Components/Medicine/models.ts @@ -0,0 +1,58 @@ +import { PerformedByModel } from "../HCX/misc"; + +interface BasePrescription { + readonly id?: string; + medicine: string; + route?: "ORAL" | "IV" | "IM" | "SC"; + dosage: string; + notes?: string; + meta?: object; + readonly prescription_type?: "DISCHARGE" | "REGULAR"; + readonly discontinued?: boolean; + discontinued_reason?: string; + readonly prescribed_by?: PerformedByModel; + readonly discontinued_date: string; + readonly last_administered_on?: string; + readonly is_migrated?: boolean; + readonly created_date?: string; + readonly modified_date?: string; +} + +export interface NormalPrescription extends BasePrescription { + frequency: + | "STAT" + | "OD" + | "HS" + | "BD" + | "TID" + | "QID" + | "Q4H" + | "QOD" + | "QWK"; + days?: number; + is_prn: false; + indicator?: undefined; + max_dosage?: undefined; + min_hours_between_doses?: undefined; +} + +export interface PRNPrescription extends BasePrescription { + indicator: string; + max_dosage?: string; + min_hours_between_doses?: number; + is_prn: true; + frequency?: undefined; + days?: undefined; +} + +export type Prescription = NormalPrescription | PRNPrescription; + +export type MedicineAdministrationRecord = { + readonly id?: string; + readonly prescription?: Prescription; + notes: string; + readonly administered_by?: PerformedByModel; + readonly administered_date?: string; + readonly created_date?: string; + readonly modified_date?: string; +}; diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index 4761204ebbf..d8d2f20abba 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -2,10 +2,18 @@ "coronasafe_network": "CoronaSafe Network", "goal": "Our goal is to continuously improve the quality and accessibility of public healthcare services using digital tools.", "something_wrong": "Something went wrong! Try again later!", + "try_again_later": "Try again later!", "contribute_github": "Contribute on Github", "footer_body": "CoronaSafe Network is an open-source public utility designed by a multi-disciplinary team of innovators and volunteers. CoronaSafe CARE is a Digital Public Good recognised by the United Nations.", "reset": "Reset", + "download": "Download", "downloads": "Downloads", + "downloading": "Downloading", + "generating": "Generating", + "send_email": "Send Email", + "email_address": "Email Address", + "email_success": "We will be sending an email shortly. Please check your inbox.", + "disclaimer": "Disclaimer", "category": "Category", "sub_category": "Sub Category", "download_type": "Download Type", @@ -83,7 +91,6 @@ "type_your_comment": "Type your comment", "any_other_comments": "Any other comments", "loading": "Loading", - "download": "Download", "facility": "Facility", "local_body": "Local body", "filters": "Filters", @@ -129,5 +136,17 @@ "RESPIRATORY_SUPPORT_OXYGEN_SUPPORT": "O2 Support", "RESPIRATORY_SUPPORT_NON_INVASIVE": "NIV", "RESPIRATORY_SUPPORT_INVASIVE": "IV", - "choose_file": "Choose File" + "choose_file": "Choose File", + "frequency": "Frequency", + "days": "Days", + "never": "never", + "notes": "Notes", + "add_notes": "Add notes", + "optional": "Optional", + "discontinue": "Discontinue", + "discontinued": "Discontinued", + "not_specified": "Not Specified", + "all_changes_have_been_saved": "All changes have been saved", + "no_data_found": "No data found", + "edit": "Edit" } \ No newline at end of file diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json index 16e03486fd0..9c6799cac16 100644 --- a/src/Locale/en/Consultation.json +++ b/src/Locale/en/Consultation.json @@ -4,5 +4,11 @@ "update_log": "Update Log", "log_lab_results": "Log Lab Results", "no_log_update_delta": "No changes since previous log update", - "virtual_nursing_assistant": "Virtual Nursing Assistant" + "virtual_nursing_assistant": "Virtual Nursing Assistant", + "discharge": "Discharge", + "generating_discharge_summary": "Generating discharge summary", + "discharge_summary_not_ready": "Discharge summary is not ready yet.", + "download_discharge_summary": "Download discharge summary", + "email_discharge_summary_description": "Enter your valid email address to receive the discharge summary", + "generated_summary_caution": "This is a computer generated summary using the information captured in the CARE system." } \ No newline at end of file diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json new file mode 100644 index 00000000000..779e93373d6 --- /dev/null +++ b/src/Locale/en/Medicine.json @@ -0,0 +1,50 @@ +{ + "medicine": "Medicine", + "route": "Route", + "dosage": "Dosage", + "indicator": "Indicator", + "inidcator_event": "Indicator Event", + "max_dosage_24_hrs": "Max. dosage in 24 hrs.", + "min_time_bw_doses": "Min. time b/w doses", + "manage_prescriptions": "Manage Prescriptions", + "prescription_details": "Prescription Details", + "prescription_medications": "Prescription Medications", + "prn_prescriptions": "PRN Prescriptions", + "prescription": "Prescription", + "discharge_prescription": "Discharge Prescription", + "edit_prescriptions": "Edit Prescriptions", + "prescription_medication": "Prescription Medication", + "add_prescription_medication": "Add Prescription Medication", + "prn_prescription": "PRN Prescription", + "add_prn_prescription": "Add PRN Prescription", + "add_prescription_to_consultation_note": "Add a new prescription to this consultation.", + "medicine_administration_history": "Medicine Administration History", + "return_to_patient_dashboard": "Return to Patient Dashboard", + "administered_on": "Administered on", + "administer": "Administer", + "administer_medicine": "Administer Medicine", + "administer_medicines": "Administer Medicines", + "administer_selected_medicines": "Administer Selected Medicines", + "select_for_administration": "Select for Administration", + "medicines_administered": "Medicine(s) administered", + "medicines_administered_error": "Error administering medicine(s)", + "prescription_discontinued": "Prescription discontinued", + "administration_notes": "Administration Notes", + "last_administered": "Last administered", + "modification_caution_note": "No modifications possible once added", + "discontinue_caution_note": "Are you sure you want to discontinue this prescription?", + "reason_for_discontinuation": "Reason for discontinuation", + "PRESCRIPTION_ROUTE_ORAL": "Oral", + "PRESCRIPTION_ROUTE_IV": "IV", + "PRESCRIPTION_ROUTE_IM": "IM", + "PRESCRIPTION_ROUTE_SC": "S/C", + "PRESCRIPTION_FREQUENCY_STAT": "Imediately", + "PRESCRIPTION_FREQUENCY_OD": "Once daily", + "PRESCRIPTION_FREQUENCY_HS": "Night only", + "PRESCRIPTION_FREQUENCY_BD": "Twice daily", + "PRESCRIPTION_FREQUENCY_TID": "8th hourly", + "PRESCRIPTION_FREQUENCY_QID": "6th hourly", + "PRESCRIPTION_FREQUENCY_Q4H": "4th hourly", + "PRESCRIPTION_FREQUENCY_QOD": "Alternate day", + "PRESCRIPTION_FREQUENCY_QWK": "Once a week" +} \ No newline at end of file diff --git a/src/Locale/en/index.js b/src/Locale/en/index.js index 700d4758daa..8bd6fca2285 100644 --- a/src/Locale/en/index.js +++ b/src/Locale/en/index.js @@ -12,6 +12,7 @@ import ExternalResult from "./ExternalResult.json"; import CoverImageEdit from "./CoverImageEdit.json"; import Resource from "./Resource.json"; import SortOptions from "./SortOptions.json"; +import Medicine from "./Medicine.json"; export default { ...Auth, @@ -24,6 +25,7 @@ export default { ...ExternalResult, ...Facility, ...Hub, + ...Medicine, ...Notifications, ...Resource, ...Shifting, diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index ffe68356d8e..3fe8d7c0149 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1,4 +1,8 @@ import { HCXClaimModel, HCXPolicyModel } from "../Components/HCX/models"; +import { + MedicineAdministrationRecord, + Prescription, +} from "../Components/Medicine/models"; import { fireRequest, fireRequestForFiles } from "./fireRequest"; export const getConfig = () => { @@ -580,8 +584,21 @@ export const deleteLastInventoryLog = (params: object) => { return fireRequest("deleteLastInventoryLog", [], {}, params); }; -export const discharge = (params: object, pathParams: object) => { - return fireRequest("discharge", [], params, pathParams); +export const generateDischargeSummary = (pathParams: object) => { + return fireRequest("dischargeSummaryGenerate", [], {}, pathParams); +}; +export const previewDischargeSummary = (pathParams: object) => { + return fireRequest( + "dischargeSummaryPreview", + [], + {}, + pathParams, + undefined, + true + ); +}; +export const emailDischargeSummary = (params: object, pathParams: object) => { + return fireRequest("dischargeSummaryEmail", [], params, pathParams); }; export const dischargePatient = (params: object, pathParams: object) => { return fireRequest("dischargePatient", [], params, pathParams); @@ -832,6 +849,62 @@ export const getAssetTransaction = (id: string) => export const listPMJYPackages = (query?: string) => fireRequest("listPMJYPackages", [], { query }); +/** Prescription related actions */ +export const PrescriptionActions = (consultation_external_id: string) => { + const pathParams = { consultation_external_id }; + + return { + list: (query?: Partial) => { + let altKey; + if (query?.is_prn !== undefined) { + altKey = query?.is_prn + ? "listPRNPrescriptions" + : "listNormalPrescriptions"; + } + return fireRequest("listPrescriptions", [], query, pathParams, altKey); + }, + + create: (obj: Prescription) => + fireRequest("createPrescription", [], obj, pathParams), + + listAdministrations: (query?: object) => + fireRequest("listAdministrations", [], query, pathParams), + + getAdministration: (external_id: string) => + fireRequest("getAdministration", [], {}, { ...pathParams, external_id }), + + /** Returns actions specific to a prescription */ + prescription(external_id: string) { + const pathParams = { consultation_external_id, external_id }; + + return { + /** Read a specific prescription of a consultation */ + get: () => fireRequest("getPrescription", [], {}, pathParams), + + /** Administer a prescription */ + administer: (obj: MedicineAdministrationRecord) => + fireRequest( + "administerPrescription", + [], + obj, + pathParams, + `administer-medicine-${external_id}` + ), + + /** Discontinue a prescription */ + discontinue: (discontinued_reason: string | undefined) => + fireRequest( + "discontinuePrescription", + [], + { discontinued_reason }, + pathParams, + `discontinue-medicine-${external_id}` + ), + }; + }, + }; +}; + // HCX Actions export const HCXActions = { diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 1278a51ea6d..54276885d0d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -588,12 +588,20 @@ const routes: Routes = { path: "/api/v1/facility/{facility_external_id}/inventory/delete_last/?item={id}", method: "DELETE", }, - discharge: { - path: "/api/v1/patient/{external_id}/discharge_summary/", + dischargeSummaryGenerate: { + path: "/api/v1/consultation/{external_id}/generate_discharge_summary/", + method: "POST", + }, + dischargeSummaryPreview: { + path: "/api/v1/consultation/{external_id}/preview_discharge_summary", + method: "GET", + }, + dischargeSummaryEmail: { + path: "/api/v1/consultation/{external_id}/email_discharge_summary/", method: "POST", }, dischargePatient: { - path: "/api/v1/patient/{id}/discharge_patient/", + path: "/api/v1/consultation/{id}/discharge_patient/", method: "POST", }, //Profile @@ -799,6 +807,43 @@ const routes: Routes = { method: "GET", }, + // Prescription endpoints + + listPrescriptions: { + path: "/api/v1/consultation/{consultation_external_id}/prescriptions/", + method: "GET", + }, + + createPrescription: { + path: "/api/v1/consultation/{consultation_external_id}/prescriptions/", + method: "POST", + }, + + listAdministrations: { + path: "/api/v1/consultation/{consultation_external_id}/prescription_administration/", + method: "GET", + }, + + getAdministration: { + path: "/api/v1/consultation/{consultation_external_id}/prescription_administration/{external_id}/", + method: "GET", + }, + + getPrescription: { + path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/", + method: "GET", + }, + + administerPrescription: { + path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/administer/", + method: "POST", + }, + + discontinuePrescription: { + path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/discontinue/", + method: "POST", + }, + // HCX Endpoints listPMJYPackages: { diff --git a/src/Router/AppRouter.tsx b/src/Router/AppRouter.tsx index 6bd81da9c0f..c79b5a95974 100644 --- a/src/Router/AppRouter.tsx +++ b/src/Router/AppRouter.tsx @@ -71,6 +71,7 @@ import FacilityCNS from "../Components/Facility/FacilityCNS"; import ConsultationClaims from "../Components/Facility/ConsultationClaims"; import { handleSignOut } from "../Utils/utils"; import SessionExpired from "../Components/ErrorPages/SessionExpired"; +import ManagePrescriptions from "../Components/Medicine/ManagePrescriptions"; export default function AppRouter() { const { static_black_logo, enable_hcx } = useConfig(); @@ -177,6 +178,8 @@ export default function AppRouter() { unspecified={true} /> ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions": + (path: any) => , "/facility/:facilityId/patient/:patientId/consultation/:id/investigation": ({ facilityId, patientId, id }: any) => ( { return `${momentDate.fromNow()} at ${momentDate.format("hh:mm A")}`; }; +export const relativeTime = (time: string | Date) => { + const momentTime = moment(time); + return `${momentTime.fromNow()}`; +}; + export const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms)); export const handleSignOut = (forceReload: boolean) => {