diff --git a/.example.env b/.example.env index 862f05eb5c4..79e826ad65f 100644 --- a/.example.env +++ b/.example.env @@ -62,3 +62,6 @@ REACT_ALLOWED_LOCALES="en,hi,ta,ml,mr,kn" # ISO 3166-1 Alpha-2 code for the default country code (default: "IN") REACT_DEFAULT_COUNTRY= + +# Maps fallback URL template (default:"https://www.openstreetmap.org/?mlat={lat}&mlon={long}&zoom=15") +REACT_MAPS_FALLBACK_URL_TEMPLATE= \ No newline at end of file diff --git a/care.config.ts b/care.config.ts index d99d6fe73cd..9f646dbab0a 100644 --- a/care.config.ts +++ b/care.config.ts @@ -55,6 +55,10 @@ const careConfig = { defaultEncounterType: (env.REACT_DEFAULT_ENCOUNTER_TYPE || "hh") as EncounterClass, + mapFallbackUrlTemplate: + env.REACT_MAPS_FALLBACK_URL_TEMPLATE || + "https://www.openstreetmap.org/?mlat={lat}&mlon={long}&zoom=15", + gmapsApiKey: env.REACT_GMAPS_API_KEY || "AIzaSyDsBAc3y7deI5ZO3NtK5GuzKwtUzQNJNUk", diff --git a/public/locale/en.json b/public/locale/en.json index 086561744a7..e4f1cb71f5c 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1510,6 +1510,7 @@ "next_sessions": "Next Sessions", "next_week_short": "Next wk", "no": "No", + "no_active_medication_recorded": "No Active Medication Recorded", "no_address_provided": "No address provided", "no_allergies_recorded": "No allergies recorded", "no_appointments": "No appointments found", @@ -1551,6 +1552,7 @@ "no_log_update_delta": "No changes since previous log update", "no_log_updates": "No log updates found", "no_medical_history_available": "No Medical History Available", + "no_medication_recorded": "No Medication Recorded", "no_medications": "No Medications", "no_medications_found_for_this_encounter": "No medications found for this encounter.", "no_medications_to_administer": "No medications to administer", @@ -2132,6 +2134,7 @@ "select_policy_to_add_items": "Select a Policy to Add Items", "select_practitioner": "Select Practitioner", "select_previous": "Select Previous Fields", + "show_on_map": "Show on Map", "select_priority": "Select Priority", "select_prn_reason": "Select reason for PRN", "select_register_patient": "Select/Register Patient", diff --git a/src/App.tsx b/src/App.tsx index 51e33c2209d..7452d7afb23 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,13 +18,23 @@ import AuthUserProvider from "@/Providers/AuthUserProvider"; import HistoryAPIProvider from "@/Providers/HistoryAPIProvider"; import Routers from "@/Routers"; import { handleHttpError } from "@/Utils/request/errorHandler"; +import { HTTPError } from "@/Utils/request/types"; import { PubSubProvider } from "./Utils/pubsubContext"; const queryClient = new QueryClient({ defaultOptions: { queries: { - retry: 2, + retry: (failureCount, error) => { + // Only retry network errors or server errors (502, 503, 504) up to 3 times + if ( + error.message === "Network Error" || + (error instanceof HTTPError && [502, 503, 504].includes(error.status)) + ) { + return failureCount < 3; + } + return false; + }, refetchOnWindowFocus: false, }, }, diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx index 119ac148aa9..080b90a0a54 100644 --- a/src/Routers/routes/ConsultationRoutes.tsx +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -16,8 +16,12 @@ const consultationRoutes: AppRoutes = { /> ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/treatment_summary": - ({ facilityId, encounterId }) => ( - + ({ facilityId, encounterId, patientId }) => ( + ), "/facility/:facilityId/patient/:patientId/encounter/:encounterId/questionnaire": ({ facilityId, encounterId, patientId }) => ( diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 3198b10bcd3..c17e08e006f 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -1,3 +1,4 @@ +import careConfig from "@careConfig"; import { differenceInMinutes, format } from "date-fns"; import { toPng } from "html-to-image"; @@ -94,6 +95,16 @@ export const isUserOnline = (user: { last_login: DateLike }) => { : false; }; +export const isAndroidDevice = /android/i.test(navigator.userAgent); + +export const getMapUrl = (latitude: string, longitude: string) => { + return isAndroidDevice + ? `geo:${latitude},${longitude}` + : careConfig.mapFallbackUrlTemplate + .replace("{lat}", latitude) + .replace("{long}", longitude); +}; + const getRelativeDateSuffix = (abbreviated: boolean) => { return { day: abbreviated ? "d" : "days", diff --git a/src/components/Common/PrintTable.tsx b/src/components/Common/PrintTable.tsx new file mode 100644 index 00000000000..7040e1e782c --- /dev/null +++ b/src/components/Common/PrintTable.tsx @@ -0,0 +1,66 @@ +import { t } from "i18next"; + +import { cn } from "@/lib/utils"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +type HeaderRow = { + key: string; + width?: number; +}; + +type TableRowType = Record; +interface GenericTableProps { + headers: HeaderRow[]; + rows: TableRowType[] | undefined; +} + +export default function PrintTable({ headers, rows }: GenericTableProps) { + return ( +
+ + + + {headers.map(({ key, width }, index) => ( + + {t(key)} + + ))} + + + + {!!rows && + rows.map((row, index) => ( + + {headers.map(({ key }) => ( + + {row[key] || "-"} + + ))} + + ))} + +
+
+ ); +} diff --git a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx index 2514c7bbdfe..c6fc5667222 100644 --- a/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx +++ b/src/components/Facility/ConsultationDetails/QuestionnaireResponsesList.tsx @@ -197,12 +197,23 @@ function StructuredResponseBadge({ ); } -function ResponseCard({ item }: { item: QuestionnaireResponse }) { +function ResponseCard({ + item, + isPrintPreview, +}: { + item: QuestionnaireResponse; + isPrintPreview?: boolean; +}) { const isStructured = !item.questionnaire; const structuredType = Object.keys(item.structured_responses || {})[0]; return ( - +
@@ -317,7 +328,12 @@ export default function QuestionnaireResponsesList({ ) : (
{questionnarieResponses?.results?.length === 0 ? ( - +
{t("no_questionnaire_responses")}
@@ -327,7 +343,11 @@ export default function QuestionnaireResponsesList({ {questionnarieResponses?.results?.map( (item: QuestionnaireResponse) => (
  • - +
  • ), )} diff --git a/src/components/Facility/FacilityForm.tsx b/src/components/Facility/FacilityForm.tsx index c6cabe1de6a..1058f324bd0 100644 --- a/src/components/Facility/FacilityForm.tsx +++ b/src/components/Facility/FacilityForm.tsx @@ -144,7 +144,7 @@ export default function FacilityForm({ const handleFeatureChange = (value: string[]) => { const features = value.map((val) => Number(val)); - form.setValue("features", features); + form.setValue("features", features, { shouldDirty: true }); }; const handleGetCurrentLocation = () => { @@ -152,8 +152,12 @@ export default function FacilityForm({ setIsGettingLocation(true); navigator.geolocation.getCurrentPosition( (position) => { - form.setValue("latitude", position.coords.latitude); - form.setValue("longitude", position.coords.longitude); + form.setValue("latitude", position.coords.latitude, { + shouldDirty: true, + }); + form.setValue("longitude", position.coords.longitude, { + shouldDirty: true, + }); setIsGettingLocation(false); toast.success(t("location_updated_successfully")); }, @@ -346,7 +350,9 @@ export default function FacilityForm({ value={form.watch("geo_organization")} selected={selectedLevels} onChange={(value) => - form.setValue("geo_organization", value) + form.setValue("geo_organization", value, { + shouldDirty: true, + }) } required /> @@ -418,6 +424,7 @@ export default function FacilityForm({ form.setValue( "latitude", e.target.value ? Number(e.target.value) : undefined, + { shouldDirty: true }, ); }} data-cy="facility-latitude" @@ -445,6 +452,7 @@ export default function FacilityForm({ form.setValue( "longitude", e.target.value ? Number(e.target.value) : undefined, + { shouldDirty: true }, ); }} data-cy="facility-longitude" @@ -493,7 +501,9 @@ export default function FacilityForm({ type="submit" className="w-full" variant="primary" - disabled={facilityId ? isUpdatePending : isPending} + disabled={ + facilityId ? isUpdatePending || !form.formState.isDirty : isPending + } data-cy={facilityId ? "update-facility" : "submit-facility"} > {facilityId ? ( diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index edd84bdf56b..c619575586c 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -35,6 +35,8 @@ import type { } from "@/types/organization/organization"; import { getOrgLabel } from "@/types/organization/organization"; +import { FacilityMapsLink } from "./FacilityMapLink"; + type Props = { facilityId: string; }; @@ -322,8 +324,13 @@ export const FacilityHome = ({ facilityId }: Props) => { {t("location_details")} - - {/* Add Location Link Here */} + + {facilityData.latitude && facilityData.longitude && ( + + )}
    diff --git a/src/components/Facility/FacilityMapLink.tsx b/src/components/Facility/FacilityMapLink.tsx new file mode 100644 index 00000000000..47a76082fbf --- /dev/null +++ b/src/components/Facility/FacilityMapLink.tsx @@ -0,0 +1,42 @@ +import { SquareArrowOutUpRight } from "lucide-react"; +import { Link } from "raviger"; +import { useTranslation } from "react-i18next"; + +import { getMapUrl, isAndroidDevice } from "@/Utils/utils"; + +const isValidLatitude = (latitude: string) => { + const lat = parseFloat(latitude.trim()); + return Number.isFinite(lat) && lat >= -90 && lat <= 90; +}; + +const isValidLongitude = (longitude: string) => { + const long = parseFloat(longitude.trim()); + return Number.isFinite(long) && long >= -180 && long <= 180; +}; + +export const FacilityMapsLink = ({ + latitude, + longitude, +}: { + latitude: string; + longitude: string; +}) => { + const { t } = useTranslation(); + + if (!isValidLatitude(latitude) || !isValidLongitude(longitude)) { + return null; + } + const target = isAndroidDevice ? "_self" : "_blank"; + + return ( + + {t("show_on_map")} + + + ); +}; diff --git a/src/components/Files/FilesTab.tsx b/src/components/Files/FilesTab.tsx index 50fb179cd36..3c1abcbfb45 100644 --- a/src/components/Files/FilesTab.tsx +++ b/src/components/Files/FilesTab.tsx @@ -251,54 +251,52 @@ export const FilesTab = (props: FilesTabProps) => { const filetype = getFileType(file); return ( <> - {editPermission() && ( -
    - {filetype === "AUDIO" && !file.is_archived && ( - - )} - {fileManager.isPreviewable(file) && ( - + )} + {fileManager.isPreviewable(file) && ( + + )} + + + - )} - { - - - - - - - - + + + + + + {editPermission() && ( + <> - - - } -
    - )} + + )} + + +
    ); }; diff --git a/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx b/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx index 93d8c430717..b70ec7e45e8 100644 --- a/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx +++ b/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useMemo, useState } from "react"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; -import { Card } from "@/components/ui/card"; +import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Popover, @@ -647,113 +647,124 @@ export const AdministrationTab: React.FC = ({ content = ; } else { content = ( - - -
    - {/* Top row without vertical borders */} -
    -
    -
    - {lastModifiedDate && ( -
    - {t("last_modified")}{" "} - {formatDistanceToNow(lastModifiedDate)} {t("ago")} -
    - )} + <> + {!filteredMedications.length && ( + +

    + {t("no_active_medication_recorded")} +

    +
    + )} + + +
    + {/* Top row without vertical borders */} +
    +
    +
    + {lastModifiedDate && ( +
    + {t("last_modified")}{" "} + {formatDistanceToNow(lastModifiedDate)} {t("ago")} +
    + )} +
    +
    + +
    -
    + {visibleSlots.map((slot) => ( + + ))} +
    - {visibleSlots.map((slot) => ( - - ))} -
    - -
    -
    - {/* Main content with borders */} -
    - {/* Headers */} -
    - {t("medicine")}: -
    - {visibleSlots.map((slot, i) => ( -
    - {i === endSlotIndex && - slot.date.getTime() === currentDate.getTime() && ( -
    -
    -
    - )} - {slot.label} + {/* Main content with borders */} +
    + {/* Headers */} +
    + {t("medicine")}:
    - ))} -
    - - {/* Medication rows */} - {filteredMedications?.map((medication) => ( - - ))} + {visibleSlots.map((slot, i) => ( +
    + {i === endSlotIndex && + slot.date.getTime() === currentDate.getTime() && ( +
    +
    +
    + )} + {slot.label} +
    + ))} +
    + + {/* Medication rows */} + {filteredMedications?.map((medication) => ( + + ))} +
    -
    - {stoppedMedications?.results?.length > 0 && !searchQuery.trim() && ( -
    setShowStopped(!showStopped)} - > - - - {showStopped ? t("hide") : t("show")}{" "} - {`${stoppedMedications?.results?.length} ${t("stopped")}`}{" "} - {t("prescriptions")} - -
    - )} - - - + {stoppedMedications?.results?.length > 0 && !searchQuery.trim() && ( +
    setShowStopped(!showStopped)} + > + + + {showStopped ? t("hide") : t("show")}{" "} + {`${stoppedMedications?.results?.length} ${t("stopped")}`}{" "} + {t("prescriptions")} + +
    + )} + + + + ); } diff --git a/src/components/Medicine/MedicationsTable.tsx b/src/components/Medicine/MedicationsTable.tsx index e347d2a9ef7..d78c736fd28 100644 --- a/src/components/Medicine/MedicationsTable.tsx +++ b/src/components/Medicine/MedicationsTable.tsx @@ -1,7 +1,6 @@ -import { useQuery } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; -import { Skeleton } from "@/components/ui/skeleton"; +import { CardContent } from "@/components/ui/card"; import { Table, TableBody, @@ -13,14 +12,12 @@ import { import { reverseFrequencyOption } from "@/components/Questionnaire/QuestionTypes/MedicationRequestQuestion"; -import query from "@/Utils/request/query"; import { INACTIVE_MEDICATION_STATUSES, MEDICATION_REQUEST_TIMING_OPTIONS, MedicationRequestDosageInstruction, MedicationRequestRead, } from "@/types/emr/medicationRequest"; -import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; import { formatDosage, formatSig } from "./utils"; @@ -37,38 +34,22 @@ export function getFrequencyDisplay( } interface MedicationsTableProps { - patientId?: string; - encounterId?: string; - medications?: MedicationRequestRead[]; + medications: MedicationRequestRead[]; } -export const MedicationsTable = ({ - medications, - patientId, - encounterId, -}: MedicationsTableProps) => { +export const MedicationsTable = ({ medications }: MedicationsTableProps) => { const { t } = useTranslation(); - const { data: allMedications, isLoading } = useQuery({ - queryKey: ["medication_requests", patientId, encounterId], - queryFn: query(medicationRequestApi.list, { - pathParams: { patientId }, - queryParams: { encounter: encounterId, limit: 50, offset: 0 }, - }), - enabled: !medications && !!patientId, - }); - - if (isLoading) { + if (!medications.length) { return ( -
    - -
    + +

    + {t("no_active_medication_recorded")} +

    +
    ); } - const displayedMedications = !medications - ? (allMedications?.results ?? []) - : medications; return (
    @@ -82,7 +63,7 @@ export const MedicationsTable = ({ - {displayedMedications?.map((medication) => { + {medications.map((medication) => { const instruction = medication.dosage_instruction[0]; const frequency = getFrequencyDisplay(instruction?.timing); const dosage = formatDosage(instruction); diff --git a/src/components/Patient/MedicationStatementList.tsx b/src/components/Patient/MedicationStatementList.tsx index 112c42414a4..606c00962d7 100644 --- a/src/components/Patient/MedicationStatementList.tsx +++ b/src/components/Patient/MedicationStatementList.tsx @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; import { useState } from "react"; import { useTranslation } from "react-i18next"; @@ -35,20 +36,14 @@ import medicationStatementApi from "@/types/emr/medicationStatement/medicationSt interface MedicationStatementListProps { patientId: string; className?: string; - isPrintPreview?: boolean; } interface MedicationRowProps { statement: MedicationStatementRead; isEnteredInError?: boolean; - isPrintPreview?: boolean; } -function MedicationRow({ - statement, - isEnteredInError, - isPrintPreview = false, -}: MedicationRowProps) { +function MedicationRow({ statement, isEnteredInError }: MedicationRowProps) { const { t } = useTranslation(); return ( @@ -80,26 +75,22 @@ function MedicationRow({ {statement.note ? (
    - {isPrintPreview ? ( - {statement.note} - ) : ( - - - - - -

    - {statement.note} -

    -
    -
    - )} + + + + + +

    + {statement.note} +

    +
    +
    ) : ( "-" @@ -121,11 +112,10 @@ function MedicationRow({ export function MedicationStatementList({ patientId, - className, - isPrintPreview = false, + className = "", }: MedicationStatementListProps) { const { t } = useTranslation(); - const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview); + const [showEnteredInError, setShowEnteredInError] = useState(false); const { data: medications, isLoading } = useQuery({ queryKey: ["medication_statements", patientId], @@ -136,16 +126,9 @@ export function MedicationStatementList({ if (isLoading) { return ( - - - {t("ongoing_medications")} - - - - - + + + ); } @@ -160,31 +143,18 @@ export function MedicationStatementList({ if (!filteredMedications?.length) { return ( - - - {t("ongoing_medications")} - - -

    {t("no_ongoing_medications")}

    -
    -
    + +

    {t("no_ongoing_medications")}

    +
    ); } return ( - - - - {t("ongoing_medications")} ({filteredMedications.length}) - - - + + <>
    @@ -226,7 +196,6 @@ export function MedicationStatementList({ key={statement.id} statement={statement} isEnteredInError={statement.status === "entered_in_error"} - isPrintPreview={isPrintPreview} /> ))} @@ -246,7 +215,29 @@ export function MedicationStatementList({ )} - - + + ); } + +const MedicationStatementListLayout = ({ + children, + className, + medicationsCount, +}: { + children: React.ReactNode; + className?: string; + medicationsCount?: number | undefined; +}) => { + return ( + + + + {t("ongoing_medications")}{" "} + {medicationsCount ? `(${medicationsCount})` : ""} + + + {children} + + ); +}; diff --git a/src/components/Patient/PatientRegistration.tsx b/src/components/Patient/PatientRegistration.tsx index 6f46b141468..bfc08e2f435 100644 --- a/src/components/Patient/PatientRegistration.tsx +++ b/src/components/Patient/PatientRegistration.tsx @@ -262,26 +262,32 @@ export default function PatientRegistration( patientQuery.data.geo_organization as unknown as Organization, ]); form.reset({ - ...patientQuery.data, + name: patientQuery.data.name || "", + phone_number: patientQuery.data.phone_number || "", + emergency_phone_number: patientQuery.data.emergency_phone_number || "", same_phone_number: patientQuery.data.phone_number === patientQuery.data.emergency_phone_number, same_address: patientQuery.data.address === patientQuery.data.permanent_address, + gender: patientQuery.data.gender as (typeof GENDERS)[number], + blood_group: patientQuery.data.blood_group, age_or_dob: patientQuery.data.date_of_birth ? "dob" : "age", + date_of_birth: patientQuery.data.date_of_birth || undefined, age: !patientQuery.data.date_of_birth && patientQuery.data.year_of_birth ? new Date().getFullYear() - patientQuery.data.year_of_birth : undefined, - date_of_birth: patientQuery.data.date_of_birth - ? patientQuery.data.date_of_birth - : undefined, + address: patientQuery.data.address || "", + permanent_address: patientQuery.data.permanent_address || "", + pincode: patientQuery.data.pincode || undefined, + nationality: patientQuery.data.nationality || "India", geo_organization: ( patientQuery.data.geo_organization as unknown as Organization )?.id, } as unknown as z.infer); } - }, [patientQuery.data]); // eslint-disable-line react-hooks/exhaustive-deps + }, [patientQuery.data]); const showDuplicate = !patientPhoneSearch.isLoading && @@ -733,7 +739,11 @@ export default function PatientRegistration( diff --git a/src/components/Patient/TreatmentSummary.tsx b/src/components/Patient/TreatmentSummary.tsx index ad87eee91a6..862d7dca5b8 100644 --- a/src/components/Patient/TreatmentSummary.tsx +++ b/src/components/Patient/TreatmentSummary.tsx @@ -2,38 +2,133 @@ import careConfig from "@careConfig"; import { useQuery } from "@tanstack/react-query"; import { format } from "date-fns"; import { t } from "i18next"; +import { Loader } from "lucide-react"; import PrintPreview from "@/CAREUI/misc/PrintPreview"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; + +import Loading from "@/components/Common/Loading"; +import PrintTable from "@/components/Common/PrintTable"; import QuestionnaireResponsesList from "@/components/Facility/ConsultationDetails/QuestionnaireResponsesList"; -import { MedicationsTable } from "@/components/Medicine/MedicationsTable"; -import { AllergyList } from "@/components/Patient/allergy/list"; -import { DiagnosisList } from "@/components/Patient/diagnosis/list"; -import { SymptomsList } from "@/components/Patient/symptoms/list"; +import { getFrequencyDisplay } from "@/components/Medicine/MedicationsTable"; +import { formatDosage, formatSig } from "@/components/Medicine/utils"; import api from "@/Utils/request/api"; import query from "@/Utils/request/query"; -import { formatName, formatPatientAge } from "@/Utils/utils"; - -import { MedicationStatementList } from "./MedicationStatementList"; +import { formatDateTime, formatName, formatPatientAge } from "@/Utils/utils"; +import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; +import diagnosisApi from "@/types/emr/diagnosis/diagnosisApi"; +import { completedEncounterStatus } from "@/types/emr/encounter"; +import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; +import medicationStatementApi from "@/types/emr/medicationStatement/medicationStatementApi"; +import symptomApi from "@/types/emr/symptom/symptomApi"; interface TreatmentSummaryProps { facilityId: string; encounterId: string; + + patientId: string; } +const SectionLayout = ({ + children, + title, +}: { + title: string; + children: React.ReactNode; +}) => { + return ( + + + {title} + + {children} + + ); +}; + +const EmptyState = ({ message }: { message: string }) => { + return ( + +

    {message}

    +
    + ); +}; export default function TreatmentSummary({ facilityId, encounterId, + patientId, }: TreatmentSummaryProps) { - const { data: encounter } = useQuery({ + const { data: encounter, isLoading: encounterLoading } = useQuery({ queryKey: ["encounter", encounterId], queryFn: query(api.encounter.get, { pathParams: { id: encounterId }, queryParams: { facility: facilityId }, }), + enabled: !!encounterId && !!facilityId, }); + const { data: allergies, isLoading: allergiesLoading } = useQuery({ + queryKey: ["allergies", patientId, encounterId], + queryFn: query.paginated(allergyIntoleranceApi.getAllergy, { + pathParams: { patientId }, + queryParams: { + encounter: ( + encounter?.status + ? completedEncounterStatus.includes(encounter.status) + : false + ) + ? encounterId + : undefined, + }, + pageSize: 100, + }), + }); + + const { data: symptoms, isLoading: symptomsLoading } = useQuery({ + queryKey: ["symptoms", patientId, encounterId], + queryFn: query.paginated(symptomApi.listSymptoms, { + pathParams: { patientId }, + queryParams: { encounter: encounterId }, + pageSize: 100, + }), + enabled: !!patientId && !!encounterId, + }); + + const { data: diagnoses, isLoading: diagnosesLoading } = useQuery({ + queryKey: ["diagnosis", patientId, encounterId], + queryFn: query.paginated(diagnosisApi.listDiagnosis, { + pathParams: { patientId }, + queryParams: { encounter: encounterId }, + pageSize: 100, + }), + enabled: !!patientId && !!encounterId, + }); + + const { data: medications, isLoading: medicationsLoading } = useQuery({ + queryKey: ["medication_requests", patientId, encounterId], + queryFn: query.paginated(medicationRequestApi.list, { + pathParams: { patientId }, + queryParams: { encounter: encounterId }, + pageSize: 100, + }), + enabled: !!encounterId, + }); + const { data: medicationStatement, isLoading: medicationStatementLoading } = + useQuery({ + queryKey: ["medication_statements", patientId], + queryFn: query.paginated(medicationStatementApi.list, { + pathParams: { patientId }, + pageSize: 100, + }), + enabled: !!patientId, + }); + + if (encounterLoading) { + return ; + } + if (!encounter) { return (
    @@ -42,11 +137,31 @@ export default function TreatmentSummary({ ); } + const isLoading = + encounterLoading || + allergiesLoading || + diagnosesLoading || + symptomsLoading || + medicationsLoading || + medicationStatementLoading; + + if (isLoading) { + return ( + +
    + +
    +
    + ); + } + return ( -
    +
    {/* Header */}
    @@ -68,36 +183,39 @@ export default function TreatmentSummary({
    {/* Patient Details */} -
    +
    -
    +
    {t("patient")} : - {encounter.patient.name} + + {encounter.patient.name} +
    -
    +
    {`${t("age")} / ${t("sex")}`} : - + {`${formatPatientAge(encounter.patient, true)}, ${t(`GENDER__${encounter.patient.gender}`)}`}
    -
    +
    {t("encounter_class")} : {t(`encounter_class__${encounter.encounter_class}`)}
    -
    +
    {t("priority")} : {t(`encounter_priority__${encounter.priority}`)}
    + {encounter.hospitalization?.admit_source && ( -
    +
    {t("admission_source")} : @@ -108,14 +226,14 @@ export default function TreatmentSummary({
    )} {encounter.hospitalization?.re_admission && ( -
    +
    {t("readmission")} : {t("yes")}
    )} {encounter.hospitalization?.diet_preference && ( -
    +
    {t("diet_preference")} : @@ -126,16 +244,19 @@ export default function TreatmentSummary({
    )}
    + + {/* Right Column */}
    -
    +
    {t("mobile_number")} : - + {encounter.patient.phone_number}
    + {encounter.period?.start && ( -
    +
    {t("encounter_date")} : @@ -146,22 +267,25 @@ export default function TreatmentSummary({
    )} -
    + +
    {t("status")} : {t(`encounter_status__${encounter.status}`)}
    -
    + +
    {t("consulting_doctor")} : {formatName(encounter.created_by)}
    + {encounter.external_identifier && ( -
    +
    {t("external_id")} : @@ -169,8 +293,9 @@ export default function TreatmentSummary({
    )} + {encounter.hospitalization?.discharge_disposition && ( -
    +
    {t("discharge_disposition")} @@ -184,51 +309,176 @@ export default function TreatmentSummary({ )}
    - {/* Medical Information */}
    {/* Allergies */} - + + {allergies?.count ? ( + ({ + allergen: allergy.code.display, + status: t(allergy.clinical_status), + criticality: t(allergy.criticality), + verification: t(allergy.verification_status), + notes: allergy.note, + logged_by: formatName(allergy.created_by), + }))} + /> + ) : ( + + )} + {/* Symptoms */} - + + + {symptoms?.count ? ( + ({ + symptom: symptom.code.display, + severity: t(symptom.severity), + status: t(symptom.clinical_status), + verification: t(symptom.verification_status), + onset: symptom.onset?.onset_datetime + ? new Date( + symptom.onset.onset_datetime, + ).toLocaleDateString() + : "-", + notes: symptom.note, + logged_by: formatName(symptom.created_by), + }))} + /> + ) : ( + + )} + {/* Diagnoses */} - + + {diagnoses?.count ? ( + ({ + diagnosis: diagnosis.code.display, + status: t(diagnosis.clinical_status), + verification: t(diagnosis.verification_status), + onset: diagnosis.onset?.onset_datetime + ? new Date( + diagnosis.onset.onset_datetime, + ).toLocaleDateString() + : undefined, + notes: diagnosis.note, + logged_by: formatName(diagnosis.created_by), + }))} + /> + ) : ( + + )} + {/* Medications */} -
    -

    - {t("medications")} -

    - -
    -
    + + {medications?.results.length ? ( + { + const instruction = medication.dosage_instruction[0]; + const frequency = getFrequencyDisplay(instruction?.timing); + const dosage = formatDosage(instruction); + const duration = + instruction?.timing?.repeat?.bounds_duration; + const remarks = formatSig(instruction); + const notes = medication.note; + return { + medicine: medication.medication?.display, + status: t(medication.status), + dosage: dosage, + frequency: instruction?.as_needed_boolean + ? `${t("as_needed_prn")} (${instruction?.as_needed_for?.display ?? "-"})` + : (frequency?.meaning ?? "-") + + (instruction?.additional_instruction?.[0]?.display + ? `, ${instruction.additional_instruction[0].display}` + : ""), + duration: duration + ? `${duration.value} ${duration.unit}` + : "-", + instructions: `${remarks || "-"}${notes ? ` (${t("note")}: ${notes})` : ""}`, + }; + })} + /> + ) : ( + + )} + - {/* Medication Statements */} - + {/* Medication Statements */} + + {medicationStatement?.results.length ? ( + ({ + medication: + medication.medication.display ?? + medication.medication.code, + dosage: medication.dosage_text, + status: medication.status, + medication_taken_between: [ + medication.effective_period?.start, + medication.effective_period?.end, + ] + .map((date) => formatDateTime(date)) + .join(" - "), + reason: medication.reason, + notes: medication.note, + logged_by: formatName(medication.created_by), + }))} + /> + ) : ( + + )} + +
    {/* Questionnaire Responses Section */}
    diff --git a/src/components/Patient/allergy/list.tsx b/src/components/Patient/allergy/list.tsx index 62695fe02e8..4e4fe877a90 100644 --- a/src/components/Patient/allergy/list.tsx +++ b/src/components/Patient/allergy/list.tsx @@ -50,7 +50,7 @@ interface AllergyListProps { patientId: string; encounterId?: string; className?: string; - isPrintPreview?: boolean; + encounterStatus?: Encounter["status"]; } @@ -72,10 +72,9 @@ export function AllergyList({ patientId, encounterId, className, - isPrintPreview = false, encounterStatus, }: AllergyListProps) { - const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview); + const [showEnteredInError, setShowEnteredInError] = useState(false); const { data: allergies, isLoading } = useQuery({ queryKey: ["allergies", patientId, encounterId, encounterStatus], @@ -178,28 +177,22 @@ export function AllergyList({ {allergy.note && (
    - {isPrintPreview ? ( - - {allergy.note} - - ) : ( - - - - - -

    - {allergy.note} -

    -
    -
    - )} + + + + + +

    + {allergy.note} +

    +
    +
    )}
    @@ -223,7 +216,6 @@ export function AllergyList({ patientId={patientId} encounterId={encounterId} className={className} - isPrintPreview={isPrintPreview} >
    @@ -295,24 +287,16 @@ const AllergyListLayout = ({ encounterId, children, className, - isPrintPreview = false, }: { facilityId?: string; patientId: string; encounterId?: string; children: ReactNode; className?: string; - isPrintPreview?: boolean; }) => { return ( - + {t("allergies")} {facilityId && encounterId && ( )} - - {children} - + {children} ); }; diff --git a/src/components/Patient/diagnosis/DiagnosisTable.tsx b/src/components/Patient/diagnosis/DiagnosisTable.tsx index ac9d93cc878..b80e0b6062a 100644 --- a/src/components/Patient/diagnosis/DiagnosisTable.tsx +++ b/src/components/Patient/diagnosis/DiagnosisTable.tsx @@ -26,13 +26,9 @@ import { interface DiagnosisTableProps { diagnoses: Diagnosis[]; - isPrintPreview?: boolean; } -export function DiagnosisTable({ - diagnoses, - isPrintPreview = false, -}: DiagnosisTableProps) { +export function DiagnosisTable({ diagnoses }: DiagnosisTableProps) { return (
    @@ -100,26 +96,22 @@ export function DiagnosisTable({ {diagnosis.note ? (
    - {isPrintPreview ? ( - {diagnosis.note} - ) : ( - - - - - -

    - {diagnosis.note} -

    -
    -
    - )} + + + + + +

    + {diagnosis.note} +

    +
    +
    ) : ( "-" @@ -132,6 +124,7 @@ export function DiagnosisTable({ className="w-4 h-4" imageUrl={diagnosis.created_by.profile_picture_url} /> + {diagnosis.created_by.username}
    diff --git a/src/components/Patient/diagnosis/list.tsx b/src/components/Patient/diagnosis/list.tsx index 4695f5900f9..cb682456b02 100644 --- a/src/components/Patient/diagnosis/list.tsx +++ b/src/components/Patient/diagnosis/list.tsx @@ -21,7 +21,6 @@ interface DiagnosisListProps { encounterId?: string; facilityId?: string; className?: string; - isPrintPreview?: boolean; } export function DiagnosisList({ @@ -29,9 +28,8 @@ export function DiagnosisList({ encounterId, facilityId, className, - isPrintPreview = false, }: DiagnosisListProps) { - const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview); + const [showEnteredInError, setShowEnteredInError] = useState(false); const { data: diagnoses, isLoading } = useQuery({ queryKey: ["diagnosis", patientId, encounterId], @@ -47,6 +45,7 @@ export function DiagnosisList({ facilityId={facilityId} patientId={patientId} encounterId={encounterId} + className={className} > @@ -71,6 +70,7 @@ export function DiagnosisList({ facilityId={facilityId} patientId={patientId} encounterId={encounterId} + className={className} >

    {t("no_diagnoses_recorded")}

    @@ -85,38 +85,39 @@ export function DiagnosisList({ patientId={patientId} encounterId={encounterId} className={className} - isPrintPreview={isPrintPreview} > - diagnosis.verification_status !== "entered_in_error", - ), - ...(showEnteredInError - ? filteredDiagnoses.filter( - (diagnosis) => - diagnosis.verification_status === "entered_in_error", - ) - : []), - ]} - isPrintPreview={isPrintPreview} - /> + <> + + diagnosis.verification_status !== "entered_in_error", + ), + ...(showEnteredInError + ? filteredDiagnoses.filter( + (diagnosis) => + diagnosis.verification_status === "entered_in_error", + ) + : []), + ]} + /> - {hasEnteredInErrorRecords && !showEnteredInError && ( - <> -
    -
    - -
    - - )} + {hasEnteredInErrorRecords && !showEnteredInError && ( + <> +
    +
    + +
    + + )} + ); } @@ -127,22 +128,17 @@ const DiagnosisListLayout = ({ encounterId, children, className, - isPrintPreview = false, }: { facilityId?: string; patientId: string; encounterId?: string; children: ReactNode; className?: string; - isPrintPreview?: boolean; }) => { return ( - + {t("diagnoses")} {facilityId && encounterId && ( @@ -155,14 +151,7 @@ const DiagnosisListLayout = ({ )} - - {children} - + {children} ); }; diff --git a/src/components/Patient/symptoms/SymptomTable.tsx b/src/components/Patient/symptoms/SymptomTable.tsx index 407a3300749..e3b3ec76cbc 100644 --- a/src/components/Patient/symptoms/SymptomTable.tsx +++ b/src/components/Patient/symptoms/SymptomTable.tsx @@ -27,13 +27,9 @@ import { interface SymptomTableProps { symptoms: Symptom[]; - isPrintPreview?: boolean; } -export function SymptomTable({ - symptoms, - isPrintPreview = false, -}: SymptomTableProps) { +export function SymptomTable({ symptoms }: SymptomTableProps) { return (
    @@ -118,26 +114,22 @@ export function SymptomTable({ {symptom.note ? (
    - {isPrintPreview ? ( - {symptom.note} - ) : ( - - - - - -

    - {symptom.note} -

    -
    -
    - )} + + + + + +

    + {symptom.note} +

    +
    +
    ) : ( "-" @@ -150,6 +142,7 @@ export function SymptomTable({ className="w-4 h-4" imageUrl={symptom.created_by.profile_picture_url} /> + {symptom.created_by.username}
    diff --git a/src/components/Patient/symptoms/list.tsx b/src/components/Patient/symptoms/list.tsx index 0f4b558d6a9..891154584f1 100644 --- a/src/components/Patient/symptoms/list.tsx +++ b/src/components/Patient/symptoms/list.tsx @@ -21,7 +21,6 @@ interface SymptomsListProps { encounterId?: string; facilityId?: string; className?: string; - isPrintPreview?: boolean; } export function SymptomsList({ @@ -29,9 +28,8 @@ export function SymptomsList({ encounterId, facilityId, className, - isPrintPreview = false, }: SymptomsListProps) { - const [showEnteredInError, setShowEnteredInError] = useState(isPrintPreview); + const [showEnteredInError, setShowEnteredInError] = useState(false); const { data: symptoms, isLoading } = useQuery({ queryKey: ["symptoms", patientId, encounterId], @@ -84,7 +82,6 @@ export function SymptomsList({ patientId={patientId} encounterId={encounterId} className={className} - isPrintPreview={isPrintPreview} > {hasEnteredInErrorRecords && !showEnteredInError && ( @@ -125,24 +121,16 @@ const SymptomListLayout = ({ encounterId, children, className, - isPrintPreview = false, }: { facilityId?: string; patientId: string; encounterId?: string; children: ReactNode; className?: string; - isPrintPreview?: boolean; }) => { return ( - + {t("symptoms")} {facilityId && encounterId && ( )} - - {children} - + {children} ); }; diff --git a/src/components/Resource/ResourceForm.tsx b/src/components/Resource/ResourceForm.tsx index bc4981b2ab9..fa443f489e0 100644 --- a/src/components/Resource/ResourceForm.tsx +++ b/src/components/Resource/ResourceForm.tsx @@ -202,7 +202,7 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) { })); const handleUserChange = (user: UserBase) => { - form.setValue("assigned_to", user.id); + form.setValue("assigned_to", user.id, { shouldDirty: true }); setAssignedToUser(user); }; @@ -210,9 +210,14 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) { form.setValue( "referring_facility_contact_name", `${authUser.first_name} ${authUser.last_name}`.trim(), + { shouldDirty: true }, ); if (authUser.phone_number) { - form.setValue("referring_facility_contact_number", authUser.phone_number); + form.setValue( + "referring_facility_contact_number", + authUser.phone_number, + { shouldDirty: true }, + ); } }; @@ -292,7 +297,9 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) { facilities?.results.find( (f) => f.id === value, ) ?? null; - form.setValue("assigned_facility", facility); + form.setValue("assigned_facility", facility, { + shouldDirty: true, + }); }} /> @@ -551,7 +558,15 @@ export default function ResourceForm({ facilityId, id }: ResourceProps) { > {t("cancel")} -