diff --git a/netlify.toml b/netlify.toml index f23b951ff21..c7b76577ef8 100644 --- a/netlify.toml +++ b/netlify.toml @@ -9,7 +9,7 @@ NODE_OPTIONS = "--max_old_space_size=4096" [[redirects]] from = "/api/*" -to = "https://careapi.ohc.network/api/:splat" +to = "https://care-ai-poc.ohc.network/api/:splat" status = 200 force = true diff --git a/src/Components/Common/components/AccordionV2.tsx b/src/Components/Common/components/AccordionV2.tsx index ef4948d8ae3..7b410e8e320 100644 --- a/src/Components/Common/components/AccordionV2.tsx +++ b/src/Components/Common/components/AccordionV2.tsx @@ -2,6 +2,7 @@ import { useRef, useState } from "react"; import { classNames } from "../../../Utils/utils"; export default function AccordionV2(props: { + prefix?: JSX.Element | JSX.Element[]; children: JSX.Element | JSX.Element[]; expandIcon?: JSX.Element; title: JSX.Element | JSX.Element[] | string; @@ -14,6 +15,7 @@ export default function AccordionV2(props: { return (
+ <>{props.prefix}
+ setOpenAIDischargeSummaryDialog(true)}> + + AI {t("discharge_summary")} + setOpenDischargeSummaryDialog(true)}> {t("discharge_summary")} diff --git a/src/Components/Facility/DischargeAISummaryModal.tsx b/src/Components/Facility/DischargeAISummaryModal.tsx new file mode 100644 index 00000000000..01d80710776 --- /dev/null +++ b/src/Components/Facility/DischargeAISummaryModal.tsx @@ -0,0 +1,579 @@ +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"; +import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; +import { + FieldChangeEvent, + FieldChangeEventHandler, +} from "../Form/FormFields/Utils"; +import CollapseV2 from "../Common/components/CollapseV2"; +import AccordionV2 from "../Common/components/AccordionV2"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; + +interface Props { + show: boolean; + onClose: () => void; + consultation: ConsultationModel; +} + +const options = [ + { + category: "Generic", + label: "Facility Name", + value: "patient.facility.name", + }, + { + category: "Patient Information", + label: "Patient Name", + value: "patient.name", + }, + { + category: "Patient Information", + label: "Patient Gender", + value: "patient.get_gender_display", + }, + { + category: "Patient Information", + label: "Patient Age", + value: "patient.age", + }, + { + category: "Patient Information", + label: "Patient Date of Birth", + value: "patient.date_of_birth", + }, + { + category: "Patient Information", + label: "Patient Blood Group", + value: "patient.blood_group", + }, + { + category: "Patient Information", + label: "Patient Phone Number", + value: "patient.phone_number", + }, + { + category: "Patient Information", + label: "Patient Address", + value: "patient.address", + }, + { + category: "Consultation", + label: "Consultation Status", + value: "consultation.get_consultation_status_display", + }, + { + category: "Consultation", + label: "Consultation Suggestion", + value: "consultation.get_suggestion_display", + }, + { + category: "Consultation", + label: "Admission Date", + value: "consultation.admission_date.date", + condition: "consultation.suggestion === 'A'", + }, + { + category: "Consultation", + label: "Consultation Notes", + value: "consultation.consultation_notes", + condition: "consultation.consultation_notes", + }, + { + category: "Consultation", + label: "Special Instruction", + value: "consultation.special_instruction", + condition: "consultation.special_instruction", + }, + { + category: "Consultation", + label: "Prescribed Medication", + value: "consultation.prescribed_medication", + condition: "consultation.prescribed_medication", + }, + { + category: "Consultation", + label: "Procedure", + value: "consultation.procedure", + condition: "consultation.procedure", + }, + { + category: "Consultation", + label: "Investigation", + value: "investigations", + condition: "consultation.investigation", + }, + { + category: "Patient Information", + label: "Present Health", + value: "patient.present_health", + }, + { + category: "Patient Information", + label: "Ongoing Medication", + value: "patient.ongoing_medication", + }, + { + category: "Patient Information", + label: "Allergies", + value: "patient.allergies", + }, + { + category: "Patient Information", + label: "IP No", + value: "consultation.ip_no", + }, + { + category: "Patient Information", + label: "Weight", + value: "consultation.weight", + }, + { + category: "Patient Information", + label: "Height", + value: "consultation.height", + }, + { + category: "Patient Information", + label: "Symptoms", + value: "consultation.get_symptoms_display|title", + condition: "consultation.consultation_status !== 1", + }, + { + category: "Patient Information", + label: "Symptoms Onset Date", + value: "consultation.symptoms_onset_date.date", + condition: "consultation.consultation_status !== 1", + }, + { + category: "Patient Information", + label: "Date of Result", + value: "patient.date_of_result.date", + condition: "patient.disease_status === 2 && patient.date_of_result", + }, + { + category: "Patient Information", + label: "Is Vaccinated", + value: "patient.is_vaccinated", + condition: "patient.disease_status === 2 && patient.is_vaccinated", + }, + { + category: "Patient Medical History", + label: "Medical History", + value: "medical_history", + condition: "medical_history", + }, + { + category: "HCX Information", + label: "Insurer Name", + value: "hcx", + condition: "hcx", + }, + { + category: "Diagnosis", + label: "Provisional Diagnosis", + value: "provisional_diagnosis", + condition: "provisional_diagnosis", + }, + { + category: "Diagnosis", + label: "Diagnosis ID", + value: "diagnosis", + condition: "diagnosis", + }, + { + category: "Discharge", + label: "Discharge Notes", + value: "consultation.discharge_notes", + condition: "consultation.suggestion === 'DD'", + }, + { + category: "Discharge", + label: "Death Date Time", + value: "consultation.death_datetime", + condition: "consultation.suggestion === 'DD'", + }, + { + category: "Discharge", + label: "Death Confirmed By", + value: "consultation.death_confirmed_by", + condition: "consultation.suggestion === 'DD'", + }, + { + category: "Discharge", + label: "Discharge Summary", + value: "consultation.suggestion === 'R'", + }, + { + category: "Prescription", + label: "Prescription", + value: "prescriptions", + condition: "prescriptions", + }, + { + category: "PRN Prescription", + label: "PRN Prescription", + value: "prn_prescriptions", + condition: "prn_prescriptions", + }, + { + category: "Examination", + label: "Examination Details", + value: "consultation.examination_details", + }, + + { + category: "Sample Information", + label: "Sample", + value: "samples", + condition: "samples", + }, + { + category: "Consultation", + label: "Treatment Plan", + value: "consultation.treatment_plan", + }, + { + category: "Admission Details", + label: "Daily Rounds", + value: "dailyrounds", + condition: "dailyrounds", + }, + { + category: "Discharge Information", + label: "Discharge Date", + value: "consultation.discharge_date", + }, + { + category: "Discharge Information", + label: "Discharge Reason", + value: "consultation.get_discharge_reason_display", + }, + { + category: "Discharge Information", + label: "Discharge Prescription ", + value: "discharge_prescriptions", + condition: + "discharge_prescriptions", + }, + { + category: "Discharge Information", + label: "Discharge PRN Prescription ", + value: "discharge_prn_prescriptions", + condition: + "discharge_prn_prescriptions", + }, + + { + category: "Discharge Information", + label: "Discharge Notes", + value: "consultation.discharge_notes", + }, + { + category: "Discharge Information", + label: "Verified By", + value: "consultation.verified_by|linebreaks", + }, + { + category: "Discharge Information", + label: "Discharge Summary", + value: "summary.discharge_summary", + }, +]; + +export default function DischargeAISummaryModal(props: Props) { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [email, setEmail] = useState(""); + const [additional_details, setAdditionalDetails] = useState(""); + const [emailError, setEmailError] = useState(""); + const [emailing, setEmailing] = useState(false); + const [downloading, setDownloading] = useState(false); + const [generating, setGenerating] = useState(false); + const [regenDischargeSummary, setRegenDischargeSummary] = useState(false); + const [selectedOptions, setSelectedOptions] = useState([]); + + const handleCheckboxChange = (e: FieldChangeEvent) => { + const value = e.name; + setSelectedOptions((prevSelectedOptions) => { + if (prevSelectedOptions.includes(value)) { + return prevSelectedOptions.filter((option) => option !== value); + } + return [...prevSelectedOptions, value]; + }); + }; + + const popup = (url: string) => { + window.open(url, "_blank"); + setDownloading(false); + props.onClose(); + }; + + const waitForDischargeSummary = async () => { + setGenerating(true); + Success({ msg: t("generating_discharge_summary") + "..." }); + + let section_data = {} + if (selectedOptions.length > 0) { + section_data = selectedOptions.join("\n"); + } + + setTimeout(async () => { + setGenerating(false); + + const res = await dispatch( + generateDischargeSummary({section_data: section_data, is_ai: true},{ external_id: props.consultation.id }) + ); + + if (res.status === 200) { + popup(res.data.read_signed_url); + return; + } + + Error({ + msg: t("discharge_summary_not_ready") + " " + t("try_again_later"), + }); + setDownloading(false); + }, 7000); + }; + + const handleRegenDischargeSummary = async () => { + setDownloading(true); + const res = await dispatch( + generateDischargeSummary({},{ external_id: props.consultation.id }) + ); + if (res.status === 406) { + Error({ + msg: + res.data?.message || + t("discharge_summary_not_ready") + " " + t("try_again_later"), + }); + setDownloading(false); + return; + } + setRegenDischargeSummary(false); + waitForDischargeSummary(); + }; + + const downloadDischargeSummary = async () => { + let section_data = {} + if (selectedOptions.length > 0) { + for(let selectedOption of selectedOptions) { + let [category, label, value] = selectedOption.split('-') + let value_split = value.split('.') + if (value_split[0] in section_data) { + section_data[value_split[0]] += `${label}: {{ ${value}|safe }}\n` + } else { + section_data[value_split[0]] = `${category}\n\n${label}: {{ ${value}|safe }}\n` + } + } + } + + // returns summary or 202 if new create task started + const res = await dispatch( + generateDischargeSummary({section_data: section_data, is_ai: true},{ external_id: props.consultation.id }) + ); + + if (res.status === 202) { + // wait for the automatic task to finish + //waitForDischargeSummary(); + return; + } + + if (res.status === 200) { + popup(res.data.read_signed_url); + return; + } + + Error({ + msg: t("discharge_summary_not_ready") + " " + t("try_again_later"), + }); + setDownloading(false); + }; + + const handleDownload = async () => { + setDownloading(true); + downloadDischargeSummary(); + }; + + 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 === 202) { + Success({ msg: t("email_success") }); + props.onClose(); + } + + setEmailing(false); + }; + + const optionsByCategory = options.reduce((acc: any, option) => { + // if (option.condition && !eval(option.condition)) { + // return acc; + // } + + if (!acc[option.category]) { + acc[option.category] = []; + } + acc[option.category].push(option); + return acc; + }, {}); + + const handleCategoryCheckboxChange = (category: string, checked: boolean) => { + const optionsInCategory = optionsByCategory[category]; + const optionValuesInCategory = optionsInCategory.map( + (option) => `${option.category}-${option.label}-${option.value}` + ); + const newSelectedOptions = checked + ? [...selectedOptions, ...optionValuesInCategory] + : selectedOptions.filter( + (option) => !optionValuesInCategory.includes(option) + ); + setSelectedOptions(newSelectedOptions); + }; + + return ( + +
+
+ + Select the fields you want to include in the discharge summary + + + + {`${t("disclaimer")}: ${t("generated_summary_caution")}`} + +
+ +
+ {Object.entries(optionsByCategory).map(([category, options]) => ( + option.category === category) + .every((option) => + selectedOptions.includes( + `${option.category}-${option.label}-${option.value}` + ) + )} + onChange={(e) => + handleCategoryCheckboxChange(category, e.target.checked) + } + /> + } + title={category} + className="mb-2 rounded-lg border border-gray-300 p-4" + > +
+ {options.map((option) => ( +
+ +
+ ))} +
+
+ ))} +
+ + setAdditionalDetails(e.value)} + /> + + setEmail(e.value)} + error={emailError} + /> + {!props.consultation.discharge_date && ( + setRegenDischargeSummary(e.value)} + /> + )} +
+ + + {downloading ? ( + + ) : ( + + )} + + {generating + ? t("generating") + "..." + : downloading + ? t("downloading") + "..." + : t("download")} + + + {/* + {emailing ? ( + + ) : ( + + )} + {t("send_email")} + */} +
+
+
+ ); +} diff --git a/src/Components/Facility/DischargeSummaryModal.tsx b/src/Components/Facility/DischargeSummaryModal.tsx index 36e2de8a591..d6fbcf626d6 100644 --- a/src/Components/Facility/DischargeSummaryModal.tsx +++ b/src/Components/Facility/DischargeSummaryModal.tsx @@ -49,7 +49,7 @@ export default function DischargeSummaryModal(props: Props) { setGenerating(false); const res = await dispatch( - previewDischargeSummary({ external_id: props.consultation.id }) + generateDischargeSummary({},{ external_id: props.consultation.id}) ); if (res.status === 200) { @@ -67,7 +67,7 @@ export default function DischargeSummaryModal(props: Props) { const handleRegenDischargeSummary = async () => { setDownloading(true); const res = await dispatch( - generateDischargeSummary({ external_id: props.consultation.id }) + generateDischargeSummary({},{ external_id: props.consultation.id }) ); if (res.status === 406) { Error({ @@ -85,7 +85,7 @@ export default function DischargeSummaryModal(props: Props) { const downloadDischargeSummary = async () => { // returns summary or 202 if new create task started const res = await dispatch( - previewDischargeSummary({ external_id: props.consultation.id }) + generateDischargeSummary({ external_id: props.consultation.id, section_data: "", is_ai: false }) ); if (res.status === 202) { @@ -115,7 +115,6 @@ export default function DischargeSummaryModal(props: Props) { downloadDischargeSummary(); }; - const handleEmail = async () => { setEmailing(true); diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 26e006f4cea..fe9bfb7110b 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -592,8 +592,11 @@ export const deleteLastInventoryLog = (params: object) => { return fireRequest("deleteLastInventoryLog", [], {}, params); }; -export const generateDischargeSummary = (pathParams: object) => { - return fireRequest("dischargeSummaryGenerate", [], {}, pathParams); +export const generateDischargeSummary = ( + params: object, + pathParams: object +) => { + return fireRequest("dischargeSummaryGenerate", [], params, pathParams); }; export const previewDischargeSummary = (pathParams: object) => { return fireRequest(