diff --git a/public/locale/en.json b/public/locale/en.json index 536be8e4be1..fc1bcffc911 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -973,6 +973,7 @@ "facility": "Facility", "facility_actions_menu": "Facility action menu", "facility_added_successfully": "Facility created successfully", + "facility_assign_request": "What facility would you like to assign the request to?", "facility_consent_requests_page_title": "Patient Consent List", "facility_count_one": "{{count}} Facility", "facility_count_other": "{{count}} Facilities ", @@ -1878,11 +1879,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", @@ -2030,6 +2032,7 @@ "start_time_before_authored_error": "Start time cannot be before the medication was prescribed", "start_time_future_error": "Start time cannot be in the future", "start_time_must_be_before_end_time": "Start time must be before end time", + "start_typing_to_search": "Start typing to search...", "state": "State", "state_reason_for_archiving": "State reason for archiving {{name}} file?", "status": "Status", diff --git a/src/components/Common/FacilitySelect.tsx b/src/components/Common/FacilitySelect.tsx deleted file mode 100644 index 4209de3fc89..00000000000 --- a/src/components/Common/FacilitySelect.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { t } from "i18next"; -import { useCallback } from "react"; - -import { FacilityModel } from "@/components/Facility/models"; -import AutoCompleteAsync from "@/components/Form/AutoCompleteAsync"; - -import request from "@/Utils/request/request"; -import facilityApi from "@/types/facility/facilityApi"; - -interface BaseFacilitySelectProps { - name: string; - exclude_user?: string; - errors?: string | undefined; - className?: string; - required?: boolean; - searchAll?: boolean; - disabled?: boolean; - multiple?: boolean; - facilityType?: number; - showAll?: boolean; - showNOptions?: number | undefined; - freeText?: boolean; - allowNone?: boolean; - placeholder?: string; - filter?: (facilities: FacilityModel) => boolean; - id?: string; -} - -interface SingleFacilitySelectProps extends BaseFacilitySelectProps { - multiple?: false; - selected: FacilityModel | null; - setSelected: (selected: FacilityModel | null) => void; -} - -interface MultipleFacilitySelectProps extends BaseFacilitySelectProps { - multiple: true; - selected: FacilityModel[]; - setSelected: (selected: FacilityModel[] | null) => void; -} - -type FacilitySelectProps = - | SingleFacilitySelectProps - | MultipleFacilitySelectProps; - -export const FacilitySelect = ({ - name, - exclude_user, - required, - multiple, - selected, - setSelected, - searchAll, - disabled = false, - showAll = true, - showNOptions, - className = "", - facilityType, - allowNone = false, - freeText = false, - errors = "", - placeholder, - filter, - id, -}: FacilitySelectProps) => { - const facilitySearch = useCallback( - async (text: string) => { - const query = { - limit: 50, - offset: 0, - search_text: text, - all: searchAll, - facility_type: facilityType, - exclude_user: exclude_user, - }; - - const { data } = await request(facilityApi.getAllFacilities, { query }); - - if (allowNone) - return [ - { name: t("no_home_facility"), id: "NONE" }, - ...(data?.results || []), - ]; - - return data?.results; - }, - [searchAll, showAll, facilityType, exclude_user, freeText], - ); - - return ( - option.name} - compareBy="id" - className={className} - error={errors} - filter={filter} - /> - ); -}; 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/Form/AutoCompleteAsync.tsx b/src/components/Form/AutoCompleteAsync.tsx deleted file mode 100644 index b561b1f0648..00000000000 --- a/src/components/Form/AutoCompleteAsync.tsx +++ /dev/null @@ -1,224 +0,0 @@ -import { - Combobox, - ComboboxButton, - ComboboxInput, - ComboboxOption, - ComboboxOptions, -} from "@headlessui/react"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import { DropdownTransition } from "@/components/Common/HelperComponents"; -import { - MultiSelectOptionChip, - dropdownOptionClassNames, -} from "@/components/Form/MultiSelectMenuV2"; - -import useDebounce from "@/hooks/useDebounce"; - -import { classNames } from "@/Utils/utils"; - -interface Props { - id?: string; - name?: string; - selected: any | any[]; - fetchData: (search: string) => Promise | undefined; - onChange: (selected: any) => void; - optionLabel?: (option: any) => string; - optionLabelChip?: (option: any) => string; - showNOptions?: number | undefined; - multiple?: boolean; - compareBy?: string; - debounceTime?: number; - className?: string; - placeholder?: string; - disabled?: boolean; - error?: string; - required?: boolean; - onBlur?: () => void; - onFocus?: () => void; - filter?: (data: any) => boolean; -} - -const AutoCompleteAsync = (props: Props) => { - const { - id, - name, - selected, - fetchData, - onChange, - optionLabel = (option: any) => option.label, - optionLabelChip = (option: any) => option.label, - showNOptions, - multiple = false, - compareBy, - debounceTime = 300, - className = "", - placeholder, - disabled = false, - required = false, - error, - filter, - } = props; - const [data, setData] = useState([]); - const [query, setQuery] = useState(""); - const [loading, setLoading] = useState(false); - const { t } = useTranslation(); - - const hasSelection = - (!multiple && selected) || (multiple && selected?.length > 0); - - const fetchDataDebounced = useDebounce(async (searchQuery: string) => { - setLoading(true); - try { - const fetchedData = (await fetchData(searchQuery)) || []; - const filteredData = filter - ? fetchedData.filter((item: any) => filter(item)) - : fetchedData; - - setData( - showNOptions !== undefined - ? filteredData.slice(0, showNOptions) - : filteredData, - ); - } catch (error) { - console.error("Error fetching data:", error); - } finally { - setLoading(false); - } - }, debounceTime); - - useEffect(() => { - fetchDataDebounced(query); - }, [query]); - - return ( -
- -
-
- - hasSelection && !multiple ? optionLabel?.(selected) : "" - } - onChange={({ target }) => setQuery(target.value)} - onFocus={props.onFocus} - onBlur={() => { - props.onBlur?.(); - }} - autoComplete="off" - /> - {!disabled && ( - -
- {hasSelection && !loading && !required && ( -
- { - e.preventDefault(); - onChange(null); - }} - /> - - {t("clear_selection")} - -
- )} - {loading ? ( - - ) : ( - - )} -
-
- )} -
- - - {data?.length === 0 ? ( -
- {query !== "" - ? "Nothing found." - : "Start typing to search..."} -
- ) : ( - data?.map((item: any) => ( - - {({ selected }) => ( -
-
- {optionLabel(item)} - {optionLabelChip(item) && ( -
- {optionLabelChip(item)} -
- )} -
- {selected && ( - - )} -
- )} -
- )) - )} -
-
- {multiple && selected?.length > 0 && ( -
- {selected?.map((option: any, index: number) => ( - - onChange( - selected.filter((item: any) => item.id !== option.id), - ) - } - /> - ))} -
- )} - {error && ( -
- {error} -
- )} -
-
-
- ); -}; - -export default AutoCompleteAsync; 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 = ({