diff --git a/public/locale/en.json b/public/locale/en.json index b6ccb84de08..ce1d79aa3dd 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -133,9 +133,13 @@ "ORAL_ISSUE__NO_ISSUE": "No issues", "ORAL_ISSUE__ODYNOPHAGIA": "Odynophagia", "OXYGEN_MODALITY__HIGH_FLOW_NASAL_CANNULA": "High Flow Nasal Cannula", + "OXYGEN_MODALITY__HIGH_FLOW_NASAL_CANNULA_short": "HFNC", "OXYGEN_MODALITY__NASAL_PRONGS": "Nasal Prongs", + "OXYGEN_MODALITY__NASAL_PRONGS_short": "NP", "OXYGEN_MODALITY__NON_REBREATHING_MASK": "Non Rebreathing Mask", + "OXYGEN_MODALITY__NON_REBREATHING_MASK_short": "NRM", "OXYGEN_MODALITY__SIMPLE_FACE_MASK": "Simple Face Mask", + "OXYGEN_MODALITY__SIMPLE_FACE_MASK_short": "SFM", "PRESCRIPTION_FREQUENCY_BD": "Twice daily", "PRESCRIPTION_FREQUENCY_HS": "Night only", "PRESCRIPTION_FREQUENCY_OD": "Once daily", @@ -206,12 +210,19 @@ "URINATION_FREQUENCY__NORMAL": "Normal", "VENTILATOR": "Detailed Update", "VENTILATOR_MODE__CMV": "Control Mechanical Ventilation (CMV)", + "VENTILATOR_MODE__CMV_short": "CMV", "VENTILATOR_MODE__PCV": "Pressure Control Ventilation (PCV)", + "VENTILATOR_MODE__PCV_short": "PCV", "VENTILATOR_MODE__PC_SIMV": "Pressure Controlled SIMV (PC-SIMV)", + "VENTILATOR_MODE__PC_SIMV_short": "PC-SIMV", "VENTILATOR_MODE__PSV": "C-PAP / Pressure Support Ventilation (PSV)", + "VENTILATOR_MODE__PSV_short": "C-PAP/PSV", "VENTILATOR_MODE__SIMV": "Synchronised Intermittent Mandatory Ventilation (SIMV)", + "VENTILATOR_MODE__SIMV_short": "SIMV", "VENTILATOR_MODE__VCV": "Volume Control Ventilation (VCV)", + "VENTILATOR_MODE__VCV_short": "VCV", "VENTILATOR_MODE__VC_SIMV": "Volume Controlled SIMV (VC-SIMV)", + "VENTILATOR_MODE__VC_SIMV_short": "VC-SIMV", "View Facility": "View Facility", "aadhaar_number": "Aadhaar Number", "aadhaar_number_will_not_be_stored": "Aadhaar number will not be stored by CARE", @@ -303,6 +314,7 @@ "are_you_still_watching": "Are you still watching?", "are_you_sure_want_to_delete": "Are you sure you want to delete {{name}}?", "are_you_sure_want_to_delete_this_record": "Are you sure want to delete this record?", + "ari": "ARI - Acute Respiratory illness", "asset_class": "Asset Class", "asset_location": "Asset Location", "asset_name": "Asset Name", @@ -313,6 +325,7 @@ "assigned_facility": "Facility assigned", "assigned_to": "Assigned to", "async_operation_warning": "This operation may take some time. Please check back later.", + "atypical_presentation_details": "Atypical presentation details", "audio__allow_permission": "Please allow microphone permission in site settings", "audio__allow_permission_button": "Click here to know how to allow", "audio__allow_permission_helper": "You might have denied microphone access in the past.", @@ -479,6 +492,8 @@ "contact_person_at_the_facility": "Contact person at the current facility", "contact_person_number": "Contact person number", "contact_phone": "Contact Person Number", + "contact_with_confirmed_carrier": "Contact with confirmed carrier", + "contact_with_suspected_carrier": "Contact with suspected carrier", "contact_your_admin_to_add_skills": "Contact your admin to add skills", "continue": "Continue", "continue_watching": "Continue watching", @@ -543,6 +558,7 @@ "diagnosis_at_discharge": "Diagnosis at Discharge", "diastolic": "Diastolic", "differential": "Differential", + "differential_diagnosis": "Differential diagnosis", "discard": "Discard", "discharge": "Discharge", "discharge_from_care": "Discharge from CARE", @@ -560,7 +576,9 @@ "district": "District", "district_program_management_supporting_unit": "District Program Management Supporting Unit", "doctor_s_medical_council_registration": "Doctor's Medical Council Registration", + "doctors_name": "Doctor's Name", "domestic_healthcare_support": "Domestic healthcare support", + "domestic_international_travel": "Domestic/international Travel (within last 28 days)", "done": "Done", "dosage": "Dosage", "down": "Down", @@ -611,6 +629,7 @@ "encounter_suggestion__OP": "Out-patient visit", "encounter_suggestion__R": "Consultation", "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation", + "end_datetime": "End Date/Time", "enter_aadhaar_number": "Enter a 12-digit Aadhaar ID", "enter_aadhaar_otp": "Enter OTP sent to the registered mobile with Aadhaar", "enter_abha_address": "Enter ABHA Address", @@ -627,6 +646,7 @@ "error_while_deleting_record": "Error while deleting record", "escape": "Escape", "estimated_contact_date": "Estimated contact date", + "etiology_identified": "Etiology identified", "events": "Events", "expand_sidebar": "Expand Sidebar", "expected_burn_rate": "Expected Burn Rate", @@ -640,6 +660,7 @@ "facility_search_placeholder": "Search by Facility / District Name", "facility_type": "Facility Type", "failed_to_link_abha_number": "Failed to link ABHA Number. Please try again later.", + "fast_track_testing_reason": "Fast track testing reason", "features": "Features", "feed_configurations": "Feed Configurations", "feed_is_currently_not_live": "Feed is currently not live", @@ -685,6 +706,7 @@ "goal": "Our goal is to continuously improve the quality and accessibility of public healthcare services using digital tools.", "granted_on": "Granted On", "has_domestic_healthcare_support": "Has domestic healthcare support?", + "has_sari": "Has SARI (Severe Acute Respiratory illness)?", "health_facility__config_registration_error": "Health ID registration failed", "health_facility__config_update_error": "Health Facility config update failed", "health_facility__config_update_success": "Health Facility config updated successfully", @@ -718,6 +740,7 @@ "hubs": "Hub Facilities", "i_declare": "I hereby declare that:", "icd11_as_recommended": "As per ICD-11 recommended by WHO", + "icmr_specimen_referral_form": "ICMR Specimen Referral Form", "incomplete_patient_details_warning": "Patient details are incomplete. Please update the details before proceeding.", "inconsistent_dosage_units_error": "Dosage units must be same", "indian_mobile": "Indian Mobile", @@ -752,15 +775,18 @@ "investigations_suggested": "Investigations Suggested", "is": "Is", "is_antenatal": "Is Antenatal", + "is_atypical_presentation": "Is Atypical presentation", "is_declared_positive": "Whether declared positive", "is_emergency": "Is emergency", "is_emergency_case": "Is emergency case", "is_it_upshift": "is it upshift", "is_this_an_emergency": "Is this an emergency?", "is_this_an_upshift": "Is this an upshift?", + "is_unusual_course": "Is unusual course", "is_up_shift": "Is up shift", "is_upshift_case": "Is upshift case", "is_vaccinated": "Whether vaccinated", + "label": "Label", "landline": "Indian landline", "language_selection": "Language Selection", "last_administered": "Last administered", @@ -890,7 +916,7 @@ "notice_board": "Notice Board", "notification_permission_denied": "Notification permission denied", "notification_permission_granted": "Notification permission granted", - "number_of_aged_dependents_above_60": "Number Of Aged Dependents (Above 60)", + "number_of_aged_dependents": "Number of Aged Dependents (Above 60)", "number_of_beds": "Number of beds", "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", "number_of_chronic_diseased_dependents": "Number Of Chronic Diseased Dependents", @@ -924,6 +950,7 @@ "password_reset_failure": "Password Reset Failed", "password_reset_success": "Password Reset successfully", "password_sent": "Password Reset Email Sent", + "patient": "Patient", "patient_address": "Patient Address", "patient_body": "Patient Body", "patient_category": "Patient Category", @@ -1065,6 +1092,7 @@ "result": "Result", "result_date": "Result Date", "result_details": "Result details", + "result_on": "Result on", "resume": "Resume", "retake": "Retake", "return_to_care": "Return to CARE", @@ -1078,7 +1106,11 @@ "sample_collection_date": "Sample Collection Date", "sample_format": "Sample Format", "sample_test": "Sample Test", + "sample_test_details": "Sample Test Details", + "sample_test_history": "Sample Test History", "sample_type": "Sample Type", + "sample_type_description": "Sample Type Description", + "sari": "SARI - Severe Acute Respiratory illness", "save": "Save", "save_and_continue": "Save and Continue", "save_investigation": "Save Investigation", @@ -1145,6 +1177,7 @@ "spokes": "Spoke Facilities", "srf_id": "SRF ID", "staff_list": "Staff List", + "start_datetime": "Start Date/Time", "start_dosage": "Start Dosage", "state": "State", "status": "Status", @@ -1167,6 +1200,7 @@ "tachycardia": "Tachycardia", "target_dosage": "Target Dosage", "test_type": "Type of test done", + "tested_on": "Tested on", "third_party_software_licenses": "Third Party Software Licenses", "titrate_dosage": "Titrate Dosage", "to_be_conducted": "To be conducted", @@ -1236,6 +1270,13 @@ "vacant": "Vacant", "vehicle_preference": "Vehicle preference", "vendor_name": "Vendor Name", + "ventilator_interface": "Respiratory Support Type", + "ventilator_log": "Ventilator Log", + "ventilator_modality": "Modality", + "ventilator_mode": "Ventilator Mode", + "ventilator_oxygen_modality": "Oxygen Modality", + "ventilator_oxygen_modality_oxygen_rate": "Oxygen Flow Rate", + "ventilator_spo2": "SpO₂", "verify_and_link": "Verify and Link", "verify_otp": "Verify OTP", "verify_otp_error": "Failed to verify OTP. Please try again later.", diff --git a/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx b/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx index 1ded0ba7684..b26ea6e0e53 100644 --- a/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx +++ b/src/components/Facility/ConsultationDetails/ConsultationVentilatorTab.tsx @@ -1,8 +1,30 @@ +import Loading from "@/components/Common/Loading"; import PageTitle from "@/components/Common/PageTitle"; import { ConsultationTabProps } from "@/components/Facility/ConsultationDetails/index"; import { VentilatorPlot } from "@/components/Facility/Consultations/VentilatorPlot"; +import VentilatorTable from "@/components/Facility/Consultations/VentilatorTable"; + +import useFilters from "@/hooks/useFilters"; + +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; export const ConsultationVentilatorTab = (props: ConsultationTabProps) => { + const { consultationId } = props; + const { qParams, Pagination, resultsPerPage } = useFilters({ limit: 36 }); + + const { loading: isLoading, data } = useQuery(routes.getDailyReports, { + pathParams: { consultationId }, + query: { + limit: resultsPerPage, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + }, + }); + + if (isLoading) { + return ; + } + return (
{ hideBack={true} breadcrumbs={false} /> - + + + {Boolean(data?.count && data.count > 0) && ( +
+ +
+ )}
); }; diff --git a/src/components/Facility/ConsultationDetails/Events/EventsList.tsx b/src/components/Facility/ConsultationDetails/Events/EventsList.tsx index e3f7072391e..47c68636a1a 100644 --- a/src/components/Facility/ConsultationDetails/Events/EventsList.tsx +++ b/src/components/Facility/ConsultationDetails/Events/EventsList.tsx @@ -68,6 +68,12 @@ export default function EventsList({ query }: { query: QueryParams }) { } const values = Object.fromEntries(entries); + if ( + values.ventilator_interface === "INVASIVE" || + values.ventilator_interface === "NON_INVASIVE" + ) { + values.ventilator_interface += " VENTILATOR"; + } switch (item.event_type.name) { case "INTERNAL_TRANSFER": diff --git a/src/components/Facility/Consultations/VentilatorPlot.tsx b/src/components/Facility/Consultations/VentilatorPlot.tsx index 6991fd02b8d..38948f165ea 100644 --- a/src/components/Facility/Consultations/VentilatorPlot.tsx +++ b/src/components/Facility/Consultations/VentilatorPlot.tsx @@ -1,14 +1,11 @@ import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; -import Pagination from "@/components/Common/Pagination"; +import Loading from "@/components/Common/Loading"; import BinaryChronologicalChart from "@/components/Facility/Consultations/components/BinaryChronologicalChart"; import { LinePlot } from "@/components/Facility/Consultations/components/LinePlot"; -import { VentilatorPlotFields } from "@/components/Facility/models"; +import { DailyRoundsModel } from "@/components/Patient/models"; -import { PAGINATION_LIMIT } from "@/common/constants"; - -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; import { formatDateTime } from "@/Utils/utils"; /* @@ -31,48 +28,222 @@ const modality: Array = [ ]; */ -export const VentilatorPlot = (props: any) => { - const { consultationId } = props; - const [results, setResults] = useState({}); - const [currentPage, setCurrentPage] = useState(1); - const [totalCount, setTotalCount] = useState(0); +interface graphDataProps { + [key: string]: { + bilateral_air_entry?: boolean; + etco2?: number; + id?: string; + ventilator_fio2?: number; + ventilator_mean_airway_pressure?: number; + ventilator_oxygen_modality_flow_rate?: number; + ventilator_oxygen_modality_oxygen_rate?: number; + ventilator_peep?: number | null; + ventilator_pip?: number; + ventilator_pressure_support?: number; + ventilator_resp_rate?: number; + ventilator_spo2?: number; + ventilator_tidal_volume?: number; + }; +} - useEffect(() => { - const fetchDailyRounds = async ( - currentPage: number, - consultationId: string, - ) => { - const { res, data } = await request(routes.dailyRoundsAnalyse, { - body: { page: currentPage, fields: VentilatorPlotFields }, - pathParams: { - consultationId, - }, +export const VentilatorPlot = ({ + dailyRoundsList, +}: { + dailyRoundsList?: DailyRoundsModel[]; +}) => { + const [results, setResults] = useState({}); + const { t } = useTranslation(); + + const getGraphData = (dailyRoundsData?: DailyRoundsModel[]) => { + const graphData: graphDataProps = {}; + const graphDataCount = dailyRoundsData?.length ?? 0; + if (dailyRoundsData) { + dailyRoundsData.forEach((currentRound: DailyRoundsModel) => { + // @ts-expect-error taken_at should always be available + graphData[currentRound.taken_at] = { + bilateral_air_entry: currentRound.bilateral_air_entry, + etco2: currentRound.etco2, + id: currentRound.id, + ventilator_fio2: currentRound.ventilator_fio2, + ventilator_mean_airway_pressure: + currentRound.ventilator_mean_airway_pressure, + ventilator_oxygen_modality_flow_rate: + currentRound.ventilator_oxygen_modality_flow_rate, + ventilator_oxygen_modality_oxygen_rate: + currentRound.ventilator_oxygen_modality_oxygen_rate, + ventilator_peep: currentRound.ventilator_peep + ? Number(currentRound.ventilator_peep) + : null, + ventilator_pip: currentRound.ventilator_pip, + ventilator_pressure_support: currentRound.ventilator_pressure_support, + ventilator_resp_rate: currentRound.ventilator_resp_rate, + ventilator_spo2: currentRound.ventilator_spo2, + ventilator_tidal_volume: currentRound.ventilator_tidal_volume, + }; }); - if (res && res.ok && data) { - setResults(data.results); - setTotalCount(data.count); - } - }; + } + return { graphData, graphDataCount }; + }; + + useEffect(() => { + const { graphData } = getGraphData(dailyRoundsList); + setResults(graphData); + }, [dailyRoundsList]); - fetchDailyRounds(currentPage, consultationId); - }, [consultationId, currentPage]); + if (!dailyRoundsList) { + return ; + } - const handlePagination = (page: number) => { - setCurrentPage(page); + const dates = Object.keys(results).map((p: string) => formatDateTime(p)); + + const getConditionAndLegend = ( + name: string, + currentRound: DailyRoundsModel, + ) => { + let condition = false; + let legend = ""; + switch (name) { + case "ventilator_pip": + case "ventilator_mean_airway_pressure": + case "ventilator_resp_rate": + case "ventilator_pressure_support": + case "ventilator_tidal_volume": + case "ventilator_peep": + condition = + (currentRound.ventilator_interface === "INVASIVE" || + currentRound.ventilator_interface === "NON_INVASIVE") && + !!currentRound.ventilator_mode; + break; + case "ventilator_fio2": + condition = + currentRound.ventilator_interface === "OXYGEN_SUPPORT" && + currentRound.ventilator_oxygen_modality === "HIGH_FLOW_NASAL_CANNULA"; + break; + case "ventilator_spo2": + condition = + currentRound.ventilator_interface === "OXYGEN_SUPPORT" && + (currentRound.ventilator_oxygen_modality === "NASAL_PRONGS" || + currentRound.ventilator_oxygen_modality === "SIMPLE_FACE_MASK" || + currentRound.ventilator_oxygen_modality === + "NON_REBREATHING_MASK" || + currentRound.ventilator_oxygen_modality === + "HIGH_FLOW_NASAL_CANNULA"); + break; + case "etco2": + case "ventilator_oxygen_modality_flow_rate": + condition = + !!currentRound.ventilator_mode || + !!currentRound.ventilator_oxygen_modality || + false; + break; + case "ventilator_oxygen_modality_oxygen_rate": + condition = + currentRound.ventilator_interface === "OXYGEN_SUPPORT" && + (currentRound.ventilator_oxygen_modality === "NASAL_PRONGS" || + currentRound.ventilator_oxygen_modality === "SIMPLE_FACE_MASK" || + currentRound.ventilator_oxygen_modality === "NON_REBREATHING_MASK"); + break; + } + switch (currentRound.ventilator_interface) { + case "OXYGEN_SUPPORT": + legend = + t( + `OXYGEN_MODALITY__${currentRound.ventilator_oxygen_modality}_short`, + ) + + " (" + + t("RESPIRATORY_SUPPORT_SHORT__OXYGEN_SUPPORT") + + ")"; + break; + case "INVASIVE": + legend = + t(`VENTILATOR_MODE__${currentRound.ventilator_mode}_short`) + + " (" + + t("RESPIRATORY_SUPPORT_SHORT__INVASIVE") + + ")"; + break; + case "NON_INVASIVE": + legend = + t(`VENTILATOR_MODE__${currentRound.ventilator_mode}_short`) + + " (" + + t("RESPIRATORY_SUPPORT_SHORT__NON_INVASIVE") + + ")"; + break; + } + return { condition, legend }; + }; + + const getModeOrModality = (round: DailyRoundsModel) => { + const ventilatorInterface = round.ventilator_interface; + if (!ventilatorInterface) return null; + switch (ventilatorInterface) { + case "INVASIVE": + case "NON_INVASIVE": + return round.ventilator_mode; + case "OXYGEN_SUPPORT": + return round.ventilator_oxygen_modality; + default: + return null; + } }; - const dates = Object.keys(results) - .map((p: string) => formatDateTime(p)) - .reverse(); + const getMarkLineData = (name: string) => { + const markLineData = []; + if (!dailyRoundsList) return []; + let index = 0; + while (index < dailyRoundsList.length) { + const currentRound = dailyRoundsList[index]; + const { condition, legend } = getConditionAndLegend(name, currentRound); + const currentInterfaceOrModality = getModeOrModality(currentRound); + if (condition) { + const startIndex = dates.findIndex( + (element) => element === formatDateTime(currentRound.taken_at), + ); + if (startIndex !== -1) { + let nextIndex = index + 1; + while (nextIndex < dailyRoundsList.length) { + const nextRound = dailyRoundsList[nextIndex]; + const nextInterfaceOrModality = getModeOrModality(nextRound); + if ( + currentRound.ventilator_interface === + nextRound.ventilator_interface && + currentInterfaceOrModality === nextInterfaceOrModality + ) { + nextIndex += 1; + } else { + break; + } + } + const position = + startIndex === 0 ? "insideMiddleBottom" : "insideMiddleTop"; + markLineData.push({ + name: legend, + xAxis: dates[startIndex], + label: { + show: true, + position, + formatter: "{b}", + color: "#000000", + textBorderColor: "#ffffff", + textBorderWidth: 2, + }, + }); + index = nextIndex; + } else { + index += 1; + } + } else { + index += 1; + } + } + return markLineData; + }; - const yAxisData = (name: string) => { - return Object.values(results) - .map((p: any) => p[name]) - .reverse(); + const yAxisData = (name: keyof graphDataProps[string]) => { + return Object.values(results).map((p) => p[name]); }; const bilateral = Object.values(results) - .map((p: any, i) => { + .map((p, i) => { return { value: p.bilateral_air_entry, timestamp: Object.keys(results)[i], @@ -91,6 +262,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_pip")} low={12} high={30} + verticalMarkerData={getMarkLineData("ventilator_pip")} />
@@ -101,6 +273,9 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_mean_airway_pressure")} low={12} high={25} + verticalMarkerData={getMarkLineData( + "ventilator_mean_airway_pressure", + )} />
@@ -111,6 +286,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_resp_rate")} low={12} high={20} + verticalMarkerData={getMarkLineData("ventilator_resp_rate")} />
@@ -121,6 +297,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_pressure_support")} low={5} high={15} + verticalMarkerData={getMarkLineData("ventilator_pressure_support")} />
@@ -129,6 +306,7 @@ export const VentilatorPlot = (props: any) => { name="Tidal Volume" xData={dates} yData={yAxisData("ventilator_tidal_volume")} + verticalMarkerData={getMarkLineData("ventilator_tidal_volume")} />
@@ -139,6 +317,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_peep")} low={5} high={10} + verticalMarkerData={getMarkLineData("ventilator_peep")} />
@@ -149,6 +328,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_fio2")} low={21} high={60} + verticalMarkerData={getMarkLineData("ventilator_fio2")} />
@@ -159,6 +339,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("ventilator_spo2")} low={90} high={100} + verticalMarkerData={getMarkLineData("ventilator_spo2")} />
@@ -169,6 +350,7 @@ export const VentilatorPlot = (props: any) => { yData={yAxisData("etco2")} low={35} high={45} + verticalMarkerData={getMarkLineData("etco2")} />
@@ -185,6 +367,9 @@ export const VentilatorPlot = (props: any) => { name="Oxygen Flow Rate" xData={dates} yData={yAxisData("ventilator_oxygen_modality_oxygen_rate")} + verticalMarkerData={getMarkLineData( + "ventilator_oxygen_modality_oxygen_rate", + )} />
@@ -193,20 +378,12 @@ export const VentilatorPlot = (props: any) => { name="Flow Rate" xData={dates} yData={yAxisData("ventilator_oxygen_modality_flow_rate")} + verticalMarkerData={getMarkLineData( + "ventilator_oxygen_modality_flow_rate", + )} />
- - {totalCount > PAGINATION_LIMIT && ( -
- -
- )} ); }; diff --git a/src/components/Facility/Consultations/VentilatorTable.tsx b/src/components/Facility/Consultations/VentilatorTable.tsx new file mode 100644 index 00000000000..2059f14bac0 --- /dev/null +++ b/src/components/Facility/Consultations/VentilatorTable.tsx @@ -0,0 +1,129 @@ +import { useTranslation } from "react-i18next"; + +import { compareByDateString, formatDateTime } from "@/Utils/utils"; + +import { DailyRoundsModel } from "../../Patient/models"; + +type VentilatorTableProps = { + dailyRoundsList?: DailyRoundsModel[]; +}; + +export default function VentilatorTable(props: VentilatorTableProps) { + const { t } = useTranslation(); + const { dailyRoundsList } = props; + + const VentilatorTableRow = ({ + dailyRound, + start_date, + end_date, + }: { + dailyRound: DailyRoundsModel; + start_date: string; + end_date: string; + }) => { + const getModeText = () => { + const { + ventilator_interface, + ventilator_mode, + ventilator_oxygen_modality, + } = dailyRound; + switch (ventilator_interface) { + case "INVASIVE": + case "NON_INVASIVE": + return t(`VENTILATOR_MODE__${ventilator_mode}`); + case "OXYGEN_SUPPORT": + return t(`OXYGEN_MODALITY__${ventilator_oxygen_modality}`); + default: + return null; + } + }; + return ( + + {start_date} + {end_date} + + {t(`RESPIRATORY_SUPPORT__${dailyRound?.ventilator_interface}`)} + + {getModeText()} + + ); + }; + + const getModeOrModality = (round: DailyRoundsModel) => { + const ventilatorInterface = round.ventilator_interface; + if (!ventilatorInterface) return null; + switch (ventilatorInterface) { + case "INVASIVE": + case "NON_INVASIVE": + return round.ventilator_mode; + case "OXYGEN_SUPPORT": + return round.ventilator_oxygen_modality; + default: + return null; + } + }; + + const VentilatorTableBody = (dailyRoundsList: DailyRoundsModel[]) => { + const rows = []; + for (let index = 0; index < dailyRoundsList.length; index++) { + const currentRound = dailyRoundsList[index]; + const currentInterfaceOrModality = getModeOrModality(currentRound); + if (!currentInterfaceOrModality) continue; + while (index < dailyRoundsList.length - 1) { + const nextRound = dailyRoundsList[index + 1]; + const nextInterfaceOrModality = getModeOrModality(nextRound); + if ( + nextInterfaceOrModality && + currentRound.ventilator_interface == nextRound.ventilator_interface && + currentInterfaceOrModality == nextInterfaceOrModality + ) { + index += 1; + } else { + break; + } + } + const end_date = + index + 1 < dailyRoundsList.length + ? formatDateTime(dailyRoundsList[index + 1].taken_at) + : ""; + const start_date = formatDateTime(currentRound.taken_at); + rows.push( + , + ); + } + return rows; + }; + + if (!dailyRoundsList?.length) { + return; + } + const sortedData: DailyRoundsModel[] = dailyRoundsList.sort( + compareByDateString("taken_at"), + ); + + return ( +
+ + + + + + + + + + + {VentilatorTableBody(sortedData)} +
+ {t("ventilator_log")} +
{t("start_datetime")}{t("end_datetime")}{t("ventilator_modality")} + {`${t("ventilator_mode")} / ${t("ventilator_oxygen_modality")}`} +
+
+ ); +} diff --git a/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx b/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx index 82eb7f3da4d..6183f19368d 100644 --- a/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx +++ b/src/components/Facility/Consultations/components/BinaryChronologicalChart.tsx @@ -4,7 +4,7 @@ import { formatDateTime } from "@/Utils/utils"; export default function BinaryChronologicalChart(props: { data: { - value: boolean; + value: boolean | undefined; timestamp: string; notes?: string; }[]; diff --git a/src/components/Facility/Consultations/components/LinePlot.tsx b/src/components/Facility/Consultations/components/LinePlot.tsx index 0829527faae..e9f1adf731f 100644 --- a/src/components/Facility/Consultations/components/LinePlot.tsx +++ b/src/components/Facility/Consultations/components/LinePlot.tsx @@ -12,12 +12,21 @@ export const LinePlot = (props: any) => { const { title, name, - xData, - yData, low = null, high = null, defaultSpace, + verticalMarkerData = null, } = props; + let { xData, yData } = props; + const yDatacount = yData.filter( + (item: number | null): item is number => + item !== null && !Number.isNaN(item), + ).length; + if (yDatacount === 0) { + yData = []; + xData = []; + } + let generalOptions: any = { grid: { top: "40px", @@ -106,6 +115,25 @@ export const LinePlot = (props: any) => { ], }; + if (verticalMarkerData && yDatacount > 0) { + let series = generalOptions.series[0]; + series = { + ...series, + markLine: { + silent: true, + data: verticalMarkerData, + symbol: "none", + lineStyle: { + color: "#000000", + }, + }, + }; + generalOptions = { + ...generalOptions, + series, + }; + } + if (props.type && props.type === "WAVEFORM") { generalOptions = { ...generalOptions, diff --git a/src/components/Facility/Consultations/components/ReactEcharts.tsx b/src/components/Facility/Consultations/components/ReactEcharts.tsx index 326bd23661a..4215b34fa4d 100644 --- a/src/components/Facility/Consultations/components/ReactEcharts.tsx +++ b/src/components/Facility/Consultations/components/ReactEcharts.tsx @@ -5,6 +5,7 @@ import { DataZoomComponent, GridComponent, LegendComponent, + MarkLineComponent, TitleComponent, ToolboxComponent, TooltipComponent, @@ -27,6 +28,7 @@ echarts.use([ TooltipComponent, VisualMapComponent, VisualMapPiecewiseComponent, + MarkLineComponent, ]); interface ReactEchartsProps extends EChartsReactProps { diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index c556177ec13..c16ef1c0e86 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -1,5 +1,6 @@ import { camelCase, capitalize, startCase } from "lodash-es"; import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; import Card from "@/CAREUI/display/Card"; @@ -17,6 +18,7 @@ import useQuery from "@/Utils/request/useQuery"; import { formatDateTime, formatPatientAge } from "@/Utils/utils"; export const SampleDetails = ({ id }: DetailRoute) => { + const { t } = useTranslation(); const { loading: isLoading, data: sampleDetails } = useQuery( routes.getTestSample, { @@ -33,9 +35,9 @@ export const SampleDetails = ({ id }: DetailRoute) => { const yesOrNoBadge = (param: any) => param ? ( - Yes + {t("yes")} ) : ( - No + {t("no")} ); const showPatientCard = (patientData: any) => { @@ -51,20 +53,24 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Name: + + {t("name")}:{" "} + {patientData?.name}
{patientData?.is_medical_worker && (
- Medical Worker:{" "} + {t("medical_worker")}:{" "} + + + {t("yes")} - Yes
)}
- Disease Status:{" "} + {t("disease_status")}:{" "} {patientData?.disease_status} @@ -72,16 +78,20 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- SRF ID: + + {t("srf_id")}:{" "} + {(patientData?.srf_id && patientData?.srf_id) || "-"}
- Test Type: + + {t("test_type")}:{" "} + {(patientData?.test_type && testType) || "-"}
- Date of Test:{" "} + {t("date_of_test")}:{" "} {(patientData?.date_of_test && formatDateTime(patientData?.date_of_test)) || @@ -89,35 +99,43 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Facility: + + {t("facility")}:{" "} + {patientData?.facility_object?.name || "-"}
{patientData?.date_of_birth ? (
- Date of birth:{" "} + {t("date_of_birth")}:{" "} {patientData?.date_of_birth}
) : (
- Age: + + {t("age")}:{" "} + {formatPatientAge(patientData)}
)}
- Gender: + + {t("gender")}:{" "} + {patientGender}
- Phone: + + {t("phone")}:{" "} + {patientData?.phone_number || "-"}
- Nationality:{" "} + {t("nationality")}:{" "} {patientData?.nationality || "-"}
@@ -125,14 +143,14 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Blood Group:{" "} + {t("blood_group")}:{" "} {patientData?.blood_group || "-"}
{patientData?.nationality !== "India" && (
- Passport Number:{" "} + {t("passport_number")}:{" "} {patientData?.passport_no || "-"}
@@ -140,56 +158,60 @@ export const SampleDetails = ({ id }: DetailRoute) => { {patientData?.nationality === "India" && ( <>
- State: + + {t("state")}:{" "} + {patientData?.state_object?.name}
- District:{" "} + {t("district")}:{" "} {patientData?.district_object?.name || "-"}
- Local Body:{" "} + {t("local_body")}:{" "} {patientData?.local_body_object?.name || "-"}
)}
- Address: + + {t("address")}:{" "} + {patientData?.address || "-"}
- Contact with confirmed carrier:{" "} + {t("contact_with_confirmed_carrier")}:{" "} {yesOrNoBadge(patientData?.contact_with_confirmed_carrier)}
- Contact with suspected carrier:{" "} + {t("contact_with_suspected_carrier")}:{" "} {yesOrNoBadge(patientData?.contact_with_suspected_carrier)}
{patientData?.estimated_contact_date && (
- Estimated contact date:{" "} + {t("estimated_contact_date")}:{" "} {formatDateTime(patientData?.estimated_contact_date)}
)}
- Has SARI (Severe Acute Respiratory illness)?:{" "} + {t("has_sari")}:{" "} {yesOrNoBadge(patientData?.has_SARI)}
- Domestic/international Travel (within last 28 days):{" "} + {t("domestic_international_travel")}:{" "} {yesOrNoBadge(patientData?.past_travel)}
@@ -197,7 +219,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { !!patientData?.countries_travelled.length && (
- Countries travelled:{" "} + {t("countries_travelled")}:{" "} {patientData?.countries_travelled.join(", ")}
@@ -205,7 +227,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {patientData?.ongoing_medication && (
- Ongoing Medications{" "} + {t("ongoing_medications")}{" "} {patientData?.ongoing_medication}
@@ -213,7 +235,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {patientData?.allergies && (
- Allergies:{" "} + {t("allergies")}:{" "} {patientData?.allergies}
@@ -221,7 +243,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {!!patientData?.number_of_aged_dependents && (
- Number Of Aged Dependents (Above 60):{" "} + {t("number_of_aged_dependents")}:{" "} {patientData?.number_of_aged_dependents}
@@ -229,7 +251,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {!!patientData?.number_of_chronic_diseased_dependents && (
- Number Of Chronic Diseased Dependents:{" "} + {t("number_of_chronic_diseased_dependents")}:{" "} {patientData?.number_of_chronic_diseased_dependents}
@@ -245,19 +267,25 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Status: {" "} + + {t("status")}:{" "} + {" "} {startCase(camelCase(flow.status))}
- Label:{" "} + {t("label")}:{" "} {capitalize(flow.notes)}
- Created On :{" "} + + {t("created_on")}: + {" "} {flow.created_date ? formatDateTime(flow.created_date) : "-"}
- Modified on:{" "} + + {t("modified_on")}: + {" "} {flow.modified_date ? formatDateTime(flow.modified_date) : "-"}
@@ -271,7 +299,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { return ( { - ICMR Specimen Referral Form + {t("icmr_specimen_referral_form")}
) @@ -289,34 +317,42 @@ export const SampleDetails = ({ id }: DetailRoute) => {
- Status:{" "} + {t("status")}:{" "} {sampleDetails?.status}
- Result:{" "} + {t("result")}:{" "} {sampleDetails?.result}
- Patient: + + {t("patient")}:{" "} + {sampleDetails?.patient_name}
{sampleDetails?.facility_object && (
- Facility: + + {t("facility")}:{" "} + {sampleDetails?.facility_object.name}
)}
- Tested on: + + {t("tested_on")}:{" "} + {sampleDetails?.date_of_result ? formatDateTime(sampleDetails.date_of_result) : "-"}
- Result on: + + {t("result_on")}:{" "} + {sampleDetails?.date_of_result ? formatDateTime(sampleDetails.date_of_result) : "-"} @@ -324,7 +360,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails?.fast_track && (
- Fast track testing reason:{" "} + {t("fast_track_testing_reason")}:{" "} {sampleDetails.fast_track}
@@ -332,21 +368,23 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails?.doctor_name && (
- Doctor's Name:{" "} + {t("doctors_name")}:{" "} {startCase(camelCase(sampleDetails.doctor_name))}
)} {sampleDetails?.diagnosis && (
- Diagnosis: + + {t("diagnosis")}:{" "} + {sampleDetails.diagnosis}
)} {sampleDetails?.diff_diagnosis && (
- Differential diagnosis:{" "} + {t("differential_diagnosis")}:{" "} {sampleDetails?.diff_diagnosis}
@@ -354,52 +392,48 @@ export const SampleDetails = ({ id }: DetailRoute) => { {sampleDetails?.etiology_identified && (
- Etiology identified:{" "} + {t("etiology_identified")}:{" "} {sampleDetails.etiology_identified}
)}
- Is Atypical presentation{" "} + {t("is_atypical_presentation")}{" "} {yesOrNoBadge(sampleDetails?.is_atypical_presentation)}
- Is unusual course{" "} + {t("is_unusual_course")}{" "} {yesOrNoBadge(sampleDetails?.is_unusual_course)}
{sampleDetails?.atypical_presentation && (
- Atypical presentation details:{" "} + {t("atypical_presentation_details")}:{" "} {sampleDetails.atypical_presentation}
)}
- - SARI - Severe Acute Respiratory illness{" "} - + {t("sari")} {yesOrNoBadge(sampleDetails?.has_sari)}
- - ARI - Acute Respiratory illness{" "} - + {t("ari")} {yesOrNoBadge(sampleDetails?.has_ari)}
- Contact with confirmed carrier{" "} + {t("contact_with_confirmed_carrier")}{" "} {yesOrNoBadge(sampleDetails?.patient_has_confirmed_contact)}
- Contact with suspected carrier{" "} + {t("contact_with_suspected_carrier")}{" "} {yesOrNoBadge(sampleDetails?.patient_has_suspected_contact)}
@@ -407,29 +441,37 @@ export const SampleDetails = ({ id }: DetailRoute) => { sampleDetails.patient_travel_history.length !== 0 && (
- Countries travelled:{" "} + {t("countries_travelled")}:{" "} {sampleDetails.patient_travel_history}
)} {sampleDetails?.sample_type && ( -
+
- Sample Type:{" "} + {t("sample_type")}:{" "} {startCase(camelCase(sampleDetails.sample_type))}
)} + {sampleDetails?.sample_type === "OTHER TYPE" && ( +
+ + {t("sample_type_description")}:{" "} + + {sampleDetails?.sample_type_other} +
+ )}
-

Details of patient

+

{t("details_of_patient")}

{showPatientCard(sampleDetails?.patient_object)}
-

Sample Test History

+

{t("sample_test_history")}

{sampleDetails?.flow && sampleDetails.flow.map((flow: FlowModel) => renderFlow(flow))}
diff --git a/src/components/Patient/SampleTest.tsx b/src/components/Patient/SampleTest.tsx index afddb81b654..0714de28492 100644 --- a/src/components/Patient/SampleTest.tsx +++ b/src/components/Patient/SampleTest.tsx @@ -218,7 +218,7 @@ export const SampleTest = ({ facilityId, patientId }: any) => { optionValue={(option) => option.id} /> - {state.form.sample_type === "OTHER TYPE" && ( + {state.form.sample_type === "9" && ( { Sample Type{" "}
- {(itemData.sample_type !== "OTHER TYPE" - ? itemData.sample_type - : itemData.sample_type_other - )?.toLowerCase()} + {itemData.sample_type?.toLowerCase()}