diff --git a/public/locale/en.json b/public/locale/en.json index 1418c94c8ec..3f7908f90d3 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1445,6 +1445,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_found_for_this_encounter": "No medications found for this encounter.", "no_medications_to_administer": "No medications to administer", "no_notices_for_you": "No notices for you.", 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/components/Common/PrintTable.tsx b/src/components/Common/PrintTable.tsx new file mode 100644 index 00000000000..e7c57133774 --- /dev/null +++ b/src/components/Common/PrintTable.tsx @@ -0,0 +1,65 @@ +import { cn } from "@/lib/utils"; + +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; + +type HeaderRow = { + title: string; + 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, title, width }, index) => ( + + {title} + + ))} + + + + {!!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/Medicine/MedicationsTable.tsx b/src/components/Medicine/MedicationsTable.tsx index 1fee30a06fe..13d94abb8fa 100644 --- a/src/components/Medicine/MedicationsTable.tsx +++ b/src/components/Medicine/MedicationsTable.tsx @@ -1,6 +1,7 @@ import { useQuery } from "@tanstack/react-query"; import { useTranslation } from "react-i18next"; +import { CardContent } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Table, @@ -60,6 +61,14 @@ export const MedicationsTable = ({
    ); } + if (!medications?.results.length) { + return ( + +

    {t("no_medication_recorded")}

    +
    + ); + } + return (
    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/TreatmentSummary.tsx b/src/components/Patient/TreatmentSummary.tsx index ad87eee91a6..01c3757b21f 100644 --- a/src/components/Patient/TreatmentSummary.tsx +++ b/src/components/Patient/TreatmentSummary.tsx @@ -2,29 +2,62 @@ 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 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({ queryKey: ["encounter", encounterId], @@ -34,6 +67,53 @@ export default function TreatmentSummary({ }), }); + const { data: allergies, isLoading: allergiesLoading } = useQuery({ + queryKey: ["allergies", patientId, encounterId], + queryFn: query(allergyIntoleranceApi.getAllergy, { + pathParams: { patientId }, + queryParams: { + encounter: ( + encounter?.status + ? completedEncounterStatus.includes(encounter.status) + : false + ) + ? encounterId + : undefined, + }, + }), + }); + + const { data: symptoms, isLoading: symptomsLoading } = useQuery({ + queryKey: ["symptoms", patientId, encounterId], + queryFn: query(symptomApi.listSymptoms, { + pathParams: { patientId }, + queryParams: encounterId ? { encounter: encounterId } : undefined, + }), + }); + + const { data: diagnoses, isLoading: diagnosesLoading } = useQuery({ + queryKey: ["diagnosis", patientId, encounterId], + queryFn: query(diagnosisApi.listDiagnosis, { + pathParams: { patientId }, + queryParams: encounterId ? { encounter: encounterId } : undefined, + }), + }); + + const { data: medications, isLoading: medicationsLoading } = useQuery({ + queryKey: ["medication_requests", patientId, encounterId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId }, + queryParams: { encounter: encounterId, limit: 50, offset: 0 }, + }), + }); + const { data: medicationStatement, isLoading: medicationStatementLoading } = + useQuery({ + queryKey: ["medication_statements", patientId], + queryFn: query(medicationStatementApi.list, { + pathParams: { patientId }, + }), + }); + if (!encounter) { return (
    @@ -42,11 +122,30 @@ export default function TreatmentSummary({ ); } + const isLoading = + allergiesLoading || + diagnosesLoading || + symptomsLoading || + medicationsLoading || + medicationStatementLoading; + + if (isLoading) { + return ( + +
    + +
    +
    + ); + } + return ( -
    +
    {/* Header */}
    @@ -68,36 +167,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 +210,14 @@ export default function TreatmentSummary({
    )} {encounter.hospitalization?.re_admission && ( -
    +
    {t("readmission")} : {t("yes")}
    )} {encounter.hospitalization?.diet_preference && ( -
    +
    {t("diet_preference")} : @@ -126,16 +228,19 @@ export default function TreatmentSummary({
    )}
    + + {/* Right Column */}
    -
    +
    {t("mobile_number")} : - + {encounter.patient.phone_number}
    + {encounter.period?.start && ( -
    +
    {t("encounter_date")} : @@ -146,22 +251,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 +277,9 @@ export default function TreatmentSummary({
    )} + {encounter.hospitalization?.discharge_disposition && ( -
    +
    {t("discharge_disposition")} @@ -184,51 +293,172 @@ 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), + 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 8b9dd9cb973..759342338cb 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 (
    @@ -110,26 +106,22 @@ export function SymptomTable({ {symptom.note ? (
    - {isPrintPreview ? ( - {symptom.note} - ) : ( - - - - - -

    - {symptom.note} -

    -
    -
    - )} + + + + + +

    + {symptom.note} +

    +
    +
    ) : ( "-" @@ -142,6 +134,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} ); };