From 27063712eccb09b4af10c95e48c69b49cc5c388e Mon Sep 17 00:00:00 2001 From: Abdul Azees Date: Sun, 9 Feb 2025 20:18:06 +0530 Subject: [PATCH 1/6] Switch to using cn and variants for AlertDialogAction (#10466) (#10499) --- src/components/Patient/PatientDetailsTab/PatientUsers.tsx | 6 ++++-- .../QuestionTypes/MedicationRequestQuestion.tsx | 4 ++-- .../QuestionTypes/MedicationStatementQuestion.tsx | 4 ++-- src/components/Questionnaire/show.tsx | 6 ++++-- src/pages/Apps/PlugConfigEdit.tsx | 6 ++++-- .../organizations/components/EditFacilityUserRoleSheet.tsx | 6 ++++-- src/pages/Organization/components/EditUserRoleSheet.tsx | 6 ++++-- 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/components/Patient/PatientDetailsTab/PatientUsers.tsx b/src/components/Patient/PatientDetailsTab/PatientUsers.tsx index 5f0e90e8297..f961544d867 100644 --- a/src/components/Patient/PatientDetailsTab/PatientUsers.tsx +++ b/src/components/Patient/PatientDetailsTab/PatientUsers.tsx @@ -3,6 +3,8 @@ import { t } from "i18next"; import { useState } from "react"; import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + import CareIcon from "@/CAREUI/icons/CareIcon"; import { @@ -16,7 +18,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Select, SelectContent, @@ -287,7 +289,7 @@ export const PatientUsers = (props: PatientProps) => { removeUser(user.id)} - className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + className={cn(buttonVariants({ variant: "destructive" }))} > {t("remove")} diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index f9ff069f431..8742560db7b 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -15,7 +15,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, @@ -201,7 +201,7 @@ export function MedicationRequestQuestion({ {t("cancel")} {t("remove")} diff --git a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx index f56be1b82dc..4a26f0afb25 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx @@ -18,7 +18,7 @@ import { AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Collapsible, CollapsibleContent, @@ -204,7 +204,7 @@ export function MedicationStatementQuestion({ {t("cancel")} {t("remove")} diff --git a/src/components/Questionnaire/show.tsx b/src/components/Questionnaire/show.tsx index 4534ce5734b..28c6efcbdf1 100644 --- a/src/components/Questionnaire/show.tsx +++ b/src/components/Questionnaire/show.tsx @@ -2,6 +2,8 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { useNavigate } from "raviger"; import { useState } from "react"; +import { cn } from "@/lib/utils"; + import CareIcon from "@/CAREUI/icons/CareIcon"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; @@ -16,7 +18,7 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { DropdownMenu, @@ -218,7 +220,7 @@ export function QuestionnaireShow({ id }: QuestionnaireShowProps) { Cancel {isPending ? "Deleting..." : "Delete"} diff --git a/src/pages/Apps/PlugConfigEdit.tsx b/src/pages/Apps/PlugConfigEdit.tsx index 353a1796b88..f6230b71f15 100644 --- a/src/pages/Apps/PlugConfigEdit.tsx +++ b/src/pages/Apps/PlugConfigEdit.tsx @@ -2,6 +2,8 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { useNavigate } from "raviger"; import { useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; + import CareIcon from "@/CAREUI/icons/CareIcon"; import { @@ -15,7 +17,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; @@ -108,7 +110,7 @@ export function PlugConfigEdit({ slug }: Props) { Cancel Delete diff --git a/src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx b/src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx index 3d96f2210a7..dfae40ba31f 100644 --- a/src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx +++ b/src/pages/Facility/settings/organizations/components/EditFacilityUserRoleSheet.tsx @@ -3,6 +3,8 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + import { AlertDialog, AlertDialogAction, @@ -14,7 +16,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Select, @@ -230,7 +232,7 @@ export default function EditUserRoleSheet({ {t("cancel")} removeRole()} - className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + className={cn(buttonVariants({ variant: "destructive" }))} > {t("remove")} diff --git a/src/pages/Organization/components/EditUserRoleSheet.tsx b/src/pages/Organization/components/EditUserRoleSheet.tsx index a70e4633340..fb05aa6dec7 100644 --- a/src/pages/Organization/components/EditUserRoleSheet.tsx +++ b/src/pages/Organization/components/EditUserRoleSheet.tsx @@ -3,6 +3,8 @@ import { useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; +import { cn } from "@/lib/utils"; + import { AlertDialog, AlertDialogAction, @@ -14,7 +16,7 @@ import { AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; -import { Button } from "@/components/ui/button"; +import { Button, buttonVariants } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Select, @@ -238,7 +240,7 @@ export default function EditUserRoleSheet({ {t("cancel")} removeRole()} - className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + className={cn(buttonVariants({ variant: "destructive" }))} > {t("remove")} From f4e941615ea92ed347b4b297134027c28c4992e0 Mon Sep 17 00:00:00 2001 From: Khavin Shankar Date: Mon, 10 Feb 2025 10:43:29 +0530 Subject: [PATCH 2/6] sanitize html before rendering it unsafely (#10485) --- src/components/ui/markdown.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/ui/markdown.tsx b/src/components/ui/markdown.tsx index 99ec3626db5..828f304ce9c 100644 --- a/src/components/ui/markdown.tsx +++ b/src/components/ui/markdown.tsx @@ -1,3 +1,4 @@ +import DOMPurify from "dompurify"; import MarkdownIt from "markdown-it"; import * as React from "react"; @@ -22,7 +23,10 @@ export interface MarkdownProps extends React.HTMLAttributes { const Markdown = React.forwardRef( ({ className, content, prose = true, ...props }, ref) => { - const html = React.useMemo(() => md.render(content), [content]); + const html = React.useMemo(() => { + const renderedHtml = md.render(content); + return DOMPurify.sanitize(renderedHtml); + }, [content]); if (prose) { return ( From be859551765b74da5225c64a6cb50e7fc2de35a1 Mon Sep 17 00:00:00 2001 From: Amjith Titus Date: Mon, 10 Feb 2025 11:27:26 +0530 Subject: [PATCH 3/6] Minor fixes in Encounter, Medication, Administration (#10488) --- public/locale/en.json | 3 +- .../ConsultationDetails/QuickAccess.tsx | 85 +++++----- .../AdministrationTab.tsx | 145 ++++++++++++------ .../MedicineAdminForm.tsx | 71 ++++++--- .../MedicineAdminSheet.tsx | 2 +- .../Medicine/MedicationRequestTable/index.tsx | 14 +- src/components/Medicine/MedicationsTable.tsx | 10 +- src/components/Patient/PatientInfoCard.tsx | 16 -- .../MedicationRequestQuestion.tsx | 2 +- .../MedicationStatementQuestion.tsx | 2 +- .../medicationAdministration.ts | 1 - 11 files changed, 218 insertions(+), 133 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 7a455f5a214..0454ba6a8ad 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1878,11 +1878,12 @@ "search_for_allergies_to_add": "Search for allergies to add", "search_for_diagnoses_to_add": "Search for diagnoses to add", "search_for_facility": "Search for Facility", + "search_for_medications_to_add": "Search for medications to add", "search_for_symptoms_to_add": "Search for symptoms to add", "search_icd11_placeholder": "Search for ICD-11 Diagnoses", "search_investigation_placeholder": "Search Investigation & Groups", "search_medication": "Search Medication", - "search_medications": "Search for medications to add", + "search_medications": "Search Medications", "search_medicine": "Search Medicine", "search_patient_page_text": "Search for existing patients using their phone number or create a new patient record", "search_patients": "Search Patients", diff --git a/src/components/Facility/ConsultationDetails/QuickAccess.tsx b/src/components/Facility/ConsultationDetails/QuickAccess.tsx index b9aa70c903d..2a8353a8f43 100644 --- a/src/components/Facility/ConsultationDetails/QuickAccess.tsx +++ b/src/components/Facility/ConsultationDetails/QuickAccess.tsx @@ -1,4 +1,3 @@ -import { useQuery } from "@tanstack/react-query"; import { Link } from "raviger"; import { useTranslation } from "react-i18next"; @@ -9,9 +8,9 @@ import { Button } from "@/components/ui/button"; import LinkDepartmentsSheet from "@/components/Patient/LinkDepartmentsSheet"; -import query from "@/Utils/request/query"; +import useQuestionnaireOptions from "@/hooks/useQuestionnaireOptions"; + import { Encounter } from "@/types/emr/encounter"; -import questionnaireApi from "@/types/questionnaire/questionnaireApi"; interface QuickAccessProps { encounter: Encounter; @@ -19,13 +18,7 @@ interface QuickAccessProps { export default function QuickAccess({ encounter }: QuickAccessProps) { const { t } = useTranslation(); - - const { data: response } = useQuery({ - queryKey: ["questionnaires"], - queryFn: query(questionnaireApi.list), - }); - - const questionnaireList = response?.results || []; + const questionnaireOptions = useQuestionnaireOptions("encounter_actions"); const encounterSettings = [ { id: "encounter_settings", label: t("encounter_settings") }, @@ -34,45 +27,47 @@ export default function QuickAccess({ encounter }: QuickAccessProps) { return (
{/* Questionnaire Section */} -
-

{t("questionnaire")}

-
- {questionnaireList.map((item) => ( - - - {item.title} - - ))} -
-
- -
- - {/* Update Encounter Details */} -
-

- {t("update_encounter_details")} -

-
- {encounterSettings.map((item) => ( -
+ {encounter.status !== "completed" && ( +
+

{t("questionnaire")}

+
+ {questionnaireOptions.map((option) => ( - {item.label} + + {t(option.title)} -
- ))} -
-
+ ))} +
+
+ + )} -
+ {/* Update Encounter Details */} + {encounter.status !== "completed" && ( +
+

+ {t("update_encounter_details")} +

+
+ {encounterSettings.map((item) => ( +
+ + {item.label} + +
+ ))} +
+
+
+ )} {/* Departments and Teams */}
diff --git a/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx b/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx index 39f477f6d65..b4af54bc4f8 100644 --- a/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx +++ b/src/components/Medicine/MedicationAdministration/AdministrationTab.tsx @@ -29,7 +29,11 @@ import { MedicationAdministration, MedicationAdministrationRequest, } from "@/types/emr/medicationAdministration/medicationAdministration"; -import { MedicationRequestRead } from "@/types/emr/medicationRequest"; +import { + ACTIVE_MEDICATION_STATUSES, + INACTIVE_MEDICATION_STATUSES, + MedicationRequestRead, +} from "@/types/emr/medicationRequest"; import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; import { MedicineAdminDialog } from "./MedicineAdminDialog"; @@ -40,14 +44,6 @@ import { createMedicationAdministrationRequest, } from "./utils"; -const ACTIVE_STATUSES = ["active", "on-hold", "draft", "unknown"] as const; -const INACTIVE_STATUSES = [ - "ended", - "completed", - "cancelled", - "entered_in_error", -] as const; - // Utility Functions function isTimeInSlot( date: Date, @@ -211,9 +207,13 @@ const MedicationRow: React.FC = ({ onEditAdministration, onDiscontinue, }) => { + const isInactive = INACTIVE_MEDICATION_STATUSES.includes( + medication.status as (typeof INACTIVE_MEDICATION_STATUSES)[number], + ); + return ( -
+
{medication.medication?.display}
@@ -247,7 +247,7 @@ const MedicationRow: React.FC = ({ return (
{administrationRecords?.map((admin) => { const colorClass = @@ -260,16 +260,34 @@ const MedicationRow: React.FC = ({ className={`flex font-medium items-center gap-2 rounded-md p-2 mb-2 cursor-pointer justify-between border ${colorClass}`} onClick={() => onEditAdministration(medication, admin)} > -
- - {new Date(admin.occurrence_period_start).toLocaleTimeString( - "en-US", - { +
+
+ + {new Date( + admin.occurrence_period_start, + ).toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true, - }, - )} + })} +
+
+ {admin.occurrence_period_end && ( + <> + {"- "} + {new Date( + admin.occurrence_period_end, + ).toLocaleTimeString("en-US", { + hour: "numeric", + minute: "2-digit", + hour12: true, + })} + + )} +
{admin.note && ( -
- + } else if (searchQuery && !filteredMedications.length) { + content = ; + } else { + content = (
@@ -684,7 +702,7 @@ export const AdministrationTab: React.FC = ({
{/* Medication rows */} - {medications?.map((medication) => ( + {filteredMedications?.map((medication) => ( = ({ + ); + } + + return ( +
+
+
+
+ + setSearchQuery(e.target.value)} + className="flex-1 bg-transparent text-sm outline-none placeholder:text-gray-500" + /> + {searchQuery && ( + + )} +
+
+ +
+ +
{content}
{selectedMedication && administrationRequest && ( = ({ // Validate and notify parent whenever times change useEffect(() => { - if ( - !administrationRequest.occurrence_period_start || - !administrationRequest.occurrence_period_end - ) { + if (!administrationRequest.occurrence_period_start) { isValid?.(false); return; } const startDate = new Date(administrationRequest.occurrence_period_start); - const endDate = new Date(administrationRequest.occurrence_period_end); - const startError = validateDateTime(startDate, true); - const endError = validateDateTime(endDate, false); - setStartTimeError(startError); - setEndTimeError(endError); - isValid?.(!startError && !endError); + // Only validate end time if status is completed or if end time is provided + if ( + administrationRequest.status === "completed" || + administrationRequest.occurrence_period_end + ) { + if (!administrationRequest.occurrence_period_end) { + isValid?.(false); + return; + } + const endDate = new Date(administrationRequest.occurrence_period_end); + const endError = validateDateTime(endDate, false); + setEndTimeError(endError); + isValid?.(!startError && !endError); + } else { + setEndTimeError(""); + isValid?.(!startError); + } }, [ administrationRequest.occurrence_period_start, administrationRequest.occurrence_period_end, + administrationRequest.status, isValid, - validateDateTime, ]); const handleDateChange = (newTime: string, isStartTime: boolean) => { @@ -238,9 +246,21 @@ export const MedicineAdminForm: React.FC = ({