diff --git a/public/locale/en.json b/public/locale/en.json index 70b63168575..40bf5df4216 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -335,6 +335,8 @@ "add_new_patient": "Add New Patient", "add_new_user": "Add New User", "add_notes": "Add notes", + "add_notes_about_diagnosis": "Add notes about the diagnosis...", + "add_notes_about_symptom": "Add notes about the symptom...", "add_organizations": "Add Organizations", "add_patient_updates": "Add Patient Updates", "add_policy": "Add Insurance Policy", @@ -385,6 +387,7 @@ "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are {{formats}}.", "already_a_member": "Already a member?", + "already_marked_as_error": "Already marked as error", "alternate_phone_number": "Alternate Phone Number", "ambulance_driver_name": "Name of ambulance driver", "ambulance_number": "Ambulance No", @@ -490,6 +493,7 @@ "bed_type__500": "Others", "before": "before", "beta": "beta", + "biologic": "Biologic", "bladder": "Bladder", "blood_group": "Blood Group", "blood_group_is_required": "Blood group is required", @@ -984,9 +988,9 @@ "enter_valid_dob": "Enter a valid date of birth", "enter_valid_dob_age": "Please enter an age greater than 15 years", "enter_year_of_birth_to_verify": "Enter year of birth to verify", - "entered-in-error": "Entered in error", "entered_in_error": "Entered in Error", "entered_in_error_warning": "This action cannot be undone. The appointment will be marked as entered in error and removed from the system.", + "environment": "Environment", "error_404": "Error 404", "error_deleting_shifting": "Error while deleting Shifting record", "error_fetching_facility_data": "Error while fetching facility data", @@ -1076,6 +1080,7 @@ "filter_by_date": "Filter by Date", "filters": "Filters", "first_name": "First Name", + "food": "Food", "footer_body": "Open Healthcare Network is an open-source public utility designed by a multi-disciplinary team of innovators and volunteers. Open Healthcare Network CARE is a Digital Public Good recognised by the United Nations.", "forget_password": "Forgot password?", "forget_password_instruction": "Enter your username, and if it exists, we will send you a link to reset your password.", @@ -1127,7 +1132,7 @@ "hearing": "We are hearing you...", "help_confirmed": "There is sufficient diagnostic and/or clinical evidence to treat this as a confirmed condition.", "help_differential": "One of a set of potential (and typically mutually exclusive) diagnoses asserted to further guide the diagnostic process and preliminary treatment.", - "help_entered-in-error": "The statement was entered in error and is not valid.", + "help_entered_in_error": "The statement was entered in error and is not valid.", "help_provisional": "This is a tentative diagnosis - still a candidate that is under consideration.", "help_refuted": "This condition has been ruled out by subsequent diagnostic and clinical evidence.", "help_unconfirmed": "There is not sufficient diagnostic and/or clinical evidence to treat this as a confirmed condition.", @@ -1140,6 +1145,7 @@ "hi__record_not_fetched_title": "This record hasn't been fetched yet", "hi__waiting_for_record": "Waiting for the Host HIP to send the record.", "hide": "Hide", + "hide_notes": "Hide notes", "high": "High", "history": "History", "home_facility": "Home Facility", @@ -1154,6 +1160,7 @@ "icmr_specimen_referral_form": "ICMR Specimen Referral Form", "immunisation-records": "Immunisation", "in_consultation": "In-Consultation", + "inactive": "Inactive", "incomplete_patient_details_warning": "Patient details are incomplete. Please update the details before proceeding.", "inconsistent_dosage_units_error": "Dosage units must be same", "indian_mobile": "Indian Mobile", @@ -1336,6 +1343,7 @@ "middleware_hostname": "Middleware Hostname", "middleware_hostname_example": "e.g. example.ohc.network", "middleware_hostname_sourced_from": "Middleware hostname sourced from {{ source }}", + "mild": "Mild", "min_char_length_error": "Must be at least {{ min_length }} characters", "min_password_len_8": "Minimum password length 8", "min_time_bw_doses": "Min. time b/w doses", @@ -1349,6 +1357,7 @@ "mobile_otp_send_success": "OTP has been sent to the given mobile number.", "mobile_otp_verify_error": "Failed to verify mobile number. Please try again later.", "mobile_otp_verify_success": "Mobile number has been verified successfully.", + "moderate": "Moderate", "modification_caution_note": "No modifications possible once added", "modified": "Modified", "modified_date": "Modified Date", @@ -1730,6 +1739,7 @@ "record_has_been_deleted_successfully": "Record has been deleted successfully.", "record_updates": "Record Updates", "recording": "Recording", + "recurrence": "Recurrence", "redirected_to_create_consultation": "Note: You will be redirected to create consultation form. Please complete the form to finish the transfer process", "reference_no": "Reference No", "referral_letter": "Referral Letter", @@ -1742,13 +1752,17 @@ "register_patient": "Register Patient", "reject": "Reject", "rejected": "Rejected", + "relapse": "Relapse", "related_person": "Related Person", "reload": "Reload", "remarks": "Remarks", "remarks_placeholder": "Enter remarks", + "remission": "Remission", "remove": "Remove", + "remove_diagnosis": "Remove Diagnosis", "remove_medication": "Remove Medication", "remove_medication_confirmation": "Are you sure you want to remove {{medication}}?", + "remove_symptom": "Remove Symptom", "remove_user": "Remove User", "remove_user_organization": "Remove User from Organization", "remove_user_warn": "Are you sure you want to remove {{firstName}} {{lastName}} from this organization? This action cannot be undone.", @@ -1789,6 +1803,7 @@ "reset": "Reset", "reset_password": "Reset Password", "reset_password_note_self": "Enter your current password, then create and confirm your new password", + "resolved": "Resolved", "resource": "Resource", "resource_approving_facility": "Resource approving facility", "resource_created_successfully": "Request created successfully", @@ -1870,7 +1885,9 @@ "search_by_phone_number": "Search by Phone Number", "search_by_username": "Search by username", "search_encounters": "Search Encounters", + "search_for_diagnoses_to_add": "Search for diagnoses to add", "search_for_facility": "Search for Facility", + "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", @@ -1958,6 +1975,7 @@ "set_home_facility": "Set as home facility", "set_your_local_language": "Set your local language", "settings_and_filters": "Settings and Filters", + "severe": "Severe", "severity": "Severity", "severity_of_breathlessness": "Severity of Breathlessness", "sex": "Sex", @@ -2094,6 +2112,7 @@ "type_to_search": "Type to search", "type_your_comment": "Type your comment", "type_your_reason_here": "Type your reason here", + "unable_to_assess": "Unable to Assess", "unable_to_get_current_position": "Unable to get current position.", "unable_to_get_location: ": "Unable to get location: ", "unassign": "Unassign", @@ -2245,6 +2264,7 @@ "video_conference_link": "Video Conference Link", "view": "View", "view_abdm_records": "View ABDM Records", + "view_all": "view all", "view_all_details": "View All Details", "view_and_manage_patient_encounters": "View and manage patient encounters", "view_asset": "View Assets", diff --git a/public/locale/hi.json b/public/locale/hi.json index 3d70a55ee82..13a9b43e422 100644 --- a/public/locale/hi.json +++ b/public/locale/hi.json @@ -342,7 +342,7 @@ "encounter_suggestion_edit_disallowed": "संपादन परामर्श में इस विकल्प पर स्विच करने की अनुमति नहीं है", "enter_file_name": "फ़ाइल का नाम दर्ज करें", "enter_valid_age": "कृपया वैध आयु दर्ज करें", - "entered-in-error": "त्रुटिवश प्रविष्ट हुआ", + "entered_in_error": "त्रुटिवश प्रविष्ट हुआ", "error_404": "त्रुटि 404", "error_deleting_shifting": "शिफ्टिंग रिकॉर्ड हटाते समय त्रुटि", "error_while_deleting_record": "रिकॉर्ड हटाते समय त्रुटि हुई", @@ -392,7 +392,7 @@ "goal": "हमारा लक्ष्य डिजिटल उपकरणों का उपयोग करके सार्वजनिक स्वास्थ्य सेवाओं की गुणवत्ता और पहुंच में निरंतर सुधार करना है।", "help_confirmed": "इस स्थिति को पुष्ट मानने के लिए पर्याप्त नैदानिक और/या नैदानिक साक्ष्य मौजूद हैं।", "help_differential": "संभावित (और आमतौर पर परस्पर अनन्य) निदानों के एक समूह में से एक, जो निदान प्रक्रिया और प्रारंभिक उपचार को आगे बढ़ाने के लिए निर्देशित किया जाता है।", - "help_entered-in-error": "यह कथन गलती से दर्ज किया गया है और मान्य नहीं है।", + "help_entered_in_error": "यह कथन गलती से दर्ज किया गया है और मान्य नहीं है।", "help_provisional": "यह एक अस्थायी निदान है - अभी भी इस पर विचार किया जा रहा है।", "help_refuted": "बाद के निदानात्मक और नैदानिक साक्ष्यों से इस स्थिति को खारिज कर दिया गया है।", "help_unconfirmed": "इसे एक पुष्ट स्थिति मानने के लिए पर्याप्त नैदानिक और/या नैदानिक साक्ष्य उपलब्ध नहीं हैं।", diff --git a/public/locale/kn.json b/public/locale/kn.json index ee7d126a69f..7056c325648 100644 --- a/public/locale/kn.json +++ b/public/locale/kn.json @@ -344,7 +344,7 @@ "encounter_suggestion_edit_disallowed": "ಸಂಪಾದನೆ ಸಮಾಲೋಚನೆಯಲ್ಲಿ ಈ ಆಯ್ಕೆಗೆ ಬದಲಾಯಿಸಲು ಅನುಮತಿಸಲಾಗುವುದಿಲ್ಲ", "enter_file_name": "ಫೈಲ್ ಹೆಸರನ್ನು ನಮೂದಿಸಿ", "enter_valid_age": "ದಯವಿಟ್ಟು ಮಾನ್ಯವಾದ ವಯಸ್ಸನ್ನು ನಮೂದಿಸಿ", - "entered-in-error": "ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ", + "entered_in_error": "ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ", "error_404": "ದೋಷ 404", "error_deleting_shifting": "ಶಿಫ್ಟಿಂಗ್ ರೆಕಾರ್ಡ್ ಅನ್ನು ಅಳಿಸುವಾಗ ದೋಷ", "error_while_deleting_record": "ದಾಖಲೆಯನ್ನು ಅಳಿಸುವಾಗ ದೋಷ", @@ -394,7 +394,7 @@ "goal": "ಡಿಜಿಟಲ್ ಉಪಕರಣಗಳನ್ನು ಬಳಸಿಕೊಂಡು ಸಾರ್ವಜನಿಕ ಆರೋಗ್ಯ ಸೇವೆಗಳ ಗುಣಮಟ್ಟ ಮತ್ತು ಪ್ರವೇಶವನ್ನು ನಿರಂತರವಾಗಿ ಸುಧಾರಿಸುವುದು ನಮ್ಮ ಗುರಿಯಾಗಿದೆ", "help_confirmed": "ಇದನ್ನು ದೃಢಪಡಿಸಿದ ಸ್ಥಿತಿ ಎಂದು ಪರಿಗಣಿಸಲು ಸಾಕಷ್ಟು ರೋಗನಿರ್ಣಯ ಮತ್ತು/ಅಥವಾ ಕ್ಲಿನಿಕಲ್ ಪುರಾವೆಗಳಿವೆ.", "help_differential": "ರೋಗನಿರ್ಣಯ ಪ್ರಕ್ರಿಯೆ ಮತ್ತು ಪ್ರಾಥಮಿಕ ಚಿಕಿತ್ಸೆಯನ್ನು ಮತ್ತಷ್ಟು ಮಾರ್ಗದರ್ಶನ ಮಾಡಲು ಸಮರ್ಥಿಸಲಾದ ಸಂಭಾವ್ಯ (ಮತ್ತು ಸಾಮಾನ್ಯವಾಗಿ ಪರಸ್ಪರ ಪ್ರತ್ಯೇಕವಾದ) ರೋಗನಿರ್ಣಯಗಳ ಒಂದು ಸೆಟ್.", - "help_entered-in-error": "ಹೇಳಿಕೆಯನ್ನು ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ ಮತ್ತು ಮಾನ್ಯವಾಗಿಲ್ಲ.", + "help_entered_in_error": "ಹೇಳಿಕೆಯನ್ನು ತಪ್ಪಾಗಿ ನಮೂದಿಸಲಾಗಿದೆ ಮತ್ತು ಮಾನ್ಯವಾಗಿಲ್ಲ.", "help_provisional": "ಇದು ತಾತ್ಕಾಲಿಕ ರೋಗನಿರ್ಣಯ - ಇನ್ನೂ ಪರಿಗಣನೆಯಲ್ಲಿರುವ ಅಭ್ಯರ್ಥಿ.", "help_refuted": "ನಂತರದ ರೋಗನಿರ್ಣಯ ಮತ್ತು ಕ್ಲಿನಿಕಲ್ ಪುರಾವೆಗಳಿಂದ ಈ ಸ್ಥಿತಿಯನ್ನು ತಳ್ಳಿಹಾಕಲಾಗಿದೆ.", "help_unconfirmed": "ಇದನ್ನು ದೃಢಪಡಿಸಿದ ಸ್ಥಿತಿ ಎಂದು ಪರಿಗಣಿಸಲು ಸಾಕಷ್ಟು ರೋಗನಿರ್ಣಯ ಮತ್ತು/ಅಥವಾ ವೈದ್ಯಕೀಯ ಪುರಾವೆಗಳಿಲ್ಲ.", diff --git a/public/locale/ml.json b/public/locale/ml.json index 37b1c5eb29b..0447dc62c21 100644 --- a/public/locale/ml.json +++ b/public/locale/ml.json @@ -901,7 +901,7 @@ "enter_valid_dob": "ഒരു സാധുവായ ജനനത്തീയതി നൽകുക", "enter_valid_dob_age": "ദയവായി 15 വയസ്സിൽ കൂടുതലുള്ള ഒരു പ്രായം നൽകുക", "enter_year_of_birth_to_verify": "സ്ഥിരീകരിക്കാൻ ജനന വർഷം നൽകുക", - "entered-in-error": "തെറ്റായി നൽകി", + "entered_in_error": "തെറ്റായി നൽകി", "entered_in_error": "പിശകിൽ പ്രവേശിച്ചു", "error_404": "പിശക് 404", "error_deleting_shifting": "ഷിഫ്റ്റിംഗ് റെക്കോർഡ് ഇല്ലാതാക്കുന്നതിൽ പിശക്", @@ -1015,7 +1015,7 @@ "hearing": "ഞങ്ങൾ പറയുന്നത് കേൾക്കുന്നു...", "help_confirmed": "ഇത് ഒരു സ്ഥിരീകരിച്ച അവസ്ഥയായി കണക്കാക്കാൻ മതിയായ ഡയഗ്നോസ്റ്റിക് കൂടാതെ/അല്ലെങ്കിൽ ക്ലിനിക്കൽ തെളിവുകൾ ഉണ്ട്.", "help_differential": "രോഗനിർണ്ണയ പ്രക്രിയയ്ക്കും പ്രാഥമിക ചികിത്സയ്ക്കും കൂടുതൽ മാർഗ്ഗനിർദ്ദേശം നൽകുന്നതിന് സാധ്യതയുള്ള (സാധാരണയായി പരസ്പരവിരുദ്ധമായ) രോഗനിർണയങ്ങളിൽ ഒന്ന്.", - "help_entered-in-error": "പ്രസ്‌താവന തെറ്റായി നൽകിയതിനാൽ സാധുതയില്ല.", + "help_entered_in_error": "പ്രസ്‌താവന തെറ്റായി നൽകിയതിനാൽ സാധുതയില്ല.", "help_provisional": "ഇതൊരു താൽക്കാലിക രോഗനിർണയമാണ് - ഇപ്പോഴും പരിഗണനയിലിരിക്കുന്ന ഒരു സ്ഥാനാർത്ഥി.", "help_refuted": "തുടർന്നുള്ള ഡയഗ്നോസ്റ്റിക്, ക്ലിനിക്കൽ തെളിവുകൾ വഴി ഈ അവസ്ഥ ഒഴിവാക്കിയിട്ടുണ്ട്.", "help_unconfirmed": "ഇത് ഒരു സ്ഥിരീകരിച്ച അവസ്ഥയായി കണക്കാക്കാൻ മതിയായ ഡയഗ്നോസ്റ്റിക് കൂടാതെ/അല്ലെങ്കിൽ ക്ലിനിക്കൽ തെളിവുകൾ ഇല്ല.", diff --git a/public/locale/ta.json b/public/locale/ta.json index b077e77a1da..f792cb7cefd 100644 --- a/public/locale/ta.json +++ b/public/locale/ta.json @@ -343,7 +343,7 @@ "encounter_suggestion_edit_disallowed": "திருத்த ஆலோசனையில் இந்த விருப்பத்திற்கு மாற அனுமதிக்கப்படவில்லை", "enter_file_name": "கோப்பு பெயரை உள்ளிடவும்", "enter_valid_age": "செல்லுபடியாகும் வயதை உள்ளிடவும்", - "entered-in-error": "தவறுதலாக உள்ளிடப்பட்டது", + "entered_in_error": "தவறுதலாக உள்ளிடப்பட்டது", "error_404": "பிழை 404", "error_deleting_shifting": "பதிவை மாற்றுவதில் பிழை", "error_while_deleting_record": "பதிவை நீக்குவதில் பிழை", @@ -393,7 +393,7 @@ "goal": "டிஜிட்டல் கருவிகளைப் பயன்படுத்தி பொது சுகாதார சேவைகளின் தரம் மற்றும் அணுகல்தன்மையை தொடர்ந்து மேம்படுத்துவதே எங்கள் குறிக்கோள்.", "help_confirmed": "இதை உறுதிப்படுத்தப்பட்ட நிலையாகக் கருதுவதற்கு போதுமான நோயறிதல் மற்றும்/அல்லது மருத்துவ சான்றுகள் உள்ளன.", "help_differential": "சாத்தியமான (மற்றும் பொதுவாக பரஸ்பரம் பிரத்தியேகமான) நோயறிதல்களின் தொகுப்பில் ஒன்று கண்டறியும் செயல்முறை மற்றும் பூர்வாங்க சிகிச்சையை மேலும் வழிகாட்டும்.", - "help_entered-in-error": "அறிக்கை பிழையாக உள்ளிடப்பட்டது, அது செல்லாது.", + "help_entered_in_error": "அறிக்கை பிழையாக உள்ளிடப்பட்டது, அது செல்லாது.", "help_provisional": "இது ஒரு தற்காலிக நோயறிதல் - இன்னும் ஒரு வேட்பாளர் பரிசீலனையில் உள்ளது.", "help_refuted": "இந்த நிலை அடுத்தடுத்த நோயறிதல் மற்றும் மருத்துவ சான்றுகளால் நிராகரிக்கப்பட்டது.", "help_unconfirmed": "இதை உறுதிப்படுத்தப்பட்ட நிலையாகக் கருதுவதற்கு போதுமான நோயறிதல் மற்றும்/அல்லது மருத்துவ சான்றுகள் இல்லை.", diff --git a/src/Utils/request/api.tsx b/src/Utils/request/api.tsx index 0288e107edf..0e3aab2f217 100644 --- a/src/Utils/request/api.tsx +++ b/src/Utils/request/api.tsx @@ -20,7 +20,6 @@ import { AppointmentPatientRegister, } from "@/pages/Patient/Utils"; import { Encounter, EncounterEditRequest } from "@/types/emr/encounter"; -import { MedicationRequestRead } from "@/types/emr/medicationRequest"; import { MedicationStatement } from "@/types/emr/medicationStatement"; import { PartialPatientModel, Patient } from "@/types/emr/newPatient"; import { @@ -646,15 +645,6 @@ const routes = { }, }, - // Medication - medicationRequest: { - list: { - path: "/api/v1/patient/{patientId}/medication/request/", - method: "GET", - TRes: Type>(), - }, - }, - medicationStatement: { list: { path: "/api/v1/patient/{patientId}/medication/statement/", diff --git a/src/components/Common/ComboboxQuantityInput.tsx b/src/components/Common/ComboboxQuantityInput.tsx index db7b8caa4d2..19674422d81 100644 --- a/src/components/Common/ComboboxQuantityInput.tsx +++ b/src/components/Common/ComboboxQuantityInput.tsx @@ -50,6 +50,7 @@ export function ComboboxQuantityInput({ const showDropdown = /^\d+$/.test(inputValue); const handleInputChange = (e: React.ChangeEvent) => { + if (disabled) return; const value = e.target.value; if (value === "" || /^\d+$/.test(value)) { setInputValue(value); @@ -65,7 +66,7 @@ export function ComboboxQuantityInput({ }; const handleKeyDown = (e: React.KeyboardEvent) => { - if (!showDropdown) return; + if (disabled || !showDropdown) return; if (e.key === "ArrowDown") { e.preventDefault(); @@ -102,7 +103,7 @@ export function ComboboxQuantityInput({ return (
- +
= EncounterContextBase & - T & { - setValue: ( - key: K, - value: (EncounterContextBase & T)[K], - ) => void; - }; - -const EncounterContext = createContext< - EncounterContextType | undefined ->(undefined); - -export const useEncounter = () => { - const context = useContext(EncounterContext); - - if (!context) { - throw new Error( - "'useEncounter' must be used within 'EncounterProvider' only", - ); - } - - return context as EncounterContextType; -}; - -interface EncounterProviderProps { - children: ReactNode; - initialContext?: Partial; -} - -export const EncounterProvider = ({ - children, - initialContext = {}, -}: EncounterProviderProps) => { - const [state, setState] = useState( - initialContext as EncounterContextBase & T, - ); - - const setValue = ( - key: K, - value: (EncounterContextBase & T)[K], - ) => { - setState((prevState) => ({ - ...prevState, - [key]: value, - })); - }; - - return ( - - } - > - {children} - - ); -}; diff --git a/src/components/Medicine/MedicineAdministrationSheet/index.tsx b/src/components/Medicine/MedicationRequestTable/index.tsx similarity index 84% rename from src/components/Medicine/MedicineAdministrationSheet/index.tsx rename to src/components/Medicine/MedicationRequestTable/index.tsx index 652ccc20605..2d9309abc81 100644 --- a/src/components/Medicine/MedicineAdministrationSheet/index.tsx +++ b/src/components/Medicine/MedicationRequestTable/index.tsx @@ -1,3 +1,5 @@ +import { useQuery } from "@tanstack/react-query"; +import { PencilIcon } from "lucide-react"; import { Link } from "raviger"; import { useState } from "react"; @@ -10,35 +12,34 @@ import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import Loading from "@/components/Common/Loading"; -import { useEncounter } from "@/components/Facility/ConsultationDetails/EncounterContext"; import { MedicationsTable } from "@/components/Medicine/MedicationsTable"; -import useSlug from "@/hooks/useSlug"; - -import routes from "@/Utils/request/api"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import query from "@/Utils/request/query"; import { MedicationRequestRead } from "@/types/emr/medicationRequest"; +import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; interface Props { readonly?: boolean; facilityId: string; + patientId: string; + encounterId: string; } -const MedicineAdministrationSheet = ({ facilityId }: Props) => { - const encounterId = useSlug("encounter"); - const { patient } = useEncounter(); +export default function MedicationRequestTable({ + facilityId, + patientId, + encounterId, +}: Props) { const [searchQuery, setSearchQuery] = useState(""); - const { data: medications, loading } = useTanStackQueryInstead( - routes.medicationRequest.list, - { - pathParams: { patientId: patient!.id }, - query: { - encounter: encounterId, - limit: 100, - }, - }, - ); + const { data: medications, isLoading: loading } = useQuery({ + queryKey: ["medication_requests", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId: patientId }, + queryParams: { encounter: encounterId }, + }), + enabled: !!patientId, + }); const filteredMedications = medications?.results?.filter( (med: MedicationRequestRead) => { @@ -82,6 +83,14 @@ const MedicineAdministrationSheet = ({ facilityId }: Props) => { title="Prescriptions" options={
+
); -}; - -export default MedicineAdministrationSheet; +} diff --git a/src/components/Medicine/MedicineAdministrationSheet/utils.ts b/src/components/Medicine/MedicationRequestTable/utils.ts similarity index 100% rename from src/components/Medicine/MedicineAdministrationSheet/utils.ts rename to src/components/Medicine/MedicationRequestTable/utils.ts diff --git a/src/components/Patient/MedicationStatementList.tsx b/src/components/Patient/MedicationStatementList.tsx index 19610079e8d..9d2549eb3ba 100644 --- a/src/components/Patient/MedicationStatementList.tsx +++ b/src/components/Patient/MedicationStatementList.tsx @@ -1,9 +1,9 @@ import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; -import { cn } from "@/lib/utils"; - import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -28,13 +28,87 @@ interface MedicationStatementListProps { patientId: string; } +interface MedicationRowProps { + statement: any; + isEnteredInError?: boolean; + index: number; +} + +function MedicationRow({ + statement, + isEnteredInError, + index, +}: MedicationRowProps) { + return ( + + + + +

{statement.medication.display ?? statement.medication.code}

+
+ +

{statement.medication.display ?? statement.medication.code}

+
+
+
+ + + +

{statement.dosage_text}

+
+ +

{statement.dosage_text}

+
+
+
+ + + {statement.status} + + + + {[statement.effective_period?.start, statement.effective_period?.end] + .map((date) => formatDateTime(date)) + .join(" - ")} + + + + +

{statement.reason}

+
+ +

{statement.reason}

+
+
+
+ + + +

{statement.note}

+
+ +

{statement.note}

+
+
+
+
+ ); +} + export function MedicationStatementList({ patientId, }: MedicationStatementListProps) { const { t } = useTranslation(); + const [showEnteredInError, setShowEnteredInError] = useState(false); const { data: medications, isLoading } = useQuery({ - queryKey: ["medication_statement", patientId], + queryKey: ["medication_statements", patientId], queryFn: query(routes.medicationStatement.list, { pathParams: { patientId }, }), @@ -53,7 +127,16 @@ export function MedicationStatementList({ ); } - if (!medications?.results?.length) { + const filteredMedications = medications?.results?.filter( + (medication) => + showEnteredInError || medication.status !== "entered_in_error", + ); + + const hasEnteredInErrorRecords = medications?.results?.some( + (medication) => medication.status === "entered_in_error", + ); + + if (!filteredMedications?.length) { return ( @@ -66,22 +149,11 @@ export function MedicationStatementList({ ); } - const getStatusBadgeStyle = (status: string | undefined) => { - switch (status?.toLowerCase()) { - case "active": - return "bg-green-100 text-green-800 border-green-200"; - case "on_hold": - return "bg-gray-100 text-gray-800 border-blue-200"; - default: - return "bg-gray-100 text-gray-800 border-gray-200"; - } - }; - return ( - {t("ongoing_medications")} ({medications.count}) + {t("ongoing_medications")} ({filteredMedications.length}) @@ -97,77 +169,34 @@ export function MedicationStatementList({ - {medications.results.map((statement, index) => ( - - - - -

- {statement.medication.display ?? - statement.medication.code} -

-
- -

- {statement.medication.display ?? - statement.medication.code} -

-
-
-
- - - -

{statement.dosage_text}

-
- -

{statement.dosage_text}

-
-
-
- - - {statement.status} - - - - {[ - statement.effective_period?.start, - statement.effective_period?.end, - ] - .map((date) => formatDateTime(date)) - .join(" - ")} - - - - -

{statement.reason ?? "-"}

-
- -

{statement.reason ?? "Not Specified"}

-
-
-
- - - -

{statement.note ?? "-"}

-
- -

{statement.note ?? "Not Specified"}

-
-
-
-
- ))} + {filteredMedications.map((statement, index) => { + const isEnteredInError = statement.status === "entered_in_error"; + + return ( + <> + + + ); + })}
+ {hasEnteredInErrorRecords && !showEnteredInError && ( +
+ +
+ )}
); diff --git a/src/components/Patient/allergy/list.tsx b/src/components/Patient/allergy/list.tsx index f03788afd24..8e1a9686a90 100644 --- a/src/components/Patient/allergy/list.tsx +++ b/src/components/Patient/allergy/list.tsx @@ -1,7 +1,11 @@ import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; +import { Link } from "raviger"; +import { ReactNode, useState } from "react"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -16,16 +20,22 @@ import { import { Avatar } from "@/components/Common/Avatar"; import query from "@/Utils/request/query"; -import { formatName } from "@/Utils/utils"; import { AllergyIntolerance } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; interface AllergyListProps { + facilityId?: string; patientId: string; encounterId?: string; } -export function AllergyList({ patientId, encounterId }: AllergyListProps) { +export function AllergyList({ + facilityId, + patientId, + encounterId, +}: AllergyListProps) { + const [showEnteredInError, setShowEnteredInError] = useState(false); + const { data: allergies, isLoading } = useQuery({ queryKey: ["allergies", patientId, encounterId], queryFn: query(allergyIntoleranceApi.getAllergy, { @@ -36,27 +46,38 @@ export function AllergyList({ patientId, encounterId }: AllergyListProps) { if (isLoading) { return ( - - - {t("allergies")} - - + + - + ); } - if (!allergies?.results?.length) { + const filteredAllergies = allergies?.results?.filter( + (allergy) => + showEnteredInError || allergy.verification_status !== "entered_in_error", + ); + + const hasEnteredInErrorRecords = allergies?.results?.some( + (allergy) => allergy.verification_status === "entered_in_error", + ); + + if (!filteredAllergies?.length) { return ( - - - {t("allergies")} - - + +

{t("no_allergies_recorded")}

-
+ ); } @@ -84,68 +105,146 @@ export function AllergyList({ patientId, encounterId }: AllergyListProps) { } }; + interface AllergyRowProps { + allergy: AllergyIntolerance; + isEnteredInError?: boolean; + } + + function AllergyRow({ allergy, isEnteredInError }: AllergyRowProps) { + return ( + + {allergy.code.display} + + + {t(allergy.category)} + + + + + {t(allergy.clinical_status)} + + + + + {t(allergy.criticality)} + + + + + {t(allergy.verification_status)} + + + + + {allergy.created_by.username} + + + ); + } + return ( - - + + + + + {t("allergen")} + {t("category")} + {t("status")} + {t("criticality")} + {t("verification")} + {t("created_by")} + + + + {/* Valid entries */} + {filteredAllergies + .filter( + (allergy) => allergy.verification_status !== "entered_in_error", + ) + .map((allergy) => ( + + ))} + + {/* Entered in error entries */} + {showEnteredInError && + filteredAllergies + .filter( + (allergy) => allergy.verification_status === "entered_in_error", + ) + .map((allergy) => ( + + ))} + +
+ {hasEnteredInErrorRecords && !showEnteredInError && ( +
+ +
+ )} +
+ ); +} + +const AllergyListLayout = ({ + facilityId, + patientId, + encounterId, + children, +}: { + facilityId?: string; + patientId: string; + encounterId?: string; + children: ReactNode; +}) => { + return ( + + {t("allergies")} + {facilityId && encounterId && ( + + + {t("edit")} + + )} - - - - - {t("allergen")} - {t("category")} - {t("status")} - {t("criticality")} - {t("created_by")} - - - - {allergies.results.map((allergy: AllergyIntolerance) => ( - - - {allergy.code.display} - - - - {allergy.category} - - - - - {allergy.clinical_status} - - - - - {allergy.criticality} - - - - - - {formatName(allergy.created_by)} - - - - ))} - -
-
+ {children}
); -} +}; diff --git a/src/components/Patient/diagnosis/DiagnosisTable.tsx b/src/components/Patient/diagnosis/DiagnosisTable.tsx index 4646c028a9e..d9134f6d0c8 100644 --- a/src/components/Patient/diagnosis/DiagnosisTable.tsx +++ b/src/components/Patient/diagnosis/DiagnosisTable.tsx @@ -54,46 +54,61 @@ export function DiagnosisTable({ )} - {diagnoses.map((diagnosis: Diagnosis, index) => ( - - - {diagnosis.code.display} - - - { + const isEnteredInError = + diagnosis.verification_status === "entered_in_error"; + + return ( + <> + - {diagnosis.clinical_status} - - - - - {diagnosis.verification_status} - - - - {diagnosis.onset?.onset_datetime - ? new Date(diagnosis.onset.onset_datetime).toLocaleDateString() - : "-"} - - - {diagnosis.note || "-"} - - -
- - - {formatName(diagnosis.created_by)} - -
-
-
- ))} + + {diagnosis.code.display} + + + + {t(diagnosis.clinical_status)} + + + + + {t(diagnosis.verification_status)} + + + + {diagnosis.onset?.onset_datetime + ? new Date( + diagnosis.onset.onset_datetime, + ).toLocaleDateString() + : "-"} + + + {diagnosis.note || "-"} + + + + + {formatName(diagnosis.created_by)} + + + + + ); + })}
); diff --git a/src/components/Patient/diagnosis/list.tsx b/src/components/Patient/diagnosis/list.tsx index 0df139f098f..d0e8c4acede 100644 --- a/src/components/Patient/diagnosis/list.tsx +++ b/src/components/Patient/diagnosis/list.tsx @@ -1,5 +1,10 @@ import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; +import { Link } from "raviger"; +import { ReactNode, useState } from "react"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; @@ -11,9 +16,16 @@ import { DiagnosisTable } from "./DiagnosisTable"; interface DiagnosisListProps { patientId: string; encounterId?: string; + facilityId?: string; } -export function DiagnosisList({ patientId, encounterId }: DiagnosisListProps) { +export function DiagnosisList({ + patientId, + encounterId, + facilityId, +}: DiagnosisListProps) { + const [showEnteredInError, setShowEnteredInError] = useState(false); + const { data: diagnoses, isLoading } = useQuery({ queryKey: ["diagnosis", patientId, encounterId], queryFn: query(diagnosisApi.listDiagnosis, { @@ -24,38 +36,100 @@ export function DiagnosisList({ patientId, encounterId }: DiagnosisListProps) { if (isLoading) { return ( - - - Diagnoses - - - - - + + + ); } - if (!diagnoses?.results?.length) { + const filteredDiagnoses = diagnoses?.results?.filter( + (diagnosis) => + showEnteredInError || + diagnosis.verification_status !== "entered_in_error", + ); + + const hasEnteredInErrorRecords = diagnoses?.results?.some( + (diagnosis) => diagnosis.verification_status === "entered_in_error", + ); + + if (!filteredDiagnoses?.length) { return ( - - - Diagnoses - - -

No diagnoses recorded

-
-
+ +

{t("no_diagnoses_recorded")}

+
); } return ( - - - Diagnoses + + diagnosis.verification_status !== "entered_in_error", + ), + ...(showEnteredInError + ? filteredDiagnoses.filter( + (diagnosis) => + diagnosis.verification_status === "entered_in_error", + ) + : []), + ]} + /> + + {hasEnteredInErrorRecords && !showEnteredInError && ( +
+ +
+ )} +
+ ); +} + +const DiagnosisListLayout = ({ + facilityId, + patientId, + encounterId, + children, +}: { + facilityId?: string; + patientId: string; + encounterId?: string; + children: ReactNode; +}) => { + return ( + + + {t("diagnoses")} + {facilityId && encounterId && ( + + + {t("edit")} + + )} - - - + {children} ); -} +}; diff --git a/src/components/Patient/symptoms/SymptomTable.tsx b/src/components/Patient/symptoms/SymptomTable.tsx index c9c10266368..b95e518d99d 100644 --- a/src/components/Patient/symptoms/SymptomTable.tsx +++ b/src/components/Patient/symptoms/SymptomTable.tsx @@ -48,52 +48,73 @@ export function SymptomTable({ {t("status")} {t("severity")} {t("onset")} + {t("verification")} {t("notes")} {t("created_by")} )} - {symptoms.map((symptom: Symptom, index: number) => ( - - - {symptom.code.display} - - - { + const isEnteredInError = + symptom.verification_status === "entered_in_error"; + + return ( + <> + - {symptom.clinical_status} - - - - - {symptom.severity} - - - - {symptom.onset?.onset_datetime - ? new Date(symptom.onset.onset_datetime).toLocaleDateString() - : "-"} - - - {symptom.note || "-"} - - -
- - - {formatName(symptom.created_by)} - -
-
-
- ))} + + {symptom.code.display} + + + + {t(symptom.clinical_status)} + + + + + {t(symptom.severity)} + + + + {symptom.onset?.onset_datetime + ? new Date( + symptom.onset.onset_datetime, + ).toLocaleDateString() + : "-"} + + + + {t(symptom.verification_status)} + + + + {symptom.note || "-"} + + + + + {formatName(symptom.created_by)} + + + + + ); + })}
); diff --git a/src/components/Patient/symptoms/list.tsx b/src/components/Patient/symptoms/list.tsx index 20914788f00..24f7a8142c8 100644 --- a/src/components/Patient/symptoms/list.tsx +++ b/src/components/Patient/symptoms/list.tsx @@ -1,5 +1,10 @@ import { useQuery } from "@tanstack/react-query"; +import { t } from "i18next"; +import { PencilIcon } from "lucide-react"; +import { Link } from "raviger"; +import { ReactNode, useState } from "react"; +import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; @@ -11,9 +16,16 @@ import { SymptomTable } from "./SymptomTable"; interface SymptomsListProps { patientId: string; encounterId?: string; + facilityId?: string; } -export function SymptomsList({ patientId, encounterId }: SymptomsListProps) { +export function SymptomsList({ + patientId, + encounterId, + facilityId, +}: SymptomsListProps) { + const [showEnteredInError, setShowEnteredInError] = useState(false); + const { data: symptoms, isLoading } = useQuery({ queryKey: ["symptoms", patientId, encounterId], queryFn: query(symptomApi.listSymptoms, { @@ -24,38 +36,98 @@ export function SymptomsList({ patientId, encounterId }: SymptomsListProps) { if (isLoading) { return ( - - - Symptoms - - - - - + + + ); } - if (!symptoms?.results?.length) { + const filteredSymptoms = symptoms?.results?.filter( + (symptom) => + showEnteredInError || symptom.verification_status !== "entered_in_error", + ); + + const hasEnteredInErrorRecords = symptoms?.results?.some( + (symptom) => symptom.verification_status === "entered_in_error", + ); + + if (!filteredSymptoms?.length) { return ( - - - Symptoms - - -

No symptoms recorded

-
-
+ +

{t("no_symptoms_recorded")}

+
); } return ( - - - Symptoms + + symptom.verification_status !== "entered_in_error", + ), + ...(showEnteredInError + ? filteredSymptoms.filter( + (symptom) => symptom.verification_status === "entered_in_error", + ) + : []), + ]} + /> + + {hasEnteredInErrorRecords && !showEnteredInError && ( +
+ +
+ )} +
+ ); +} + +const SymptomListLayout = ({ + facilityId, + patientId, + encounterId, + children, +}: { + facilityId?: string; + patientId: string; + encounterId?: string; + children: ReactNode; +}) => { + return ( + + + {t("symptoms")} + {facilityId && ( + + + {t("edit")} + + )} - - - + {children} ); -} +}; diff --git a/src/components/Questionnaire/QuestionRenderer.tsx b/src/components/Questionnaire/QuestionRenderer.tsx index b4f5168c60e..284e1cf616f 100644 --- a/src/components/Questionnaire/QuestionRenderer.tsx +++ b/src/components/Questionnaire/QuestionRenderer.tsx @@ -3,7 +3,10 @@ import { useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; import { QuestionValidationError } from "@/types/questionnaire/batch"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import { Question, StructuredQuestionType, @@ -20,7 +23,7 @@ const FULL_WIDTH_QUESTION_TYPES: StructuredQuestionType[] = [ interface QuestionRendererProps { questions: Question[]; responses: QuestionnaireResponse[]; - onResponseChange: (responses: QuestionnaireResponse[]) => void; + onResponseChange: (values: ResponseValue[], questionId: string) => void; errors: QuestionValidationError[]; clearError: (questionId: string) => void; disabled?: boolean; @@ -53,19 +56,6 @@ export function QuestionRenderer({ } }, [activeGroupId]); - const handleResponseChange = (updatedResponse: QuestionnaireResponse) => { - const newResponses = [...responses]; - const index = newResponses.findIndex( - (r) => r.question_id === updatedResponse.question_id, - ); - if (index !== -1) { - newResponses[index] = updatedResponse; - } else { - newResponses.push(updatedResponse); - } - onResponseChange(newResponses); - }; - const shouldBeFullWidth = (question: Question): boolean => question.type === "structured" && !!question.structured_type && @@ -86,7 +76,7 @@ export function QuestionRenderer({ question={question} encounterId={encounterId} questionnaireResponses={responses} - updateQuestionnaireResponseCB={handleResponseChange} + updateQuestionnaireResponseCB={onResponseChange} errors={errors} clearError={clearError} disabled={disabled} diff --git a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx index 6e8364937f4..8270ae98c71 100644 --- a/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AllergyQuestion.tsx @@ -15,7 +15,6 @@ import { LeafIcon, } from "lucide-react"; import React, { useEffect, useState } from "react"; -import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { @@ -47,19 +46,28 @@ import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; import query from "@/Utils/request/query"; import { + ALLERGY_VERIFICATION_STATUS, AllergyIntolerance, AllergyIntoleranceRequest, + AllergyVerificationStatus, } from "@/types/emr/allergyIntolerance/allergyIntolerance"; import allergyIntoleranceApi from "@/types/emr/allergyIntolerance/allergyIntoleranceApi"; import { Code } from "@/types/questionnaire/code"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; interface AllergyQuestionProps { patientId: string; question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -117,25 +125,23 @@ export function AllergyQuestion({ queryKey: ["allergies", patientId], queryFn: query(allergyIntoleranceApi.getAllergy, { pathParams: { patientId }, + queryParams: { + limit: 100, + }, }), }); useEffect(() => { - if (patientAllergies?.results && !allergies.length) { - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + if (patientAllergies?.results) { + updateQuestionnaireResponseCB( + [ { type: "allergy_intolerance", value: patientAllergies.results.map(convertToAllergyRequest), }, ], - }); - if (patientAllergies.count > patientAllergies.results.length) { - toast.info( - `Showing first ${patientAllergies.results.length} of ${patientAllergies.count} allergies`, - ); - } + questionnaireResponse.question_id, + ); } }, [patientAllergies]); @@ -144,18 +150,43 @@ export function AllergyQuestion({ ...allergies, { ...ALLERGY_INITIAL_VALUE, code }, ] as AllergyIntoleranceRequest[]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "allergy_intolerance", value: newAllergies }], - }); + updateQuestionnaireResponseCB( + [{ type: "allergy_intolerance", value: newAllergies }], + questionnaireResponse.question_id, + ); }; const handleRemoveAllergy = (index: number) => { - const newAllergies = allergies.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "allergy_intolerance", value: newAllergies }], - }); + const allergy = allergies[index]; + if (allergy.id) { + // For existing records, update verification status to entered_in_error + const newAllergies = allergies.map((a, i) => + i === index + ? { ...a, verification_status: "entered_in_error" as const } + : a, + ) as AllergyIntoleranceRequest[]; + updateQuestionnaireResponseCB( + [ + { + type: "allergy_intolerance", + value: newAllergies, + }, + ], + questionnaireResponse.question_id, + ); + } else { + // For new records, remove them completely + const newAllergies = allergies.filter((_, i) => i !== index); + updateQuestionnaireResponseCB( + [ + { + type: "allergy_intolerance", + value: newAllergies, + }, + ], + questionnaireResponse.question_id, + ); + } }; const handleUpdateAllergy = ( @@ -165,10 +196,10 @@ export function AllergyQuestion({ const newAllergies = allergies.map((allergy, i) => i === index ? { ...allergy, ...updates } : allergy, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "allergy_intolerance", value: newAllergies }], - }); + updateQuestionnaireResponseCB( + [{ type: "allergy_intolerance", value: newAllergies }], + questionnaireResponse.question_id, + ); }; return ( @@ -212,11 +243,13 @@ export function AllergyQuestion({
@@ -226,7 +259,7 @@ export function AllergyQuestion({ onValueChange={(value) => handleUpdateAllergy(index, { category: value }) } - disabled={disabled} + disabled={disabled || !!allergy.id} > @@ -337,7 +370,7 @@ export function AllergyQuestion({ Low High - + Unable to Assess @@ -350,7 +383,8 @@ export function AllergyQuestion({ value={allergy.verification_status} onValueChange={(value) => handleUpdateAllergy(index, { - verification_status: value, + verification_status: + value as AllergyVerificationStatus, }) } disabled={disabled} @@ -359,9 +393,13 @@ export function AllergyQuestion({ - Confirmed - Unconfirmed - Refuted + {Object.entries(ALLERGY_VERIFICATION_STATUS).map( + ([value, label]) => ( + + {label} + + ), + )}
@@ -426,11 +464,13 @@ const AllergyTableRow = ({ const [showNotes, setShowNotes] = useState(allergy.note !== undefined); const rowClassName = `group ${ - allergy.clinical_status === "inactive" - ? "opacity-60" - : allergy.clinical_status === "resolved" - ? "line-through" - : "" + allergy.verification_status === "entered_in_error" + ? "opacity-40 pointer-events-none" + : allergy.clinical_status === "inactive" + ? "opacity-60" + : allergy.clinical_status === "resolved" + ? "line-through" + : "" }`; const handleNotesToggle = () => { @@ -450,7 +490,7 @@ const AllergyTableRow = ({ diff --git a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx index 916f97018e5..7f30558df4f 100644 --- a/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/AppointmentQuestion.tsx @@ -33,7 +33,11 @@ import { UserBase } from "@/types/user/user"; interface FollowUpVisitQuestionProps { question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -54,15 +58,16 @@ export function AppointmentQuestion({ const handleUpdate = (updates: Partial) => { const appointment = { ...value, ...updates }; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "appointment", value: [appointment] as unknown as ResponseValue["value"], }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; const facilityId = useSlug("facility"); diff --git a/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx b/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx index b2b094790b4..fb360302835 100644 --- a/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/BooleanQuestion.tsx @@ -1,13 +1,20 @@ import { Label } from "@/components/ui/label"; import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import type { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import type { Question } from "@/types/questionnaire/question"; interface BooleanQuestionProps { question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; clearError: () => void; } @@ -24,15 +31,16 @@ export function BooleanQuestion({ value={questionnaireResponse.values[0]?.value?.toString()} onValueChange={(value) => { clearError(); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "boolean", value: value === "true", }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }} disabled={disabled} > diff --git a/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx b/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx index 4c0f7259bef..95475f7acc5 100644 --- a/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/ChoiceQuestion.tsx @@ -9,14 +9,19 @@ import { } from "@/components/ui/select"; import { properCase } from "@/Utils/utils"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import type { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; import type { AnswerOption, Question } from "@/types/questionnaire/question"; interface ChoiceQuestionProps { question: Question; questionnaireResponse: QuestionnaireResponse; updateQuestionnaireResponseCB: ( - questionnaireResponse: QuestionnaireResponse, + values: ResponseValue[], + questionId: string, + note?: string, ) => void; disabled?: boolean; withLabel?: boolean; @@ -43,10 +48,11 @@ export const ChoiceQuestion = memo(function ChoiceQuestion({ value: newValue, }; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: newValues, - }); + updateQuestionnaireResponseCB( + newValues, + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; return ( diff --git a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx index b20629e171c..9fa2e201157 100644 --- a/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DateTimeQuestion.tsx @@ -13,11 +13,18 @@ import { PopoverTrigger, } from "@/components/ui/popover"; -import type { QuestionnaireResponse } from "@/types/questionnaire/form"; +import type { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; interface DateTimeQuestionProps { questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; clearError: () => void; classes?: string; @@ -43,15 +50,16 @@ export function DateTimeQuestion({ date.setMinutes(currentValue.getMinutes()); } - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ { type: "dateTime", value: date.toISOString(), }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; const handleTimeChange = (event: React.ChangeEvent) => { @@ -62,15 +70,17 @@ export function DateTimeQuestion({ date.setHours(hours); date.setMinutes(minutes); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ + updateQuestionnaireResponseCB( + [ + ...questionnaireResponse.values, { type: "dateTime", value: date.toISOString(), }, ], - }); + questionnaireResponse.question_id, + questionnaireResponse.note, + ); }; const formatTime = (date: Date | undefined) => { diff --git a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx index af85d52b152..b4aab2c07c4 100644 --- a/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/DiagnosisQuestion.tsx @@ -3,7 +3,12 @@ import { MinusCircledIcon, Pencil2Icon, } from "@radix-ui/react-icons"; -import React, { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import { format } from "date-fns"; +import { t } from "i18next"; +import React, { useEffect, useState } from "react"; + +import { cn } from "@/lib/utils"; import { Button } from "@/components/ui/button"; import { @@ -25,77 +30,168 @@ import { import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; +import query from "@/Utils/request/query"; import { DIAGNOSIS_CLINICAL_STATUS, DIAGNOSIS_VERIFICATION_STATUS, Diagnosis, + DiagnosisRequest, } from "@/types/emr/diagnosis/diagnosis"; +import diagnosisApi from "@/types/emr/diagnosis/diagnosisApi"; import { Code } from "@/types/questionnaire/code"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; interface DiagnosisQuestionProps { + patientId: string; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } -const DIAGNOSIS_INITIAL_VALUE: Partial = { +const DIAGNOSIS_INITIAL_VALUE: Omit = { code: { code: "", display: "", system: "" }, clinical_status: "active", verification_status: "confirmed", onset: { onset_datetime: new Date().toISOString().split("T")[0] }, }; +function convertToDiagnosisRequest(diagnosis: Diagnosis): DiagnosisRequest { + return { + id: diagnosis.id, + code: diagnosis.code, + clinical_status: diagnosis.clinical_status, + verification_status: diagnosis.verification_status, + onset: diagnosis.onset + ? { + ...diagnosis.onset, + onset_datetime: diagnosis.onset.onset_datetime + ? format(new Date(diagnosis.onset.onset_datetime), "yyyy-MM-dd") + : "", + } + : undefined, + recorded_date: diagnosis.recorded_date, + note: diagnosis.note, + encounter: "", // This will be set when submitting the form + }; +} + export function DiagnosisQuestion({ + patientId, questionnaireResponse, updateQuestionnaireResponseCB, disabled, }: DiagnosisQuestionProps) { const diagnoses = - (questionnaireResponse.values?.[0]?.value as Diagnosis[]) || []; + (questionnaireResponse.values?.[0]?.value as DiagnosisRequest[]) || []; + + const { data: patientDiagnoses } = useQuery({ + queryKey: ["diagnoses", patientId], + queryFn: query(diagnosisApi.listDiagnosis, { + pathParams: { patientId }, + queryParams: { + limit: 100, + }, + }), + }); + + useEffect(() => { + if (patientDiagnoses?.results) { + updateQuestionnaireResponseCB( + [ + { + type: "diagnosis", + value: patientDiagnoses.results.map(convertToDiagnosisRequest), + }, + ], + questionnaireResponse.question_id, + ); + } + }, [patientDiagnoses]); const handleAddDiagnosis = (code: Code) => { const newDiagnoses = [ ...diagnoses, { ...DIAGNOSIS_INITIAL_VALUE, code }, - ] as Diagnosis[]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "diagnosis", value: newDiagnoses }], - }); + ] as DiagnosisRequest[]; + updateQuestionnaireResponseCB( + [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], + questionnaireResponse.question_id, + ); }; const handleRemoveDiagnosis = (index: number) => { - const newDiagnoses = diagnoses.filter((_, i) => i !== index); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "diagnosis", value: newDiagnoses }], - }); + const diagnosis = diagnoses[index]; + if (diagnosis.id) { + // For existing records, update verification status to entered_in_error + const newDiagnoses = diagnoses.map((d, i) => + i === index + ? { ...d, verification_status: "entered_in_error" as const } + : d, + ) as DiagnosisRequest[]; + updateQuestionnaireResponseCB( + [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], + questionnaireResponse.question_id, + ); + } else { + // For new records, remove them completely + const newDiagnoses = diagnoses.filter((_, i) => i !== index); + updateQuestionnaireResponseCB( + [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], + questionnaireResponse.question_id, + ); + } }; const handleUpdateDiagnosis = ( index: number, - updates: Partial, + updates: Partial, ) => { const newDiagnoses = diagnoses.map((diagnosis, i) => i === index ? { ...diagnosis, ...updates } : diagnosis, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "diagnosis", value: newDiagnoses }], - }); + updateQuestionnaireResponseCB( + [ + { + type: "diagnosis", + value: newDiagnoses, + }, + ], + questionnaireResponse.question_id, + ); }; return ( - <> +
{diagnoses.length > 0 && (
-
Diagnosis
-
Onset Date
-
Status
-
Verification
-
Action
+
{t("diagnosis")}
+
{t("date")}
+
{t("status")}
+
{t("verification")}
+
{t("action")}
{diagnoses.map((diagnosis, index) => ( @@ -112,18 +208,18 @@ export function DiagnosisQuestion({ )} - +
); } interface DiagnosisItemProps { - diagnosis: Diagnosis; + diagnosis: DiagnosisRequest; disabled?: boolean; - onUpdate?: (diagnosis: Partial) => void; + onUpdate?: (diagnosis: Partial) => void; onRemove?: () => void; } @@ -133,10 +229,15 @@ const DiagnosisItem: React.FC = ({ onUpdate, onRemove, }) => { - const [showNotes, setShowNotes] = useState(false); + const [showNotes, setShowNotes] = useState(Boolean(diagnosis.note)); return ( -
+
= ({ setShowNotes(!showNotes)}> - {showNotes ? "Hide Notes" : "Add Notes"} + {showNotes ? t("hide_notes") : t("add_notes")} = ({ onClick={onRemove} > - Remove Diagnosis + {t("remove_diagnosis")} @@ -176,7 +277,9 @@ const DiagnosisItem: React.FC = ({
- + = ({ onset: { onset_datetime: e.target.value }, }) } - disabled={disabled} + disabled={disabled || !!diagnosis.id} className="h-8 md:h-9" />
- + onUpdate?.({ verification_status: - value as Diagnosis["verification_status"], + value as DiagnosisRequest["verification_status"], }) } disabled={disabled} > - + {DIAGNOSIS_VERIFICATION_STATUS.map((status) => ( @@ -240,7 +345,7 @@ const DiagnosisItem: React.FC = ({ value={status} className="capitalize" > - {status.replace(/_/g, " ")} + {t(status)} ))} @@ -262,7 +367,7 @@ const DiagnosisItem: React.FC = ({ setShowNotes(!showNotes)}> - {showNotes ? "Hide Notes" : "Add Notes"} + {showNotes ? t("hide_notes") : t("add_notes")} = ({ onClick={onRemove} > - Remove Diagnosis + {t("remove_diagnosis")} @@ -280,7 +385,7 @@ const DiagnosisItem: React.FC = ({
onUpdate?.({ note: e.target.value })} disabled={disabled} diff --git a/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx b/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx index 61cc44e4a95..315221b64de 100644 --- a/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/EncounterQuestion.tsx @@ -40,7 +40,11 @@ interface EncounterQuestionProps { question: Question; encounterId: string; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; clearError: () => void; organizations?: string[]; @@ -124,10 +128,10 @@ export function EncounterQuestion({ value: [encounterRequest] as unknown as typeof responseValue.value, }; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [responseValue], - }); + updateQuestionnaireResponseCB( + [responseValue], + questionnaireResponse.question_id, + ); }; if (isLoading) { diff --git a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx index f078fa982a2..fb2fb85fd54 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationRequestQuestion.tsx @@ -1,6 +1,7 @@ import { MinusCircledIcon, Pencil2Icon } from "@radix-ui/react-icons"; +import { useQuery } from "@tanstack/react-query"; import { t } from "i18next"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { cn } from "@/lib/utils"; @@ -36,6 +37,12 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { ComboboxQuantityInput } from "@/components/Common/ComboboxQuantityInput"; import { MultiValueSetSelect } from "@/components/Medicine/MultiValueSetSelect"; @@ -44,6 +51,7 @@ import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; import useBreakpoints from "@/hooks/useBreakpoints"; +import query from "@/Utils/request/query"; import { DoseRange, MEDICATION_REQUEST_INTENT, @@ -54,12 +62,21 @@ import { UCUM_TIME_UNITS, parseMedicationStringToRequest, } from "@/types/emr/medicationRequest"; +import medicationRequestApi from "@/types/emr/medicationRequest/medicationRequestApi"; import { Code } from "@/types/questionnaire/code"; -import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { + QuestionnaireResponse, + ResponseValue, +} from "@/types/questionnaire/form"; interface MedicationRequestQuestionProps { + patientId: string; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -67,10 +84,30 @@ export function MedicationRequestQuestion({ questionnaireResponse, updateQuestionnaireResponseCB, disabled, + patientId, }: MedicationRequestQuestionProps) { const medications = (questionnaireResponse.values?.[0]?.value as MedicationRequest[]) || []; + const { data: patientMedications } = useQuery({ + queryKey: ["medication_requests", patientId], + queryFn: query(medicationRequestApi.list, { + pathParams: { patientId }, + queryParams: { + limit: 100, + }, + }), + }); + + useEffect(() => { + if (patientMedications?.results) { + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: patientMedications.results }], + questionnaireResponse.question_id, + ); + } + }, [patientMedications]); + const [expandedMedicationIndex, setExpandedMedicationIndex] = useState< number | null >(null); @@ -88,15 +125,10 @@ export function MedicationRequestQuestion({ authored_on: new Date().toISOString(), }, ]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_request", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); setExpandedMedicationIndex(newMedications.length - 1); }; @@ -107,13 +139,28 @@ export function MedicationRequestQuestion({ const confirmRemoveMedication = () => { if (medicationToDelete === null) return; - const newMedications = medications.filter( - (_, i) => i !== medicationToDelete, - ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_request", value: newMedications }], - }); + const medication = medications[medicationToDelete]; + if (medication.id) { + // For existing records, update status to entered_in_error + const newMedications = medications.map((med, i) => + i === medicationToDelete + ? { ...med, status: "entered_in_error" as const } + : med, + ); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); + } else { + // For new records, remove them completely + const newMedications = medications.filter( + (_, i) => i !== medicationToDelete, + ); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); + } setMedicationToDelete(null); }; @@ -125,15 +172,10 @@ export function MedicationRequestQuestion({ i === index ? { ...medication, ...updates } : medication, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_request", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_request", value: newMedications }], + questionnaireResponse.question_id, + ); }; return ( @@ -257,18 +299,32 @@ export function MedicationRequestQuestion({ )} - + + + + + + + {medication.status === "entered_in_error" + ? t("medication_already_marked_as_error") + : t("remove_medication")} + + +
@@ -330,6 +386,7 @@ const MedicationRequestGridRow: React.FC = ({ const [showDosageDialog, setShowDosageDialog] = useState(false); const desktopLayout = useBreakpoints({ lg: true, default: false }); const dosageInstruction = medication.dosage_instruction[0]; + const isReadOnly = !!medication.id; const handleUpdateDosageInstruction = ( updates: Partial, @@ -368,7 +425,7 @@ const MedicationRequestGridRow: React.FC = ({ }, })); }} - disabled={disabled} + disabled={disabled || isReadOnly} />
@@ -385,7 +442,7 @@ const MedicationRequestGridRow: React.FC = ({ }, })); }} - disabled={disabled || !localDoseRange.low.value} + disabled={disabled || !localDoseRange.low.value || isReadOnly} />
@@ -414,7 +471,8 @@ const MedicationRequestGridRow: React.FC = ({ !localDoseRange.low.value || !localDoseRange.high.value || !localDoseRange.low.unit || - !localDoseRange.high.unit + !localDoseRange.high.unit || + isReadOnly } > {t("save")} @@ -443,7 +501,15 @@ const MedicationRequestGridRow: React.FC = ({ }; return ( -
+
{/* Medicine Name */}
@@ -480,7 +546,7 @@ const MedicationRequestGridRow: React.FC = ({ }, }); }} - disabled={disabled} + disabled={disabled || isReadOnly} />
@@ -547,7 +614,7 @@ const MedicationRequestGridRow: React.FC = ({ }); } }} - disabled={disabled} + disabled={disabled || isReadOnly} > @@ -595,7 +662,8 @@ const MedicationRequestGridRow: React.FC = ({ disabled={ disabled || !dosageInstruction?.timing?.repeat || - dosageInstruction?.as_needed_boolean + dosageInstruction?.as_needed_boolean || + isReadOnly } className="h-9 text-sm" /> @@ -624,7 +692,8 @@ const MedicationRequestGridRow: React.FC = ({ disabled={ disabled || !dosageInstruction?.timing?.repeat || - dosageInstruction?.as_needed_boolean + dosageInstruction?.as_needed_boolean || + isReadOnly } > @@ -671,7 +740,7 @@ const MedicationRequestGridRow: React.FC = ({ }, }, ]} - disabled={disabled} + disabled={disabled || isReadOnly} /> ) : ( = ({ }) } placeholder={t("select_additional_instructions")} - disabled={disabled} + disabled={disabled || isReadOnly} /> )}
@@ -695,7 +764,7 @@ const MedicationRequestGridRow: React.FC = ({ value={dosageInstruction?.route} onSelect={(route) => handleUpdateDosageInstruction({ route })} placeholder={t("select_route")} - disabled={disabled} + disabled={disabled || isReadOnly} />
{/* Site */} @@ -706,7 +775,7 @@ const MedicationRequestGridRow: React.FC = ({ value={dosageInstruction?.site} onSelect={(site) => handleUpdateDosageInstruction({ site })} placeholder={t("select_site")} - disabled={disabled} + disabled={disabled || isReadOnly} wrapTextForSmallScreen={true} />
@@ -718,7 +787,7 @@ const MedicationRequestGridRow: React.FC = ({ value={dosageInstruction?.method} onSelect={(method) => handleUpdateDosageInstruction({ method })} placeholder={t("select_method")} - disabled={disabled} + disabled={disabled || isReadOnly} count={20} />
@@ -730,7 +799,7 @@ const MedicationRequestGridRow: React.FC = ({ onValueChange={(value: MedicationRequestIntent) => onUpdate?.({ intent: value }) } - disabled={disabled} + disabled={disabled || isReadOnly} > = ({ if (!date) return; onUpdate?.({ authored_on: date.toISOString() }); }} - disabled={disabled} + disabled={disabled || isReadOnly} />
{/* Notes */} @@ -791,8 +860,8 @@ const MedicationRequestGridRow: React.FC = ({ values: [], note: medication.note, }} - updateQuestionnaireResponseCB={(response) => { - onUpdate?.({ note: response.note }); + handleUpdateNote={(note) => { + onUpdate?.({ note: note }); }} disabled={disabled} /> diff --git a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx index 6f58f874125..936a2b3b0b3 100644 --- a/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx +++ b/src/components/Questionnaire/QuestionTypes/MedicationStatementQuestion.tsx @@ -1,5 +1,7 @@ import { MinusCircledIcon, Pencil2Icon } from "@radix-ui/react-icons"; -import React, { useState } from "react"; +import { useQuery } from "@tanstack/react-query"; +import React, { useEffect } from "react"; +import { useState } from "react"; import { useTranslation } from "react-i18next"; import { cn } from "@/lib/utils"; @@ -32,26 +34,40 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from "@/components/ui/tooltip"; import { NotesInput } from "@/components/Questionnaire/QuestionTypes/NotesInput"; import ValueSetSelect from "@/components/Questionnaire/ValueSetSelect"; import useBreakpoints from "@/hooks/useBreakpoints"; +import query from "@/Utils/request/query"; import { MEDICATION_STATEMENT_STATUS, MedicationStatementInformationSourceType, MedicationStatementRequest, MedicationStatementStatus, } from "@/types/emr/medicationStatement"; +import medicationStatementApi from "@/types/emr/medicationStatement/medicationStatementApi"; import { Code } from "@/types/questionnaire/code"; import { QuestionnaireResponse } from "@/types/questionnaire/form"; +import { ResponseValue } from "@/types/questionnaire/form"; import { Question } from "@/types/questionnaire/question"; interface MedicationStatementQuestionProps { + patientId: string; question: Question; questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + updateQuestionnaireResponseCB: ( + values: ResponseValue[], + questionId: string, + note?: string, + ) => void; disabled?: boolean; } @@ -73,6 +89,7 @@ export function MedicationStatementQuestion({ questionnaireResponse, updateQuestionnaireResponseCB, disabled, + patientId, }: MedicationStatementQuestionProps) { const { t } = useTranslation(); const desktopLayout = useBreakpoints({ lg: true, default: false }); @@ -87,20 +104,34 @@ export function MedicationStatementQuestion({ (questionnaireResponse.values?.[0] ?.value as MedicationStatementRequest[]) || []; + const { data: patientMedications } = useQuery({ + queryKey: ["medication_statements", patientId], + queryFn: query(medicationStatementApi.list, { + pathParams: { patientId }, + queryParams: { + limit: 100, + }, + }), + }); + + useEffect(() => { + if (patientMedications?.results) { + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: patientMedications.results }], + questionnaireResponse.question_id, + ); + } + }, [patientMedications]); + const handleAddMedication = (medication: Code) => { const newMedications: MedicationStatementRequest[] = [ ...medications, { ...MEDICATION_STATEMENT_INITIAL_VALUE, medication }, ]; - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_statement", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); setExpandedMedicationIndex(newMedications.length - 1); }; @@ -111,13 +142,28 @@ export function MedicationStatementQuestion({ const confirmRemoveMedication = () => { if (medicationToDelete === null) return; - const newMedications = medications.filter( - (_, i) => i !== medicationToDelete, - ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [{ type: "medication_statement", value: newMedications }], - }); + const medication = medications[medicationToDelete]; + if (medication.id) { + // For existing records, update status to entered_in_error + const newMedications = medications.map((med, i) => + i === medicationToDelete + ? { ...med, status: "entered_in_error" as const } + : med, + ); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); + } else { + // For new records, remove them completely + const newMedications = medications.filter( + (_, i) => i !== medicationToDelete, + ); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); + } setMedicationToDelete(null); }; @@ -129,15 +175,10 @@ export function MedicationStatementQuestion({ i === index ? { ...medication, ...updates } : medication, ); - updateQuestionnaireResponseCB({ - ...questionnaireResponse, - values: [ - { - type: "medication_statement", - value: newMedications, - }, - ], - }); + updateQuestionnaireResponseCB( + [{ type: "medication_statement", value: newMedications }], + questionnaireResponse.question_id, + ); }; return ( @@ -249,18 +290,32 @@ export function MedicationStatementQuestion({ )} - + + + + + + + {medication.status === "entered_in_error" + ? t("medication_already_marked_as_error") + : t("remove_medication")} + + +
@@ -272,6 +327,7 @@ export function MedicationStatementQuestion({ handleUpdateMedication(index, updates) } onRemove={() => handleRemoveMedication(index)} + index={index} />
@@ -284,6 +340,7 @@ export function MedicationStatementQuestion({ handleUpdateMedication(index, updates) } onRemove={() => handleRemoveMedication(index)} + index={index} /> )} @@ -311,6 +368,7 @@ interface MedicationStatementGridRowProps { disabled?: boolean; onUpdate?: (medication: Partial) => void; onRemove?: () => void; + index: number; } const MedicationStatementGridRow: React.FC = ({ @@ -318,17 +376,26 @@ const MedicationStatementGridRow: React.FC = ({ disabled, onUpdate, onRemove, + index, }) => { const { t } = useTranslation(); const desktopLayout = useBreakpoints({ lg: true, default: false }); + const isReadOnly = !!medication.id; return ( -
- {/* Medicine Name */} -
- - {medication.medication?.display} - +
+
+

+ {index + 1}. {medication.medication?.display} +

{/* Source */} @@ -339,7 +406,7 @@ const MedicationStatementGridRow: React.FC = ({ onValueChange={(value: MedicationStatementInformationSourceType) => onUpdate?.({ information_source: value }) } - disabled={disabled} + disabled={disabled || isReadOnly} > @@ -410,7 +477,7 @@ const MedicationStatementGridRow: React.FC = ({ value={medication.dosage_text || ""} onChange={(e) => onUpdate?.({ dosage_text: e.target.value })} placeholder={t("enter_dosage_instructions")} - disabled={disabled} + disabled={disabled || isReadOnly} className="h-9 text-sm" />
@@ -448,7 +515,7 @@ const MedicationStatementGridRow: React.FC = ({ placeholder={t("reason_for_medication")} value={medication.reason || ""} onChange={(e) => onUpdate?.({ reason: e.target.value })} - disabled={disabled} + disabled={disabled || isReadOnly} className="h-9 text-sm" />
@@ -479,8 +546,8 @@ const MedicationStatementGridRow: React.FC = ({ values: [], note: medication.note, }} - updateQuestionnaireResponseCB={(response) => { - onUpdate?.({ note: response.note }); + handleUpdateNote={(note) => { + onUpdate?.({ note: note }); }} disabled={disabled} /> diff --git a/src/components/Questionnaire/QuestionTypes/NotesInput.tsx b/src/components/Questionnaire/QuestionTypes/NotesInput.tsx index e6ec41120c0..5cbec9d3a5a 100644 --- a/src/components/Questionnaire/QuestionTypes/NotesInput.tsx +++ b/src/components/Questionnaire/QuestionTypes/NotesInput.tsx @@ -14,14 +14,14 @@ import type { QuestionnaireResponse } from "@/types/questionnaire/form"; interface NotesInputProps { questionnaireResponse: QuestionnaireResponse; - updateQuestionnaireResponseCB: (response: QuestionnaireResponse) => void; + handleUpdateNote: (note: string) => void; disabled?: boolean; className?: string; } export function NotesInput({ questionnaireResponse, - updateQuestionnaireResponseCB, + handleUpdateNote, disabled, className, }: NotesInputProps) { @@ -50,12 +50,7 @@ export function NotesInput({