From 4e7325b34b8d4cf082bfbe8bd9581e3fa09b6bf2 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Wed, 15 Jan 2025 17:34:16 +0530 Subject: [PATCH 1/4] Prefill MedicationRequest; Add Edit Links for Structured --- public/locale/en.json | 1 + src/Utils/request/api.tsx | 10 -- .../index.tsx | 47 ++++---- .../utils.ts | 0 src/components/Patient/allergy/list.tsx | 112 ++++++++++++------ src/components/Patient/diagnosis/list.tsx | 84 +++++++++---- src/components/Patient/symptoms/list.tsx | 84 +++++++++---- .../QuestionTypes/AllergyQuestion.tsx | 35 ++++-- .../MedicationRequestQuestion.tsx | 34 +++++- .../Questionnaire/QuestionnaireForm.tsx | 2 +- .../Questionnaire/data/StructuredFormData.tsx | 84 +++++++++++++ src/pages/Encounters/PrintPrescription.tsx | 3 +- .../Encounters/tabs/EncounterMedicinesTab.tsx | 4 +- .../Encounters/tabs/EncounterUpdatesTab.tsx | 19 ++- .../allergyIntolerance/allergyIntolerance.ts | 24 +++- .../medicationRequest/medicationRequestApi.ts | 15 +++ 16 files changed, 425 insertions(+), 133 deletions(-) rename src/components/Medicine/{MedicineAdministrationSheet => MedicationRequestTable}/index.tsx (91%) rename src/components/Medicine/{MedicineAdministrationSheet => MedicationRequestTable}/utils.ts (100%) create mode 100644 src/types/emr/medicationRequest/medicationRequestApi.ts diff --git a/public/locale/en.json b/public/locale/en.json index a8211b379a4..54b723cace4 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1343,6 +1343,7 @@ "next_week_short": "Next wk", "no": "No", "no_address_provided": "No address provided", + "no_allergies_recorded": "No allergies recorded", "no_appointments": "No appointments found", "no_attachments_found": "This communication has no attachments.", "no_availabilities_yet": "No availabilities yet", diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index eafc48e3c81..35959eb66f3 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -20,7 +20,6 @@ import { AppointmentPatientRegister, } from "@/pages/Patient/Utils"; import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; -import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatement } from "@/types/emr/medicationStatement"; import { PartialPatientModel, Patient } from "@/types/emr/newPatient"; import { @@ -645,15 +644,6 @@ const routes = { }, }, - // Medication - medicationRequest: { - list: { - path: "/api/v1/patient/{patientId}/medication/request/", - method: "GET", - TRes: Type>(), - }, - }, - medicationStatement: { list: { path: "/api/v1/patient/{patientId}/medication/statement/", diff --git a/src/components/Medicine/MedicineAdministrationSheet/index.tsx b/src/components/Medicine/MedicationRequestTable/index.tsx similarity index 91% rename from src/components/Medicine/MedicineAdministrationSheet/index.tsx rename to src/components/Medicine/MedicationRequestTable/index.tsx index 166c22016cb..dc56233f6e5 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/components/Medicine/MedicationRequestTable/index.tsx @@ -1,4 +1,6 @@ +import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; import { Link } from "raviger"; import { useState } from "react"; @@ -13,12 +15,10 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import Loading from "@/components/Common/Loading"; import { useEncounter } from "@/components/Facility/ConsultationDetails/EncounterContext"; -import useSlug from "@/hooks/useSlug"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import query from "@/Utils/request/query"; import { classNames } from "@/Utils/utils"; import { MedicationRequest } from "@/types/emr/medicationRequest"; +import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; interface Props { readonly?: boolean; @@ -45,21 +45,22 @@ function getFrequencyDisplay( return FREQUENCY_DISPLAY[key]; } -const MedicineAdministrationSheet = ({ facilityId }: Props) => { - const encounterId = useSlug("encounter"); - const { patient } = useEncounter(); +export default function MedicationRequestTable({ facilityId }: Props) { + const { patient, encounter } = useEncounter(); + + const patientId = patient?.id; + const encounterId = encounter?.id; + const [searchQuery, setSearchQuery] = useState(""); - const { data: medications, loading } = useTanStackQueryInstead( - routes.medicationRequest.list, - { - pathParams: { patientId: patient!.id }, - query: { - encounter: encounterId, - limit: 100, - }, - }, - ); + const { data: medications, isLoading: loading } = useQuery({ + queryKey: ["medications", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId: encounter?.patient?.id || "" }, + queryParams: { encounter: encounterId }, + }), + enabled: !!encounter?.patient?.id, + }); const filteredMedications = medications?.results?.filter( (med: MedicationRequest) => { @@ -103,6 +104,14 @@ const MedicineAdministrationSheet = ({ facilityId }: Props) => { title="Prescriptions" options={
+
); -}; +} const PrescriptionEntry = ({ medication, @@ -349,5 +358,3 @@ const PrescriptionEntry = ({ ); }; - -export default MedicineAdministrationSheet; diff --git a/src/components/Medicine/MedicineAdministrationSheet/utils.ts b/src/components/Medicine/MedicationRequestTable/utils.ts similarity index 100% rename from src/components/Medicine/MedicineAdministrationSheet/utils.ts rename to src/components/Medicine/MedicationRequestTable/utils.ts diff --git a/src/components/Patient/allergy/list.tsx b/src/components/Patient/allergy/list.tsx index 456f61c759e..a61955faaa3 100644 --- a/src/components/Patient/allergy/list.tsx +++ b/src/components/Patient/allergy/list.tsx @@ -1,5 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; +import { Link } from "raviger"; +import { ReactNode } from "react"; import { Badge } from "@/components/ui/badge"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; @@ -20,11 +23,16 @@ import { AllergyIntolerance } from "@/types/emr/allergyIntolerance/allergyIntole import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; interface AllergyListProps { + facilityId?: string; patientId: string; encounterId?: string; } -export function AllergyList({ patientId, encounterId }: AllergyListProps) { +export function AllergyList({ + facilityId, + patientId, + encounterId, +}: AllergyListProps) { const { data: allergies, isLoading } = useQuery({ queryKey: ["allergies", patientId, encounterId], queryFn: query(allergyIntoleranceApi.getAllergy, { @@ -35,27 +43,29 @@ export function AllergyList({ patientId, encounterId }: AllergyListProps) { if (isLoading) { return ( - - - Allergies - + - + ); } if (!allergies?.results?.length) { return ( - - - Allergies - + -

No allergies recorded

+

{t("no_allergies_recorded")}

-
+ ); } @@ -84,23 +94,30 @@ export function AllergyList({ patientId, encounterId }: AllergyListProps) { }; return ( - - - {t("allergies")} - - - - - - {t("allergen")} - {t("category")} - {t("status")} - {t("criticality")} - {t("created_by")} - - - - {allergies.results.map((allergy: AllergyIntolerance) => ( + +
+ + + {t("allergen")} + {t("category")} + {t("status")} + {t("criticality")} + {t("created_by")} + + + + {allergies.results + .sort((a, _b) => { + if (a.clinical_status === "inactive") { + return 1; + } + return -1; + }) + .map((allergy: AllergyIntolerance) => ( {allergy.code.display} @@ -140,9 +157,38 @@ export function AllergyList({ patientId, encounterId }: AllergyListProps) { ))} - -
-
-
+ + + ); } + +const AllergyListLayout = ({ + facilityId, + patientId, + encounterId, + children, +}: { + facilityId?: string; + patientId: string; + encounterId?: string; + children: ReactNode; +}) => { + return ( + + + {t("allergies")} + {facilityId && encounterId && ( + + + {t("edit")} + + )} + + {children} + + ); +}; diff --git a/src/components/Patient/diagnosis/list.tsx b/src/components/Patient/diagnosis/list.tsx index 0df139f098f..cf1e375fd58 100644 --- a/src/components/Patient/diagnosis/list.tsx +++ b/src/components/Patient/diagnosis/list.tsx @@ -1,4 +1,8 @@ import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; +import { Link } from "raviger"; +import { ReactNode } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; @@ -11,9 +15,14 @@ import { DiagnosisTable } from "./DiagnosisTable"; interface DiagnosisListProps { patientId: string; encounterId?: string; + facilityId?: string; } -export function DiagnosisList({ patientId, encounterId }: DiagnosisListProps) { +export function DiagnosisList({ + patientId, + encounterId, + facilityId, +}: DiagnosisListProps) { const { data: diagnoses, isLoading } = useQuery({ queryKey: ["diagnosis", patientId, encounterId], queryFn: query(diagnosisApi.listDiagnosis, { @@ -24,38 +33,65 @@ export function DiagnosisList({ patientId, encounterId }: DiagnosisListProps) { if (isLoading) { return ( - - - Diagnoses - - - - - + + + ); } if (!diagnoses?.results?.length) { return ( - - - Diagnoses - - -

No diagnoses recorded

-
-
+ +

No diagnoses recorded

+
); } return ( - - - Diagnoses + + + + ); +} + +const DiagnosisListLayout = ({ + facilityId, + patientId, + encounterId, + children, +}: { + facilityId?: string; + patientId: string; + encounterId?: string; + children: ReactNode; +}) => { + return ( + + + {t("diagnoses")} + {facilityId && encounterId && ( + + + {t("edit")} + + )} - - - + {children} ); -} +}; diff --git a/src/components/Patient/symptoms/list.tsx b/src/components/Patient/symptoms/list.tsx index 20914788f00..70673d72295 100644 --- a/src/components/Patient/symptoms/list.tsx +++ b/src/components/Patient/symptoms/list.tsx @@ -1,4 +1,8 @@ import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; +import { Link } from "raviger"; +import { ReactNode } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; @@ -11,9 +15,14 @@ import { SymptomTable } from "./SymptomTable"; interface SymptomsListProps { patientId: string; encounterId?: string; + facilityId?: string; } -export function SymptomsList({ patientId, encounterId }: SymptomsListProps) { +export function SymptomsList({ + patientId, + encounterId, + facilityId, +}: SymptomsListProps) { const { data: symptoms, isLoading } = useQuery({ queryKey: ["symptoms", patientId, encounterId], queryFn: query(symptomApi.listSymptoms, { @@ -24,38 +33,65 @@ export function SymptomsList({ patientId, encounterId }: SymptomsListProps) { if (isLoading) { return ( - - - Symptoms - - - - - + + + ); } if (!symptoms?.results?.length) { return ( - - - Symptoms - - -

No symptoms recorded

-
-
+ +

No symptoms recorded

+
); } return ( - - - Symptoms + + + + ); +} + +const SymptomListLayout = ({ + facilityId, + patientId, + encounterId, + children, +}: { + facilityId?: string; + patientId: string; + encounterId?: string; + children: ReactNode; +}) => { + return ( + + + {t("symptoms")} + {facilityId && ( + + + {t("edit")} + + )} - - - + {children} ); -} +}; diff --git a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx index 6e8364937f4..16e9536a4e5 100644 --- a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx @@ -47,8 +47,10 @@ import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; import query from "@/Utils/request/query"; import { + ALLERGY_VERIFICATION_STATUS, AllergyIntolerance, AllergyIntoleranceRequest, + AllergyVerificationStatus, } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; import { Code } from "@/types/questionnaire/code"; @@ -350,7 +352,8 @@ export function AllergyQuestion({ value={allergy.verification_status} onValueChange={(value) => handleUpdateAllergy(index, { - verification_status: value, + verification_status: + value as AllergyVerificationStatus, }) } disabled={disabled} @@ -359,9 +362,13 @@ export function AllergyQuestion({ - Confirmed - Unconfirmed - Refuted + {Object.entries(ALLERGY_VERIFICATION_STATUS).map( + ([value, label]) => ( + + {label} + + ), + )} @@ -500,18 +507,26 @@ const AllergyTableRow = ({ diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index 73361b3e774..224c8978455 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -1,6 +1,8 @@ import { MinusCircledIcon } from "@radix-ui/react-icons"; +import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; +import { toast } from "sonner"; import { cn } from "@/lib/utils"; @@ -41,6 +43,7 @@ import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; import useBreakpoints from "@/hooks/useBreakpoints"; +import query from "@/Utils/request/query"; import { DoseRange, MEDICATION_REQUEST_INTENT, @@ -51,10 +54,12 @@ import { UCUM_TIME_UNITS, parseMedicationStringToRequest, } from "@/types/emr/medicationRequest"; +import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; import { Code } from "@/types/questionnaire/code"; import { QuestionnaireResponse } from "@/types/questionnaire/form"; interface MedicationRequestQuestionProps { + patientId: string; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; disabled?: boolean; @@ -64,10 +69,37 @@ export function MedicationRequestQuestion({ questionnaireResponse, updateQuestionnaireResponseCB, disabled, + patientId, }: MedicationRequestQuestionProps) { const medications = (questionnaireResponse.values?.[0]?.value as MedicationRequest[]) || []; + const { data: patientMedications } = useQuery({ + queryKey: ["medications", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId }, + }), + }); + + useEffect(() => { + if (patientMedications?.results && !medications.length) { + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "medication_request", + value: patientMedications.results, + }, + ], + }); + if (patientMedications.count > patientMedications.results.length) { + toast.info( + `Showing first ${patientMedications.results.length} of ${patientMedications.count} medications`, + ); + } + } + }, [patientMedications]); + const [expandedMedicationIndex, setExpandedMedicationIndex] = useState< number | null >(null); diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index 3196bbc43ab..1172b0990ea 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -98,7 +98,7 @@ export function QuestionnaireForm({ // TODO: Use useBlocker hook after switching to tanstack router // https://tanstack.com/router/latest/docs/framework/react/guide/navigation-blocking#how-do-i-use-navigation-blocking - useNavigationPrompt(isDirty, t("unsaved_changes")); + useNavigationPrompt(isDirty && !import.meta.env.DEV, t("unsaved_changes")); useEffect(() => { if (!isInitialized && questionnaireSlug) { diff --git a/src/components/Questionnaire/data/StructuredFormData.tsx b/src/components/Questionnaire/data/StructuredFormData.tsx index 7a5aca3c248..918d2a1561f 100644 --- a/src/components/Questionnaire/data/StructuredFormData.tsx +++ b/src/components/Questionnaire/data/StructuredFormData.tsx @@ -40,7 +40,91 @@ const medication_request_questionnaire: QuestionnaireDetail = { tags: [], }; +const allergy_intolerance_questionnaire: QuestionnaireDetail = { + id: "allergy_intolerance", + slug: "allergy_intolerance", + version: "0.0.1", + title: "Allergy Intolerance", + status: "active", + subject_type: "patient", + questions: [ + { + id: "allergy_intolerance", + text: "Allergy Intolerance", + type: "structured", + structured_type: "allergy_intolerance", + link_id: "1.1", + required: true, + }, + ], + tags: [], +}; + +const medication_statement_questionnaire: QuestionnaireDetail = { + id: "medication_statement", + slug: "medication_statement", + version: "0.0.1", + title: "Medication Statement", + status: "active", + subject_type: "patient", + questions: [ + { + id: "medication_statement", + text: "Medication Statement", + type: "structured", + structured_type: "medication_statement", + link_id: "1.1", + required: true, + }, + ], + tags: [], +}; + +const diagnosis_questionnaire: QuestionnaireDetail = { + id: "diagnosis", + slug: "diagnosis", + version: "0.0.1", + title: "Diagnosis", + status: "active", + subject_type: "patient", + questions: [ + { + id: "diagnosis", + text: "Diagnosis", + type: "structured", + structured_type: "diagnosis", + link_id: "1.1", + required: true, + }, + ], + tags: [], +}; + +const symptom_questionnaire: QuestionnaireDetail = { + id: "symptom", + slug: "symptom", + version: "0.0.1", + title: "Symptom", + status: "active", + subject_type: "patient", + questions: [ + { + id: "symptom", + text: "Symptom", + type: "structured", + structured_type: "symptom", + link_id: "1.1", + required: true, + }, + ], + tags: [], +}; + export const FIXED_QUESTIONNAIRES: Record = { encounter: encounterQuestionnaire, medication_request: medication_request_questionnaire, + allergy_intolerance: allergy_intolerance_questionnaire, + medication_statement: medication_statement_questionnaire, + diagnosis: diagnosis_questionnaire, + symptom: symptom_questionnaire, }; diff --git a/src/pages/Encounters/PrintPrescription.tsx b/src/pages/Encounters/PrintPrescription.tsx index c071091306d..7b4d3820871 100644 --- a/src/pages/Encounters/PrintPrescription.tsx +++ b/src/pages/Encounters/PrintPrescription.tsx @@ -24,6 +24,7 @@ import { MEDICATION_REQUEST_TIMING_OPTIONS, MedicationRequest, } from "@/types/emr/medicationRequest"; +import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; function getFrequencyDisplay( timing?: MedicationRequest["dosage_instruction"][0]["timing"], @@ -92,7 +93,7 @@ export const PrintPrescription = (props: { const { data: medications } = useQuery({ queryKey: ["medications", encounter?.patient?.id], - queryFn: query(api.medicationRequest.list, { + queryFn: query(medicationRequestApi.list, { pathParams: { patientId: encounter?.patient?.id || "" }, queryParams: { encounter: encounterId }, }), diff --git a/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx b/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx index a7b8202cb1e..7ae0067aed2 100644 --- a/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx +++ b/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx @@ -1,11 +1,11 @@ -import MedicineAdministrationSheet from "@/components/Medicine/MedicineAdministrationSheet"; +import MedicationRequestTable from "@/components/Medicine/MedicationRequestTable"; import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; export const EncounterMedicinesTab = (props: EncounterTabProps) => { return (
- +
); }; diff --git a/src/pages/Encounters/tabs/EncounterUpdatesTab.tsx b/src/pages/Encounters/tabs/EncounterUpdatesTab.tsx index 72de24a181b..d3ace63e672 100644 --- a/src/pages/Encounters/tabs/EncounterUpdatesTab.tsx +++ b/src/pages/Encounters/tabs/EncounterUpdatesTab.tsx @@ -7,6 +7,7 @@ import { SymptomsList } from "@/components/Patient/symptoms/list"; import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; export const EncounterUpdatesTab = ({ + facilityId, encounter, patient, }: EncounterTabProps) => { @@ -18,17 +19,29 @@ export const EncounterUpdatesTab = ({
{/* Allergies Section */}
- +
{/* Symptoms Section */}
- +
{/* Diagnoses Section */}
- +
{/* Questionnaire Responses Section */} diff --git a/src/types/emr/allergyIntolerance/allergyIntolerance.ts b/src/types/emr/allergyIntolerance/allergyIntolerance.ts index 860cfe14243..d0870191bde 100644 --- a/src/types/emr/allergyIntolerance/allergyIntolerance.ts +++ b/src/types/emr/allergyIntolerance/allergyIntolerance.ts @@ -1,12 +1,20 @@ import { Code } from "../../questionnaire/code"; import { UserBase } from "../../user/user"; +export type AllergyVerificationStatus = + | "unconfirmed" + | "confirmed" + | "refuted" + | "presumed" + | "entered-in-error"; + +export type AllergyClinicalStatus = "active" | "inactive" | "resolved"; // Base type for allergy data export interface AllergyIntolerance { id: string; code: Code; - clinical_status: string; - verification_status: string; + clinical_status: AllergyClinicalStatus; + verification_status: AllergyVerificationStatus; category: string; criticality: string; last_occurrence?: string; @@ -20,8 +28,8 @@ export interface AllergyIntolerance { // Added optional id here as this type is used only in one place export interface AllergyIntoleranceRequest { id?: string; - clinical_status: string; - verification_status: string; + clinical_status: AllergyClinicalStatus; + verification_status: AllergyVerificationStatus; category: string; criticality: string; code: Code; @@ -29,3 +37,11 @@ export interface AllergyIntoleranceRequest { note?: string; encounter: string; } + +export const ALLERGY_VERIFICATION_STATUS = { + unconfirmed: "Unconfirmed", + confirmed: "Confirmed", + refuted: "Refuted", + presumed: "Presumed", + "entered-in-error": "Entered in Error", +}; diff --git a/src/types/emr/medicationRequest/medicationRequestApi.ts b/src/types/emr/medicationRequest/medicationRequestApi.ts new file mode 100644 index 00000000000..0e5b6f7245f --- /dev/null +++ b/src/types/emr/medicationRequest/medicationRequestApi.ts @@ -0,0 +1,15 @@ +import { Type } from "@/Utils/request/api"; +import { PaginatedResponse } from "@/Utils/request/types"; + +import { MedicationRequest } from "../medicationRequest"; + +const medicationRequestApi = { + // Medication + list: { + path: "/api/v1/patient/{patientId}/medication/request/", + method: "GET", + TRes: Type>(), + }, +} as const; + +export default medicationRequestApi; From fcb58c7c9f5d7e94f5244ebfeafd1af849cafc9d Mon Sep 17 00:00:00 2001 From: Gigin George Date: Wed, 15 Jan 2025 17:45:28 +0530 Subject: [PATCH 2/4] Fix types --- src/types/emr/medicationRequest/medicationRequestApi.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/emr/medicationRequest/medicationRequestApi.ts b/src/types/emr/medicationRequest/medicationRequestApi.ts index 0e5b6f7245f..c2c50020b2d 100644 --- a/src/types/emr/medicationRequest/medicationRequestApi.ts +++ b/src/types/emr/medicationRequest/medicationRequestApi.ts @@ -1,14 +1,14 @@ import { Type } from "@/Utils/request/api"; import { PaginatedResponse } from "@/Utils/request/types"; -import { MedicationRequest } from "../medicationRequest"; +import { MedicationRequestRead } from "../medicationRequest"; const medicationRequestApi = { // Medication list: { path: "/api/v1/patient/{patientId}/medication/request/", method: "GET", - TRes: Type>(), + TRes: Type>(), }, } as const; From 4caed514f47c41b656486bb8299b7985288db5d0 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Wed, 15 Jan 2025 18:37:52 +0530 Subject: [PATCH 3/4] Remove EncounterContext; Add MedicationStatement prefill --- .../ConsultationDetails/EncounterContext.tsx | 70 ------------ .../Medicine/MedicationRequestTable/index.tsx | 18 ++-- .../MedicationStatementQuestion.tsx | 101 +++++++++++++++--- src/pages/Encounters/EncounterShow.tsx | 10 +- .../Encounters/tabs/EncounterMedicinesTab.tsx | 6 +- .../medicationStatementApi.ts | 14 +++ 6 files changed, 117 insertions(+), 102 deletions(-) delete mode 100644 src/components/Facility/ConsultationDetails/EncounterContext.tsx create mode 100644 src/types/emr/medicationStatement/medicationStatementApi.ts diff --git a/src/components/Facility/ConsultationDetails/EncounterContext.tsx b/src/components/Facility/ConsultationDetails/EncounterContext.tsx deleted file mode 100644 index 07d76a7f538..00000000000 --- a/src/components/Facility/ConsultationDetails/EncounterContext.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { ReactNode, createContext, useContext, useState } from "react"; - -import { Encounter } from "@/types/emr/encounter"; -import { Patient } from "@/types/emr/newPatient"; - -interface EncounterContextBase { - encounter?: Encounter; - patient?: Patient; -} - -type EncounterContextType = EncounterContextBase & - T & { - setValue: ( - key: K, - value: (EncounterContextBase & T)[K], - ) => void; - }; - -const EncounterContext = createContext< - EncounterContextType | undefined ->(undefined); - -export const useEncounter = () => { - const context = useContext(EncounterContext); - - if (!context) { - throw new Error( - "'useEncounter' must be used within 'EncounterProvider' only", - ); - } - - return context as EncounterContextType; -}; - -interface EncounterProviderProps { - children: ReactNode; - initialContext?: Partial; -} - -export const EncounterProvider = ({ - children, - initialContext = {}, -}: EncounterProviderProps) => { - const [state, setState] = useState( - initialContext as EncounterContextBase & T, - ); - - const setValue = ( - key: K, - value: (EncounterContextBase & T)[K], - ) => { - setState((prevState) => ({ - ...prevState, - [key]: value, - })); - }; - - return ( - - } - > - {children} - - ); -}; diff --git a/src/components/Medicine/MedicationRequestTable/index.tsx b/src/components/Medicine/MedicationRequestTable/index.tsx index dc56233f6e5..6afe5e836fe 100644 --- a/src/components/Medicine/MedicationRequestTable/index.tsx +++ b/src/components/Medicine/MedicationRequestTable/index.tsx @@ -13,7 +13,6 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import Loading from "@/components/Common/Loading"; -import { useEncounter } from "@/components/Facility/ConsultationDetails/EncounterContext"; import query from "@/Utils/request/query"; import { classNames } from "@/Utils/utils"; @@ -23,6 +22,8 @@ import medicationRequestApi from "@/types/emr/medicationRequest/medicationReques interface Props { readonly?: boolean; facilityId: string; + patientId: string; + encounterId: string; } const FREQUENCY_DISPLAY: Record = { @@ -45,21 +46,20 @@ function getFrequencyDisplay( return FREQUENCY_DISPLAY[key]; } -export default function MedicationRequestTable({ facilityId }: Props) { - const { patient, encounter } = useEncounter(); - - const patientId = patient?.id; - const encounterId = encounter?.id; - +export default function MedicationRequestTable({ + facilityId, + patientId, + encounterId, +}: Props) { const [searchQuery, setSearchQuery] = useState(""); const { data: medications, isLoading: loading } = useQuery({ queryKey: ["medications", patientId], queryFn: query(medicationRequestApi.list, { - pathParams: { patientId: encounter?.patient?.id || "" }, + pathParams: { patientId: patientId }, queryParams: { encounter: encounterId }, }), - enabled: !!encounter?.patient?.id, + enabled: !!patientId, }); const filteredMedications = medications?.results?.filter( diff --git a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx index 3482310b778..1c8fb573126 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx @@ -2,9 +2,12 @@ import { MinusCircledIcon, QuestionMarkCircledIcon, TextAlignLeftIcon, + TrashIcon, } from "@radix-ui/react-icons"; -import React from "react"; +import { useQuery } from "@tanstack/react-query"; +import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; +import { toast } from "sonner"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; @@ -20,20 +23,29 @@ import { SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; +import query from "@/Utils/request/query"; import { MEDICATION_STATEMENT_STATUS, MedicationStatement, MedicationStatementInformationSourceType, MedicationStatementStatus, } from "@/types/emr/medicationStatement"; +import medicationStatementApi from "@/types/emr/medicationStatement/medicationStatementApi"; import { Code } from "@/types/questionnaire/code"; import { QuestionnaireResponse } from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; interface MedicationStatementQuestionProps { + patientId: string; question: Question; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; @@ -56,12 +68,39 @@ export function MedicationStatementQuestion({ questionnaireResponse, updateQuestionnaireResponseCB, disabled, + patientId, }: MedicationStatementQuestionProps) { const { t } = useTranslation(); const medications = (questionnaireResponse.values?.[0]?.value as MedicationStatement[]) || []; + const { data: patientMedications } = useQuery({ + queryKey: ["medication_statements", patientId], + queryFn: query(medicationStatementApi.list, { + pathParams: { patientId }, + }), + }); + + useEffect(() => { + if (patientMedications?.results && !medications.length) { + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "medication_statement", + value: patientMedications.results, + }, + ], + }); + if (patientMedications.count > patientMedications.results.length) { + toast.info( + `Showing first ${patientMedications.results.length} of ${patientMedications.count} medication statements`, + ); + } + } + }, [patientMedications]); + const handleAddMedication = (medication: Code) => { const newMedications: Omit< MedicationStatement, @@ -82,11 +121,26 @@ export function MedicationStatementQuestion({ }; const handleRemoveMedication = (index: number) => { - const newMedications = medications.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_statement", value: newMedications }], - }); + const medication = medications[index]; + if (medication.id) { + // For existing records, update status to entered-in-error + const newMedications = medications.map((med, i) => + i === index + ? { ...med, status: "entered_in_error" as MedicationStatementStatus } + : med, + ); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [{ type: "medication_statement", value: newMedications }], + }); + } else { + // For new records, remove them completely + const newMedications = medications.filter((_, i) => i !== index); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [{ type: "medication_statement", value: newMedications }], + }); + } }; const handleUpdateMedication = ( @@ -181,14 +235,33 @@ const MedicationStatementItem: React.FC<{ - + + + + + + + {medication.status === "entered_in_error" + ? t("medication_already_marked_as_error") + : medication.id + ? t("mark_as_entered_in_error") + : t("remove_medication")} + + +
diff --git a/src/pages/Encounters/EncounterShow.tsx b/src/pages/Encounters/EncounterShow.tsx index 0440aa49aae..c79935d8101 100644 --- a/src/pages/Encounters/EncounterShow.tsx +++ b/src/pages/Encounters/EncounterShow.tsx @@ -6,7 +6,6 @@ import Loading from "@/components/Common/Loading"; import PageHeadTitle from "@/components/Common/PageHeadTitle"; import PageTitle from "@/components/Common/PageTitle"; import ErrorPage from "@/components/ErrorPages/DefaultErrorPage"; -import { EncounterProvider } from "@/components/Facility/ConsultationDetails/EncounterContext"; import PatientInfoCard from "@/components/Patient/PatientInfoCard"; import { useCareAppConsultationTabs } from "@/hooks/useCareApps"; @@ -177,12 +176,7 @@ export const EncounterShow = (props: Props) => { }`; return ( - +
- + ); }; diff --git a/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx b/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx index 7ae0067aed2..6d5ce4c0606 100644 --- a/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx +++ b/src/pages/Encounters/tabs/EncounterMedicinesTab.tsx @@ -5,7 +5,11 @@ import { EncounterTabProps } from "@/pages/Encounters/EncounterShow"; export const EncounterMedicinesTab = (props: EncounterTabProps) => { return (
- +
); }; diff --git a/src/types/emr/medicationStatement/medicationStatementApi.ts b/src/types/emr/medicationStatement/medicationStatementApi.ts new file mode 100644 index 00000000000..51daaf8af27 --- /dev/null +++ b/src/types/emr/medicationStatement/medicationStatementApi.ts @@ -0,0 +1,14 @@ +import { Type } from "@/Utils/request/api"; +import { PaginatedResponse } from "@/Utils/request/types"; + +import { MedicationStatement } from "../medicationStatement"; + +const medicationStatementApi = { + list: { + path: "/api/v1/patient/{patientId}/medication/statement/", + method: "GET", + TRes: Type>(), + }, +} as const; + +export default medicationStatementApi; From 3de35dd5aca72e1ed96e6ca2592d210c2c139d1f Mon Sep 17 00:00:00 2001 From: Gigin George Date: Fri, 17 Jan 2025 08:54:51 +0530 Subject: [PATCH 4/4] Add Remove as entered-in-error --- .../MedicationRequestQuestion.tsx | 82 ++++++++++++++----- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index 224c8978455..63d48609ae1 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -37,6 +37,12 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { ComboboxQuantityInput } from "@/components/Common/ComboboxQuantityInput"; import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; @@ -135,13 +141,28 @@ export function MedicationRequestQuestion({ const confirmRemoveMedication = () => { if (medicationToDelete === null) return; - const newMedications = medications.filter( - (_, i) => i !== medicationToDelete, - ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_request", value: newMedications }], - }); + const medication = medications[medicationToDelete]; + if (medication.id) { + // For existing records, update status to entered-in-error + const newMedications = medications.map((med, i) => + i === medicationToDelete + ? { ...med, status: "entered_in_error" as const } + : med, + ); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [{ type: "medication_request", value: newMedications }], + }); + } else { + // For new records, remove them completely + const newMedications = medications.filter( + (_, i) => i !== medicationToDelete, + ); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [{ type: "medication_request", value: newMedications }], + }); + } setMedicationToDelete(null); }; @@ -295,18 +316,32 @@ export function MedicationRequestQuestion({ )} - + + + + + + + {medication.status === "entered_in_error" + ? t("medication_already_marked_as_error") + : t("remove_medication")} + + + @@ -481,7 +516,14 @@ const MedicationRequestGridRow: React.FC = ({ }; return ( -
+
{/* Medicine Name */}