diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 8ad2e72032c..f1aed577cea 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1172,6 +1172,21 @@ export const IN_LANDLINE_AREA_CODES = [ "891", "4822", ]; + +export const CONSENT_TYPE_CHOICES = [ + { id: 1, text: "Consent for admission" }, + { id: 2, text: "Patient Code Status" }, + { id: 3, text: "Consent for procedure" }, + { id: 4, text: "High risk consent" }, + { id: 5, text: "Others" }, +]; + +export const CONSENT_PATIENT_CODE_STATUS_CHOICES = [ + { id: 1, text: "Do Not Hospitalise (DNH)" }, + { id: 2, text: "Do Not Resuscitate (DNR)" }, + { id: 3, text: "Comfort Care Only" }, + { id: 4, text: "Active treatment (Default)" }, +]; export const OCCUPATION_TYPES = [ { id: 1, text: "Student", value: "STUDENT" }, { diff --git a/src/Components/Common/PageTitle.tsx b/src/Components/Common/PageTitle.tsx index 4f29ca892e3..6fb38a36f08 100644 --- a/src/Components/Common/PageTitle.tsx +++ b/src/Components/Common/PageTitle.tsx @@ -26,6 +26,7 @@ export interface PageTitleProps { }; focusOnLoad?: boolean; isInsidePage?: boolean; + changePageMetadata?: boolean; } export default function PageTitle({ @@ -40,6 +41,7 @@ export default function PageTitle({ justifyContents = "justify-start", focusOnLoad = false, isInsidePage = false, + changePageMetadata = true, }: PageTitleProps) { const divRef = useRef(); @@ -56,7 +58,7 @@ export default function PageTitle({ ref={divRef} className={isInsidePage ? "" : `mb-2 pt-4 md:mb-4 ${className}`} > - + {changePageMetadata && }
{!hideBack && ( diff --git a/src/Components/Common/components/Page.tsx b/src/Components/Common/components/Page.tsx index ce5e84deefe..437a4ef21f7 100644 --- a/src/Components/Common/components/Page.tsx +++ b/src/Components/Common/components/Page.tsx @@ -6,6 +6,7 @@ import { SidebarShrinkContext } from "../Sidebar/Sidebar"; interface PageProps extends PageTitleProps { children: React.ReactNode | React.ReactNode[]; options?: React.ReactNode | React.ReactNode[]; + changePageMetadata?: boolean; className?: string; noImplicitPadding?: boolean; ref?: RefObject; @@ -39,6 +40,7 @@ export default function Page(props: PageProps) {
(); const [adding, setAdding] = useState(false); - const hasError = !!props.disallowed.find((d) => d.id === selected?.id); + const hasError = !!props.disallowed.find((d) => d?.id === selected?.id); const { fetchOptions, isLoading, options } = useAsyncOptions("id"); diff --git a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx index e8a64b5310d..b3aca51c615 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationUpdatesTab.tsx @@ -7,7 +7,12 @@ import { BedModel } from "../models"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; import useVitalsAspectRatioConfig from "../../VitalsMonitor/useVitalsAspectRatioConfig"; -import { DISCHARGE_REASONS, SYMPTOM_CHOICES } from "../../../Common/constants"; +import { + CONSENT_PATIENT_CODE_STATUS_CHOICES, + CONSENT_TYPE_CHOICES, + DISCHARGE_REASONS, + SYMPTOM_CHOICES, +} from "../../../Common/constants"; import PrescriptionsTable from "../../Medicine/PrescriptionsTable"; import Chip from "../../../CAREUI/display/Chip"; import { formatAge, formatDate, formatDateTime } from "../../../Utils/utils"; @@ -16,6 +21,7 @@ import DailyRoundsList from "../Consultations/DailyRoundsList"; import EventsList from "./Events/EventsList"; import SwitchTabs from "../../Common/components/SwitchTabs"; import { getVitalsMonitorSocketUrl } from "../../VitalsMonitor/utils"; +import { FileUpload } from "../../Patient/FileUpload"; const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -669,6 +675,46 @@ export const ConsultationUpdatesTab = (props: ConsultationTabProps) => {
+ {( + props.consultationData.consent_records?.filter( + (record) => record.deleted !== true + ) || [] + ).length > 0 && ( + <> +
+

+ Consent Records +

+ {props.consultationData.consent_records + ?.filter((record) => record.deleted !== true) + ?.map((record, i) => ( +
+
+ { + CONSENT_TYPE_CHOICES.find( + (c) => c.id === record.type + )?.text + }{" "} + {record.patient_code_status && + `( ${ + CONSENT_PATIENT_CODE_STATUS_CHOICES.find( + (c) => c.id === record.patient_code_status + )?.text + } )`} +
+ +
+ ))} +
+ + )}
diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index ea49f24871b..4d2d58457e5 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -8,6 +8,8 @@ import { PATIENT_CATEGORIES, REVIEW_AT_CHOICES, TELEMEDICINE_ACTIONS, + CONSENT_TYPE_CHOICES, + CONSENT_PATIENT_CODE_STATUS_CHOICES, } from "../../Common/constants"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; @@ -30,6 +32,7 @@ import { createConsultation, getConsultation, getPatient, + partialUpdateConsultation, updateConsultation, } from "../../Redux/actions"; import { statusType, useAbortableEffect } from "../../Common/utils"; @@ -74,12 +77,21 @@ import { CreateDiagnosesBuilder, EditDiagnosesBuilder, } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.js"; +import { FileUpload } from "../Patient/FileUpload.js"; +import ConfirmDialog from "../Common/ConfirmDialog.js"; const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); type BooleanStrings = "true" | "false"; +export type ConsentRecord = { + id: string; + type: (typeof CONSENT_TYPE_CHOICES)[number]["id"]; + patient_code_status?: (typeof CONSENT_PATIENT_CODE_STATUS_CHOICES)[number]["id"]; + deleted?: boolean; +}; + type FormDetails = { symptoms: number[]; other_symptoms: string; @@ -128,6 +140,7 @@ type FormDetails = { death_confirmed_doctor: string; InvestigationAdvice: InvestigationType[]; procedures: ProcedureType[]; + consent_records: ConsentRecord[]; }; const initForm: FormDetails = { @@ -178,6 +191,7 @@ const initForm: FormDetails = { death_confirmed_doctor: "", InvestigationAdvice: [], procedures: [], + consent_records: [], }; const initError = Object.assign( @@ -228,6 +242,7 @@ type ConsultationFormSection = | "Consultation Details" | "Diagnosis" | "Treatment Plan" + | "Consent Records" | "Bed Status"; type Props = { @@ -261,7 +276,14 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { const [diagnosisVisible, diagnosisRef] = useVisibility(-300); const [treatmentPlanVisible, treatmentPlanRef] = useVisibility(-300); const [bedStatusVisible, bedStatusRef] = useVisibility(-300); + const [consentRecordsVisible, consentRecordsRef] = useVisibility(-300); const [disabledFields, setDisabledFields] = useState([]); + const [collapsedConsentRecords, setCollapsedConsentRecords] = useState< + number[] + >([]); + const [showDeleteConsent, setShowDeleteConsent] = useState( + null + ); const { min_encounter_date } = useConfig(); @@ -281,6 +303,11 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { visible: treatmentPlanVisible, ref: treatmentPlanRef, }, + "Consent Records": { + iconClass: "care-l-file-alt", + visible: consentRecordsVisible, + ref: consentRecordsRef, + }, "Bed Status": { iconClass: "l-bed", visible: bedStatusVisible, @@ -293,6 +320,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { if (consultationDetailsVisible) return "Consultation Details"; if (diagnosisVisible) return "Diagnosis"; if (treatmentPlanVisible) return "Treatment Plan"; + if (consentRecordsVisible) return "Consent Records"; if (bedStatusVisible) return "Bed Status"; return prev; }); @@ -300,6 +328,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { consultationDetailsVisible, diagnosisVisible, treatmentPlanVisible, + consentRecordsVisible, bedStatusVisible, ]); @@ -762,6 +791,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { height: Number(state.form.height), bed: bed && bed instanceof Array ? bed[0]?.id : bed?.id, patient_no: state.form.patient_no || null, + consent_records: state.form.consent_records || [], }; const res = await dispatchAction( @@ -904,6 +934,62 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { }; }; + const handleConsentTypeChange: FieldChangeEventHandler = async ( + event + ) => { + if (!id) return; + const consentRecords = [...state.form.consent_records]; + if ( + consentRecords + .filter((cr) => cr.deleted !== true) + .map((cr) => cr.type) + .includes(event.value) + ) { + return; + } else { + const randomId = "consent-" + new Date().getTime().toString(); + const newRecords = [ + ...consentRecords, + { id: randomId, type: event.value }, + ]; + await dispatchAction( + partialUpdateConsultation(id, { consent_records: newRecords }) + ); + dispatch({ + type: "set_form", + form: { ...state.form, consent_records: newRecords }, + }); + } + }; + + const handleConsentPCSChange: FieldChangeEventHandler = (event) => { + dispatch({ + type: "set_form", + form: { + ...state.form, + consent_records: state.form.consent_records.map((cr) => + cr.type === 2 ? { ...cr, patient_code_status: event.value } : cr + ), + }, + }); + }; + + const handleDeleteConsent = async () => { + const consent_id = showDeleteConsent; + if (!consent_id || !id) return; + const newRecords = state.form.consent_records.map((cr) => + cr.id === consent_id ? { ...cr, deleted: true } : cr + ); + await dispatchAction( + partialUpdateConsultation(id, { consent_records: newRecords }) + ); + dispatch({ + type: "set_form", + form: { ...state.form, consent_records: newRecords }, + }); + setShowDeleteConsent(null); + }; + return (
{
{Object.keys(sections).map((sectionTitle) => { - if (!isUpdate && sectionTitle === "Bed Status") { + if ( + !isUpdate && + ["Bed Status", "Consent Records"].includes(sectionTitle) + ) { return null; } const isCurrent = currentSection === sectionTitle; @@ -1477,7 +1566,114 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { )}
- + {id && ( + <> +
+ {sectionTitle("Consent Records", true)} +
+ setShowDeleteConsent(null)} + onConfirm={handleDeleteConsent} + action="Delete" + variant="danger" + description={ + "Are you sure you want to delete this consent record?" + } + title="Delete Consent" + className="w-auto" + /> + + !state.form.consent_records + .filter((r) => r.deleted !== true) + .map((record) => record.type) + .includes(c.id) + )} + /> +
+ {state.form.consent_records + .filter((record) => record.deleted !== true) + .map((record, index) => ( +
+
+ + +
+
+
+ {record.type === 2 && ( + + )} +
+ +
+
+ ))} +
+ + )}
diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 58386bef3a4..1e590797f83 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -7,6 +7,7 @@ import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; import { NormalPrescription, PRNPrescription } from "../Medicine/models"; import { AssignedToObjectModel, DailyRoundsModel } from "../Patient/models"; import { UserBareMinimum } from "../Users/models"; +import { ConsentRecord } from "./ConsultationForm"; export interface LocalBodyModel { id: number; @@ -164,6 +165,7 @@ export interface ConsultationModel { is_readmission?: boolean; medico_legal_case?: boolean; investigation?: InvestigationType[]; + consent_records?: ConsentRecord[]; } export interface PatientStatsModel { diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index 6e6d289d187..0c8cad60005 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -104,11 +104,15 @@ interface FileUploadProps { patientId?: any; facilityId?: any; consultationId?: any; + consentId?: string; hideBack: boolean; audio?: boolean; unspecified: boolean; sampleId?: string; claimId?: string; + className?: string; + hideUpload?: boolean; + changePageMetadata?: boolean; } interface URLS { @@ -144,12 +148,14 @@ export const FileUpload = (props: FileUploadProps) => { facilityId, consultationId, patientId, + consentId, type, hideBack, audio, unspecified, sampleId, claimId, + changePageMetadata, } = props; const id = patientId; const [isLoading, setIsLoading] = useState(false); @@ -302,6 +308,8 @@ export const FileUpload = (props: FileUploadProps) => { switch (type) { case "PATIENT": return patientId; + case "CONSENT_RECORD": + return consentId; case "CONSULTATION": return consultationId; case "SAMPLE_MANAGEMENT": @@ -559,7 +567,10 @@ export const FileUpload = (props: FileUploadProps) => { const renderFileUpload = (item: FileUploadModel) => { const isPreviewSupported = previewExtensions.includes(item.extension ?? ""); return ( -
+
{!item.is_archived ? ( <> {item.file_category === "AUDIO" ? ( @@ -1132,7 +1143,7 @@ export const FileUpload = (props: FileUploadProps) => { }; return ( -
+
{
- -
- {audio ? ( -
-

Record and Upload Audio File

- { - setAudioName(e.value); - }} - error={audioFileError} - /> - {audiouploadStarted ? ( - - ) : ( -
- {audioBlobExists && ( -
+ {!props.hideUpload && ( + +
+ {audio ? ( +
+

Record and Upload Audio File

+ { + setAudioName(e.value); + }} + error={audioFileError} + /> + {audiouploadStarted ? ( + + ) : ( +
+ {audioBlobExists && ( +
+ { + deleteAudioBlob(); + }} + > + Delete + +
+ )} +
+ + {!audioBlobExists && ( + + + Please allow browser permission before you start + speaking + + )} +
+ {audioBlobExists && ( +
+ { + handleAudioUpload(); + }} + className="w-full" + > + + Save + +
+ )} +
+ )} +
+ ) : null} + {unspecified ? ( +
+
+

Upload New File

+
+ { + setUploadFileName(e.value); + }} + error={uploadFileError} + /> +
+ {uploadStarted ? ( + + ) : ( +
+ + {({ isAuthorized }) => + isAuthorized ? ( + + ) : ( + <> + ) + } + setModalOpenForCamera(true)} + className="w-full" + > + + Open Camera + + { - deleteAudioBlob(); - }} > - Delete + + {t("upload")}
)} -
- - {!audioBlobExists && ( - - - Please allow browser permission before you start - speaking - - )} -
- {audioBlobExists && ( -
- + {file?.name} +
)}
- )} -
- ) : null} - {unspecified ? ( -
-
-

Upload New File

-
- { - setUploadFileName(e.value); - }} - error={uploadFileError} - /> -
- {uploadStarted ? ( - - ) : ( -
- - {({ isAuthorized }) => - isAuthorized ? ( - - ) : ( - <> - ) - } - - setModalOpenForCamera(true)} - className="w-full" - > - - Open Camera - - - - {t("upload")} - -
- )} - {file && ( -
- {file?.name} - -
- )}
-
- ) : null} -
-
- - + ) : null} +
+ + )} + { export const updateConsultation = (id: string, params: object) => { return fireRequest("updateConsultation", [], params, { id: id }); }; +export const partialUpdateConsultation = (id: string, params: object) => { + return fireRequest("partialUpdateConsultation", [], params, { id: id }); +}; export const generateDischargeSummary = (pathParams: object) => { return fireRequest("dischargeSummaryGenerate", [], {}, pathParams);