- {investigations.map((investigation, i) => {
+ {investigations?.map((investigation, i) => {
const setFrequency = (frequency: string) => {
setItem(
{
diff --git a/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx b/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx
index 0391748c929..b6495143f5d 100644
--- a/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx
+++ b/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx
@@ -1,4 +1,4 @@
-import { useState } from "react";
+import { useEffect, useState } from "react";
import useSlug from "../../../Common/hooks/useSlug";
import {
ConsultationDiagnosis,
@@ -83,6 +83,11 @@ interface EditDiagnosesProps {
export const EditDiagnosesBuilder = (props: EditDiagnosesProps) => {
const consultation = useSlug("consultation");
const [diagnoses, setDiagnoses] = useState(props.value);
+
+ useEffect(() => {
+ setDiagnoses(props.value);
+ }, [props.value]);
+
return (
diff --git a/src/Components/Medicine/ManagePrescriptions.tsx b/src/Components/Medicine/ManagePrescriptions.tsx
index a5ae50813a2..8409e721779 100644
--- a/src/Components/Medicine/ManagePrescriptions.tsx
+++ b/src/Components/Medicine/ManagePrescriptions.tsx
@@ -10,7 +10,15 @@ export default function ManagePrescriptions() {
const { goBack } = useAppHistory();
return (
-
+
+
+ Print
+
+ }
+ >
{
options={
!readonly &&
!!data?.results && (
-
+ <>
+
+
+
+
+ {t("edit_prescriptions")}
+
+ {t("edit")}
+
+ refetch()}
+ />
+
-
-
- {t("edit_prescriptions")}
-
- {t("edit")}
+
+ Print
- refetch()}
- />
-
+ >
)
}
/>
diff --git a/src/Components/Medicine/PrintPreview.tsx b/src/Components/Medicine/PrintPreview.tsx
new file mode 100644
index 00000000000..09bad44d630
--- /dev/null
+++ b/src/Components/Medicine/PrintPreview.tsx
@@ -0,0 +1,271 @@
+import { useTranslation } from "react-i18next";
+import PrintPreview from "../../CAREUI/misc/PrintPreview";
+import { useSlugs } from "../../Common/hooks/useSlug";
+import routes from "../../Redux/api";
+import useQuery from "../../Utils/request/useQuery";
+import {
+ classNames,
+ formatDate,
+ formatDateTime,
+ formatName,
+ patientAgeInYears,
+} from "../../Utils/utils";
+import MedicineRoutes from "./routes";
+import { Prescription } from "./models";
+import useConfig from "../../Common/hooks/useConfig";
+import { ReactNode } from "react";
+
+export default function PrescriptionsPrintPreview() {
+ const { main_logo } = useConfig();
+ const { t } = useTranslation();
+ const [patientId, consultationId] = useSlugs("patient", "consultation");
+
+ const patientQuery = useQuery(routes.getPatient, {
+ pathParams: { id: patientId },
+ });
+
+ const encounterQuery = useQuery(routes.getConsultation, {
+ pathParams: { id: consultationId },
+ });
+
+ const prescriptionsQuery = useQuery(MedicineRoutes.listPrescriptions, {
+ pathParams: { consultation: consultationId },
+ query: { discontinued: false, limit: 100 },
+ });
+
+ const patient = patientQuery.data;
+ const encounter = encounterQuery.data;
+
+ const items = prescriptionsQuery.data?.results;
+ const normalPrescriptions = items?.filter((p) => p.dosage_type !== "PRN");
+ const prnPrescriptions = items?.filter((p) => p.dosage_type === "PRN");
+
+ return (
+
+
+
{encounter?.facility_name}
+
+
+
+
+ {patient && (
+ <>
+ {patient.name} -{" "}
+ {t(`GENDER__${patient.gender}`)},{" "}
+ {patientAgeInYears(patient).toString()}yrs
+ >
+ )}
+
+
+ {encounter?.patient_no}
+
+
+
+ {formatDate(encounter?.encounter_date)}
+
+
+ {encounter?.current_bed?.bed_object.location_object?.name}
+ {" - "}
+ {encounter?.current_bed?.bed_object.name}
+
+
+
+ {patient?.allergies ?? "None"}
+
+
+
+
+
+
+
+
+ Sign of the Consulting Doctor
+
+
+ {encounter?.treating_physician_object &&
+ formatName(encounter?.treating_physician_object)}
+
+
+ Generated on: {formatDateTime(new Date())}
+
+
+ This is a computer generated prescription. It shall be issued to the
+ patient only after the concerned doctor has verified the content and
+ authorized the same by affixing signature.
+
+
+
+ );
+}
+
+const PatientDetail = ({
+ name,
+ children,
+ className,
+}: {
+ name: string;
+ children?: ReactNode;
+ className?: string;
+}) => {
+ return (
+
+
{name}:
+ {children != null ? (
+
{children}
+ ) : (
+
+ )}
+
+ );
+};
+
+const PrescriptionsTable = ({
+ items,
+ prn,
+}: {
+ items?: Prescription[];
+ prn?: boolean;
+}) => {
+ if (!items) {
+ return (
+
+ );
+ }
+
+ if (!items.length) {
+ return;
+ }
+
+ return (
+
+
+ {prn && "PRN"} Prescriptions
+
+
+
+ Medicine |
+ Dosage |
+ Directions |
+ {/* {prn ? "Indicator" : "Freq."} | */}
+ Notes / Instructions |
+
+
+
+ {items.map((item) => (
+
+ ))}
+
+
+ );
+};
+
+const PrescriptionEntry = ({ obj }: { obj: Prescription }) => {
+ const { t } = useTranslation();
+ const medicine = obj.medicine_object;
+
+ return (
+
+
+
+
+ {medicine?.name ?? obj.medicine_old}
+ {" "}
+
+ {medicine?.type === "brand" && (
+
+
+ Generic:{" "}
+
+ {medicine.generic ?? "--"}
+
+
+
+ Brand:{" "}
+
+ {medicine.company ?? "--"}
+
+
+
+ )}
+ |
+
+ {obj.dosage_type === "TITRATED" && Titrated }
+
+ {obj.base_dosage}{" "}
+ {obj.target_dosage != null && `→ ${obj.target_dosage}`}{" "}
+
+ {obj.max_dosage && (
+
+ Max. {obj.max_dosage} in
+ 24hrs
+
+ )}
+ {obj.min_hours_between_doses && (
+
+ Min.{" "}
+
+ {obj.min_hours_between_doses}hrs
+ {" "}
+ b/w doses
+
+ )}
+ |
+
+ {obj.route && (
+
+ Route:
+
+ {t(`PRESCRIPTION_ROUTE_${obj.route}`)}
+
+
+ )}
+ {obj.frequency && (
+
+ Freq:
+
+ {t(`PRESCRIPTION_FREQUENCY_${obj.frequency}`)}
+
+
+ )}
+ {obj.days && (
+
+ Days:
+ {obj.days} day(s)
+
+ )}
+ {obj.indicator && (
+
+ Indicator:
+ {obj.indicator}
+
+ )}
+ |
+
+ {obj.notes}
+ {obj.instruction_on_titration && (
+
+ Titration instructions:{" "}
+ {obj.instruction_on_titration}
+
+ )}
+ |
+
+ );
+};
diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx
index ce64b266ce4..4ec6b879b23 100644
--- a/src/Components/Patient/DailyRounds.tsx
+++ b/src/Components/Patient/DailyRounds.tsx
@@ -29,7 +29,7 @@ import RadioFormField from "../Form/FormFields/RadioFormField";
import request from "../../Utils/request/request";
import routes from "../../Redux/api";
import { Scribe } from "../Scribe/Scribe";
-import { DAILY_ROUND_FORM_SCRIBE_DATA } from "../Scribe/formDetails";
+import { SCRIBE_FORMS } from "../Scribe/formDetails";
import { DailyRoundsModel } from "./models";
import InvestigationBuilder from "../Common/prescription-builder/InvestigationBuilder";
import { FieldErrorText } from "../Form/FormFields/FormField";
@@ -45,6 +45,9 @@ import { EncounterSymptomsBuilder } from "../Symptoms/SymptomsBuilder";
import { FieldLabel } from "../Form/FormFields/FormField";
import useAuthUser from "../../Common/hooks/useAuthUser";
import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField";
+import SymptomsApi from "../Symptoms/api";
+import DiagnosesRoutes from "../Diagnosis/routes";
+import MedicineRoutes from "../Medicine/routes";
import { scrollTo } from "../../Utils/utils";
const Loading = lazy(() => import("../Common/Loading"));
@@ -54,6 +57,8 @@ export const DailyRounds = (props: any) => {
const authUser = useAuthUser();
const { goBack } = useAppHistory();
const { facilityId, patientId, consultationId, id } = props;
+ const [symptomsSeed, setSymptomsSeed] = useState
(1);
+ const [prescriptionSeed, setPrescriptionSeed] = useState(1);
const initForm: any = {
physical_examination_info: "",
@@ -478,11 +483,129 @@ export const DailyRounds = (props: any) => {
>
{
+ form={SCRIBE_FORMS.daily_round}
+ onFormUpdate={async (fields) => {
+ // Symptoms
+ let rounds_type = fields.rounds_type || state.form.rounds_type;
+ if (fields.additional_symptoms) {
+ for (const symptom of fields.additional_symptoms) {
+ const { res } = await request(SymptomsApi.add, {
+ pathParams: { consultationId },
+ body: {
+ ...symptom,
+ },
+ });
+ if (res?.ok) setSymptomsSeed((s) => s + 1);
+ }
+ }
+
+ // ICD11 Diagnosis
+ if (fields.icd11_diagnosis) {
+ for (const diagnosis of fields.icd11_diagnosis) {
+ // Fetch available diagnoses
+
+ const { res: icdRes, data: icdData } = await request(
+ routes.listICD11Diagnosis,
+ {
+ query: { query: diagnosis.diagnosis },
+ },
+ );
+
+ if (!icdRes?.ok) {
+ error({
+ text: "Failed to fetch ICD11 Diagnosis",
+ });
+ continue;
+ }
+
+ const availableDiagnosis = icdData?.[0]?.id;
+
+ if (!availableDiagnosis) {
+ error({
+ text: "Could not find the requested diagnosis. Please enter manually.",
+ });
+ continue;
+ }
+
+ const { res, data } = await request(
+ DiagnosesRoutes.createConsultationDiagnosis,
+ {
+ pathParams: { consultation: consultationId },
+ body: {
+ ...diagnosis,
+ diagnosis: availableDiagnosis,
+ },
+ },
+ );
+
+ if (res?.ok && data)
+ setDiagnoses((diagnoses) => [...(diagnoses || []), data]);
+ }
+ }
+
+ // Prescriptions
+ if (fields.prescriptions || fields.prn_prescriptions) {
+ const combined_prescriptions = [
+ ...(fields.prescriptions || []),
+ ...(fields.prn_prescriptions || []),
+ ];
+ for (const prescription of combined_prescriptions) {
+ // fetch medicine
+ const { res: medicineRes, data: medicineData } = await request(
+ routes.listMedibaseMedicines,
+ {
+ query: { query: prescription.medicine },
+ },
+ );
+
+ if (!medicineRes?.ok) {
+ error({
+ text: "Failed to fetch medicine",
+ });
+ continue;
+ }
+
+ const availableMedicine = medicineData?.[0]?.id;
+
+ if (!availableMedicine) {
+ error({
+ text: "Could not find the requested medicine. Please enter manually.",
+ });
+ continue;
+ }
+
+ const { res } = await request(
+ MedicineRoutes.createPrescription,
+ {
+ pathParams: { consultation: consultationId },
+ body: {
+ ...prescription,
+ medicine: availableMedicine,
+ },
+ },
+ );
+
+ if (res?.ok) setPrescriptionSeed((s) => s + 1);
+ }
+ }
+
+ if (
+ Object.keys(fields).some((f) =>
+ [
+ "investigations",
+ "icd11_diagnosis",
+ "additional_symptoms",
+ "prescriptions",
+ "prn_prescriptions",
+ ].includes(f),
+ )
+ ) {
+ rounds_type = "DOCTORS_LOG";
+ }
+
dispatch({
type: "set_form",
- form: { ...state.form, ...fields },
+ form: { ...state.form, ...fields, rounds_type },
});
fields.action !== undefined && setPreviousAction(fields.action);
fields.review_interval !== undefined &&
@@ -536,6 +659,7 @@ export const DailyRounds = (props: any) => {
Symptoms
{
handleFormFieldChange({
name: "symptoms_dirty",
@@ -763,6 +887,7 @@ export const DailyRounds = (props: any) => {
discontinued={
showDiscontinuedPrescriptions ? undefined : false
}
+ key={prescriptionSeed}
actions={["discontinue"]}
/>
@@ -787,6 +912,7 @@ export const DailyRounds = (props: any) => {
showDiscontinuedPrescriptions ? undefined : false
}
actions={["discontinue"]}
+ key={prescriptionSeed}
/>
diff --git a/src/Components/Scribe/Scribe.tsx b/src/Components/Scribe/Scribe.tsx
index a59fb1039b3..f1298bc33c5 100644
--- a/src/Components/Scribe/Scribe.tsx
+++ b/src/Components/Scribe/Scribe.tsx
@@ -21,8 +21,15 @@ export interface Field {
description: string;
type: string;
example: string;
- default: string;
+ default: any;
options?: readonly FieldOption[];
+ validator: (value: any) => boolean;
+}
+
+export interface ScribeForm {
+ id: string;
+ name: string;
+ fields: () => Promise | Field[];
}
export type ScribeModel = {
@@ -45,7 +52,8 @@ export type ScribeModel = {
};
interface ScribeProps {
- fields: Field[];
+ form: ScribeForm;
+ existingData?: { [key: string]: any };
onFormUpdate: (fields: any) => void;
}
@@ -54,7 +62,7 @@ const SCRIBE_FILE_TYPES = {
SCRIBE: 1,
};
-export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
+export const Scribe: React.FC = ({ form, onFormUpdate }) => {
const { enable_scribe } = useConfig();
const [open, setOpen] = useState(false);
const [_progress, setProgress] = useState(0);
@@ -71,6 +79,21 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
const [updatedTranscript, setUpdatedTranscript] = useState("");
const [scribeID, setScribeID] = useState("");
const stageRef = useRef(stage);
+ const [fields, setFields] = useState([]);
+
+ useEffect(() => {
+ const loadFields = async () => {
+ const fields = await form.fields();
+ setFields(
+ fields.map((f) => ({
+ ...f,
+ validate: undefined,
+ default: JSON.stringify(f.default),
+ })),
+ );
+ };
+ loadFields();
+ }, [form]);
useEffect(() => {
if (stageRef.current === "cancelled") {
@@ -312,8 +335,20 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
setProgress(100);
const parsedFormData = JSON.parse(updatedFieldsResponse ?? "{}");
if (stageRef.current === "cancelled") return;
- setFormFields(parsedFormData);
- onFormUpdate(parsedFormData);
+
+ // run type validations
+ const validated = Object.entries(parsedFormData)
+ .filter(([k, v]) => {
+ const f = fields.find((f) => f.id === k);
+ if (!f) return false;
+ if (v === f.default) return false;
+ //if (f.validator) return f.validator(f.type === "number" ? Number(v) : v);
+ return true;
+ })
+ .map(([k, v]) => ({ [k]: v }))
+ .reduce((acc, curr) => ({ ...acc, ...curr }), {});
+ setFormFields(validated as any);
+ onFormUpdate(validated);
setStage("final-review");
} catch (error) {
setErrors(["Error retrieving form data"]);
@@ -373,35 +408,76 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
stageRef.current = "cancelled";
};
- const processFormField = (
+ function processFormField(
fieldDetails: Field | undefined,
- formFields: { [key: string]: string | string[] | number },
+ formFields: { [key: string]: any },
field: string,
- ) => {
- if (fieldDetails?.options) {
- // Check if the form field is an array (multiple selections allowed)
- if (Array.isArray(formFields[field])) {
- // Map each selected option ID to its corresponding text
- return (formFields[field] as string[])
- .map((option) => {
- const optionDetails = fieldDetails.options?.find(
- (o) => o.id === option,
- );
- return optionDetails?.text ?? option; // Use option text if found, otherwise fallback to option ID
- })
- .join(", ");
- } else {
- // Single selection scenario, find the option that matches the field value
- return (
- fieldDetails.options?.find((o) => o.id === formFields[field])?.text ??
- JSON.stringify(formFields[field])
- );
+ ): React.ReactNode {
+ const value = formFields[field];
+ if (!fieldDetails || !value) return value;
+
+ const { options } = fieldDetails;
+
+ const getHumanizedKey = (key: string): string => {
+ return key
+ .split("_")
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" ");
+ };
+
+ const getOptionText = (value: string | number): string => {
+ if (!options) return value.toString();
+ const option = options.find((opt) => opt.id === value);
+ return option ? option.text : value.toString();
+ };
+
+ const renderPrimitive = (value: any): any => {
+ return options ? getOptionText(value) : value;
+ };
+
+ const renderArray = (values: any[]): React.ReactNode => {
+ return values.map((value) => renderPrimitive(value)).join(", ");
+ };
+
+ const renderObject = (obj: { [key: string]: any }): React.ReactNode => {
+ return (
+
+ {Object.keys(obj).map((key, keyIndex) => (
+
+ {getHumanizedKey(key)}: {renderPrimitive(obj[key])}
+
+ ))}
+
+ );
+ };
+
+ const renderObjectArray = (objects: any[]): React.ReactNode => {
+ return (
+
+ {objects.map((obj, objIndex) => (
+
{renderObject(obj)}
+ ))}
+
+ );
+ };
+
+ if (Array.isArray(value)) {
+ if (
+ value.length > 0 &&
+ typeof value[0] === "object" &&
+ !Array.isArray(value[0])
+ ) {
+ return renderObjectArray(value);
}
- } else {
- // If no options are available, return the field value in JSON string format
- return JSON.stringify(formFields[field]);
+ return renderArray(value);
}
- };
+
+ if (typeof value === "object") {
+ return renderObject(value);
+ }
+
+ return renderPrimitive(value);
+ }
const renderContentBasedOnStage = () => {
switch (stage) {
@@ -599,13 +675,13 @@ export const Scribe: React.FC = ({ fields, onFormUpdate }) => {
{fieldDetails?.friendlyName}
-
+
{processFormField(
fieldDetails,
formFields,
field,
)}
-
+
);
})}
diff --git a/src/Components/Scribe/formDetails.ts b/src/Components/Scribe/formDetails.ts
index 0a1381913cb..7d9af28cecc 100644
--- a/src/Components/Scribe/formDetails.ts
+++ b/src/Components/Scribe/formDetails.ts
@@ -6,19 +6,27 @@ import {
RHYTHM_CHOICES,
TELEMEDICINE_ACTIONS,
} from "../../Common/constants";
+import { loadInvestigations } from "../Common/prescription-builder/InvestigationBuilder";
import { SYMPTOM_CHOICES } from "../Symptoms/types";
-import { Field } from "./Scribe";
+import { Field, ScribeForm } from "./Scribe";
-export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
+const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
{
friendlyName: "Additional Symptoms",
id: "additional_symptoms",
- type: "number[]",
- example: "[1,2,3]",
- default: "[]",
- description:
- "A numeric array of option IDs to store symptoms of the patient.",
+ type: "{symptom: number, other_symptom?: string, onset_date: string, cure_date?: string}[]",
+ example:
+ "[{symptom: 1, onset_date: '2024-12-03'}, {symptom: 2, onset_date: '2024-12-03', cure_date: '2024-12-05'}, {symptom: 9, other_symptom: 'Other symptom', onset_date: '2024-12-03'}]",
+ default: [],
+ description: `An array of objects to store the patient's symptoms along with their date of onset and date of cure (if any). The symptom field should be an integer corresponding to the symptom's ID. The onset_date and cure_date fields should be date strings (e.g., '2022-01-01'). If no onset_date has been specified, use todays date which is '${new Date().toISOString().slice(0, 10)}'. If the symptom is ongoing, the cure_date field should not be included. If the user has 'Other Symptom', only then the other_symptom field should be included with a string value describing the symptom.`,
options: SYMPTOM_CHOICES,
+ validator: (value) => {
+ if (!Array.isArray(value)) return false;
+ value.forEach((s) => {
+ if (!s.symptom || !s.onset_date) return false;
+ });
+ return true;
+ },
},
{
friendlyName: "Other Symptoms",
@@ -27,6 +35,9 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
example: "",
default: "",
description: "Just leave it blank",
+ validator: () => {
+ return true;
+ },
},
{
friendlyName: "Physical Examination Info",
@@ -37,6 +48,9 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
default: "",
description:
"This field is designated for storing detailed findings from the physical examination of the patient. It should include all observable physical attributes, conditions, or symptoms noted during the examination. When processing a doctor's transcript, identify and extract descriptions that pertain directly to the patient's physical state, such as visible conditions, physical symptoms, or any abnormalities noted by touch, sight, or measurement. This can include, but is not limited to, descriptions of skin conditions, swellings, lacerations, posture, mobility issues, and any other physically observable traits.",
+ validator: (value) => {
+ return typeof value === "string";
+ },
},
{
friendlyName: "Other Details",
@@ -47,6 +61,9 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
"Patient reports trouble sleeping and a decreased appetite. Additionally, the patient is allergic to penicillin and has a history of asthma.",
description:
"This field is for capturing any supplementary details about the patient that are mentioned in the doctor's transcript but do not directly pertain to the physical examination findings. This includes, but is not limited to, behavioral observations, medical history, patient complaints, lifestyle factors, allergies, or any other non-physical observations that are relevant to the patient's overall health and well-being. When processing a transcript, extract information that describes the patient's health, habits, or conditions in a broader sense than what is observed through physical examination alone.",
+ validator: (value) => {
+ return typeof value === "string";
+ },
},
{
friendlyName: "Patient Category",
@@ -59,14 +76,20 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
id: category.id,
text: category.text,
})),
+ validator: (value) => {
+ return typeof value === "string";
+ },
},
{
friendlyName: "Actions",
id: "actions",
type: "null",
example: "null",
- default: "null",
+ default: null,
description: "Leave blank.",
+ validator: (value) => {
+ return value === null;
+ },
},
{
friendlyName: "Action",
@@ -79,16 +102,18 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
id: action.text,
text: action.desc,
})),
+ validator: (value) => typeof value === "string",
},
{
friendlyName: "Review Interval",
id: "review_interval",
type: "number",
- default: "0",
+ default: 0,
example: "15",
description:
"An integer to represent the interval at which the patient's condition is reviewed.",
options: REVIEW_AT_CHOICES,
+ validator: (value) => typeof value === "number",
},
{
friendlyName: "Admitted To",
@@ -98,63 +123,75 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
example: "General Ward",
description:
"A string to store the department or ward where the patient is admitted.",
+ validator: (value) => typeof value === "string",
},
{
friendlyName: "bp",
id: "bp",
- default: "{ systolic: undefined, diastolic: undefined, mean: undefined }",
- type: "{ systolic: number, diastolic: number, mean: number }",
- example: "{ systolic: 120, diastolic: 80, mean: 100 }",
+ default: { systolic: null, diastolic: null, mean: null },
+ type: "{ systolic?: number, diastolic?: number }",
+ example: "{ systolic: 120 }",
description:
- "An object to store the blood pressure of the patient. It contains two integers, systolic and diastolic. Output mean is calculated from these two.",
+ "An object to store the blood pressure of the patient. It may contain two integers, systolic and diastolic.",
+ validator: (value) => {
+ if (typeof value !== "object") return false;
+ if (value.systolic && typeof value.systolic !== "number") return false;
+ if (value.diastolic && typeof value.diastolic !== "number") return false;
+ return true;
+ },
},
{
friendlyName: "Pulse",
id: "pulse",
type: "number",
- default: "null",
+ default: null,
example: "72",
description:
"An integer to store the pulse rate of the patient. It can be null if the pulse rate is not taken.",
+ validator: (value) => typeof value === "number",
},
{
friendlyName: "Respiratory Rate",
id: "resp",
type: "number",
- default: "null",
+ default: null,
example: "16",
description:
"An integer to store the respiratory rate of the patient. It can be null if the respiratory rate is not taken.",
+ validator: (value) => typeof value === "number",
},
{
friendlyName: "Temperature",
id: "temperature",
type: "number",
- default: "null",
+ default: null,
example: "98.6",
description:
"A float to store the temperature of the patient. It can be null if the temperature is not taken.",
+ validator: (value) => typeof value === "number",
},
{
friendlyName: "SPO2",
id: "ventilator_spo2",
type: "number",
- default: "null",
+ default: null,
example: "98",
description:
"An integer to store the SPO2 level of the patient. It can be null if the SPO2 level is not taken.",
+ validator: (value) => typeof value === "number",
},
{
friendlyName: "Rhythm",
id: "rhythm",
- type: "string",
+ type: "number",
example: "5",
- default: "0",
+ default: 0,
description: "An option to store the rhythm of the patient.",
options: RHYTHM_CHOICES.map((rhythm) => ({
id: rhythm.id,
text: rhythm.desc ?? "",
})),
+ validator: (value) => typeof value === "number",
},
{
friendlyName: "Rhythm Detail",
@@ -164,6 +201,7 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
example: "Just minor irregularities.",
description:
"A string to store the details about the rhythm of the patient.",
+ validator: (value) => typeof value === "string",
},
{
friendlyName: "Level Of Consciousness",
@@ -177,13 +215,168 @@ export const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [
...loc,
text: t(`CONSCIOUSNESS_LEVEL__${loc.value}`),
})),
+ validator: (value) => typeof value === "string",
+ },
+ {
+ friendlyName: "Diagnosis",
+ id: "icd11_diagnosis",
+ type: '{diagnosis: string, verification_status: "unconfirmed" | "provisional" | "differential" | "confirmed", is_principal: boolean}[]',
+ default: [],
+ example:
+ "[{diagnosis: '4A42.0 Paediatric onset systemic sclerosis', verification_status: 'confirmed', is_principal: true}, {diagnosis: 2, verification_status: 'provisional', is_principal: false}]",
+ description:
+ "A list of objects to store the patient's diagnosis along with their verification status and whether it is the principal diagnosis. If not specifically said, set is_principal to false. NOTE: only one principal diagnosis can exist. The diagnosis field should be a string that may contain a corresponding diagnosis ID. The verification_status field should be a string with one of the following values: 'unconfirmed', 'provisional', 'differential', or 'confirmed'.",
+ validator: (value) => {
+ if (!Array.isArray(value)) return false;
+ value.forEach((d) => {
+ if (!d.diagnosis || !d.verification_status) return false;
+ });
+ return true;
+ },
+ },
+ {
+ friendlyName: "Investigations",
+ id: "investigations",
+ type: `{
+ type: string[],
+ repetitive: boolean,
+ time?: string,
+ frequency?: '15 min' | '30 min' | '1 hr' | '6 hrs' | '12 hrs' | '24 hrs' | '48 hrs',
+ notes?: string
+ }[]`,
+ default: [],
+ example: `[
+ {
+ type: ["Haemotology (GROUP)"],
+ repetitive: false,
+ time: "2024-07-31T18:10",
+ notes: "Patient is allergic to penicillin."
+ },
+ {
+ type: ["ECG", "X-Ray"],
+ repetitive: true,
+ frequency: "24 hrs",
+ notes: "Patient is going nuts"
+ }
+ ]`,
+ description:
+ "A list of objects to store the patient's investigations. The type field should be an array of strings corresponding to the names of the investigations provided in the options. The repetitive field should be a boolean value. The time field should be a string and only be filled if repetitive field is false. The frequency field should be a string with one of the following values: '15 min', '30 min', '1 hr', '6 hrs', '12 hrs', '24 hrs', or '48 hrs' and should be only filled if this is a repititive investigation. The time field should be of the example format if present - (2024-07-31T18:10). The notes field should be a string. If the type is not available in options, DO NOT MAKE IT.",
+ validator: (value) => {
+ if (!Array.isArray(value)) return false;
+ value.forEach((i) => {
+ if (!i.type || !i.repetitive) return false;
+ if (i.repetitive && !i.frequency) return false;
+ });
+ return true;
+ },
+ },
+ {
+ friendlyName: "Prescriptions",
+ id: "prescriptions",
+ type: `{
+ base_dosage: number + " " + ("mg" | "g" | "ml" | "drop(s)" | "ampule(s)" | "tsp" | "mcg" | "unit(s)"),
+ days: number,
+ dosage_type: "REGULAR" | "TITRATED",
+ frequency: "STAT" | "OD" | "HS" | "BD" | "TID" | "QID" | "Q4H" | "QOD" | "QWK",
+ medicine: string,
+ notes: string,
+ route: "ORAL" | "IV" | "IM" | "SC" | "INHALATION" | "NASOGASTRIC" | "INTRATHECAL" | "TRANSDERMAL" | "RECTAL" | "SUBLINGUAL",
+ instruction_on_titration: string,
+ target_dosage: number + " " + ("mg" | "g" | "ml" | "drop(s)" | "ampule(s)" | "tsp" | "mcg" | "unit(s)"),
+ }[]`,
+ default: [],
+ example: `[
+ {base_dosage: "5 ampule(s)", days: 7, dosage_type: "REGULAR", frequency: "STAT", medicine: "DOLO", notes: "Give with water", route: "ORAL"},
+ {base_dosage: "7 ml", days: 3, dosage_type: "TITRATED", frequency: "Q4H", medicine: "Albumin", route: "INHALATION", instruction_on_titration: "Example", target_dosage: "40 ml"},
+ ]`,
+ description: `A list of objects to store the patient's prescriptions. The prescription can be regular or titrated. If titrated, the prescription should also include instruction_on_titration, and a target_dosage. NOTE: target_dosage should have the same unit as base_dosage.
+ The frequency should be any of the mentioned ones. They are short for:
+ STAT: Imediately,
+ 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
+ `,
+ validator: (value) => {
+ if (!Array.isArray(value)) return false;
+ return true;
+ },
+ },
+ {
+ friendlyName: "PRN Prescriptions",
+ id: "prn_prescriptions",
+ type: `{
+ base_dosage: number + " " + ("mg" | "g" | "ml" | "drop(s)" | "ampule(s)" | "tsp" | "mcg" | "unit(s)"),
+ dosage_type: "PRN",
+ medicine: string,
+ notes: string,
+ route: "ORAL" | "IV" | "IM" | "SC" | "INHALATION" | "NASOGASTRIC" | "INTRATHECAL" | "TRANSDERMAL" | "RECTAL" | "SUBLINGUAL",
+ indicator: string,
+ min_hours_between_doses: number,
+ max_dosage: number + " " + ("mg" | "g" | "ml" | "drop(s)" | "ampule(s)" | "tsp" | "mcg" | "unit(s)"),
+ }[]`,
+ default: [],
+ example: `[
+ {base_dosage: "3 drop(s)", dosage_type:"PRN", indicator: "If patient gets fever", max_dosage: "5 drops(s)", min_hours_between_doses: 12, route: "IV", medicine: "Glentona", notes: "Example"}
+ ]`,
+ description: "A list of objects to store the patient's PRN prescriptions.",
+ validator: (value) => {
+ if (!Array.isArray(value)) return false;
+ return true;
+ },
},
+ /*{
+ friendlyName: "Round Type",
+ id: "rounds_type",
+ type: "string",
+ default: "NORMAL",
+ example: "TELEMEDICINE",
+ description: "A string to store the type of round.",
+ options: [
+ { id: "NORMAL", text: "Brief Update" },
+ { id: "VENTILATOR", text: "Detailed Update" },
+ { id: "DOCTORS_LOG", text: "Progress Note" },
+ { id: "TELEMEDICINE", text: "Telemedicine" },
+ ],
+ validator: (value) => typeof value === "string",
+ },
+ {
+ friendlyName: "Measured At",
+ id: "taken_at",
+ type: "string",
+ default: "",
+ example: "2024-07-31T18:10",
+ description:
+ "A string to store the date and time at which the round was taken or measured. 'The round was taken yesterday/today' would amount to yesterday/today's date.",
+ validator: (value) => typeof value === "string",
+ },
+*/
];
-export const SCRIBE_FORMS = [
- {
+export const SCRIBE_FORMS: { [key: string]: ScribeForm } = {
+ daily_round: {
id: "daily_round",
name: "Daily Round",
- fields: DAILY_ROUND_FORM_SCRIBE_DATA,
+ fields: async () => {
+ const investigations = await loadInvestigations();
+
+ return DAILY_ROUND_FORM_SCRIBE_DATA.map((field) => {
+ if (field.id === "investigations") {
+ return {
+ ...field,
+ options: investigations.map((investigation, i) => ({
+ id: i,
+ text: investigation,
+ currentData: undefined,
+ })),
+ };
+ }
+ return field;
+ });
+ },
},
-];
+};
diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json
index 0565b0f3adf..1220ad40aa9 100644
--- a/src/Locale/en/Common.json
+++ b/src/Locale/en/Common.json
@@ -178,5 +178,8 @@
"caution": "Caution",
"feed_optimal_experience_for_phones": "For optimal viewing experience, consider rotating your device.",
"feed_optimal_experience_for_apple_phones": "For optimal viewing experience, consider rotating your device. Ensure auto-rotate is enabled in your device settings.",
- "action_irreversible": "This action is irreversible"
+ "action_irreversible": "This action is irreversible",
+ "GENDER__1": "Male",
+ "GENDER__2": "Female",
+ "GENDER__3": "Non-binary"
}
\ No newline at end of file
diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json
index d9b1a242a96..435e23684ed 100644
--- a/src/Locale/en/Consultation.json
+++ b/src/Locale/en/Consultation.json
@@ -40,6 +40,12 @@
"back_to_consultation": "Go back to Consultation",
"no_treating_physicians_available": "This facility does not have any home facility doctors. Please contact your admin.",
"encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation",
+ "encounter_suggestion__A": "Admission",
+ "encounter_suggestion__DC": "Domiciliary Care",
+ "encounter_suggestion__OP": "Out-patient visit",
+ "encounter_suggestion__DD": "Consultation",
+ "encounter_suggestion__HI": "Consultation",
+ "encounter_suggestion__R": "Consultation",
"encounter_date_field_label__A": "Date & Time of Admission to the Facility",
"encounter_date_field_label__DC": "Date & Time of Domiciliary Care commencement",
"encounter_date_field_label__OP": "Date & Time of Out-patient visit",
diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json
index d559ef2fdbf..80726d83fb2 100644
--- a/src/Locale/en/Medicine.json
+++ b/src/Locale/en/Medicine.json
@@ -47,7 +47,7 @@
"PRESCRIPTION_ROUTE_IM": "IM",
"PRESCRIPTION_ROUTE_SC": "S/C",
"PRESCRIPTION_ROUTE_INHALATION": "Inhalation",
- "PRESCRIPTION_ROUTE_NASOGASTRIC": "Nasogastric/Gastrostomy tube",
+ "PRESCRIPTION_ROUTE_NASOGASTRIC": "Nasogastric / Gastrostomy tube",
"PRESCRIPTION_ROUTE_INTRATHECAL": "intrathecal injection",
"PRESCRIPTION_ROUTE_TRANSDERMAL": "Transdermal",
"PRESCRIPTION_ROUTE_RECTAL": "Rectal",
@@ -61,4 +61,4 @@
"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/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx
index 24fc7c3b312..e9dc37deb8f 100644
--- a/src/Routers/routes/ConsultationRoutes.tsx
+++ b/src/Routers/routes/ConsultationRoutes.tsx
@@ -10,6 +10,7 @@ import TreatmentSummary from "../../Components/Facility/TreatmentSummary";
import ConsultationDoctorNotes from "../../Components/Facility/ConsultationDoctorNotes";
import PatientConsentRecords from "../../Components/Patient/PatientConsentRecords";
import CriticalCareEditor from "../../Components/LogUpdate/CriticalCareEditor";
+import PrescriptionsPrintPreview from "../../Components/Medicine/PrintPreview";
export default {
"/facility/:facilityId/patient/:patientId/consultation": ({
@@ -48,6 +49,8 @@ export default {
),
"/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions":
(path: any) =>
,
+ "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions/print":
+ () =>
,
"/facility/:facilityId/patient/:patientId/consultation/:id/investigation": ({
facilityId,
patientId,