From 4e7325b34b8d4cf082bfbe8bd9581e3fa09b6bf2 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Wed, 15 Jan 2025 17:34:16 +0530 Subject: [PATCH 01/17] 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 02/17] 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 03/17] 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 04/17] 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 */}
From 4196a75e07cb12b49d52d865c7b4f1dfaca65d93 Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Mon, 20 Jan 2025 16:09:47 +0530 Subject: [PATCH 05/17] Symptom & DIagnosis --- public/locale/en.json | 19 + .../QuestionTypes/DiagnosisQuestion.tsx | 173 +++++-- .../QuestionTypes/SymptomQuestion.tsx | 472 +++++++++++------- .../Questionnaire/structured/handlers.ts | 61 +-- .../Questionnaire/structured/types.ts | 12 +- src/types/emr/diagnosis/diagnosis.ts | 2 + src/types/emr/symptom/symptom.ts | 4 +- src/types/emr/symptom/symptomApi.ts | 8 +- src/types/questionnaire/form.ts | 8 +- 9 files changed, 488 insertions(+), 271 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index b4d2aecf30f..7ddc25feecf 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -335,6 +335,8 @@ "add_new_patient": "Add New Patient", "add_new_user": "Add New User", "add_notes": "Add notes", + "add_notes_about_diagnosis": "Add notes about the diagnosis...", + "add_notes_about_symptom": "Add notes about the symptom...", "add_organizations": "Add Organizations", "add_patient_updates": "Add Patient Updates", "add_policy": "Add Insurance Policy", @@ -382,6 +384,7 @@ "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are", "already_a_member": "Already a member?", + "already_marked_as_error": "Already marked as error", "alternate_phone_number": "Alternate Phone Number", "ambulance_driver_name": "Name of ambulance driver", "ambulance_number": "Ambulance No", @@ -1123,6 +1126,7 @@ "hi__record_not_fetched_title": "This record hasn't been fetched yet", "hi__waiting_for_record": "Waiting for the Host HIP to send the record.", "hide": "Hide", + "hide_notes": "Hide notes", "high": "High", "history": "History", "home_facility": "Home Facility", @@ -1135,6 +1139,7 @@ "icmr_specimen_referral_form": "ICMR Specimen Referral Form", "immunisation-records": "Immunisation", "in_consultation": "In-Consultation", + "inactive": "Inactive", "incomplete_patient_details_warning": "Patient details are incomplete. Please update the details before proceeding.", "inconsistent_dosage_units_error": "Dosage units must be same", "indian_mobile": "Indian Mobile", @@ -1312,6 +1317,7 @@ "middleware_hostname": "Middleware Hostname", "middleware_hostname_example": "e.g. example.ohc.network", "middleware_hostname_sourced_from": "Middleware hostname sourced from {{ source }}", + "mild": "Mild", "min_char_length_error": "Must be at least {{ min_length }} characters", "min_password_len_8": "Minimum password length 8", "min_time_bw_doses": "Min. time b/w doses", @@ -1325,6 +1331,7 @@ "mobile_otp_send_success": "OTP has been sent to the given mobile number.", "mobile_otp_verify_error": "Failed to verify mobile number. Please try again later.", "mobile_otp_verify_success": "Mobile number has been verified successfully.", + "moderate": "Moderate", "modification_caution_note": "No modifications possible once added", "modified": "Modified", "modified_date": "Modified Date", @@ -1698,6 +1705,7 @@ "record_has_been_deleted_successfully": "Record has been deleted successfully.", "record_updates": "Record Updates", "recording": "Recording", + "recurrence": "Recurrence", "redirected_to_create_consultation": "Note: You will be redirected to create consultation form. Please complete the form to finish the transfer process", "reference_no": "Reference No", "referral_letter": "Referral Letter", @@ -1710,12 +1718,16 @@ "register_patient": "Register Patient", "reject": "Reject", "rejected": "Rejected", + "relapse": "Relapse", "reload": "Reload", "remarks": "Remarks", "remarks_placeholder": "Enter remarks", + "remission": "Remission", "remove": "Remove", + "remove_diagnosis": "Remove Diagnosis", "remove_medication": "Remove Medication", "remove_medication_confirmation": "Are you sure you want to remove {{medication}}?", + "remove_symptom": "Remove Symptom", "remove_user": "Remove User", "remove_user_organization": "Remove User from Organization", "remove_user_warn": "Are you sure you want to remove {{firstName}} {{lastName}} from this organization? This action cannot be undone.", @@ -1752,6 +1764,7 @@ "reset": "Reset", "reset_password": "Reset Password", "reset_password_note_self": "Enter your current password, then create and confirm your new password", + "resolved": "Resolved", "resource": "Resource", "resource_approving_facility": "Resource approving facility", "resource_created_successfully": "Request created successfully", @@ -1832,7 +1845,9 @@ "search_by_phone_number": "Search by Phone Number", "search_by_username": "Search by username", "search_encounters": "Search Encounters", + "search_for_diagnoses_to_add": "Search for diagnoses to add", "search_for_facility": "Search for Facility", + "search_for_symptoms_to_add": "Search for symptoms to add", "search_icd11_placeholder": "Search for ICD-11 Diagnoses", "search_investigation_placeholder": "Search Investigation & Groups", "search_medication": "Search Medication", @@ -1913,6 +1928,8 @@ "set_home_facility": "Set as home facility", "set_your_local_language": "Set your local language", "settings_and_filters": "Settings and Filters", + "severe": "Severe", + "severity": "Severity", "severity_of_breathlessness": "Severity of Breathlessness", "sex": "Sex", "shared_by": "Shared By", @@ -1989,6 +2006,7 @@ "switch": "Switch", "switch_bed": "Switch Bed", "switch_camera_is_not_available": "Switch camera is not available.", + "symptom": "Symptom", "symptoms": "Symptoms", "systolic": "Systolic", "tachycardia": "Tachycardia", @@ -2176,6 +2194,7 @@ "ventilator_oxygen_modality": "Oxygen Modality", "ventilator_oxygen_modality_oxygen_rate": "Oxygen Flow Rate", "ventilator_spo2": "SpO₂", + "verification": "Verification", "verification_failed": "Verification Failed", "verify": "Verify", "verify_and_link": "Verify and Link", diff --git a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx index af85d52b152..5dbc5fbc279 100644 --- a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx @@ -3,7 +3,11 @@ import { MinusCircledIcon, Pencil2Icon, } from "@radix-ui/react-icons"; -import React, { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { t } from "i18next"; +import React, { useEffect, useState } from "react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { @@ -25,77 +29,168 @@ import { import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; +import query from "@/Utils/request/query"; import { DIAGNOSIS_CLINICAL_STATUS, DIAGNOSIS_VERIFICATION_STATUS, Diagnosis, + DiagnosisRequest, } from "@/types/emr/diagnosis/diagnosis"; +import diagnosisApi from "@/types/emr/diagnosis/diagnosisApi"; import { Code } from "@/types/questionnaire/code"; import { QuestionnaireResponse } from "@/types/questionnaire/form"; interface DiagnosisQuestionProps { + patientId: string; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; disabled?: boolean; } -const DIAGNOSIS_INITIAL_VALUE: Partial = { +const DIAGNOSIS_INITIAL_VALUE: Omit = { code: { code: "", display: "", system: "" }, clinical_status: "active", verification_status: "confirmed", onset: { onset_datetime: new Date().toISOString().split("T")[0] }, }; +function convertToDiagnosisRequest(diagnosis: Diagnosis): DiagnosisRequest { + return { + id: diagnosis.id, + code: diagnosis.code, + clinical_status: diagnosis.clinical_status, + verification_status: diagnosis.verification_status, + onset: diagnosis.onset + ? { + ...diagnosis.onset, + onset_datetime: diagnosis.onset.onset_datetime + ? format(new Date(diagnosis.onset.onset_datetime), "yyyy-MM-dd") + : "", + } + : undefined, + recorded_date: diagnosis.recorded_date, + note: diagnosis.note, + encounter: "", // This will be set when submitting the form + }; +} + export function DiagnosisQuestion({ + patientId, questionnaireResponse, updateQuestionnaireResponseCB, disabled, }: DiagnosisQuestionProps) { const diagnoses = - (questionnaireResponse.values?.[0]?.value as Diagnosis[]) || []; + (questionnaireResponse.values?.[0]?.value as DiagnosisRequest[]) || []; + + const { data: patientDiagnoses } = useQuery({ + queryKey: ["diagnoses", patientId], + queryFn: query(diagnosisApi.listDiagnosis, { + pathParams: { patientId }, + }), + }); + + useEffect(() => { + if (patientDiagnoses?.results && !diagnoses.length) { + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "diagnosis", + value: patientDiagnoses.results.map(convertToDiagnosisRequest), + }, + ], + }); + if (patientDiagnoses.count > patientDiagnoses.results.length) { + toast.info( + `Showing first ${patientDiagnoses.results.length} of ${patientDiagnoses.count} diagnoses`, + ); + } + } + }, [ + patientDiagnoses, + questionnaireResponse, + diagnoses.length, + updateQuestionnaireResponseCB, + ]); const handleAddDiagnosis = (code: Code) => { const newDiagnoses = [ ...diagnoses, { ...DIAGNOSIS_INITIAL_VALUE, code }, - ] as Diagnosis[]; + ] as DiagnosisRequest[]; updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [{ type: "diagnosis", value: newDiagnoses }], + values: [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], }); }; const handleRemoveDiagnosis = (index: number) => { - const newDiagnoses = diagnoses.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "diagnosis", value: newDiagnoses }], - }); + const diagnosis = diagnoses[index]; + if (diagnosis.id) { + // For existing records, update verification status to entered_in_error + const newDiagnoses = diagnoses.map((d, i) => + i === index + ? { ...d, verification_status: "entered-in-error" as const } + : d, + ) as DiagnosisRequest[]; + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], + }); + } else { + // For new records, remove them completely + const newDiagnoses = diagnoses.filter((_, i) => i !== index); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], + }); + } }; const handleUpdateDiagnosis = ( index: number, - updates: Partial, + updates: Partial, ) => { const newDiagnoses = diagnoses.map((diagnosis, i) => i === index ? { ...diagnosis, ...updates } : diagnosis, ); updateQuestionnaireResponseCB({ ...questionnaireResponse, - values: [{ type: "diagnosis", value: newDiagnoses }], + values: [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], }); }; return ( - <> +
{diagnoses.length > 0 && (
-
Diagnosis
-
Onset Date
-
Status
-
Verification
-
Action
+
{t("diagnosis")}
+
{t("date")}
+
{t("status")}
+
{t("verification")}
+
{t("action")}
{diagnoses.map((diagnosis, index) => ( @@ -112,18 +207,18 @@ export function DiagnosisQuestion({ )} - +
); } interface DiagnosisItemProps { - diagnosis: Diagnosis; + diagnosis: DiagnosisRequest; disabled?: boolean; - onUpdate?: (diagnosis: Partial) => void; + onUpdate?: (diagnosis: Partial) => void; onRemove?: () => void; } @@ -133,7 +228,7 @@ const DiagnosisItem: React.FC = ({ onUpdate, onRemove, }) => { - const [showNotes, setShowNotes] = useState(false); + const [showNotes, setShowNotes] = useState(Boolean(diagnosis.note)); return (
@@ -160,7 +255,7 @@ const DiagnosisItem: React.FC = ({ setShowNotes(!showNotes)}> - {showNotes ? "Hide Notes" : "Add Notes"} + {showNotes ? t("hide_notes") : t("add_notes")} = ({ onClick={onRemove} > - Remove Diagnosis + {t("remove_diagnosis")} @@ -176,7 +271,9 @@ const DiagnosisItem: React.FC = ({
- + = ({ />
- + onUpdate?.({ verification_status: - value as Diagnosis["verification_status"], + value as DiagnosisRequest["verification_status"], }) } disabled={disabled} > - + {DIAGNOSIS_VERIFICATION_STATUS.map((status) => ( @@ -240,7 +339,7 @@ const DiagnosisItem: React.FC = ({ value={status} className="capitalize" > - {status.replace(/_/g, " ")} + {t(status)} ))} @@ -262,7 +361,7 @@ const DiagnosisItem: React.FC = ({ setShowNotes(!showNotes)}> - {showNotes ? "Hide Notes" : "Add Notes"} + {showNotes ? t("hide_notes") : t("add_notes")} = ({ onClick={onRemove} > - Remove Diagnosis + {t("remove_diagnosis")} @@ -280,7 +379,7 @@ const DiagnosisItem: React.FC = ({
onUpdate?.({ note: e.target.value })} disabled={disabled} diff --git a/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx b/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx index bf0b7e98e8a..70b1cf9dffa 100644 --- a/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx @@ -5,7 +5,11 @@ import { MinusCircledIcon, Pencil2Icon, } from "@radix-ui/react-icons"; -import React, { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { t } from "i18next"; +import React, { useCallback, useEffect, useState } from "react"; +import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { @@ -16,7 +20,6 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; import { Select, SelectContent, @@ -27,16 +30,19 @@ import { import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; +import query from "@/Utils/request/query"; import { SYMPTOM_CLINICAL_STATUS, SYMPTOM_SEVERITY, Symptom, SymptomRequest, } from "@/types/emr/symptom/symptom"; +import symptomApi from "@/types/emr/symptom/symptomApi"; import { Code } from "@/types/questionnaire/code"; import { QuestionnaireResponse } from "@/types/questionnaire/form"; interface SymptomQuestionProps { + patientId: string; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; disabled?: boolean; @@ -50,19 +56,258 @@ const SYMPTOM_INITIAL_VALUE: Omit = { onset: { onset_datetime: new Date().toISOString().split("T")[0] }, }; +function convertToSymptomRequest(symptom: Symptom): SymptomRequest { + return { + id: symptom.id, + code: symptom.code, + clinical_status: symptom.clinical_status, + verification_status: symptom.verification_status, + severity: symptom.severity, + onset: symptom.onset + ? { + ...symptom.onset, + onset_datetime: symptom.onset.onset_datetime + ? format(new Date(symptom.onset.onset_datetime), "yyyy-MM-dd") + : "", + } + : undefined, + recorded_date: symptom.recorded_date, + note: symptom.note, + encounter: "", // This will be set when submitting the form + }; +} + +interface SymptomRowProps { + symptom: SymptomRequest; + index: number; + disabled?: boolean; + onUpdate: (index: number, updates: Partial) => void; + onRemove: (index: number) => void; +} + +function SymptomActionsMenu({ + showNotes, + verificationStatus, + disabled, + onToggleNotes, + onRemove, +}: { + showNotes: boolean; + verificationStatus: string; + disabled?: boolean; + onToggleNotes: () => void; + onRemove: () => void; +}) { + return ( + + + + + + + + {showNotes ? t("hide_notes") : t("add_notes")} + + + + + {verificationStatus === "entered_in_error" + ? t("already_marked_as_error") + : t("remove_symptom")} + + + + ); +} + +const SymptomRow = React.memo(function SymptomRow({ + symptom, + index, + disabled, + onUpdate, + onRemove, +}: SymptomRowProps) { + const [showNotes, setShowNotes] = useState(Boolean(symptom.note)); + + const handleDateChange = useCallback( + (e: React.ChangeEvent) => + onUpdate(index, { + onset: { onset_datetime: e.target.value }, + }), + [index, onUpdate], + ); + + const handleStatusChange = useCallback( + (value: string) => + onUpdate(index, { + clinical_status: value as SymptomRequest["clinical_status"], + }), + [index, onUpdate], + ); + + const handleSeverityChange = useCallback( + (value: string) => + onUpdate(index, { + severity: value as SymptomRequest["severity"], + }), + [index, onUpdate], + ); + + const handleNotesChange = useCallback( + (e: React.ChangeEvent) => + onUpdate(index, { note: e.target.value }), + [index, onUpdate], + ); + + const handleRemove = useCallback(() => onRemove(index), [index, onRemove]); + const handleToggleNotes = useCallback(() => setShowNotes((n) => !n), []); + + return ( +
+
+
+
+ {symptom.code.display} +
+
+
+
+ {t("date")} +
+ +
+
+
+ {t("status")} +
+ +
+
+
+ {t("severity")} +
+ +
+
+ +
+
+ {showNotes && ( +
+ +
+ )} +
+ ); +}); + export function SymptomQuestion({ + patientId, questionnaireResponse, updateQuestionnaireResponseCB, disabled, }: SymptomQuestionProps) { const symptoms = - (questionnaireResponse.values?.[0]?.value as Symptom[]) || []; + (questionnaireResponse.values?.[0]?.value as SymptomRequest[]) || []; + + const { data: patientSymptoms } = useQuery({ + queryKey: ["symptoms", patientId], + queryFn: query(symptomApi.listSymptoms, { + pathParams: { patientId }, + }), + }); + + useEffect(() => { + if (patientSymptoms?.results && !symptoms.length) { + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [ + { + type: "symptom", + value: patientSymptoms.results.map(convertToSymptomRequest), + }, + ], + }); + if (patientSymptoms.count > patientSymptoms.results.length) { + toast.info( + `Showing first ${patientSymptoms.results.length} of ${patientSymptoms.count} symptoms`, + ); + } + } + }, [ + patientSymptoms, + questionnaireResponse, + symptoms.length, + updateQuestionnaireResponseCB, + ]); const handleAddSymptom = (code: Code) => { const newSymptoms = [ ...symptoms, { ...SYMPTOM_INITIAL_VALUE, code }, - ] as Symptom[]; + ] as SymptomRequest[]; updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [{ type: "symptom", value: newSymptoms }], @@ -70,14 +315,32 @@ export function SymptomQuestion({ }; const handleRemoveSymptom = (index: number) => { - const newSymptoms = symptoms.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "symptom", value: newSymptoms }], - }); + const symptom = symptoms[index]; + if (symptom.id) { + // For existing records, update verification status to entered_in_error + const newSymptoms = symptoms.map((s, i) => + i === index + ? { ...s, verification_status: "entered_in_error" as const } + : s, + ); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [{ type: "symptom", value: newSymptoms }], + }); + } else { + // For new records, remove them completely + const newSymptoms = symptoms.filter((_, i) => i !== index); + updateQuestionnaireResponseCB({ + ...questionnaireResponse, + values: [{ type: "symptom", value: newSymptoms }], + }); + } }; - const handleUpdateSymptom = (index: number, updates: Partial) => { + const handleUpdateSymptom = ( + index: number, + updates: Partial, + ) => { const newSymptoms = symptoms.map((symptom, i) => i === index ? { ...symptom, ...updates } : symptom, ); @@ -88,24 +351,25 @@ export function SymptomQuestion({ }; return ( - <> +
{symptoms.length > 0 && (
-
Symptom
-
Date
-
Status
-
Severity
-
Action
+
{t("symptom")}
+
{t("date")}
+
{t("status")}
+
{t("severity")}
+
{t("action")}
{symptoms.map((symptom, index) => ( - handleUpdateSymptom(index, updates)} - onRemove={() => handleRemoveSymptom(index)} + onUpdate={handleUpdateSymptom} + onRemove={handleRemoveSymptom} /> ))}
@@ -113,176 +377,10 @@ export function SymptomQuestion({ )} - - ); -} - -interface SymptomItemProps { - symptom: Symptom; - disabled?: boolean; - onUpdate?: (symptom: Partial) => void; - onRemove?: () => void; -} - -const SymptomItem: React.FC = ({ - symptom, - disabled, - onUpdate, - onRemove, -}) => { - const [showNotes, setShowNotes] = useState(false); - - return ( -
-
-
-
- {symptom.code.display} -
-
- - - - - - setShowNotes(!showNotes)}> - - {showNotes ? "Hide Notes" : "Add Notes"} - - - - - Remove Symptom - - - -
-
-
-
- - - onUpdate?.({ - onset: { onset_datetime: e.target.value }, - }) - } - disabled={disabled} - className="h-8 md:h-9" - /> -
-
- - -
-
- - -
-
-
- - - - - - setShowNotes(!showNotes)}> - - {showNotes ? "Hide Notes" : "Add Notes"} - - - - - Remove Symptom - - - -
-
- {showNotes && ( -
- onUpdate?.({ note: e.target.value })} - disabled={disabled} - /> -
- )}
); -}; +} diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts index 8e5fdba0078..86fd15d4e96 100644 --- a/src/components/Questionnaire/structured/handlers.ts +++ b/src/components/Questionnaire/structured/handlers.ts @@ -80,47 +80,38 @@ const handlers: { }, }, symptom: { - getRequests: (symptoms, { patientId, encounterId }) => - symptoms.map((symptom) => { - const body: RequestTypeFor<"symptom"> = { - clinical_status: symptom.clinical_status, - verification_status: symptom.verification_status, - code: symptom.code, - severity: symptom.severity, - onset: symptom.onset, - recorded_date: symptom.recorded_date, - note: symptom.note, - encounter: encounterId, - }; - - return { - url: `/api/v1/patient/${patientId}/symptom/`, + getRequests: (symptoms, { patientId, encounterId }) => { + return [ + { + url: `/api/v1/patient/${patientId}/symptom/upsert/`, method: "POST", - body, + body: { + datapoints: symptoms.map((symptom) => ({ + ...symptom, + encounter: encounterId, + })), + }, reference_id: "symptom", - }; - }), + }, + ]; + }, }, diagnosis: { - getRequests: (diagnoses, { patientId, encounterId }) => - diagnoses.map((diagnosis) => { - const body: RequestTypeFor<"diagnosis"> = { - clinical_status: diagnosis.clinical_status, - verification_status: diagnosis.verification_status, - code: diagnosis.code, - onset: diagnosis.onset, - recorded_date: diagnosis.recorded_date, - note: diagnosis.note, - encounter: encounterId, - }; - - return { - url: `/api/v1/patient/${patientId}/diagnosis/`, + getRequests: (diagnoses, { patientId, encounterId }) => { + return [ + { + url: `/api/v1/patient/${patientId}/diagnosis/upsert/`, method: "POST", - body, + body: { + datapoints: diagnoses.map((diagnosis) => ({ + ...diagnosis, + encounter: encounterId, + })), + }, reference_id: "diagnosis", - }; - }), + }, + ]; + }, }, encounter: { getRequests: (encounters, { patientId, encounterId }) => { diff --git a/src/components/Questionnaire/structured/types.ts b/src/components/Questionnaire/structured/types.ts index e871f8bdb6d..de61c9f9b5c 100644 --- a/src/components/Questionnaire/structured/types.ts +++ b/src/components/Questionnaire/structured/types.ts @@ -1,9 +1,9 @@ import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; -import { Diagnosis, DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; +import { DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatement } from "@/types/emr/medicationStatement"; -import { Symptom, SymptomRequest } from "@/types/emr/symptom/symptom"; +import { SymptomRequest } from "@/types/emr/symptom/symptom"; import { StructuredQuestionType } from "@/types/questionnaire/question"; import { AppointmentCreateRequest, @@ -15,8 +15,8 @@ export interface StructuredDataMap { allergy_intolerance: AllergyIntoleranceRequest; medication_request: MedicationRequest; medication_statement: MedicationStatement; - symptom: Symptom; - diagnosis: Diagnosis; + symptom: SymptomRequest; + diagnosis: DiagnosisRequest; encounter: Encounter; appointment: CreateAppointmentQuestion; } @@ -26,8 +26,8 @@ export interface StructuredRequestMap { allergy_intolerance: { datapoints: AllergyIntoleranceRequest[] }; medication_request: { datapoints: MedicationRequest[] }; medication_statement: { datapoints: MedicationStatement[] }; - symptom: SymptomRequest; - diagnosis: DiagnosisRequest; + symptom: { datapoints: SymptomRequest[] }; + diagnosis: { datapoints: DiagnosisRequest[] }; encounter: EncounterEditRequest; appointment: AppointmentCreateRequest; } diff --git a/src/types/emr/diagnosis/diagnosis.ts b/src/types/emr/diagnosis/diagnosis.ts index 62e5a92c946..7c91413a778 100644 --- a/src/types/emr/diagnosis/diagnosis.ts +++ b/src/types/emr/diagnosis/diagnosis.ts @@ -33,6 +33,7 @@ export type Onset = { }; export interface Diagnosis { + id: string; code: Code; clinical_status: DiagnosisClinicalStatus; verification_status: DiagnosisVerificationStatus; @@ -44,6 +45,7 @@ export interface Diagnosis { } export interface DiagnosisRequest { + id?: string; clinical_status: DiagnosisClinicalStatus; verification_status: DiagnosisVerificationStatus; code: Code; diff --git a/src/types/emr/symptom/symptom.ts b/src/types/emr/symptom/symptom.ts index 3d55cfbad3e..cbe0c33d6c6 100644 --- a/src/types/emr/symptom/symptom.ts +++ b/src/types/emr/symptom/symptom.ts @@ -18,7 +18,7 @@ export const SYMPTOM_VERIFICATION_STATUS = [ "differential", "confirmed", "refuted", - "entered-in-error", + "entered_in_error", ] as const; export type SymptomVerificationStatus = @@ -36,6 +36,7 @@ type Onset = { }; export interface Symptom { + id: string; code: Code; clinical_status: SymptomClinicalStatus; verification_status: SymptomVerificationStatus; @@ -48,6 +49,7 @@ export interface Symptom { } export interface SymptomRequest { + id?: string; clinical_status: SymptomClinicalStatus; verification_status: SymptomVerificationStatus; code: Code; diff --git a/src/types/emr/symptom/symptomApi.ts b/src/types/emr/symptom/symptomApi.ts index 4ddaeead93f..4b91791e504 100644 --- a/src/types/emr/symptom/symptomApi.ts +++ b/src/types/emr/symptom/symptomApi.ts @@ -1,7 +1,7 @@ import { HttpMethod, Type } from "@/Utils/request/api"; import { PaginatedResponse } from "@/Utils/request/types"; -import { Symptom } from "./symptom"; +import { Symptom, SymptomRequest } from "./symptom"; export default { listSymptoms: { @@ -14,4 +14,10 @@ export default { method: HttpMethod.GET, TRes: Type(), }, + upsertSymptoms: { + path: "/api/v1/patient/{patientId}/symptom/upsert/", + method: HttpMethod.POST, + TRes: Type(), + TBody: Type<{ datapoints: SymptomRequest[] }>(), + }, }; diff --git a/src/types/questionnaire/form.ts b/src/types/questionnaire/form.ts index 3841c45a0db..6027dabcdb1 100644 --- a/src/types/questionnaire/form.ts +++ b/src/types/questionnaire/form.ts @@ -1,9 +1,9 @@ import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; -import { Diagnosis } from "@/types/emr/diagnosis/diagnosis"; +import { DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; import { Encounter } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatement } from "@/types/emr/medicationStatement"; -import { Symptom } from "@/types/emr/symptom/symptom"; +import { SymptomRequest } from "@/types/emr/symptom/symptom"; import { Code } from "@/types/questionnaire/code"; import { Quantity } from "@/types/questionnaire/quantity"; import { StructuredQuestionType } from "@/types/questionnaire/question"; @@ -31,8 +31,8 @@ export type ResponseValue = { | AllergyIntoleranceRequest[] | MedicationRequest[] | MedicationStatement[] - | Symptom[] - | Diagnosis[] + | SymptomRequest[] + | DiagnosisRequest[] | Encounter | CreateAppointmentQuestion; value_code?: Code; From ccb44f98577c91ca6bc3e79d47dd3ecfa7041dc2 Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Mon, 20 Jan 2025 17:43:33 +0530 Subject: [PATCH 06/17] Fixed status option to match BE --- public/locale/en.json | 7 +++---- public/locale/hi.json | 4 ++-- public/locale/kn.json | 4 ++-- public/locale/ml.json | 4 ++-- public/locale/ta.json | 4 ++-- .../Questionnaire/QuestionTypes/AllergyQuestion.tsx | 4 ++-- .../Questionnaire/QuestionTypes/DiagnosisQuestion.tsx | 2 +- .../QuestionTypes/MedicationRequestQuestion.tsx | 2 +- .../QuestionTypes/MedicationStatementQuestion.tsx | 2 +- src/types/emr/allergyIntolerance/allergyIntolerance.ts | 4 ++-- src/types/emr/diagnosis/diagnosis.ts | 2 +- 11 files changed, 19 insertions(+), 20 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index a841fa37bcc..12e34180892 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -975,7 +975,6 @@ "enter_valid_dob": "Enter a valid date of birth", "enter_valid_dob_age": "Please enter an age greater than 15 years", "enter_year_of_birth_to_verify": "Enter year of birth to verify", - "entered-in-error": "Entered in error", "entered_in_error": "Entered in Error", "entered_in_error_warning": "This action cannot be undone. The appointment will be marked as entered in error and removed from the system.", "error_404": "Error 404", @@ -1118,7 +1117,7 @@ "hearing": "We are hearing you...", "help_confirmed": "There is sufficient diagnostic and/or clinical evidence to treat this as a confirmed condition.", "help_differential": "One of a set of potential (and typically mutually exclusive) diagnoses asserted to further guide the diagnostic process and preliminary treatment.", - "help_entered-in-error": "The statement was entered in error and is not valid.", + "help_entered_in_error": "The statement was entered in error and is not valid.", "help_provisional": "This is a tentative diagnosis - still a candidate that is under consideration.", "help_refuted": "This condition has been ruled out by subsequent diagnostic and clinical evidence.", "help_unconfirmed": "There is not sufficient diagnostic and/or clinical evidence to treat this as a confirmed condition.", @@ -1878,8 +1877,8 @@ "select_class": "Select Class", "select_date": "Select date", "select_department": "Select Department", - "select_diff_role": "Please select a different role", "select_diet_preference": "Select diet preference", + "select_diff_role": "Please select a different role", "select_discharge_disposition": "Select discharge disposition", "select_eligible_policy": "Select an Eligible Insurance Policy", "select_facility": "Select Facility", @@ -1904,8 +1903,8 @@ "select_policy_to_add_items": "Select a Policy to Add Items", "select_practitioner": "Select Practitioner", "select_previous": "Select Previous Fields", - "select_prn_reason": "Select reason for PRN", "select_priority": "Select Priority", + "select_prn_reason": "Select reason for PRN", "select_register_patient": "Select/Register Patient", "select_role": "Select Role", "select_route": "Select route", diff --git a/public/locale/hi.json b/public/locale/hi.json index 70704e2cee6..d2761d94d41 100644 --- a/public/locale/hi.json +++ b/public/locale/hi.json @@ -343,7 +343,7 @@ "encounter_suggestion_edit_disallowed": "संपादन परामर्श में इस विकल्प पर स्विच करने की अनुमति नहीं है", "enter_file_name": "फ़ाइल का नाम दर्ज करें", "enter_valid_age": "कृपया वैध आयु दर्ज करें", - "entered-in-error": "त्रुटिवश प्रविष्ट हुआ", + "entered_in_error": "त्रुटिवश प्रविष्ट हुआ", "error_404": "त्रुटि 404", "error_deleting_shifting": "शिफ्टिंग रिकॉर्ड हटाते समय त्रुटि", "error_while_deleting_record": "रिकॉर्ड हटाते समय त्रुटि हुई", @@ -393,7 +393,7 @@ "goal": "हमारा लक्ष्य डिजिटल उपकरणों का उपयोग करके सार्वजनिक स्वास्थ्य सेवाओं की गुणवत्ता और पहुंच में निरंतर सुधार करना है।", "help_confirmed": "इस स्थिति को पुष्ट मानने के लिए पर्याप्त नैदानिक और/या नैदानिक साक्ष्य मौजूद हैं।", "help_differential": "संभावित (और आमतौर पर परस्पर अनन्य) निदानों के एक समूह में से एक, जो निदान प्रक्रिया और प्रारंभिक उपचार को आगे बढ़ाने के लिए निर्देशित किया जाता है।", - "help_entered-in-error": "यह कथन गलती से दर्ज किया गया है और मान्य नहीं है।", + "help_entered_in_error": "यह कथन गलती से दर्ज किया गया है और मान्य नहीं है।", "help_provisional": "यह एक अस्थायी निदान है - अभी भी इस पर विचार किया जा रहा है।", "help_refuted": "बाद के निदानात्मक और नैदानिक साक्ष्यों से इस स्थिति को खारिज कर दिया गया है।", "help_unconfirmed": "इसे एक पुष्ट स्थिति मानने के लिए पर्याप्त नैदानिक और/या नैदानिक साक्ष्य उपलब्ध नहीं हैं।", diff --git a/public/locale/kn.json b/public/locale/kn.json index 0ef06eea43c..d10d8cf542d 100644 --- a/public/locale/kn.json +++ b/public/locale/kn.json @@ -345,7 +345,7 @@ "encounter_suggestion_edit_disallowed": "ಸಂಪಾದನೆ ಸಮಾಲೋಚನೆಯಲ್ಲಿ ಈ ಆಯ್ಕೆಗೆ ಬದಲಾಯಿಸಲು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ", "enter_file_name": "ಫೈಲ್ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", "enter_valid_age": "ದಯವಿಟ್ಟು ಮಾನ್ಯವಾದ ವಯಸ್ಸನ್ನು ನಮೂದಿಸಿ", - "entered-in-error": "ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ", + "entered_in_error": "ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ", "error_404": "ದೋಷ 404", "error_deleting_shifting": "ಶಿಫ್ಟಿಂಗ್ ರೆಕಾರ್ಡ್ ಅನ್ನು ಅಳಿಸುವಾಗ ದೋಷ", "error_while_deleting_record": "ದಾಖಲೆಯನ್ನು ಅಳಿಸುವಾಗ ದೋಷ", @@ -395,7 +395,7 @@ "goal": "ಡಿಜಿಟಲ್ ಉಪಕರಣಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಸಾರ್ವಜನಿಕ ಆರೋಗ್ಯ ಸೇವೆಗಳ ಗುಣಮಟ್ಟ ಮತ್ತು ಪ್ರವೇಶವನ್ನು ನಿರಂತರವಾಗಿ ಸುಧಾರಿಸುವುದು ನಮ್ಮ ಗುರಿಯಾಗಿದೆ", "help_confirmed": "ಇದನ್ನು ದೃಢಪಡಿಸಿದ ಸ್ಥಿತಿ ಎಂದು ಪರಿಗಣಿಸಲು ಸಾಕಷ್ಟು ರೋಗನಿರ್ಣಯ ಮತ್ತು/ಅಥವಾ ಕ್ಲಿನಿಕಲ್ ಪುರಾವೆಗಳಿವೆ.", "help_differential": "ರೋಗನಿರ್ಣಯ ಪ್ರಕ್ರಿಯೆ ಮತ್ತು ಪ್ರಾಥಮಿಕ ಚಿಕಿತ್ಸೆಯನ್ನು ಮತ್ತಷ್ಟು ಮಾರ್ಗದರ್ಶನ ಮಾಡಲು ಸಮರ್ಥಿಸಲಾದ ಸಂಭಾವ್ಯ (ಮತ್ತು ಸಾಮಾನ್ಯವಾಗಿ ಪರಸ್ಪರ ಪ್ರತ್ಯೇಕವಾದ) ರೋಗನಿರ್ಣಯಗಳ ಒಂದು ಸೆಟ್.", - "help_entered-in-error": "ಹೇಳಿಕೆಯನ್ನು ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ ಮತ್ತು ಮಾನ್ಯವಾಗಿಲ್ಲ.", + "help_entered_in_error": "ಹೇಳಿಕೆಯನ್ನು ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ ಮತ್ತು ಮಾನ್ಯವಾಗಿಲ್ಲ.", "help_provisional": "ಇದು ತಾತ್ಕಾಲಿಕ ರೋಗನಿರ್ಣಯ - ಇನ್ನೂ ಪರಿಗಣನೆಯಲ್ಲಿರುವ ಅಭ್ಯರ್ಥಿ.", "help_refuted": "ನಂತರದ ರೋಗನಿರ್ಣಯ ಮತ್ತು ಕ್ಲಿನಿಕಲ್ ಪುರಾವೆಗಳಿಂದ ಈ ಸ್ಥಿತಿಯನ್ನು ತಳ್ಳಿಹಾಕಲಾಗಿದೆ.", "help_unconfirmed": "ಇದನ್ನು ದೃಢಪಡಿಸಿದ ಸ್ಥಿತಿ ಎಂದು ಪರಿಗಣಿಸಲು ಸಾಕಷ್ಟು ರೋಗನಿರ್ಣಯ ಮತ್ತು/ಅಥವಾ ವೈದ್ಯಕೀಯ ಪುರಾವೆಗಳಿಲ್ಲ.", diff --git a/public/locale/ml.json b/public/locale/ml.json index 189017c78a3..0b2ba70808f 100644 --- a/public/locale/ml.json +++ b/public/locale/ml.json @@ -902,7 +902,7 @@ "enter_valid_dob": "ഒരു സാധുവായ ജനനത്തീയതി നൽകുക", "enter_valid_dob_age": "ദയവായി 15 വയസ്സിൽ കൂടുതലുള്ള ഒരു പ്രായം നൽകുക", "enter_year_of_birth_to_verify": "സ്ഥിരീകരിക്കാൻ ജനന വർഷം നൽകുക", - "entered-in-error": "തെറ്റായി നൽകി", + "entered_in_error": "തെറ്റായി നൽകി", "entered_in_error": "പിശകിൽ പ്രവേശിച്ചു", "error_404": "പിശക് 404", "error_deleting_shifting": "ഷിഫ്റ്റിംഗ് റെക്കോർഡ് ഇല്ലാതാക്കുന്നതിൽ പിശക്", @@ -1016,7 +1016,7 @@ "hearing": "ഞങ്ങൾ പറയുന്നത് കേൾക്കുന്നു...", "help_confirmed": "ഇത് ഒരു സ്ഥിരീകരിച്ച അവസ്ഥയായി കണക്കാക്കാൻ മതിയായ ഡയഗ്നോസ്റ്റിക് കൂടാതെ/അല്ലെങ്കിൽ ക്ലിനിക്കൽ തെളിവുകൾ ഉണ്ട്.", "help_differential": "രോഗനിർണ്ണയ പ്രക്രിയയ്ക്കും പ്രാഥമിക ചികിത്സയ്ക്കും കൂടുതൽ മാർഗ്ഗനിർദ്ദേശം നൽകുന്നതിന് സാധ്യതയുള്ള (സാധാരണയായി പരസ്പരവിരുദ്ധമായ) രോഗനിർണയങ്ങളിൽ ഒന്ന്.", - "help_entered-in-error": "പ്രസ്‌താവന തെറ്റായി നൽകിയതിനാൽ സാധുതയില്ല.", + "help_entered_in_error": "പ്രസ്‌താവന തെറ്റായി നൽകിയതിനാൽ സാധുതയില്ല.", "help_provisional": "ഇതൊരു താൽക്കാലിക രോഗനിർണയമാണ് - ഇപ്പോഴും പരിഗണനയിലിരിക്കുന്ന ഒരു സ്ഥാനാർത്ഥി.", "help_refuted": "തുടർന്നുള്ള ഡയഗ്നോസ്റ്റിക്, ക്ലിനിക്കൽ തെളിവുകൾ വഴി ഈ അവസ്ഥ ഒഴിവാക്കിയിട്ടുണ്ട്.", "help_unconfirmed": "ഇത് ഒരു സ്ഥിരീകരിച്ച അവസ്ഥയായി കണക്കാക്കാൻ മതിയായ ഡയഗ്നോസ്റ്റിക് കൂടാതെ/അല്ലെങ്കിൽ ക്ലിനിക്കൽ തെളിവുകൾ ഇല്ല.", diff --git a/public/locale/ta.json b/public/locale/ta.json index cfe2c19b91e..1caa3b6ea4c 100644 --- a/public/locale/ta.json +++ b/public/locale/ta.json @@ -344,7 +344,7 @@ "encounter_suggestion_edit_disallowed": "திருத்த ஆலோசனையில் இந்த விருப்பத்திற்கு மாற அனுமதிக்கப்படவில்லை", "enter_file_name": "கோப்பு பெயரை உள்ளிடவும்", "enter_valid_age": "செல்லுபடியாகும் வயதை உள்ளிடவும்", - "entered-in-error": "தவறுதலாக உள்ளிடப்பட்டது", + "entered_in_error": "தவறுதலாக உள்ளிடப்பட்டது", "error_404": "பிழை 404", "error_deleting_shifting": "பதிவை மாற்றுவதில் பிழை", "error_while_deleting_record": "பதிவை நீக்குவதில் பிழை", @@ -394,7 +394,7 @@ "goal": "டிஜிட்டல் கருவிகளைப் பயன்படுத்தி பொது சுகாதார சேவைகளின் தரம் மற்றும் அணுகல்தன்மையை தொடர்ந்து மேம்படுத்துவதே எங்கள் குறிக்கோள்.", "help_confirmed": "இதை உறுதிப்படுத்தப்பட்ட நிலையாகக் கருதுவதற்கு போதுமான நோயறிதல் மற்றும்/அல்லது மருத்துவ சான்றுகள் உள்ளன.", "help_differential": "சாத்தியமான (மற்றும் பொதுவாக பரஸ்பரம் பிரத்தியேகமான) நோயறிதல்களின் தொகுப்பில் ஒன்று கண்டறியும் செயல்முறை மற்றும் பூர்வாங்க சிகிச்சையை மேலும் வழிகாட்டும்.", - "help_entered-in-error": "அறிக்கை பிழையாக உள்ளிடப்பட்டது, அது செல்லாது.", + "help_entered_in_error": "அறிக்கை பிழையாக உள்ளிடப்பட்டது, அது செல்லாது.", "help_provisional": "இது ஒரு தற்காலிக நோயறிதல் - இன்னும் ஒரு வேட்பாளர் பரிசீலனையில் உள்ளது.", "help_refuted": "இந்த நிலை அடுத்தடுத்த நோயறிதல் மற்றும் மருத்துவ சான்றுகளால் நிராகரிக்கப்பட்டது.", "help_unconfirmed": "இதை உறுதிப்படுத்தப்பட்ட நிலையாகக் கருதுவதற்கு போதுமான நோயறிதல் மற்றும்/அல்லது மருத்துவ சான்றுகள் இல்லை.", diff --git a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx index 16e9536a4e5..fbd1a606593 100644 --- a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx @@ -339,7 +339,7 @@ export function AllergyQuestion({ Low High - + Unable to Assess @@ -500,7 +500,7 @@ const AllergyTableRow = ({ Low High - Unable to Assess + Unable to Assess diff --git a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx index 5dbc5fbc279..4d9c1b2c5d9 100644 --- a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx @@ -136,7 +136,7 @@ export function DiagnosisQuestion({ // For existing records, update verification status to entered_in_error const newDiagnoses = diagnoses.map((d, i) => i === index - ? { ...d, verification_status: "entered-in-error" as const } + ? { ...d, verification_status: "entered_in_error" as const } : d, ) as DiagnosisRequest[]; updateQuestionnaireResponseCB({ diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index 2795c3155e7..6bdafac98db 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -143,7 +143,7 @@ export function MedicationRequestQuestion({ const medication = medications[medicationToDelete]; if (medication.id) { - // For existing records, update status to entered-in-error + // For existing records, update status to entered_in_error const newMedications = medications.map((med, i) => i === medicationToDelete ? { ...med, status: "entered_in_error" as const } diff --git a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx index 1c8fb573126..be4b326eb4b 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx @@ -123,7 +123,7 @@ export function MedicationStatementQuestion({ const handleRemoveMedication = (index: number) => { const medication = medications[index]; if (medication.id) { - // For existing records, update status to entered-in-error + // For existing records, update status to entered_in_error const newMedications = medications.map((med, i) => i === index ? { ...med, status: "entered_in_error" as MedicationStatementStatus } diff --git a/src/types/emr/allergyIntolerance/allergyIntolerance.ts b/src/types/emr/allergyIntolerance/allergyIntolerance.ts index d0870191bde..0b0b857590f 100644 --- a/src/types/emr/allergyIntolerance/allergyIntolerance.ts +++ b/src/types/emr/allergyIntolerance/allergyIntolerance.ts @@ -6,7 +6,7 @@ export type AllergyVerificationStatus = | "confirmed" | "refuted" | "presumed" - | "entered-in-error"; + | "entered_in_error"; export type AllergyClinicalStatus = "active" | "inactive" | "resolved"; // Base type for allergy data @@ -43,5 +43,5 @@ export const ALLERGY_VERIFICATION_STATUS = { confirmed: "Confirmed", refuted: "Refuted", presumed: "Presumed", - "entered-in-error": "Entered in Error", + entered_in_error: "Entered in Error", }; diff --git a/src/types/emr/diagnosis/diagnosis.ts b/src/types/emr/diagnosis/diagnosis.ts index 7c91413a778..690f1d64795 100644 --- a/src/types/emr/diagnosis/diagnosis.ts +++ b/src/types/emr/diagnosis/diagnosis.ts @@ -19,7 +19,7 @@ export const DIAGNOSIS_VERIFICATION_STATUS = [ "differential", "confirmed", "refuted", - "entered-in-error", + "entered_in_error", ] as const; export type DiagnosisVerificationStatus = From 372b02c11340e2a1f7d34c9928eb42f6be802f5b Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Mon, 20 Jan 2025 18:02:06 +0530 Subject: [PATCH 07/17] Lint fix --- src/types/emr/medicationRequest/medicationRequestApi.ts | 3 +-- src/types/emr/medicationStatement/medicationStatementApi.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/types/emr/medicationRequest/medicationRequestApi.ts b/src/types/emr/medicationRequest/medicationRequestApi.ts index c2c50020b2d..5fe8c146b1f 100644 --- a/src/types/emr/medicationRequest/medicationRequestApi.ts +++ b/src/types/emr/medicationRequest/medicationRequestApi.ts @@ -1,7 +1,6 @@ import { Type } from "@/Utils/request/api"; import { PaginatedResponse } from "@/Utils/request/types"; - -import { MedicationRequestRead } from "../medicationRequest"; +import { MedicationRequestRead } from "@/types/emr/medicationRequest"; const medicationRequestApi = { // Medication diff --git a/src/types/emr/medicationStatement/medicationStatementApi.ts b/src/types/emr/medicationStatement/medicationStatementApi.ts index 51daaf8af27..68736e4ab13 100644 --- a/src/types/emr/medicationStatement/medicationStatementApi.ts +++ b/src/types/emr/medicationStatement/medicationStatementApi.ts @@ -1,7 +1,6 @@ import { Type } from "@/Utils/request/api"; import { PaginatedResponse } from "@/Utils/request/types"; - -import { MedicationStatement } from "../medicationStatement"; +import { MedicationStatement } from "@/types/emr/medicationStatement"; const medicationStatementApi = { list: { From 44bde9aea542703b6c5f36c743417e468d146f14 Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Tue, 21 Jan 2025 20:46:02 +0530 Subject: [PATCH 08/17] Fix cache issue after updating --- src/components/Patient/symptoms/list.tsx | 2 +- .../Questionnaire/QuestionTypes/AllergyQuestion.tsx | 2 +- .../Questionnaire/QuestionTypes/DiagnosisQuestion.tsx | 9 ++------- .../QuestionTypes/MedicationRequestQuestion.tsx | 2 +- .../QuestionTypes/MedicationStatementQuestion.tsx | 2 +- .../Questionnaire/QuestionTypes/SymptomQuestion.tsx | 9 ++------- src/components/Questionnaire/QuestionnaireForm.tsx | 4 +++- 7 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/components/Patient/symptoms/list.tsx b/src/components/Patient/symptoms/list.tsx index 70673d72295..64100b7ca1f 100644 --- a/src/components/Patient/symptoms/list.tsx +++ b/src/components/Patient/symptoms/list.tsx @@ -83,7 +83,7 @@ const SymptomListLayout = ({ {t("symptoms")} {facilityId && ( diff --git a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx index fbd1a606593..114b7dc6340 100644 --- a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx @@ -123,7 +123,7 @@ export function AllergyQuestion({ }); useEffect(() => { - if (patientAllergies?.results && !allergies.length) { + if (patientAllergies?.results) { updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [ diff --git a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx index 4d9c1b2c5d9..3b219d30324 100644 --- a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx @@ -91,7 +91,7 @@ export function DiagnosisQuestion({ }); useEffect(() => { - if (patientDiagnoses?.results && !diagnoses.length) { + if (patientDiagnoses?.results) { updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [ @@ -107,12 +107,7 @@ export function DiagnosisQuestion({ ); } } - }, [ - patientDiagnoses, - questionnaireResponse, - diagnoses.length, - updateQuestionnaireResponseCB, - ]); + }, [patientDiagnoses]); const handleAddDiagnosis = (code: Code) => { const newDiagnoses = [ diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index 6bdafac98db..fe588f251ee 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -88,7 +88,7 @@ export function MedicationRequestQuestion({ }); useEffect(() => { - if (patientMedications?.results && !medications.length) { + if (patientMedications?.results) { updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [ diff --git a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx index be4b326eb4b..3b2a6a5ee8a 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx @@ -83,7 +83,7 @@ export function MedicationStatementQuestion({ }); useEffect(() => { - if (patientMedications?.results && !medications.length) { + if (patientMedications?.results) { updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [ diff --git a/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx b/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx index 70b1cf9dffa..c1d6e1a0bca 100644 --- a/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/SymptomQuestion.tsx @@ -280,7 +280,7 @@ export function SymptomQuestion({ }); useEffect(() => { - if (patientSymptoms?.results && !symptoms.length) { + if (patientSymptoms?.results) { updateQuestionnaireResponseCB({ ...questionnaireResponse, values: [ @@ -296,12 +296,7 @@ export function SymptomQuestion({ ); } } - }, [ - patientSymptoms, - questionnaireResponse, - symptoms.length, - updateQuestionnaireResponseCB, - ]); + }, [patientSymptoms]); const handleAddSymptom = (code: Code) => { const newSymptoms = [ diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index 9bf220806ca..97251af439c 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -1,4 +1,4 @@ -import { useMutation, useQuery } from "@tanstack/react-query"; +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { t } from "i18next"; import { useNavigationPrompt } from "raviger"; import { useEffect, useState } from "react"; @@ -64,6 +64,7 @@ export function QuestionnaireForm({ onCancel, facilityId, }: QuestionnaireFormProps) { + const queryClient = useQueryClient(); const [isDirty, setIsDirty] = useState(false); const [questionnaireForms, setQuestionnaireForms] = useState< QuestionnaireFormState[] @@ -87,6 +88,7 @@ export function QuestionnaireForm({ const { mutate: submitBatch, isPending } = useMutation({ mutationFn: mutate(routes.batchRequest, { silent: true }), onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["allergies", patientId] }); toast.success(t("questionnaire_submitted_successfully")); onSubmit?.(); }, From a3d2371c09486d81682a131ffe909148994b990a Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Thu, 23 Jan 2025 13:42:38 +0530 Subject: [PATCH 09/17] Rebuild questionnaireForm updateHandler to receive values instead of entire QuestionnaireResponse --- .../Questionnaire/QuestionRenderer.tsx | 27 ++++--- .../QuestionTypes/AllergyQuestion.tsx | 43 ++++++----- .../QuestionTypes/AppointmentQuestion.tsx | 15 ++-- .../QuestionTypes/BooleanQuestion.tsx | 20 ++++-- .../QuestionTypes/ChoiceQuestion.tsx | 18 +++-- .../QuestionTypes/DateTimeQuestion.tsx | 30 +++++--- .../QuestionTypes/DiagnosisQuestion.tsx | 51 +++++++------ .../QuestionTypes/EncounterQuestion.tsx | 14 ++-- .../MedicationRequestQuestion.tsx | 72 +++++++++---------- .../MedicationStatementQuestion.tsx | 66 ++++++++--------- .../QuestionTypes/NotesInput.tsx | 11 +-- .../QuestionTypes/NumberQuestion.tsx | 20 ++++-- .../QuestionTypes/QuestionGroup.tsx | 11 ++- .../QuestionTypes/QuestionInput.tsx | 39 ++++++---- .../QuestionTypes/SymptomQuestion.tsx | 51 +++++++------ .../QuestionTypes/TextQuestion.tsx | 20 ++++-- .../Questionnaire/QuestionnaireForm.tsx | 40 +++++++++-- 17 files changed, 320 insertions(+), 228 deletions(-) diff --git a/src/components/Questionnaire/QuestionRenderer.tsx b/src/components/Questionnaire/QuestionRenderer.tsx index b4f5168c60e..d63aa0e4cf7 100644 --- a/src/components/Questionnaire/QuestionRenderer.tsx +++ b/src/components/Questionnaire/QuestionRenderer.tsx @@ -3,7 +3,10 @@ import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; import { QuestionValidationError } from "@/types/questionnaire/batch"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import { Question, StructuredQuestionType, @@ -20,7 +23,7 @@ const FULL_WIDTH_QUESTION_TYPES: StructuredQuestionType[] = [ interface QuestionRendererProps { questions: Question[]; responses: QuestionnaireResponse[]; - onResponseChange: (responses: QuestionnaireResponse[]) => void; + onResponseChange: (values: ResponseValue[], questionId: string) => void; errors: QuestionValidationError[]; clearError: (questionId: string) => void; disabled?: boolean; @@ -53,18 +56,12 @@ export function QuestionRenderer({ } }, [activeGroupId]); - const handleResponseChange = (updatedResponse: QuestionnaireResponse) => { - const newResponses = [...responses]; - const index = newResponses.findIndex( - (r) => r.question_id === updatedResponse.question_id, - ); - if (index !== -1) { - newResponses[index] = updatedResponse; - } else { - newResponses.push(updatedResponse); - } - onResponseChange(newResponses); - }; + // const handleResponseChange = ( + // updatedResponse: ResponseValue[], + // questionId: string, + // ) => { + // onResponseChange(newResponses, questionId); + // }; const shouldBeFullWidth = (question: Question): boolean => question.type === "structured" && @@ -86,7 +83,7 @@ export function QuestionRenderer({ question={question} encounterId={encounterId} questionnaireResponses={responses} - updateQuestionnaireResponseCB={handleResponseChange} + updateQuestionnaireResponseCB={onResponseChange} errors={errors} clearError={clearError} disabled={disabled} diff --git a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx index 8ccc6512821..8d6e7fed76b 100644 --- a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx @@ -53,14 +53,21 @@ import { } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; import { Code } from "@/types/questionnaire/code"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; interface AllergyQuestionProps { patientId: string; question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -126,15 +133,15 @@ export function AllergyQuestion({ useEffect(() => { if (patientAllergies?.results) { - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "allergy_intolerance", value: patientAllergies.results.map(convertToAllergyRequest), }, ], - }); + questionnaireResponse.question_id, + ); } }, [patientAllergies]); @@ -143,18 +150,18 @@ export function AllergyQuestion({ ...allergies, { ...ALLERGY_INITIAL_VALUE, code }, ] as AllergyIntoleranceRequest[]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "allergy_intolerance", value: newAllergies }], - }); + updateQuestionnaireResponseCB( + [{ type: "allergy_intolerance", value: newAllergies }], + questionnaireResponse.question_id, + ); }; const handleRemoveAllergy = (index: number) => { const newAllergies = allergies.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "allergy_intolerance", value: newAllergies }], - }); + updateQuestionnaireResponseCB( + [{ type: "allergy_intolerance", value: newAllergies }], + questionnaireResponse.question_id, + ); }; const handleUpdateAllergy = ( @@ -164,10 +171,10 @@ export function AllergyQuestion({ const newAllergies = allergies.map((allergy, i) => i === index ? { ...allergy, ...updates } : allergy, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "allergy_intolerance", value: newAllergies }], - }); + updateQuestionnaireResponseCB( + [{ type: "allergy_intolerance", value: newAllergies }], + questionnaireResponse.question_id, + ); }; return ( diff --git a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx index 916f97018e5..7f30558df4f 100644 --- a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx @@ -33,7 +33,11 @@ import { UserBase } from "@/types/user/user"; interface FollowUpVisitQuestionProps { question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -54,15 +58,16 @@ export function AppointmentQuestion({ const handleUpdate = (updates: Partial) => { const appointment = { ...value, ...updates }; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "appointment", value: [appointment] as unknown as ResponseValue["value"], }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; const facilityId = useSlug("facility"); diff --git a/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx b/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx index b2b094790b4..fb360302835 100644 --- a/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx @@ -1,13 +1,20 @@ import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import type { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import type { Question } from "@/types/questionnaire/question"; interface BooleanQuestionProps { question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; clearError: () => void; } @@ -24,15 +31,16 @@ export function BooleanQuestion({ value={questionnaireResponse.values[0]?.value?.toString()} onValueChange={(value) => { clearError(); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "boolean", value: value === "true", }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }} disabled={disabled} > diff --git a/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx b/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx index 4c0f7259bef..95475f7acc5 100644 --- a/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx @@ -9,14 +9,19 @@ import { } from "@/components/ui/select"; import { properCase } from "@/Utils/utils"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import type { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import type { AnswerOption, Question } from "@/types/questionnaire/question"; interface ChoiceQuestionProps { question: Question; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: ( - questionnaireResponse: QuestionnaireResponse, + values: ResponseValue[], + questionId: string, + note?: string, ) => void; disabled?: boolean; withLabel?: boolean; @@ -43,10 +48,11 @@ export const ChoiceQuestion = memo(function ChoiceQuestion({ value: newValue, }; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: newValues, - }); + updateQuestionnaireResponseCB( + newValues, + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; return ( diff --git a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx index b20629e171c..9fa2e201157 100644 --- a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx @@ -13,11 +13,18 @@ import { PopoverTrigger, } from "@/components/ui/popover"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import type { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; interface DateTimeQuestionProps { questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; clearError: () => void; classes?: string; @@ -43,15 +50,16 @@ export function DateTimeQuestion({ date.setMinutes(currentValue.getMinutes()); } - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "dateTime", value: date.toISOString(), }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; const handleTimeChange = (event: React.ChangeEvent) => { @@ -62,15 +70,17 @@ export function DateTimeQuestion({ date.setHours(hours); date.setMinutes(minutes); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ + ...questionnaireResponse.values, { type: "dateTime", value: date.toISOString(), }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; const formatTime = (date: Date | undefined) => { diff --git a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx index da825a94a7d..e02a760abd5 100644 --- a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx @@ -37,12 +37,19 @@ import { } from "@/types/emr/diagnosis/diagnosis"; import diagnosisApi from "@/types/emr/diagnosis/diagnosisApi"; import { Code } from "@/types/questionnaire/code"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; interface DiagnosisQuestionProps { patientId: string; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -94,15 +101,15 @@ export function DiagnosisQuestion({ useEffect(() => { if (patientDiagnoses?.results) { - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "diagnosis", value: patientDiagnoses.results.map(convertToDiagnosisRequest), }, ], - }); + questionnaireResponse.question_id, + ); } }, [patientDiagnoses]); @@ -111,15 +118,15 @@ export function DiagnosisQuestion({ ...diagnoses, { ...DIAGNOSIS_INITIAL_VALUE, code }, ] as DiagnosisRequest[]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "diagnosis", value: newDiagnoses, }, ], - }); + questionnaireResponse.question_id, + ); }; const handleRemoveDiagnosis = (index: number) => { @@ -131,27 +138,27 @@ export function DiagnosisQuestion({ ? { ...d, verification_status: "entered_in_error" as const } : d, ) as DiagnosisRequest[]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "diagnosis", value: newDiagnoses, }, ], - }); + questionnaireResponse.question_id, + ); } else { // For new records, remove them completely const newDiagnoses = diagnoses.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "diagnosis", value: newDiagnoses, }, ], - }); + questionnaireResponse.question_id, + ); } }; @@ -162,15 +169,15 @@ export function DiagnosisQuestion({ const newDiagnoses = diagnoses.map((diagnosis, i) => i === index ? { ...diagnosis, ...updates } : diagnosis, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "diagnosis", value: newDiagnoses, }, ], - }); + questionnaireResponse.question_id, + ); }; return ( diff --git a/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx b/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx index 61cc44e4a95..315221b64de 100644 --- a/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx @@ -40,7 +40,11 @@ interface EncounterQuestionProps { question: Question; encounterId: string; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; clearError: () => void; organizations?: string[]; @@ -124,10 +128,10 @@ export function EncounterQuestion({ value: [encounterRequest] as unknown as typeof responseValue.value, }; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [responseValue], - }); + updateQuestionnaireResponseCB( + [responseValue], + questionnaireResponse.question_id, + ); }; if (isLoading) { diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index fdea9a249d3..f91d6dfaf3a 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -64,12 +64,19 @@ import { } from "@/types/emr/medicationRequest"; import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; import { Code } from "@/types/questionnaire/code"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; interface MedicationRequestQuestionProps { patientId: string; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -83,7 +90,7 @@ export function MedicationRequestQuestion({ (questionnaireResponse.values?.[0]?.value as MedicationRequest[]) || []; const { data: patientMedications } = useQuery({ - queryKey: ["medications", patientId], + queryKey: ["medication_requests", patientId], queryFn: query(medicationRequestApi.list, { pathParams: { patientId }, queryParams: { @@ -94,15 +101,10 @@ export function MedicationRequestQuestion({ useEffect(() => { if (patientMedications?.results) { - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_request", - value: patientMedications.results, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: patientMedications.results }], + questionnaireResponse.question_id, + ); } }, [patientMedications]); @@ -123,15 +125,10 @@ export function MedicationRequestQuestion({ authored_on: new Date().toISOString(), }, ]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_request", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); setExpandedMedicationIndex(newMedications.length - 1); }; @@ -150,19 +147,19 @@ export function MedicationRequestQuestion({ ? { ...med, status: "entered_in_error" as const } : med, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_request", value: newMedications }], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); } else { // For new records, remove them completely const newMedications = medications.filter( (_, i) => i !== medicationToDelete, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_request", value: newMedications }], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); } setMedicationToDelete(null); }; @@ -175,15 +172,10 @@ export function MedicationRequestQuestion({ i === index ? { ...medication, ...updates } : medication, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_request", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); }; return ( @@ -862,8 +854,8 @@ const MedicationRequestGridRow: React.FC = ({ values: [], note: medication.note, }} - updateQuestionnaireResponseCB={(response) => { - onUpdate?.({ note: response.note }); + handleUpdateNote={(note) => { + onUpdate?.({ note: note }); }} disabled={disabled} /> diff --git a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx index 305094808f3..2179d134fd3 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx @@ -56,13 +56,18 @@ import { import medicationStatementApi from "@/types/emr/medicationStatement/medicationStatementApi"; import { Code } from "@/types/questionnaire/code"; import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { ResponseValue } from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; interface MedicationStatementQuestionProps { patientId: string; question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -111,15 +116,10 @@ export function MedicationStatementQuestion({ useEffect(() => { if (patientMedications?.results) { - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_statement", - value: patientMedications.results, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: patientMedications.results }], + questionnaireResponse.question_id, + ); } }, [patientMedications]); @@ -128,15 +128,10 @@ export function MedicationStatementQuestion({ ...medications, { ...MEDICATION_STATEMENT_INITIAL_VALUE, medication }, ]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_statement", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); setExpandedMedicationIndex(newMedications.length - 1); }; @@ -155,19 +150,19 @@ export function MedicationStatementQuestion({ ? { ...med, status: "entered_in_error" as const } : med, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_statement", value: newMedications }], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); } else { // For new records, remove them completely const newMedications = medications.filter( (_, i) => i !== medicationToDelete, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_statement", value: newMedications }], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); } setMedicationToDelete(null); }; @@ -180,15 +175,10 @@ export function MedicationStatementQuestion({ i === index ? { ...medication, ...updates } : medication, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_statement", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); }; return ( @@ -547,8 +537,8 @@ const MedicationStatementGridRow: React.FC = ({ values: [], note: medication.note, }} - updateQuestionnaireResponseCB={(response) => { - onUpdate?.({ note: response.note }); + handleUpdateNote={(note) => { + onUpdate?.({ note: note }); }} disabled={disabled} /> diff --git a/src/components/Questionnaire/QuestionTypes/NotesInput.tsx b/src/components/Questionnaire/QuestionTypes/NotesInput.tsx index e6ec41120c0..5cbec9d3a5a 100644 --- a/src/components/Questionnaire/QuestionTypes/NotesInput.tsx +++ b/src/components/Questionnaire/QuestionTypes/NotesInput.tsx @@ -14,14 +14,14 @@ import type { QuestionnaireResponse } from "@/types/questionnaire/form"; interface NotesInputProps { questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + handleUpdateNote: (note: string) => void; disabled?: boolean; className?: string; } export function NotesInput({ questionnaireResponse, - updateQuestionnaireResponseCB, + handleUpdateNote, disabled, className, }: NotesInputProps) { @@ -50,12 +50,7 @@ export function NotesInput({