From 2fdffa22adf8e9898b579faf2cf6c2d877a7b3d8 Mon Sep 17 00:00:00 2001 From: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> Date: Fri, 22 Nov 2024 17:27:30 +0530 Subject: [PATCH 01/37] Fixed the cypress env config file and removed hardcoded fallback (#9184) --- cypress.config.ts | 5 ++++- .../{PatientFileUpload.ts => PatientFileUpload.cy.ts} | 0 cypress/pageobject/Patient/PatientFileupload.ts | 4 +++- package-lock.json | 2 +- src/components/Files/AudioCaptureDialog.tsx | 2 +- 5 files changed, 9 insertions(+), 4 deletions(-) rename cypress/e2e/patient_spec/{PatientFileUpload.ts => PatientFileUpload.cy.ts} (100%) diff --git a/cypress.config.ts b/cypress.config.ts index b6e8fadd462..4da5d989a88 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,7 +1,10 @@ import { defineConfig } from "cypress"; import cypressSplit from "cypress-split"; +import * as dotenv from "dotenv"; import fs from "fs"; +dotenv.config(); + export default defineConfig({ projectId: "wf7d2m", defaultCommandTimeout: 10000, @@ -32,7 +35,7 @@ export default defineConfig({ requestTimeout: 15000, }, env: { - API_URL: process.env.REACT_CARE_API_URL ?? "http://localhost:9000", + API_URL: process.env.REACT_CARE_API_URL, ENABLE_HCX: process.env.REACT_ENABLE_HCX ?? false, }, }); diff --git a/cypress/e2e/patient_spec/PatientFileUpload.ts b/cypress/e2e/patient_spec/PatientFileUpload.cy.ts similarity index 100% rename from cypress/e2e/patient_spec/PatientFileUpload.ts rename to cypress/e2e/patient_spec/PatientFileUpload.cy.ts diff --git a/cypress/pageobject/Patient/PatientFileupload.ts b/cypress/pageobject/Patient/PatientFileupload.ts index 6cde7d78568..c70170a744d 100644 --- a/cypress/pageobject/Patient/PatientFileupload.ts +++ b/cypress/pageobject/Patient/PatientFileupload.ts @@ -20,7 +20,9 @@ export class PatientFileUpload { recordAudio() { cy.get("#record-audio").click(); - cy.wait(5000); + cy.wait(2000); + cy.get("#start-recording").click(); + cy.wait(2000); cy.get("#stop-recording").click(); cy.wait(1000); cy.get("#save-recording").click(); diff --git a/package-lock.json b/package-lock.json index 5c703af8fe1..63af1164ef0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20862,4 +20862,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/components/Files/AudioCaptureDialog.tsx b/src/components/Files/AudioCaptureDialog.tsx index 4d60f64a135..5512fad9dc6 100644 --- a/src/components/Files/AudioCaptureDialog.tsx +++ b/src/components/Files/AudioCaptureDialog.tsx @@ -133,7 +133,7 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) {

{t("audio__record")}

{t("audio__record_helper")}
-
+
- advancedFilter.setShow(true)} - /> navigate("/shifting/board", { query: qParams })} @@ -284,6 +281,9 @@ export default function ListView() { {t("board_view")} + advancedFilter.setShow(true)} + />
} From b7c467fcc4a7690b9702ee4b3d3672b4050d5265 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 27 Nov 2024 14:09:08 +0530 Subject: [PATCH 07/37] Patient Home Page redesign (#9203) Co-authored-by: mahendar Co-authored-by: vinu tv --- .../patient_spec/PatientRegistration.cy.ts | 5 +- .../pageobject/Patient/PatientConsultation.ts | 3 +- cypress/pageobject/Patient/PatientCreation.ts | 4 +- .../Patient/PatientMedicalHistory.ts | 4 +- cypress/pageobject/Sample/SampleTestCreate.ts | 2 + public/locale/en.json | 91 + public/locale/hi.json | 1 + public/locale/kn.json | 1 + public/locale/ml.json | 1 + public/locale/mr.json | 34 +- src/CAREUI/display/Chip.tsx | 2 +- src/Routers/AppRouter.tsx | 6 +- src/Routers/routes/PatientRoutes.tsx | 12 +- src/common/constants.tsx | 2 + src/components/Common/Breadcrumbs.tsx | 20 +- src/components/Common/Menu.tsx | 2 +- src/components/Common/Page.tsx | 4 +- src/components/Facility/ConsultationCard.tsx | 231 +-- .../Facility/ConsultationDetails/index.tsx | 179 +- src/components/Patient/InsuranceDetails.tsx | 4 +- .../Patient/InsuranceDetailsCard.tsx | 96 +- .../Patient/PatientDetailsTab/Demography.tsx | 454 +++++ .../PatientDetailsTab/EncounterHistory.tsx | 90 + .../HealthProfileSummary.tsx | 135 ++ .../PatientDetailsTab/ImmunisationRecords.tsx | 124 ++ .../Patient/PatientDetailsTab/Notes.tsx | 184 ++ .../PatientDetailsTab/SampleTestHistory.tsx | 107 ++ .../PatientDetailsTab/ShiftingHistory.tsx | 73 + .../Patient/PatientDetailsTab/index.tsx | 45 + src/components/Patient/PatientHome.tsx | 1663 +++++------------ src/components/Patient/PatientInfoCard.tsx | 2 +- src/components/Patient/PatientRegister.tsx | 35 +- src/components/Patient/SampleTestCard.tsx | 4 +- src/components/Patient/models.tsx | 2 +- src/components/Shifting/ShiftingBlock.tsx | 43 +- src/components/Shifting/ShiftingList.tsx | 228 +-- src/components/Shifting/ShiftingTable.tsx | 261 +++ src/components/Users/ManageUsers.tsx | 2 +- 38 files changed, 2390 insertions(+), 1766 deletions(-) create mode 100644 src/components/Patient/PatientDetailsTab/Demography.tsx create mode 100644 src/components/Patient/PatientDetailsTab/EncounterHistory.tsx create mode 100644 src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx create mode 100644 src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx create mode 100644 src/components/Patient/PatientDetailsTab/Notes.tsx create mode 100644 src/components/Patient/PatientDetailsTab/SampleTestHistory.tsx create mode 100644 src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx create mode 100644 src/components/Patient/PatientDetailsTab/index.tsx create mode 100644 src/components/Shifting/ShiftingTable.tsx diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index 2058760f6d2..2da35c840e0 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -132,6 +132,7 @@ describe("Patient Creation with consultation", () => { "Middle Class", "Family member", ); + patientMedicalHistory.verifyPatientMedicalDetails( patientOnePresentHealth, patientOneOngoingMedication, @@ -216,11 +217,9 @@ describe("Patient Creation with consultation", () => { patientMedicalHistory.verifyNoSymptosPresent("Diabetes"); // verify insurance details and dedicatd page cy.get("[data-testid=patient-details]") - .contains("member id") + .contains("Member ID") .scrollIntoView(); cy.wait(2000); - patientInsurance.clickPatientInsuranceViewDetail(); - cy.wait(3000); patientInsurance.verifyPatientPolicyDetails( patientOneFirstSubscriberId, patientOneFirstPolicyId, diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index c8046a39a9e..edd8ae135a4 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -122,9 +122,10 @@ export class PatientConsultationPage { } clickViewConsultationButton() { + cy.get("a").contains("Encounters").click(); cy.verifyAndClickElement( "#view_consultation_and_log_updates", - "View Consultation / Log Updates", + "View Updates", ); } diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index a38b8ab6a5c..1f915f5474e 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -181,7 +181,7 @@ export class PatientPage { expect($dashboard).to.contain(patientName); expect($dashboard).to.contain(phoneNumber); expect($dashboard).to.contain(emergencyPhoneNumber); - expect($dashboard).to.contain(yearOfBirth); + //expect($dashboard).to.contain(yearOfBirth); //Commented out because new proposed UI does not have DOB. Can change later. expect($dashboard).to.contain(bloodGroup); expect($dashboard).to.contain(occupation); socioeconomicStatus && expect($dashboard).to.contain(socioeconomicStatus); @@ -221,7 +221,7 @@ export class PatientPage { } clickPatientUpdateDetails() { - cy.verifyAndClickElement("#update-patient-details", "Update Details"); + cy.verifyAndClickElement("#update-patient-details", "Edit Profile"); } interceptFacilities() { diff --git a/cypress/pageobject/Patient/PatientMedicalHistory.ts b/cypress/pageobject/Patient/PatientMedicalHistory.ts index 93fdd1b38b3..bf2296b4471 100644 --- a/cypress/pageobject/Patient/PatientMedicalHistory.ts +++ b/cypress/pageobject/Patient/PatientMedicalHistory.ts @@ -33,7 +33,9 @@ class PatientMedicalHistory { patientSymptoms6: string, patientSymptoms7: string, ) { - cy.get("[data-testid=patient-details]").then(($dashboard) => { + cy.get("a").contains("Health Profile").click(); + cy.wait(2000); + cy.get("[data-test-id=patient-health-profile]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(patientPresentHealth); expect($dashboard).to.contain(patientOngoingMedication); diff --git a/cypress/pageobject/Sample/SampleTestCreate.ts b/cypress/pageobject/Sample/SampleTestCreate.ts index cd2e93e5072..445d08732c3 100644 --- a/cypress/pageobject/Sample/SampleTestCreate.ts +++ b/cypress/pageobject/Sample/SampleTestCreate.ts @@ -1,5 +1,6 @@ export class SampleTestPage { visitSampleRequestPage(): void { + cy.get("a").contains("Service Request").click(); cy.verifyAndClickElement("#sample-request-btn", "Request Sample Test"); cy.url().should("include", "/sample-test"); } @@ -60,6 +61,7 @@ export class SampleTestPage { fastTrack: string, sampleTestResult: string, ): void { + cy.get("a").contains("Service Request").click(); cy.verifyContentPresence("#sample-test-status", [sampleTestStatus]); cy.verifyContentPresence("#sample-test-type", [sampleTestType]); cy.verifyContentPresence("#sample-test-fast-track", [fastTrack]); diff --git a/public/locale/en.json b/public/locale/en.json index 8a4a36a1a5c..56f84a1883a 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -274,6 +274,7 @@ "accept_all": "Accept All", "access_level": "Access Level", "action_irreversible": "This action is irreversible", + "actions": "Actions", "active": "Active", "active_prescriptions": "Active Prescriptions", "add": "Add", @@ -281,7 +282,10 @@ "add_attachments": "Add Attachments", "add_beds": "Add Bed(s)", "add_beds_to_configure_presets": "Add beds to this location to configure presets for them.", + "add_consultation": "Add consultation", + "add_consultation_update": "Add Consultation Update", "add_details_of_patient": "Add Details of Patient", + "add_insurance_details": "Add Insurance Details", "add_location": "Add Location", "add_new_beds": "Add New Bed(s)", "add_new_user": "Add New User", @@ -301,11 +305,14 @@ "administered_on": "Administered on", "administration_dosage_range_error": "Dosage should be between start and target dosage", "administration_notes": "Administration Notes", + "admitted": "Admitted", + "admitted_on": "Admitted On", "advanced_filters": "Advanced Filters", "age": "Age", "all_changes_have_been_saved": "All changes have been saved", "all_details": "All Details", "allergies": "Allergies", + "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are", "already_a_member": "Already a member?", "ambulance_driver_name": "Name of ambulance driver", @@ -316,6 +323,7 @@ "any_id_description": "Currently we support: Aadhaar Number / Mobile Number", "any_other_comments": "Any other comments", "apply": "Apply", + "approve": "Approve", "approved_by_district_covid_control_room": "Approved by District COVID Control Room", "approving_facility": "Name of Approving Facility", "archive": "Archive", @@ -332,8 +340,14 @@ "asset_qr_id": "Asset QR ID", "asset_type": "Asset Type", "assets": "Assets", + "assign": "Assign", + "assign_a_volunteer_to": "Assign a volunteer to {{name}}", + "assign_bed": "Assign Bed", + "assign_to_volunteer": "Assign to a Volunteer", + "assigned_doctor": "Assigned Doctor", "assigned_facility": "Facility assigned", "assigned_to": "Assigned to", + "assigned_volunteer": "Assigned Volunteer", "async_operation_warning": "This operation may take some time. Please check back later.", "atypical_presentation_details": "Atypical presentation details", "audio__allow_permission": "Please allow microphone permission in site settings", @@ -365,6 +379,7 @@ "bed_capacity": "Bed Capacity", "bed_created_notification_one": "{{count}} Bed created successfully", "bed_created_notification_other": "{{count}} Beds created successfully", + "bed_history": "Bed History", "bed_not_linked_to_camera": "This bed has not been linked to this camera.", "bed_search_placeholder": "Search by beds name", "bed_type": "Bed Type", @@ -397,6 +412,7 @@ "central_nursing_station": "Central Nursing Station", "change_file": "Change File", "change_password": "Change Password", + "chat_on_whatsapp": "Chat on Whatsapp", "check_eligibility": "Check Eligibility", "check_for_available_update": "Check for available update", "check_for_update": "Check for Update", @@ -493,6 +509,7 @@ "consent_request_rejected": "Patient has rejected the consent request", "consent_request_waiting_approval": "Waiting for the Patient to approve the consent request", "consent_requested_successfully": "Consent requested successfully!", + "consultation_history": "Consultation History", "consultation_missing_warning": "You have not created a consultation for the patient in", "consultation_not_filed": "You have not filed a consultation for this patient yet.", "consultation_not_filed_description": "Please file a consultation for this patient to continue.", @@ -516,6 +533,7 @@ "countries_travelled": "Countries travelled", "covid_19_cat_gov": "Covid_19 Clinical Category as per Govt. of Kerala guideline (A/B/C)", "covid_19_death_reporting_form_1": "Covid-19 Death Reporting : Form 1", + "covid_details": "Covid Details", "create": "Create", "create_abha_address": "Create ABHA Address", "create_add_more": "Create & Add More", @@ -533,6 +551,7 @@ "created_date": "Created Date", "created_on": "Created On", "csv_file_in_the_specified_format": "Select a CSV file in the specified format", + "current_address": "Current Address", "current_password": "Current Password", "customer_support_email": "Customer Support Email", "customer_support_name": "Customer Support Name", @@ -546,13 +565,16 @@ "date_of_birth": "Date of birth", "date_of_positive_covid_19_swab": "Date of Positive Covid 19 Swab", "date_of_result": "Covid confirmation date", + "date_of_return": "Date of Return", "date_of_test": "Date of sample collection for Covid testing", "days": "Days", + "death_report": "Death Report", "delete": "Delete", "delete_facility": "Delete Facility", "delete_item": "Delete {{name}}", "delete_record": "Delete Record", "deleted_successfully": "{{name}} deleted successfully", + "demography": "Demography", "denied_on": "Denied On", "describe_why_the_asset_is_not_working": "Describe why the asset is not working", "description": "Description", @@ -573,6 +595,7 @@ "diastolic": "Diastolic", "differential": "Differential", "differential_diagnosis": "Differential diagnosis", + "disable_transfer": "Disable Transfer", "discard": "Discard", "discharge": "Discharge", "discharge_from_care": "Discharge from CARE", @@ -580,6 +603,7 @@ "discharge_summary": "Discharge Summary", "discharge_summary_not_ready": "Discharge summary is not ready yet.", "discharged": "Discharged", + "discharged_on": "Discharged On", "discharged_patients": "Discharged Patients", "discharged_patients_empty": "No discharged patients present in this facility", "disclaimer": "Disclaimer", @@ -614,6 +638,7 @@ "edit_policy": "Edit Insurance Policy", "edit_policy_description": "Add or edit patient's insurance details", "edit_prescriptions": "Edit Prescriptions", + "edit_profile": "Edit Profile", "edit_user_profile": "Edit Profile", "edited_by": "Edited by", "edited_on": "Edited on", @@ -627,7 +652,11 @@ "email_discharge_summary_description": "Enter your valid email address to receive the discharge summary", "email_success": "We will be sending an email shortly. Please check your inbox.", "emergency": "Emergency", + "emergency_contact": "Emergency Contact", "emergency_contact_number": "Emergency Contact Number", + "emergency_contact_person_name": "Emergency Contact Person Name", + "emergency_contact_person_name_volunteer": "Emergency Contact Person Name (Volunteer)", + "emergency_contact_volunteer": "Emergency Contact (Volunteer)", "empty_date_time": "--:-- --; --/--/----", "encounter_date_field_label__A": "Date & Time of Admission to the Facility", "encounter_date_field_label__DC": "Date & Time of Domiciliary Care commencement", @@ -643,6 +672,7 @@ "encounter_suggestion__OP": "Out-patient visit", "encounter_suggestion__R": "Consultation", "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation", + "encounters": "Encounters", "end_datetime": "End Date/Time", "enter_aadhaar_number": "Enter a 12-digit Aadhaar ID", "enter_aadhaar_otp": "Enter OTP sent to the registered mobile with Aadhaar", @@ -664,6 +694,7 @@ "events": "Events", "expand_sidebar": "Expand Sidebar", "expected_burn_rate": "Expected Burn Rate", + "expired": "Expired", "expired_on": "Expired On", "expires_on": "Expires On", "facilities": "Facilities", @@ -721,6 +752,7 @@ "granted_on": "Granted On", "has_domestic_healthcare_support": "Has domestic healthcare support?", "has_sari": "Has SARI (Severe Acute Respiratory illness)?", + "health-profile": "Health Profile", "health_facility__config_registration_error": "Health ID registration failed", "health_facility__config_update_error": "Health Facility config update failed", "health_facility__config_update_success": "Health Facility config updated successfully", @@ -757,12 +789,17 @@ "i_declare": "I hereby declare that:", "icd11_as_recommended": "As per ICD-11 recommended by WHO", "icmr_specimen_referral_form": "ICMR Specimen Referral Form", + "immunisation-records": "Immunisation", "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", "indicator": "Indicator", "inidcator_event": "Indicator Event", "instruction_on_titration": "Instruction on titration", + "insurance__insurer_id": "Insurer ID", + "insurance__insurer_name": "Insurer Name", + "insurance__member_id": "Member ID", + "insurance__policy_name": "Policy ID / Policy Name", "insurer_name_required": "Insurer Name is required", "international_mobile": "International Mobile", "invalid_asset_id_msg": "Oops! The asset ID you entered does not appear to be valid.", @@ -771,6 +808,7 @@ "invalid_link_msg": "It appears that the password reset link you have used is either invalid or expired. Please request a new password reset link.", "invalid_password": "Password doesn't meet the requirements", "invalid_password_reset_link": "Invalid password reset link", + "invalid_patient_data": "Invalid Patient Data", "invalid_phone": "Please enter valid phone number", "invalid_phone_number": "Invalid Phone Number", "invalid_pincode": "Invalid Pincode", @@ -789,6 +827,8 @@ "investigations__result": "Result", "investigations__unit": "Unit", "investigations_suggested": "Investigations Suggested", + "investigations_summary": "Investigations Summary", + "ip_encounter": "IP Encounter", "is": "Is", "is_antenatal": "Is Antenatal", "is_atypical_presentation": "Is Atypical presentation", @@ -796,21 +836,26 @@ "is_emergency": "Is emergency", "is_emergency_case": "Is emergency case", "is_it_upshift": "is it upshift", + "is_pregnant": "Is pregnant", "is_this_an_emergency": "Is this an emergency?", "is_this_an_upshift": "Is this an upshift?", "is_unusual_course": "Is unusual course", "is_up_shift": "Is up shift", "is_upshift_case": "Is upshift case", "is_vaccinated": "Whether vaccinated", + "kasp_enabled_date": "{{kasp_string}} enabled date", "label": "Label", "landline": "Indian landline", "language_selection": "Language Selection", "last_administered": "Last administered", + "last_discharge_reason": "Last Discharge Reason", "last_edited": "Last Edited", "last_modified": "Last Modified", "last_name": "Last Name", "last_online": "Last Online", "last_serviced_on": "Last Serviced On", + "last_updated_by": "Last updated by", + "last_vaccinated_on": "Last Vaccinated on", "latitude_invalid": "Latitude must be between -90 and 90", "left": "Left", "length": "Length ({{unit}})", @@ -859,6 +904,7 @@ "max_size_for_image_uploaded_should_be": "Max size for image uploaded should be", "measured_after": "Measured after", "measured_before": "Measured before", + "medical": "Medical", "medical_council_registration": "Medical Council Registration", "medical_worker": "Medical Worker", "medicine": "Medicine", @@ -903,6 +949,8 @@ "no_beds_available": "No beds available", "no_changes": "No changes", "no_changes_made": "No changes made", + "no_consultation_filed": "No consultation filed", + "no_consultation_history": "No consultation history available", "no_consultation_updates": "No consultation updates", "no_data_found": "No data found", "no_duplicate_facility": "You should not create duplicate facilities", @@ -915,6 +963,7 @@ "no_linked_facilities": "No Linked Facilities", "no_log_update_delta": "No changes since previous log update", "no_log_updates": "No log updates found", + "no_medical_history_available": "No Medical History Available", "no_notices_for_you": "No notices for you.", "no_patients_found": "No Patients Found", "no_patients_to_show": "No patients to show.", @@ -924,6 +973,7 @@ "no_records_found": "No Records Found", "no_remarks": "No remarks", "no_results_found": "No Results Found", + "no_social_profile_details_available": "No Social Profile Details Available", "no_staff": "No staff found", "no_tests_taken": "No tests taken", "no_treating_physicians_available": "This facility does not have any home facility doctors. Please contact your admin.", @@ -943,6 +993,7 @@ "number_of_beds": "Number of beds", "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", "number_of_chronic_diseased_dependents": "Number Of Chronic Diseased Dependents", + "number_of_covid_vaccine_doses": "Number of Covid vaccine doses", "nursing_care": "Nursing Care", "nursing_information": "Nursing Information", "nutrition": "Nutrition", @@ -952,6 +1003,8 @@ "on": "On", "ongoing_medications": "Ongoing Medications", "only_indian_mobile_numbers_supported": "Currently only Indian numbers are supported", + "op_encounter": "OP Encounter", + "op_file_closed": "OP file closed", "open": "Open", "open_camera": "Open Camera", "open_live_monitoring": "Open Live Monitoring", @@ -974,6 +1027,11 @@ "password_reset_success": "Password Reset successfully", "password_sent": "Password Reset Email Sent", "patient": "Patient", + "patient-notes": "Notes", + "patient__general-info": "General Info", + "patient__insurance-details": "Insurance Details", + "patient__social-profile": "Social Profile", + "patient__volunteer-contact": "Volunteer Contact", "patient_address": "Patient Address", "patient_body": "Patient Body", "patient_category": "Patient Category", @@ -997,6 +1055,8 @@ "patient_notes_thread__Doctors": "Doctor's Discussions", "patient_notes_thread__Nurses": "Nurse's Discussions", "patient_phone_number": "Patient Phone Number", + "patient_profile": "Patient Profile", + "patient_profile_created_by": "Patient profile created by", "patient_registration__address": "Address", "patient_registration__age": "Age", "patient_registration__comorbidities": "Comorbidities", @@ -1009,12 +1069,15 @@ "patient_status": "Patient Status", "patient_transfer_birth_match_note": "Note: Year of birth must match the patient to process the transfer request.", "patients": "Patients", + "permanent_address": "Permanent Address", + "permission_denied": "You do not have permission to perform this action", "personal_information": "Personal Information", "phone": "Phone", "phone_no": "Phone no.", "phone_number": "Phone Number", "phone_number_at_current_facility": "Phone Number of Contact person at current Facility", "pincode": "Pincode", + "please_assign_bed_to_patient": "Please assign a bed to this patient", "please_enter_a_reason_for_the_shift": "Please enter a reason for the shift.", "please_select_a_facility": "Please select a facility", "please_select_breathlessness_level": "Please select Breathlessness Level", @@ -1036,6 +1099,7 @@ "policy__subscriber_id__example": "SUB001", "policy_id_required": "Policy Id or Policy Name is required", "position": "Position", + "post_partum": "Post-partum", "post_your_comment": "Post Your Comment", "powered_by": "Powered By", "preferred_facility_type": "Preferred Facility Type", @@ -1050,6 +1114,7 @@ "prescriptions__medicine": "Medicine", "prescriptions__route": "Route", "prescriptions__start_date": "Prescribed On", + "present_health": "Present Health", "preset_deleted": "Preset deleted", "preset_name_placeholder": "Specify an identifiable name for the new preset", "preset_updated": "Preset updated", @@ -1100,9 +1165,11 @@ "req_atleast_one_lowercase": "Require at least one lower case letter", "req_atleast_one_symbol": "Require at least one symbol", "req_atleast_one_uppercase": "Require at least one upper case", + "request-sample-test": "Service Request", "request_consent": "Request Consent", "request_description": "Description of Request", "request_description_placeholder": "Type your description here", + "request_sample_test": "Request Sample Test", "request_title": "Request Title", "request_title_placeholder": "Type your title here", "required": "Required", @@ -1126,6 +1193,8 @@ "return_to_login": "Return to Login", "return_to_password_reset": "Return to Password Reset", "return_to_patient_dashboard": "Return to Patient Dashboard", + "review_before": "Review Before", + "review_missed": "Review Missed", "revoked_on": "Revoked On", "right": "Right", "route": "Route", @@ -1173,6 +1242,8 @@ "send_otp_error": "Failed to send OTP. Please try again later.", "send_otp_success": "OTP has been sent to the respective mobile number", "send_reset_link": "Send Reset Link", + "send_sample_to_collection_centre_description": "Are you sure you want to send the sample to Collection Centre?", + "send_sample_to_collection_centre_title": "Send sample to collection centre", "serial_number": "Serial Number", "serviced_on": "Serviced on", "session_expired": "Session Expired", @@ -1181,6 +1252,8 @@ "set_your_local_language": "Set your local language", "settings_and_filters": "Settings and Filters", "severity_of_breathlessness": "Severity of Breathlessness", + "sex": "Sex", + "shift": "Shift Patient", "shift_request_updated_successfully": "Shift request updated successfully", "shifting": "Shifting", "shifting_approval_facility": "Shifting approval facility", @@ -1188,6 +1261,7 @@ "shifting_approving_facility_can_not_be_empty": "Shifting approving facility can not be empty.", "shifting_deleted": "Shifting record has been deleted successfully.", "shifting_details": "Shifting details", + "shifting_history": "Shifting History", "shifting_status": "Shifting status", "show_abha_profile": "Show ABHA Profile", "show_all": "Show all", @@ -1197,6 +1271,7 @@ "show_unread_notifications": "Show Unread", "sign_out": "Sign Out", "skills": "Skills", + "social_profile": "Social Profile", "socioeconomic_status": "Socioeconomic status", "software_update": "Software Update", "something_went_wrong": "Something went wrong..!", @@ -1225,9 +1300,11 @@ "subscription_error": "Subscription Error", "subscription_failed": "Subscription Failed", "suggested_investigations": "Suggested Investigations", + "suggestion": "Suggestion", "summary": "Summary", "support": "Support", "switch": "Switch", + "switch_bed": "Switch Bed", "switch_camera_is_not_available": "Switch camera is not available.", "symptoms": "Symptoms", "systolic": "Systolic", @@ -1244,7 +1321,10 @@ "total_users": "Total Users", "transcript_edit_info": "You can update this if we made an error", "transcript_information": "This is what we heard", + "transfer_allowed": "Transfer Allowed", + "transfer_blocked": "Transfer Blocked", "transfer_in_progress": "TRANSFER IN PROGRESS", + "transfer_status_updated": "Transfer Status Updated", "transfer_to_receiving_facility": "Transfer to receiving facility", "travel_within_last_28_days": "Domestic/international Travel (within last 28 days)", "treating_doctor": "Treating Doctor", @@ -1305,6 +1385,8 @@ "username": "Username", "users": "Users", "vacant": "Vacant", + "vaccinated": "Vaccinated", + "vaccine_name": "Vaccine name", "vehicle_preference": "Vehicle preference", "vendor_name": "Vendor Name", "ventilator_interface": "Respiratory Support Type", @@ -1320,21 +1402,30 @@ "verify_otp_success": "OTP has been verified successfully.", "verify_patient_identifier": "Please verify the patient identifier", "verify_using": "Verify Using", + "video_call": "Video Call", "video_conference_link": "Video Conference Link", "view": "View", "view_abdm_records": "View ABDM Records", + "view_all_details": "View All Details", "view_asset": "View Assets", "view_cns": "View CNS", "view_consultation_and_log_updates": "View Consultation / Log Updates", "view_details": "View Details", "view_faciliy": "View Facility", + "view_files": "View Files", "view_patients": "View Patients", + "view_update_patient_files": "View/Update patient files", + "view_updates": "View Updates", "view_users": "View Users", + "village": "Village", "virtual_nursing_assistant": "Virtual Nursing Assistant", "vitals": "Vitals", "vitals_monitor": "Vitals Monitor", "vitals_present": "Vitals Monitor present", "voice_autofill": "Voice Autofill", + "volunteer_assigned": "Volunteer assigned successfuly", + "volunteer_contact": "Volunteer Contact", + "volunteer_unassigned": "Volunteer unassigned successfuly", "ward": "Ward", "warranty_amc_expiry": "Warranty / AMC Expiry", "what_facility_assign_the_patient_to": "What facility would you like to assign the patient to", diff --git a/public/locale/hi.json b/public/locale/hi.json index 0eb0cc28cfd..b3c172021dd 100644 --- a/public/locale/hi.json +++ b/public/locale/hi.json @@ -567,6 +567,7 @@ "patient_name": "मरीज का नाम", "patient_no": "ओपी/आईपी संख्या", "patient_phone_number": "मरीज़ का फ़ोन नंबर", + "patient_profile": "रोगी प्रोफ़ाइल", "patient_registration__address": "पता", "patient_registration__age": "आयु", "patient_registration__comorbidities": "comorbidities", diff --git a/public/locale/kn.json b/public/locale/kn.json index be18432e0ce..faf50da3a23 100644 --- a/public/locale/kn.json +++ b/public/locale/kn.json @@ -568,6 +568,7 @@ "patient_name": "ರೋಗಿಯ ಹೆಸರು", "patient_no": "OP/IP ಸಂ", "patient_phone_number": "ರೋಗಿಯ ಫೋನ್ ಸಂಖ್ಯೆ", + "patient_profile": "ರೋಗಿ ಪ್ರೊಫೈಲ್", "patient_registration__address": "ವಿಳಾಸ", "patient_registration__age": "ವಯಸ್ಸು", "patient_registration__comorbidities": "ಸಹವರ್ತಿ ರೋಗಗಳು", diff --git a/public/locale/ml.json b/public/locale/ml.json index cf381c32c0e..33dadcf9fb7 100644 --- a/public/locale/ml.json +++ b/public/locale/ml.json @@ -567,6 +567,7 @@ "patient_name": "രോഗിയുടെ പേര്", "patient_no": "OP/IP നമ്പർ", "patient_phone_number": "രോഗിയുടെ ഫോൺ നമ്പർ", + "patient_profile": "രോഗിയുടെ പ്രൊഫൈൽ", "patient_registration__address": "വിലാസം", "patient_registration__age": "പ്രായം", "patient_registration__comorbidities": "കോമോർബിഡിറ്റികൾ", diff --git a/public/locale/mr.json b/public/locale/mr.json index 06e9c5bbbde..543278bf867 100644 --- a/public/locale/mr.json +++ b/public/locale/mr.json @@ -49,6 +49,7 @@ "new_password": "नवीन पासवर्ड", "no_duplicate_facility": "बनावट हॉस्पिटल सुविधा मुळीच तयार करू नका", "no_facilities": "कोणतेही हॉस्पिटल नाही", + "patient_profile": "रुग्ण प्रोफाइल", "password": "पासवर्ड", "password_mismatch": "\"पासवर्ड\" आणि \"पासवर्डची खात्री करा\" दोन्ही सारखे हवेत.", "password_reset_failure": "पासवर्ड रिसेट झाला नाही", @@ -62,5 +63,36 @@ "send_reset_link": "रीसेट लिंक पाठवा", "sign_out": "साइन आउट", "something_wrong": "काहीतरी चूक झाली! पुन्हा प्रयत्न करा", - "username": "युजरनेम" + "username": "युजरनेम", + "general_info": "सामान्य माहिती", + "phone": "फोन", + "date_of_birth": "जन्म तारीख", + "sex": "लिंग", + "emergency_contact": "आपत्कालीन संपर्क", + "emergency_contact_person_name": "आपत्कालीन संपर्क व्यक्तीचे नाव", + "covid_details": "कोविड तपशील", + "number_of_covid_vaccine_doses": "कोविड लसीचे डोस", + "vaccine_name": "लस नाव", + "last_vaccinated_on": "शेवटचा लसीकरणाचा दिवस", + "countries_travelled": "प्रवास केलेले देश", + "date_of_return": "परतण्याची तारीख", + "social_profile": "सामाजिक प्रोफाइल", + "no_social_profile_details_available": "कोणतीही सामाजिक प्रोफाइल माहिती उपलब्ध नाही", + "location": "स्थान", + "current_address": "सध्याचा पत्ता", + "permanent_address": "स्थायी पत्ता", + "nationality": "राष्ट्रीयत्व", + "state": "राज्य", + "local_body": "स्थानिक संस्था", + "ward": "वॉर्ड", + "village": "गाव", + "volunteer_contact": "स्वयंसेवक संपर्क", + "emergency_contact_volunteer": "आपत्कालीन संपर्क (स्वयंसेवक)", + "emergency_contact_person_name_volunteer": "आपत्कालीन संपर्क व्यक्तीचे नाव (स्वयंसेवक)", + "medical": "वैद्यकीय", + "no_medical_history_available": "कोणताही वैद्यकीय इतिहास उपलब्ध नाही", + "present_health": "सध्याचे आरोग्य", + "ongoing_medications": "चालू औषधे", + "allergies": "अॅलर्जी", + "is_pregnant": "गर्भवती आहे" } diff --git a/src/CAREUI/display/Chip.tsx b/src/CAREUI/display/Chip.tsx index a2bacf8bd61..e501bf26f90 100644 --- a/src/CAREUI/display/Chip.tsx +++ b/src/CAREUI/display/Chip.tsx @@ -26,7 +26,7 @@ export default function Chip({ -
+
<>
{" "} @@ -121,7 +121,7 @@ export default function AppRouter() {
-
+
@@ -91,32 +91,26 @@ export default function Breadcrumbs({ +
{
+
+ +
+
- + + {showPatientNotesPopup && ( + - - {showPatientNotesPopup && ( - - )} -
+ )} ); }; diff --git a/src/components/Patient/InsuranceDetails.tsx b/src/components/Patient/InsuranceDetails.tsx index 248c7304bfa..3efe7866316 100644 --- a/src/components/Patient/InsuranceDetails.tsx +++ b/src/components/Patient/InsuranceDetails.tsx @@ -1,7 +1,7 @@ import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; import { HCXPolicyModel } from "@/components/HCX/models"; -import { InsuranceDetialsCard } from "@/components/Patient/InsuranceDetailsCard"; +import { InsuranceDetailsCard } from "@/components/Patient/InsuranceDetailsCard"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; @@ -53,7 +53,7 @@ export const InsuranceDetails = (props: IProps) => { data-testid="patient-details" > {insuranceDetials?.results.map((data: HCXPolicyModel) => ( - + ))} )} diff --git a/src/components/Patient/InsuranceDetailsCard.tsx b/src/components/Patient/InsuranceDetailsCard.tsx index 62098fc6a07..6685e7a99a2 100644 --- a/src/components/Patient/InsuranceDetailsCard.tsx +++ b/src/components/Patient/InsuranceDetailsCard.tsx @@ -1,79 +1,53 @@ -import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; -import ButtonV2 from "@/components/Common/ButtonV2"; import { HCXPolicyModel } from "@/components/HCX/models"; interface InsuranceDetails { - data?: HCXPolicyModel; - showViewAllDetails?: boolean; + data: HCXPolicyModel; } -export const InsuranceDetialsCard = (props: InsuranceDetails) => { - const { data, showViewAllDetails } = props; +export const InsuranceDetailsCard = (props: InsuranceDetails) => { + const { data } = props; + + const { t } = useTranslation(); return (
-
-
- Policy Details -
- {data ? ( -
-
-
- Member ID -
-
- {data.subscriber_id || ""} -
+
+
+
+
+ {t("insurance__member_id")}
-
-
- Policy ID / Policy Name -
-
- {data.policy_id || ""} -
+
+ {data.subscriber_id || ""}
-
-
- Insurer ID -
-
- {data.insurer_id || ""} -
+
+
+
+ {t("insurance__policy_name")}
-
-
- Insurer Name -
-
- {data.insurer_name || ""} -
+
+ {data.policy_id || ""}
- {showViewAllDetails && ( -
-
- { - navigate( - `/facility/${data.patient_object?.facility_object?.id}/patient/${data.patient_object?.id}/insurance`, - ); - }} - className="h-auto whitespace-pre-wrap border border-secondary-500 bg-white text-black hover:bg-secondary-300" - > - View All Details - -
-
- )}
- ) : ( -
- No Insurance Details Available +
+
+ {t("insurance__insurer_id")} +
+
+ {data.insurer_id || ""} +
- )} +
+
+ {t("insurance__insurer_name")} +
+
+ {data.insurer_name || ""} +
+
+
); diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx new file mode 100644 index 00000000000..7ac854b8ee6 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -0,0 +1,454 @@ +import dayjs from "dayjs"; +import { navigate } from "raviger"; +import { Fragment, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Chip from "@/CAREUI/display/Chip"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import ButtonV2 from "@/components/Common/ButtonV2"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import { GENDER_TYPES } from "@/common/constants"; + +import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; +import { formatName, formatPatientAge } from "@/Utils/utils"; + +import { PatientProps } from "."; +import * as Notification from "../../../Utils/Notifications"; +import { InsuranceDetailsCard } from "../InsuranceDetailsCard"; +import { parseOccupation } from "../PatientHome"; +import { AssignedToObjectModel } from "../models"; + +export const Demography = (props: PatientProps) => { + const { patientData, facilityId, id } = props; + const authUser = useAuthUser(); + const { t } = useTranslation(); + const [_assignedVolunteerObject, setAssignedVolunteerObject] = + useState(); + + const [activeSection, setActiveSection] = useState(null); + + useEffect(() => { + setAssignedVolunteerObject(patientData.assigned_to_object); + + const observedSections: Element[] = []; + const sections = document.querySelectorAll("div[id]"); + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveSection(entry.target.id); + } + }); + }, + { + threshold: 0.6, + }, + ); + + sections.forEach((section) => { + observer.observe(section); + observedSections.push(section); + }); + + return () => { + observedSections.forEach((section) => observer.unobserve(section)); + }; + }, [patientData.assigned_to_object]); + + const { data: insuranceDetials } = useQuery(routes.hcx.policies.list, { + query: { + patient: id, + }, + }); + + const patientGender = GENDER_TYPES.find( + (i) => i.id === patientData.gender, + )?.text; + + const scrollToSection = (sectionId: string) => { + const section = document.getElementById(sectionId); + if (section) { + section.scrollIntoView({ behavior: "smooth" }); + } + }; + + const handleEditClick = (sectionId: string) => { + navigate( + `/facility/${facilityId}/patient/${id}/update?section=${sectionId}`, + ); + }; + + const hasEditPermission = () => { + const showAllFacilityUsers = ["DistrictAdmin", "StateAdmin"]; + if ( + !showAllFacilityUsers.includes(authUser.user_type) && + authUser.home_facility_object?.id !== patientData.facility + ) { + Notification.Error({ + msg: "Oops! Non-Home facility users don't have permission to perform this action.", + }); + return false; + } + return true; + }; + + const EmergencyContact = (props: { number?: string; name?: string }) => ( +
+
+ {/* Emergency Contact Section */} +
+
+ {t("emergency_contact")} +
+ +
+ + {props.number && ( + + )} +
+
+ +
+
+ {t("emergency_contact_person_name")} +
+
+ {props.name || "-"} +
+
+
+
+ ); + + const withPermissionCheck = (action: () => void) => () => { + if (!hasEditPermission()) { + Notification.Error({ + msg: t("permission_denied"), + }); + return; + } + action(); + }; + + type Data = { + id: string; + hidden?: boolean; + allowEdit?: boolean; + details: (React.ReactNode | { label: string; value: React.ReactNode })[]; + }; + + const data: Data[] = [ + { + id: "general-info", + allowEdit: true, + details: [ + { label: t("full_name"), value: patientData.name }, + { + label: t("phone_number"), + value: ( + + ), + }, + { + label: t("date_of_birth"), + value: ( + <> + {dayjs(patientData.date_of_birth).format("DD MMM YYYY")} ( + {formatPatientAge(patientData, true)}) + + ), + }, + { + label: t("sex"), + value: patientGender, + }, + , + { + label: t("current_address"), + value: patientData.address, + }, + { + label: t("permanent_address"), + value: patientData.permanent_address, + }, + { + label: t("nationality"), + value: patientData.nationality, + }, + { + label: t("state"), + value: patientData.state, + }, + { + label: t("district"), + value: patientData.district_object?.name, + }, + { + label: t("local_body"), + value: patientData.local_body_object?.name, + }, + { + label: t("ward"), + value: ( + <> + {(patientData.ward_object && + patientData.ward_object.number + + ", " + + patientData.ward_object.name) || + "-"} + + ), + }, + { + label: t("village"), + value: patientData.village, + }, + ], + }, + { + id: "social-profile", + allowEdit: true, + details: [ + { + label: t("occupation"), + value: parseOccupation(patientData.meta_info?.occupation), + }, + { + label: t("ration_card_category"), + value: + !!patientData.ration_card_category && + t(`ration_card__${patientData.ration_card_category}`), + }, + { + label: t("socioeconomic_status"), + value: + patientData.meta_info?.socioeconomic_status && + t( + `SOCIOECONOMIC_STATUS__${patientData.meta_info?.socioeconomic_status}`, + ), + }, + { + label: t("domestic_healthcare_support"), + value: + patientData.meta_info?.domestic_healthcare_support && + t( + `DOMESTIC_HEALTHCARE_SUPPORT__${patientData.meta_info?.domestic_healthcare_support}`, + ), + }, + ], + }, + { + id: "volunteer-contact", + hidden: !patientData.assigned_to_object, + details: [ + , + ], + }, + { + id: "insurance-details", + details: [ +
+ {insuranceDetials?.results.map((insurance) => ( + + ))} + {!!insuranceDetials?.results && + insuranceDetials.results.length === 0 && ( +
+ {t("no_data_found")} +
+ )} + + handleEditClick("insurance-details"), + )} + > + + {t("add_insurance_details")} + +
, + ], + }, + ]; + + return ( +
+
+
+ {data + .filter((s) => !s.hidden) + .map((subtab, i) => ( + + ))} +
+ +
+
+
+
+ {t("patient_status")} +
+
+ +
+
+
+ + navigate( + `/facility/${patientData?.facility}/patient/${id}/update`, + ), + )} + > + + {t("edit_profile")} + +
+
+ {/*
+ {[ + { label: t("abha_number"), value: "-" }, + { label: t("abha_address"), value: "-" }, + ].map((info, i) => ( +
+

+ {info.label}: +

+

+ {info.value} +

+
+ ))} +
*/} +
+ {data + .filter((s) => !s.hidden) + .map((subtab, i) => ( +
+
+
+

{t(`patient__${subtab.id}`)}

+ {subtab.allowEdit && ( + + )} +
+
+ {subtab.details.map((detail, j) => + detail && + typeof detail === "object" && + "label" in detail ? ( +
+
+ {detail.label} +
+
+ {detail.value || "-"} +
+
+ ) : ( + {detail} + ), + )} +
+
+ ))} +
+
+
+
+ ); +}; diff --git a/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx b/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx new file mode 100644 index 00000000000..71865483800 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx @@ -0,0 +1,90 @@ +import React, { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import PaginatedList from "@/CAREUI/misc/PaginatedList"; + +import CircularProgress from "@/components/Common/CircularProgress"; +import Loading from "@/components/Common/Loading"; +import { ConsultationCard } from "@/components/Facility/ConsultationCard"; +import { ConsultationModel } from "@/components/Facility/models"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import { triggerGoal } from "@/Integrations/Plausible"; +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; + +import { PatientProps } from "."; +import { PatientModel } from "../models"; + +const EncounterHistory = (props: PatientProps) => { + const { patientData: initialPatientData, facilityId, id } = props; + const [patientData, setPatientData] = + useState(initialPatientData); + const authUser = useAuthUser(); + + useEffect(() => { + setPatientData(initialPatientData); + }, [initialPatientData]); + + const { t } = useTranslation(); + + const { loading: isLoading, refetch } = useQuery(routes.getPatient, { + pathParams: { + id, + }, + onResponse: ({ res, data }) => { + if (res?.ok && data) { + setPatientData(data); + } + triggerGoal("Patient Profile Viewed", { + facilityId: facilityId, + userId: authUser.id, + }); + }, + }); + + if (isLoading) { + return ; + } + + return ( + + {(_) => ( +
+ + + + +
+
+ {t("no_consultation_history")} +
+
+
+ > + {(item) => ( + + )} + +
+ +
+
+ )} +
+ ); +}; + +export default EncounterHistory; diff --git a/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx b/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx new file mode 100644 index 00000000000..3fb6b60d330 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx @@ -0,0 +1,135 @@ +import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { UserModel } from "@/components/Users/models"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import { ADMIN_USER_TYPES } from "@/common/constants"; + +import { PatientProps } from "."; +import * as Notification from "../../../Utils/Notifications"; +import { PatientModel } from "../models"; + +export const HealthProfileSummary = (props: PatientProps) => { + const { patientData, facilityId, id } = props; + + const authUser = useAuthUser(); + const { t } = useTranslation(); + + const handleEditClick = (sectionId: string) => { + navigate( + `/facility/${facilityId}/patient/${id}/update?section=${sectionId}`, + ); + }; + + let patientMedHis: JSX.Element[] = []; + + if (patientData?.medical_history?.length) { + const medHis = patientData.medical_history; + patientMedHis = medHis + .filter((item) => item.disease !== "NO") + .map((item, idx) => ( +
+
+ {item.disease} +
+
+ {item.details} +
+
+ )); + } + + const canEditPatient = (authUser: UserModel, patientData: PatientModel) => { + return ( + ADMIN_USER_TYPES.includes( + authUser.user_type as (typeof ADMIN_USER_TYPES)[number], + ) || authUser.home_facility_object?.id === patientData.facility + ); + }; + + return ( +
+
+
+
+
+
+ {t("medical")} +
+ +
+ +
+
+
+ {t("present_health")} +
+
+ {patientData.present_health || "-"} +
+
+ +
+
+ {t("ongoing_medications")} +
+
+ {patientData.ongoing_medication || "-"} +
+
+ +
+
+ {t("allergies")} +
+
+ {patientData.allergies || "-"} +
+
+ + {patientData.gender === 2 && patientData.is_antenatal && ( +
+
+ {t("is_pregnant")} +
+
+ {t("yes")} +
+
+ )} + {patientMedHis} +
+
+
+
+ ); +}; diff --git a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx new file mode 100644 index 00000000000..eb298737f3c --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx @@ -0,0 +1,124 @@ +import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { UserModel } from "@/components/Users/models"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import { ADMIN_USER_TYPES } from "@/common/constants"; + +import { formatDateTime } from "@/Utils/utils"; + +import { PatientProps } from "."; +import * as Notification from "../../../Utils/Notifications"; +import { PatientModel } from "../models"; + +export const ImmunisationRecords = (props: PatientProps) => { + const { patientData, facilityId, id } = props; + + const authUser = useAuthUser(); + const { t } = useTranslation(); + + const handleEditClick = (sectionId: string) => { + navigate( + `/facility/${facilityId}/patient/${id}/update?section=${sectionId}`, + ); + }; + + const canEditPatient = (authUser: UserModel, patientData: PatientModel) => { + return ( + ADMIN_USER_TYPES.includes( + authUser.user_type as (typeof ADMIN_USER_TYPES)[number], + ) || authUser.home_facility_object?.id === patientData.facility + ); + }; + + return ( +
+
+
+
+
+

{t("covid_details")}

+ +
+ +
+
+
+ {t("number_of_covid_vaccine_doses")} +
+
+ {patientData.is_vaccinated && patientData.number_of_doses + ? patientData.number_of_doses + : "-"} +
+
+ +
+
+ {t("vaccine_name")} +
+
+ {patientData.is_vaccinated && patientData.vaccine_name + ? patientData.vaccine_name + : "-"} +
+
+ +
+
+ {t("last_vaccinated_on")} +
+
+ {patientData.is_vaccinated && patientData.last_vaccinated_date + ? formatDateTime(patientData.last_vaccinated_date) + : "-"} +
+
+ +
+
+ {t("countries_travelled")} +
+
+ {patientData.countries_travelled && + patientData.countries_travelled.length > 0 + ? patientData.countries_travelled.join(", ") + : "-"} +
+
+ +
+
+ {t("date_of_return")} +
+
+ {patientData.date_of_return + ? formatDateTime(patientData.date_of_return) + : "-"} +
+
+
+
+
+
+ ); +}; diff --git a/src/components/Patient/PatientDetailsTab/Notes.tsx b/src/components/Patient/PatientDetailsTab/Notes.tsx new file mode 100644 index 00000000000..646e97d3bd5 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/Notes.tsx @@ -0,0 +1,184 @@ +import { t } from "i18next"; +import { useEffect, useState } from "react"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import ButtonV2 from "@/components/Common/ButtonV2"; +import DoctorNoteReplyPreviewCard from "@/components/Facility/DoctorNoteReplyPreviewCard"; +import PatientNotesList from "@/components/Facility/PatientNotesList"; +import { + PatientNoteStateType, + PatientNotesModel, +} from "@/components/Facility/models"; +import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; + +import useAuthUser from "@/hooks/useAuthUser"; +import { useMessageListener } from "@/hooks/useMessageListener"; + +import { PATIENT_NOTES_THREADS } from "@/common/constants"; + +import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import routes from "@/Utils/request/api"; +import request from "@/Utils/request/request"; +import { classNames, keysOf } from "@/Utils/utils"; + +import * as Notification from "../../../Utils/Notifications"; + +interface PatientNotesProps { + id: string; + facilityId: string; +} + +const PatientNotes = (props: PatientNotesProps) => { + const { id: patientId, facilityId } = props; + + const authUser = useAuthUser(); + const [thread, setThread] = useState( + authUser.user_type === "Nurse" + ? PATIENT_NOTES_THREADS.Nurses + : PATIENT_NOTES_THREADS.Doctors, + ); + + const [patientActive, setPatientActive] = useState(true); + const [noteField, setNoteField] = useState(""); + const [reload, setReload] = useState(false); + const [reply_to, setReplyTo] = useState( + undefined, + ); + + const initialData: PatientNoteStateType = { + notes: [], + cPage: 1, + totalPages: 1, + }; + const [state, setState] = useState(initialData); + + const onAddNote = async () => { + if (!/\S+/.test(noteField)) { + Notification.Error({ + msg: "Note Should Contain At Least 1 Character", + }); + return; + } + + try { + const { res } = await request(routes.addPatientNote, { + pathParams: { patientId: patientId }, + body: { + note: noteField, + thread, + reply_to: reply_to?.id, + }, + }); + if (res?.status === 201) { + setNoteField(""); + setReload(!reload); + setState({ ...state, cPage: 1 }); + setReplyTo(undefined); + Notification.Success({ msg: "Note added successfully" }); + } + } catch (error) { + Notification.Error({ + msg: "Failed to add note. Please try again.", + }); + } + }; + + useEffect(() => { + async function fetchPatientName() { + if (patientId) { + try { + const { data } = await request(routes.getPatient, { + pathParams: { id: patientId }, + }); + if (data) { + setPatientActive(data.is_active ?? true); + } + } catch (error) { + Notification.Error({ + msg: "Failed to fetch patient status", + }); + } + } + } + fetchPatientName(); + }, [patientId]); + + useMessageListener((data) => { + const message = data?.message; + if ( + (message?.from == "patient/doctor_notes/create" || + message?.from == "patient/doctor_notes/edit") && + message?.facility_id == facilityId && + message?.patient_id == patientId + ) { + setReload(true); + } + }); + + return ( +
+
+
+ {keysOf(PATIENT_NOTES_THREADS).map((current) => ( + + ))} +
+ + setReplyTo(undefined)} + > +
+ setNoteField(e.value)} + className="w-full grow" + errorClassName="hidden" + innerClassName="pr-10" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + /> + + + +
+
+
+
+ ); +}; + +export default PatientNotes; diff --git a/src/components/Patient/PatientDetailsTab/SampleTestHistory.tsx b/src/components/Patient/PatientDetailsTab/SampleTestHistory.tsx new file mode 100644 index 00000000000..b7a907fe662 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/SampleTestHistory.tsx @@ -0,0 +1,107 @@ +import { navigate } from "raviger"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; +import PaginatedList from "@/CAREUI/misc/PaginatedList"; + +import ButtonV2 from "@/components/Common/ButtonV2"; +import CircularProgress from "@/components/Common/CircularProgress"; + +import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import routes from "@/Utils/request/api"; + +import { PatientProps } from "."; +import { SampleTestCard } from "../SampleTestCard"; +import { PatientModel, SampleTestModel } from "../models"; + +export const SampleTestHistory = (props: PatientProps) => { + const { patientData, facilityId, id } = props; + const [_selectedStatus, setSelectedStatus] = useState<{ + status: number; + sample: SampleTestModel | null; + }>({ status: 0, sample: null }); + const [_showAlertMessage, setShowAlertMessage] = useState(false); + + const isPatientInactive = (patientData: PatientModel, facilityId: string) => { + if (!patientData) return true; + return ( + !patientData.is_active || + !( + patientData?.last_consultation && + patientData.last_consultation.facility === facilityId + ) + ); + }; + + const confirmApproval = (status: number, sample: SampleTestModel) => { + setSelectedStatus({ status, sample }); + setShowAlertMessage(true); + }; + + const { t } = useTranslation(); + + return ( +
+
+
+

+ {t("sample_test_history")} +

+ + navigate( + `/facility/${patientData?.facility}/patient/${id}/sample-test`, + ) + } + authorizeFor={NonReadOnlyUsers} + id="sample-request-btn" + > + + + {t("request_sample_test")} + + +
+
+ + + {(_, query) => ( +
+ + + + +
+
+ {t("no_records_found")} +
+
+
+ > + {(item) => ( + + )} + +
+ +
+
+ )} +
+
+ ); +}; diff --git a/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx b/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx new file mode 100644 index 00000000000..b7ef154bf4d --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx @@ -0,0 +1,73 @@ +import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import ButtonV2 from "@/components/Common/ButtonV2"; +import { formatFilter } from "@/components/Resource/ResourceCommons"; +import ShiftingTable from "@/components/Shifting/ShiftingTable"; + +import useFilters from "@/hooks/useFilters"; + +import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; + +import { PatientProps } from "."; +import { PatientModel } from "../models"; + +const ShiftingHistory = (props: PatientProps) => { + const { patientData, facilityId, id } = props; + const { t } = useTranslation(); + const { qParams, Pagination, resultsPerPage } = useFilters({ + cacheBlacklist: ["patient_name"], + }); + + const isPatientInactive = (patientData: PatientModel, facilityId: string) => { + return ( + !patientData.is_active || + !(patientData?.last_consultation?.facility === facilityId) + ); + }; + + const { data: shiftData, loading } = useQuery(routes.listShiftRequests, { + query: { + ...formatFilter({ + ...qParams, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + }), + patient: id, + }, + prefetch: !!id, + }); + + return ( +
+
+

+ {t("shifting_history")} +

+ + navigate(`/facility/${facilityId}/patient/${id}/shift/new`) + } + authorizeFor={NonReadOnlyUsers} + > + + + {t("shift")} + + +
+ +
+ +
{" "} +
+ ); +}; + +export default ShiftingHistory; diff --git a/src/components/Patient/PatientDetailsTab/index.tsx b/src/components/Patient/PatientDetailsTab/index.tsx new file mode 100644 index 00000000000..effc9b667f8 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/index.tsx @@ -0,0 +1,45 @@ +import { PatientModel } from "../models"; +import { Demography } from "./Demography"; +import EncounterHistory from "./EncounterHistory"; +import { HealthProfileSummary } from "./HealthProfileSummary"; +import { ImmunisationRecords } from "./ImmunisationRecords"; +import PatientNotes from "./Notes"; +import { SampleTestHistory } from "./SampleTestHistory"; +import ShiftingHistory from "./ShiftingHistory"; + +export interface PatientProps { + facilityId: string; + id: string; + patientData: PatientModel; +} + +export const patientTabs = [ + { + route: "demography", + component: Demography, + }, + { + route: "encounters", + component: EncounterHistory, + }, + { + route: "health-profile", + component: HealthProfileSummary, + }, + { + route: "immunisation-records", + component: ImmunisationRecords, + }, + { + route: "shift", + component: ShiftingHistory, + }, + { + route: "request-sample-test", + component: SampleTestHistory, + }, + { + route: "patient-notes", + component: PatientNotes, + }, +]; diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx index a7b08bdea58..d6b9844b699 100644 --- a/src/components/Patient/PatientHome.tsx +++ b/src/components/Patient/PatientHome.tsx @@ -1,26 +1,9 @@ -import { navigate } from "raviger"; +import { Link, navigate } from "raviger"; import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import Chip from "@/CAREUI/display/Chip"; -import CareIcon from "@/CAREUI/icons/CareIcon"; -import PaginatedList from "@/CAREUI/misc/PaginatedList"; - -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Button } from "@/components/ui/button"; - -import ButtonV2 from "@/components/Common/ButtonV2"; -import CircularProgress from "@/components/Common/CircularProgress"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import RelativeDateUserMention from "@/components/Common/RelativeDateUserMention"; import UserAutocomplete from "@/components/Common/UserAutocompleteFormField"; -import { ConsultationCard } from "@/components/Facility/ConsultationCard"; -import { ConsultationModel, ShiftingModel } from "@/components/Facility/models"; -import { InsuranceDetialsCard } from "@/components/Patient/InsuranceDetailsCard"; -import { SampleTestCard } from "@/components/Patient/SampleTestCard"; -import { PatientModel, SampleTestModel } from "@/components/Patient/models"; import useAuthUser from "@/hooks/useAuthUser"; @@ -31,98 +14,67 @@ import { SAMPLE_TEST_STATUS, } from "@/common/constants"; -import { triggerGoal } from "@/Integrations/Plausible"; -import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; -import * as Notification from "@/Utils/Notifications"; import dayjs from "@/Utils/dayjs"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useQuery from "@/Utils/request/useQuery"; + +import Chip from "../../CAREUI/display/Chip"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { triggerGoal } from "../../Integrations/Plausible"; +import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; +import * as Notification from "../../Utils/Notifications"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; import { - classNames, - formatDate, formatDateTime, formatName, formatPatientAge, + humanizeStrings, isAntenatal, isPostPartum, -} from "@/Utils/utils"; + relativeDate, +} from "../../Utils/utils"; +import { Avatar } from "../Common/Avatar"; +import ButtonV2 from "../Common/ButtonV2"; +import Loading from "../Common/Loading"; +import Page from "../Common/Page"; +import { SkillModel, UserBareMinimum } from "../Users/models"; +import { patientTabs } from "./PatientDetailsTab"; +import { isPatientMandatoryDataFilled } from "./Utils"; +import { AssignedToObjectModel, PatientModel, SampleTestModel } from "./models"; export const parseOccupation = (occupation: string | undefined) => { return OCCUPATION_TYPES.find((i) => i.value === occupation)?.text; }; -export const PatientHome = (props: any) => { - const { facilityId, id } = props; - const [showShifts, setShowShifts] = useState(false); - const [isShiftClicked, setIsShiftClicked] = useState(false); +export const PatientHome = (props: { + facilityId?: string; + id: string; + page: (typeof patientTabs)[0]["route"]; +}) => { + const { facilityId, id, page } = props; const [patientData, setPatientData] = useState({}); - const [assignedVolunteerObject, setAssignedVolunteerObject] = - useState(null); + const authUser = useAuthUser(); const { t } = useTranslation(); - const [selectedStatus, setSelectedStatus] = useState<{ + const [selectedStatus, _setSelectedStatus] = useState<{ status: number; - sample: any; + sample: SampleTestModel | null; }>({ status: 0, sample: null }); - const [showAlertMessage, setShowAlertMessage] = useState(false); - const [modalFor, setModalFor] = useState(); - const [openAssignVolunteerDialog, setOpenAssignVolunteerDialog] = - useState(false); - const initErr: any = {}; - const errors = initErr; + const [assignedVolunteer, setAssignedVolunteer] = useState< + AssignedToObjectModel | undefined + >(patientData.assigned_to_object); useEffect(() => { - setAssignedVolunteerObject(patientData.assigned_to_object); + setAssignedVolunteer(patientData.assigned_to_object); }, [patientData.assigned_to_object]); - const handleTransferComplete = async (shift: ShiftingModel) => { - if (!shift) return; - await request(routes.completeTransfer, { - pathParams: { externalId: shift.external_id }, - }); - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, - ); - }; - - const { data: insuranceDetials } = useQuery(routes.hcx.policies.list, { - query: { - patient: id, - limit: 1, - }, - }); - - const handlePatientTransfer = async (value: boolean) => { - const dummyPatientData = Object.assign({}, patientData); - dummyPatientData["allow_transfer"] = value; - - await request(routes.patchPatient, { - pathParams: { - id: patientData.id as string, - }, - - body: { allow_transfer: value }, - - onResponse: ({ res }) => { - if ((res || {}).status === 200) { - const dummyPatientData = Object.assign({}, patientData); - dummyPatientData["allow_transfer"] = value; - setPatientData(dummyPatientData); - - Notification.Success({ - msg: "Transfer status updated.", - }); - } - }, - }); - }; - - const handleVolunteerSelect = (volunteer: any) => { - setAssignedVolunteerObject(volunteer.value); - }; + const [showAlertMessage, setShowAlertMessage] = useState(false); + const [openAssignVolunteerDialog, setOpenAssignVolunteerDialog] = + useState(false); + const initErr: any = {}; + const errors = initErr; const { loading: isLoading, refetch } = useQuery(routes.getPatient, { pathParams: { id, @@ -144,20 +96,18 @@ export const PatientHome = (props: any) => { id: patientData.id as string, }, body: { - assigned_to: assignedVolunteerObject - ? assignedVolunteerObject.id - : null, + assigned_to: (assignedVolunteer as UserBareMinimum)?.id || null, }, }); if (res?.ok && data) { setPatientData(data); - if (assignedVolunteerObject) { + if (!assignedVolunteer) { Notification.Success({ - msg: "Volunteer assigned successfully.", + msg: t("volunteer_assigned"), }); } else { Notification.Success({ - msg: "Volunteer unassigned successfully.", + msg: t("volunteer_unassigned"), }); } refetch(); @@ -166,34 +116,41 @@ export const PatientHome = (props: any) => { if (errors["assignedVolunteer"]) delete errors["assignedVolunteer"]; }; - const { loading: isShiftDataLoading, data: activeShiftingData } = useQuery( - routes.listShiftRequests, - { - query: { - patient: id, - }, - prefetch: isShiftClicked, + const consultation = patientData?.last_consultation; + const skillsQuery = useQuery(routes.userListSkill, { + pathParams: { + username: consultation?.treating_physician_object?.username ?? "", }, - ); + prefetch: !!consultation?.treating_physician_object?.username, + }); + const formatSkills = (arr: SkillModel[]) => { + const skills = arr.map((skill) => skill.skill_object.name); + + if (skills.length === 0) { + return ""; + } - const confirmApproval = (status: number, sample: any) => { - setSelectedStatus({ status, sample }); - setShowAlertMessage(true); + if (skills.length <= 3) { + return humanizeStrings(skills); + } + + const [first, second, ...rest] = skills; + return `${first}, ${second} and ${rest.length} other skills...`; }; const handleApproval = async () => { const { status, sample } = selectedStatus; const sampleData = { - id: sample.id, + id: sample?.id, status: status.toString(), - consultation: sample.consultation, + consultation: sample?.consultation, }; const statusName = SAMPLE_TEST_STATUS.find((i) => i.id === status)?.desc; await request(routes.patchSample, { body: sampleData, pathParams: { - id: sample.id, + id: sample?.id || "", }, onResponse: ({ res }) => { if (res?.ok) { @@ -214,351 +171,390 @@ export const PatientHome = (props: any) => { (i) => i.id === patientData.gender, )?.text; - let patientMedHis: any[] = []; - if ( - patientData && - patientData.medical_history && - patientData.medical_history.length - ) { - const medHis = patientData.medical_history; - patientMedHis = medHis - .filter((item) => item.disease !== "NO") - .map((item, idx) => ( -
-
- {item.disease} -
-
- {item.details} -
-
- )); - } - - const isPatientInactive = (patientData: PatientModel, facilityId: string) => { - return ( - !patientData.is_active || - !(patientData?.last_consultation?.facility === facilityId) - ); + const handlePatientTransfer = async (value: boolean) => { + await request(routes.patchPatient, { + pathParams: { + id: patientData.id as string, + }, + body: { allow_transfer: value }, + onResponse: ({ res }) => { + if (res?.status === 200) { + setPatientData((prev) => ({ + ...prev, + allow_transfer: value, + })); + Notification.Success({ + msg: t("transfer_status_updated"), + }); + } + }, + }); }; + const Tab = patientTabs.find((t) => t.route === page)?.component; + return ( handleApproval()} onClose={() => setShowAlertMessage(false)} /> -
-
-
-
- {patientData?.last_consultation?.assigned_to_object && ( -

- - Assigned Doctor: - {formatName( - patientData.last_consultation.assigned_to_object, - )} - - {patientData?.last_consultation?.assigned_to_object - .alt_phone_number && ( - - Video Call - - )} -

- )} - {patientData.assigned_to_object && ( -

- - Assigned Volunteer: - {formatName(patientData.assigned_to_object)} - -

- )} -
-
-
- {(patientData?.facility != patientData?.last_consultation?.facility || - (patientData.is_active && - patientData?.last_consultation?.discharge_date)) && ( - -
- -
- - {t("consultation_not_filed")} - - - - {t("consultation_not_filed_description")} - - +
+
+
+
+
+
+
+
+ +
+
+

+ {patientData.name} +

+

+ {formatPatientAge(patientData, true)},{" "} + {patientGender},{" "} {patientData.blood_group || "-"} +

+
+
+
+
+
+
+ {patientData?.is_active && + (!patientData?.last_consultation || + patientData?.last_consultation?.discharge_date) && ( +
+ + navigate( + `/facility/${patientData?.facility}/patient/${id}/consultation`, + ) + } + > + + + {t("add_consultation")} + + +
+ )} +
+
+
-
- - - )} -
-
-
-
-

- {patientData.name} - {formatPatientAge(patientData, true)} -

-
- {patientData.is_vaccinated && ( - - )} - {patientData.allow_transfer ? ( - - ) : ( - +
+ {isPatientMandatoryDataFilled(patientData) && + (!patientData.last_consultation || + patientData.last_consultation?.facility !== + patientData.facility || + (patientData.last_consultation?.discharge_date && + patientData.is_active)) && ( + + + + + + + )} - {patientData.gender === 2 && ( - <> - {patientData.is_antenatal && - isAntenatal( - patientData.last_menstruation_start_date, - ) && ( - - )} - {isPostPartum(patientData.date_of_delivery) && ( + {patientData.is_vaccinated && ( + + )} + {patientData.allow_transfer ? ( + + ) : ( + + )} + + {patientData.gender === 2 && ( + <> + {patientData.is_antenatal && + isAntenatal( + patientData.last_menstruation_start_date, + ) && ( )} - - )} - {patientData.last_consultation?.is_telemedicine && ( - - )} -
+ {isPostPartum(patientData.date_of_delivery) && ( + + )} + + )} + {patientData.last_consultation?.is_telemedicine && ( + + )} + {patientData.allergies && ( + + )}
-

- - {patientData.facility_object?.name || "-"} -

-

- {patientGender} | {patientData.blood_group || "-"} | Born on{" "} - {patientData.date_of_birth - ? formatDate(patientData.date_of_birth) - : patientData.year_of_birth} -

-
- -
-
- {t("patient_registration__contact")} -
- + +
+
+

+ {t("facility")}: +

+

+ {patientData.facility_object?.name || "-"} +

- {patientData.date_of_return && ( -
-
- Date of Return -
-
- {formatDateTime(patientData.date_of_return)} -
-
- )} - {patientData.is_vaccinated && !!patientData.number_of_doses && ( -
-
- Number of vaccine doses -
-
- {patientData.number_of_doses} -
-
- )} - {patientData.is_vaccinated && patientData.vaccine_name && ( -
-
- Vaccine name -
-
- {patientData.vaccine_name} + + {patientData?.last_consultation?.treating_physician_object && ( +
+

+ {t("treating_doctor")}: +

+
+

+ {formatName( + patientData.last_consultation + .treating_physician_object, + )} +

+

+ {!!skillsQuery.data?.results?.length && + formatSkills(skillsQuery.data?.results)} +

)} - {patientData.is_vaccinated && - patientData.last_vaccinated_date && ( -
-
- Last Vaccinated on -
-
- {formatDateTime(patientData.last_vaccinated_date)} -
-
- )} - {patientData.countries_travelled && - !!patientData.countries_travelled.length && ( -
-
- Countries travelled -
-
- {patientData.countries_travelled.join(", ")} -
-
- )} - {patientData.meta_info?.occupation && ( -
-
- {t("occupation")} -
-
- {parseOccupation(patientData.meta_info.occupation)} + {patientData?.last_consultation?.assigned_to_object && ( +
+

+ {t("assigned_doctor")}: +

+
+

+ {formatName( + patientData.last_consultation.assigned_to_object, + )} +

+ {patientData?.last_consultation?.assigned_to_object + .alt_phone_number && ( + + {" "} + {t("video_call")} + + )}
)} - {patientData.ration_card_category && ( -
-
- {t("ration_card_category")} -
-
- {t(`ration_card__${patientData.ration_card_category}`)} -
+ + {patientData.assigned_to_object && ( +
+

+ {t("assigned_volunteer")}: +

+

+ {formatName(patientData.assigned_to_object)} +

)} - {patientData.meta_info?.socioeconomic_status && ( -
-
- {t("socioeconomic_status")} -
-
- {t( - `SOCIOECONOMIC_STATUS__${patientData.meta_info.socioeconomic_status}`, - )} +
+
+
+
+ +
+
+ {patientTabs.map((tab) => ( + + {t(tab.route)} + + ))} +
+
+ +
+
+ {Tab && ( + + )} +
+
+
+
+
+ {t("actions")} +
+
+
+
+ + navigate(`/patient/${id}/investigation_reports`) + } + > + + + {t("investigations_summary")} + +
-
- )} - {patientData.meta_info?.domestic_healthcare_support && ( -
-
- {t("domestic_healthcare_support")} +
+ + navigate( + `/facility/${patientData?.facility}/patient/${id}/files`, + ) + } + > + + + {t("view_update_patient_files")} + +
-
- {t( - `DOMESTIC_HEALTHCARE_SUPPORT__${patientData.meta_info.domestic_healthcare_support}`, - )} + + {NonReadOnlyUsers && ( +
+ setOpenAssignVolunteerDialog(true)} + disabled={false} + authorizeFor={NonReadOnlyUsers} + className="w-full bg-white font-semibold text-green-800 hover:bg-secondary-200" + size="large" + > + + {" "} + {t("assign_to_volunteer")} + + +
+ )} + +
+ + handlePatientTransfer(!patientData.allow_transfer) + } + authorizeFor={NonReadOnlyUsers} + > + + + {patientData.allow_transfer + ? t("disable_transfer") + : t("allow_transfer")} + +
- )} +
-
-
-
+
+
{patientData.review_time && @@ -567,7 +563,7 @@ export const PatientHome = (props: any) => { 0 && (
{

{(dayjs().isBefore(patientData.review_time) - ? "Review before: " - : "Review Missed: ") + + ? t("review_before") + : t("review_missed")) + + ": " + formatDateTime(patientData.review_time)}

)} -
-
-
-
- Status -
-
- {patientData.is_active ? "LIVE" : "DISCHARGED"} -
-
-
-
- Last Discharged Reason + +
+
+
+
+ {t("last_discharge_reason")}
-
+
{patientData.is_active ? ( "-" ) : !patientData.last_consultation ?.new_discharge_reason ? ( {patientData?.last_consultation?.suggestion === "OP" - ? "OP file closed" - : "UNKNOWN"} + ? t("op_file_closed") + : t("unknown")} ) : patientData.last_consultation ?.new_discharge_reason === DISCHARGE_REASONS.find((i) => i.text == "Expired") ?.id ? ( - EXPIRED + + {t("expired")} + ) : ( DISCHARGE_REASONS.find( (reason) => @@ -623,31 +615,46 @@ export const PatientHome = (props: any) => {
-
-
-
- Created -
-
-
- +
+
+
+ {t("last_updated_by")}{" "} + + {patientData.last_edited?.first_name}{" "} + {patientData.last_edited?.last_name} + +
+
+
+ + {patientData.modified_date + ? formatDateTime(patientData.modified_date) + : "--:--"} + + {patientData.modified_date + ? relativeDate(patientData.modified_date) + : "--:--"}
-
-
- Last Edited -
-
-
- +
+
+ {t("patient_profile_created_by")}{" "} + + {patientData.created_by?.first_name}{" "} + {patientData.created_by?.last_name} + +
+
+
+ + {patientData.created_date + ? formatDateTime(patientData.created_date) + : "--:--"} + + {patientData.modified_date + ? relativeDate(patientData.modified_date) + : "--:--"}
@@ -659,784 +666,40 @@ export const PatientHome = (props: any) => {
navigate(`/death_report/${id}`)} > - Death Report + {t("death_report")}
)} -
- { - const showAllFacilityUsers = [ - "DistrictAdmin", - "StateAdmin", - ]; - if ( - !showAllFacilityUsers.includes(authUser.user_type) && - authUser.home_facility_object?.id !== - patientData.facility - ) { - Notification.Error({ - msg: "Oops! Non-Home facility users don't have permission to perform this action.", - }); - } else { - navigate( - `/facility/${patientData?.facility}/patient/${id}/update`, - ); - } - }} - > - - Update Details - -
-
- - handlePatientTransfer(!patientData.allow_transfer) - } - authorizeFor={NonReadOnlyUsers} - > - - {patientData.allow_transfer - ? "Disable Transfer" - : "Allow Transfer"} - -
-
-
-
-
-
-
{ - setShowShifts(!showShifts); - setIsShiftClicked(true); - }} - > -
{t("shifting")}
- {showShifts ? ( - - ) : ( - - )} -
-
- {activeShiftingData?.count ? ( - activeShiftingData.results.map((shift: ShiftingModel) => ( -
-
-
-
-
-
- {shift.emergency && ( - - Emergency - - )} -
-
-
-
-
- -
- {shift.status} -
- -
-
-
- -
- {(shift.origin_facility_object || {})?.name} -
- -
-
-
- -
- { - ( - shift.shifting_approving_facility_object || - {} - )?.name - } -
- -
-
-
- -
- {(shift.assigned_facility_object || {})?.name || - "Yet to be decided"} -
- -
- -
-
- -
- {formatDateTime(shift.modified_date) || "--"} -
- -
-
-
- -
- - navigate(`/shifting/${shift.external_id}`) - } - > - - All Details - -
- {shift.status === "COMPLETED" && - shift.assigned_facility && ( -
- setModalFor(shift)} - > - {t("transfer_to_receiving_facility")} - - setModalFor(undefined)} - onConfirm={() => handleTransferComplete(shift)} - /> -
- )} -
-
-
- )) - ) : ( -
- {isShiftDataLoading ? "Loading..." : "No Shifting Records!"} -
- )} -
-
- -
-
-
-
- {t("location")} -
-
-
-
- {t("address")} -
-
- {patientData.address || "-"} -
-
-
-
- {t("district")} -
-
- {patientData.district_object?.name || "-"} -
-
-
-
- Village -
-
- {patientData.village || "-"} -
-
-
-
- {t("ward")} -
-
- {(patientData.ward_object && - patientData.ward_object.number + - ", " + - patientData.ward_object.name) || - "-"} -
-
-
-
- State, Country - Pincode -
-
- {patientData?.state_object?.name}, - {patientData.nationality || "-"} - {patientData.pincode} -
-
-
-
- {t("local_body")} -
-
- {patientData.local_body_object?.name || "-"} -
-
-
-
-
- Medical -
- {!patientData.present_health && - !patientData.allergies && - !patientData.ongoing_medication && - !(patientData.gender === 2 && patientData.is_antenatal) && - !patientData.medical_history?.some( - (history) => history.disease !== "NO", - ) && ( -
- No Medical History Available -
- )} -
- {patientData.present_health && ( -
-
- Present Health -
-
- {patientData.present_health} -
-
- )} - {patientData.ongoing_medication && ( -
-
- Ongoing Medications -
-
- {patientData.ongoing_medication} -
-
- )} - {patientData.allergies && ( -
-
- Allergies -
-
- {patientData.allergies} -
-
- )} - {patientData.gender === 2 && patientData.is_antenatal && ( -
-
- Is pregnant -
-
- Yes -
-
- )} - {patientMedHis} -
-
-
- - 1 - } - /> -
-
-
-
-
navigate(`/patient/${id}/investigation_reports`)} - > -
-
- - - -
-
-

- Investigations Summary -

-
-
-
-
- navigate( - `/facility/${patientData?.facility}/patient/${id}/files/`, - ) - } - > -
-
- - - -
-
-

- View/Upload Patient Files -

-
-
-
-
{ - if (!isPatientInactive(patientData, facilityId)) { - navigate(`/facility/${facilityId}/patient/${id}/shift/new`); - } - }} - > -
-
- - - -
- -
-

- Shift Patient -

-
-
-
-
{ - if (!isPatientInactive(patientData, facilityId)) { - navigate( - `/facility/${patientData?.facility}/patient/${id}/sample-test`, - ); - } - }} - > -
-
- - - -
-
-

- Request Sample Test -

-
-
-
-
- navigate( - `/facility/${patientData?.facility}/patient/${id}/notes`, - ) - } - > -
-
- - - -
-
-

- View Patient Notes -

-
-
-
-
{ - if (!isPatientInactive(patientData, facilityId)) { - setOpenAssignVolunteerDialog(true); - } - }} - > -
-
- - - -
-
-

- Assign to a volunteer -

-
-
-
-
-
-
-
-
-
- - navigate(`/patient/${id}/investigation_reports`) - } - > - - - Investigations Summary - - -
-
- - navigate( - `/facility/${patientData?.facility}/patient/${id}/files`, - ) - } - > - - - View/Upload Patient Files - - -
-
- - navigate( - `/facility/${facilityId}/patient/${id}/shift/new`, - ) - } - authorizeFor={NonReadOnlyUsers} - > - - - Shift Patient - - -
-
- - navigate( - `/facility/${patientData?.facility}/patient/${id}/sample-test`, - ) - } - authorizeFor={NonReadOnlyUsers} - id="sample-request-btn" - > - - - Request Sample Test - - -
-
- - navigate( - `/facility/${patientData?.facility}/patient/${id}/notes`, - ) - } - > - - - View Patient Notes - - -
-
- setOpenAssignVolunteerDialog(true)} - disabled={false} - authorizeFor={NonReadOnlyUsers} - > - - - Assign to a volunteer - - -
-
-
-
-
+
setOpenAssignVolunteerDialog(false)} description={
setAssignedVolunteer(user.value)} userType={"Volunteer"} name={"assign_volunteer"} error={errors.assignedVolunteer} />
} - action="Assign" + action={t("assign")} onConfirm={handleAssignedVolunteer} /> - -
-

- Consultation History -

- - - {(_) => ( -
- - - - -
-
- No Consultation History Available -
-
-
- > - {(item) => ( - - )} - -
- -
-
- )} -
-
- -
-

- Sample Test History -

- - {(_, query) => ( -
- - - - -
-
- No Sample Test History Available -
-
-
- > - {(item) => ( - - )} - -
- -
-
- )} -
-
); }; diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index e415d928a0d..486e8b103fd 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -538,7 +538,7 @@ export default function PatientInfoCard(props: PatientInfoCardProps) { )} {!!consultation?.discharge_date && (
-
+
Discharge Reason
diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 35d93214385..5828517adf9 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -2,7 +2,7 @@ import careConfig from "@careConfig"; import { startCase, toLower } from "lodash-es"; import { debounce } from "lodash-es"; import { navigate } from "raviger"; -import { useCallback, useReducer, useRef, useState } from "react"; +import { useCallback, useEffect, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -232,6 +232,25 @@ export const PatientRegister = (props: PatientRegisterProps) => { const headerText = !id ? "Add Details of Patient" : "Update Patient Details"; const buttonText = !id ? "Add Patient" : "Save Details"; + useEffect(() => { + const getQueryParams = () => { + const params = new URLSearchParams(window.location.search); + return { + section: params.get("section"), + }; + }; + + const { section } = getQueryParams(); + if (section) { + setTimeout(() => { + const element = document.getElementById(section); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } + }, 2000); + } + }, []); + const fetchDistricts = useCallback(async (id: number) => { if (id > 0) { setIsDistrictLoading(true); @@ -1402,7 +1421,7 @@ export const PatientRegister = (props: PatientRegisterProps) => {
{field("nationality").value === "India" && ( -
+
{
)} -
+
{
-
+

Medical History

@@ -1661,7 +1683,10 @@ export const PatientRegister = (props: PatientRegisterProps) => {
-
+

Insurance Details diff --git a/src/components/Patient/SampleTestCard.tsx b/src/components/Patient/SampleTestCard.tsx index a0303705eb7..c22155c494e 100644 --- a/src/components/Patient/SampleTestCard.tsx +++ b/src/components/Patient/SampleTestCard.tsx @@ -16,8 +16,8 @@ import request from "@/Utils/request/request"; import { formatDateTime } from "@/Utils/utils"; interface SampleDetailsProps { - facilityId: number; - patientId: number; + facilityId: string; + patientId: string; itemData: SampleTestModel; refetch: () => void; handleApproval: (status: number, sample: SampleTestModel) => void; diff --git a/src/components/Patient/models.tsx b/src/components/Patient/models.tsx index 15063be4e9c..85614e707d5 100644 --- a/src/components/Patient/models.tsx +++ b/src/components/Patient/models.tsx @@ -136,7 +136,7 @@ export interface PatientModel { is_declared_positive?: boolean; last_edited?: UserBareMinimum; created_by?: UserBareMinimum; - assigned_to?: { first_name?: string; username?: string; last_name?: string }; + assigned_to?: number | null; assigned_to_object?: AssignedToObjectModel; occupation?: Occupation; meta_info?: PatientMeta; diff --git a/src/components/Shifting/ShiftingBlock.tsx b/src/components/Shifting/ShiftingBlock.tsx index 09bbcfb3b9f..db173b2c6d8 100644 --- a/src/components/Shifting/ShiftingBlock.tsx +++ b/src/components/Shifting/ShiftingBlock.tsx @@ -13,7 +13,7 @@ import { classNames, formatDateTime, formatName } from "@/Utils/utils"; export default function ShiftingBlock(props: { shift: ShiftingModel; - onTransfer: () => unknown; + onTransfer?: () => unknown; }) { const { shift, onTransfer } = props; const { t } = useTranslation(); @@ -123,25 +123,28 @@ export default function ShiftingBlock(props: { {t("all_details")} - {shift.status === "COMPLETED" && shift.assigned_facility && ( - <> - - - )} + {shift.status === "COMPLETED" && + shift.assigned_facility && + onTransfer && ( + <> + + + )}

); diff --git a/src/components/Shifting/ShiftingList.tsx b/src/components/Shifting/ShiftingList.tsx index db7ec691f79..7161ea29441 100644 --- a/src/components/Shifting/ShiftingList.tsx +++ b/src/components/Shifting/ShiftingList.tsx @@ -1,29 +1,25 @@ -import careConfig from "@careConfig"; import { navigate } from "raviger"; -import { useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; import ButtonV2 from "@/components/Common/ButtonV2"; -import ConfirmDialog from "@/components/Common/ConfirmDialog"; import { ExportButton } from "@/components/Common/Export"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; -import { ShiftingModel } from "@/components/Facility/models"; import SearchInput from "@/components/Form/SearchInput"; import BadgesList from "@/components/Shifting/ShiftingBadges"; import { formatFilter } from "@/components/Shifting/ShiftingCommons"; import ListFilter from "@/components/Shifting/ShiftingFilters"; -import useAuthUser from "@/hooks/useAuthUser"; import useFilters from "@/hooks/useFilters"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useQuery from "@/Utils/request/useQuery"; -import { formatDateTime } from "@/Utils/utils"; + +import ShiftingTable from "./ShiftingTable"; export default function ListView() { const { @@ -35,31 +31,7 @@ export default function ListView() { resultsPerPage, } = useFilters({ cacheBlacklist: ["patient_name"] }); - const [modalFor, setModalFor] = useState<{ - externalId: string | undefined; - loading: boolean; - }>({ - externalId: undefined, - loading: false, - }); - - const authUser = useAuthUser(); const { t } = useTranslation(); - - const handleTransferComplete = async (shift: ShiftingModel) => { - setModalFor({ ...modalFor, loading: true }); - try { - await request(routes.completeTransfer, { - pathParams: { externalId: shift.external_id }, - }); - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, - ); - } catch (error) { - setModalFor({ externalId: undefined, loading: false }); - } - }; - const { data: shiftData, loading, @@ -71,179 +43,6 @@ export default function ListView() { }), }); - const showShiftingCardList = (data: ShiftingModel[]) => { - if (loading) { - return ; - } - if (data && !data.length) { - return ( -
- {t("no_patients_to_show")} -
- ); - } - - return data.map((shift: ShiftingModel) => ( -
-
-
-
- {shift.patient_object.name} -
-
- {shift.patient_object.age} -
-
- -
-
-
- -
- {shift.patient_object.phone_number || ""} -
- -
-
-
- -
- {shift.patient_object.address || "--"} -
- -
-
- -
-
-
- -
{shift.status}
- - -
- {shift.emergency && ( - - {t("emergency")} - - )} -
-
- -
-
- -
- {formatDateTime(shift.modified_date) || "--"} -
- -
-
- -
-
- -
- {shift.origin_facility_object?.name} -
- - - {careConfig.wartimeShifting && ( -
- -
- {shift.shifting_approving_facility_object?.name} -
- - )} - -
- -
- {shift.assigned_facility_external || - shift.assigned_facility_object?.name || - t("yet_to_be_decided")} -
- -
-
- navigate(`/shifting/${shift.external_id}`)} - variant="secondary" - border - className="w-full" - > - {t("all_details")} - - {shift.status === "COMPLETED" && shift.assigned_facility && ( -
- - setModalFor({ - externalId: shift.external_id, - loading: false, - }) - } - > - {t("transfer_to_receiving_facility")} - - - setModalFor({ externalId: undefined, loading: false }) - } - onConfirm={() => handleTransferComplete(shift)} - /> -
- )} -
-
-
- )); - }; - return ( +
{loading ? ( @@ -307,27 +107,7 @@ export default function ListView() { {t("refresh_list")}
-
-
-
- {t("patients")} -
-
- {t("contact_info")} -
-
- {t("consent__status")} -
-
- {t("facilities")} -
-
- {t("LOG_UPDATE_FIELD_LABEL__action")} -
-
-
{showShiftingCardList(shiftData?.results || [])}
-
- +
diff --git a/src/components/Shifting/ShiftingTable.tsx b/src/components/Shifting/ShiftingTable.tsx new file mode 100644 index 00000000000..de186a13d56 --- /dev/null +++ b/src/components/Shifting/ShiftingTable.tsx @@ -0,0 +1,261 @@ +import careConfig from "@careConfig"; +import { navigate } from "raviger"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import { cn } from "@/lib/utils"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import routes from "@/Utils/request/api"; +import request from "@/Utils/request/request"; +import { formatDateTime } from "@/Utils/utils"; + +import ButtonV2 from "../Common/ButtonV2"; +import ConfirmDialog from "../Common/ConfirmDialog"; +import Loading from "../Common/Loading"; +import { ShiftingModel } from "../Facility/models"; + +export default function ShiftingTable(props: { + data?: ShiftingModel[]; + loading?: boolean; + hidePatient?: boolean; +}) { + const { data, loading, hidePatient } = props; + + const { t } = useTranslation(); + const authUser = useAuthUser(); + const [modalFor, setModalFor] = useState<{ + externalId: string | undefined; + loading: boolean; + }>({ + externalId: undefined, + loading: false, + }); + + const handleTransferComplete = async (shift: ShiftingModel) => { + setModalFor({ ...modalFor, loading: true }); + try { + await request(routes.completeTransfer, { + pathParams: { externalId: shift.external_id }, + }); + navigate( + `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, + ); + } catch (error) { + setModalFor({ externalId: undefined, loading: false }); + } + }; + + if (loading) { + return ; + } + if (data && !data.length) { + return ( +
+ {t("no_results_found")} +
+ ); + } + + return ( +
+
+ {!hidePatient && ( +
+ {t("patients")} +
+ )} +
+ {t("contact_info")} +
+
+ {t("consent__status")} +
+
+ {t("facilities")} +
+
+ {t("LOG_UPDATE_FIELD_LABEL__action")} +
+
+
+ {data?.map((shift: ShiftingModel) => ( +
+
+ {!hidePatient && ( +
+
+ {shift.patient_object.name} +
+
+ {shift.patient_object.age} +
+
+ )} + +
+
+
+ +
+ {shift.patient_object.phone_number || ""} +
+ +
+
+
+ +
+ {shift.patient_object.address || "--"} +
+ +
+
+ +
+
+
+ +
{shift.status}
+ + +
+ {shift.emergency && ( + + {t("emergency")} + + )} +
+
+ +
+
+ +
+ {formatDateTime(shift.modified_date) || "--"} +
+ +
+
+ +
+
+ +
+ {shift.origin_facility_object?.name} +
+ + + {careConfig.wartimeShifting && ( +
+ +
+ {shift.shifting_approving_facility_object?.name} +
+ + )} + +
+ +
+ {shift.assigned_facility_external || + shift.assigned_facility_object?.name || + t("yet_to_be_decided")} +
+ +
+
+ navigate(`/shifting/${shift.external_id}`)} + variant="secondary" + border + className="w-full" + > + {t("all_details")} + + {shift.status === "COMPLETED" && shift.assigned_facility && ( +
+ + setModalFor({ + externalId: shift.external_id, + loading: false, + }) + } + > + {t("transfer_to_receiving_facility")} + + + setModalFor({ externalId: undefined, loading: false }) + } + onConfirm={() => handleTransferComplete(shift)} + /> +
+ )} +
+
+
+ ))} +
+
+ ); +} diff --git a/src/components/Users/ManageUsers.tsx b/src/components/Users/ManageUsers.tsx index 0b6a9149dbb..a1c00e5eaef 100644 --- a/src/components/Users/ManageUsers.tsx +++ b/src/components/Users/ManageUsers.tsx @@ -250,7 +250,7 @@ export default function ManageUsers() {
{formatName(user)} From e309fcd9f9da1de5c2c1e542c362c6d17ce4a044 Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Wed, 27 Nov 2024 14:41:19 +0530 Subject: [PATCH 08/37] Fix unable to use camera switch button (#9159) --- src/components/Files/CameraCaptureDialog.tsx | 66 ++++++++++++-------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/src/components/Files/CameraCaptureDialog.tsx b/src/components/Files/CameraCaptureDialog.tsx index 91b158bcb44..acfbaf96ec4 100644 --- a/src/components/Files/CameraCaptureDialog.tsx +++ b/src/components/Files/CameraCaptureDialog.tsx @@ -7,7 +7,7 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import ButtonV2, { Submit } from "@/components/Common/ButtonV2"; import DialogModal from "@/components/Common/Dialog"; -import useWindowDimensions from "@/hooks/useWindowDimensions"; +import useBreakpoints from "@/hooks/useBreakpoints"; import * as Notify from "@/Utils/Notifications"; @@ -19,34 +19,56 @@ export interface CameraCaptureDialogProps { export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { const { show, onHide, onCapture } = props; + const isLaptopScreen = useBreakpoints({ lg: true, default: false }); - const [cameraFacingFront, setCameraFacingFront] = useState(true); + const [cameraFacingMode, setCameraFacingMode] = useState( + isLaptopScreen ? "user" : "environment", + ); const [previewImage, setPreviewImage] = useState(null); const webRef = useRef(null); const videoConstraints = { width: { ideal: 4096 }, height: { ideal: 2160 }, - facingMode: "user", + facingMode: cameraFacingMode, }; useEffect(() => { if (!show) return; - navigator.mediaDevices.getUserMedia({ video: true }).catch(() => { - Notify.Warn({ - msg: t("camera_permission_denied"), + let stream: MediaStream | null = null; + + navigator.mediaDevices + .getUserMedia({ video: { facingMode: cameraFacingMode } }) + .then((mediaStream) => { + stream = mediaStream; + }) + .catch(() => { + Notify.Warn({ + msg: t("camera_permission_denied"), + }); + onHide(); }); - onHide(); - }); - }, [show]); - const handleSwitchCamera = useCallback(() => { - const supportedConstraints = - navigator.mediaDevices.getSupportedConstraints(); - if ( - !isLaptopScreen && - typeof supportedConstraints.facingMode === "string" && - (supportedConstraints.facingMode as string).includes("environment") - ) { - setCameraFacingFront((prevState) => !prevState); + + return () => { + if (stream) { + stream.getTracks().forEach((track) => { + track.stop(); + }); + } + }; + }, [show, cameraFacingMode, onHide]); + + const handleSwitchCamera = useCallback(async () => { + const devices = await navigator.mediaDevices.enumerateDevices(); + const videoInputs = devices.filter( + (device) => device.kind === "videoinput", + ); + const backCamera = videoInputs.some((device) => + device.label.toLowerCase().includes("back"), + ); + if (!isLaptopScreen && backCamera) { + setCameraFacingMode((prevMode) => + prevMode === "environment" ? "user" : "environment", + ); } else { Notify.Warn({ msg: t("switch_camera_is_not_available"), @@ -54,10 +76,6 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { } }, []); - const { width } = useWindowDimensions(); - const LaptopScreenBreakpoint = 640; - const isLaptopScreen = width >= LaptopScreenBreakpoint ? true : false; - const captureImage = () => { setPreviewImage(webRef.current.getScreenshot()); const canvas = webRef.current.getCanvas(); @@ -70,10 +88,6 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }); }; - const cameraFacingMode = cameraFacingFront - ? "user" - : { exact: "environment" }; - return ( Date: Wed, 27 Nov 2024 15:04:43 +0530 Subject: [PATCH 09/37] Fix Camera Capture on Retake and Close (#9180) --- src/components/Files/AudioCaptureDialog.tsx | 13 +++++++++++-- src/components/Files/CameraCaptureDialog.tsx | 7 ++++++- src/hooks/useFileUpload.tsx | 11 +++++++---- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/components/Files/AudioCaptureDialog.tsx b/src/components/Files/AudioCaptureDialog.tsx index 5512fad9dc6..d198ac7db16 100644 --- a/src/components/Files/AudioCaptureDialog.tsx +++ b/src/components/Files/AudioCaptureDialog.tsx @@ -1,5 +1,5 @@ import { Link } from "raviger"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -26,6 +26,7 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const { show, onHide, onCapture, autoRecord = false } = props; const [status, setStatus] = useState(null); + const mediaStreamRef = useRef(null); const { t } = useTranslation(); const { audioURL, resetRecording, startRecording, stopRecording } = @@ -42,7 +43,8 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const handleStartRecording = () => { navigator.mediaDevices .getUserMedia({ audio: true }) - .then(() => { + .then((stream) => { + mediaStreamRef.current = stream; setStatus("RECORDING"); startRecording(); timer.start(); @@ -105,6 +107,13 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { if (autoRecord && show && status === "RECORDING") { handleStartRecording(); } + + return () => { + if (mediaStreamRef.current) { + mediaStreamRef.current.getTracks().forEach((track) => track.stop()); + mediaStreamRef.current = null; + } + }; }, [autoRecord, status, show]); return ( diff --git a/src/components/Files/CameraCaptureDialog.tsx b/src/components/Files/CameraCaptureDialog.tsx index acfbaf96ec4..f81d854c559 100644 --- a/src/components/Files/CameraCaptureDialog.tsx +++ b/src/components/Files/CameraCaptureDialog.tsx @@ -15,10 +15,11 @@ export interface CameraCaptureDialogProps { show: boolean; onHide: () => void; onCapture: (file: File, fileName: string) => void; + onResetCapture: () => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { - const { show, onHide, onCapture } = props; + const { show, onHide, onCapture, onResetCapture } = props; const isLaptopScreen = useBreakpoints({ lg: true, default: false }); const [cameraFacingMode, setCameraFacingMode] = useState( @@ -160,6 +161,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { { setPreviewImage(null); + onResetCapture(); }} className="m-2" > @@ -183,6 +185,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { variant="secondary" onClick={() => { setPreviewImage(null); + onResetCapture(); onHide(); }} className="m-2" @@ -221,6 +224,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { { setPreviewImage(null); + onResetCapture(); }} > {t("retake")} @@ -242,6 +246,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { variant="secondary" onClick={() => { setPreviewImage(null); + onResetCapture(); onHide(); }} > diff --git a/src/hooks/useFileUpload.tsx b/src/hooks/useFileUpload.tsx index 56f0d337a5e..d580ad7d557 100644 --- a/src/hooks/useFileUpload.tsx +++ b/src/hooks/useFileUpload.tsx @@ -249,6 +249,11 @@ export default function useFileUpload( setUploadFileNames([]); }; + const clearFiles = () => { + setFiles([]); + setUploadFileNames([]); + }; + const Dialogues = ( <> { setFiles((prev) => [...prev, file]); }} + onResetCapture={clearFiles} /> prev.filter((_, i) => i !== index)); setUploadFileNames((prev) => prev.filter((_, i) => i !== index)); }, - clearFiles: () => { - setFiles([]); - setUploadFileNames([]); - }, + clearFiles, uploading, }; } From 8b02709b3cadb01a5f7d82f00ae2cd7cbb5601b0 Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 27 Nov 2024 18:36:02 +0530 Subject: [PATCH 10/37] Fix duplicated content in patient consultation (#9221) --- src/components/Facility/ConsultationDetails/index.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/components/Facility/ConsultationDetails/index.tsx b/src/components/Facility/ConsultationDetails/index.tsx index 08427a5e6ec..4834ba40198 100644 --- a/src/components/Facility/ConsultationDetails/index.tsx +++ b/src/components/Facility/ConsultationDetails/index.tsx @@ -397,9 +397,6 @@ export const ConsultationDetails = (props: any) => {
-
- -
Date: Wed, 27 Nov 2024 18:39:44 +0530 Subject: [PATCH 11/37] Solved the problem of overlapping notifications during updates (#9207) --- package-lock.json | 2 +- src/components/Users/UserProfile.tsx | 46 +++++++++++++++------------- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 63af1164ef0..5c703af8fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20862,4 +20862,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/components/Users/UserProfile.tsx b/src/components/Users/UserProfile.tsx index 15cedafab2e..7b0df12cac0 100644 --- a/src/components/Users/UserProfile.tsx +++ b/src/components/Users/UserProfile.tsx @@ -36,7 +36,6 @@ import request from "@/Utils/request/request"; import uploadFile from "@/Utils/request/uploadFile"; import useQuery from "@/Utils/request/useQuery"; import { - classNames, dateQueryString, formatDate, formatDisplayName, @@ -1006,31 +1005,34 @@ export default function UserProfile() {

- {updateStatus.isUpdateAvailable && ( - - +
+ {updateStatus.isChecking ? ( + // While checking for updates +
- - {t("update_available")} + + {t("checking_for_update")}
- - )} -
- {!updateStatus.isUpdateAvailable && ( - - {" "} + ) : updateStatus.isUpdateAvailable ? ( + // When an update is available + + +
+ + {t("update_available")} +
+
+
+ ) : ( + // Default state to check for updates +
- - {updateStatus.isChecking - ? t("checking_for_update") - : t("check_for_update")} + + {t("check_for_update")}
)} From 3e1960722016e652ca57603a3202dc99b18451b1 Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 27 Nov 2024 20:38:51 +0530 Subject: [PATCH 12/37] Add gitattributes to enforce proper line endings (#9223) --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..2125666142e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file From 532945766d2b76bd64dc4ce7ced9aceedece313d Mon Sep 17 00:00:00 2001 From: Rithvik Nishad Date: Wed, 27 Nov 2024 21:12:10 +0530 Subject: [PATCH 13/37] Fix loading state management in useQuery when requests are aborted (#9222) --- src/Utils/request/request.ts | 3 +++ src/Utils/request/useMutation.ts | 10 ++++++---- src/Utils/request/useQuery.ts | 6 ++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Utils/request/request.ts b/src/Utils/request/request.ts index d036ad49750..847b7c9ba60 100644 --- a/src/Utils/request/request.ts +++ b/src/Utils/request/request.ts @@ -70,6 +70,9 @@ export default async function request( return result; } catch (error: any) { result = { error, res: undefined, data: undefined }; + if (error.name === "AbortError") { + return result; + } } } diff --git a/src/Utils/request/useMutation.ts b/src/Utils/request/useMutation.ts index fbc6edc3940..7c368f5e45d 100644 --- a/src/Utils/request/useMutation.ts +++ b/src/Utils/request/useMutation.ts @@ -10,7 +10,7 @@ import { mergeRequestOptions } from "@/Utils/request/utils"; export default function useMutation( route: MutationRoute, - options: RequestOptions, + options: RequestOptions, ) { const [response, setResponse] = React.useState>(); const [isProcessing, setIsProcessing] = React.useState(false); @@ -18,7 +18,7 @@ export default function useMutation( const controllerRef = React.useRef(); const runQuery = React.useCallback( - async (overrides?: RequestOptions) => { + async (overrides?: RequestOptions) => { controllerRef.current?.abort(); const controller = new AbortController(); @@ -31,8 +31,10 @@ export default function useMutation( setIsProcessing(true); const response = await request(route, { ...resolvedOptions, controller }); - setResponse(response); - setIsProcessing(false); + if (response.error?.name !== "AbortError") { + setResponse(response); + setIsProcessing(false); + } return response; }, [route, JSON.stringify(options)], diff --git a/src/Utils/request/useQuery.ts b/src/Utils/request/useQuery.ts index 109ff0bb2f1..5f4c10a289e 100644 --- a/src/Utils/request/useQuery.ts +++ b/src/Utils/request/useQuery.ts @@ -37,8 +37,10 @@ export default function useQuery( setLoading(true); const response = await request(route, { ...resolvedOptions, controller }); - setResponse(response); - setLoading(false); + if (response.error?.name !== "AbortError") { + setResponse(response); + setLoading(false); + } return response; }, [route, JSON.stringify(options)], From 6bea49af43e0cc75f23d4bd1a14bb5a9119a9c9c Mon Sep 17 00:00:00 2001 From: Tanuj Nainwal <125687187+Tanuj1718@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:44:57 +0530 Subject: [PATCH 14/37] updated crowdin.yml file (#9211) Co-authored-by: Tanuj Nainwal --- crowdin.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crowdin.yml b/crowdin.yml index 0ab1a042711..fdcb6bcd32d 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,5 +1,6 @@ files: - - source: /src/Locale/en/*.json - translation: /src/Locale/%two_letters_code%/%original_file_name% + - source: /public/locale/{{lang}}.json + translation: /public/locale/%two_letters_code%/%original_file_name% bundles: - 2 + From fa5fa25bad596c4863d38864d0e1ecb105995cf0 Mon Sep 17 00:00:00 2001 From: Shaurya Gupta Date: Thu, 28 Nov 2024 15:13:54 +0530 Subject: [PATCH 15/37] Fix: Breadcrumbs redirection to patient edit consultation form (#8985) --- src/components/Facility/ConsultationDetails/index.tsx | 4 ++++ src/components/Patient/PatientConsentRecords.tsx | 6 +++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Facility/ConsultationDetails/index.tsx b/src/components/Facility/ConsultationDetails/index.tsx index 4834ba40198..191d0156d03 100644 --- a/src/components/Facility/ConsultationDetails/index.tsx +++ b/src/components/Facility/ConsultationDetails/index.tsx @@ -213,6 +213,10 @@ export const ConsultationDetails = (props: any) => { crumbsReplacements={{ [facilityId]: { name: patientData?.facility_object?.name }, [patientId]: { name: patientData?.name }, + consultation: { + name: "Consultation", + uri: `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/update`, + }, [consultationId]: { name: consultationData.suggestion === "A" diff --git a/src/components/Patient/PatientConsentRecords.tsx b/src/components/Patient/PatientConsentRecords.tsx index 162dcb2ff77..cbeb56b894f 100644 --- a/src/components/Patient/PatientConsentRecords.tsx +++ b/src/components/Patient/PatientConsentRecords.tsx @@ -112,6 +112,10 @@ export default function PatientConsentRecords(props: { crumbsReplacements={{ [facilityId]: { name: patient?.facility_object?.name }, [patientId]: { name: patient?.name }, + consultation: { + name: "Consultation", + uri: `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/update`, + }, [consultationId]: { name: patient?.last_consultation?.suggestion === "A" @@ -121,7 +125,7 @@ export default function PatientConsentRecords(props: { : patient?.last_consultation?.suggestion_text, }, }} - backUrl={`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/`} + backUrl={`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/update`} > {fileUpload.Dialogues} {fileManager.Dialogues} From fc2ac3b0f66775c05e36a012823125228271fdfd Mon Sep 17 00:00:00 2001 From: JavidSumra <112365664+JavidSumra@users.noreply.github.com> Date: Thu, 28 Nov 2024 16:11:16 +0530 Subject: [PATCH 16/37] Fixes PR thank you msg. from potentially tagging the PR author twice (#9228) --- .github/workflows/thank-you.yml | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/.github/workflows/thank-you.yml b/.github/workflows/thank-you.yml index 192f71d1a64..b91642dd24f 100644 --- a/.github/workflows/thank-you.yml +++ b/.github/workflows/thank-you.yml @@ -18,26 +18,20 @@ jobs: uses: actions/github-script@v6.3.3 with: script: | - const thankyouNote = 'Your efforts have helped advance digital healthcare and TeleICU systems. :rocket: Thank you for taking the time out to make CARE better. We hope you continue to innovate and contribute; your impact is immense! :raised_hands:' + const thankyouNote = 'Your efforts have helped advance digital healthcare and TeleICU systems. :rocket: Thank you for taking the time out to make CARE better. We hope you continue to innovate and contribute; your impact is immense! :raised_hands:'; const options = { issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - } + }; - const result = await github.rest.issues.get({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }) - - const { assignees, user } = result.data + const { data : { assignees, user } } = await github.rest.issues.get(options); - const assignees_tagged = assignees.map((user) => '@' + user.login).join(' ') - const owner_tagged = '@' + user.login + const taggedUsers = [...new Set( + assignees.map(u => "@"+u.login).concat("@"+user.login) + )].join(" ") - if (assignees.length == 0) { - await github.rest.issues.createComment({ ...options, body: `${owner_tagged} ${thankyouNote}` }) - } else { - await github.rest.issues.createComment({ ...options, body: `${assignees_tagged} ${owner_tagged} ${thankyouNote}` }) - } + await github.rest.issues.createComment({ + ...options, + body: `${taggedUsers} ${thankyouNote}` + }); From c4071204a02f029542ffe19e9de9986e02e6dfb8 Mon Sep 17 00:00:00 2001 From: Sarvesh <142646866+sarvesh-official@users.noreply.github.com> Date: Fri, 29 Nov 2024 10:35:14 +0530 Subject: [PATCH 17/37] Implemented Next and Previous button for files preview (#9196) --- src/components/Common/FilePreviewDialog.tsx | 113 ++++++++++++++++++-- src/components/Files/FileUpload.tsx | 12 ++- src/components/Patient/FileUploadPage.tsx | 2 - src/hooks/useFileManager.tsx | 9 +- 4 files changed, 123 insertions(+), 13 deletions(-) diff --git a/src/components/Common/FilePreviewDialog.tsx b/src/components/Common/FilePreviewDialog.tsx index 4b7a07bd0cd..fa8a333515d 100644 --- a/src/components/Common/FilePreviewDialog.tsx +++ b/src/components/Common/FilePreviewDialog.tsx @@ -4,6 +4,7 @@ import { SetStateAction, Suspense, lazy, + useEffect, useState, } from "react"; import { useTranslation } from "react-i18next"; @@ -15,6 +16,8 @@ import CircularProgress from "@/components/Common/CircularProgress"; import DialogModal from "@/components/Common/Dialog"; import { StateInterface } from "@/components/Files/FileUpload"; +import { FileUploadModel } from "../Patient/models"; + const PDFViewer = lazy(() => import("@/components/Common/PDFViewer")); export const zoom_values = [ @@ -40,6 +43,9 @@ type FilePreviewProps = { className?: string; titleAction?: ReactNode; fixedWidth?: boolean; + uploadedFiles?: FileUploadModel[]; + loadFile?: (file: FileUploadModel, associating_id: string) => void; + currentIndex: number; }; const previewExtensions = [ @@ -56,12 +62,28 @@ const previewExtensions = [ ]; const FilePreviewDialog = (props: FilePreviewProps) => { - const { show, onClose, file_state, setFileState, downloadURL, fileUrl } = - props; + const { + show, + onClose, + file_state, + setFileState, + downloadURL, + fileUrl, + uploadedFiles, + loadFile, + currentIndex, + } = props; const { t } = useTranslation(); const [page, setPage] = useState(1); const [numPages, setNumPages] = useState(1); + const [index, setIndex] = useState(currentIndex); + + useEffect(() => { + if (uploadedFiles && show) { + setIndex(currentIndex); + } + }, [uploadedFiles, show, currentIndex]); const handleZoomIn = () => { const checkFull = file_state.zoom === zoom_values.length; @@ -79,9 +101,27 @@ const FilePreviewDialog = (props: FilePreviewProps) => { }); }; + const handleNext = (newIndex: number) => { + if ( + !uploadedFiles?.length || + !loadFile || + newIndex < 0 || + newIndex >= uploadedFiles.length + ) + return; + + const nextFile = uploadedFiles[newIndex]; + if (!nextFile?.id) return; + + const associating_id = nextFile.associating_id || ""; + loadFile(nextFile, associating_id); + setIndex(newIndex); + }; + const handleClose = () => { setPage(1); setNumPages(1); + setIndex(-1); onClose?.(); }; @@ -102,6 +142,21 @@ const FilePreviewDialog = (props: FilePreviewProps) => { : `rotate-${normalizedRotation}`; } + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (!show) return; + if (e.key === "ArrowLeft" && index > 0) { + handleNext(index - 1); + } + if (e.key === "ArrowRight" && index < (uploadedFiles?.length || 0) - 1) { + handleNext(index + 1); + } + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, [show, index, uploadedFiles]); + return ( { onClose={() => { handleClose(); }} - title={t("file_preview")} + title={{t("file_preview")}} show={show} > {fileUrl ? ( <> -
-

- {file_state.name}.{file_state.extension} -

-
+
+
+

+ {file_state.name}.{file_state.extension} +

+ {uploadedFiles && + uploadedFiles[index] && + uploadedFiles[index].created_date && ( +

+ Created on{" "} + {new Date( + uploadedFiles[index].created_date!, + ).toLocaleString("en-US", { + dateStyle: "long", + timeStyle: "short", + })} +

+ )} +
+
{downloadURL && downloadURL.length > 0 && ( {
+ {uploadedFiles && uploadedFiles.length > 1 && ( + handleNext(index - 1)} + disabled={index <= 0} + aria-label="Previous file" + onKeyDown={(e) => + e.key === "ArrowLeft" && handleNext(index - 1) + } + > + + + )}
{file_state.isImage ? ( {
)}
+ + {uploadedFiles && uploadedFiles.length > 1 && ( + handleNext(index + 1)} + disabled={index >= uploadedFiles.length - 1} + aria-label="Next file" + onKeyDown={(e) => + e.key === "ArrowRight" && handleNext(index + 1) + } + > + + + )}
diff --git a/src/components/Files/FileUpload.tsx b/src/components/Files/FileUpload.tsx index d98df131863..50ab3ffdb01 100644 --- a/src/components/Files/FileUpload.tsx +++ b/src/components/Files/FileUpload.tsx @@ -69,6 +69,8 @@ export interface StateInterface { isZoomInDisabled: boolean; isZoomOutDisabled: boolean; rotation: number; + id?: string; + associating_id?: string; } export const FileUpload = (props: FileUploadProps) => { @@ -208,8 +210,15 @@ export const FileUpload = (props: FileUploadProps) => { type, onArchive: refetchAll, onEdit: refetchAll, + uploadedFiles: + fileQuery?.data?.results + .slice() + .reverse() + .map((file) => ({ + ...file, + associating_id: associatedId, + })) || [], }); - const dischargeSummaryFileManager = useFileManager({ type: "DISCHARGE_SUMMARY", onArchive: refetchAll, @@ -244,7 +253,6 @@ export const FileUpload = (props: FileUploadProps) => { id: "record-audio", }, ]; - return (
{fileUpload.Dialogues} diff --git a/src/components/Patient/FileUploadPage.tsx b/src/components/Patient/FileUploadPage.tsx index 518644f54d7..d38b149ebeb 100644 --- a/src/components/Patient/FileUploadPage.tsx +++ b/src/components/Patient/FileUploadPage.tsx @@ -11,12 +11,10 @@ export default function FileUploadPage(props: { type: "CONSULTATION" | "PATIENT"; }) { const { facilityId, patientId, consultationId, type } = props; - const { data: patient } = useQuery(routes.getPatient, { pathParams: { id: patientId }, prefetch: !!patientId, }); - return ( void; onEdit?: () => void; + uploadedFiles?: FileUploadModel[]; } export interface FileManagerResult { viewFile: (file: FileUploadModel, associating_id: string) => void; @@ -48,7 +49,7 @@ export interface FileManagerResult { export default function useFileManager( options: FileManagerOptions, ): FileManagerResult { - const { type: fileType, onArchive, onEdit } = options; + const { type: fileType, onArchive, onEdit, uploadedFiles } = options; const [file_state, setFileState] = useState({ open: false, @@ -72,6 +73,7 @@ export default function useFileManager( const [editDialogueOpen, setEditDialogueOpen] = useState(null); const [editError, setEditError] = useState(""); + const [currentIndex, setCurrentIndex] = useState(-1); const getExtension = (url: string) => { const div1 = url.split("?")[0].split("."); @@ -80,6 +82,8 @@ export default function useFileManager( }; const viewFile = async (file: FileUploadModel, associating_id: string) => { + const index = uploadedFiles?.findIndex((f) => f.id === file.id) ?? -1; + setCurrentIndex(index); setFileUrl(""); setFileState({ ...file_state, open: true }); const { data } = await request(routes.retrieveUpload, { @@ -225,9 +229,12 @@ export default function useFileManager( file_state={file_state} setFileState={setFileState} downloadURL={downloadURL} + uploadedFiles={uploadedFiles} onClose={handleFilePreviewClose} fixedWidth={false} className="h-[80vh] w-full md:h-screen" + loadFile={viewFile} + currentIndex={currentIndex} /> Date: Fri, 29 Nov 2024 11:58:38 +0530 Subject: [PATCH 18/37] Fix: Date of Birth to Year of Birth on Patient Details Page when DOB is Missing (#9242) --- public/locale/en.json | 3 ++- .../Patient/PatientDetailsTab/Demography.tsx | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 56f84a1883a..cd921bef06a 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -562,7 +562,7 @@ "date_and_time": "Date and Time", "date_declared_positive": "Date of declaring positive", "date_of_admission": "Date of Admission", - "date_of_birth": "Date of birth", + "date_of_birth": "Date of Birth", "date_of_positive_covid_19_swab": "Date of Positive Covid 19 Swab", "date_of_result": "Covid confirmation date", "date_of_return": "Date of Return", @@ -1433,6 +1433,7 @@ "why_the_asset_is_not_working": "Why the asset is not working?", "width": "Width ({{unit}})", "working_status": "Working Status", + "year_of_birth": "Year of Birth", "years": "years", "years_of_experience": "Years of Experience", "years_of_experience_of_the_doctor": "Years of Experience of the Doctor", diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx index 7ac854b8ee6..2a412f03f9a 100644 --- a/src/components/Patient/PatientDetailsTab/Demography.tsx +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -188,12 +188,19 @@ export const Demography = (props: PatientProps) => { ), }, { - label: t("date_of_birth"), - value: ( + label: t( + patientData.date_of_birth ? "date_of_birth" : "year_of_birth", + ), + value: patientData.date_of_birth ? ( <> {dayjs(patientData.date_of_birth).format("DD MMM YYYY")} ( {formatPatientAge(patientData, true)}) + ) : ( + <> + {patientData.year_of_birth} ({formatPatientAge(patientData, true)} + ) + ), }, { From 2a89332514050c5f519ec650d6d5baf84c786f69 Mon Sep 17 00:00:00 2001 From: Bodhish Thomas Date: Fri, 29 Nov 2024 15:31:18 +0530 Subject: [PATCH 19/37] Update cursorrules --- .cursorrules | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/.cursorrules b/.cursorrules index 2eed3afe459..94ebea1bd57 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,23 +1,35 @@ -Care is a React Typescript Project, built with Vite and styled with TailwindCSS. +You are an expert in TypeScript, React, Shadcn UI, Tailwind. -Care uses a Plugin Architecture. Apps are installed in /apps. +Key Principles -Care uses a custom useQuery hook to fetch data from the API. APIs are defined in the api.tsx file +- Write concise, technical TypeScript code with accurate examples. +- Use functional and declarative programming patterns; avoid classes. +- Prefer iteration and modularization over code duplication. +- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). -Here's an example of how to use the useQuery hook to fetch data from the API: +Naming Conventions -``` -useQuery from "@/common/hooks/useQuery"; -const { data, loading, error } = useQuery(routes.getFacilityUsers, { - facility_id: "1", -}); +- Use lowercase with dashes for directories (e.g., components/auth-wizard). +- Favor named exports for components. -request from "@/common/utils/request"; -const { res } = await request(routes.partialUpdateAsset, { - pathParams: { external_id: assetId }, - body: data, -}); -``` +TypeScript Usage +- Use TypeScript for all code; prefer interfaces over types. +- Avoid enums; use maps instead. +- Use functional components with TypeScript interfaces. +Syntax and Formatting +- Use the "function" keyword for pure functions. +- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements. +- Use declarative JSX. + +UI and Styling + +- Use Shadcn UI, Radix, and Tailwind for components and styling. +- Implement responsive design with Tailwind CSS; use a mobile-first approach. + +General Guidelines + +- Care uses a custom useQuery hook to fetch data from the API. (Docs @ /Utils/request/useQuery) +- APIs are defined in the api.tsx file. From c58de1777e8945a41b26690be3f73e055d19d143 Mon Sep 17 00:00:00 2001 From: Swanand Shekhar Bhuskute <103440604+SwanandBhuskute@users.noreply.github.com> Date: Fri, 29 Nov 2024 18:27:32 +0530 Subject: [PATCH 20/37] fixing back button for camera asset (#9246) --- src/components/CameraFeed/ConfigureCamera.tsx | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/CameraFeed/ConfigureCamera.tsx b/src/components/CameraFeed/ConfigureCamera.tsx index f969ed2b0ca..eec453053d6 100644 --- a/src/components/CameraFeed/ConfigureCamera.tsx +++ b/src/components/CameraFeed/ConfigureCamera.tsx @@ -89,16 +89,15 @@ export default function ConfigureCamera(props: Props) { const firstBedId = linkedAssetBeds?.[0]?.bed_object.id ?? unlinkedBeds?.[0]?.id; - useEffect(() => { - if (!query.bed && firstBedId) { - setQuery({ bed: firstBedId }); - } - }, [query.bed, firstBedId]); + + const selectedBedId = query.bed || firstBedId; const selectedAssetBed = linkedAssetBeds?.find( - (a) => a.bed_object.id === query.bed, + (a) => a.bed_object.id === selectedBedId, + ); + const selectedUnlinkedBed = unlinkedBeds?.find( + (bed) => bed.id === selectedBedId, ); - const selectedUnlinkedBed = unlinkedBeds?.find((bed) => bed.id === query.bed); const cameraPresetsQuery = useQuery(FeedRoutes.listAssetBedPresets, { pathParams: { assetbed_id: selectedAssetBed?.id ?? "" }, From 59cc025ee2026213392a9337a2bcc89baec7e783 Mon Sep 17 00:00:00 2001 From: Anvesh Nalimela <151531961+AnveshNalimela@users.noreply.github.com> Date: Sat, 30 Nov 2024 05:57:40 +0530 Subject: [PATCH 21/37] Rewriting UsersCreation.cy.ts according to POM approach (#8930) Co-authored-by: Mohammed Nihal <57055998+nihal467@users.noreply.github.com> --- .../e2e/facility_spec/FacilityLocation.cy.ts | 5 +- cypress/e2e/users_spec/UsersCreation.cy.ts | 215 +++++++++--------- cypress/e2e/users_spec/UsersManage.cy.ts | 7 +- cypress/e2e/users_spec/UsersProfile.cy.ts | 15 +- cypress/pageobject/Users/UserCreation.ts | 79 +++---- cypress/pageobject/Users/UserProfilePage.ts | 42 ++-- cypress/pageobject/utils/constants.ts | 11 +- 7 files changed, 179 insertions(+), 195 deletions(-) diff --git a/cypress/e2e/facility_spec/FacilityLocation.cy.ts b/cypress/e2e/facility_spec/FacilityLocation.cy.ts index 9ac85e9ba4c..19c43d4dcf4 100644 --- a/cypress/e2e/facility_spec/FacilityLocation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityLocation.cy.ts @@ -36,6 +36,7 @@ describe("Location Management Section", () => { const bedType = "ICU"; const bedStatus = "Vacant"; const bedModifiedName = "test modified bed"; + const duplicateBedName = "ICCU"; const bedModifiedDescrption = "test modified description"; const bedModifiedType = "Isolation"; const numberOfBeds = 10; @@ -96,12 +97,12 @@ describe("Location Management Section", () => { facilityHome.verifyAndCloseNotifyModal(); // edit the created bed facilityLocation.clickEditBedButton(); - facilityLocation.enterBedName(bedModifiedName); + facilityLocation.enterBedName(duplicateBedName); facilityLocation.enterBedDescription(bedModifiedDescrption); facilityLocation.selectBedType(bedModifiedType); assetPage.clickassetupdatebutton(); // verify the modification - facilityLocation.verifyBedNameBadge(bedModifiedName); + facilityLocation.verifyBedNameBadge(duplicateBedName); facilityLocation.verifyBedBadge(bedModifiedType); facilityLocation.verifyBedBadge(bedStatus); facilityLocation.closeNotification(); diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index f495a136d97..96b953a3e17 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -1,3 +1,6 @@ +import ManageUserPage from "pageobject/Users/ManageUserPage"; +import UserProfilePage from "pageobject/Users/UserProfilePage"; + import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; @@ -11,13 +14,15 @@ import { describe("User Creation", () => { const userPage = new UserPage(); const loginPage = new LoginPage(); + const userProfilePage = new UserProfilePage(); + const manageUserPage = new ManageUserPage(); const userCreationPage = new UserCreationPage(); const facilityPage = new FacilityPage(); const assetSearchPage = new AssetSearchPage(); - const phone_number = generatePhoneNumber(); - const emergency_phone_number = generateEmergencyPhoneNumber(); + const phoneNumber = generatePhoneNumber(); + const emergencyPhoneNumber = generateEmergencyPhoneNumber(); const fillFacilityName = "Dummy Facility 40"; - const makeid = (length: number) => { + const makeId = (length: number) => { let result = ""; const characters = "abcdefghijklmnopqrstuvwxyz0123456789"; const charactersLength = characters.length; @@ -26,8 +31,8 @@ describe("User Creation", () => { } return result; }; - const username = makeid(8); - const alreadylinkedusersviews = [ + const username = makeId(8); + const alreadyLinkedUsersViews = [ "devdoctor", "devstaff2", "devdistrictadmin", @@ -53,6 +58,25 @@ describe("User Creation", () => { "This field is required", "Please enter valid phone number", ]; + const userName = "devdistrictadmin"; + const firstName = "District Editted"; + const lastName = "Cypress"; + const gender = "Male"; + const email = "test@test.com"; + const password = "Test@123"; + const qualification = "MBBS"; + const experience = "2"; + const regNo = "123456789"; + const newUserFirstName = "cypress test"; + const newUserLastName = "staff user"; + const state = "Kerala"; + const district = "Ernakulam"; + const role = "Doctor"; + const homeFacility = "Dummy Shifting Center"; + const weeklyWorkingHrs = "14"; + const dob = "01011998"; + const formattedDob = "01/01/1998"; + const newUserDob = "25081999"; before(() => { loginPage.loginAsDistrictAdmin(); @@ -66,123 +90,92 @@ describe("User Creation", () => { }); it("Update the existing user profile and verify its reflection", () => { - userCreationPage.clickElementById("user-profile-name"); - userCreationPage.clickElementById("profile-button"); - userCreationPage.verifyElementContainsText( - "username-profile-details", - "devdistrictadmin", - ); - userCreationPage.clickElementById("edit-cancel-profile-button"); - userCreationPage.typeIntoElementByIdPostClear( - "firstName", - "District Editted", - ); - userCreationPage.typeIntoElementByIdPostClear("lastName", "Cypress"); - userCreationPage.selectDropdownOption("gender", "Male"); - userCreationPage.typeIntoElementByIdPostClear("phoneNumber", phone_number); - userCreationPage.typeIntoElementByIdPostClear( - "altPhoneNumber", - emergency_phone_number, - ); - userCreationPage.typeIntoElementByIdPostClear("email", "test@test.com"); - userCreationPage.typeIntoElementByIdPostClear("weekly_working_hours", "14"); - userCreationPage.typeIntoElementByIdPostClearDob( - "date_of_birth", - "01011998", - ); - userCreationPage.clickElementById("submit"); - userCreationPage.verifyElementContainsText( - "contactno-profile-details", - "+91" + phone_number, - ); - userCreationPage.verifyElementContainsText( - "whatsapp-profile-details", - "+91" + emergency_phone_number, - ); - userCreationPage.verifyElementContainsText( - "firstname-profile-details", - "District Editted", - ); - userCreationPage.verifyElementContainsText( - "lastname-profile-details", - "Cypress", - ); - userCreationPage.verifyElementContainsText( - "date_of_birth-profile-details", - "01/01/1998", - ); - userCreationPage.verifyElementContainsText( - "emailid-profile-details", - "test@test.com", - ); - userCreationPage.verifyElementContainsText( - "gender-profile-details", - "Male", - ); - userCreationPage.verifyElementContainsText( - "averageworkinghour-profile-details", - "14", - ); + manageUserPage.navigateToProfile(); + cy.verifyContentPresence("#username-profile-details", [userName]); + userProfilePage.clickEditProfileButton(); + userCreationPage.clearFirstName(); + userCreationPage.typeFirstName(firstName); + userCreationPage.clearLastName(); + userCreationPage.typeLastName(lastName); + userProfilePage.selectGender(gender); + userProfilePage.clearPhoneNumber(); + userProfilePage.typePhoneNumber(phoneNumber); + userProfilePage.clearAltPhoneNumber(); + userProfilePage.typeWhatsappNumber(emergencyPhoneNumber); + userProfilePage.clearEmail(); + userProfilePage.typeEmail(email); + userProfilePage.clearWorkingHours(); + userProfilePage.typeWorkingHours(weeklyWorkingHrs); + userProfilePage.typeDateOfBirth(dob); + cy.intercept("PATCH", "/api/v1/users/*").as("updateUser"); + userProfilePage.clickUpdateButton(); + cy.wait("@updateUser").its("response.statusCode").should("eq", 200); + cy.verifyContentPresence("#contactno-profile-details", [ + "+91" + phoneNumber, + ]); + cy.verifyContentPresence("#whatsapp-profile-details", [ + "+91" + emergencyPhoneNumber, + ]); + cy.verifyContentPresence("#firstname-profile-details", [firstName]); + cy.verifyContentPresence("#lastname-profile-details", [lastName]); + cy.verifyContentPresence("#date_of_birth-profile-details", [formattedDob]); + cy.verifyContentPresence("#emailid-profile-details", [email]); + cy.verifyContentPresence("#gender-profile-details", [gender]); + cy.verifyContentPresence("#averageworkinghour-profile-details", [ + weeklyWorkingHrs, + ]); }); it("Update the existing user profile Form Mandatory File Error", () => { - userCreationPage.clickElementById("user-profile-name"); - userCreationPage.clickElementById("profile-button"); - userCreationPage.clickElementById("edit-cancel-profile-button"); - userCreationPage.clearIntoElementById("firstName"); - userCreationPage.clearIntoElementById("lastName"); - userCreationPage.clearIntoElementById("phoneNumber"); - userCreationPage.clearIntoElementById("altPhoneNumber"); - userCreationPage.clearIntoElementById("weekly_working_hours"); - userCreationPage.clickElementById("submit"); + manageUserPage.navigateToProfile(); + userProfilePage.clickEditProfileButton(); + userCreationPage.clearFirstName(); + userCreationPage.clearLastName(); + userProfilePage.clearPhoneNumber(); + userProfilePage.clearAltPhoneNumber(); + userProfilePage.clearWorkingHours(); + userProfilePage.clickUpdateButton(); userCreationPage.verifyErrorMessages(EXPECTED_PROFILE_ERROR_MESSAGES); }); it("create new user and verify reflection", () => { - userCreationPage.clickElementById("addUserButton"); - userCreationPage.selectFacility("Dummy Shifting Center"); - userCreationPage.typeIntoElementById("username", username); - userCreationPage.typeIntoElementById("password", "Test@123"); - userCreationPage.selectHomeFacility("Dummy Shifting Center"); - userCreationPage.typeIntoElementById("phone_number", phone_number); - userCreationPage.setInputDate("date_of_birth", "25081999"); - userCreationPage.selectDropdownOption("user_type", "Doctor"); - userCreationPage.typeIntoElementById("c_password", "Test@123"); - userCreationPage.typeIntoElementById("qualification", "MBBS"); - userCreationPage.typeIntoElementById("doctor_experience_commenced_on", "2"); - userCreationPage.typeIntoElementById( - "doctor_medical_council_registration", - "123456789", - ); - userCreationPage.typeIntoElementById("first_name", "cypress test"); - userCreationPage.typeIntoElementById("last_name", "staff user"); - userCreationPage.typeIntoElementById("email", "test@test.com"); - userCreationPage.selectDropdownOption("gender", "Male"); - userCreationPage.selectDropdownOption("state", "Kerala"); - userCreationPage.selectDropdownOption("district", "Ernakulam"); - userCreationPage.clickElementById("submit"); - userCreationPage.verifyNotification("User added successfully"); + userCreationPage.clickAddUserButton(); + userCreationPage.selectFacility(homeFacility); + userCreationPage.typeUserName(username); + userCreationPage.typePassword(password); + userCreationPage.typeConfirmPassword(password); + userCreationPage.selectHomeFacility(homeFacility); + userPage.typeInPhoneNumber(phoneNumber); + userProfilePage.typeDateOfBirth(newUserDob); + userCreationPage.selectUserType(role); + userProfilePage.typeQualification(qualification); + userProfilePage.typeDoctorYoE(experience); + userProfilePage.typeMedicalCouncilRegistration(regNo); + userPage.typeInFirstName(newUserFirstName); + userPage.typeInLastName(newUserLastName); + userProfilePage.typeEmail(email); + userCreationPage.selectGender(gender); + userCreationPage.selectState(state); + userCreationPage.selectDistrict(district); + cy.intercept("POST", "/api/v1/users/add_user/").as("createUser"); + userCreationPage.clickSaveUserButton(); + cy.wait("@createUser").its("response.statusCode").should("eq", 201); + cy.verifyNotification("User added successfully"); userPage.typeInSearchInput(username); userPage.checkUsernameText(username); - userCreationPage.verifyElementContainsText("name", "cypress test"); - userCreationPage.verifyElementContainsText("role", "Doctor"); - userCreationPage.verifyElementContainsText("district", "Ernakulam"); - userCreationPage.verifyElementContainsText( - "home_facility", - "Dummy Shifting Center", - ); - userCreationPage.verifyElementContainsText("qualification", "MBBS"); - userCreationPage.verifyElementContainsText("doctor-experience", "2"); - userCreationPage.verifyElementContainsText( - "medical-council-registration", - "123456789", - ); + cy.verifyContentPresence("#name", [newUserFirstName]); + cy.verifyContentPresence("#role", [role]); + cy.verifyContentPresence("#district", [district]); + cy.verifyContentPresence("#home_facility", [homeFacility]); + cy.verifyContentPresence("#qualification", [qualification]); + cy.verifyContentPresence("#doctor-experience", [experience]); + cy.verifyContentPresence("#medical-council-registration", [regNo]); }); it("create new user form throwing mandatory field error", () => { - userCreationPage.clickElementById("addUserButton"); - userCreationPage.clickElementById("submit"); - cy.wait(2000); + userCreationPage.clickAddUserButton(); + userCreationPage.clickSaveUserButton(); + cy.get(".error-text", { timeout: 10000 }).should("be.visible"); userCreationPage.verifyErrorMessages(EXPECTED_ERROR_MESSAGES); }); @@ -194,7 +187,7 @@ describe("User Creation", () => { facilityPage.visitAlreadyCreatedFacility(); facilityPage.clickManageFacilityDropdown(); facilityPage.clickViewUsersOption(); - userPage.verifyMultipleBadgesWithSameId(alreadylinkedusersviews); + userPage.verifyMultipleBadgesWithSameId(alreadyLinkedUsersViews); }); afterEach(() => { diff --git a/cypress/e2e/users_spec/UsersManage.cy.ts b/cypress/e2e/users_spec/UsersManage.cy.ts index 40d436be0a6..74baee3deac 100644 --- a/cypress/e2e/users_spec/UsersManage.cy.ts +++ b/cypress/e2e/users_spec/UsersManage.cy.ts @@ -2,7 +2,6 @@ import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; -import { UserCreationPage } from "../../pageobject/Users/UserCreation"; import { UserPage } from "../../pageobject/Users/UserSearch"; describe("Manage User", () => { @@ -15,7 +14,6 @@ describe("Manage User", () => { const usernameToLinkSkill = "devdoctor"; const firstNameUserSkill = "Dev"; const lastNameUserSkill = "Doctor"; - const userCreationPage = new UserCreationPage(); const usernameforworkinghour = "devdistrictadmin"; const usernamerealname = "Dummy Doctor"; const facilitytolinkusername = "Dummy Shifting Center"; @@ -48,10 +46,9 @@ describe("Manage User", () => { manageUserPage.clickCloseSlideOver(); cy.wait(5000); manageUserPage.navigateToProfile(); - userCreationPage.verifyElementContainsText( - "username-profile-details", + cy.verifyContentPresence("#username-profile-details", [ usernameforworkinghour, - ); + ]); manageUserPage.assertSkillInAlreadyLinkedSkills(linkedskill); }); diff --git a/cypress/e2e/users_spec/UsersProfile.cy.ts b/cypress/e2e/users_spec/UsersProfile.cy.ts index 35771024180..32b812a188f 100644 --- a/cypress/e2e/users_spec/UsersProfile.cy.ts +++ b/cypress/e2e/users_spec/UsersProfile.cy.ts @@ -32,25 +32,22 @@ describe("Manage User Profile", () => { it("Set Dob, Gender, Email, Phone and Working Hours for a user and verify its reflection in user profile", () => { userProfilePage.clickEditProfileButton(); - userProfilePage.typedate_of_birth(date_of_birth); + userProfilePage.typeDateOfBirth(date_of_birth); userProfilePage.selectGender(gender); userProfilePage.typeEmail(email); - userProfilePage.typePhone(phone); - userProfilePage.typeWhatsApp(phone); + userProfilePage.typePhoneNumber(phone); + userProfilePage.typeWhatsappNumber(phone); userProfilePage.typeWorkingHours(workinghours); userProfilePage.typeQualification(qualification); userProfilePage.typeDoctorYoE(doctorYoE); userProfilePage.typeMedicalCouncilRegistration(medicalCouncilRegistration); - userProfilePage.clickUpdateButton(); - cy.verifyNotification("Details updated successfully"); - - userProfilePage.assertdate_of_birth("01/01/1999"); + userProfilePage.assertDateOfBirth("01/01/1999"); userProfilePage.assertGender(gender); userProfilePage.assertEmail(email); - userProfilePage.assertPhone(phone); - userProfilePage.assertWhatsApp(phone); + userProfilePage.assertPhoneNumber(phone); + userProfilePage.assertAltPhoneNumber(phone); userProfilePage.assertWorkingHours(workinghours); }); diff --git a/cypress/pageobject/Users/UserCreation.ts b/cypress/pageobject/Users/UserCreation.ts index 906c07e797a..72157861f2c 100644 --- a/cypress/pageobject/Users/UserCreation.ts +++ b/cypress/pageobject/Users/UserCreation.ts @@ -1,69 +1,52 @@ // UserCreation.ts export class UserCreationPage { - clickElementById(elementId: string) { - cy.get("#" + elementId).click(); + clickAddUserButton() { + cy.verifyAndClickElement("#addUserButton", "Add New User"); } - - typeIntoElementById(elementId: string, value: string) { - cy.get("#" + elementId) - .click() - .type(value); + typeUserName(username: string) { + cy.get("#username").click().type(username); } - - typeIntoElementByIdPostClear(elementId: string, value: string) { - cy.get("#" + elementId) - .click() - .clear() - .click() - .type(value); + typeFirstName(firstName: string) { + cy.get("#firstName").click().type(firstName); } - typeIntoElementByIdPostClearDob(elementId: string, value: string) { - cy.clickAndTypeDate("#" + elementId, value); + typeLastName(lastName: string) { + cy.get("#lastName").click().type(lastName); } - clearIntoElementById(elementId: string) { - cy.get("#" + elementId) - .click() - .clear(); + typePassword(password: string) { + cy.get("#password").click().type(password); } - - typeIntoInputByName(inputName: string, value: string) { - cy.get("input[name='" + inputName + "']") - .click() - .type(value); + typeConfirmPassword(password: string) { + cy.get("#c_password").click().type(password); } - - selectOptionContainingText(text: string) { - cy.get("[role='option']").contains(text).click(); + clearFirstName() { + cy.get("#firstName").click().clear(); } - - verifyNotification(message: string) { - cy.verifyNotification(message); + clearLastName() { + cy.get("#lastName").click().clear(); } - - selectFacility(name: string) { - this.typeIntoInputByName("facilities", name); - this.selectOptionContainingText(name); - cy.get("input[name='facilities'] + button") - .find("#dropdown-toggle") - .click(); + selectUserType(role: string) { + cy.clickAndSelectOption("#user_type", role); } - selectHomeFacility(name: string) { - this.clickElementById("home_facility"); - this.selectOptionContainingText(name); + cy.clickAndSelectOption("#home_facility", name); } - setInputDate(dateElementId: string, dateValue: string) { - cy.clickAndTypeDate("#" + dateElementId, dateValue); + selectGender(gender: string) { + cy.clickAndSelectOption("#gender", gender); + } + selectState(state: string) { + cy.clickAndSelectOption("#state", state); + } + selectDistrict(district: string) { + cy.clickAndSelectOption("#district", district); } - selectDropdownOption(dropdownId: string, optionText: string) { - this.clickElementById(dropdownId); - this.selectOptionContainingText(optionText); + selectFacility(name: string) { + cy.typeAndSelectOption("input[name='facilities']", name); } - verifyElementContainsText(elementId: string, expectedText: string) { - cy.get("#" + elementId).should("contain.text", expectedText); + clickSaveUserButton() { + cy.clickSubmitButton("Save User"); } verifyErrorMessages(errorMessages: string[]) { diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts index 3744c5a5d82..882be0b7b9b 100644 --- a/cypress/pageobject/Users/UserProfilePage.ts +++ b/cypress/pageobject/Users/UserProfilePage.ts @@ -12,11 +12,24 @@ export default class UserProfilePage { } clickUpdateButton() { - cy.get("#submit").click(); + cy.clickSubmitButton("Update"); } - typedate_of_birth(date_of_birth: string) { - cy.clickAndTypeDate("#date_of_birth", date_of_birth); + typeDateOfBirth(dob: string) { + cy.clickAndTypeDate("#date_of_birth", dob); + } + + clearPhoneNumber() { + cy.get("#phoneNumber").click().clear(); + } + clearAltPhoneNumber() { + cy.get("#altPhoneNumber").click().clear(); + } + clearWorkingHours() { + cy.get("#weekly_working_hours").click().clear(); + } + clearEmail() { + cy.get("#email").click().clear(); } selectGender(gender: string) { @@ -28,16 +41,16 @@ export default class UserProfilePage { cy.get("#email").click().clear().type(email); } - typePhone(phone: string) { + typePhoneNumber(phone: string) { cy.get("#phoneNumber").click().clear().type(phone); } - typeWhatsApp(phone: string) { + typeWhatsappNumber(phone: string) { cy.get("#altPhoneNumber").click().clear().type(phone); } - typeWorkingHours(workinghours: string) { - cy.get("#weekly_working_hours").click().clear().type(workinghours); + typeWorkingHours(workingHours: string) { + cy.get("#weekly_working_hours").click().clear().type(workingHours); } typeQualification = (qualification: string) => { @@ -55,11 +68,8 @@ export default class UserProfilePage { .type(medicalCouncilRegistration); }; - assertdate_of_birth(date_of_birth: string) { - cy.get("#date_of_birth-profile-details").should( - "contain.text", - date_of_birth, - ); + assertDateOfBirth(dob: string) { + cy.get("#date_of_birth-profile-details").should("contain.text", dob); } assertGender(gender: string) { @@ -70,18 +80,18 @@ export default class UserProfilePage { cy.get("#emailid-profile-details").should("contain.text", email); } - assertPhone(phone: string) { + assertPhoneNumber(phone: string) { cy.get("#contactno-profile-details").should("contain.text", phone); } - assertWhatsApp(phone: string) { + assertAltPhoneNumber(phone: string) { cy.get("#whatsapp-profile-details").should("contain.text", phone); } - assertWorkingHours(workinghours: string) { + assertWorkingHours(workingHours: string) { cy.get("#averageworkinghour-profile-details").should( "contain.text", - workinghours, + workingHours, ); } } diff --git a/cypress/pageobject/utils/constants.ts b/cypress/pageobject/utils/constants.ts index 08411e84b6a..053d0561ce8 100644 --- a/cypress/pageobject/utils/constants.ts +++ b/cypress/pageobject/utils/constants.ts @@ -1,7 +1,10 @@ -export function generatePhoneNumber() { - return "9" + Math.floor(100000000 + Math.random() * 900000000).toString(); +export function generatePhoneNumber(): string { + const array = new Uint32Array(1); + window.crypto.getRandomValues(array); + const randomNum = (array[0] % 900000000) + 100000000; + return "9" + randomNum.toString(); } -export function generateEmergencyPhoneNumber() { - return "9" + Math.floor(100000000 + Math.random() * 900000000).toString(); +export function generateEmergencyPhoneNumber(): string { + return generatePhoneNumber(); } From 7d56fdc69265b21c8ebc60f7fffc97e4ad5612df Mon Sep 17 00:00:00 2001 From: Rajab Shoukath Kunnath <142793649+rayyjeb@users.noreply.github.com> Date: Sat, 30 Nov 2024 09:47:35 +0530 Subject: [PATCH 22/37] Fixed Breadcrumb to Display Detailed Name Instead of ID (#9200) --- src/components/Patient/SampleDetails.tsx | 1 + src/components/Patient/models.tsx | 2 +- src/components/Shifting/ShiftDetails.tsx | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index edc0b4d53a6..abf099a6e7d 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -300,6 +300,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { return ( From f2a96983f225789bd6758ef4b4ef8682091f71e1 Mon Sep 17 00:00:00 2001 From: Mahendar Chikolla <119734520+Mahendar0701@users.noreply.github.com> Date: Sat, 30 Nov 2024 10:06:09 +0530 Subject: [PATCH 23/37] Fix : Advanced Filters Autofilling on Revisit to Patient Tab (#9251) --- src/components/Patient/ManagePatients.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/Patient/ManagePatients.tsx b/src/components/Patient/ManagePatients.tsx index 0664532b0a1..9507ce3d8d7 100644 --- a/src/components/Patient/ManagePatients.tsx +++ b/src/components/Patient/ManagePatients.tsx @@ -1143,7 +1143,10 @@ export const PatientManager = () => { />
- +
{managePatients}
From 66326dd053cffbb7020f4381ad397217b4de2698 Mon Sep 17 00:00:00 2001 From: Don Xavier <98073418+DonXavierdev@users.noreply.github.com> Date: Sat, 30 Nov 2024 10:09:06 +0530 Subject: [PATCH 24/37] Add edit button to immunisation section; replaced buttons with shadcn buttons across PatientsDetail section (#9245) --- .../Patient/PatientDetailsTab/Demography.tsx | 54 +++++++++---------- .../HealthProfileSummary.tsx | 12 +++-- .../PatientDetailsTab/ImmunisationRecords.tsx | 12 +++-- 3 files changed, 41 insertions(+), 37 deletions(-) diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx index 2a412f03f9a..b9d2b8fece4 100644 --- a/src/components/Patient/PatientDetailsTab/Demography.tsx +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -5,8 +5,9 @@ import { useTranslation } from "react-i18next"; import Chip from "@/CAREUI/display/Chip"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import AuthorizedChild from "@/CAREUI/misc/AuthorizedChild"; -import ButtonV2 from "@/components/Common/ButtonV2"; +import { Button } from "@/components/ui/button"; import useAuthUser from "@/hooks/useAuthUser"; @@ -312,10 +313,9 @@ export const Demography = (props: PatientProps) => { {t("no_data_found")}
)} - handleEditClick("insurance-details"), @@ -323,7 +323,7 @@ export const Demography = (props: PatientProps) => { > {t("add_insurance_details")} - +
, ], }, @@ -373,21 +373,24 @@ export const Demography = (props: PatientProps) => {
- - navigate( - `/facility/${patientData?.facility}/patient/${id}/update`, - ), + + {({ isAuthorized }) => ( + )} - > - - {t("edit_profile")} - +
{/*
@@ -415,22 +418,19 @@ export const Demography = (props: PatientProps) => { className="group mt-4 rounded-md bg-white pb-2 pl-5 pt-5 shadow" >
-
+

{t(`patient__${subtab.id}`)}

{subtab.allowEdit && ( - + )}
diff --git a/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx b/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx index 3fb6b60d330..374d5ede08b 100644 --- a/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx +++ b/src/components/Patient/PatientDetailsTab/HealthProfileSummary.tsx @@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Button } from "@/components/ui/button"; + import { UserModel } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; @@ -56,12 +58,12 @@ export const HealthProfileSummary = (props: PatientProps) => {

-
+
{t("medical")}
- +
diff --git a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx index eb298737f3c..7ee828868fc 100644 --- a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx +++ b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx @@ -3,6 +3,8 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Button } from "@/components/ui/button"; + import { UserModel } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; @@ -40,10 +42,10 @@ export const ImmunisationRecords = (props: PatientProps) => {

-
+

{t("covid_details")}

- +
From 8118ccfa3c07d883336050b3fb694bffc735c14e Mon Sep 17 00:00:00 2001 From: HITMAN <112652819+ayushpatil2122@users.noreply.github.com> Date: Sat, 30 Nov 2024 16:39:33 +0530 Subject: [PATCH 25/37] phone size responsive issue shifting details page (#9256) --- package-lock.json | 8 ++++---- package.json | 2 +- src/components/Shifting/ShiftDetails.tsx | 13 +++++-------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c703af8fe1..97073c88ecb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -92,7 +92,7 @@ "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "glob": "^11.0.0", - "husky": "^9.1.6", + "husky": "^9.1.7", "jsdom": "^25.0.1", "lint-staged": "^15.2.10", "local-cypress": "^1.2.6", @@ -10449,9 +10449,9 @@ "license": "Unlicense" }, "node_modules/husky": { - "version": "9.1.6", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", - "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { diff --git a/package.json b/package.json index 020d73f5e65..798c910ea91 100644 --- a/package.json +++ b/package.json @@ -131,7 +131,7 @@ "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "glob": "^11.0.0", - "husky": "^9.1.6", + "husky": "^9.1.7", "jsdom": "^25.0.1", "lint-staged": "^15.2.10", "local-cypress": "^1.2.6", diff --git a/src/components/Shifting/ShiftDetails.tsx b/src/components/Shifting/ShiftDetails.tsx index 3eeb440a1b1..bc97b0b0f24 100644 --- a/src/components/Shifting/ShiftDetails.tsx +++ b/src/components/Shifting/ShiftDetails.tsx @@ -483,25 +483,22 @@ export default function ShiftDetails(props: { id: string }) { }} backUrl="/shifting/board" options={ -
+
- navigate(`/shifting/${data?.external_id}/update`) - } + disabled={data?.status === "COMPLETED" || data?.status === "CANCELLED"} + onClick={() => navigate(`/shifting/${data?.external_id}/update`)} > {t("update_status_details")} - setIsPrintMode(true)}> + setIsPrintMode(true)}> {" "} {t("referral_letter")} From f76c6250ec25c6d9216f58088ca4f9833e872743 Mon Sep 17 00:00:00 2001 From: Kamishetty Rishith <119791436+Rishith25@users.noreply.github.com> Date: Sun, 1 Dec 2024 12:21:46 +0530 Subject: [PATCH 26/37] =?UTF-8?q?Fix:=20Reflect=20Updated=20Service=20Note?= =?UTF-8?q?s=20Immediately=20in=20Asset=20Details=20without=C2=A0page?= =?UTF-8?q?=C2=A0reload=20(#9226)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Assets/AssetManage.tsx | 11 +++++++++-- src/components/Shifting/ShiftDetails.tsx | 13 ++++++++++--- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/components/Assets/AssetManage.tsx b/src/components/Assets/AssetManage.tsx index c78be71d627..b4377d2e1aa 100644 --- a/src/components/Assets/AssetManage.tsx +++ b/src/components/Assets/AssetManage.tsx @@ -69,7 +69,11 @@ const AssetManage = (props: AssetManageProps) => { >(); const [transactionFilter, setTransactionFilter] = useState({}); - const { data: asset, loading } = useQuery(routes.getAsset, { + const { + data: asset, + loading, + refetch, + } = useQuery(routes.getAsset, { pathParams: { external_id: assetId, }, @@ -594,7 +598,10 @@ const AssetManage = (props: AssetManageProps) => { handleClose={() => setServiceEditData({ ...serviceEditData, open: false }) } - handleUpdate={() => serviceRefetch()} + handleUpdate={() => { + serviceRefetch(); + refetch(); + }} show={serviceEditData.open} viewOnly={serviceEditData.viewOnly} /> diff --git a/src/components/Shifting/ShiftDetails.tsx b/src/components/Shifting/ShiftDetails.tsx index bc97b0b0f24..cb5140d6ed0 100644 --- a/src/components/Shifting/ShiftDetails.tsx +++ b/src/components/Shifting/ShiftDetails.tsx @@ -492,13 +492,20 @@ export default function ShiftDetails(props: { id: string }) { : "" } tooltipClassName="tooltip-top -translate-x-28 -translate-y-1 text-xs" - disabled={data?.status === "COMPLETED" || data?.status === "CANCELLED"} - onClick={() => navigate(`/shifting/${data?.external_id}/update`)} + disabled={ + data?.status === "COMPLETED" || data?.status === "CANCELLED" + } + onClick={() => + navigate(`/shifting/${data?.external_id}/update`) + } > {t("update_status_details")} - setIsPrintMode(true)}> + setIsPrintMode(true)} + > {" "} {t("referral_letter")} From 2d9669e75f4f5a04b803d64bfaae2e1d1fb2a0d9 Mon Sep 17 00:00:00 2001 From: Jacob John Jeevan <40040905+Jacobjeevan@users.noreply.github.com> Date: Mon, 2 Dec 2024 14:22:44 +0530 Subject: [PATCH 27/37] Camera Preset Not Resetting on Re-selection Bug (#9186) --- public/locale/en.json | 2 + .../ConsultationFeedTab.tsx | 39 ++++++------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index cd921bef06a..7e92d927a64 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -945,6 +945,7 @@ "next_sessions": "Next Sessions", "no": "No", "no_attachments_found": "This communication has no attachments.", + "no_bed_asset_linked_allocated": "No bed/asset linked allocated", "no_bed_types_found": "No Bed Types found", "no_beds_available": "No beds available", "no_changes": "No changes", @@ -1364,6 +1365,7 @@ "update_facility_middleware_success": "Facility middleware updated successfully", "update_log": "Update Log", "update_patient_details": "Update Patient Details", + "update_preset": "Update Preset", "update_preset_position_to_current": "Update preset's position to camera's current position", "update_record": "Update Record", "update_record_for_asset": "Update record for asset", diff --git a/src/components/Facility/ConsultationDetails/ConsultationFeedTab.tsx b/src/components/Facility/ConsultationDetails/ConsultationFeedTab.tsx index 21c6f304cb5..bc518feb02f 100644 --- a/src/components/Facility/ConsultationDetails/ConsultationFeedTab.tsx +++ b/src/components/Facility/ConsultationDetails/ConsultationFeedTab.tsx @@ -34,6 +34,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { const bed = props.consultationData.current_bed?.bed_object; const feedStateSessionKey = `encounterFeedState[${props.consultationId}]`; const [preset, setPreset] = useState(); + const [selectedPreset, setSelectedPreset] = useState(); const [showPresetSaveConfirmation, setShowPresetSaveConfirmation] = useState(false); const [isUpdatingPreset, setIsUpdatingPreset] = useState(false); @@ -78,28 +79,14 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { return presets.find((obj) => obj.id === lastState.value); } if (lastState.type === "position") { - const assetBedObj = presets.find( - (p) => p.asset_bed.id === lastState.assetBed, - )?.asset_bed; - - if (!assetBedObj) { - return; - } - - return { - ...presets[0], - id: "", - asset_bed: assetBedObj, - position: lastState.value, - } satisfies CameraPreset; + return; } } })() ?? presets[0]; - console.log({ preset, presets }); - if (preset) { setPreset(preset); + setSelectedPreset(preset); } }, }); @@ -126,6 +113,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { await presetsQuery.refetch(); setPreset(updated); + setSelectedPreset(updated); setHasMoved(false); setIsUpdatingPreset(false); setShowPresetSaveConfirmation(false); @@ -154,7 +142,7 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { } if (!bed || !asset) { - return No bed/asset linked allocated; + return {t("no_bed_asset_linked_allocated")}; } const cannotSaveToPreset = !hasMoved || !preset?.id; @@ -162,8 +150,8 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { return ( setShowPresetSaveConfirmation(false)} @@ -180,11 +168,9 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { { - if (!preset) { - return; - } + setSelectedPreset(undefined); setHasMoved(true); setTimeout(async () => { const { data } = await operate({ type: "get_status" }); @@ -193,7 +179,6 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { feedStateSessionKey, JSON.stringify({ type: "position", - assetBed: preset.asset_bed.id, value: (data as GetStatusResponse).result.position, } satisfies LastAccessedPosition), ); @@ -222,16 +207,17 @@ export const ConsultationFeedTab = (props: ConsultationTabProps) => { obj.name} - value={preset} + value={selectedPreset} onChange={(value) => { triggerGoal("Camera Preset Clicked", { - presetName: preset?.name, + presetName: selectedPreset?.name, consultationId: props.consultationId, userId: authUser.id, result: "success", }); setHasMoved(false); setPreset(value); + setSelectedPreset(value); }} /> {isUpdatingPreset ? ( @@ -277,7 +263,6 @@ type LastAccessedPreset = { type LastAccessedPosition = { type: "position"; - assetBed: string; value: PTZPayload; }; From 1868edd32cfcd0abfebe8e9c93cf687758e74278 Mon Sep 17 00:00:00 2001 From: Nithish Kumar Siliveru Date: Tue, 3 Dec 2024 20:54:36 +0530 Subject: [PATCH 28/37] Update Sample File Upload and upgrade buttons to Shadcn button (#9157) --- public/locale/en.json | 1 + src/components/Patient/UpdateStatusDialog.tsx | 190 ++++++++---------- src/components/ui/button.tsx | 2 + 3 files changed, 82 insertions(+), 111 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 7e92d927a64..b5f46dd7308 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1380,6 +1380,7 @@ "upload_headings__patient": "Upload New Patient File", "upload_headings__sample_report": "Upload Sample Report", "upload_headings__supporting_info": "Upload Supporting Info", + "upload_report": "Upload Report", "uploading": "Uploading", "use_existing_abha_address": "Use Existing ABHA Address", "user_deleted_successfuly": "User Deleted Successfuly", diff --git a/src/components/Patient/UpdateStatusDialog.tsx b/src/components/Patient/UpdateStatusDialog.tsx index 9414ba9bb95..e9a767847aa 100644 --- a/src/components/Patient/UpdateStatusDialog.tsx +++ b/src/components/Patient/UpdateStatusDialog.tsx @@ -1,31 +1,28 @@ -import { useEffect, useReducer, useState } from "react"; +import { useEffect, useReducer } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import { Submit } from "@/components/Common/ButtonV2"; +import { Button } from "@/components/ui/button"; + import ConfirmDialog from "@/components/Common/ConfirmDialog"; import { LinearProgressWithLabel } from "@/components/Files/FileUpload"; import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; import TextFormField from "@/components/Form/FormFields/TextFormField"; import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import { - CreateFileResponse, - SampleTestModel, -} from "@/components/Patient/models"; + +import useFileUpload from "@/hooks/useFileUpload"; import { - HEADER_CONTENT_TYPES, SAMPLE_FLOW_RULES, SAMPLE_TEST_RESULT, SAMPLE_TEST_STATUS, } from "@/common/constants"; import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import uploadFile from "@/Utils/request/uploadFile"; + +import { SampleTestModel } from "./models"; interface Props { sample: SampleTestModel; @@ -34,7 +31,6 @@ interface Props { } const statusChoices = [...SAMPLE_TEST_STATUS]; - const statusFlow = { ...SAMPLE_FLOW_RULES }; const initForm: any = { @@ -60,17 +56,16 @@ const updateStatusReducer = (state = initialState, action: any) => { return state; } }; - const UpdateStatusDialog = (props: Props) => { const { t } = useTranslation(); const { sample, handleOk, handleCancel } = props; const [state, dispatch] = useReducer(updateStatusReducer, initialState); - const [file, setfile] = useState(); - const [contentType, setcontentType] = useState(""); - const [uploadPercent, setUploadPercent] = useState(0); - const [uploadStarted, setUploadStarted] = useState(false); - const [uploadDone, setUploadDone] = useState(false); + const fileUpload = useFileUpload({ + type: "SAMPLE_MANAGEMENT", + allowedExtensions: ["pdf", "jpg", "jpeg", "png"], + allowNameFallback: true, + }); const currentStatus = SAMPLE_TEST_STATUS.find( (i) => i.text === sample.status, ); @@ -104,79 +99,26 @@ const UpdateStatusDialog = (props: Props) => { dispatch({ type: "set_form", form }); }; - const uploadfile = (data: CreateFileResponse) => { - const url = data.signed_url; - const internal_name = data.internal_name; - - const f = file; - if (f === undefined) return; - const newFile = new File([f], `${internal_name}`); - - uploadFile( - url, - newFile, - "PUT", - { - "Content-Type": contentType, - "Content-disposition": "inline", - }, - (xhr: XMLHttpRequest) => { - if (xhr.status >= 200 && xhr.status < 300) { - setUploadStarted(false); - setUploadDone(true); - request(routes.editUpload, { - pathParams: { - id: data.id, - fileType: "SAMPLE_MANAGEMENT", - associatingId: sample.id?.toString() ?? "", - }, - body: { upload_completed: true }, - }); - Notification.Success({ msg: "File Uploaded Successfully" }); + const handleUpload = async () => { + if (fileUpload.files.length > 0) { + if (!fileUpload.fileNames[0]) { + Notification.Error({ + msg: "Please enter a file name before uploading", + }); + return; + } + if (sample.id) { + await fileUpload.handleFileUpload(sample.id); + if (!fileUpload.error) { + return; } else { - setUploadStarted(false); + Notification.Error({ msg: `Upload failed: ${fileUpload.error}` }); } - }, - setUploadPercent, - () => { - setUploadStarted(false); - }, - ); - }; - - const onFileChange = (e: React.ChangeEvent) => { - if (e.target.files == null) { - throw new Error("Error finding e.target.files"); - } - setfile(e.target.files[0]); - const fileName = e.target.files[0].name; - const ext: string = fileName.split(".")[1]; - setcontentType( - HEADER_CONTENT_TYPES[ext as keyof typeof HEADER_CONTENT_TYPES], - ); - return e.target.files[0]; - }; - const handleUpload = async () => { - const f = file; - if (f === undefined) return; - const category = "UNSPECIFIED"; - const name = f.name; - setUploadStarted(true); - setUploadDone(false); - - const { data } = await request(routes.createUpload, { - body: { - original_name: name, - file_type: "SAMPLE_MANAGEMENT", - name: `${sample.patient_name} Sample Report`, - associating_id: sample.id ?? "", - file_category: category, - mime_type: contentType, - }, - }); - - if (data) { - uploadfile(data); + } else { + Notification.Error({ msg: "Sample ID is missing" }); + } + } else { + Notification.Error({ msg: "No file selected for upload" }); } }; @@ -218,32 +160,58 @@ const UpdateStatusDialog = (props: Props) => { onChange={handleChange} /> - Upload Report : + {t("upload_report")}: - {uploadStarted ? ( - - ) : ( -
-