From 5240247300df6580206325182552e08ae7939c31 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 12:34:12 +0530 Subject: [PATCH 01/10] Use only active questionnaires --- src/components/Questionnaire/QuestionnaireSearch.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Questionnaire/QuestionnaireSearch.tsx b/src/components/Questionnaire/QuestionnaireSearch.tsx index f2227944d5a..70fb2e613c7 100644 --- a/src/components/Questionnaire/QuestionnaireSearch.tsx +++ b/src/components/Questionnaire/QuestionnaireSearch.tsx @@ -47,6 +47,7 @@ export function QuestionnaireSearch({ ...conditionalAttribute(!!subjectType, { subject_type: subjectType, }), + status: "active", }, }), }); From a1821e39ae858af84fb0891ae12efb7c8bdd60d7 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 12:34:50 +0530 Subject: [PATCH 02/10] Add support for Location Assignment Question --- src/common/constants.tsx | 1 + src/components/Auth/Login.tsx | 9 +- src/components/Common/LoginHeader.tsx | 9 +- .../QuestionnaireResponsesList.tsx | 5 +- src/components/Location/LocationSearch.tsx | 84 +++++++++++++ .../QuestionTypes/LocationQuestion.tsx | 116 ++++++++++++++++++ .../QuestionTypes/QuestionInput.tsx | 13 ++ .../Questionnaire/QuestionnaireEditor.tsx | 29 +++-- .../Questionnaire/QuestionnaireForm.tsx | 7 +- .../Questionnaire/structured/handlers.ts | 43 ++++++- .../Questionnaire/structured/types.ts | 6 + src/types/location/association.ts | 22 ++++ src/types/location/locationApi.ts | 28 +++++ src/types/questionnaire/form.ts | 7 +- src/types/questionnaire/question.ts | 3 +- 15 files changed, 360 insertions(+), 22 deletions(-) create mode 100644 src/components/Location/LocationSearch.tsx create mode 100644 src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx create mode 100644 src/types/location/association.ts diff --git a/src/common/constants.tsx b/src/common/constants.tsx index fcfc2619663..8fda21a820f 100644 --- a/src/common/constants.tsx +++ b/src/common/constants.tsx @@ -9,6 +9,7 @@ export const LocalStorageKeys = { accessToken: "care_access_token", refreshToken: "care_refresh_token", patientTokenKey: "care_patient_token", + loginPreference: "care_login_preference", }; export interface OptionsType { diff --git a/src/components/Auth/Login.tsx b/src/components/Auth/Login.tsx index efd4403c23d..39191bdca9c 100644 --- a/src/components/Auth/Login.tsx +++ b/src/components/Auth/Login.tsx @@ -1,7 +1,7 @@ import careConfig from "@careConfig"; import { useMutation } from "@tanstack/react-query"; import { Link, useQueryParams } from "raviger"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import ReCaptcha from "react-google-recaptcha"; import { useTranslation } from "react-i18next"; import { isValidPhoneNumber } from "react-phone-number-input"; @@ -31,6 +31,8 @@ import BrowserWarning from "@/components/ErrorPages/BrowserWarning"; import { useAuthContext } from "@/hooks/useAuthUser"; +import { LocalStorageKeys } from "@/common/constants"; + import FiltersCache from "@/Utils/FiltersCache"; import ViewCache from "@/Utils/ViewCache"; import routes from "@/Utils/request/api"; @@ -97,6 +99,11 @@ const Login = (props: LoginProps) => { const [otpError, setOtpError] = useState(""); const [otpValidationError, setOtpValidationError] = useState(""); + // Remember the last login mode + useEffect(() => { + localStorage.setItem(LocalStorageKeys.loginPreference, loginMode); + }, [loginMode]); + // Staff Login Mutation const staffLoginMutation = useMutation({ mutationFn: async (data: LoginFormData) => { diff --git a/src/components/Common/LoginHeader.tsx b/src/components/Common/LoginHeader.tsx index 3daa043bd83..32ebc669799 100644 --- a/src/components/Common/LoginHeader.tsx +++ b/src/components/Common/LoginHeader.tsx @@ -74,7 +74,14 @@ export const LoginHeader = () => { diff --git a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx index a9cb31b9307..f5e5e02dc9a 100644 --- a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx +++ b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx @@ -23,6 +23,7 @@ import { Encounter } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatementRequest } from "@/types/emr/medicationStatement"; import { SymptomRequest } from "@/types/emr/symptom/symptom"; +import { LocationAssociationQuestion } from "@/types/location/association"; import { Question } from "@/types/questionnaire/question"; import { QuestionnaireResponse } from "@/types/questionnaire/questionnaireResponse"; import { CreateAppointmentQuestion } from "@/types/scheduling/schedule"; @@ -32,6 +33,7 @@ interface Props { patientId: string; } +// TODO: Ensure that this type is not defined elsewhere. type ResponseValueType = { value?: | string @@ -44,7 +46,8 @@ type ResponseValueType = { | MedicationStatementRequest[] | SymptomRequest[] | DiagnosisRequest[] - | CreateAppointmentQuestion; + | CreateAppointmentQuestion[] + | LocationAssociationQuestion[]; value_quantity?: { value: number; }; diff --git a/src/components/Location/LocationSearch.tsx b/src/components/Location/LocationSearch.tsx new file mode 100644 index 00000000000..58913c14613 --- /dev/null +++ b/src/components/Location/LocationSearch.tsx @@ -0,0 +1,84 @@ +import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; + +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; + +import query from "@/Utils/request/query"; +import { LocationList } from "@/types/location/location"; +import locationApi from "@/types/location/locationApi"; + +interface LocationSearchProps { + facilityId: string; + mode?: "kind" | "location"; + onSelect: (location: LocationList) => void; + disabled?: boolean; + value?: LocationList | null; +} + +export function LocationSearch({ + facilityId, + mode, + onSelect, + disabled, + value, +}: LocationSearchProps) { + const [open, setOpen] = useState(false); + const [search, setSearch] = useState(""); + + const { data: locations } = useQuery({ + queryKey: ["locations", facilityId, mode, search], + queryFn: query(locationApi.list, { + pathParams: { facility_id: facilityId }, + queryParams: { mode, search }, + }), + }); + + return ( + + +
+ {value?.name || "Select location..."} +
+
+ + + + No locations found. + + {locations?.results.map((location) => ( + { + onSelect(location); + setOpen(false); + }} + > + {location.name} + + ))} + + + +
+ ); +} diff --git a/src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx b/src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx new file mode 100644 index 00000000000..ed60dbe4b7e --- /dev/null +++ b/src/components/Questionnaire/QuestionTypes/LocationQuestion.tsx @@ -0,0 +1,116 @@ +import { format } from "date-fns"; +import React, { useState } from "react"; + +import { Input } from "@/components/ui/input"; + +import { LocationSearch } from "@/components/Location/LocationSearch"; + +import { LocationAssociationQuestion } from "@/types/location/association"; +import { LocationList } from "@/types/location/location"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; +import { Question } from "@/types/questionnaire/question"; + +interface LocationQuestionProps { + question: Question; + questionnaireResponse: QuestionnaireResponse; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; + disabled?: boolean; + facilityId: string; + locationId: string; + encounterId: string; +} + +export function LocationQuestion({ + questionnaireResponse, + updateQuestionnaireResponseCB, + disabled, + facilityId, + encounterId, +}: LocationQuestionProps) { + const [selectedLocation, setSelectedLocation] = useState( + null, + ); + + const values = + (questionnaireResponse.values?.[0] + ?.value as unknown as LocationAssociationQuestion[]) || []; + + const association = values[0] ?? {}; + + const handleUpdateAssociation = ( + updates: Partial, + ) => { + const newAssociation: LocationAssociationQuestion = { + id: association?.id || null, + encounter: encounterId, + start_datetime: association?.start_datetime || new Date().toISOString(), + end_datetime: null, + status: "active", + location: association?.location || "", + meta: {}, + created_by: null, + updated_by: null, + ...updates, + }; + + updateQuestionnaireResponseCB( + [{ type: "location_association", value: [newAssociation] }], + questionnaireResponse.question_id, + ); + }; + + const handleLocationSelect = (location: LocationList) => { + setSelectedLocation(location); + handleUpdateAssociation({ location: location.id }); + }; + + return ( +
+
+
+ + +
+ + {selectedLocation && ( +
+ + + handleUpdateAssociation({ + start_datetime: new Date(e.target.value).toISOString(), + }) + } + disabled={disabled} + className="h-9" + /> +
+ )} +
+
+ ); +} diff --git a/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx b/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx index deec52da280..e82b50b09d4 100644 --- a/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx +++ b/src/components/Questionnaire/QuestionTypes/QuestionInput.tsx @@ -20,6 +20,7 @@ import { ChoiceQuestion } from "./ChoiceQuestion"; import { DateTimeQuestion } from "./DateTimeQuestion"; import { DiagnosisQuestion } from "./DiagnosisQuestion"; import { EncounterQuestion } from "./EncounterQuestion"; +import { LocationQuestion } from "./LocationQuestion"; import { MedicationRequestQuestion } from "./MedicationRequestQuestion"; import { MedicationStatementQuestion } from "./MedicationStatementQuestion"; import { NotesInput } from "./NotesInput"; @@ -167,6 +168,18 @@ export function QuestionInput({ ); } return null; + case "location_association": + if (encounterId) { + return ( + + ); + } + return null; } return null; diff --git a/src/components/Questionnaire/QuestionnaireEditor.tsx b/src/components/Questionnaire/QuestionnaireEditor.tsx index 6182d537a83..66156a235e4 100644 --- a/src/components/Questionnaire/QuestionnaireEditor.tsx +++ b/src/components/Questionnaire/QuestionnaireEditor.tsx @@ -63,6 +63,17 @@ interface QuestionnaireEditorProps { id?: string; } +const STRUCTURED_QUESTION_TYPES = [ + { value: "allergy_intolerance", label: "Allergy Intolerance" }, + { value: "medication_request", label: "Medication Request" }, + { value: "medication_statement", label: "Medication Statement" }, + { value: "symptom", label: "Symptom" }, + { value: "diagnosis", label: "Diagnosis" }, + { value: "encounter", label: "Encounter" }, + { value: "appointment", label: "Appointment" }, + { value: "location_association", label: "Location Association" }, +] as const; + export default function QuestionnaireEditor({ id }: QuestionnaireEditorProps) { const navigate = useNavigate(); const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit"); @@ -832,19 +843,11 @@ function QuestionEditor({ - - Allergy Intolerance - - - Medication Request - - - Medication Statement - - Symptom - Diagnosis - Encounter - Appointment + {STRUCTURED_QUESTION_TYPES.map((type) => ( + + {type.label} + + ))} diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index a7da1010ba2..450628ee661 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -262,13 +262,18 @@ export function QuestionnaireForm({ // Continue with existing submission logic... const requests: BatchRequest[] = []; if (encounterId && patientId) { - const context = { patientId, encounterId }; + const context = { facilityId, patientId, encounterId }; // First, collect all structured data requests if encounterId is provided formsWithValidation.forEach((form) => { form.responses.forEach((response) => { if (response.structured_type) { + console.log( + "Processing structured response", + response.structured_type, + ); const structuredData = response.values?.[0]?.value; if (Array.isArray(structuredData) && structuredData.length > 0) { + console.log("Structured data found", structuredData); const structuredRequests = getStructuredRequests( response.structured_type, structuredData, diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts index 86fd15d4e96..4066d00479a 100644 --- a/src/components/Questionnaire/structured/handlers.ts +++ b/src/components/Questionnaire/structured/handlers.ts @@ -3,6 +3,8 @@ import { RequestTypeFor, } from "@/components/Questionnaire/structured/types"; +import { LocationAssociationQuestion } from "@/types/location/association"; +import locationApi from "@/types/location/locationApi"; import { StructuredQuestionType } from "@/types/questionnaire/question"; interface StructuredHandlerContext { @@ -23,7 +25,7 @@ type StructuredHandler = { }>; }; -const handlers: { +export const structuredHandlers: { [K in StructuredQuestionType]: StructuredHandler; } = { allergy_intolerance: { @@ -154,10 +156,47 @@ const handlers: { ]; }, }, + location_association: { + getRequests: ( + locationAssociations: LocationAssociationQuestion[], + { facilityId, encounterId }, + ) => { + if (!locationAssociations.length) { + console.log("No location associations found"); + return []; + } + + if (!facilityId) { + console.log("No facility ID found"); + return []; + } + + return locationAssociations.map((locationAssociation) => { + console.log( + "Creating location association request", + locationAssociation, + ); + return { + url: locationApi.createAssociation.path + .replace("{facility_external_id}", facilityId) + .replace("{location_external_id}", locationAssociation.location), + method: locationApi.createAssociation.method, + body: { + encounter: encounterId, + start_datetime: locationAssociation.start_datetime, + end_datetime: locationAssociation.end_datetime, + status: locationAssociation.status, + meta: locationAssociation.meta, + }, + reference_id: `location_association_${locationAssociation}`, + }; + }); + }, + }, }; export const getStructuredRequests = ( type: T, data: DataTypeFor[], context: StructuredHandlerContext, -) => handlers[type].getRequests(data, context); +) => structuredHandlers[type].getRequests(data, context); diff --git a/src/components/Questionnaire/structured/types.ts b/src/components/Questionnaire/structured/types.ts index 457afc1af45..e2a581b5c1f 100644 --- a/src/components/Questionnaire/structured/types.ts +++ b/src/components/Questionnaire/structured/types.ts @@ -4,6 +4,10 @@ import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatementRequest } from "@/types/emr/medicationStatement"; import { SymptomRequest } from "@/types/emr/symptom/symptom"; +import { + LocationAssociationQuestion, + LocationAssociationWrite, +} from "@/types/location/association"; import { StructuredQuestionType } from "@/types/questionnaire/question"; import { AppointmentCreateRequest, @@ -19,6 +23,7 @@ export interface StructuredDataMap { medication_statement: MedicationStatementRequest; encounter: Encounter; appointment: CreateAppointmentQuestion; + location_association: LocationAssociationQuestion; } // Map structured types to their request types @@ -30,6 +35,7 @@ export interface StructuredRequestMap { medication_statement: { datapoints: MedicationStatementRequest[] }; encounter: EncounterEditRequest; appointment: AppointmentCreateRequest; + location_association: LocationAssociationWrite; } export type RequestTypeFor = diff --git a/src/types/location/association.ts b/src/types/location/association.ts new file mode 100644 index 00000000000..4397a665a25 --- /dev/null +++ b/src/types/location/association.ts @@ -0,0 +1,22 @@ +export interface LocationAssociation { + meta: Record; + id: string | null; + encounter: string; + start_datetime: string; + end_datetime: string | null; + status: string; + created_by: string | null; + updated_by: string | null; +} + +export interface LocationAssociationQuestion extends LocationAssociation { + location: string; +} + +export interface LocationAssociationWrite { + encounter: string; + start_datetime: string; + end_datetime?: string | null; + status: string; + meta?: Record; +} diff --git a/src/types/location/locationApi.ts b/src/types/location/locationApi.ts index db826371cdb..d58b6392bf6 100644 --- a/src/types/location/locationApi.ts +++ b/src/types/location/locationApi.ts @@ -2,6 +2,7 @@ import { HttpMethod, Type } from "@/Utils/request/api"; import { PaginatedResponse } from "@/Utils/request/types"; import { FacilityOrganization } from "@/types/facilityOrganization/facilityOrganization"; +import { LocationAssociation, LocationAssociationWrite } from "./association"; import { LocationDetail, LocationList, LocationWrite } from "./location"; export default { @@ -44,4 +45,31 @@ export default { TRes: Type(), TBody: Type<{ organization: string }>(), }, + listAssociations: { + path: "/api/v1/facility/{facility_external_id}/location/{location_external_id}/association/", + method: HttpMethod.GET, + TRes: Type>(), + }, + createAssociation: { + path: "/api/v1/facility/{facility_external_id}/location/{location_external_id}/association/", + method: HttpMethod.POST, + TRes: Type(), + TBody: Type(), + }, + getAssociation: { + path: "/api/v1/facility/{facility_external_id}/location/{location_external_id}/association/{external_id}/", + method: HttpMethod.GET, + TRes: Type(), + }, + updateAssociation: { + path: "/api/v1/facility/{facility_external_id}/location/{location_external_id}/association/{external_id}/", + method: HttpMethod.PUT, + TRes: Type(), + TBody: Type(), + }, + deleteAssociation: { + path: "/api/v1/facility/{facility_external_id}/location/{location_external_id}/association/{external_id}/", + method: HttpMethod.DELETE, + TRes: Type(), + }, }; diff --git a/src/types/questionnaire/form.ts b/src/types/questionnaire/form.ts index e634aa749fc..6f804d3d8e9 100644 --- a/src/types/questionnaire/form.ts +++ b/src/types/questionnaire/form.ts @@ -4,6 +4,7 @@ import { Encounter } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatementRequest } from "@/types/emr/medicationStatement"; import { SymptomRequest } from "@/types/emr/symptom/symptom"; +import { LocationAssociationQuestion } from "@/types/location/association"; import { Code } from "@/types/questionnaire/code"; import { Quantity } from "@/types/questionnaire/quantity"; import { StructuredQuestionType } from "@/types/questionnaire/question"; @@ -21,7 +22,8 @@ export type ResponseValue = { | "symptom" | "diagnosis" | "encounter" - | "appointment"; + | "appointment" + | "location_association"; value?: | string @@ -34,7 +36,8 @@ export type ResponseValue = { | SymptomRequest[] | DiagnosisRequest[] | Encounter - | CreateAppointmentQuestion; + | CreateAppointmentQuestion[] + | LocationAssociationQuestion[]; value_code?: Code; value_quantity?: Quantity; }; diff --git a/src/types/questionnaire/question.ts b/src/types/questionnaire/question.ts index 1297033349f..cc16b086bc7 100644 --- a/src/types/questionnaire/question.ts +++ b/src/types/questionnaire/question.ts @@ -23,7 +23,8 @@ export type StructuredQuestionType = | "symptom" | "diagnosis" | "encounter" - | "appointment"; + | "appointment" + | "location_association"; type EnableWhenNumeric = { operator: "greater" | "less" | "greater_or_equals" | "less_or_equals"; From bc23521432ec719134f8a7b7224845fa896d304a Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 12:39:23 +0530 Subject: [PATCH 03/10] Remove Debugging logs --- src/components/Questionnaire/QuestionnaireForm.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index 450628ee661..d9596b07be5 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -267,13 +267,8 @@ export function QuestionnaireForm({ formsWithValidation.forEach((form) => { form.responses.forEach((response) => { if (response.structured_type) { - console.log( - "Processing structured response", - response.structured_type, - ); const structuredData = response.values?.[0]?.value; if (Array.isArray(structuredData) && structuredData.length > 0) { - console.log("Structured data found", structuredData); const structuredRequests = getStructuredRequests( response.structured_type, structuredData, From e96f204d42070203bfd305f82a62768bcffbd76b Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Thu, 6 Feb 2025 13:32:34 +0530 Subject: [PATCH 04/10] Removed duplicate ResponseValue type --- .../QuestionnaireResponsesList.tsx | 34 +++---------------- 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx index f5e5e02dc9a..0284403e013 100644 --- a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx +++ b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx @@ -17,53 +17,27 @@ import { RESULTS_PER_PAGE_LIMIT } from "@/common/constants"; import routes from "@/Utils/request/api"; import query from "@/Utils/request/query"; import { formatDateTime, properCase } from "@/Utils/utils"; -import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; -import { DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; import { Encounter } from "@/types/emr/encounter"; -import { MedicationRequest } from "@/types/emr/medicationRequest"; -import { MedicationStatementRequest } from "@/types/emr/medicationStatement"; -import { SymptomRequest } from "@/types/emr/symptom/symptom"; -import { LocationAssociationQuestion } from "@/types/location/association"; +import { ResponseValue } from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; import { QuestionnaireResponse } from "@/types/questionnaire/questionnaireResponse"; -import { CreateAppointmentQuestion } from "@/types/scheduling/schedule"; interface Props { encounter?: Encounter; patientId: string; } -// TODO: Ensure that this type is not defined elsewhere. -type ResponseValueType = { - value?: - | string - | number - | boolean - | Date - | Encounter - | AllergyIntoleranceRequest[] - | MedicationRequest[] - | MedicationStatementRequest[] - | SymptomRequest[] - | DiagnosisRequest[] - | CreateAppointmentQuestion[] - | LocationAssociationQuestion[]; - value_quantity?: { - value: number; - }; -}; - interface QuestionResponseProps { question: Question; response?: { - values: ResponseValueType[]; + values: ResponseValue[]; note?: string; question_id: string; }; } export function formatValue( - value: ResponseValueType["value"], + value: ResponseValue["value"], type: string, ): string { if (!value) return ""; @@ -123,7 +97,7 @@ function QuestionGroup({ }: { group: Question; responses: { - values: ResponseValueType[]; + values: ResponseValue[]; note?: string; question_id: string; }[]; From 31dfe8bddf3d64e386fb046646243e27c2d1b07f Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 13:58:23 +0530 Subject: [PATCH 05/10] Use as ID for questionnaire preview --- src/components/Location/LocationSearch.tsx | 1 + src/components/Questionnaire/show.tsx | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/Location/LocationSearch.tsx b/src/components/Location/LocationSearch.tsx index 58913c14613..53f7b8e6c38 100644 --- a/src/components/Location/LocationSearch.tsx +++ b/src/components/Location/LocationSearch.tsx @@ -42,6 +42,7 @@ export function LocationSearch({ pathParams: { facility_id: facilityId }, queryParams: { mode, search }, }), + enabled: facilityId !== "preview", }); return ( diff --git a/src/components/Questionnaire/show.tsx b/src/components/Questionnaire/show.tsx index 8df7b174cb5..5977b61eae7 100644 --- a/src/components/Questionnaire/show.tsx +++ b/src/components/Questionnaire/show.tsx @@ -304,10 +304,10 @@ export function QuestionnaireShow({ id }: QuestionnaireShowProps) { From 9f08025354714a32e68abd59e157e878c2a7b801 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 14:52:58 +0530 Subject: [PATCH 06/10] Add Location Edit Form; Show Location in PatientInfo --- src/components/Patient/PatientInfoCard.tsx | 37 +++++++++++++++++++ .../Questionnaire/data/StructuredFormData.tsx | 21 +++++++++++ src/types/emr/encounter.ts | 2 + 3 files changed, 60 insertions(+) diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index f73d9fbab3f..92b11804f5b 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -201,6 +201,43 @@ export default function PatientInfoCard(props: PatientInfoCardProps) { + {props.encounter.current_location && ( + + +
+ + + {props.encounter.current_location.name} + + +
+
+ +
+

+ Current Location +

+

+ {props.encounter.current_location.name} +

+

+ {props.encounter.current_location.description} +

+
+ +
+
+ )}
diff --git a/src/components/Questionnaire/data/StructuredFormData.tsx b/src/components/Questionnaire/data/StructuredFormData.tsx index 918d2a1561f..78d10fcb982 100644 --- a/src/components/Questionnaire/data/StructuredFormData.tsx +++ b/src/components/Questionnaire/data/StructuredFormData.tsx @@ -120,6 +120,26 @@ const symptom_questionnaire: QuestionnaireDetail = { tags: [], }; +const location_association_questionnaire: QuestionnaireDetail = { + id: "location_association", + slug: "location_association", + version: "0.0.1", + title: "Location Association", + status: "active", + subject_type: "patient", + questions: [ + { + id: "location_association", + text: "Location Association", + type: "structured", + structured_type: "location_association", + link_id: "1.1", + required: true, + }, + ], + tags: [], +}; + export const FIXED_QUESTIONNAIRES: Record = { encounter: encounterQuestionnaire, medication_request: medication_request_questionnaire, @@ -127,4 +147,5 @@ export const FIXED_QUESTIONNAIRES: Record = { medication_statement: medication_statement_questionnaire, diagnosis: diagnosis_questionnaire, symptom: symptom_questionnaire, + location_association: location_association_questionnaire, }; diff --git a/src/types/emr/encounter.ts b/src/types/emr/encounter.ts index fdcffa02149..f63a118a83f 100644 --- a/src/types/emr/encounter.ts +++ b/src/types/emr/encounter.ts @@ -1,5 +1,6 @@ import { Patient } from "@/types/emr/newPatient"; import { FacilityOrganization } from "@/types/facilityOrganization/facilityOrganization"; +import { LocationList } from "@/types/location/location"; import { UserBase } from "@/types/user/user"; export const ENCOUNTER_ADMIT_SOURCE = [ @@ -136,6 +137,7 @@ export interface Encounter { encounter_class_history: EncounterClassHistory; status_history: StatusHistory; organizations: FacilityOrganization[]; + current_location: LocationList; } export interface EncounterEditRequest { From fe386ac3502c936ebafb18b0482fcb4c38ad3861 Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 15:24:31 +0530 Subject: [PATCH 07/10] Cleanup Questionnaire Handlers --- .../QuestionTypes/AppointmentQuestion.tsx | 2 +- .../QuestionTypes/DateTimeQuestion.tsx | 4 +- .../QuestionTypes/EncounterQuestion.tsx | 2 +- .../Questionnaire/structured/handlers.ts | 13 ++- .../Questionnaire/structured/types.ts | 4 +- src/types/questionnaire/form.ts | 105 +++++++++++++----- 6 files changed, 90 insertions(+), 40 deletions(-) diff --git a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx index ee9174f00e7..dec289eedf4 100644 --- a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx @@ -62,7 +62,7 @@ export function AppointmentQuestion({ [ { type: "appointment", - value: [appointment] as unknown as ResponseValue["value"], + value: [appointment], }, ], questionnaireResponse.question_id, diff --git a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx index 8eb00eb8f33..4432477b82b 100644 --- a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx @@ -55,7 +55,7 @@ export function DateTimeQuestion({ [ { type: "dateTime", - value: date.toISOString(), + value: date, }, ], questionnaireResponse.question_id, @@ -75,7 +75,7 @@ export function DateTimeQuestion({ [ { type: "dateTime", - value: date.toISOString(), + value: date, }, ], questionnaireResponse.question_id, diff --git a/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx b/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx index 315221b64de..1383cf8f398 100644 --- a/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx @@ -125,7 +125,7 @@ export function EncounterQuestion({ // Create the response value with the encounter request const responseValue: ResponseValue = { type: "encounter", - value: [encounterRequest] as unknown as typeof responseValue.value, + value: [encounterRequest], }; updateQuestionnaireResponseCB( diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts index 4066d00479a..3bd8d41a7de 100644 --- a/src/components/Questionnaire/structured/handlers.ts +++ b/src/components/Questionnaire/structured/handlers.ts @@ -116,8 +116,11 @@ export const structuredHandlers: { }, }, encounter: { - getRequests: (encounters, { patientId, encounterId }) => { + getRequests: (encounters, { facilityId, patientId, encounterId }) => { if (!encounterId) return []; + if (!facilityId) { + throw new Error("Cannot create encounter without a facility"); + } return encounters.map((encounter) => { const body: RequestTypeFor<"encounter"> = { organizations: [], @@ -128,7 +131,7 @@ export const structuredHandlers: { hospitalization: encounter.hospitalization, priority: encounter.priority, external_identifier: encounter.external_identifier, - facility: encounter.facility.id, + facility: facilityId, }; return { @@ -162,13 +165,13 @@ export const structuredHandlers: { { facilityId, encounterId }, ) => { if (!locationAssociations.length) { - console.log("No location associations found"); return []; } if (!facilityId) { - console.log("No facility ID found"); - return []; + throw new Error( + "Cannot create location association without a facility", + ); } return locationAssociations.map((locationAssociation) => { diff --git a/src/components/Questionnaire/structured/types.ts b/src/components/Questionnaire/structured/types.ts index e2a581b5c1f..03bc5f87a5b 100644 --- a/src/components/Questionnaire/structured/types.ts +++ b/src/components/Questionnaire/structured/types.ts @@ -1,6 +1,6 @@ import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import { DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; -import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; +import { EncounterEditRequest } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatementRequest } from "@/types/emr/medicationStatement"; import { SymptomRequest } from "@/types/emr/symptom/symptom"; @@ -21,7 +21,7 @@ export interface StructuredDataMap { symptom: SymptomRequest; diagnosis: DiagnosisRequest; medication_statement: MedicationStatementRequest; - encounter: Encounter; + encounter: EncounterEditRequest; appointment: CreateAppointmentQuestion; location_association: LocationAssociationQuestion; } diff --git a/src/types/questionnaire/form.ts b/src/types/questionnaire/form.ts index 6f804d3d8e9..82b1f2c286f 100644 --- a/src/types/questionnaire/form.ts +++ b/src/types/questionnaire/form.ts @@ -1,6 +1,6 @@ import { AllergyIntoleranceRequest } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import { DiagnosisRequest } from "@/types/emr/diagnosis/diagnosis"; -import { Encounter } from "@/types/emr/encounter"; +import { EncounterEditRequest } from "@/types/emr/encounter"; import { MedicationRequest } from "@/types/emr/medicationRequest"; import { MedicationStatementRequest } from "@/types/emr/medicationStatement"; import { SymptomRequest } from "@/types/emr/symptom/symptom"; @@ -10,38 +10,85 @@ import { Quantity } from "@/types/questionnaire/quantity"; import { StructuredQuestionType } from "@/types/questionnaire/question"; import { CreateAppointmentQuestion } from "@/types/scheduling/schedule"; -export type ResponseValue = { - type: - | "string" - | "number" - | "boolean" - | "dateTime" - | "allergy_intolerance" - | "medication_request" - | "medication_statement" - | "symptom" - | "diagnosis" - | "encounter" - | "appointment" - | "location_association"; - - value?: - | string - | number - | boolean - | Date - | AllergyIntoleranceRequest[] - | MedicationRequest[] - | MedicationStatementRequest[] - | SymptomRequest[] - | DiagnosisRequest[] - | Encounter - | CreateAppointmentQuestion[] - | LocationAssociationQuestion[]; +type ResponseValueBase = { value_code?: Code; value_quantity?: Quantity; }; +export type StringResponseValue = ResponseValueBase & { + type: "string"; + value: string | undefined; +}; + +export type NumberResponseValue = ResponseValueBase & { + type: "number"; + value: number | undefined; +}; + +export type BooleanResponseValue = ResponseValueBase & { + type: "boolean"; + value: boolean | undefined; +}; + +export type DateTimeResponseValue = ResponseValueBase & { + type: "dateTime"; + value: Date | undefined; +}; + +export type AllergyIntoleranceResponseValue = ResponseValueBase & { + type: "allergy_intolerance"; + value: AllergyIntoleranceRequest[]; +}; + +export type MedicationRequestResponseValue = ResponseValueBase & { + type: "medication_request"; + value: MedicationRequest[]; +}; + +export type MedicationStatementResponseValue = ResponseValueBase & { + type: "medication_statement"; + value: MedicationStatementRequest[]; +}; + +export type LocationAssociationResponseValue = ResponseValueBase & { + type: "location_association"; + value: LocationAssociationQuestion[]; +}; + +export type SymptomResponseValue = ResponseValueBase & { + type: "symptom"; + value: SymptomRequest[]; +}; + +export type DiagnosisResponseValue = ResponseValueBase & { + type: "diagnosis"; + value: DiagnosisRequest[]; +}; + +export type EncounterResponseValue = ResponseValueBase & { + type: "encounter"; + value: EncounterEditRequest[]; +}; + +export type CreateAppointmentResponseValue = ResponseValueBase & { + type: "appointment"; + value: CreateAppointmentQuestion[]; +}; + +export type ResponseValue = + | StringResponseValue + | NumberResponseValue + | BooleanResponseValue + | DateTimeResponseValue + | AllergyIntoleranceResponseValue + | MedicationRequestResponseValue + | MedicationStatementResponseValue + | LocationAssociationResponseValue + | SymptomResponseValue + | DiagnosisResponseValue + | EncounterResponseValue + | CreateAppointmentResponseValue; + export interface QuestionnaireResponse { question_id: string; structured_type: StructuredQuestionType | null; From cb75dd93aca39fbb21a310e4b4e3708ba3152b03 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Feb 2025 15:51:12 +0530 Subject: [PATCH 08/10] Refactor ResponseValue Type with Generic RV Shorthand --- src/types/questionnaire/form.ts | 91 +++++++-------------------------- 1 file changed, 18 insertions(+), 73 deletions(-) diff --git a/src/types/questionnaire/form.ts b/src/types/questionnaire/form.ts index 82b1f2c286f..07cec713f51 100644 --- a/src/types/questionnaire/form.ts +++ b/src/types/questionnaire/form.ts @@ -10,84 +10,29 @@ import { Quantity } from "@/types/questionnaire/quantity"; import { StructuredQuestionType } from "@/types/questionnaire/question"; import { CreateAppointmentQuestion } from "@/types/scheduling/schedule"; -type ResponseValueBase = { +/** + * A short hand for defining response value types + */ +type RV = { value_code?: Code; value_quantity?: Quantity; -}; - -export type StringResponseValue = ResponseValueBase & { - type: "string"; - value: string | undefined; -}; - -export type NumberResponseValue = ResponseValueBase & { - type: "number"; - value: number | undefined; -}; - -export type BooleanResponseValue = ResponseValueBase & { - type: "boolean"; - value: boolean | undefined; -}; - -export type DateTimeResponseValue = ResponseValueBase & { - type: "dateTime"; - value: Date | undefined; -}; - -export type AllergyIntoleranceResponseValue = ResponseValueBase & { - type: "allergy_intolerance"; - value: AllergyIntoleranceRequest[]; -}; - -export type MedicationRequestResponseValue = ResponseValueBase & { - type: "medication_request"; - value: MedicationRequest[]; -}; - -export type MedicationStatementResponseValue = ResponseValueBase & { - type: "medication_statement"; - value: MedicationStatementRequest[]; -}; - -export type LocationAssociationResponseValue = ResponseValueBase & { - type: "location_association"; - value: LocationAssociationQuestion[]; -}; - -export type SymptomResponseValue = ResponseValueBase & { - type: "symptom"; - value: SymptomRequest[]; -}; - -export type DiagnosisResponseValue = ResponseValueBase & { - type: "diagnosis"; - value: DiagnosisRequest[]; -}; - -export type EncounterResponseValue = ResponseValueBase & { - type: "encounter"; - value: EncounterEditRequest[]; -}; - -export type CreateAppointmentResponseValue = ResponseValueBase & { - type: "appointment"; - value: CreateAppointmentQuestion[]; + type: T; + value: V; }; export type ResponseValue = - | StringResponseValue - | NumberResponseValue - | BooleanResponseValue - | DateTimeResponseValue - | AllergyIntoleranceResponseValue - | MedicationRequestResponseValue - | MedicationStatementResponseValue - | LocationAssociationResponseValue - | SymptomResponseValue - | DiagnosisResponseValue - | EncounterResponseValue - | CreateAppointmentResponseValue; + | RV<"string", string | undefined> + | RV<"number", number | undefined> + | RV<"boolean", boolean | undefined> + | RV<"dateTime", Date | undefined> + | RV<"allergy_intolerance", AllergyIntoleranceRequest[]> + | RV<"medication_request", MedicationRequest[]> + | RV<"medication_statement", MedicationStatementRequest[]> + | RV<"location_association", LocationAssociationQuestion[]> + | RV<"symptom", SymptomRequest[]> + | RV<"diagnosis", DiagnosisRequest[]> + | RV<"encounter", EncounterEditRequest[]> + | RV<"appointment", CreateAppointmentQuestion[]>; export interface QuestionnaireResponse { question_id: string; From e3ae50bbf8eb2a73c3074f3500d69b05bc619687 Mon Sep 17 00:00:00 2001 From: rithviknishad Date: Thu, 6 Feb 2025 15:51:51 +0530 Subject: [PATCH 09/10] remove invalid classes and console --- src/components/Location/LocationSearch.tsx | 2 +- src/components/Questionnaire/structured/handlers.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/Location/LocationSearch.tsx b/src/components/Location/LocationSearch.tsx index 53f7b8e6c38..9525e18294d 100644 --- a/src/components/Location/LocationSearch.tsx +++ b/src/components/Location/LocationSearch.tsx @@ -49,7 +49,7 @@ export function LocationSearch({
diff --git a/src/components/Questionnaire/structured/handlers.ts b/src/components/Questionnaire/structured/handlers.ts index 3bd8d41a7de..47b020347f9 100644 --- a/src/components/Questionnaire/structured/handlers.ts +++ b/src/components/Questionnaire/structured/handlers.ts @@ -175,10 +175,6 @@ export const structuredHandlers: { } return locationAssociations.map((locationAssociation) => { - console.log( - "Creating location association request", - locationAssociation, - ); return { url: locationApi.createAssociation.path .replace("{facility_external_id}", facilityId) From d3a677ff162ebd3e729a0d73597f23a0663372ea Mon Sep 17 00:00:00 2001 From: Gigin George Date: Thu, 6 Feb 2025 17:34:08 +0530 Subject: [PATCH 10/10] Transform Date Time to ISO String --- .../Questionnaire/QuestionnaireForm.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/components/Questionnaire/QuestionnaireForm.tsx b/src/components/Questionnaire/QuestionnaireForm.tsx index d9596b07be5..6c8716058de 100644 --- a/src/components/Questionnaire/QuestionnaireForm.tsx +++ b/src/components/Questionnaire/QuestionnaireForm.tsx @@ -303,11 +303,18 @@ export function QuestionnaireForm({ ) .map((response) => ({ question_id: response.question_id, - values: response.values.map((value) => ({ - ...(value.value_code - ? { value_code: value.value_code } - : { value: String(value.value) }), - })), + values: response.values.map((value) => { + if (value.type === "dateTime" && value.value) { + return { + ...value, + value: value.value.toISOString(), + }; + } + if (value.value_code) { + return { value_code: value.value_code }; + } + return { value: String(value.value) }; + }), note: response.note, body_site: response.body_site, method: response.method,