From 5027d5b013776850107fe5f5fb1de9842687f16d Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Fri, 20 Sep 2024 17:13:21 +0530 Subject: [PATCH 01/30] Rename procedure `Tracheostomy Tube Change` and fix oral issue not getting unselected (#8576) --- src/Common/constants.tsx | 2 +- src/Components/Patient/DailyRounds.tsx | 4 ++++ src/Locale/en/LogUpdate.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 8149a144ed9..c2ddf842665 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -759,7 +759,7 @@ export const NURSING_CARE_PROCEDURES = [ "restrain", "chest_tube_care", "tracheostomy_care", - "tracheostomy_change", + "tracheostomy_tube_change", "stoma_care", "catheter_care", "catheter_change", diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index f949bd9e404..ff5ddf43fdf 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -444,6 +444,10 @@ export const DailyRounds = (props: any) => { form["investigations_dirty"] = true; } + if (event.name === "nutrition_route" && event.value !== "ORAL") { + form["oral_issue"] = undefined; + } + dispatch({ type: "set_form", form }); }; diff --git a/src/Locale/en/LogUpdate.json b/src/Locale/en/LogUpdate.json index ec5dfe8521e..2e94643fd91 100644 --- a/src/Locale/en/LogUpdate.json +++ b/src/Locale/en/LogUpdate.json @@ -131,7 +131,7 @@ "NURSING_CARE_PROCEDURE__restrain": "Restrain", "NURSING_CARE_PROCEDURE__chest_tube_care": "Chest Tube Care", "NURSING_CARE_PROCEDURE__tracheostomy_care": "Tracheostomy Care", - "NURSING_CARE_PROCEDURE__tracheostomy_change": "Tracheostomy change ", + "NURSING_CARE_PROCEDURE__tracheostomy_tube_change": "Tracheostomy Tube Change", "NURSING_CARE_PROCEDURE__stoma_care": "Stoma Care", "NURSING_CARE_PROCEDURE__catheter_care": "Catheter Care", "NURSING_CARE_PROCEDURE__catheter_change": "Catheter Change", From 71637463023834a6da71cc4f4cc86a38603eba52 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Sat, 21 Sep 2024 09:53:20 +0530 Subject: [PATCH 02/30] Updated blood pressure range and remove hyphen for infusion "nor-adrenaline" (#8498) --- .../Common/BloodPressureFormField.tsx | 41 ++++------ .../DailyRounds/LogUpdateCardAttribute.tsx | 10 +-- .../Facility/Consultations/Mews.tsx | 2 +- .../Consultations/PrimaryParametersPlot.tsx | 18 +---- src/Components/Facility/models.tsx | 7 +- .../Form/FormFields/RangeFormField.tsx | 48 ++++++++---- .../LogUpdate/CriticalCarePreview.tsx | 3 +- .../LogUpdate/Sections/IOBalance.tsx | 2 +- src/Components/LogUpdate/Sections/Vitals.tsx | 77 ++++++++++--------- src/Components/Patient/DailyRounds.tsx | 11 +-- src/Components/Patient/models.tsx | 7 +- src/Components/Scribe/formDetails.ts | 4 +- src/Locale/en/Common.json | 1 - src/Locale/en/LogUpdate.json | 5 +- src/Locale/hi/LogUpdate.json | 1 - src/Locale/kn/LogUpdate.json | 1 - src/Locale/ml/LogUpdate.json | 1 - src/Locale/ta/LogUpdate.json | 1 - 18 files changed, 112 insertions(+), 128 deletions(-) diff --git a/src/Components/Common/BloodPressureFormField.tsx b/src/Components/Common/BloodPressureFormField.tsx index 53c4adf2194..205bc9a4905 100644 --- a/src/Components/Common/BloodPressureFormField.tsx +++ b/src/Components/Common/BloodPressureFormField.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next"; import { FieldValidator } from "../Form/FieldValidators"; import FormField from "../Form/FormFields/FormField"; import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; @@ -8,42 +9,34 @@ import { } from "../Form/FormFields/Utils"; import { BloodPressure } from "../Patient/models"; -type Props = FormFieldBaseProps; +type Props = FormFieldBaseProps; export default function BloodPressureFormField(props: Props) { + const { t } = useTranslation(); const field = useFormFieldPropsResolver(props); + const map = meanArterialPressure(props.value)?.toFixed(); const handleChange = (event: FieldChangeEvent) => { - const value: BloodPressure = { - ...field.value, - [event.name]: event.value, - }; - value.mean = meanArterialPressure(value); - field.onChange({ name: field.name, value }); + const bp = field.value ?? {}; + bp[event.name as keyof BloodPressure] = event.value; + field.handleChange(Object.values(bp).filter(Boolean).length ? bp : null); }; - const map = - !!props.value?.diastolic && - !!props.value.systolic && - meanArterialPressure(props.value); - return ( MAP: {map.toFixed(1)} - ) : undefined, + labelSuffix: map && MAP: {map}, }} >
/ { - if (diastolic != null && systolic != null) { - return (2 * diastolic + systolic) / 3; +export const meanArterialPressure = (bp?: BloodPressure | null) => { + if (bp?.diastolic == null || bp?.systolic == null) { + return; } + return (2 * bp.diastolic + bp.systolic) / 3; }; export const BloodPressureValidator: FieldValidator = (bp) => { diff --git a/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx b/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx index cc6f467b186..c1b5fec3c8c 100644 --- a/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx @@ -1,10 +1,6 @@ import { useTranslation } from "react-i18next"; import PatientCategoryBadge from "../../../Common/PatientCategoryBadge"; -import { - BloodPressure, - DailyRoundsModel, - NameQuantity, -} from "../../../Patient/models"; +import { DailyRoundsModel, NameQuantity } from "../../../Patient/models"; import { PatientCategory } from "../../models"; interface Props { @@ -45,8 +41,8 @@ const LogUpdateCardAttribute = ({
- {(attributeValue as BloodPressure).systolic}/ - {(attributeValue as BloodPressure).diastolic} mmHg + {(attributeValue as DailyRoundsModel["bp"])?.systolic || "--"}/ + {(attributeValue as DailyRoundsModel["bp"])?.diastolic || "--"} mmHg
); diff --git a/src/Components/Facility/Consultations/Mews.tsx b/src/Components/Facility/Consultations/Mews.tsx index df54b83972f..91473a9a4ce 100644 --- a/src/Components/Facility/Consultations/Mews.tsx +++ b/src/Components/Facility/Consultations/Mews.tsx @@ -23,7 +23,7 @@ const getHeartRateScore = (value?: number) => { return 3; }; -const getSystolicBPScore = (value?: number) => { +const getSystolicBPScore = (value?: number | null) => { if (typeof value !== "number") return; if (value <= 70) return 3; diff --git a/src/Components/Facility/Consultations/PrimaryParametersPlot.tsx b/src/Components/Facility/Consultations/PrimaryParametersPlot.tsx index 0fb0844c576..271c39709ad 100644 --- a/src/Components/Facility/Consultations/PrimaryParametersPlot.tsx +++ b/src/Components/Facility/Consultations/PrimaryParametersPlot.tsx @@ -10,6 +10,7 @@ import CareIcon from "../../../CAREUI/icons/CareIcon"; import { PainDiagrams } from "./PainDiagrams"; import PageTitle from "../../Common/PageTitle"; import dayjs from "../../../Utils/dayjs"; +import { meanArterialPressure } from "../../Common/BloodPressureFormField"; import { PrimaryParametersPlotFields } from "../models"; interface PrimaryParametersPlotProps { @@ -18,17 +19,6 @@ interface PrimaryParametersPlotProps { consultationId: string; } -const sanitizeBPAttribute = (value: number | undefined) => { - // Temp. hack until the cleaning of daily rounds as a db migration is done. - // TODO: remove once migration is merged. - - if (value == null || value < 0) { - return; - } - - return value; -}; - export const PrimaryParametersPlot = ({ consultationId, }: PrimaryParametersPlotProps) => { @@ -77,19 +67,19 @@ export const PrimaryParametersPlot = ({ { name: "diastolic", data: Object.values(results) - .map((p: any) => p.bp && sanitizeBPAttribute(p.bp.diastolic)) + .map((p: any) => p.bp?.diastolic) .reverse(), }, { name: "systolic", data: Object.values(results) - .map((p: any) => p.bp && sanitizeBPAttribute(p.bp.systolic)) + .map((p: any) => p.bp?.systolic) .reverse(), }, { name: "mean", data: Object.values(results) - .map((p: any) => p.bp && sanitizeBPAttribute(p.bp.mean)) + .map((p: any) => meanArterialPressure(p.bp)) .reverse(), }, ]; diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 92a55ae6ae7..a5455cb29bd 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -14,6 +14,7 @@ import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; import { NormalPrescription, PRNPrescription } from "../Medicine/models"; import { AssignedToObjectModel, + BloodPressure, DailyRoundsModel, FileUploadModel, } from "../Patient/models"; @@ -427,11 +428,7 @@ export const PrimaryParametersPlotFields = [ ] as const satisfies (keyof DailyRoundsModel)[]; export type PrimaryParametersPlotRes = { - bp: { - mean?: number; - systolic?: number; - diastolic?: number; - }; + bp: BloodPressure; pulse: number; temperature: string; resp: number; diff --git a/src/Components/Form/FormFields/RangeFormField.tsx b/src/Components/Form/FormFields/RangeFormField.tsx index aeda58ac0fd..e7e9b1e9a53 100644 --- a/src/Components/Form/FormFields/RangeFormField.tsx +++ b/src/Components/Form/FormFields/RangeFormField.tsx @@ -12,9 +12,12 @@ import { SelectFormField } from "./SelectFormField"; type BaseProps = FormFieldBaseProps & { min: number; max: number; + sliderMin?: number; + sliderMax?: number; step?: number; valueDescriptions?: ValueDescription[]; hideInput?: boolean; + hideUnitInLabel?: boolean; }; type PropsWithUnit = BaseProps & { @@ -75,18 +78,20 @@ export default function RangeFormField(props: Props) { ? props.valueDescriptions?.find((vd) => (vd.till || props.max) >= value) : undefined; - const roundedValue = - Math.round(((value || min) + Number.EPSILON) * 100) / 100; - const allValueColors = props.valueDescriptions?.every((vd) => vd.color); - const trailPercent = ((roundedValue - min) / ((max || 0) - (min || 0))) * 100; + const [sliderMin, sliderMax] = [ + props.sliderMin ?? props.min, + props.sliderMax ?? props.max, + ].map(unit.conversionFn); - const snapStopLength = Math.min( - (props.max - props.min) / (props.step || 1), - props.max - props.min, - 20, - ); + const sliderDelta = sliderMax - sliderMin; + + const trailPercent = + ((Math.round(((value || sliderMin) + Number.EPSILON) * 100) / 100 - + sliderMin) / + sliderDelta) * + 100; const handleChange = (v: number) => field.handleChange(unit.inversionFn(v)); @@ -98,7 +103,10 @@ export default function RangeFormField(props: Props) { ...field, label: ( <> - {field.label} {unit.label && ({unit.label})} + {field.label}{" "} + {!props.hideUnitInLabel && unit.label && ( + ({unit.label}) + )} ), labelSuffix: ( @@ -124,6 +132,7 @@ export default function RangeFormField(props: Props) { max={max} errorClassName="hidden" inputClassName="py-1.5 mr-4" + disabled={props.disabled} /> {props.units?.length ? ( handleChange(e.target.valueAsNumber)} />
- {Array.from({ length: snapStopLength + 1 }).map((_, index) => ( + {Array.from({ + length: + 1 + Math.min(sliderDelta / (props.step || 1), sliderDelta, 20), + }).map((_, index) => (
))}
- {properRoundOf(min)} - {properRoundOf(max)} + {properRoundOf(sliderMin)} + {properRoundOf(sliderMax)}
); diff --git a/src/Components/LogUpdate/CriticalCarePreview.tsx b/src/Components/LogUpdate/CriticalCarePreview.tsx index 838887decf9..a458b1e245c 100644 --- a/src/Components/LogUpdate/CriticalCarePreview.tsx +++ b/src/Components/LogUpdate/CriticalCarePreview.tsx @@ -16,6 +16,7 @@ import { VentilatorFields } from "./Sections/RespiratorySupport/Ventilator"; import PressureSore from "./Sections/PressureSore/PressureSore"; import { IOBalanceSections } from "./Sections/IOBalance"; import PainChart from "./components/PainChart"; +import { meanArterialPressure } from "../Common/BloodPressureFormField"; import { DailyRoundsModel } from "../Patient/models"; type Props = { @@ -255,7 +256,7 @@ export default function CriticalCarePreview(props: Props) { />
)} diff --git a/src/Components/LogUpdate/Sections/IOBalance.tsx b/src/Components/LogUpdate/Sections/IOBalance.tsx index ca0bd4867d2..10f76581a46 100644 --- a/src/Components/LogUpdate/Sections/IOBalance.tsx +++ b/src/Components/LogUpdate/Sections/IOBalance.tsx @@ -14,7 +14,7 @@ export const IOBalanceSections = [ name: "Infusions", options: [ "Adrenalin", - "Nor-adrenalin", + "Noradrenalin", "Vasopressin", "Dopamine", "Dobutamine", diff --git a/src/Components/LogUpdate/Sections/Vitals.tsx b/src/Components/LogUpdate/Sections/Vitals.tsx index 0561debc88c..44902bbf0b8 100644 --- a/src/Components/LogUpdate/Sections/Vitals.tsx +++ b/src/Components/LogUpdate/Sections/Vitals.tsx @@ -2,61 +2,31 @@ import { useTranslation } from "react-i18next"; import { celsiusToFahrenheit, fahrenheitToCelsius, - properRoundOf, rangeValueDescription, } from "../../../Utils/utils"; import { meanArterialPressure } from "../../Common/BloodPressureFormField"; - import RadioFormField from "../../Form/FormFields/RadioFormField"; import RangeFormField from "../../Form/FormFields/RangeFormField"; import TextAreaFormField from "../../Form/FormFields/TextAreaFormField"; -import { FieldChangeEvent } from "../../Form/FormFields/Utils"; import PainChart from "../components/PainChart"; import { LogUpdateSectionMeta, LogUpdateSectionProps } from "../utils"; import { HEARTBEAT_RHYTHM_CHOICES } from "../../../Common/constants"; +import { BloodPressure } from "../../Patient/models"; const Vitals = ({ log, onChange }: LogUpdateSectionProps) => { const { t } = useTranslation(); - const handleBloodPressureChange = (event: FieldChangeEvent) => { - const bp = { - ...(log.bp ?? {}), - [event.name]: event.value, - }; - bp.mean = meanArterialPressure(bp); - onChange({ bp }); - }; return (
-

{t("blood_pressure")}

+

{t("LOG_UPDATE_FIELD_LABEL__bp")}

- {t("map_acronym")}:{" "} - {(log.bp?.mean && properRoundOf(log.bp.mean)) || "--"} + {t("map_acronym")}: {meanArterialPressure(log.bp)?.toFixed() ?? "--"}{" "} + mmHg
- - + +
{ ); }; +const BPAttributeEditor = ({ + attribute, + log, + onChange, +}: LogUpdateSectionProps & { attribute: "systolic" | "diastolic" }) => { + const { t } = useTranslation(); + + return ( + { + const bp = log.bp ?? {}; + bp[event.name as keyof BloodPressure] = event.value; + onChange({ + bp: Object.values(bp).filter(Boolean).length ? bp : undefined, + }); + }} + value={log.bp?.[attribute] ?? undefined} + min={0} + max={400} + sliderMin={30} + sliderMax={270} + step={1} + unit="mmHg" + valueDescriptions={rangeValueDescription( + attribute === "systolic" + ? { low: 99, high: 139 } + : { low: 49, high: 89 }, + )} + hideUnitInLabel + /> + ); +}; + Vitals.meta = { title: "Vitals", icon: "l-heartbeat", diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index ff5ddf43fdf..c0cd7fe803c 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -92,12 +92,7 @@ export const DailyRounds = (props: any) => { rhythm_detail: "", ventilator_spo2: null, consciousness_level: undefined, - bp: { - systolic: undefined, - diastolic: undefined, - mean: undefined, - }, - // bed: null, + bp: undefined, }; const initError = Object.assign( @@ -255,7 +250,7 @@ export const DailyRounds = (props: any) => { } return; case "bp": { - const error = BloodPressureValidator(state.form.bp); + const error = state.form.bp && BloodPressureValidator(state.form.bp); if (error) { errors.bp = error; invalidForm = true; @@ -351,7 +346,7 @@ export const DailyRounds = (props: any) => { if (state.form.rounds_type !== "VENTILATOR") { data = { ...data, - bp: state.form.bp ?? {}, + bp: state.form.bp, pulse: state.form.pulse ?? null, resp: state.form.resp ?? null, temperature: state.form.temperature ?? null, diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index b1098dcbbc0..48b01eeed3f 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -277,11 +277,10 @@ export const DailyRoundTypes = [ "TELEMEDICINE", ] as const; -export interface BloodPressure { - diastolic?: number; - mean?: number; +export type BloodPressure = { systolic?: number; -} + diastolic?: number; +}; export interface IPainScale { description: string; diff --git a/src/Components/Scribe/formDetails.ts b/src/Components/Scribe/formDetails.ts index 6300ac7e3c2..c889dcf2558 100644 --- a/src/Components/Scribe/formDetails.ts +++ b/src/Components/Scribe/formDetails.ts @@ -129,9 +129,9 @@ const DAILY_ROUND_FORM_SCRIBE_DATA: Field[] = [ { friendlyName: "bp", id: "bp", - default: { systolic: null, diastolic: null, mean: null }, + default: { systolic: null, diastolic: null }, type: "{ systolic?: number, diastolic?: number }", - example: "{ systolic: 120 }", + example: "{ systolic: 120, diastolic: 90 }", description: "An object to store the blood pressure of the patient. It may contain two integers, systolic and diastolic.", validator: (value) => { diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index b0316e4d98c..fb36e6a07f8 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -71,7 +71,6 @@ "date_of_positive_covid_19_swab": "Date of Positive Covid 19 Swab", "patient_no": "OP/IP No", "date_of_admission": "Date of Admission", - "india_1": "India", "unique_id": "Unique Id", "date_and_time": "Date and Time", "facility_type": "Facility type", diff --git a/src/Locale/en/LogUpdate.json b/src/Locale/en/LogUpdate.json index 2e94643fd91..386415d173e 100644 --- a/src/Locale/en/LogUpdate.json +++ b/src/Locale/en/LogUpdate.json @@ -32,7 +32,7 @@ "ROUNDS_TYPE__AUTOMATED": "Virtual Nursing Assistant", "ROUNDS_TYPE__TELEMEDICINE": "Tele-medicine Log", "RESPIRATORY_SUPPORT_SHORT__UNKNOWN": "None", - "RESPIRATORY_SUPPORT_SHORT__OXYGEN_SUPPORT": "O2 Support", + "RESPIRATORY_SUPPORT_SHORT__OXYGEN_SUPPORT": "O₂ Support", "RESPIRATORY_SUPPORT_SHORT__NON_INVASIVE": "NIV", "RESPIRATORY_SUPPORT_SHORT__INVASIVE": "IV", "RESPIRATORY_SUPPORT__UNKNOWN": "None", @@ -138,7 +138,6 @@ "HEARTBEAT_RHYTHM__REGULAR": "Regular", "HEARTBEAT_RHYTHM__IRREGULAR": "Irregular", "HEARTBEAT_RHYTHM__UNKNOWN": "Unknown", - "blood_pressure": "Blood Pressure", "map_acronym": "M.A.P.", "systolic": "Systolic", "diastolic": "Diastolic", @@ -146,11 +145,11 @@ "pain_chart_description": "Mark region and intensity of pain", "bradycardia": "Bradycardia", "tachycardia": "Tachycardia", + "vitals": "Vitals", "procedures_select_placeholder": "Select procedures to add details", "oral_issue_for_non_oral_nutrition_route_error": "Can be specified only if nutrition route is set to Oral", "routine": "Routine", "bladder": "Bladder", "nutrition": "Nutrition", - "vitals": "Vitals", "nursing_care": "Nursing Care" } \ No newline at end of file diff --git a/src/Locale/hi/LogUpdate.json b/src/Locale/hi/LogUpdate.json index 176886c855b..9adc6a1e093 100644 --- a/src/Locale/hi/LogUpdate.json +++ b/src/Locale/hi/LogUpdate.json @@ -56,7 +56,6 @@ "HEARTBEAT_RHYTHM__REGULAR": "नियमित", "HEARTBEAT_RHYTHM__IRREGULAR": "अनियमित", "HEARTBEAT_RHYTHM__UNKNOWN": "अज्ञात", - "blood_pressure": "रक्तचाप", "map_acronym": "मानचित्र", "systolic": "सिस्टोलिक", "diastolic": "डायस्टोलिक", diff --git a/src/Locale/kn/LogUpdate.json b/src/Locale/kn/LogUpdate.json index 25e4ee4623e..188b02c3925 100644 --- a/src/Locale/kn/LogUpdate.json +++ b/src/Locale/kn/LogUpdate.json @@ -56,7 +56,6 @@ "HEARTBEAT_RHYTHM__REGULAR": "ನಿಯಮಿತ", "HEARTBEAT_RHYTHM__IRREGULAR": "ಅನಿಯಮಿತ", "HEARTBEAT_RHYTHM__UNKNOWN": "ಅಜ್ಞಾತ", - "blood_pressure": "ರಕ್ತದೊತ್ತಡ", "map_acronym": "ನಕ್ಷೆ", "systolic": "ಸಿಸ್ಟೊಲಿಕ್", "diastolic": "ಡಯಾಸ್ಟೊಲಿಕ್", diff --git a/src/Locale/ml/LogUpdate.json b/src/Locale/ml/LogUpdate.json index d2503cd8d34..f327199d509 100644 --- a/src/Locale/ml/LogUpdate.json +++ b/src/Locale/ml/LogUpdate.json @@ -56,7 +56,6 @@ "HEARTBEAT_RHYTHM__REGULAR": "പതിവ്", "HEARTBEAT_RHYTHM__IRREGULAR": "ക്രമരഹിതം", "HEARTBEAT_RHYTHM__UNKNOWN": "അജ്ഞാതം", - "blood_pressure": "രക്തസമ്മർദ്ദം", "map_acronym": "മാപ്പ്", "systolic": "സിസ്റ്റോളിക്", "diastolic": "ഡയസ്റ്റോളിക്", diff --git a/src/Locale/ta/LogUpdate.json b/src/Locale/ta/LogUpdate.json index 61a52f69d48..668dbd54a46 100644 --- a/src/Locale/ta/LogUpdate.json +++ b/src/Locale/ta/LogUpdate.json @@ -56,7 +56,6 @@ "HEARTBEAT_RHYTHM__REGULAR": "வழக்கமான", "HEARTBEAT_RHYTHM__IRREGULAR": "ஒழுங்கற்ற", "HEARTBEAT_RHYTHM__UNKNOWN": "தெரியவில்லை", - "blood_pressure": "இரத்த அழுத்தம்", "map_acronym": "வரைபடம்", "systolic": "சிஸ்டாலிக்", "diastolic": "டயஸ்டாலிக்", From 7a8241629a93ecad57a75d87e5f1b8c06b8db8a6 Mon Sep 17 00:00:00 2001 From: Nithin Date: Sat, 21 Sep 2024 10:10:00 +0530 Subject: [PATCH 03/30] Removed hacks for loading investigations in Consultation Form, updated type definitions (#8416) --------- Co-authored-by: rithviknishad --- src/Components/Facility/ConsultationForm.tsx | 4 +--- .../DailyRounds/LogUpdateCardAttribute.tsx | 2 +- src/Components/Facility/models.tsx | 3 --- src/Components/Patient/DailyRoundListDetails.tsx | 10 +++------- src/Components/Patient/models.tsx | 1 - 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/src/Components/Facility/ConsultationForm.tsx b/src/Components/Facility/ConsultationForm.tsx index 2cfe9c8f4df..06b50d69912 100644 --- a/src/Components/Facility/ConsultationForm.tsx +++ b/src/Components/Facility/ConsultationForm.tsx @@ -404,9 +404,7 @@ export const ConsultationForm = ({ facilityId, patientId, id }: Props) => { cause_of_death: data?.discharge_notes || "", death_datetime: data?.death_datetime || "", death_confirmed_doctor: data?.death_confirmed_doctor || "", - InvestigationAdvice: Array.isArray(data.investigation) - ? data.investigation - : [], + InvestigationAdvice: data.investigation ?? [], diagnoses: data.diagnoses?.sort( (a: ConsultationDiagnosis, b: ConsultationDiagnosis) => ConditionVerificationStatuses.indexOf(a.verification_status) - diff --git a/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx b/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx index c1b5fec3c8c..7e58f96069c 100644 --- a/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx +++ b/src/Components/Facility/Consultations/DailyRounds/LogUpdateCardAttribute.tsx @@ -66,7 +66,7 @@ const LogUpdateCardAttribute = ({
- {t(attributeValue)} + {t(attributeValue as string)}
); diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index a5455cb29bd..723f0af1946 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -11,7 +11,6 @@ import { RouteToFacility } from "../Common/RouteToFacilitySelect"; import { InvestigationType } from "../Common/prescription-builder/InvestigationBuilder"; import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; -import { NormalPrescription, PRNPrescription } from "../Medicine/models"; import { AssignedToObjectModel, BloodPressure, @@ -133,8 +132,6 @@ export interface ConsultationModel { created_date?: string; discharge_date?: string; new_discharge_reason?: (typeof DISCHARGE_REASONS)[number]["id"]; - discharge_prescription?: NormalPrescription; - discharge_prn_prescription?: PRNPrescription; discharge_notes?: string; examination_details?: string; history_of_present_illness?: string; diff --git a/src/Components/Patient/DailyRoundListDetails.tsx b/src/Components/Patient/DailyRoundListDetails.tsx index fbf001427dd..eb7eda62064 100644 --- a/src/Components/Patient/DailyRoundListDetails.tsx +++ b/src/Components/Patient/DailyRoundListDetails.tsx @@ -16,13 +16,9 @@ export const DailyRoundListDetails = (props: any) => { const { loading: isLoading } = useQuery(routes.getDailyReport, { pathParams: { consultationId, id }, - onResponse: ({ res, data }) => { - if (res && data) { - const tdata: DailyRoundsModel = { - ...data, - medication_given: data.medication_given ?? [], - }; - setDailyRoundListDetails(tdata); + onResponse: ({ data }) => { + if (data) { + setDailyRoundListDetails(data); } }, }); diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 48b01eeed3f..cf35680de11 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -310,7 +310,6 @@ export interface DailyRoundsModel { physical_examination_info?: string; other_details?: string; consultation?: number; - medication_given?: Array; action?: string; review_interval?: number; id?: string; From e14183bebc1f1dacd6057ed6a831457f75886ac6 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 23 Sep 2024 13:03:52 +0530 Subject: [PATCH 04/30] add JWKS_BASE64 for cypress workflow (#8589) --- .github/workflows/cypress.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 14a15d30ee8..42a39a6c3b7 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -45,9 +45,12 @@ jobs: cd care echo DISABLE_RATELIMIT=True >> docker/.prebuilt.env echo "CORS_ALLOWED_ORIGINS=\"[\\\"http://localhost:4000\\\"]\"" >> docker/.prebuilt.env + echo JWKS_BASE64=\"$JWKS_BASE64\" >> docker/.prebuilt.env make docker_config_file=docker-compose.pre-built.yaml up make docker_config_file=docker-compose.pre-built.yaml load-dummy-data cd .. + env: + JWKS_BASE64: ${{ secrets.JWKS_BASE64 }} - name: Wait for care to be up ♻ uses: nick-fields/retry@v2 From 53ed1623380f70d57ad5bf3c7303e9d7f32f1a92 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Mon, 23 Sep 2024 14:04:28 +0530 Subject: [PATCH 05/30] fix cypress workflow on forked prs (#8591) --- .github/runner-files/jwks.b64.txt | 1 + .github/workflows/cypress.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 .github/runner-files/jwks.b64.txt diff --git a/.github/runner-files/jwks.b64.txt b/.github/runner-files/jwks.b64.txt new file mode 100644 index 00000000000..189ca3ec08e --- /dev/null +++ b/.github/runner-files/jwks.b64.txt @@ -0,0 +1 @@ +eyJrZXlzIjogW3sibiI6ICJ4X21fNGNKQ3NHTHN4WkFIa2VCbFZQa2ZqNFNSckdHN3UySERFM3VLX3dFNERhTWhHQ2lxTXFsaTFDM2pxSE5JTVhuWV9ab1M5R3pHbnJpdGg1UUZGVDlLMGtBdF9YaXBJNnV1djcwOWtOV2FFNXZrYks4VlFRcFd2UFp4NUJSeTBGay0wU2lxZG1xOTNJRXdUTnNTLUpETnRXQm5VX0F1cjU0UXptQmI3SmhfOGttRDAtaHhROVZVejRpSUU3QTlySU5vQXNHSHhfdGtuNXd6YmpPR3F2Q3JqRi1RWXo0OGJXVkZzVVliNkFqUlFrZER2RWVwQlpNSHZsNVUxQlZxOVdRTTJGTmRUR0tJb3ZYeDRuTDFybUVONHpxbEpqWmc2bEZiODVuOUhkc01VblFiSXhkUjVlVl8wWmNVaHBrQWc0NldrejRZWkxMNm5NaGR0RmFTMXciLCAiZSI6ICJBUUFCIiwgImQiOiAiRXo4T3dDZ2xxZnREWlhwSXVEbjhGck1KWGhNNHZfb0NDdlZNUko1QjBPd3BuR3BrWDRKZWF4VFJYYkZ5OVQzdkowX2VXZjRQcl9XZUloMk5HZnpkaGw5NmtJUzd5R2JxQkhSY0U3a2ZhVWFkbHlDTVdnZDV5TEk1aWVOQUw5N2w4X1o2N0w5NHRIX3VlUF80Q1pXV0hGVTNieXJ4bHVzSld6NmZ5SFVPczlVSDFxV284V1RNOVp0RndqV0cxOWpkUXZ1RkQ0Z0x4UEJYZEk5ZV9zdTNwbmZHR0ZkN0xfMDFwcEFkQ2Y5eHhNTWFEel8wZ0xXM0NENFhnWU1rS2d3eDdKWlVlN0VaWFhyU0lETzFZN202MmF4NEpZRU5pSDFiaVlwQk15dmVfM3FzdnRQOWR4eVg2VkFROGZFNVdnOTBCcThsd1F0NzROWmgzMHhDY0dDNHFRIiwgInAiOiAiOXhXeXNjbVExT1FRcEptWmkzZDR5Tk5qT29Ja3lQVDRyZVRBNlJJdW5PSjVDUVFiVE1BVlpzUmsxa05rQ2R1cGVQU1ZnNWVXNktpOXZIV1M3b2NnclVEYzJTRkw0RWtNY21UR2lHRnh1YVIzNHRMTXpUQlJEVVFxZnR6WGxraGRJWmdKenJCT2h4NEEyZ1FQTnRrNkNVSEJWRGRncng0RmR3c21SaElSOU9rIiwgInEiOiAienpEb2FvclpqZ2ZPeDVHckFJZ3R3SGVhM29vQTlqbkFSeDdvM1V2bHprY3p1eC1DY1JORGppS3duaGxzVEROWFFWRTBXUlpWa0ZTQ0JVU3JSS1dLd0JGcnFVQzhlMkxwQVpBb0ZTQ0dqdllIMi1hdGUxeEhxc0NGY09OVlVYM1JXcFd4OFQ4RGhSNGpfaTRKN3g0d2VHc1hkRHRpelY2eHlZMmNHSjltY2I4IiwgImRwIjogInpUV3FLZHA4ZlRQRlZzOXpKTV9lOHZ3TnA2UTdKT1BBUGJ5Rk00MjBSUHdiQmdfeEZIZGJ6dlJCdzJwSkJaNzRTOHJtLWxuR0xna25QQVJ5T2NUa3NMXzBMQ2xwT1NleVBMZlI0NmI2cXZJYjE3aTMtNXFyVmxkTTZfeEMyVF9VaVhnYWZSMFV1MGVCOFlfNWl0WXpTMGpmWmpCd0RrRGl6UkhuZ2I2MFJ6RSIsICJkcSI6ICJYZHdoSGNyaV9ZV3A5aHlXWV9wTkI2am5Qck16OWxkNU5IN2JMUTBxQVFXZWVNR3dmUHNtR21pNnJCU0dUQXJpRjFQckxBU0RKSXcwRHFEcUdZSUkxalBPR3ZHWnNTZkF1SldPb3V1R0taTnBRZ1JCU09Zb0RVR0Q4Zno2ZEoxVHp2NkxpdWRwOTg4TXJTUThHZGdLU3pMd2dCWTdEeUE3MkR2UG9CUHQtODgiLCAicWkiOiAiSlF0UUJERXdnQVVMcWJFRWoybmdyXy02aV9pUmdRWmJTQ0hXNGdpZG5fdHlwdUJWS2R0ZW1jT3J6M3NnTnk0ekZrZElTbUZfbmdnbFlJb080TjNza1NNUXdGY1B0S1kzWUVlT2ZGNzMzRDZaTVJtSTFTby12QU54YVBDUkREbnUwTWk1TnUzbS02SkhGSFF5RDU2ZTFsQUlwUS1lakpuTWR6MXQ0aUplbEFzIiwgImt0eSI6ICJSU0EiLCAia2lkIjogIlhjckcyVS0tR3B4SVhORXpKOWNWREdRaEUzeXlIamREMDN1aDZmYXpRX2siLCAiYWxnIjogIlJTMjU2In1dfQ== \ No newline at end of file diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 42a39a6c3b7..500dbd92be0 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -45,7 +45,7 @@ jobs: cd care echo DISABLE_RATELIMIT=True >> docker/.prebuilt.env echo "CORS_ALLOWED_ORIGINS=\"[\\\"http://localhost:4000\\\"]\"" >> docker/.prebuilt.env - echo JWKS_BASE64=\"$JWKS_BASE64\" >> docker/.prebuilt.env + echo JWKS_BASE64=\"$(cat ../.github/runner-files/jwks.b64.txt)\" >> docker/.prebuilt.env make docker_config_file=docker-compose.pre-built.yaml up make docker_config_file=docker-compose.pre-built.yaml load-dummy-data cd .. From fc64a14b550250802616deb48a00d85106a77078 Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Mon, 23 Sep 2024 15:04:14 +0530 Subject: [PATCH 06/30] HCX Plug (#8420) --- cypress.config.ts | 1 + cypress/e2e/assets_spec/AssetHomepage.cy.ts | 1 + .../patient_spec/PatientPrescription.cy.ts | 1 + .../patient_spec/PatientRegistration.cy.ts | 55 ++- cypress/e2e/users_spec/UsersCreation.cy.ts | 1 + cypress/e2e/users_spec/UsersManage.cy.ts | 1 - .../pageobject/Patient/PatientInsurance.ts | 42 ++- cypress/pageobject/Users/ManageUserPage.ts | 10 +- public/config.json | 28 -- public/env.json | 0 src/CAREUI/icons/CareIcon.tsx | 3 +- src/Common/hooks/useAsyncOptions.ts | 114 ------ .../PMJAYProcedurePackageAutocomplete.tsx | 35 +- .../Facility/ConsultationClaims.tsx | 70 ++-- src/Components/Facility/DischargeModal.tsx | 76 ++-- src/Components/Facility/models.tsx | 10 +- src/Components/Files/AudioCaptureDialog.tsx | 11 +- src/Components/Files/CameraCaptureDialog.tsx | 4 +- src/Components/Files/FileUpload.tsx | 10 +- .../Form/FormFields/Autocomplete.tsx | 1 + src/Components/HCX/ClaimCard.tsx | 55 +++ src/Components/HCX/ClaimCardCommunication.tsx | 300 +++++++++++++++ ...{ClaimDetailCard.tsx => ClaimCardInfo.tsx} | 84 +++-- src/Components/HCX/ClaimCreatedModal.tsx | 30 +- src/Components/HCX/ClaimsItemsBuilder.tsx | 153 ++++---- src/Components/HCX/CreateClaimCard.tsx | 180 +++++---- .../HCX/InsuranceDetailsBuilder.tsx | 48 +-- src/Components/HCX/InsurerAutocomplete.tsx | 29 +- .../HCX/PatientInsuranceDetailsEditor.tsx | 73 ++-- src/Components/HCX/PolicyEligibilityCheck.tsx | 160 ++++---- src/Components/HCX/models.ts | 26 +- src/Components/Patient/InsuranceDetails.tsx | 21 +- .../Patient/PatientConsentRecords.tsx | 6 +- src/Components/Patient/PatientHome.tsx | 36 +- src/Components/Patient/PatientRegister.tsx | 62 ++-- src/Locale/en/Common.json | 8 +- src/Locale/en/FileUpload.json | 3 +- src/Locale/en/HCX.json | 63 ++++ src/Locale/en/index.js | 6 +- src/Redux/actions.tsx | 83 ----- src/Redux/api.tsx | 342 +++++++++++------- src/Routers/AppRouter.tsx | 2 +- src/Routers/routes/HCXRoutes.tsx | 8 +- src/Utils/useFileUpload.tsx | 195 +++++----- 44 files changed, 1425 insertions(+), 1022 deletions(-) delete mode 100644 public/config.json delete mode 100644 public/env.json delete mode 100644 src/Common/hooks/useAsyncOptions.ts create mode 100644 src/Components/HCX/ClaimCard.tsx create mode 100644 src/Components/HCX/ClaimCardCommunication.tsx rename src/Components/HCX/{ClaimDetailCard.tsx => ClaimCardInfo.tsx} (67%) create mode 100644 src/Locale/en/HCX.json diff --git a/cypress.config.ts b/cypress.config.ts index f7302869051..0c939ce50b3 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -34,5 +34,6 @@ export default defineConfig({ }, env: { API_URL: process.env.REACT_CARE_API_URL ?? "http://localhost:9000", + ENABLE_HCX: process.env.REACT_ENABLE_HCX ?? false, }, }); diff --git a/cypress/e2e/assets_spec/AssetHomepage.cy.ts b/cypress/e2e/assets_spec/AssetHomepage.cy.ts index 0f6bfe4da2c..f462bda4327 100644 --- a/cypress/e2e/assets_spec/AssetHomepage.cy.ts +++ b/cypress/e2e/assets_spec/AssetHomepage.cy.ts @@ -98,6 +98,7 @@ describe("Asset Tab", () => { it("Export asset", () => { assetPage.selectassetimportbutton(); + cy.wait(2000); assetPage.selectjsonexportbutton(); assetPage.selectassetimportbutton(); assetPage.selectcsvexportbutton(); diff --git a/cypress/e2e/patient_spec/PatientPrescription.cy.ts b/cypress/e2e/patient_spec/PatientPrescription.cy.ts index a150bfa6e31..de4e121a9db 100644 --- a/cypress/e2e/patient_spec/PatientPrescription.cy.ts +++ b/cypress/e2e/patient_spec/PatientPrescription.cy.ts @@ -113,6 +113,7 @@ describe("Patient Medicine Administration", () => { cy.closeNotification(); // Administer the medicine in edit form patientPrescription.clickAdministerButton(); + cy.wait(2000); patientPrescription.enterAdministerDosage(medicineBaseDosage); patientPrescription.enterAdministerNotes(medicineAdministerNote); cy.submitButton("Administer Medicine"); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index ef92c2e9bd1..589b60f1830 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -8,6 +8,7 @@ import PatientInsurance from "../../pageobject/Patient/PatientInsurance"; import PatientMedicalHistory from "../../pageobject/Patient/PatientMedicalHistory"; const yearOfBirth = "2001"; +const isHCXEnabled = Cypress.env("ENABLE_HCX"); const calculateAge = () => { const currentYear = new Date().getFullYear(); @@ -180,16 +181,21 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneFirstPolicyId, ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_id", - patientOneFirstInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_name", - patientOneFirstInsurerName, - ); + if (isHCXEnabled) { + patientInsurance.selectInsurer("test"); + } else { + patientInsurance.typePatientInsuranceDetail( + patientOneFirstInsuranceId, + "insurer_id", + patientOneFirstInsurerId, + ); + patientInsurance.typePatientInsuranceDetail( + patientOneFirstInsuranceId, + "insurer_name", + patientOneFirstInsurerName, + ); + } + patientInsurance.clickAddInsruanceDetails(); patientInsurance.typePatientInsuranceDetail( patientOneSecondInsuranceId, @@ -201,16 +207,21 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneSecondPolicyId, ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_id", - patientOneSecondInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_name", - patientOneSecondInsurerName, - ); + if (isHCXEnabled) { + patientInsurance.selectInsurer("Care"); + } else { + patientInsurance.typePatientInsuranceDetail( + patientOneSecondInsuranceId, + "insurer_id", + patientOneSecondInsurerId, + ); + patientInsurance.typePatientInsuranceDetail( + patientOneSecondInsuranceId, + "insurer_name", + patientOneSecondInsurerName, + ); + } + patientPage.clickUpdatePatient(); cy.wait(3000); patientPage.verifyPatientUpdated(); @@ -239,7 +250,9 @@ describe("Patient Creation with consultation", () => { patientOneFirstPolicyId, patientOneFirstInsurerId, patientOneFirstInsurerName, + isHCXEnabled, ); + patientInsurance.clickPatientInsuranceViewDetail(); cy.wait(3000); patientInsurance.verifyPatientPolicyDetails( @@ -247,12 +260,14 @@ describe("Patient Creation with consultation", () => { patientOneFirstPolicyId, patientOneFirstInsurerId, patientOneFirstInsurerName, + isHCXEnabled, ); patientInsurance.verifyPatientPolicyDetails( patientOneSecondSubscriberId, patientOneSecondPolicyId, patientOneSecondInsurerId, patientOneSecondInsurerName, + isHCXEnabled, ); }); diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index cc3a8250971..e24fdeca9e2 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -183,6 +183,7 @@ describe("User Creation", () => { it("create new user form throwing mandatory field error", () => { userCreationPage.clickElementById("addUserButton"); userCreationPage.clickElementById("submit"); + cy.wait(2000); userCreationPage.verifyErrorMessages(EXPECTED_ERROR_MESSAGES); }); diff --git a/cypress/e2e/users_spec/UsersManage.cy.ts b/cypress/e2e/users_spec/UsersManage.cy.ts index 9c339f4b8e3..0d3a757df0f 100644 --- a/cypress/e2e/users_spec/UsersManage.cy.ts +++ b/cypress/e2e/users_spec/UsersManage.cy.ts @@ -132,7 +132,6 @@ describe("Manage User", () => { manageUserPage.clickLinkFacility(); manageUserPage.clickUnlinkFacilityButton(); manageUserPage.clickSubmit(); - manageUserPage.assertnotLinkedFacility; manageUserPage.linkedfacilitylistnotvisible(); manageUserPage.clickCloseSlideOver(); // Go to particular facility doctor connect and all user-id are reflected based on there access diff --git a/cypress/pageobject/Patient/PatientInsurance.ts b/cypress/pageobject/Patient/PatientInsurance.ts index 60eaefffa44..bdd571e9d0c 100644 --- a/cypress/pageobject/Patient/PatientInsurance.ts +++ b/cypress/pageobject/Patient/PatientInsurance.ts @@ -9,6 +9,34 @@ class PatientInsurance { }); } + selectInsurer(insurer: string) { + cy.intercept("GET", "**/api/v1/hcx/payors/**", { + statusCode: 200, + body: [ + { + name: "test payor 2", + code: "testpayor2.swasthmock@swasth-hcx-staging", + }, + { + name: "Care Payor", + code: "khavinshankar.gmail@swasth-hcx-staging", + }, + { + name: "Alliance", + code: "hcxdemo.yopmail@swasth-hcx-staging", + }, + ], + }).as("getInsurer"); + cy.get("[name='insurer']") + .last() + .click() + .type(insurer) + .then(() => { + cy.wait("@getInsurer"); + cy.get("[role='option']").contains(insurer).click(); + }); + } + clickPatientInsuranceViewDetail() { cy.get("#insurance-view-details").scrollIntoView(); cy.get("#insurance-view-details").click(); @@ -18,13 +46,21 @@ class PatientInsurance { cy.get("[data-testid=add-insurance-button]").click(); } - verifyPatientPolicyDetails(subscriberId, policyId, insurerId, insurerName) { + verifyPatientPolicyDetails( + subscriberId, + policyId, + insurerId, + insurerName, + isHcxEnabled, + ) { cy.get("[data-testid=patient-details]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(subscriberId); expect($dashboard).to.contain(policyId); - expect($dashboard).to.contain(insurerId); - expect($dashboard).to.contain(insurerName); + if (!isHcxEnabled) { + expect($dashboard).to.contain(insurerId); + expect($dashboard).to.contain(insurerName); + } }); } } diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index c57ecbf172f..41d41d218d4 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -142,8 +142,14 @@ export class ManageUserPage { } assertDoctorConnectVisibility(realName) { - cy.get("#doctor-connect-home-doctor").should("contain.text", realName); - cy.get("#doctor-connect-remote-doctor").should("contain.text", realName); + cy.get('*[id="doctor-connect-home-doctor"]').should( + "contain.text", + realName, + ); + cy.get('*[id="doctor-connect-remote-doctor"]').should( + "contain.text", + realName, + ); } assertVideoConnectLink(docName: string, link: string) { diff --git a/public/config.json b/public/config.json deleted file mode 100644 index 678e218070c..00000000000 --- a/public/config.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "github_url": "https://github.com/ohcnetwork", - "ohcn_url": "https://ohc.network?ref=care", - "site_url": "care.ohc.network", - "analytics_server_url": "https://plausible.10bedicu.in", - "header_logo": { - "light": "https://cdn.ohc.network/header_logo.png", - "dark": "https://cdn.ohc.network/header_logo.png" - }, - "main_logo": { - "light": "https://cdn.ohc.network/light-logo.svg", - "dark": "https://cdn.ohc.network/black-logo.svg" - }, - "gmaps_api_key": "AIzaSyDsBAc3y7deI5ZO3NtK5GuzKwtUzQNJNUk", - "gov_data_api_key": "579b464db66ec23bdd000001cdd3946e44ce4aad7209ff7b23ac571b", - "recaptcha_site_key": "6LdvxuQUAAAAADDWVflgBqyHGfq-xmvNJaToM0pN", - "sentry_dsn": "https://8801155bd0b848a09de9ebf6f387ebc8@sentry.io/5183632", - "sentry_environment": "staging", - "kasp_enabled": false, - "kasp_string": "KASP", - "kasp_full_string": "Karunya Arogya Suraksha Padhathi", - "sample_format_asset_import": "/asset-import-template.xlsx", - "sample_format_external_result_import": "/External-Results-Template.csv", - "enable_abdm": true, - "enable_hcx": false, - "enable_scribe": false, - "min_encounter_date": "2020-01-01" -} \ No newline at end of file diff --git a/public/env.json b/public/env.json deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/CAREUI/icons/CareIcon.tsx b/src/CAREUI/icons/CareIcon.tsx index 90a59d972cb..3bb67629bc2 100644 --- a/src/CAREUI/icons/CareIcon.tsx +++ b/src/CAREUI/icons/CareIcon.tsx @@ -1,8 +1,7 @@ +import iconData from "./UniconPaths.json"; import { transformIcons } from "./icon"; import { useEffect } from "react"; -import iconData from "./UniconPaths.json"; - export type IconName = keyof typeof iconData; export interface CareIconProps { diff --git a/src/Common/hooks/useAsyncOptions.ts b/src/Common/hooks/useAsyncOptions.ts deleted file mode 100644 index a81209f34f0..00000000000 --- a/src/Common/hooks/useAsyncOptions.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { debounce } from "lodash-es"; -import { useMemo, useState } from "react"; -import { useDispatch } from "react-redux"; -import { mergeQueryOptions } from "../../Utils/utils"; - -interface IUseAsyncOptionsArgs { - debounceInterval?: number; - queryResponseExtractor?: (data: any) => any; -} - -/** - * Deprecated. This is no longer needed as `useQuery` with `mergeQueryOptions` - * can be reused for this. - * - * Hook to implement async autocompletes with ease and typesafety. - * - * See `DiagnosisSelectFormField` for usage. - * - * **Example usage:** - * ```jsx - * const { fetchOptions, isLoading, options } = useAsyncOptions("id"); - * - * return ( - * fetchOptions(action({ query }))} - * optionValue={(option) => option} - * ... - * /> - * ); - * ``` - */ -export function useAsyncOptions>( - uniqueKey: keyof T, - args?: IUseAsyncOptionsArgs, -) { - const dispatch = useDispatch(); - const [queryOptions, setQueryOptions] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - const fetchOptions = useMemo( - () => - debounce(async (action: any) => { - setIsLoading(true); - const res = await dispatch(action); - if (res?.data) - setQueryOptions( - args?.queryResponseExtractor?.(res.data) ?? (res.data as T[]), - ); - setIsLoading(false); - }, args?.debounceInterval ?? 300), - [dispatch, args?.debounceInterval], - ); - - const mergeValueWithQueryOptions = (selected?: T[]) => { - return mergeQueryOptions( - selected ?? [], - queryOptions, - (obj) => obj[uniqueKey], - ); - }; - - return { - /** - * Merges query options and selected options. - * - * **Example usage:** - * ```jsx - * const { isLoading } = useAsyncOptions("id"); - * - * - * ``` - */ - fetchOptions, - - /** - * Merges query options and selected options. - * - * **Example usage:** - * ```jsx - * const { options } = useAsyncOptions("id"); - * - * fetchOptions(action({ query }))} - * ... - * /> - * ``` - */ - isLoading, - - /** - * Merges query options and selected options. - * - * **Example usage:** - * ```jsx - * const { options } = useAsyncOptions("id"); - * - * - * ``` - */ - options: mergeValueWithQueryOptions, - }; -} diff --git a/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx b/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx index 1fee5599220..0da23ac3d18 100644 --- a/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx +++ b/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx @@ -1,13 +1,16 @@ -import { useAsyncOptions } from "../../Common/hooks/useAsyncOptions"; -import { listPMJYPackages } from "../../Redux/actions"; -import { Autocomplete } from "../Form/FormFields/Autocomplete"; -import FormField from "../Form/FormFields/FormField"; import { FormFieldBaseProps, useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; -type PMJAYPackageItem = { +import { Autocomplete } from "../Form/FormFields/Autocomplete"; +import FormField from "../Form/FormFields/FormField"; +import routes from "../../Redux/api"; +import { useState } from "react"; +import useQuery from "../../Utils/request/useQuery"; +import { mergeQueryOptions } from "../../Utils/utils"; + +export type PMJAYPackageItem = { name?: string; code?: string; price?: number; @@ -19,8 +22,11 @@ type Props = FormFieldBaseProps; export default function PMJAYProcedurePackageAutocomplete(props: Props) { const field = useFormFieldPropsResolver(props); - const { fetchOptions, isLoading, options } = - useAsyncOptions("code"); + const [query, setQuery] = useState(""); + + const { data, loading } = useQuery(routes.hcx.claims.listPMJYPackages, { + query: { query, limit: 10 }, + }); return ( @@ -30,19 +36,20 @@ export default function PMJAYProcedurePackageAutocomplete(props: Props) { disabled={field.disabled} value={field.value} onChange={field.handleChange} - options={options(field.value ? [field.value] : []).map((o) => { - // TODO: update backend to return price as number instead - return { + options={mergeQueryOptions( + (field.value ? [field.value] : []).map((o) => ({ ...o, price: o.price && parseFloat(o.price?.toString().replaceAll(",", "")), - }; - })} + })), + data ?? [], + (obj) => obj.code, + )} optionLabel={optionLabel} optionDescription={optionDescription} optionValue={(option) => option} - onQuery={(query) => fetchOptions(listPMJYPackages(query))} - isLoading={isLoading} + onQuery={setQuery} + isLoading={loading} /> ); diff --git a/src/Components/Facility/ConsultationClaims.tsx b/src/Components/Facility/ConsultationClaims.tsx index 3022b7a47a2..41535ce1853 100644 --- a/src/Components/Facility/ConsultationClaims.tsx +++ b/src/Components/Facility/ConsultationClaims.tsx @@ -1,15 +1,16 @@ -import { useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { HCXActions } from "../../Redux/actions"; -import PageTitle from "../Common/PageTitle"; -import ClaimDetailCard from "../HCX/ClaimDetailCard"; +import * as Notification from "../../Utils/Notifications"; + +import ClaimCard from "../HCX/ClaimCard"; import CreateClaimCard from "../HCX/CreateClaimCard"; -import { HCXClaimModel } from "../HCX/models"; -import { useMessageListener } from "../../Common/hooks/useMessageListener"; +import PageTitle from "../Common/PageTitle"; import { navigate } from "raviger"; -import * as Notification from "../../Utils/Notifications"; +import routes from "../../Redux/api"; +import { useMessageListener } from "../../Common/hooks/useMessageListener"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; -interface Props { +export interface IConsultationClaimsProps { facilityId: string; patientId: string; consultationId: string; @@ -19,33 +20,34 @@ export default function ConsultationClaims({ facilityId, consultationId, patientId, -}: Props) { - const dispatch = useDispatch(); - const [claims, setClaims] = useState(); +}: IConsultationClaimsProps) { + const { t } = useTranslation(); + const [isCreateLoading, setIsCreateLoading] = useState(false); - const fetchClaims = useCallback(async () => { - const res = await dispatch( - HCXActions.claims.list({ + const { data: claimsResult, refetch: refetchClaims } = useQuery( + routes.hcx.claims.list, + { + query: { ordering: "-modified_date", consultation: consultationId, - }), - ); + }, + onResponse: (res) => { + if (!isCreateLoading) return; - if (res.data && res.data.results) { - setClaims(res.data.results); - if (isCreateLoading) - Notification.Success({ msg: "Fetched Claim Approval Results" }); - } else { - if (isCreateLoading) - Notification.Success({ msg: "Error Fetched Claim Approval Results" }); - } - setIsCreateLoading(false); - }, [dispatch, consultationId]); + if (res.data?.results) { + Notification.Success({ + msg: t("claim__fetched_claim_approval_results"), + }); + return; + } - useEffect(() => { - fetchClaims(); - }, [fetchClaims]); + Notification.Error({ + msg: t("claim__error_fetching_claim_approval_results"), + }); + }, + }, + ); useMessageListener((data) => { if ( @@ -53,14 +55,14 @@ export default function ConsultationClaims({ (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && data.message === "success" ) { - fetchClaims(); + refetchClaims(); } }); return (
{ navigate( @@ -81,9 +83,9 @@ export default function ConsultationClaims({
- {claims?.map((claim) => ( + {claimsResult?.results.map((claim) => (
- +
))}
diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx index ac86ff02c55..5957b3e812f 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -1,33 +1,34 @@ import * as Notification from "../../Utils/Notifications"; import { Cancel, Submit } from "../Common/components/ButtonV2"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import ClaimDetailCard from "../HCX/ClaimDetailCard"; +import CircularProgress from "../Common/components/CircularProgress"; +import ClaimCard from "../HCX/ClaimCard"; import { ConsultationModel } from "./models"; import CreateClaimCard from "../HCX/CreateClaimCard"; import { DISCHARGE_REASONS } from "../../Common/constants"; import DialogModal from "../Common/Dialog"; +import { FacilityModel } from "./models"; +import { FacilitySelect } from "../Common/FacilitySelect"; +import { FieldError } from "../Form/FieldValidators"; import { FieldLabel } from "../Form/FormFields/FormField"; -import { HCXActions } from "../../Redux/actions"; import { HCXClaimModel } from "../HCX/models"; +import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; +import dayjs from "../../Utils/dayjs"; import { dischargePatient } from "../../Redux/actions"; import { useDispatch } from "react-redux"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; -import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; -import CircularProgress from "../Common/components/CircularProgress"; -import { FacilitySelect } from "../Common/FacilitySelect"; -import { FacilityModel } from "./models"; -import dayjs from "../../Utils/dayjs"; -import { FieldError } from "../Form/FieldValidators"; +import useQuery from "../../Utils/request/useQuery"; import { useTranslation } from "react-i18next"; import useConfirmedAction from "../../Common/hooks/useConfirmedAction"; import ConfirmDialog from "../Common/ConfirmDialog"; import careConfig from "@careConfig"; +import routes from "../../Redux/api"; interface PreDischargeFormInterface { new_discharge_reason: number | null; @@ -81,6 +82,34 @@ const DischargeModal = ({ const [facility, setFacility] = useState(referred_to); const [errors, setErrors] = useState({}); + const { refetch: refetchLatestClaim } = useQuery(routes.hcx.claims.list, { + query: { + consultation: consultationData.id, + ordering: "-modified_date", + use: "claim", + outcome: "complete", + limit: 1, + }, + onResponse: (res) => { + if (!isCreateClaimLoading) return; + + setIsCreateClaimLoading(false); + + if (res?.data?.results?.length !== 0) { + setLatestClaim(res?.data?.results[0]); + Notification.Success({ + msg: t("claim__fetched_claim_approval_results"), + }); + return; + } + + setLatestClaim(undefined); + Notification.Success({ + msg: t("claim__error_fetching_claim_approval_results"), + }); + }, + }); + useEffect(() => { setPreDischargeForm((prev) => ({ ...prev, @@ -97,38 +126,13 @@ const DischargeModal = ({ const discharge_reason = new_discharge_reason ?? preDischargeForm.new_discharge_reason; - const fetchLatestClaim = useCallback(async () => { - const res = await dispatch( - HCXActions.claims.list({ - ordering: "-modified_date", - use: "claim", - consultation: consultationData.id, - }), - ); - - if (res?.data?.results?.length > 0) { - setLatestClaim(res.data.results[0]); - if (isCreateClaimLoading) - Notification.Success({ msg: "Fetched Claim Approval Results" }); - } else { - setLatestClaim(undefined); - if (isCreateClaimLoading) - Notification.Success({ msg: "Error Fetched Claim Approval Results" }); - } - setIsCreateClaimLoading(false); - }, [consultationData.id, dispatch]); - - useEffect(() => { - fetchLatestClaim(); - }, [fetchLatestClaim]); - useMessageListener((data) => { if ( data.type === "MESSAGE" && (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && data.message === "success" ) { - fetchLatestClaim(); + refetchLatestClaim(); } }); @@ -389,7 +393,7 @@ const DischargeModal = ({

Claim Insurance

{latestClaim ? ( - + ) : ( ; } -// Voluntarily made as `type` for it to achieve type-safety when used with -// `useAsyncOptions` export type ICD11DiagnosisModel = { id: string; label: string; diff --git a/src/Components/Files/AudioCaptureDialog.tsx b/src/Components/Files/AudioCaptureDialog.tsx index d686022a5b6..097de0088aa 100644 --- a/src/Components/Files/AudioCaptureDialog.tsx +++ b/src/Components/Files/AudioCaptureDialog.tsx @@ -8,7 +8,7 @@ import { t } from "i18next"; export interface AudioCaptureDialogProps { show: boolean; onHide: () => void; - onCapture: (file: File) => void; + onCapture: (file: File, fileName: string) => void; autoRecord?: boolean; } @@ -56,14 +56,11 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const handleSubmit = async () => { const response = await fetch(audioURL); const blob = await response.blob(); - const file = new File( - [blob], - `recording_${new Date().toISOString().replaceAll(".", "_").replaceAll(":", "_")}.mp3`, - { type: "audio/mpeg" }, - ); + const fileName = `recording_${new Date().toISOString().replaceAll(".", "_").replaceAll(":", "_")}.mp3`; + const file = new File([blob], fileName, { type: "audio/mpeg" }); resetRecording(); onHide(); - onCapture(file); + onCapture(file, fileName); }; useEffect(() => { diff --git a/src/Components/Files/CameraCaptureDialog.tsx b/src/Components/Files/CameraCaptureDialog.tsx index ee7d7da177c..96de547eebe 100644 --- a/src/Components/Files/CameraCaptureDialog.tsx +++ b/src/Components/Files/CameraCaptureDialog.tsx @@ -9,7 +9,7 @@ import useWindowDimensions from "../../Common/hooks/useWindowDimensions"; export interface CameraCaptureDialogProps { show: boolean; onHide: () => void; - onCapture: (file: File) => void; + onCapture: (file: File, fileName: string) => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { @@ -41,7 +41,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { const myFile = new File([blob], `capture.${extension}`, { type: blob.type, }); - onCapture(myFile); + onCapture(myFile, `capture.${extension}`); }); }; diff --git a/src/Components/Files/FileUpload.tsx b/src/Components/Files/FileUpload.tsx index 2e3d6ea58b4..d827391f650 100644 --- a/src/Components/Files/FileUpload.tsx +++ b/src/Components/Files/FileUpload.tsx @@ -250,15 +250,15 @@ export const FileUpload = (props: FileUploadProps) => { isAuthorized ? ( <>

{UPLOAD_HEADING[type]}

- {fileUpload.file ? ( + {fileUpload.files[0] ? (
- {fileUpload.file.name} + {fileUpload.files[0].name} +
+
+
+ ))} +
+ setInputText(e.value)} + placeholder={t("enter_message")} + rows={1} + className="-mb-3 flex-1" + /> + +
+ +
+ + {t("send_message")} + + + {error && ( +

{error}

+ )} + + ); +} + +interface ICommunicationChatInterfaceProps { + communications: HCXCommunicationModel[]; +} + +function CommunicationChatInterface({ + communications, +}: ICommunicationChatInterfaceProps) { + return ( +
+ {communications?.map((communication) => ( + + ))} +
+ ); +} + +interface ICommunicationChatMessageProps { + communication: HCXCommunicationModel; +} + +function CommunicationChatMessage({ + communication, +}: ICommunicationChatMessageProps) { + const { t } = useTranslation(); + const [attachments, setAttachments] = useState( + null, + ); + const [isFetchingAttachments, setIsFetchingAttachments] = useState(false); + const [isDownloadingAttachment, setIsDownloadingAttachment] = useState(false); + + return ( +
+ {communication.content?.map((message) => ( +

+ {message.data} +

+ ))} + {attachments ? ( +
+ {attachments.length === 0 ? ( +

+ {t("no_attachments_found")} +

+ ) : ( + attachments.map((attachment) => ( +
+
+
+ +
+
+
+

{attachment.name}

+ +
+
+ )) + )} +
+ ) : ( + + )} +
+ ); +} diff --git a/src/Components/HCX/ClaimDetailCard.tsx b/src/Components/HCX/ClaimCardInfo.tsx similarity index 67% rename from src/Components/HCX/ClaimDetailCard.tsx rename to src/Components/HCX/ClaimCardInfo.tsx index ee6671cc90a..1b348a28f4f 100644 --- a/src/Components/HCX/ClaimDetailCard.tsx +++ b/src/Components/HCX/ClaimCardInfo.tsx @@ -1,35 +1,45 @@ import { classNames, formatCurrency, formatDateTime } from "../../Utils/utils"; -import { HCXClaimModel } from "../HCX/models"; + +import { HCXClaimModel } from "./models"; +import { useTranslation } from "react-i18next"; interface IProps { claim: HCXClaimModel; } -export default function ClaimDetailCard({ claim }: IProps) { +const claimStatus = { + PENDING: "pending", + APPROVED: "approved", + REJECTED: "rejected", +}; + +export default function ClaimCardInfo({ claim }: IProps) { + const { t } = useTranslation(); + const status = - claim.outcome === "Processing Complete" + claim.outcome === "Complete" ? claim.error_text - ? "Rejected" - : "Approved" - : "Pending"; + ? claimStatus.REJECTED + : claimStatus.APPROVED + : claimStatus.PENDING; return ( -
-
+ <> +

#{claim.id?.slice(0, 5)}

- Created on{" "} -

-
+
{claim.use && ( {claim.use} @@ -38,39 +48,45 @@ export default function ClaimDetailCard({ claim }: IProps) { - {status} + {t(`claim__status__${status}`)}
-
+

{claim.policy_object?.policy_id || "NA"}

-

Policy ID

+

{t("policy__policy_id")}

{claim.policy_object?.subscriber_id || "NA"}

-

Subscriber ID

+

+ {t("policy__subscriber_id")} +

- {claim.policy_object?.insurer_id || "NA"} + {claim.policy_object?.insurer_id?.split("@").shift() || "NA"}

-

Insurer ID

+

+ {t("policy__insurer_id")} +

{claim.policy_object?.insurer_name || "NA"}

-

Insurer Name

+

+ {t("policy__insurer_name")} +

@@ -81,7 +97,7 @@ export default function ClaimDetailCard({ claim }: IProps) { scope="col" className="py-3.5 pl-6 pr-3 text-left text-sm font-semibold text-secondary-900 sm:pl-0" > - Items + {t("claim__items")} @@ -89,7 +105,7 @@ export default function ClaimDetailCard({ claim }: IProps) { scope="col" className="py-3.5 pl-3 pr-6 text-right text-sm font-semibold text-secondary-900 sm:pr-0" > - Price + {t("claim__item__price")} @@ -115,15 +131,9 @@ export default function ClaimDetailCard({ claim }: IProps) { - Total Claim Amount - - - Total Claim Amount + {t("claim__total_claim_amount")} {claim.total_claim_amount && @@ -135,15 +145,9 @@ export default function ClaimDetailCard({ claim }: IProps) { - Total Amount Approved - - - Total Amount Approved + {t("claim__total_approved_amount")} {claim.total_amount_approved @@ -159,6 +163,6 @@ export default function ClaimDetailCard({ claim }: IProps) { {claim.error_text}
)} -
+ ); } diff --git a/src/Components/HCX/ClaimCreatedModal.tsx b/src/Components/HCX/ClaimCreatedModal.tsx index 6565c0c11be..41d58aa7df4 100644 --- a/src/Components/HCX/ClaimCreatedModal.tsx +++ b/src/Components/HCX/ClaimCreatedModal.tsx @@ -1,12 +1,14 @@ -import { useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXActions } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; -import { Submit } from "../Common/components/ButtonV2"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; import DialogModal from "../Common/Dialog"; import { FileUpload } from "../Files/FileUpload"; import { HCXClaimModel } from "./models"; +import { Submit } from "../Common/components/ButtonV2"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface Props { claim: HCXClaimModel; @@ -15,7 +17,8 @@ interface Props { } export default function ClaimCreatedModal({ claim, ...props }: Props) { - const dispatch = useDispatch(); + const { t } = useTranslation(); + const [isMakingClaim, setIsMakingClaim] = useState(false); const { use } = claim; @@ -23,8 +26,11 @@ export default function ClaimCreatedModal({ claim, ...props }: Props) { const handleSubmit = async () => { setIsMakingClaim(true); - const res = await dispatch(HCXActions.makeClaim(claim.id ?? "")); - if (res.data) { + const { res } = await request(routes.hcx.claims.makeClaim, { + body: { claim: claim.id }, + }); + + if (res?.ok) { Notification.Success({ msg: `${use} requested` }); props.onClose(); } @@ -35,8 +41,8 @@ export default function ClaimCreatedModal({ claim, ...props }: Props) { @@ -44,8 +50,8 @@ export default function ClaimCreatedModal({ claim, ...props }: Props) { )} {isMakingClaim - ? `Requesting ${use === "Claim" ? "Claim" : "Pre-Authorization"}...` - : `Request ${use === "Claim" ? "Claim" : "Pre-Authorization"}`} + ? t("claim__requesting_claim") + : t("claim__request_claim")} } > diff --git a/src/Components/HCX/ClaimsItemsBuilder.tsx b/src/Components/HCX/ClaimsItemsBuilder.tsx index 73ca2305030..d636eb1a528 100644 --- a/src/Components/HCX/ClaimsItemsBuilder.tsx +++ b/src/Components/HCX/ClaimsItemsBuilder.tsx @@ -1,20 +1,24 @@ -import CareIcon from "../../CAREUI/icons/CareIcon"; -import ButtonV2 from "../Common/components/ButtonV2"; -import PMJAYProcedurePackageAutocomplete from "../Common/PMJAYProcedurePackageAutocomplete"; -import AutocompleteFormField from "../Form/FormFields/Autocomplete"; -import FormField, { FieldLabel } from "../Form/FormFields/FormField"; -import TextFormField from "../Form/FormFields/TextFormField"; import { FieldChangeEvent, FormFieldBaseProps, useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; -import { ITEM_CATEGORIES } from "./constants"; +import FormField, { FieldLabel } from "../Form/FormFields/FormField"; + +import AutocompleteFormField from "../Form/FormFields/Autocomplete"; +import ButtonV2 from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXItemModel } from "./models"; +import { ITEM_CATEGORIES } from "./constants"; +import PMJAYProcedurePackageAutocomplete from "../Common/PMJAYProcedurePackageAutocomplete"; +import TextFormField from "../Form/FormFields/TextFormField"; +import { useTranslation } from "react-i18next"; type Props = FormFieldBaseProps; export default function ClaimsItemsBuilder(props: Props) { + const { t } = useTranslation(); + const field = useFormFieldPropsResolver(props); const handleUpdate = (index: number) => { @@ -59,7 +63,7 @@ export default function ClaimsItemsBuilder(props: Props) { >
- Item {index + 1} + {t("claim__item")} {index + 1} {!props.disabled && ( - Delete + {t("remove")} )}
-
+
o.display} optionValue={(o) => o.code} @@ -89,71 +93,66 @@ export default function ClaimsItemsBuilder(props: Props) { disabled={props.disabled} errorClassName="hidden" /> - {obj.category === "HBP" && !obj.id ? ( - <> - - - ) : ( - <> - o.code} - // optionDescription={(o) => o.name || ""} - // optionValue={(o) => o.code} - onChange={handleUpdate(index)} - value={obj.id} - disabled={props.disabled} - errorClassName="hidden" - /> - o.name || o.code} - // optionDescription={(o) => o.code} - // optionValue={(o) => o.name || o.code} - disabled={props.disabled} - errorClassName="hidden" - // options={PROCEDURES} - /> - - handleUpdate(index)({ - name: event.name, - value: parseFloat(event.value), - }) - } - disabled={props.disabled} - errorClassName="hidden" - /> - - )} + +
+ {obj.category === "HBP" && !obj.id ? ( + <> + + + ) : ( + <> + + + + handleUpdate(index)({ + name: event.name, + value: parseFloat(event.value), + }) + } + disabled={props.disabled} + errorClassName="hidden" + /> + + )} +
); diff --git a/src/Components/HCX/CreateClaimCard.tsx b/src/Components/HCX/CreateClaimCard.tsx index 46a02cf7f86..b4701aadf7a 100644 --- a/src/Components/HCX/CreateClaimCard.tsx +++ b/src/Components/HCX/CreateClaimCard.tsx @@ -1,18 +1,22 @@ -import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { getConsultation, HCXActions } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; -import { classNames, formatCurrency } from "../../Utils/utils"; + import ButtonV2, { Submit } from "../Common/components/ButtonV2"; +import { HCXClaimModel, HCXItemModel, HCXPolicyModel } from "./models"; +import { classNames, formatCurrency } from "../../Utils/utils"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ClaimCreatedModal from "./ClaimCreatedModal"; import ClaimsItemsBuilder from "./ClaimsItemsBuilder"; -import { HCXClaimModel, HCXPolicyModel, HCXItemModel } from "./models"; -import HCXPolicyEligibilityCheck from "./PolicyEligibilityCheck"; import DialogModal from "../Common/Dialog"; +import HCXPolicyEligibilityCheck from "./PolicyEligibilityCheck"; import PatientInsuranceDetailsEditor from "./PatientInsuranceDetailsEditor"; -import ClaimCreatedModal from "./ClaimCreatedModal"; import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface Props { consultationId: string; @@ -29,7 +33,8 @@ export default function CreateClaimCard({ isCreating, use = "preauthorization", }: Props) { - const dispatch = useDispatch(); + const { t } = useTranslation(); + const [showAddPolicy, setShowAddPolicy] = useState(false); const [policy, setPolicy] = useState(); const [items, setItems] = useState(); @@ -37,60 +42,66 @@ export default function CreateClaimCard({ const [createdClaim, setCreatedClaim] = useState(); const [use_, setUse_] = useState(use); - useEffect(() => { - async function autoFill() { - const latestApprovedPreAuthsRes = await dispatch( - HCXActions.preauths.list(consultationId), - ); + const { res: consultationRes, data: consultationData } = useQuery( + routes.getConsultation, + { pathParams: { id: consultationId }, prefetch: !!consultationId }, + ); - if (latestApprovedPreAuthsRes.data?.results?.length) { - // TODO: offload outcome filter to server side once payer server is back - const latestApprovedPreAuth = ( - latestApprovedPreAuthsRes.data.results as HCXClaimModel[] - ).find((o) => o.outcome === "Processing Complete"); - if (latestApprovedPreAuth) { - setPolicy(latestApprovedPreAuth.policy_object); - setItems(latestApprovedPreAuth.items ?? []); - return; - } - } - - const res = await dispatch(getConsultation(consultationId as any)); - - if (res.data && Array.isArray(res.data.procedure)) { - setItems( - res.data.procedure.map((obj: ProcedureType) => { - return { - id: obj.procedure, - name: obj.procedure, - price: 0.0, - category: "900000", // provider's packages - }; - }), - ); - } else { - setItems([]); - } + const autoFill = async (policy?: HCXPolicyModel) => { + if (!policy) { + setItems([]); + return; } - autoFill(); - }, [consultationId, dispatch]); + const { res, data: latestApprovedPreAuth } = await request( + routes.hcx.claims.list, + { + query: { + consultation: consultationId, + policy: policy.id, + ordering: "-modified_date", + use: "preauthorization", + outcome: "complete", + limit: 1, + }, + }, + ); + + if (res?.ok && latestApprovedPreAuth?.results.length !== 0) { + setItems(latestApprovedPreAuth?.results[0].items ?? []); + return; + } + if (consultationRes?.ok && Array.isArray(consultationData?.procedure)) { + setItems( + consultationData.procedure.map((obj: ProcedureType) => { + return { + id: obj.procedure ?? "", + name: obj.procedure ?? "", + price: 0.0, + category: "900000", // provider's packages + }; + }), + ); + } else { + setItems([]); + } + }; const validate = () => { if (!policy) { - Notification.Error({ msg: "Please select a policy" }); + Notification.Error({ msg: t("select_policy") }); return false; } - if (policy?.outcome !== "Processing Complete") { - Notification.Error({ msg: "Please select an eligible policy" }); + if (policy?.outcome !== "Complete") { + Notification.Error({ msg: t("select_eligible_policy") }); return false; } if (!items || items.length === 0) { - setItemsError("Please add at least one item"); + setItemsError(t("claim__item__add_at_least_one")); return false; } if (items?.some((p) => !p.id || !p.name || p.price === 0 || !p.category)) { - setItemsError("Please fill all the item details"); + setItemsError(t("claim__item__fill_all_details")); return false; } @@ -102,22 +113,23 @@ export default function CreateClaimCard({ setIsCreating(true); - const res = await dispatch( - HCXActions.claims.create({ + const { res, data } = await request(routes.hcx.claims.create, { + body: { policy: policy?.id, items, consultation: consultationId, - use, - }), - ); + use: use_, + }, + silent: true, + }); - if (res.data) { + if (res?.ok && data) { setItems([]); setItemsError(undefined); setPolicy(undefined); - setCreatedClaim(res.data); + setCreatedClaim(data); } else { - Notification.Error({ msg: "Failed to create pre-authorization" }); + Notification.Error({ msg: t(`claim__failed_to_create_${use_}`) }); } setIsCreating(false); @@ -133,10 +145,10 @@ export default function CreateClaimCard({ /> )} setShowAddPolicy(false)} - description="Add or edit patient's insurance details" + description={t("edit_policy_description")} className="w-full max-w-screen-md" > setShowAddPolicy(false)} /> + {/* Check Insurance Policy Eligibility */}
-
-

- Check Insurance Policy Eligibility -

- setShowAddPolicy(true)} ghost border> +
+

{t("check_policy_eligibility")}

+ setShowAddPolicy(true)} + ghost + border + > - Edit Patient Insurance Details + {t("edit_policy")}
{ + setPolicy(policy); + autoFill(policy); + }} />
{/* Procedures */}
-

Items

+

{t("claim__items")}

- Add Item + {t("claim__add_item")}
- Select a policy to add items + {t("select_policy_to_add_items")} setItems(value)} error={itemsError} /> -
- {"Total Amount: "} +
+ {t("total_amount")} :{" "} {items ? ( {formatCurrency( @@ -208,29 +227,34 @@ export default function CreateClaimCard({
-
+
setUse_(value)} position="below" + className="w-52 max-sm:w-full" optionLabel={(value) => value.label} optionValue={(value) => value.id as "preauthorization" | "claim"} /> {isCreating && } {isCreating - ? `Creating ${use === "claim" ? "Claim" : "Pre-Authorization"}...` - : "Proceed"} + ? t(`claim__creating_${use_}`) + : t(`claim__create_${use_}`)}
diff --git a/src/Components/HCX/InsuranceDetailsBuilder.tsx b/src/Components/HCX/InsuranceDetailsBuilder.tsx index 2c51d3b90d3..ee8816fcf4b 100644 --- a/src/Components/HCX/InsuranceDetailsBuilder.tsx +++ b/src/Components/HCX/InsuranceDetailsBuilder.tsx @@ -4,21 +4,24 @@ import { useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; import FormField, { FieldLabel } from "../Form/FormFields/FormField"; -import { HCXPolicyModel } from "./models"; + import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { HCXPolicyModel } from "./models"; +import InsurerAutocomplete from "./InsurerAutocomplete"; import TextFormField from "../Form/FormFields/TextFormField"; -import { useDispatch } from "react-redux"; -import { HCXActions } from "../../Redux/actions"; import { classNames } from "../../Utils/utils"; -import InsurerAutocomplete from "./InsurerAutocomplete"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { useTranslation } from "react-i18next"; import careConfig from "@careConfig"; type Props = FormFieldBaseProps & { gridView?: boolean }; export default function InsuranceDetailsBuilder(props: Props) { + const { t } = useTranslation(); + const field = useFormFieldPropsResolver(props); - const dispatch = useDispatch(); const handleUpdate = (index: number) => { return (event: FieldChangeEvent) => { @@ -43,9 +46,11 @@ export default function InsuranceDetailsBuilder(props: Props) { const handleRemove = (index: number) => { return () => { field.handleChange( - (props.value || [])?.filter((obj, i) => { + (props.value || [])?.filter(async (obj, i) => { if (obj.id && i === index) { - dispatch(HCXActions.policies.delete(obj.id)); + await request(routes.hcx.policies.delete, { + pathParams: { external_id: obj.id }, + }); } return i !== index; }), @@ -58,7 +63,7 @@ export default function InsuranceDetailsBuilder(props: Props) {
    {props.value?.length === 0 && ( - No insurance details added + {t("no_policy_added")} )} {props.value?.map((policy, index) => ( @@ -93,6 +98,7 @@ const InsuranceDetailEditCard = ({ handleRemove: () => void; gridView?: boolean; }) => { + const { t } = useTranslation(); const seletedInsurer = policy.insurer_id && policy.insurer_name ? { code: policy.insurer_id, name: policy.insurer_name } @@ -101,9 +107,9 @@ const InsuranceDetailEditCard = ({ return (
    - Policy + {t("policy")} - Delete + {t("remove")}
    @@ -119,25 +125,25 @@ const InsuranceDetailEditCard = ({ {careConfig.hcx.enabled ? ( handleUpdates({ @@ -150,15 +156,15 @@ const InsuranceDetailEditCard = ({ <> diff --git a/src/Components/HCX/InsurerAutocomplete.tsx b/src/Components/HCX/InsurerAutocomplete.tsx index b673b37feab..1105642629f 100644 --- a/src/Components/HCX/InsurerAutocomplete.tsx +++ b/src/Components/HCX/InsurerAutocomplete.tsx @@ -1,12 +1,15 @@ -import { useAsyncOptions } from "../../Common/hooks/useAsyncOptions"; -import { HCXActions } from "../../Redux/actions"; -import { Autocomplete } from "../Form/FormFields/Autocomplete"; -import FormField from "../Form/FormFields/FormField"; import { FormFieldBaseProps, useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; +import { Autocomplete } from "../Form/FormFields/Autocomplete"; +import FormField from "../Form/FormFields/FormField"; +import routes from "../../Redux/api"; +import { mergeQueryOptions } from "../../Utils/utils"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; + export type InsurerOptionModel = { name: string; code: string; @@ -18,8 +21,12 @@ type Props = FormFieldBaseProps & { export default function InsurerAutocomplete(props: Props) { const field = useFormFieldPropsResolver(props); - const { fetchOptions, isLoading, options } = - useAsyncOptions("code"); + + const [query, setQuery] = useState(""); + + const { data, loading } = useQuery(routes.hcx.policies.listPayors, { + query: { query, limit: 10 }, + }); return ( @@ -31,12 +38,16 @@ export default function InsurerAutocomplete(props: Props) { placeholder={props.placeholder} value={field.value} onChange={field.handleChange} - options={options(props.value && [props.value])} + options={mergeQueryOptions( + field.value ? [field.value] : [], + data ?? [], + (obj) => obj.code, + )} optionLabel={(option) => option.name} optionDescription={(option) => option.code} optionValue={(option) => option} - onQuery={(query) => fetchOptions(HCXActions.payors.list(query))} - isLoading={isLoading} + onQuery={setQuery} + isLoading={loading} /> ); diff --git a/src/Components/HCX/PatientInsuranceDetailsEditor.tsx b/src/Components/HCX/PatientInsuranceDetailsEditor.tsx index 2515c98c53e..78de8ef201d 100644 --- a/src/Components/HCX/PatientInsuranceDetailsEditor.tsx +++ b/src/Components/HCX/PatientInsuranceDetailsEditor.tsx @@ -1,12 +1,14 @@ -import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXActions } from "../../Redux/actions"; -import * as Notifications from "../../Utils/Notifications"; import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; -import InsuranceDetailsBuilder from "./InsuranceDetailsBuilder"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXPolicyModel } from "./models"; import HCXPolicyValidator from "./validators"; +import InsuranceDetailsBuilder from "./InsuranceDetailsBuilder"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface Props { patient: string; @@ -19,34 +21,24 @@ export default function PatientInsuranceDetailsEditor({ onSubmitted, onCancel, }: Props) { - const dispatch = useDispatch(); - const [insuranceDetails, setInsuranceDetails] = useState(); + const { t } = useTranslation(); + + const [insuranceDetails, setInsuranceDetails] = useState( + [], + ); const [insuranceDetailsError, setInsuranceDetailsError] = useState(); const [isUpdating, setIsUpdating] = useState(false); - useEffect(() => { - const fetchPatientInsuranceDetails = async () => { - const res = await dispatch(HCXActions.policies.list({ patient })); - if (res && res.data) { + useQuery(routes.hcx.policies.list, { + query: { patient }, + onResponse(res) { + if (res?.res?.ok && res.data) { if (res.data.results.length) { setInsuranceDetails(res.data.results); - } else { - setInsuranceDetails([ - { - subscriber_id: "", - policy_id: "", - insurer_id: "", - insurer_name: "", - }, - ]); } - } else { - Notifications.Error({ msg: "Something went wrong " }); } - }; - - fetchPatientInsuranceDetails(); - }, [dispatch, patient]); + }, + }); const handleSubmit = async () => { // Validate @@ -62,22 +54,19 @@ export default function PatientInsuranceDetailsEditor({ await Promise.all( insuranceDetails.map(async (obj) => { const policy: HCXPolicyModel = { ...obj, patient }; - const policyRes = await (policy.id - ? dispatch(HCXActions.policies.update(policy.id, policy)) - : dispatch(HCXActions.policies.create(policy))); - - const eligibilityCheckRes = await dispatch( - HCXActions.checkEligibility(policyRes.data.id), - ); - if (eligibilityCheckRes.status === 200) { - Notifications.Success({ msg: "Checking Policy Eligibility..." }); - } else { - Notifications.Error({ msg: "Something Went Wrong..." }); - } + policy.id + ? await request(routes.hcx.policies.update, { + pathParams: { external_id: policy.id }, + body: policy, + }) + : await request(routes.hcx.policies.create, { + body: policy, + }); }), ); setIsUpdating(false); onSubmitted?.(); + onCancel?.(); }; return ( @@ -112,7 +101,7 @@ export default function PatientInsuranceDetailsEditor({ } > - Add Insurance Details + {t("add_policy")}
    @@ -120,10 +109,10 @@ export default function PatientInsuranceDetailsEditor({ {isUpdating ? ( <> - Updating... + {t("updating")} ) : ( - "Update" + {t("update")} )}
    diff --git a/src/Components/HCX/PolicyEligibilityCheck.tsx b/src/Components/HCX/PolicyEligibilityCheck.tsx index 883e771ba18..1d951bd0638 100644 --- a/src/Components/HCX/PolicyEligibilityCheck.tsx +++ b/src/Components/HCX/PolicyEligibilityCheck.tsx @@ -1,12 +1,16 @@ -import { useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXActions } from "../../Redux/actions"; +import * as Notification from "../../Utils/Notifications.js"; + +import { useEffect, useState } from "react"; + import ButtonV2 from "../Common/components/ButtonV2"; -import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXPolicyModel } from "./models"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import request from "../../Utils/request/request.js"; +import routes from "../../Redux/api"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; -import * as Notification from "../../Utils/Notifications.js"; +import useQuery from "../../Utils/request/useQuery"; +import { useTranslation } from "react-i18next"; interface Props { className?: string; @@ -19,128 +23,107 @@ export default function HCXPolicyEligibilityCheck({ patient, onEligiblePolicySelected, }: Props) { - const dispatch = useDispatch(); - const [insuranceDetails, setInsuranceDetails] = useState(); - const [policy, setPolicy] = useState(); - const [eligibility, setEligibility] = useState< - Record - >({}); - const [isChecking, setIsChecking] = useState(false); - - const fetchPatientInsuranceDetails = useCallback(async () => { - setInsuranceDetails(undefined); - setEligibility({}); - - const res = await dispatch(HCXActions.policies.list({ patient })); - - if (res.data?.results) { - const results = res.data.results as HCXPolicyModel[]; - setInsuranceDetails(results); - setEligibility( - results.reduce?.((acc: any, policy: HCXPolicyModel) => { - if (policy.outcome) - acc[policy.id ?? ""] = - !policy.error_text && policy.outcome === "Processing Complete"; - return acc; - }, {}), - ); - setIsChecking(false); - } - }, [patient, dispatch]); + const { t } = useTranslation(); - useEffect(() => { - fetchPatientInsuranceDetails(); - }, [fetchPatientInsuranceDetails]); + const [selectedPolicy, setSelectedPolicy] = useState(); + const [isCheckingEligibility, setIsCheckingEligibility] = useState(false); + + const { + refetch, + data: policiesResponse, + loading, + } = useQuery(routes.hcx.policies.list, { query: { patient } }); useMessageListener((data) => { if ( data.type === "MESSAGE" && data.from === "coverageelegibility/on_check" ) { - fetchPatientInsuranceDetails(); + refetch(); } }); useEffect(() => { - if (policy && eligibility[policy]) { - const eligiblePolicy = insuranceDetails?.find((p) => p.id === policy); - onEligiblePolicySelected(eligiblePolicy); - } else { - onEligiblePolicySelected(undefined); - } - }, [policy, insuranceDetails, eligibility, onEligiblePolicySelected]); + onEligiblePolicySelected( + isPolicyEligible(selectedPolicy) ? selectedPolicy : undefined, + ); + }, [selectedPolicy]); // eslint-disable-line react-hooks/exhaustive-deps const checkEligibility = async () => { - if (!policy) return; + if (!selectedPolicy || isPolicyEligible()) return; - // Skip checking eligibility if we already know the policy is eligible - if (eligibility[policy]) return; + setIsCheckingEligibility(true); - setIsChecking(true); + const { res } = await request(routes.hcx.policies.checkEligibility, { + body: { policy: selectedPolicy.id }, + }); - const res = await dispatch(HCXActions.checkEligibility(policy)); - if (res.status === 200) { - Notification.Success({ msg: "Checking Policy Eligibility..." }); - } else { - Notification.Error({ msg: "Something Went Wrong..." }); + if (res?.ok) { + Notification.Success({ msg: t("checking_policy_eligibility") }); } + + setIsCheckingEligibility(false); }; return (
    -
    +
    option.id as string} + options={policiesResponse?.results ?? []} + optionValue={(option) => option.id} optionLabel={(option) => option.policy_id} optionSelectedLabel={(option) => - option.id && eligibility[option.id] !== undefined ? ( + option.outcome ? (
    {option.policy_id} - +
    ) : ( option.policy_id ) } optionIcon={(option) => - eligibility[option.id!] !== undefined && ( - + option.outcome && ( + ) } - onChange={({ value }) => setPolicy(value)} - value={policy} + onChange={({ value }) => { + setSelectedPolicy( + policiesResponse?.results.find((policy) => policy.id === value), + ); + }} + value={selectedPolicy?.id} placeholder={ - insuranceDetails - ? insuranceDetails.length - ? "Select a policy to check eligibility" - : "No policies for the patient" - : "Loading..." + loading + ? t("loading") + : policiesResponse?.results.length + ? t("select_policy") + : t("no_policy_found") } - disabled={!insuranceDetails} + disabled={!policiesResponse?.results.length} optionDescription={(option) => (
    - - Member ID - + + {t("policy__subscriber_id")} + {option.subscriber_id} - - Insurer ID - + + {t("policy__insurer_id")} + {option.insurer_id} - - Insurer Name - + + {t("policy__insurer_name")} + {option.insurer_name} @@ -154,17 +137,21 @@ export default function HCXPolicyEligibilityCheck({ )} /> - {isChecking ? ( + {isCheckingEligibility ? ( <> - Checking ... + {t("checking_eligibility")} ) : ( - "Check Eligibility" + t("check_eligibility") )}
    @@ -173,6 +160,8 @@ export default function HCXPolicyEligibilityCheck({ } const EligibilityChip = ({ eligible }: { eligible: boolean }) => { + const { t } = useTranslation(); + return (
    { > - {eligible ? "Eligible" : "Not Eligible"} + {eligible ? t("eligible") : t("not_eligible")}
    ); }; + +const isPolicyEligible = (policy?: HCXPolicyModel) => + policy && !policy.error_text && policy.outcome === "Complete"; diff --git a/src/Components/HCX/models.ts b/src/Components/HCX/models.ts index a8a9812d31d..e75fc28214d 100644 --- a/src/Components/HCX/models.ts +++ b/src/Components/HCX/models.ts @@ -8,7 +8,7 @@ export type HCXPolicyStatus = | "Active" | "Cancelled" | "Draft" - | "Entered in Error"; + | "Entered In Error"; export type HCXPolicyPurpose = | "Auth Requirements" | "Benefits" @@ -16,12 +16,12 @@ export type HCXPolicyPurpose = | "Validation"; export type HCXPolicyOutcome = | "Queued" - | "Processing Complete" + | "Complete" | "Error" | "Partial Processing"; export interface HCXPolicyModel { - id?: string; + id: string; patient?: string; patient_object?: PatientModel; subscriber_id: string; @@ -29,14 +29,26 @@ export interface HCXPolicyModel { insurer_id?: string; insurer_name?: string; status?: HCXPolicyStatus; - priority?: "Immediate" | "Normal" | "Deferred"; - purpose?: "Auth Requirements" | "Benefits" | "Discovery" | "Validation"; - outcome?: "Queued" | "Processing Complete" | "Error" | "Partial Processing"; + priority?: HCXPriority; + purpose?: HCXPolicyPurpose; + outcome?: HCXPolicyOutcome; error_text?: string; created_date?: string; modified_date?: string; } +export interface HCXCommunicationModel { + id?: string; + identifier?: string; + claim?: string; + claim_object?: HCXClaimModel; + content?: { type: string; data: string }[]; + created_by?: string | null; + last_modified_by?: string | null; + created_date?: string; + modified_date?: string; +} + export interface HCXItemModel { id: string; name: string; @@ -44,7 +56,7 @@ export interface HCXItemModel { category?: string; } -export type HCXClaimUse = "Claim" | "Pre-Authorization" | "Pre-Determination"; +export type HCXClaimUse = "Claim" | "Pre Authorization" | "Pre Determination"; export type HCXClaimStatus = HCXPolicyStatus; export type HCXClaimType = | "Institutional" diff --git a/src/Components/Patient/InsuranceDetails.tsx b/src/Components/Patient/InsuranceDetails.tsx index 41f1da11e89..9f9fe8507ae 100644 --- a/src/Components/Patient/InsuranceDetails.tsx +++ b/src/Components/Patient/InsuranceDetails.tsx @@ -1,11 +1,9 @@ -import { lazy } from "react"; - -import Page from "../Common/components/Page"; - -import useQuery from "../../Utils/request/useQuery"; -import routes from "../../Redux/api"; import { HCXPolicyModel } from "../HCX/models"; import { InsuranceDetialsCard } from "./InsuranceDetailsCard"; +import Page from "../Common/components/Page"; +import { lazy } from "react"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; const Loading = lazy(() => import("../Common/Loading")); @@ -17,11 +15,14 @@ interface IProps { export const InsuranceDetails = (props: IProps) => { const { facilityId, id } = props; - const { data: insuranceDetials, loading } = useQuery(routes.listHCXPolicies, { - query: { - patient: id, + const { data: insuranceDetials, loading } = useQuery( + routes.hcx.policies.list, + { + query: { + patient: id, + }, }, - }); + ); if (loading) { return ; diff --git a/src/Components/Patient/PatientConsentRecords.tsx b/src/Components/Patient/PatientConsentRecords.tsx index e277b1d20fc..564d72ac435 100644 --- a/src/Components/Patient/PatientConsentRecords.tsx +++ b/src/Components/Patient/PatientConsentRecords.tsx @@ -181,11 +181,11 @@ export default function PatientConsentRecords(props: { fileUpload.setFileName(e.value)} />
    - {fileUpload.file ? ( + {fileUpload.files[0] ? ( <> { @@ -215,7 +215,7 @@ export default function PatientConsentRecords(props: { diff --git a/src/Components/Patient/PatientHome.tsx b/src/Components/Patient/PatientHome.tsx index 6fe6ba8c3ce..956cd9a40fd 100644 --- a/src/Components/Patient/PatientHome.tsx +++ b/src/Components/Patient/PatientHome.tsx @@ -1,19 +1,12 @@ -import { navigate } from "raviger"; -import { lazy, useEffect, useState } from "react"; +import * as Notification from "../../Utils/Notifications"; import { DISCHARGE_REASONS, GENDER_TYPES, - SAMPLE_TEST_STATUS, OCCUPATION_TYPES, + SAMPLE_TEST_STATUS, } from "../../Common/constants"; - -import * as Notification from "../../Utils/Notifications"; -import { ConsultationCard } from "../Facility/ConsultationCard"; -import { ConsultationModel } from "../Facility/models"; import { PatientModel, SampleTestModel } from "./models"; -import { SampleTestCard } from "./SampleTestCard"; -import Chip from "../../CAREUI/display/Chip"; import { classNames, formatDate, @@ -23,23 +16,30 @@ import { isAntenatal, isPostPartum, } from "../../Utils/utils"; +import { lazy, useEffect, useState } from "react"; + import ButtonV2 from "../Common/components/ButtonV2"; -import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; -import RelativeDateUserMention from "../Common/RelativeDateUserMention"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import { useTranslation } from "react-i18next"; +import Chip from "../../CAREUI/display/Chip"; import CircularProgress from "../Common/components/CircularProgress"; -import Page from "../Common/components/Page"; import ConfirmDialog from "../Common/ConfirmDialog"; +import { ConsultationCard } from "../Facility/ConsultationCard"; +import { ConsultationModel } from "../Facility/models"; +import { InsuranceDetialsCard } from "./InsuranceDetailsCard"; +import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import Page from "../Common/components/Page"; +import PaginatedList from "../../CAREUI/misc/PaginatedList"; +import RelativeDateUserMention from "../Common/RelativeDateUserMention"; +import { SampleTestCard } from "./SampleTestCard"; import UserAutocomplete from "../Common/UserAutocompleteFormField"; import dayjs from "../../Utils/dayjs"; +import { navigate } from "raviger"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; import { triggerGoal } from "../../Integrations/Plausible"; import useAuthUser from "../../Common/hooks/useAuthUser"; import useQuery from "../../Utils/request/useQuery"; -import routes from "../../Redux/api"; -import { InsuranceDetialsCard } from "./InsuranceDetailsCard"; -import request from "../../Utils/request/request"; -import PaginatedList from "../../CAREUI/misc/PaginatedList"; +import { useTranslation } from "react-i18next"; const Loading = lazy(() => import("../Common/Loading")); @@ -87,7 +87,7 @@ export const PatientHome = (props: any) => { ); }; - const { data: insuranceDetials } = useQuery(routes.listHCXPolicies, { + const { data: insuranceDetials } = useQuery(routes.hcx.policies.list, { query: { patient: id, limit: 1, diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index 033d0187649..9a9715ba081 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -10,16 +10,21 @@ import { SOCIOECONOMIC_STATUS_CHOICES, VACCINES, } from "../../Common/constants"; +import { DistrictModel, DupPatientModel, WardModel } from "../Facility/models"; import { + FieldError, + PhoneNumberValidator, + RequiredFieldValidator, +} from "../Form/FieldValidators"; +import { FieldErrorText, FieldLabel } from "../Form/FormFields/FormField"; +import { + compareBy, dateQueryString, getPincodeDetails, includesIgnoreCase, parsePhoneNumber, scrollTo, - compareBy, } from "../../Utils/utils"; -import { navigate, useQueryParams } from "raviger"; -import { statusType, useAbortableEffect } from "../../Common/utils"; import { lazy, useCallback, @@ -28,8 +33,11 @@ import { useRef, useState, } from "react"; +import { navigate, useQueryParams } from "raviger"; +import { statusType, useAbortableEffect } from "../../Common/utils"; import AccordionV2 from "../Common/components/AccordionV2"; +import AutocompleteFormField from "../Form/FormFields/Autocomplete.js"; import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; @@ -37,43 +45,34 @@ import CollapseV2 from "../Common/components/CollapseV2"; import ConfirmDialog from "../Common/ConfirmDialog"; import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "../Common/Dialog"; -import { DistrictModel, DupPatientModel, WardModel } from "../Facility/models"; import DuplicatePatientDialog from "../Facility/DuplicatePatientDialog"; -import { - FieldError, - PhoneNumberValidator, - RequiredFieldValidator, -} from "../Form/FieldValidators"; -import { FieldErrorText, FieldLabel } from "../Form/FormFields/FormField"; +import Error404 from "../ErrorPages/404"; import Form from "../Form/Form"; +import { FormContextValue } from "../Form/FormContext.js"; import { HCXPolicyModel } from "../HCX/models"; import HCXPolicyValidator from "../HCX/validators"; +import { ILocalBodies } from "../ExternalResult/models.js"; import InsuranceDetailsBuilder from "../HCX/InsuranceDetailsBuilder"; import LinkABHANumberModal from "../ABDM/LinkABHANumberModal"; import { PatientModel, Occupation, PatientMeta } from "./models"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import RadioFormField from "../Form/FormFields/RadioFormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import AutocompleteFormField from "../Form/FormFields/Autocomplete.js"; +import SelectMenuV2 from "../Form/SelectMenuV2.js"; import Spinner from "../Common/Spinner"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import TransferPatientDialog from "../Facility/TransferPatientDialog"; +import _ from "lodash"; import countryList from "../../Common/static/countries.json"; import { debounce } from "lodash-es"; - +import request from "../../Utils/request/request.js"; +import routes from "../../Redux/api.js"; import useAppHistory from "../../Common/hooks/useAppHistory"; -import { validatePincode } from "../../Common/validation"; -import { FormContextValue } from "../Form/FormContext.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; import useQuery from "../../Utils/request/useQuery.js"; -import routes from "../../Redux/api.js"; -import request from "../../Utils/request/request.js"; -import Error404 from "../ErrorPages/404"; -import SelectMenuV2 from "../Form/SelectMenuV2.js"; -import _ from "lodash"; -import { ILocalBodies } from "../ExternalResult/models.js"; import { useTranslation } from "react-i18next"; +import { validatePincode } from "../../Common/validation"; import careConfig from "@careConfig"; const Loading = lazy(() => import("../Common/Loading")); @@ -481,7 +480,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { [id], ); - useQuery(routes.listHCXPolicies, { + useQuery(routes.hcx.policies.list, { query: { patient: id, }, @@ -818,29 +817,14 @@ export const PatientRegister = (props: PatientRegisterProps) => { insurer_id: obj.insurer_id || undefined, insurer_name: obj.insurer_name || undefined, }; - const { data: policyData } = policy.id - ? await request(routes.updateHCXPolicy, { + policy.id + ? await request(routes.hcx.policies.update, { pathParams: { external_id: policy.id }, body: policy, }) - : await request(routes.createHCXPolicy, { + : await request(routes.hcx.policies.create, { body: policy, }); - - if (careConfig.hcx.enabled && policyData?.id) { - await request(routes.hcxCheckEligibility, { - body: { policy: policyData?.id }, - onResponse: ({ res }) => { - if (res?.ok) { - Notification.Success({ - msg: "Checking Policy Eligibility...", - }); - } else { - Notification.Error({ msg: "Something Went Wrong..." }); - } - }, - }); - } }), ); diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index fb36e6a07f8..fee9e43afe5 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -183,6 +183,11 @@ "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", + "send_message": "Send Message", + "enter_message": "Start typing...", + "see_attachments": "See Attachments", + "no_attachments_found": "This communication has no attachments.", + "fetching": "Fetching", "GENDER__1": "Male", "GENDER__2": "Female", "GENDER__3": "Non-binary", @@ -196,6 +201,7 @@ "live": "Live", "discharged": "Discharged", "archived": "Archived", + "created_on": "Created On", "no_changes_made": "No changes made", "user_deleted_successfuly": "User Deleted Successfuly", "users": "Users", @@ -206,4 +212,4 @@ "unsupported_browser": "Unsupported Browser", "unsupported_browser_description": "Your browser ({{name}} version {{version}}) is not supported. Please update your browser to the latest version or switch to a supported browser for the best experience.", "add_remarks": "Add remarks" -} \ No newline at end of file +} diff --git a/src/Locale/en/FileUpload.json b/src/Locale/en/FileUpload.json index 93b61943944..33914134602 100644 --- a/src/Locale/en/FileUpload.json +++ b/src/Locale/en/FileUpload.json @@ -20,7 +20,8 @@ "file_list_headings__sample_report": "Sample Report", "file_list_headings__supporting_info": "Supporting Info", "file_error__choose_file": "Please choose a file to upload", - "file_error__file_name": "Please enter file name", + "file_error__file_name": "Please give a name for all files!", + "change_file": "Change File", "file_error__file_size": "Maximum size of files is 100 MB", "file_error__file_type": "Invalid file type \".{{extension}}\" Allowed types: {{allowedExtensions}}", "file_uploaded": "File Uploaded Successfully", diff --git a/src/Locale/en/HCX.json b/src/Locale/en/HCX.json new file mode 100644 index 00000000000..8c5d60c642d --- /dev/null +++ b/src/Locale/en/HCX.json @@ -0,0 +1,63 @@ +{ + "checking_policy_eligibility": "Checking Policy Eligibility", + "check_policy_eligibility": "Check Policy Eligibility", + "add_policy": "Add Insurance Policy", + "edit_policy": "Edit Insurance Policy", + "edit_policy_description": "Add or edit patient's insurance details", + "select_policy": "Select an Insurance Policy", + "select_eligible_policy": "Select an Eligible Insurance Policy", + "no_policy_found": "No Insurance Policy Found for this Patient", + "no_policy_added": "No Insurance Policy Added", + "checking_eligibility": "Checking Eligibility", + "check_eligibility": "Check Eligibility", + "eligible": "Eligible", + "not_eligible": "Not Eligible", + "policy": "Policy", + "policy__subscriber_id": "Member ID", + "policy__subscriber_id__example": "SUB001", + "policy__policy_id": "Policy ID / Policy Name", + "policy__policy_id__example": "POL001", + "policy__insurer": "Insurer", + "policy__insurer__example": "GICOFINDIA", + "policy__insurer_id": "Insurer ID", + "policy__insurer_id__example": "GICOFINDIA", + "policy__insurer_name": "Insurer Name", + "policy__insurer_name__example": "GIC OF INDIA", + "claims": "Claims", + "claim__item": "Item", + "claim__items": "Items", + "claim__add_item": "Add Item", + "claim__item__add_at_least_one": "Add at least one item", + "claim__item__fill_all_details": "Fill all the item details", + "claim__item__category": "Category", + "claim__item__procedure": "Procedure", + "claim__item__id": "ID", + "claim__item__id__example": "PROC001", + "claim__item__name": "Name", + "claim__item__name__example": "Knee Replacement", + "claim__item__price": "Price", + "claim__item__price__example": "100.00", + "claim__failed_to_create_preauthorization": "Failed to create Pre Authorization", + "claim__failed_to_create_claim": "Failed to create Claim", + "claim__creating_preauthorization": "Creating Pre Authorization", + "claim__creating_claim": "Creating Claim", + "claim__create_preauthorization": "Create Pre Authorization", + "claim__create_claim": "Create Claim", + "claim__use": "Use", + "claim__use__preauthorization": "Pre Authorization", + "claim__use__claim": "Claim", + "select_policy_to_add_items": "Select a Policy to Add Items", + "total_amount": "Total Amount", + "claim__status__rejected": "Rejected", + "claim__status__approved": "Approved", + "claim__status__pending": "Pending", + "claim__total_claim_amount": "Total Claim Amount", + "claim__total_approved_amount": "Total Approved Amount", + "communication__sent_to_hcx": "Sent communication to HCX", + "fetched_attachments_successfully": "Fetched attachments successfully", + "add_attachments": "Add Attachments", + "claim__request_claim": "Request Claim", + "claim__requesting_claim": "Requesting Claim", + "claim__fetched_claim_approval_results": "Fetched Claim Approval Results", + "claim__error_fetching_claim_approval_results": "Error Fetching Claim Approval Results" +} diff --git a/src/Locale/en/index.js b/src/Locale/en/index.js index 0ad729cadbe..6452253c179 100644 --- a/src/Locale/en/index.js +++ b/src/Locale/en/index.js @@ -9,6 +9,8 @@ import Entities from "./Entities.json"; import ErrorPages from "./ErrorPages.json"; import ExternalResult from "./ExternalResult.json"; import Facility from "./Facility.json"; +import FileUpload from "./FileUpload.json"; +import HCX from "./HCX.json"; import Hub from "./Hub.json"; import LogUpdate from "./LogUpdate.json"; import Medicine from "./Medicine.json"; @@ -18,7 +20,6 @@ import Resource from "./Resource.json"; import Shifting from "./Shifting.json"; import SortOptions from "./SortOptions.json"; import Users from "./Users.json"; -import FileUpload from "./FileUpload.json"; export default { ...Auth, @@ -41,5 +42,6 @@ export default { ...Users, ...LogUpdate, ...FileUpload, - SortOptions, + ...HCX, + ...SortOptions, }; diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 05a6cf18dc4..00e96e48eef 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1,4 +1,3 @@ -import { HCXClaimModel, HCXPolicyModel } from "../Components/HCX/models"; import { fireRequest } from "./fireRequest"; // asset bed @@ -92,85 +91,3 @@ export const listAssets = (params: object) => fireRequest("listAssets", [], params); export const operateAsset = (id: string, params: object) => fireRequest("operateAsset", [], params, { external_id: id }); - -export const listPMJYPackages = (query?: string) => - fireRequest("listPMJYPackages", [], { query }); - -// HCX Actions -export const HCXActions = { - checkEligibility: (policy: string) => { - return fireRequest("hcxCheckEligibility", [], { policy }); - }, - - payors: { - list(query: string) { - return fireRequest("hcxListPayors", [], { query }); - }, - }, - - policies: { - list(params: object) { - return fireRequest("listHCXPolicies", [], params); - }, - create(obj: HCXPolicyModel) { - return fireRequest("createHCXPolicy", [], obj); - }, - read(id: string) { - return fireRequest("getHCXPolicy", [], {}, { external_id: id }); - }, - update(id: string, obj: HCXPolicyModel) { - return fireRequest("updateHCXPolicy", [], obj, { external_id: id }); - }, - partialUpdate(id: string, obj: Partial) { - return fireRequest("partialUpdateHCXPolicy", [], obj, { - external_id: id, - }); - }, - delete(id: string) { - return fireRequest("deleteHCXPolicy", [], {}, { external_id: id }); - }, - }, - - claims: { - list(params: object) { - return fireRequest("listHCXClaims", [], params); - }, - create(obj: object) { - return fireRequest("createHCXClaim", [], obj); - }, - read(id: string) { - return fireRequest("getHCXClaim", [], {}, { external_id: id }); - }, - update(id: string, obj: HCXClaimModel) { - return fireRequest("updateHCXClaim", [], obj, { external_id: id }); - }, - partialUpdate(id: string, obj: Partial) { - return fireRequest("partialUpdateHCXClaim", [], obj, { - external_id: id, - }); - }, - delete(id: string) { - return fireRequest("deleteHCXClaim", [], {}, { external_id: id }); - }, - }, - - preauths: { - list(consultation: string) { - return fireRequest( - "listHCXClaims", - [], - { - consultation, - ordering: "-modified_date", - use: "preauthorization", - }, - {}, - `listPreAuths-${consultation}`, - ); - }, - }, - - makeClaim(claim: string) { - return fireRequest("hcxMakeClaim", [], { claim }); - }, -}; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 228b24c7360..e53342e3d7c 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -3,25 +3,6 @@ import { CreateConsentTBody, } from "../Components/ABDM/types/consent"; import { HealthInformationModel } from "../Components/ABDM/types/health-information"; -import { - IAadhaarOtp, - IAadhaarOtpTBody, - ICheckAndGenerateMobileOtp, - IConfirmMobileOtp, - IcreateHealthFacilityTBody, - ICreateHealthIdRequest, - ICreateHealthIdResponse, - IGenerateMobileOtpTBody, - IgetAbhaCardTBody, - IHealthFacility, - IHealthId, - IinitiateAbdmAuthenticationTBody, - ILinkABHANumber, - ILinkViaQRBody, - IpartialUpdateHealthFacilityTBody, - ISearchByHealthIdTBody, - IVerifyAadhaarOtpTBody, -} from "../Components/ABDM/models"; import { AssetBedBody, AssetBedModel, @@ -33,15 +14,6 @@ import { AvailabilityRecord, PatientAssetBed, } from "../Components/Assets/AssetTypes"; -import { - IDeleteBedCapacity, - IDeleteExternalResult, - IExternalResult, - IExternalResultCsv, - ILocalBodies, - ILocalBodyByDistrict, - IPartialUpdateExternalResult, -} from "../Components/ExternalResult/models"; import { BedModel, CapacityModal, @@ -52,40 +24,86 @@ import { DailyRoundsRes, DistrictModel, DoctorModal, - DupPatientModel, FacilityModel, FacilityRequest, IFacilityNotificationRequest, IFacilityNotificationResponse, + IUserFacilityRequest, InventoryItemsModel, InventoryLogResponse, InventorySummaryResponse, - IUserFacilityRequest, LocalBodyModel, LocationModel, MinimumQuantityItemResponse, - PatientConsentModel, PatientNotesEditModel, PatientNotesModel, PatientStatsModel, - PatientTransferRequest, PatientTransferResponse, StateModel, WardModel, } from "../Components/Facility/models"; +import { + DailyRoundsModel, + PatientModel, + SampleReportModel, + SampleTestModel, +} from "../Components/Patient/models"; +import { + IAadhaarOtp, + IAadhaarOtpTBody, + ICheckAndGenerateMobileOtp, + IConfirmMobileOtp, + ICreateHealthIdRequest, + ICreateHealthIdResponse, + IGenerateMobileOtpTBody, + IHealthFacility, + IHealthId, + ILinkABHANumber, + ILinkViaQRBody, + ISearchByHealthIdTBody, + IVerifyAadhaarOtpTBody, + IcreateHealthFacilityTBody, + IgetAbhaCardTBody, + IinitiateAbdmAuthenticationTBody, + IpartialUpdateHealthFacilityTBody, +} from "../Components/ABDM/models"; +import { IComment, IResource } from "../Components/Resource/models"; +import { + IDeleteBedCapacity, + IDeleteExternalResult, + IExternalResult, + IExternalResultCsv, + ILocalBodies, + ILocalBodyByDistrict, + IPartialUpdateExternalResult, +} from "../Components/ExternalResult/models"; +import { + InvestigationGroup, + InvestigationType, +} from "../Components/Facility/Investigations"; +import { + DupPatientModel, + PatientConsentModel, + PatientTransferRequest, +} from "../Components/Facility/models"; import { MedibaseMedicine, Prescription } from "../Components/Medicine/models"; import { NotificationData, PNconfigData, } from "../Components/Notifications/models"; +import { + HCXClaimModel, + HCXCommunicationModel, + HCXPolicyModel, +} from "../Components/HCX/models"; +import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; +import { IShift } from "../Components/Shifting/models"; +import { Investigation } from "../Components/Facility/Investigations/Reports/types"; +import { PaginatedResponse } from "../Utils/request/types"; import { CreateFileRequest, CreateFileResponse, - DailyRoundsModel, FileUploadModel, - PatientModel, - SampleReportModel, - SampleTestModel, } from "../Components/Patient/models"; import { SkillModel, @@ -94,24 +112,15 @@ import { UserAssignedModel, UserModel, } from "../Components/Users/models"; -import { PaginatedResponse } from "../Utils/request/types"; - -import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; import { EventGeneric, type Type, } from "../Components/Facility/ConsultationDetails/Events/types"; -import { - InvestigationGroup, - InvestigationType, -} from "../Components/Facility/Investigations"; import { InvestigationSessionType } from "../Components/Facility/Investigations/investigationsTab"; -import { Investigation } from "../Components/Facility/Investigations/Reports/types"; -import { HCXPolicyModel } from "../Components/HCX/models"; -import { IComment, IResource } from "../Components/Resource/models"; -import { IShift } from "../Components/Shifting/models"; import { AbhaNumberModel } from "../Components/ABDM/types/abha"; import { ScribeModel } from "../Components/Scribe/Scribe"; +import { InsurerOptionModel } from "../Components/HCX/InsurerAutocomplete"; +import { PMJAYPackageItem } from "../Components/Common/PMJAYProcedurePackageAutocomplete"; /** * A fake function that returns an empty object casted to type T @@ -1579,89 +1588,166 @@ const routes = { }, // HCX Endpoints + hcx: { + policies: { + list: { + path: "/api/hcx/policy/", + method: "GET", + TRes: Type>(), + }, + + create: { + path: "/api/hcx/policy/", + method: "POST", + TRes: Type(), + }, + + get: { + path: "/api/hcx/policy/{external_id}/", + method: "GET", + }, + + update: { + path: "/api/hcx/policy/{external_id}/", + method: "PUT", + TRes: Type(), + }, + + partialUpdate: { + path: "/api/hcx/policy/{external_id}/", + method: "PATCH", + }, + + delete: { + path: "/api/hcx/policy/{external_id}/", + method: "DELETE", + TRes: Type>(), + }, + + listPayors: { + path: "/api/hcx/payors/", + method: "GET", + TRes: Type(), + }, + + checkEligibility: { + path: "/api/hcx/check_eligibility/", + method: "POST", + TBody: Type<{ policy: string }>(), + TRes: Type(), + }, + }, - listPMJYPackages: { - path: "/api/v1/hcx/pmjy_packages/", - method: "GET", - }, - - hcxListPayors: { - path: "/api/v1/hcx/payors/", - method: "GET", - }, - - hcxCheckEligibility: { - path: "/api/v1/hcx/check_eligibility/", - method: "POST", - TRes: Type(), - }, - - listHCXPolicies: { - path: "/api/v1/hcx/policy/", - method: "GET", - TRes: Type>(), - }, - - createHCXPolicy: { - path: "/api/v1/hcx/policy/", - method: "POST", - TRes: Type(), - }, - - getHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "GET", - }, - - updateHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "PUT", - TRes: Type(), - }, - - partialUpdateHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "PATCH", - }, - - deleteHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "DELETE", - }, - - listHCXClaims: { - path: "/api/v1/hcx/claim/", - method: "GET", - }, - - createHCXClaim: { - path: "/api/v1/hcx/claim/", - method: "POST", - }, - - getHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "GET", - }, - - updateHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "PUT", - }, - - partialUpdateHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "PATCH", - }, - - deleteHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "DELETE", - }, + claims: { + list: { + path: "/api/hcx/claim/", + method: "GET", + TRes: Type>(), + }, + + create: { + path: "/api/hcx/claim/", + method: "POST", + TBody: Type<{ + policy: string; + items: { + id: string; + price: number; + category?: string; + name: string; + }[]; + consultation: string; + use: "preauthorization" | "claim"; + }>(), + TRes: Type(), + }, + + get: { + path: "/api/hcx/claim/{external_id}/", + method: "GET", + }, + + update: { + path: "/api/hcx/claim/{external_id}/", + method: "PUT", + }, + + partialUpdate: { + path: "/api/hcx/claim/{external_id}/", + method: "PATCH", + }, + + delete: { + path: "/api/hcx/claim/{external_id}/", + method: "DELETE", + }, + + listPMJYPackages: { + path: "/api/hcx/pmjy_packages/", + method: "GET", + TRes: Type(), + }, + + makeClaim: { + path: "/api/hcx/make_claim/", + method: "POST", + TBody: Type<{ claim: string }>(), + TRes: Type(), + }, + }, - hcxMakeClaim: { - path: "/api/v1/hcx/make_claim/", - method: "POST", + communications: { + list: { + path: "/api/hcx/communication/", + method: "GET", + TRes: Type>(), + }, + + create: { + path: "/api/hcx/communication/", + method: "POST", + TRes: Type(), + TBody: Type<{ + claim: string; + content: { + type: string; + data: string; + }[]; + }>(), + }, + + get: { + path: "/api/hcx/communication/{external_id}/", + method: "GET", + TRes: Type(), + }, + + update: { + path: "/api/hcx/communication/{external_id}/", + method: "PUT", + TRes: Type(), + }, + + partialUpdate: { + path: "/api/hcx/communication/{external_id}/", + method: "PATCH", + TRes: Type(), + }, + + delete: { + path: "/api/hcx/communication/{external_id}/", + method: "DELETE", + }, + + send: { + path: "/api/hcx/send_communication/", + method: "POST", + TRes: Type(), + TBody: Type<{ + communication: string; + }>(), + }, + }, }, } as const; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 60e0f9411d5..f5ca25f45b2 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -63,7 +63,7 @@ export default function AppRouter() { let routes = Routes; if (careConfig.hcx.enabled) { - routes = { ...routes, ...HCXRoutes }; + routes = { ...HCXRoutes, ...routes }; } if ( diff --git a/src/Routers/routes/HCXRoutes.tsx b/src/Routers/routes/HCXRoutes.tsx index 8a36e033c15..80378b24621 100644 --- a/src/Routers/routes/HCXRoutes.tsx +++ b/src/Routers/routes/HCXRoutes.tsx @@ -1,6 +1,10 @@ -import ConsultationClaims from "../../Components/Facility/ConsultationClaims"; +import ConsultationClaims, { + IConsultationClaimsProps, +} from "../../Components/Facility/ConsultationClaims"; export default { "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": - (pathParams: any) => , + (pathParams: IConsultationClaimsProps) => ( + + ), }; diff --git a/src/Utils/useFileUpload.tsx b/src/Utils/useFileUpload.tsx index fdd69c45765..ac246287196 100644 --- a/src/Utils/useFileUpload.tsx +++ b/src/Utils/useFileUpload.tsx @@ -20,6 +20,7 @@ import AudioCaptureDialog from "../Components/Files/AudioCaptureDialog"; import { t } from "i18next"; export type FileUploadOptions = { + multiple?: boolean; type: string; category?: FileCategory; onUpload?: (file: FileUploadModel) => void; @@ -41,15 +42,18 @@ export interface FileInputProps export type FileUploadReturn = { progress: null | number; error: null | string; + validateFiles: () => boolean; handleCameraCapture: () => void; handleAudioCapture: () => void; handleFileUpload: (associating_id: string) => Promise; Dialogues: JSX.Element; Input: (_: FileInputProps) => JSX.Element; - fileName: string; - file: File | null; - setFileName: (name: string) => void; - clearFile: () => void; + fileNames: string[]; + files: File[]; + setFileName: (names: string, index?: number) => void; + setFileNames: (names: string[]) => void; + removeFile: (index: number) => void; + clearFiles: () => void; }; // Array of image extensions @@ -67,73 +71,70 @@ const ExtImage: string[] = [ export default function useFileUpload( options: FileUploadOptions, ): FileUploadReturn { - const { type, onUpload, category = "UNSPECIFIED" } = options; + const { type, onUpload, category = "UNSPECIFIED", multiple } = options; - const [uploadFileName, setUploadFileName] = useState(""); + const [uploadFileNames, setUploadFileNames] = useState([]); const [error, setError] = useState(null); const [progress, setProgress] = useState(null); const [cameraModalOpen, setCameraModalOpen] = useState(false); const [audioModalOpen, setAudioModalOpen] = useState(false); - const [file, setFile] = useState(null); + const [files, setFiles] = useState([]); const onFileChange = (e: ChangeEvent): any => { if (!e.target.files?.length) { return; } - const f = e.target.files[0]; - const fileName = f.name; - setFile(e.target.files[0]); + const selectedFiles = Array.from(e.target.files); + setFiles((prev) => [...prev, ...selectedFiles]); - // This is commented out to prompt users to input valid file names. See https://github.com/ohcnetwork/care_fe/issues/7942#issuecomment-2324391329 - //setUploadFileName( - // uploadFileName || - // fileName.substring(0, fileName.lastIndexOf(".")) || - // fileName, - //); - - const ext: string = fileName.split(".")[1]; - - if (ExtImage.includes(ext)) { - const options = { - initialQuality: 0.6, - alwaysKeepResolution: true, - }; - imageCompression(f, options).then((compressedFile: File) => { - setFile(compressedFile); - }); - return; - } - setFile(f); + selectedFiles.forEach((file) => { + const ext: string = file.name.split(".")[1]; + if (ExtImage.includes(ext)) { + const options = { + initialQuality: 0.6, + alwaysKeepResolution: true, + }; + imageCompression(file, options).then((compressedFile: File) => { + setFiles((prev) => + prev.map((f) => (f.name === file.name ? compressedFile : f)), + ); + }); + } + }); }; const validateFileUpload = () => { - const filenameLength = uploadFileName.trim().length; - const f = file; - if (f === undefined || f === null) { + if (files.length === 0) { setError(t("file_error__choose_file")); return false; } - if (filenameLength === 0) { - setError(t("file_error__file_name")); - return false; - } - if (f.size > 10e7) { - setError(t("file_error__file_size")); - return false; - } - const extension = f.name.split(".").pop(); - if ( - "allowedExtensions" in options && - !options.allowedExtensions?.includes(extension || "") - ) { - setError( - t("file_error__file_type", { - extension, - allowedExtensions: options.allowedExtensions?.join(", "), - }), - ); - return false; + + for (const file of files) { + const filenameLength = file.name.trim().length; + if (filenameLength === 0) { + setError(t("file_error__file_name")); + return false; + } + if (file.size > 10e7) { + setError(t("file_error__file_size")); + return false; + } + const extension = file.name.split(".").pop(); + if ( + "allowedExtensions" in options && + !options.allowedExtensions + ?.map((extension) => extension.replace(".", "")) + ?.includes(extension || "") + ) { + setError( + t("file_error__file_type", { + extension, + allowedExtensions: options.allowedExtensions?.join(", "), + }), + ); + return false; + } } return true; }; @@ -151,23 +152,20 @@ export default function useFileUpload( }); }; - const uploadfile = async (data: CreateFileResponse) => { + const uploadfile = async (data: CreateFileResponse, file: File) => { const url = data.signed_url; const internal_name = data.internal_name; - const f = file; - if (!f) return; - const newFile = new File([f], `${internal_name}`); + const newFile = new File([file], `${internal_name}`); + return new Promise((resolve, reject) => { uploadFile( url, newFile, "PUT", - { "Content-Type": file?.type }, + { "Content-Type": file.type }, (xhr: XMLHttpRequest) => { if (xhr.status >= 200 && xhr.status < 300) { setProgress(null); - setFile(null); - setUploadFileName(""); Notification.Success({ msg: t("file_uploaded"), }); @@ -196,27 +194,34 @@ export default function useFileUpload( const handleUpload = async (associating_id: string) => { if (!validateFileUpload()) return; - const f = file; - const filename = uploadFileName === "" && f ? f.name : uploadFileName; - const name = f?.name; setProgress(0); - const { data } = await request(routes.createUpload, { - body: { - original_name: name ?? "", - file_type: type, - name: filename, - associating_id, - file_category: category, - mime_type: f?.type ?? "", - }, - }); + for (const [index, file] of files.entries()) { + const filename = + uploadFileNames[index] === "" && file + ? file.name + : uploadFileNames[index]; + + const { data } = await request(routes.createUpload, { + body: { + original_name: file.name ?? "", + file_type: type, + name: filename, + associating_id, + file_category: category, + mime_type: file.type ?? "", + }, + }); - if (data) { - await uploadfile(data); - await markUploadComplete(data, associating_id); + if (data) { + await uploadfile(data, file); + await markUploadComplete(data, associating_id); + } } + + setFiles([]); + setUploadFileNames([]); }; const Dialogues = ( @@ -224,17 +229,17 @@ export default function useFileUpload( setCameraModalOpen(false)} - onCapture={(f) => { - setFile(f); - setUploadFileName(uploadFileName || ""); + onCapture={(file, fileName) => { + setFiles((prev) => [...prev, file]); + setUploadFileNames((prev) => [...prev, fileName]); }} /> setAudioModalOpen(false)} - onCapture={(f) => { - setFile(f); - setUploadFileName(uploadFileName || ""); + onCapture={(file, fileName) => { + setFiles((prev) => [...prev, file]); + setUploadFileNames((prev) => [...prev, fileName]); }} autoRecord /> @@ -245,9 +250,10 @@ export default function useFileUpload( "." + e).join(",") @@ -262,18 +268,27 @@ export default function useFileUpload( return { progress, error, + validateFiles: validateFileUpload, handleCameraCapture: () => setCameraModalOpen(true), handleAudioCapture: () => setAudioModalOpen(true), handleFileUpload: handleUpload, Dialogues, Input, - fileName: uploadFileName, - file: file, - setFileName: setUploadFileName, - clearFile: () => { - setFile(null); - setError(null); - setUploadFileName(""); + fileNames: uploadFileNames, + files: files, + setFileNames: setUploadFileNames, + setFileName: (name: string, index = 0) => { + setUploadFileNames((prev) => + prev.map((n, i) => (i === index ? name : n)), + ); + }, + removeFile: (index = 0) => { + setFiles((prev) => prev.filter((_, i) => i !== index)); + setUploadFileNames((prev) => prev.filter((_, i) => i !== index)); + }, + clearFiles: () => { + setFiles([]); + setUploadFileNames([]); }, }; } From 15ea98617b7e63197685f3d42e072d4082636a1b Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Mon, 23 Sep 2024 17:56:45 +0530 Subject: [PATCH 07/30] Cleanup bed types (#8456) --- .../e2e/facility_spec/FacilityCreation.cy.ts | 4 +-- .../e2e/facility_spec/FacilityManage.cy.ts | 2 +- src/Common/constants.tsx | 26 +------------------ src/Components/Facility/BedCapacity.tsx | 16 +++++++----- .../Facility/FacilityBedCapacity.tsx | 9 ++++--- src/Components/Facility/FacilityCreate.tsx | 4 +-- src/Locale/en/Facility.json | 5 ++++ 7 files changed, 27 insertions(+), 39 deletions(-) diff --git a/cypress/e2e/facility_spec/FacilityCreation.cy.ts b/cypress/e2e/facility_spec/FacilityCreation.cy.ts index 57735a9dcde..918e926d107 100644 --- a/cypress/e2e/facility_spec/FacilityCreation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityCreation.cy.ts @@ -142,7 +142,7 @@ describe("Facility Creation", () => { facilityPage.submitForm(); cy.closeNotification(); // create multiple bed capacity and verify card reflection - facilityPage.selectBedType("Oxygen beds"); + facilityPage.selectBedType("Oxygen Supported Bed"); facilityPage.fillTotalCapacity(bedCapacity); facilityPage.fillCurrentlyOccupied(bedOccupancy); facilityPage.clickbedcapcityaddmore(); @@ -216,7 +216,7 @@ describe("Facility Creation", () => { facilityPage.fillPhoneNumber(facilityNumber); facilityPage.submitForm(); // add the bed capacity - facilityPage.selectBedType("Oxygen beds"); + facilityPage.selectBedType("Oxygen Supported Bed"); facilityPage.fillTotalCapacity(oxygenCapacity); facilityPage.fillCurrentlyOccupied(oxygenExpected); facilityPage.saveAndExitBedCapacityForm(); diff --git a/cypress/e2e/facility_spec/FacilityManage.cy.ts b/cypress/e2e/facility_spec/FacilityManage.cy.ts index e4f4ba40ff1..c53943733e9 100644 --- a/cypress/e2e/facility_spec/FacilityManage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityManage.cy.ts @@ -138,7 +138,7 @@ describe("Facility Manage Functions", () => { it("Modify bed capacity in Facility detail page", () => { // add multiple new bed capacity facilityManage.clickFacilityAddBedTypeButton(); - facilityPage.selectBedType("Oxygen beds"); + facilityPage.selectBedType("Isolation Bed"); facilityPage.fillTotalCapacity(totalCapacity); facilityPage.fillCurrentlyOccupied(currentOccupied); facilityPage.saveAndExitBedCapacityForm(); diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index c2ddf842665..307e912844f 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -8,7 +8,6 @@ import { ConsentHIType, ConsentPurpose, } from "../Components/ABDM/types/consent"; -import careConfig from "@careConfig"; export const RESULTS_PER_PAGE_LIMIT = 14; export const PAGINATION_LIMIT = 36; @@ -217,30 +216,7 @@ export const DISCHARGED_PATIENT_SORT_OPTIONS: SortOption[] = [ { isAscending: false, value: "-name" }, ]; -const { kasp } = careConfig; - -const KASP_BED_TYPES = kasp.enabled - ? [ - { id: 40, text: kasp.string + " Ordinary Beds" }, - { id: 60, text: kasp.string + " Oxygen beds" }, - { id: 50, text: kasp.string + " ICU (ICU without ventilator)" }, - { id: 70, text: kasp.string + " ICU (ICU with ventilator)" }, - ] - : []; - -export const BED_TYPES: OptionsType[] = [ - { id: 1, text: "Ordinary Beds" }, - { id: 150, text: "Oxygen beds" }, - { id: 10, text: "ICU (ICU without ventilator)" }, - { id: 20, text: "Ventilator (ICU with ventilator)" }, - { id: 30, text: "Covid Ordinary Beds" }, - { id: 120, text: "Covid Oxygen beds" }, - { id: 110, text: "Covid ICU (ICU without ventilator)" }, - { id: 100, text: "Covid Ventilators (ICU with ventilator)" }, - ...KASP_BED_TYPES, - { id: 2, text: "Hostel" }, - { id: 3, text: "Single Room with Attached Bathroom" }, -]; +export const BED_TYPES = [100, 200, 300, 400, 500]; export const DOCTOR_SPECIALIZATION: Array = [ { id: 1, text: "General Medicine" }, diff --git a/src/Components/Facility/BedCapacity.tsx b/src/Components/Facility/BedCapacity.tsx index a4437823d5d..7d36190c441 100644 --- a/src/Components/Facility/BedCapacity.tsx +++ b/src/Components/Facility/BedCapacity.tsx @@ -1,14 +1,14 @@ import { useEffect, useReducer, useState } from "react"; import * as Notification from "../../Utils/Notifications.js"; -import { CapacityModal } from "./models"; +import { CapacityModal, OptionsType } from "./models"; import TextFormField from "../Form/FormFields/TextFormField"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import { BED_TYPES } from "../../Common/constants"; import routes from "../../Redux/api"; import request from "../../Utils/request/request"; import { useTranslation } from "react-i18next"; -import { BED_TYPES } from "../../Common/constants.js"; interface BedCapacityProps extends CapacityModal { facilityId: string; @@ -52,8 +52,10 @@ export const BedCapacity = (props: BedCapacityProps) => { const { t } = useTranslation(); const { facilityId, handleClose, handleUpdate, className, id } = props; const [state, dispatch] = useReducer(bedCountReducer, initialState); - const [bedTypes, setBedTypes] = useState(BED_TYPES); const [isLastOptionType, setIsLastOptionType] = useState(false); + const [bedTypes, setBedTypes] = useState( + BED_TYPES.map((o) => ({ id: o, text: t(`bed_type__${o}`) })), + ); const [isLoading, setIsLoading] = useState(false); const headerText = !id ? "Add Bed Capacity" : "Edit Bed Capacity"; @@ -77,10 +79,11 @@ export const BedCapacity = (props: BedCapacityProps) => { // disable existing bed types const updatedBedTypes = BED_TYPES.map((type) => { const isExisting = existingData.find( - (i: CapacityModal) => i.room_type === type.id, + (i: CapacityModal) => i.room_type === type, ); return { - ...type, + id: type, + text: t(`bed_type__${type}`), disabled: !!isExisting, }; }); @@ -111,7 +114,8 @@ export const BedCapacity = (props: BedCapacityProps) => { useEffect(() => { const lastBedType = - bedTypes.filter((i) => i.disabled).length === BED_TYPES.length - 1; + bedTypes.filter((i: OptionsType) => i.disabled).length === + BED_TYPES.length - 1; setIsLastOptionType(lastBedType); }, [bedTypes]); diff --git a/src/Components/Facility/FacilityBedCapacity.tsx b/src/Components/Facility/FacilityBedCapacity.tsx index 77481fa8716..a293787afce 100644 --- a/src/Components/Facility/FacilityBedCapacity.tsx +++ b/src/Components/Facility/FacilityBedCapacity.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { BED_TYPES } from "../../Common/constants"; import routes from "../../Redux/api"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import useQuery from "../../Utils/request/useQuery"; @@ -7,9 +8,11 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { BedCapacity } from "./BedCapacity"; import BedTypeCard from "./BedTypeCard"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import { BED_TYPES } from "../../Common/constants"; +import { useTranslation } from "react-i18next"; export const FacilityBedCapacity = (props: any) => { + const { t } = useTranslation(); + const [bedCapacityModalOpen, setBedCapacityModalOpen] = useState(false); const capacityQuery = useQuery(routes.getCapacity, { @@ -45,7 +48,7 @@ export const FacilityBedCapacity = (props: any) => { /> {BED_TYPES.map((x) => { const res = capacityQuery.data?.results.find((data) => { - return data.room_type === x.id; + return data.room_type === x; }); if ( res && @@ -64,7 +67,7 @@ export const FacilityBedCapacity = (props: any) => { bedCapacityId={res.id} key={`bed_${res.id}`} room_type={res.room_type} - label={x.text} + label={t(`bed_type__${x}`)} used={res.current_capacity} total={res.total_capacity} lastUpdated={res.modified_date} diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index 9c76e6e0ea0..1fe9d2e207e 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -564,7 +564,7 @@ export const FacilityCreate = (props: FacilityProps) => { /> {BED_TYPES.map((x) => { const res = capacityData.find((data) => { - return data.room_type === x.id; + return data.room_type === x; }); if (res) { const removeCurrentBedType = (bedTypeId: number | undefined) => { @@ -579,7 +579,7 @@ export const FacilityCreate = (props: FacilityProps) => { bedCapacityId={res.id} key={`bed_${res.id}`} room_type={res.room_type} - label={x.text} + label={t(`bed_type__${x}`)} used={res.current_capacity || 0} total={res.total_capacity || 0} lastUpdated={res.modified_date} diff --git a/src/Locale/en/Facility.json b/src/Locale/en/Facility.json index e061dee620e..4e835bec675 100644 --- a/src/Locale/en/Facility.json +++ b/src/Locale/en/Facility.json @@ -101,6 +101,11 @@ "duplicate_patient_record_birth_unknown": "Please contact your district care coordinator, the shifting facility or the patient themselves if you are not sure about the patient's year of birth.", "patient_transfer_birth_match_note": "Note: Year of birth must match the patient to process the transfer request.", "cover_image_updated_note": "It could take a while to see the updated cover image", + "bed_type__100": "ICU Bed", + "bed_type__200": "Ordinary Bed", + "bed_type__300": "Oxygen Supported Bed", + "bed_type__400": "Isolation Bed", + "bed_type__500": "Others", "available_features": "Available Features", "update_facility": "Update Facility", "configure_facility": "Configure Facility", From 09fa1f017daf37a171b96a080ff4a78d21b44c5c Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Mon, 23 Sep 2024 18:01:09 +0530 Subject: [PATCH 08/30] Fixes file upload missing pagination component (#8597) --- src/Components/Files/FileUpload.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Components/Files/FileUpload.tsx b/src/Components/Files/FileUpload.tsx index d827391f650..85d991d7c3b 100644 --- a/src/Components/Files/FileUpload.tsx +++ b/src/Components/Files/FileUpload.tsx @@ -364,12 +364,12 @@ export const FileUpload = (props: FileUploadProps) => {
    )}
    - {(fileQuery?.data?.results || []).length > RESULTS_PER_PAGE_LIMIT && ( + {(fileQuery?.data?.count ?? 0) > RESULTS_PER_PAGE_LIMIT && (
    From effeaf930ee357861cb0112c7fcda20393ad54cc Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 24 Sep 2024 11:34:53 +0530 Subject: [PATCH 09/30] update caniuse-lite (#8599) --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 553224ffc27..99f4f706f58 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6121,9 +6121,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001603", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001603.tgz", - "integrity": "sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==", + "version": "1.0.30001663", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz", + "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==", "funding": [ { "type": "opencollective", @@ -19228,4 +19228,4 @@ } } } -} \ No newline at end of file +} From 214b373d6c5d804ef6e0994771afb745576b7f2b Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Tue, 24 Sep 2024 08:09:39 +0200 Subject: [PATCH 10/30] Enhance the multi-select component to immediately open onFocus (#8598) --------- Co-authored-by: Khavin Shankar Co-authored-by: rithviknishad --- .../e2e/facility_spec/FacilityHomepage.cy.ts | 21 +++++++------------ .../e2e/patient_spec/PatientLogUpdate.cy.ts | 8 +++---- cypress/pageobject/Facility/FacilityHome.ts | 5 ++++- src/Components/Form/AutoCompleteAsync.tsx | 1 + .../FormFields/AutocompleteMultiselect.tsx | 1 + 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts index 11b3ec6f37f..e2a12b01a05 100644 --- a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts @@ -15,7 +15,6 @@ describe("Facility Homepage Function", () => { const userPage = new UserPage(); const assetPagination = new AssetPagination(); const facilitiesAlias = "downloadFacilitiesCSV"; - const capacitiesAlias = "downloadCapacitiesCSV"; const doctorsAlias = "downloadDoctorsCSV"; const triagesAlias = "downloadTriagesCSV"; const facilityName = "Dummy Facility 40"; @@ -91,30 +90,26 @@ describe("Facility Homepage Function", () => { }); it("Verify Facility Export Functionality", () => { - // Download the Facilities CSV + // Verify Facility Export facilityHome.csvDownloadIntercept(facilitiesAlias, ""); facilityHome.clickExportButton(); facilityHome.clickMenuItem("Facilities"); facilityHome.verifyDownload(facilitiesAlias); - facilityHome.clickSearchButton(); // to avoid flaky test, as sometimes, the test is unable to focus on the object - // Download the Capacities CSV - facilityHome.csvDownloadIntercept(capacitiesAlias, "&capacity"); - facilityHome.clickExportButton(); - facilityHome.clickMenuItem("Capacities"); - facilityHome.verifyDownload(capacitiesAlias); - facilityHome.clickSearchButton(); - // Download the Doctors CSV + // Verify Doctor Export facilityHome.csvDownloadIntercept(doctorsAlias, "&doctors"); facilityHome.clickExportButton(); facilityHome.clickMenuItem("Doctors"); facilityHome.verifyDownload(doctorsAlias); - facilityHome.clickSearchButton(); - // Download the Triages CSV + // Verify Triage Export facilityHome.csvDownloadIntercept(triagesAlias, "&triage"); facilityHome.clickExportButton(); facilityHome.clickMenuItem("Triages"); facilityHome.verifyDownload(triagesAlias); - facilityHome.clickSearchButton(); + }); + + it("Verify Capacity Export Functionality", () => { + facilityHome.clickExportButton(); + facilityHome.clickMenuItem("Capacities"); }); it("Verify Facility Detail page redirection to CNS and Live Minitoring ", () => { diff --git a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts index 923d9410657..99d4170fde5 100644 --- a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts +++ b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts @@ -17,10 +17,10 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const additionalSymptoms = "Fever"; const physicalExamination = "physical examination details"; const otherExamination = "Other"; - const patientSystolic = "119"; - const patientDiastolic = "150"; - const patientModifiedSystolic = "120"; - const patientModifiedDiastolic = "145"; + const patientSystolic = "149"; + const patientDiastolic = "119"; + const patientModifiedSystolic = "145"; + const patientModifiedDiastolic = "120"; const patientPulse = "152"; const patientTemperature = "96.6"; const patientRespiratory = "140"; diff --git a/cypress/pageobject/Facility/FacilityHome.ts b/cypress/pageobject/Facility/FacilityHome.ts index b10368717a6..956c374ada4 100644 --- a/cypress/pageobject/Facility/FacilityHome.ts +++ b/cypress/pageobject/Facility/FacilityHome.ts @@ -6,6 +6,7 @@ class FacilityHome { // Operations clickExportButton() { + cy.get(this.exportButton).scrollIntoView(); cy.get(this.exportButton).click(); } @@ -89,7 +90,9 @@ class FacilityHome { } verifyDownload(alias: string) { - cy.wait(`@${alias}`).its("response.statusCode").should("eq", 200); + cy.wait(`@${alias}`, { timeout: 60000 }) + .its("response.statusCode") + .should("eq", 200); } getURL() { diff --git a/src/Components/Form/AutoCompleteAsync.tsx b/src/Components/Form/AutoCompleteAsync.tsx index 1cd07a93df1..18bffb0e11c 100644 --- a/src/Components/Form/AutoCompleteAsync.tsx +++ b/src/Components/Form/AutoCompleteAsync.tsx @@ -93,6 +93,7 @@ const AutoCompleteAsync = (props: Props) => { onChange={onChange} by={compareBy} multiple={multiple as any} + immediate >
    diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx index a727db8b749..d8bde2dba03 100644 --- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx @@ -110,6 +110,7 @@ export const AutocompleteMutliSelect = ( return (
    Date: Tue, 24 Sep 2024 15:18:17 +0530 Subject: [PATCH 11/30] Show IV/NIV Badge only for active consultations (#8603) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- .../e2e/patient_spec/PatientLogUpdate.cy.ts | 224 +++++++++--------- package-lock.json | 19 +- package.json | 3 +- src/Components/Patient/ManagePatients.tsx | 3 +- 4 files changed, 134 insertions(+), 115 deletions(-) diff --git a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts index 99d4170fde5..7faaeed5a9f 100644 --- a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts +++ b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts @@ -12,7 +12,6 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const patientLogupdate = new PatientLogupdate(); const patientInvestigation = new PatientInvestigation(); const patientPrescription = new PatientPrescription(); - const domicilaryPatient = "Dummy Patient 11"; const patientCategory = "Moderate"; const additionalSymptoms = "Fever"; const physicalExamination = "physical examination details"; @@ -33,6 +32,13 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const patientInsulinDosage = "56"; const patientFluidBalance = "500"; const patientNetBalance = "1000"; + const patientOne = "Dummy Patient 9"; + const bedOne = "Dummy Bed 5"; + const patientTwo = "Dummy Patient 10"; + const bedTwo = "Dummy Bed 2"; + const patientThree = "Dummy Patient 8"; + const bedThree = "Dummy Bed 3"; + const domicilaryPatient = "Dummy Patient 11"; before(() => { loginPage.loginAsDisctrictAdmin(); @@ -45,15 +51,110 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.awaitUrl("/patients"); }); - it("Create a basic critical care log update for a admitted patient and edit it", () => { - patientPage.visitPatient("Dummy Patient 10"); + it("Create a new TeleIcu log update for a domicilary care patient", () => { + patientPage.visitPatient(domicilaryPatient); + patientConsultationPage.clickEditConsultationButton(); + patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); + cy.submitButton("Update Consultation"); + cy.verifyNotification("Consultation updated successfully"); + cy.closeNotification(); + patientLogupdate.clickLogupdate(); + patientLogupdate.typePhysicalExamination(physicalExamination); + patientLogupdate.selectRoundType("Tele-medicine Log"); + patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.typeOtherDetails(otherExamination); + patientLogupdate.selectSymptomsDate("01012024"); + patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); + patientLogupdate.typeSystolic(patientSystolic); + patientLogupdate.typeDiastolic(patientDiastolic); + patientLogupdate.typePulse(patientPulse); + patientLogupdate.typeTemperature(patientTemperature); + patientLogupdate.typeRespiratory(patientRespiratory); + patientLogupdate.typeSpo2(patientSpo2); + patientLogupdate.selectRhythm(patientRhythmType); + patientLogupdate.typeRhythm(patientRhythm); + cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); + cy.submitButton("Save"); + cy.verifyNotification("Tele-medicine Log created successfully"); + }); + + it("Create a new Progress log update for a admitted patient and edit it", () => { + patientPage.visitPatient(patientOne); patientLogupdate.clickLogupdate(); cy.verifyNotification("Please assign a bed to the patient"); - patientLogupdate.selectBed("Dummy Bed 2"); + patientLogupdate.selectBed(bedOne); cy.closeNotification(); patientLogupdate.clickLogupdate(); + // Only will be using random non-unique progress note fields + patientLogupdate.selectRoundType("Progress Note"); patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.selectSymptomsDate("01012024"); + patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); + patientLogupdate.typeTemperature(patientTemperature); + // add diagnosis + patientConsultationPage.selectPatientDiagnosis( + "1A06", + "add-icd11-diagnosis-as-differential", + ); + // add a investigation for the patient + patientInvestigation.clickAddInvestigation(); + patientInvestigation.selectInvestigation("Vitals (GROUP)"); + patientInvestigation.clickInvestigationCheckbox(); + patientInvestigation.selectInvestigationFrequency("6"); + // add a medicine for the patient + patientPrescription.clickAddPrescription(); + patientPrescription.interceptMedibase(); + patientPrescription.selectMedicinebox(); + patientPrescription.selectMedicine("DOLO"); + patientPrescription.enterDosage("4"); + patientPrescription.selectDosageFrequency("Twice daily"); + cy.submitButton("Submit"); + cy.verifyNotification("Medicine prescribed"); + cy.closeNotification(); + // Submit the doctors log update + cy.submitButton("Save and Continue"); + cy.verifyNotification("Progress Note created successfully"); + cy.closeNotification(); + // modify the relevant critical care log update + patientLogupdate.selectCriticalCareSection("Neurological Monitoring"); + cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); + cy.get("#left_pupil_light_reaction-option-FIXED").click(); + cy.submitButton("Update Details"); + cy.verifyNotification( + "Neurological Monitoring details succesfully updated.", + ); + cy.closeNotification(); + // Final Submission of the form + cy.submitButton("Complete"); + cy.verifyNotification("Progress Note Log Update filed successfully"); + cy.closeNotification(); + // Verify the data reflection + cy.contains("button", "Daily Rounds").click(); + patientLogupdate.clickLogUpdateViewDetails( + "#dailyround-entry", + patientCategory, + ); + cy.verifyContentPresence("#consultation-preview", [ + patientCategory, + patientTemperature, + ]); + // verify the edit functionality + patientLogupdate.clickUpdateDetail(); + patientLogupdate.typeSystolic(patientModifiedSystolic); + patientLogupdate.typeDiastolic(patientModifiedDiastolic); + cy.submitButton("Continue"); + cy.verifyNotification("Progress Note updated successfully"); + }); + + it("Create a basic critical care log update for a admitted patient and edit it", () => { + patientPage.visitPatient(patientTwo); + patientLogupdate.clickLogupdate(); + cy.verifyNotification("Please assign a bed to the patient"); + patientLogupdate.selectBed(bedTwo); + cy.closeNotification(); + patientLogupdate.clickLogupdate(); patientLogupdate.selectRoundType("Detailed Update"); + patientLogupdate.selectPatientCategory(patientCategory); cy.submitButton("Save and Continue"); cy.verifyNotification("Detailed Update created successfully"); cy.closeNotification(); @@ -127,88 +228,19 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { ]); }); - it("Create a new Progress log update for a admitted patient and edit it", () => { - patientPage.visitPatient("Dummy Patient 12"); + it("Create a new Normal update for a admission patient and verify its reflection in cards", () => { + patientPage.visitPatient(patientThree); patientLogupdate.clickLogupdate(); cy.verifyNotification("Please assign a bed to the patient"); - patientLogupdate.selectBed("Dummy Bed 4"); - cy.closeNotification(); - patientLogupdate.clickLogupdate(); - // Only will be using random non-unique progress note fields - patientLogupdate.selectPatientCategory(patientCategory); - patientLogupdate.selectRoundType("Progress Note"); - patientLogupdate.selectSymptomsDate("01012024"); - patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.typeTemperature(patientTemperature); - // add diagnosis - patientConsultationPage.selectPatientDiagnosis( - "1A06", - "add-icd11-diagnosis-as-differential", - ); - // add a investigation for the patient - patientInvestigation.clickAddInvestigation(); - patientInvestigation.selectInvestigation("Vitals (GROUP)"); - patientInvestigation.clickInvestigationCheckbox(); - patientInvestigation.selectInvestigationFrequency("6"); - // add a medicine for the patient - patientPrescription.clickAddPrescription(); - patientPrescription.interceptMedibase(); - patientPrescription.selectMedicinebox(); - patientPrescription.selectMedicine("DOLO"); - patientPrescription.enterDosage("4"); - patientPrescription.selectDosageFrequency("Twice daily"); - cy.submitButton("Submit"); - cy.verifyNotification("Medicine prescribed"); - cy.closeNotification(); - // Submit the doctors log update - cy.submitButton("Save and Continue"); - cy.verifyNotification("Progress Note created successfully"); - cy.closeNotification(); - // modify the relevant critical care log update - patientLogupdate.selectCriticalCareSection("Neurological Monitoring"); - cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); - cy.get("#left_pupil_light_reaction-option-FIXED").click(); - cy.submitButton("Update Details"); - cy.verifyNotification( - "Neurological Monitoring details succesfully updated.", - ); - cy.closeNotification(); - // Final Submission of the form - cy.submitButton("Complete"); - cy.verifyNotification("Progress Note Log Update filed successfully"); - cy.closeNotification(); - // Verify the data reflection - cy.contains("button", "Daily Rounds").click(); - patientLogupdate.clickLogUpdateViewDetails( - "#dailyround-entry", - patientCategory, - ); - cy.verifyContentPresence("#consultation-preview", [ - patientCategory, - patientTemperature, - ]); - // verify the edit functionality - patientLogupdate.clickUpdateDetail(); - patientLogupdate.typeSystolic(patientModifiedSystolic); - patientLogupdate.typeDiastolic(patientModifiedDiastolic); - cy.submitButton("Continue"); - cy.verifyNotification("Progress Note updated successfully"); - }); - - it("Create a new TeleIcu log update for a domicilary care patient", () => { - patientPage.visitPatient("Dummy Patient 11"); - patientConsultationPage.clickEditConsultationButton(); - patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); - cy.submitButton("Update Consultation"); - cy.verifyNotification("Consultation updated successfully"); + patientLogupdate.selectBed(bedThree); cy.closeNotification(); patientLogupdate.clickLogupdate(); patientLogupdate.typePhysicalExamination(physicalExamination); - patientLogupdate.selectRoundType("Tele-medicine Log"); + patientLogupdate.selectPatientCategory(patientCategory); patientLogupdate.typeOtherDetails(otherExamination); patientLogupdate.selectSymptomsDate("01012024"); patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.clickAddSymptom(); patientLogupdate.typeSystolic(patientSystolic); patientLogupdate.typeDiastolic(patientDiastolic); patientLogupdate.typePulse(patientPulse); @@ -219,7 +251,11 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.typeRhythm(patientRhythm); cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); cy.submitButton("Save"); - cy.verifyNotification("Tele-medicine Log created successfully"); + cy.wait(2000); + cy.verifyNotification("Brief Update created successfully"); + // Verify the card content + cy.get("#basic-information").scrollIntoView(); + cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]); }); it("Create a new Normal Log update for a domicilary care patient and edit it", () => { @@ -283,36 +319,6 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { ]); }); - it("Create a new Normal update for a admission patient and verify its reflection in cards", () => { - patientPage.visitPatient("Dummy Patient 13"); - patientLogupdate.clickLogupdate(); - cy.verifyNotification("Please assign a bed to the patient"); - patientLogupdate.selectBed("Dummy Bed 6"); - cy.closeNotification(); - patientLogupdate.clickLogupdate(); - patientLogupdate.typePhysicalExamination(physicalExamination); - patientLogupdate.typeOtherDetails(otherExamination); - patientLogupdate.selectSymptomsDate("01012024"); - patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.clickAddSymptom(); - patientLogupdate.selectPatientCategory(patientCategory); - patientLogupdate.typeSystolic(patientSystolic); - patientLogupdate.typeDiastolic(patientDiastolic); - patientLogupdate.typePulse(patientPulse); - patientLogupdate.typeTemperature(patientTemperature); - patientLogupdate.typeRespiratory(patientRespiratory); - patientLogupdate.typeSpo2(patientSpo2); - patientLogupdate.selectRhythm(patientRhythmType); - patientLogupdate.typeRhythm(patientRhythm); - cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); - cy.submitButton("Save"); - cy.wait(2000); - cy.verifyNotification("Brief Update created successfully"); - // Verify the card content - cy.get("#basic-information").scrollIntoView(); - cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]); - }); - it("Create a Normal Log update to verify MEWS Score Functionality", () => { patientPage.visitPatient(domicilaryPatient); patientConsultationPage.clickEditConsultationButton(); diff --git a/package-lock.json b/package-lock.json index 99f4f706f58..051c12bd2b4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -56,6 +56,7 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", + "@types/cypress": "^1.1.3", "@types/events": "^3.0.3", "@types/google.maps": "^3.55.8", "@types/lodash-es": "^4.17.12", @@ -68,7 +69,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.19", - "cypress": "^13.14.1", + "cypress": "^13.14.2", "cypress-localstorage-commands": "^2.2.5", "cypress-split": "^1.23.2", "eslint-config-prettier": "^9.1.0", @@ -4425,6 +4426,16 @@ "@types/node": "*" } }, + "node_modules/@types/cypress": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/cypress/-/cypress-1.1.3.tgz", + "integrity": "sha512-OXe0Gw8LeCflkG1oPgFpyrYWJmEKqYncBsD/J0r17r0ETx/TnIGDNLwXt/pFYSYuYTpzcq1q3g62M9DrfsBL4g==", + "deprecated": "This is a stub types definition for cypress (https://cypress.io). cypress provides its own type definitions, so you don't need @types/cypress installed!", + "dev": true, + "dependencies": { + "cypress": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -6629,9 +6640,9 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cypress": { - "version": "13.14.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.1.tgz", - "integrity": "sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg==", + "version": "13.14.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.2.tgz", + "integrity": "sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==", "dev": true, "hasInstallScript": true, "dependencies": { diff --git a/package.json b/package.json index cc2c7b955c2..62cdc8a0fb9 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", + "@types/cypress": "^1.1.3", "@types/events": "^3.0.3", "@types/google.maps": "^3.55.8", "@types/lodash-es": "^4.17.12", @@ -103,7 +104,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.19", - "cypress": "^13.14.1", + "cypress": "^13.14.2", "cypress-localstorage-commands": "^2.2.5", "cypress-split": "^1.23.2", "eslint-config-prettier": "^9.1.0", diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 944a5163c96..7a5b0d28a38 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -721,7 +721,8 @@ export const PatientManager = () => { {patient.last_consultation?.last_daily_round ?.ventilator_interface && patient.last_consultation?.last_daily_round - ?.ventilator_interface !== "UNKNOWN" && ( + ?.ventilator_interface !== "UNKNOWN" && + !patient.last_consultation?.discharge_date && (
    { RESPIRATORY_SUPPORT.find( From 5accddc9be467b72a58dc91dda54b53da4da3d72 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Tue, 24 Sep 2024 15:54:44 +0530 Subject: [PATCH 12/30] fix i18n for sort options (#8602) --- src/Common/hooks/useFilters.tsx | 2 +- src/Components/Common/SortDropdown.tsx | 2 +- .../Facility/CentralNursingStation.tsx | 2 +- src/Locale/en/Common.json | 20 ++++++++++++-- src/Locale/en/SortOptions.json | 18 ------------- src/Locale/en/index.js | 2 -- src/Locale/hi/Common.json | 18 ++++++++++++- src/Locale/hi/SortOptions.json | 18 ------------- src/Locale/kn/Common.json | 18 ++++++++++++- src/Locale/kn/SortOptions.json | 18 ------------- src/Locale/kn/index.js | 26 +++++++++++++++++++ src/Locale/ml/Common.json | 18 ++++++++++++- src/Locale/ml/SortOptions.json | 18 ------------- src/Locale/ta/Common.json | 18 ++++++++++++- src/Locale/ta/SortOptions.json | 18 ------------- 15 files changed, 115 insertions(+), 101 deletions(-) delete mode 100644 src/Locale/en/SortOptions.json delete mode 100644 src/Locale/hi/SortOptions.json delete mode 100644 src/Locale/kn/SortOptions.json delete mode 100644 src/Locale/ml/SortOptions.json delete mode 100644 src/Locale/ta/SortOptions.json diff --git a/src/Common/hooks/useFilters.tsx b/src/Common/hooks/useFilters.tsx index a4b924edcdb..188dec2229d 100644 --- a/src/Common/hooks/useFilters.tsx +++ b/src/Common/hooks/useFilters.tsx @@ -109,7 +109,7 @@ export default function useFilters({ return { name, paramKey, - value: qParams[paramKey] && t("SortOptions." + qParams[paramKey]), + value: qParams[paramKey] && t("SORT_OPTIONS__" + qParams[paramKey]), }; }, value(name: string, paramKey: FilterBadgeProps["paramKey"], value: string) { diff --git a/src/Components/Common/SortDropdown.tsx b/src/Components/Common/SortDropdown.tsx index 8ffd09ba269..b54edffa595 100644 --- a/src/Components/Common/SortDropdown.tsx +++ b/src/Components/Common/SortDropdown.tsx @@ -41,7 +41,7 @@ export default function SortDropdownMenu(props: Props) { /> } > - {t("SortOptions." + value)} + {t("SORT_OPTIONS__" + value)} ))} diff --git a/src/Components/Facility/CentralNursingStation.tsx b/src/Components/Facility/CentralNursingStation.tsx index 178bd2bc25c..fbbdf24d7f2 100644 --- a/src/Components/Facility/CentralNursingStation.tsx +++ b/src/Components/Facility/CentralNursingStation.tsx @@ -145,7 +145,7 @@ export default function CentralNursingStation({ facilityId }: Props) { value={qParams.ordering || "bed__name"} onChange={({ value }) => updateQuery({ ordering: value })} options={SORT_OPTIONS} - optionLabel={({ value }) => t("SortOptions." + value)} + optionLabel={({ value }) => t("SORT_OPTIONS__" + value)} optionIcon={({ isAscending }) => ( Date: Tue, 24 Sep 2024 15:14:04 +0200 Subject: [PATCH 13/30] switch bed functionality test (#8613) --- .../patient_spec/PatientBedManagement.cy.ts | 56 +++++++++++++++++++ .../pageobject/Patient/PatientLogupdate.ts | 6 +- src/CAREUI/display/Timeline.tsx | 2 +- .../Facility/Consultations/Beds.tsx | 2 +- src/Components/Patient/PatientInfoCard.tsx | 1 + 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 cypress/e2e/patient_spec/PatientBedManagement.cy.ts diff --git a/cypress/e2e/patient_spec/PatientBedManagement.cy.ts b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts new file mode 100644 index 00000000000..d9453806c9f --- /dev/null +++ b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts @@ -0,0 +1,56 @@ +import LoginPage from "../../pageobject/Login/LoginPage"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; +import PatientLogupdate from "../../pageobject/Patient/PatientLogupdate"; +import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; + +describe("Patient swtich bed functionality", () => { + const loginPage = new LoginPage(); + const patientPage = new PatientPage(); + const patientLogupdate = new PatientLogupdate(); + const patientConsultationPage = new PatientConsultationPage(); + const switchBedOne = "Dummy Bed 4"; + const switchBedTwo = "Dummy Bed 1"; + const switchBedThree = "Dummy Bed 3"; + const switchPatientOne = "Dummy Patient 6"; + const switchPatientTwo = "Dummy Patient 7"; + + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Assign a bed for a admitted patient from update consultation page", () => { + // Open the update consultation page and assign a bed + patientPage.visitPatient(switchPatientTwo); + patientConsultationPage.clickEditConsultationButton(); + patientLogupdate.selectBed(switchBedThree); + // verify the notification + cy.verifyNotification("Bed allocated successfully"); + }); + + it("Assign a bed for a admitted patient from patient dashboard", () => { + // Assign a bed to a patient + patientPage.visitPatient(switchPatientOne); + patientLogupdate.clickSwitchBed(); + patientLogupdate.selectBed(switchBedOne); + cy.verifyNotification("Bed allocated successfully"); + // Clear the bed and reassign + patientLogupdate.clickSwitchBed(); + cy.get("#clear-button").click(); + patientLogupdate.selectBed(switchBedTwo); + cy.verifyNotification("Bed allocated successfully"); + // verify the card is reflected + patientLogupdate.clickSwitchBed(); + cy.verifyContentPresence("#previousbed-list", [switchBedOne, switchBedTwo]); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts index 8a4c11ab25b..add3fbb0590 100644 --- a/cypress/pageobject/Patient/PatientLogupdate.ts +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -5,13 +5,17 @@ class PatientLogupdate { cy.wait(2000); } + clickSwitchBed() { + cy.get("#switch-bed").click(); + } + selectRoundType(roundType: string) { cy.clickAndSelectOption("#rounds_type", roundType); } selectBed(bed: string) { cy.searchAndSelectOption("input[name='bed']", bed); - cy.submitButton("Update"); + cy.get("#update-switchbed").click(); cy.wait(2000); } diff --git a/src/CAREUI/display/Timeline.tsx b/src/CAREUI/display/Timeline.tsx index 7fcb57f56ea..ee21b337724 100644 --- a/src/CAREUI/display/Timeline.tsx +++ b/src/CAREUI/display/Timeline.tsx @@ -26,7 +26,7 @@ const TimelineContext = createContext(""); export default function Timeline({ className, children, name }: TimelineProps) { return ( -
    +
      {children} diff --git a/src/Components/Facility/Consultations/Beds.tsx b/src/Components/Facility/Consultations/Beds.tsx index 0333ec40f63..0b62af9fdcb 100644 --- a/src/Components/Facility/Consultations/Beds.tsx +++ b/src/Components/Facility/Consultations/Beds.tsx @@ -231,7 +231,7 @@ const Beds = (props: BedsProps) => {
    - + Update diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 300c2190a60..1f581b9b0e7 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -230,6 +230,7 @@ export default function PatientInfoCard(props: { )} {consultation?.admitted && ( setOpen(true)} className="mt-1 px-[10px] py-1" From ea578313545013649da6a6bd3c0e609811c47eb3 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:12:50 +0200 Subject: [PATCH 14/30] patient pagination test (#8614) --- .../e2e/patient_spec/PatientHomepage.cy.ts | 42 +++++++++++++++++++ cypress/pageobject/Patient/PatientHome.ts | 14 +++++++ 2 files changed, 56 insertions(+) create mode 100644 cypress/e2e/patient_spec/PatientHomepage.cy.ts create mode 100644 cypress/pageobject/Patient/PatientHome.ts diff --git a/cypress/e2e/patient_spec/PatientHomepage.cy.ts b/cypress/e2e/patient_spec/PatientHomepage.cy.ts new file mode 100644 index 00000000000..c1575057fe4 --- /dev/null +++ b/cypress/e2e/patient_spec/PatientHomepage.cy.ts @@ -0,0 +1,42 @@ +import LoginPage from "../../pageobject/Login/LoginPage"; +import PatientHome from "../../pageobject/Patient/PatientHome"; + +describe("Patient Homepage present functionalities", () => { + const loginPage = new LoginPage(); + const patientHome = new PatientHome(); + + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Verify the functionality of the patient tab pagination", () => { + let firstPatientPageOne: string; + cy.get('[data-cy="patient"]') + .first() + .invoke("text") + .then((patientOne: string) => { + firstPatientPageOne = patientOne.trim(); + patientHome.clickNextPage(); + patientHome.verifySecondPageUrl(); + cy.get('[data-cy="patient"]') + .first() + .invoke("text") + .then((patientTwo: string) => { + const firstPatientPageTwo = patientTwo.trim(); + expect(firstPatientPageOne).not.to.eq(firstPatientPageTwo); + patientHome.clickPreviousPage(); + }); + }); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/pageobject/Patient/PatientHome.ts b/cypress/pageobject/Patient/PatientHome.ts new file mode 100644 index 00000000000..94801cd4bb8 --- /dev/null +++ b/cypress/pageobject/Patient/PatientHome.ts @@ -0,0 +1,14 @@ +class PatientHome { + clickNextPage() { + cy.get("#next-pages").click(); + } + + verifySecondPageUrl() { + cy.url().should("include", "/patients?page=2"); + } + + clickPreviousPage() { + cy.get("#prev-pages").click(); + } +} +export default PatientHome; From a0f5c523f8248be6f7924dbfde7af5f308803c50 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 24 Sep 2024 20:13:10 +0530 Subject: [PATCH 15/30] enabled filter (#7933) Co-authored-by: Rithvik Nishad --- src/Components/Patient/PatientFilter.tsx | 40 +++++++++++------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/Components/Patient/PatientFilter.tsx b/src/Components/Patient/PatientFilter.tsx index 773a3f333b7..f496a59df6d 100644 --- a/src/Components/Patient/PatientFilter.tsx +++ b/src/Components/Patient/PatientFilter.tsx @@ -343,27 +343,25 @@ export default function PatientFilter(props: any) { />
    - {props.dischargePage || ( -
    - - {props.dischargePage && "Last "}Admitted to (Bed Types) - - o.id} - optionLabel={(o) => o.text} - onChange={(o) => - setFilterState({ - ...filterState, - last_consultation_admitted_bed_type_list: o, - }) - } - /> -
    - )} +
    + + {props.dischargePage && "Last "}Admitted to (Bed Types) + + o.id} + optionLabel={(o) => o.text} + onChange={(o) => + setFilterState({ + ...filterState, + last_consultation_admitted_bed_type_list: o, + }) + } + /> +
    ); diff --git a/src/Components/Facility/DoctorNote.tsx b/src/Components/Facility/DoctorNote.tsx index 91c361cc82e..c2bf8155cf2 100644 --- a/src/Components/Facility/DoctorNote.tsx +++ b/src/Components/Facility/DoctorNote.tsx @@ -1,17 +1,19 @@ import InfiniteScroll from "react-infinite-scroll-component"; import CircularProgress from "../Common/components/CircularProgress"; import PatientNoteCard from "./PatientNoteCard"; -import { PatientNoteStateType } from "./models"; +import { PatientNoteStateType, PatientNotesModel } from "./models"; +import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard"; interface DoctorNoteProps { state: PatientNoteStateType; setReload: any; handleNext: () => void; disableEdit?: boolean; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; } const DoctorNote = (props: DoctorNoteProps) => { - const { state, handleNext, setReload, disableEdit } = props; + const { state, handleNext, setReload, disableEdit, setReplyTo } = props; return (
    { scrollableTarget="patient-notes-list" > {state.notes.map((note) => ( - + parentNote={note.reply_to_object} + > + + ))} ) : ( diff --git a/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx b/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx new file mode 100644 index 00000000000..470f05f2bfe --- /dev/null +++ b/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { PaitentNotesReplyModel } from "./models"; +import { USER_TYPES_MAP } from "../../Common/constants"; +import { formatDateTime, relativeDate } from "../../Utils/utils"; + +interface Props { + parentNote: PaitentNotesReplyModel | undefined; + children: React.ReactNode; + cancelReply?: () => void; +} + +const DoctorNoteReplyPreviewCard = ({ + parentNote, + children, + cancelReply, +}: Props) => { + return ( +
    + {parentNote ? ( +
    +
    +
    +
    +
    + + {parentNote.created_by_object?.first_name || "Unknown"}{" "} + {parentNote.created_by_object?.last_name} + + {parentNote.user_type && ( + + {`(${USER_TYPES_MAP[parentNote.user_type]})`} + + )} +
    +
    +
    + + {formatDateTime(parentNote.created_date)} + + Created {relativeDate(parentNote.created_date, true)} +
    +
    +
    + {cancelReply && ( +
    + Cancel +
    + )} +
    +
    {parentNote.note}
    +
    +
    {children}
    +
    + ) : ( +
    {children}
    + )} +
    + ); +}; + +export default DoctorNoteReplyPreviewCard; diff --git a/src/Components/Facility/PatientConsultationNotesList.tsx b/src/Components/Facility/PatientConsultationNotesList.tsx index f81ef122f6c..15238ff189f 100644 --- a/src/Components/Facility/PatientConsultationNotesList.tsx +++ b/src/Components/Facility/PatientConsultationNotesList.tsx @@ -14,12 +14,21 @@ interface PatientNotesProps { setReload?: (value: boolean) => void; disableEdit?: boolean; thread: PatientNotesModel["thread"]; + setReplyTo?: (value: PatientNotesModel | undefined) => void; } const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientConsultationNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, disableEdit, thread } = props; + const { + state, + setState, + reload, + setReload, + disableEdit, + thread, + setReplyTo, + } = props; const consultationId = useSlug("consultation") ?? ""; const [isLoading, setIsLoading] = useState(true); @@ -95,6 +104,7 @@ const PatientConsultationNotesList = (props: PatientNotesProps) => { handleNext={handleNext} setReload={setReload} disableEdit={disableEdit} + setReplyTo={setReplyTo} /> ); }; diff --git a/src/Components/Facility/PatientNoteCard.tsx b/src/Components/Facility/PatientNoteCard.tsx index 7d2a8c6eb70..9ddfbc009e6 100644 --- a/src/Components/Facility/PatientNoteCard.tsx +++ b/src/Components/Facility/PatientNoteCard.tsx @@ -23,10 +23,12 @@ const PatientNoteCard = ({ note, setReload, disableEdit, + setReplyTo, }: { note: PatientNotesModel; setReload: any; disableEdit?: boolean; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; }) => { const patientId = useSlug("patient"); const [isEditing, setIsEditing] = useState(false); @@ -128,19 +130,28 @@ const PatientNoteCard = ({ ) }
    - - {!disableEdit && - note.created_by_object.id === authUser.id && - !isEditing && ( - { - setIsEditing(true); - }} - > - - - )} +
    + {!disableEdit && + note.created_by_object.id === authUser.id && + !isEditing && ( + { + setIsEditing(true); + }} + > + + + )} + { + setReplyTo && setReplyTo(note); + }} + > + + +
    {
    diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 4e1d0a5a7a6..bbd037e866c 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -14,12 +14,13 @@ interface PatientNotesProps { reload?: boolean; setReload?: any; thread: PatientNotesModel["thread"]; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; } const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, thread } = props; + const { state, setState, reload, setReload, thread, setReplyTo } = props; const [isLoading, setIsLoading] = useState(true); @@ -83,7 +84,12 @@ const PatientNotesList = (props: PatientNotesProps) => { } return ( - + ); }; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index d7847c3add7..52f99aee763 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -8,11 +8,12 @@ import { useMessageListener } from "../../Common/hooks/useMessageListener"; import PatientConsultationNotesList from "./PatientConsultationNotesList"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; -import { PatientNoteStateType } from "./models"; +import { PatientNoteStateType, PaitentNotesReplyModel } from "./models"; import useKeyboardShortcut from "use-keyboard-shortcut"; import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { PATIENT_NOTES_THREADS } from "../../Common/constants.js"; +import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard.js"; import useNotificationSubscriptionState from "../../Common/hooks/useNotificationSubscriptionState.js"; import { Link } from "raviger"; import { t } from "i18next"; @@ -36,6 +37,9 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const [patientActive, setPatientActive] = useState(true); const [reload, setReload] = useState(false); const [focused, setFocused] = useState(false); + const [reply_to, setReplyTo] = useState( + undefined, + ); useEffect(() => { if (notificationSubscriptionState === "unsubscribed") { @@ -79,6 +83,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { note: noteField, consultation: consultationId, thread, + reply_to: reply_to?.id, }, }); if (res?.status === 201) { @@ -86,6 +91,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setNoteField(""); setState({ ...state, cPage: 1 }); setReload(true); + setReplyTo(undefined); } }; @@ -216,36 +222,42 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setReload={setReload} disableEdit={!patientActive} thread={thread} + setReplyTo={setReplyTo} /> -
    - setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - - -
    + setReplyTo(undefined)} + > +
    + setNoteField(e.value)} + className="w-full grow" + errorClassName="hidden" + innerClassName="pr-10" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + /> + + + +
    +
    )}
    diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index cdb78a5f712..7756bf73f69 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -546,6 +546,14 @@ export interface PatientNotesEditModel { note: string; } +export interface PaitentNotesReplyModel { + id: string; + note: string; + user_type?: UserRole | "RemoteSpecialist"; + created_by_object: BaseUserModel; + created_date: string; +} + export interface PatientNotesModel { id: string; note: string; @@ -556,6 +564,7 @@ export interface PatientNotesModel { created_date: string; last_edited_by?: BaseUserModel; last_edited_date?: string; + reply_to_object?: PaitentNotesReplyModel; } export interface PatientNoteStateType { diff --git a/src/Components/Patient/PatientNotes.tsx b/src/Components/Patient/PatientNotes.tsx index 7138f4df8e5..da97e5d3f4f 100644 --- a/src/Components/Patient/PatientNotes.tsx +++ b/src/Components/Patient/PatientNotes.tsx @@ -6,11 +6,12 @@ import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import PatientNotesList from "../Facility/PatientNotesList"; import Page from "../Common/components/Page"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; -import { PatientNoteStateType } from "../Facility/models"; +import { PatientNoteStateType, PatientNotesModel } from "../Facility/models"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; import { PATIENT_NOTES_THREADS } from "../../Common/constants.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; +import DoctorNoteReplyPreviewCard from "../Facility/DoctorNoteReplyPreviewCard.js"; import { classNames, keysOf } from "../../Utils/utils.js"; import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; import { t } from "i18next"; @@ -35,6 +36,9 @@ const PatientNotes = (props: PatientNotesProps) => { const [reload, setReload] = useState(false); const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); + const [reply_to, setReplyTo] = useState( + undefined, + ); const initialData: PatientNoteStateType = { notes: [], @@ -56,6 +60,7 @@ const PatientNotes = (props: PatientNotesProps) => { body: { note: noteField, thread, + reply_to: reply_to?.id, }, }); if (res?.status === 201) { @@ -63,6 +68,7 @@ const PatientNotes = (props: PatientNotesProps) => { setNoteField(""); setReload(!reload); setState({ ...state, cPage: 1 }); + setReplyTo(undefined); } }; @@ -130,33 +136,38 @@ const PatientNotes = (props: PatientNotesProps) => { reload={reload} setReload={setReload} thread={thread} + setReplyTo={setReplyTo} /> - -
    - setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - /> - - - -
    + setReplyTo(undefined)} + > +
    + setNoteField(e.value)} + className="w-full grow" + errorClassName="hidden" + innerClassName="pr-10" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + /> + + + +
    +
    ); diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index e53342e3d7c..0243db57df7 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -774,7 +774,10 @@ const routes = { method: "POST", TRes: Type(), TBody: Type< - Pick & { consultation?: string } + Pick & { + consultation?: string; + reply_to?: string; + } >(), }, updatePatientNote: { From 7878cd6ed70e5c648f31401246bc5f65d90627dc Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Tue, 24 Sep 2024 20:57:01 +0530 Subject: [PATCH 18/30] Added facility hubs (#7772) --- src/Common/constants.tsx | 16 +- src/Components/Common/FacilitySelect.tsx | 6 + src/Components/Facility/FacilityBlock.tsx | 34 ++++ src/Components/Facility/FacilityCreate.tsx | 11 +- src/Components/Facility/FacilityHome.tsx | 22 +++ .../Facility/SpokeFacilityEditor.tsx | 154 ++++++++++++++++++ src/Components/Facility/models.tsx | 33 +++- src/Components/Form/AutoCompleteAsync.tsx | 6 +- src/Components/Form/ModelCrudEditor.tsx | 153 +++++++++++++++++ src/Locale/en/Common.json | 3 + src/Redux/api.tsx | 36 +++- 11 files changed, 463 insertions(+), 11 deletions(-) create mode 100644 src/Components/Facility/FacilityBlock.tsx create mode 100644 src/Components/Facility/SpokeFacilityEditor.tsx create mode 100644 src/Components/Form/ModelCrudEditor.tsx diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index 307e912844f..70b5bc78e23 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1,4 +1,7 @@ -import { PatientCategory } from "../Components/Facility/models"; +import { + PatientCategory, + SpokeRelationship, +} from "../Components/Facility/models"; import { SortOption } from "../Components/Common/SortDropdown"; import { dateQueryString } from "../Utils/utils"; import { IconName } from "../CAREUI/icons/CareIcon"; @@ -1517,6 +1520,17 @@ export const DEFAULT_ALLOWED_EXTENSIONS = [ "application/vnd.oasis.opendocument.spreadsheet,application/pdf", ]; +export const SPOKE_RELATION_TYPES = [ + { + text: "Regular", + value: SpokeRelationship.REGULAR, + }, + { + text: "Tele ICU", + value: SpokeRelationship.TELE_ICU, + }, +]; + export const HumanBodyPaths = { anterior: [ { diff --git a/src/Components/Common/FacilitySelect.tsx b/src/Components/Common/FacilitySelect.tsx index d718ef3e781..d91b9a7f8fc 100644 --- a/src/Components/Common/FacilitySelect.tsx +++ b/src/Components/Common/FacilitySelect.tsx @@ -23,6 +23,8 @@ interface FacilitySelectProps { selected?: FacilityModel | FacilityModel[] | null; setSelected: (selected: FacilityModel | FacilityModel[] | null) => void; allowNone?: boolean; + placeholder?: string; + filter?: (facilities: FacilityModel) => boolean; } export const FacilitySelect = (props: FacilitySelectProps) => { @@ -44,6 +46,8 @@ export const FacilitySelect = (props: FacilitySelectProps) => { allowNone = false, freeText = false, errors = "", + placeholder, + filter, } = props; const facilitySearch = useCallback( @@ -82,6 +86,7 @@ export const FacilitySelect = (props: FacilitySelectProps) => { return ( { compareBy="id" className={className} error={errors} + filter={filter} /> ); }; diff --git a/src/Components/Facility/FacilityBlock.tsx b/src/Components/Facility/FacilityBlock.tsx new file mode 100644 index 00000000000..64c0a24d78d --- /dev/null +++ b/src/Components/Facility/FacilityBlock.tsx @@ -0,0 +1,34 @@ +import { Link } from "raviger"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { FacilityModel } from "./models"; + +export default function FacilityBlock(props: { facility: FacilityModel }) { + const { facility } = props; + + return ( + +
    + {facility.read_cover_image_url ? ( + + ) : ( + <> + + + )} +
    +
    + {facility.name} +

    + {facility.address} {facility.local_body_object?.name} +

    +
    + + ); +} diff --git a/src/Components/Facility/FacilityCreate.tsx b/src/Components/Facility/FacilityCreate.tsx index 1fe9d2e207e..e9e95fbcdfc 100644 --- a/src/Components/Facility/FacilityCreate.tsx +++ b/src/Components/Facility/FacilityCreate.tsx @@ -62,6 +62,7 @@ import routes from "../../Redux/api.js"; import useQuery from "../../Utils/request/useQuery.js"; import { RequestResult } from "../../Utils/request/types.js"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import SpokeFacilityEditor from "./SpokeFacilityEditor.js"; import careConfig from "@careConfig"; const Loading = lazy(() => import("../Common/Loading")); @@ -247,7 +248,7 @@ export const FacilityCreate = (props: FacilityProps) => { }, ); - useQuery(routes.getPermittedFacility, { + const facilityQuery = useQuery(routes.getPermittedFacility, { pathParams: { id: facilityId!, }, @@ -850,6 +851,14 @@ export const FacilityCreate = (props: FacilityProps) => { required types={["mobile", "landline"]} /> +
    +

    {t("spokes")}

    + {facilityId && ( + + )} +
    { }); }; + const spokesQuery = useQuery(routes.getFacilitySpokes, { + pathParams: { + id: facilityId, + }, + silent: true, + }); + if (isLoading) { return ; } @@ -289,6 +297,20 @@ export const FacilityHome = ({ facilityId }: Props) => { />
    + {!!spokesQuery.data?.results.length && ( +
    +
    +

    + {t("spokes")} +

    +
    + {spokesQuery.data?.results.map((spoke) => ( + + ))} +
    +
    +
    + )}
    diff --git a/src/Components/Facility/SpokeFacilityEditor.tsx b/src/Components/Facility/SpokeFacilityEditor.tsx new file mode 100644 index 00000000000..197d68da2bf --- /dev/null +++ b/src/Components/Facility/SpokeFacilityEditor.tsx @@ -0,0 +1,154 @@ +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import { + FacilityModel, + FacilitySpokeErrors, + FacilitySpokeModel, + FacilitySpokeRequest, + SpokeRelationship, +} from "./models"; +import ModelCrudEditor from "../Form/ModelCrudEditor"; +import { FacilitySelect } from "../Common/FacilitySelect"; +import { useEffect, useState } from "react"; +import { SPOKE_RELATION_TYPES } from "../../Common/constants"; +import FacilityBlock from "./FacilityBlock"; +import { useTranslation } from "react-i18next"; + +export interface SpokeFacilityEditorProps { + facility: Omit & { id: string }; +} + +export default function SpokeFacilityEditor(props: SpokeFacilityEditorProps) { + const { facility } = props; + + const { t } = useTranslation(); + + const spokesQuery = useQuery(routes.getFacilitySpokes, { + pathParams: { + id: facility.id, + }, + }); + + const spokes = spokesQuery.data?.results; + + const createSpoke = (body: FacilitySpokeRequest) => + request(routes.createFacilitySpoke, { + body, + pathParams: { + id: facility.id, + }, + onResponse: ({ res }) => { + if (res?.ok) { + spokesQuery.refetch(); + } + }, + }); + + const deleteSpoke = (spokeFacilityId: string) => + request(routes.deleteFacilitySpoke, { + pathParams: { + id: facility.id, + spoke_id: spokeFacilityId, + }, + onResponse: ({ res }) => { + if (res?.ok) { + spokesQuery.refetch(); + } + }, + }); + + const updateSpoke = (spokeFacilityId: string, body: FacilitySpokeRequest) => + request(routes.updateFacilitySpokes, { + pathParams: { + id: facility.id, + spoke_id: spokeFacilityId, + }, + body, + onResponse: ({ res }) => { + if (res?.ok) { + spokesQuery.refetch(); + } + }, + }); + + const FormRender = ( + item: FacilitySpokeModel | FacilitySpokeRequest, + setItem: (item: FacilitySpokeModel | FacilitySpokeRequest) => void, + processing: boolean, + ) => { + const [selectedFacility, setSelectedFacility] = useState(); + + useEffect(() => { + setItem({ ...item, spoke: selectedFacility?.id }); + }, [selectedFacility]); + + return ( +
    + {"id" in item ? ( +
    + +
    + ) : ( + + v && !Array.isArray(v) && setSelectedFacility(v) + } + errors="" + className="w-full" + disabled={processing} + filter={(f) => + !!f.id && + facility.id !== f.id && + !spokes?.flatMap((s) => s.spoke_object.id).includes(f.id) + } + /> + )} + v.text} + optionValue={(v) => v.value} + value={item.relationship} + onChange={(v) => setItem({ ...item, relationship: v.value })} + errorClassName="hidden" + className="w-full shrink-0 md:w-auto" + disabled={processing} + /> +
    + ); + }; + + return ( + <> + + items={spokes} + onCreate={createSpoke} + onUpdate={updateSpoke} + onDelete={deleteSpoke} + loading={spokesQuery.loading} + errors={{}} + emptyText={"No Spokes"} + empty={{ + spoke: "", + relationship: SpokeRelationship.REGULAR, + }} + createText="Add Spoke" + allowCreate={(item) => !item.relationship || !item.spoke} + > + {FormRender} + + + ); +} diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 7756bf73f69..97d81674658 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -12,6 +12,7 @@ import { AssignedToObjectModel, BloodPressure, DailyRoundsModel, + FacilityNameModel, FileUploadModel, } from "../Patient/models"; import { EncounterSymptom } from "../Symptoms/types"; @@ -79,8 +80,32 @@ export interface FacilityModel { local_body?: number; ward?: number; pincode?: string; + latitude?: string; + longitude?: string; + kasp_empanelled?: boolean; + patient_count?: string; + bed_count?: string; +} + +export enum SpokeRelationship { + REGULAR = 1, + TELE_ICU = 2, +} + +export interface FacilitySpokeModel { + id: string; + hub_object: FacilityNameModel; + spoke_object: FacilityNameModel; + relationship: SpokeRelationship; } +export interface FacilitySpokeRequest { + spoke?: string; + relationship?: SpokeRelationship; +} + +export interface FacilitySpokeErrors {} + export interface CapacityModal { id?: number; room_type?: number; @@ -588,13 +613,7 @@ export type IUserFacilityRequest = { facility: string; }; -export type FacilityRequest = Omit & { - latitude?: string; - longitude?: string; - kasp_empanelled?: boolean; - patient_count?: string; - bed_count?: string; -}; +export type FacilityRequest = Omit; export type InventorySummaryResponse = { id: string; diff --git a/src/Components/Form/AutoCompleteAsync.tsx b/src/Components/Form/AutoCompleteAsync.tsx index 18bffb0e11c..f362918dfc2 100644 --- a/src/Components/Form/AutoCompleteAsync.tsx +++ b/src/Components/Form/AutoCompleteAsync.tsx @@ -36,6 +36,7 @@ interface Props { required?: boolean; onBlur?: () => void; onFocus?: () => void; + filter?: (data: any) => boolean; } const AutoCompleteAsync = (props: Props) => { @@ -56,6 +57,7 @@ const AutoCompleteAsync = (props: Props) => { disabled = false, required = false, error, + filter, } = props; const [data, setData] = useState([]); const [query, setQuery] = useState(""); @@ -69,7 +71,9 @@ const AutoCompleteAsync = (props: Props) => { () => debounce(async (query: string) => { setLoading(true); - const data = (await fetchData(query)) || []; + const data = ((await fetchData(query)) || [])?.filter((d: any) => + filter ? filter(d) : true, + ); if (showNOptions !== undefined) { setData(data.slice(0, showNOptions)); diff --git a/src/Components/Form/ModelCrudEditor.tsx b/src/Components/Form/ModelCrudEditor.tsx new file mode 100644 index 00000000000..3ce4b15eba3 --- /dev/null +++ b/src/Components/Form/ModelCrudEditor.tsx @@ -0,0 +1,153 @@ +import { useEffect, useState } from "react"; +import { classNames } from "../../Utils/utils"; +import ButtonV2 from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { useTranslation } from "react-i18next"; + +interface Identifier { + id: string; +} + +export interface ModelCrudEditorProps { + onCreate?: (req: TReq) => Promise; + onUpdate?: (itemId: string, req: TReq) => Promise; + onDelete?: (itemId: string) => Promise; + items?: TRes[]; + children: ( + item: TRes | TReq, + setItem: (item: TRes | TReq) => void, + processing: boolean, + errors?: TErr, + ) => React.ReactNode; + loading: boolean; + errors: TErr; + emptyText?: React.ReactNode; + empty: TReq; + createText?: React.ReactNode; + allowCreate?: (item: TReq) => boolean; +} + +export default function ModelCrudEditor( + props: ModelCrudEditorProps, +) { + const { t } = useTranslation(); + + const { + onCreate, + onUpdate, + onDelete, + items, + children, + loading, + errors, + emptyText, + empty, + createText, + allowCreate, + } = props; + const [creating, setCreating] = useState(false); + const [updating, setUpdating] = useState(null); + + const handleUpdate = async (itemId: string, item: TReq) => { + if (!onUpdate) return; + setUpdating(itemId); + await onUpdate(itemId, item); + setUpdating(null); + }; + + const handleDelete = async (itemId: string) => { + if (!onDelete) return; + setUpdating(itemId); + await onDelete(itemId); + setUpdating(null); + }; + + const handleCreate = async (item: TReq) => { + if (!onCreate) return; + setCreating(true); + await onCreate(item); + setCreating(false); + }; + + type FormProps = + | { + type: "creating"; + item: TReq; + } + | { + type: "updating"; + item: TRes; + }; + + const Form = (props: FormProps) => { + const [item, setItem] = useState(props.item); + const processing = + props.type === "creating" ? creating : props.item.id === updating; + + useEffect(() => { + if ( + props.type === "updating" && + JSON.stringify(item) !== JSON.stringify(props.item) + ) { + const timeout = setTimeout(() => { + handleUpdate(props.item.id, item as TReq); + }, 1000); + return () => clearTimeout(timeout); + } + }, [item]); + + return ( +
    + {children(item, setItem, processing, errors)} + {props.type === "creating" && ( + handleCreate(item as TReq)} + > + {createText || "Create"} + + )} + {props.type === "updating" && onDelete && ( + + )} +
    + ); + }; + + return ( +
    +
      + {items?.map((item, i) => ( +
    • +
      +
    • + ))} + + {items?.length === 0 && ( +
      + {emptyText} +
      + )} +
    +
    + +
    +
    + ); +} diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index 661a1872cca..7f0b9f51802 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -175,6 +175,9 @@ "summary": "Summary", "report": "Report", "treating_doctor": "Treating Doctor", + "hubs": "Hub Facilities", + "spokes": "Spoke Facilities", + "add_spoke" : "Add Spoke Facility", "ration_card__NO_CARD": "Non-card holder", "ration_card__BPL": "BPL", "ration_card__APL": "APL", diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 0243db57df7..0bedb12dca5 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -26,6 +26,8 @@ import { DoctorModal, FacilityModel, FacilityRequest, + FacilitySpokeModel, + FacilitySpokeRequest, IFacilityNotificationRequest, IFacilityNotificationResponse, IUserFacilityRequest, @@ -366,7 +368,7 @@ const routes = { getPermittedFacility: { path: "/api/v1/facility/{id}/", method: "GET", - TRes: Type(), + TRes: Type(), }, getAnyFacility: { @@ -389,6 +391,38 @@ const routes = { TBody: Type>(), }, + getFacilitySpokes: { + path: "/api/v1/facility/{id}/spokes/", + method: "GET", + TRes: Type>(), + }, + + updateFacilitySpokes: { + path: "/api/v1/facility/{id}/spokes/{spoke_id}/", + method: "PATCH", + TRes: Type(), + TBody: Type(), + }, + + getFacilitySpoke: { + path: "/api/v1/facility/{id}/spokes/{spoke_id}/", + method: "GET", + TRes: Type(), + }, + + createFacilitySpoke: { + path: "/api/v1/facility/{id}/spokes/", + method: "POST", + TRes: Type(), + TBody: Type>(), + }, + + deleteFacilitySpoke: { + path: "/api/v1/facility/{id}/spokes/{spoke_id}/", + method: "DELETE", + TRes: Type>(), + }, + deleteFacilityCoverImage: { path: "/api/v1/facility/{id}/cover_image/", method: "DELETE", From 67c524068d079bc17500bc2388d7667d8957c21e Mon Sep 17 00:00:00 2001 From: vishwansh01 <165544401+vishwansh01@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:51:14 +0530 Subject: [PATCH 19/30] Adding cache control headers (#8575) Co-authored-by: Aakash Singh --- nginx/nginx.conf | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 40a878bcee2..e9ece31ea0e 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,17 +1,13 @@ server { - listen 80; + root /usr/share/nginx/html; - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; + location = /index.html { + expires 6h; } - error_page 500 502 503 504 /50x.html; - - location = /50x.html { - root /usr/share/nginx/html; + location / { + expires 7d; + try_files $uri $uri/ /index.html; } - } From 60068ad187ab45c1baecddcda08333902180f439 Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 25 Sep 2024 14:37:16 +0530 Subject: [PATCH 20/30] resolve conflicts (#8619) --- nginx/nginx.conf | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index e9ece31ea0e..5ad797a13ec 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,13 +1,21 @@ server { - listen 80; - root /usr/share/nginx/html; - location = /index.html { - expires 6h; + include mime.types; + types { + text/javascript js mjs; } + listen 80; + location / { - expires 7d; + root /usr/share/nginx/html; + index index.html index.htm; try_files $uri $uri/ /index.html; } + + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } } From f5152fca3e1b11665ea77ba7bf9a2fc65a030f5b Mon Sep 17 00:00:00 2001 From: Aakash Singh Date: Wed, 25 Sep 2024 14:43:30 +0530 Subject: [PATCH 21/30] Adding cache control headers (#8622) --- nginx/nginx.conf | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 5ad797a13ec..595873a4be2 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,21 +1,18 @@ server { + listen 80; + root /usr/share/nginx/html; include mime.types; types { text/javascript js mjs; } - listen 80; + location = /index.html { + expires 6h; + } location / { - root /usr/share/nginx/html; - index index.html index.htm; + expires 7d; try_files $uri $uri/ /index.html; } - - error_page 500 502 503 504 /50x.html; - - location = /50x.html { - root /usr/share/nginx/html; - } } From f1e96cfc2bfd2dcfc52d70669ec494c5bb96fd45 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Thu, 26 Sep 2024 19:14:47 +0530 Subject: [PATCH 22/30] Plausible: Track applied filters (#8640) --- src/Common/hooks/useFilters.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Common/hooks/useFilters.tsx b/src/Common/hooks/useFilters.tsx index 188dec2229d..a6974a5a43b 100644 --- a/src/Common/hooks/useFilters.tsx +++ b/src/Common/hooks/useFilters.tsx @@ -6,6 +6,7 @@ import PaginationComponent from "../../Components/Common/Pagination"; import { classNames, humanizeStrings } from "../../Utils/utils"; import FiltersCache from "../../Utils/FiltersCache"; import careConfig from "@careConfig"; +import { triggerGoal } from "../../Integrations/Plausible"; export type FilterState = Record; @@ -42,6 +43,17 @@ export default function useFilters({ ) => { query = FiltersCache.utils.clean(query); _setQueryParams(query, options); + + // For each of the newly applied filters (additional filters compared to + // previously applied ones), trigger a plausible goal "Advanced filter + // applied" with the applied filter's query key and current location as tags. + Object.keys(query).forEach((filter) => + triggerGoal("Advanced filter applied", { + filter, + location: location.pathname, + }), + ); + updateCache(query); }; From 53c5a7aeb9f8eee7408ff644024668f65b868ceb Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Fri, 27 Sep 2024 12:49:46 +0530 Subject: [PATCH 23/30] Fixed fileupload name bug (#8638) --- src/Components/Files/FileUpload.tsx | 11 ++++--- .../Patient/PatientConsentRecords.tsx | 7 ++-- src/Locale/en/FileUpload.json | 1 + src/Utils/useFileUpload.tsx | 33 +++++++++++++++---- 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/src/Components/Files/FileUpload.tsx b/src/Components/Files/FileUpload.tsx index 85d991d7c3b..1701a632472 100644 --- a/src/Components/Files/FileUpload.tsx +++ b/src/Components/Files/FileUpload.tsx @@ -195,6 +195,7 @@ export const FileUpload = (props: FileUploadProps) => { "ods", "pdf", ], + allowNameFallback: false, onUpload: refetchAll, }); @@ -259,7 +260,7 @@ export const FileUpload = (props: FileUploadProps) => {