diff --git a/.cursorrules b/.cursorrules index 94ebea1bd57..84ed409d44b 100644 --- a/.cursorrules +++ b/.cursorrules @@ -31,5 +31,5 @@ UI and Styling General Guidelines -- Care uses a custom useQuery hook to fetch data from the API. (Docs @ /Utils/request/useQuery) +- Care uses TanStack Query for data fetching from the API along with query and mutate utilities for the queryFn and mutationFn. (Docs @ /Utils/request/README.md) - APIs are defined in the api.tsx file. diff --git a/.github/workflows/label-merge-conflict.yml b/.github/workflows/label-merge-conflict.yml index 0491a102b2c..a7c77d06910 100644 --- a/.github/workflows/label-merge-conflict.yml +++ b/.github/workflows/label-merge-conflict.yml @@ -1,6 +1,7 @@ name: Auto Label Conflicts permissions: + contents: read issues: write pull-requests: write diff --git a/cypress/e2e/assets_spec/AssetHomepage.cy.ts b/cypress/e2e/assets_spec/AssetHomepage.cy.ts index e19f885db72..fc195c7a803 100644 --- a/cypress/e2e/assets_spec/AssetHomepage.cy.ts +++ b/cypress/e2e/assets_spec/AssetHomepage.cy.ts @@ -113,7 +113,6 @@ rolesToTest.forEach((role) => { it("Export the list of assets in CSV & Json", () => { if (role === "districtAdmin") { assetHome.selectAssetImportButton("click"); - cy.wait(2000); assetHome.selectJsonExportButton(); assetHome.selectAssetImportButton("click"); assetHome.selectCsvExportButton(); diff --git a/cypress/e2e/facility_spec/FacilityCreation.cy.ts b/cypress/e2e/facility_spec/FacilityCreation.cy.ts index d2893166745..4a07a665896 100644 --- a/cypress/e2e/facility_spec/FacilityCreation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityCreation.cy.ts @@ -65,7 +65,6 @@ describe("Facility Creation with multiple user roles", () => { beforeEach(() => { cy.viewport(1280, 720); cy.restoreLocalStorage(); - cy.awaitUrl("/facility"); }); it("Create a new facility with all fields | Edit Existing Data | Verify its reflection", () => { diff --git a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts index 0aa8518a6d1..c43106ba869 100644 --- a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts @@ -196,7 +196,9 @@ describe("Facility Homepage Function", () => { }); it("Verify the bed capacity badge reflection", () => { + facilityHome.interceptFacilitySearchReq(); facilityHome.typeFacilitySearch(facilityWithNoAvailableBeds); + facilityHome.verifyFacilitySearchReq(); facilityHome.assertFacilityInCard(facilityWithNoAvailableBeds); cy.url().then((url) => { const facilityUrl = url.toString(); diff --git a/cypress/e2e/facility_spec/FacilityInventory.cy.ts b/cypress/e2e/facility_spec/FacilityInventory.cy.ts index b3f77479763..b79998fd5cd 100644 --- a/cypress/e2e/facility_spec/FacilityInventory.cy.ts +++ b/cypress/e2e/facility_spec/FacilityInventory.cy.ts @@ -25,15 +25,20 @@ describe("Inventory Management Section", () => { it("Add New Inventory | Modify data and delete last entry ", () => { // add a new item + facilityPage.interceptManageInventoryItem(); facilityPage.clickManageInventory(); + facilityPage.verifyManageInventoryItem(); facilityPage.fillInventoryDetails("PPE", "Add Stock", "10"); facilityPage.clickAddInventory(); facilityPage.verifySuccessNotification("Inventory created successfully"); + cy.closeNotification(); facilityPage.clickManageInventory(); // modify the new item facilityPage.fillInventoryDetails("PPE", "Use Stock", "5"); facilityPage.clickAddInventory(); - facilityPage.verifySuccessNotification("Inventory created successfully"); + facilityPage.verifySuccessNotification( + "Inventory use stock updated successfully", + ); // verify the new modification facilityPage.verifyPpeQuantity("PPE"); facilityPage.verifyPpeQuantity("5"); @@ -43,7 +48,6 @@ describe("Inventory Management Section", () => { // verify the last entry deletion facilityPage.verifyStockInRow("#row-0", "Added Stock"); facilityPage.verifyStockInRow("#row-1", "Used Stock"); - cy.wait(3000); facilityHome.navigateBack(); facilityPage.verifyPpeQuantity("PPE"); }); @@ -57,9 +61,10 @@ describe("Inventory Management Section", () => { cy.closeNotification(); // Verify Backend minimum badge facilityPage.verifyBadgeWithText(".badge-danger", "Low Stock"); + facilityPage.interceptMinimumQuantity(); // modify with manual minimum badge - facilityPage.clickAddMinimumQuanitity(); - cy.wait(3000); + facilityPage.clickAddMinimumQuantity(); + facilityPage.verifyMinimumQuantity(); cy.get("body").then(($body) => { if ($body.find("#update-minimum-quantity").is(":visible")) { // If the 'update-minimum-quantity' element is visible, click it diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index a257d127942..2969d2ddb93 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -104,8 +104,9 @@ describe("Patient Consultation in multiple combination", () => { patientPrescription.selectMedicine(medicineOne); patientPrescription.enterDosage("3"); patientPrescription.selectDosageFrequency("Twice daily"); + patientPrescription.interceptPrescriptions(); cy.clickSubmitButton("Submit"); - cy.wait(2000); + patientPrescription.verifyPrescription(); cy.verifyNotification("Medicine prescribed"); patientPrescription.clickReturnToDashboard(); // Verify the data's across the dashboard @@ -376,7 +377,9 @@ describe("Patient Consultation in multiple combination", () => { it("Edit created consultation to existing patient", () => { patientPage.visitPatient("Dummy Patient Thirteen"); + patientConsultationPage.interceptConsultation(); patientConsultationPage.clickEditConsultationButton(); + patientConsultationPage.verifyConsultation(); patientConsultationPage.typePatientIllnessHistory("editted"); patientConsultationPage.selectPatientDiagnosis( diagnosis5, diff --git a/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts b/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts index b7ef6936804..a3b4906e0ca 100644 --- a/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts @@ -25,6 +25,7 @@ describe("Patient Discharge based on multiple reason", () => { }); beforeEach(() => { + cy.viewport(1280, 720); cy.restoreLocalStorage(); cy.clearLocalStorage(/filters--.+/); cy.awaitUrl("/patients"); @@ -35,7 +36,9 @@ describe("Patient Discharge based on multiple reason", () => { patientDischarge.clickDischarge(); patientDischarge.selectDischargeReason(patientDischargeReason4); cy.clickSubmitButton("Confirm Discharge"); + patientDischarge.interceptDischargePatient(); cy.clickSubmitButton("Acknowledge & Submit"); + patientDischarge.verifyDischargePatient(); cy.verifyNotification("Patient Discharged Successfully"); cy.closeNotification(); // Verify the consultation dashboard reflection @@ -53,7 +56,9 @@ describe("Patient Discharge based on multiple reason", () => { patientDischarge.typeDischargeNote(patientDeathCause); patientDischarge.typeDoctorName(doctorName); cy.clickSubmitButton("Confirm Discharge"); + patientDischarge.interceptDischargePatient(); cy.clickSubmitButton("Acknowledge & Submit"); + patientDischarge.verifyDischargePatient(); cy.verifyNotification("Patient Discharged Successfully"); cy.closeNotification(); // Verify the consultation dashboard reflection @@ -76,10 +81,10 @@ describe("Patient Discharge based on multiple reason", () => { patientDischarge.clickClearButton(); // select a non-registered facility and perform the discharge patientDischarge.typeReferringFacility(referringFreetextFacility); - cy.wait(2000); cy.clickSubmitButton("Confirm Discharge"); + patientDischarge.interceptDischargePatient(); cy.clickSubmitButton("Acknowledge & Submit"); - cy.wait(2000); + patientDischarge.verifyDischargePatient(); cy.verifyNotification("Patient Discharged Successfully"); cy.closeNotification(); // Verify the consultation dashboard reflection @@ -106,22 +111,14 @@ describe("Patient Discharge based on multiple reason", () => { patientPrescription.selectDosageFrequency("Twice daily"); cy.clickSubmitButton("Submit"); cy.verifyNotification("Medicine prescribed"); - cy.wait(2000); cy.closeNotification(); // submit the discharge pop-up cy.clickSubmitButton("Confirm Discharge"); + patientDischarge.interceptDischargePatient(); cy.clickSubmitButton("Acknowledge & Submit"); - cy.wait(2000); + patientDischarge.verifyDischargePatient(); cy.verifyNotification("Patient Discharged Successfully"); - cy.closeNotification(); // Verify the consultation dashboard reflection - cy.verifyContentPresence("#consultation-buttons", ["Recovered"]); - // Verify the dashboard and discharge information - cy.verifyContentPresence("#discharge-information", [ - patientDischargeReason1, - patientDischargeAdvice, - patientMedicine, - ]); }); afterEach(() => { diff --git a/cypress/e2e/patient_spec/PatientHomepage.cy.ts b/cypress/e2e/patient_spec/PatientHomepage.cy.ts index 8949fc3e324..8f5ac03a9ef 100644 --- a/cypress/e2e/patient_spec/PatientHomepage.cy.ts +++ b/cypress/e2e/patient_spec/PatientHomepage.cy.ts @@ -161,7 +161,6 @@ describe("Patient Homepage present functionalities", () => { .then((patientOne: string) => { firstPatientPageOne = patientOne.trim(); pageNavigation.navigateToNextPage(); - cy.wait(2000); pageNavigation.verifyCurrentPageNumber(2); cy.get('[data-cy="patient"]') .first() diff --git a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts index ec5aef250dc..963f62a76e0 100644 --- a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts +++ b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts @@ -13,6 +13,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const patientInvestigation = new PatientInvestigation(); const patientPrescription = new PatientPrescription(); const patientCategory = "Moderate"; + const patientModifiedCategory = "Critical"; const additionalSymptoms = "Fever"; const physicalExamination = "physical examination details"; const otherExamination = "Other"; @@ -58,7 +59,9 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.clickSubmitButton("Update Consultation"); cy.verifyNotification("Consultation updated successfully"); cy.closeNotification(); + patientPage.interceptGetPatient(); patientLogupdate.clickLogupdate(); + patientPage.verifyGetPatientResponse(); patientLogupdate.typePhysicalExamination(physicalExamination); patientLogupdate.selectRoundType("Tele-medicine Log"); patientLogupdate.selectPatientCategory(patientCategory); @@ -80,11 +83,15 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { it("Create a new Progress log update for a admitted patient and edit it", () => { patientPage.visitPatient(patientOne); + patientLogupdate.interceptConsultationBed(); patientLogupdate.clickLogupdate(); + patientLogupdate.verifyConsultationBed(); cy.verifyNotification("Please assign a bed to the patient"); patientLogupdate.selectBed(bedOne); cy.closeNotification(); + patientPage.interceptGetPatient(); patientLogupdate.clickLogupdate(); + patientPage.verifyGetPatientResponse(); // Only will be using random non-unique progress note fields patientLogupdate.selectRoundType("Progress Note"); patientLogupdate.selectPatientCategory(patientCategory); @@ -112,15 +119,18 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.verifyNotification("Medicine prescribed"); cy.closeNotification(); // Submit the doctors log update + patientLogupdate.interceptDailyRounds(); cy.clickSubmitButton("Save and Continue"); - cy.wait(2000); + patientLogupdate.verifyDailyRounds(); cy.verifyNotification("Progress Note created successfully"); cy.closeNotification(); // modify the relevant critical care log update patientLogupdate.selectCriticalCareSection("Neurological Monitoring"); cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); cy.get("#left_pupil_light_reaction-option-FIXED").click(); + patientLogupdate.interceptpatchDailyRounds(); cy.clickSubmitButton("Update Details"); + patientLogupdate.verifypatchDailyRounds(); cy.verifyNotification( "Neurological Monitoring details succesfully updated.", ); @@ -141,6 +151,9 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { ]); // verify the edit functionality patientLogupdate.clickUpdateDetail(); + patientLogupdate.verifyPatientCategory(patientCategory); + patientLogupdate.verifyRoundType("Progress Note"); + patientLogupdate.selectPatientCategory(patientModifiedCategory); patientLogupdate.typeSystolic(patientModifiedSystolic); patientLogupdate.typeDiastolic(patientModifiedDiastolic); cy.clickSubmitButton("Continue"); @@ -156,7 +169,9 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.clickLogupdate(); patientLogupdate.selectRoundType("Detailed Update"); patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.interceptDailyRounds(); cy.clickSubmitButton("Save and Continue"); + patientLogupdate.verifyDailyRounds(); cy.verifyNotification("Detailed Update created successfully"); cy.closeNotification(); // Select two Section - First One is Respiratory Support @@ -235,9 +250,12 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.verifyNotification("Please assign a bed to the patient"); patientLogupdate.selectBed(bedThree); cy.closeNotification(); + patientPage.interceptGetPatient(); patientLogupdate.clickLogupdate(); - patientLogupdate.typePhysicalExamination(physicalExamination); + patientPage.verifyGetPatientResponse(); + patientLogupdate.verifyRoundType("Brief Update"); patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.typePhysicalExamination(physicalExamination); patientLogupdate.typeOtherDetails(otherExamination); patientLogupdate.selectSymptomsDate("01012024"); patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); @@ -251,9 +269,11 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.selectRhythm(patientRhythmType); patientLogupdate.typeRhythm(patientRhythm); cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); + patientConsultationPage.interceptConsultation(); cy.clickSubmitButton("Save"); - cy.wait(2000); + patientConsultationPage.verifyConsultation(); cy.verifyNotification("Brief Update created successfully"); + cy.closeNotification(); // Verify the card content cy.get("#basic-information").scrollIntoView(); cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]); @@ -267,11 +287,13 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.verifyNotification("Consultation updated successfully"); cy.closeNotification(); patientLogupdate.clickLogupdate(); + patientLogupdate.verifyRoundType("Brief Update"); // Verify the default round type + patientLogupdate.selectRoundType("Brief Update"); + patientLogupdate.selectPatientCategory(patientCategory); patientLogupdate.typePhysicalExamination(physicalExamination); patientLogupdate.typeOtherDetails(otherExamination); patientLogupdate.selectSymptomsDate("01012024"); patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.selectPatientCategory(patientCategory); patientLogupdate.typeSystolic(patientSystolic); patientLogupdate.typeDiastolic(patientDiastolic); patientLogupdate.typePulse(patientPulse); @@ -303,10 +325,10 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientRhythm, ]); patientLogupdate.clickUpdateDetail(); - patientLogupdate.clearIntoElementById("#systolic"); - patientLogupdate.typeSystolic(patientModifiedSystolic); - patientLogupdate.clearIntoElementById("#diastolic"); - patientLogupdate.typeDiastolic(patientModifiedDiastolic); + patientLogupdate.verifyPatientCategory(patientCategory); + patientLogupdate.verifyRoundType("Brief Update"); + patientLogupdate.typeSystolic(patientModifiedSystolic, true); + patientLogupdate.typeDiastolic(patientModifiedDiastolic, true); cy.clickSubmitButton("Continue"); cy.verifyNotification("Brief Update updated successfully"); cy.contains("button", "Log Updates").click(); diff --git a/cypress/e2e/patient_spec/PatientPrescription.cy.ts b/cypress/e2e/patient_spec/PatientPrescription.cy.ts index 61f8067eea0..bc883e7a5fd 100644 --- a/cypress/e2e/patient_spec/PatientPrescription.cy.ts +++ b/cypress/e2e/patient_spec/PatientPrescription.cy.ts @@ -113,7 +113,6 @@ describe("Patient Medicine Administration", () => { cy.closeNotification(); // Administer the medicine in edit form patientPrescription.clickAdministerButton(); - cy.wait(2000); patientPrescription.enterAdministerDosage(medicineBaseDosage); patientPrescription.enterAdministerNotes(medicineAdministerNote); cy.clickSubmitButton("Administer Medicine"); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index b11d0f8f585..a8b1e47bb4d 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -1,3 +1,5 @@ +import { PatientConsultationPage } from "pageobject/Patient/PatientConsultation"; + import LoginPage from "../../pageobject/Login/LoginPage"; import { PatientData, @@ -6,7 +8,11 @@ import { import PatientInsurance from "../../pageobject/Patient/PatientInsurance"; import PatientMedicalHistory from "../../pageobject/Patient/PatientMedicalHistory"; import PatientTransfer from "../../pageobject/Patient/PatientTransfer"; -import { generatePhoneNumber } from "../../pageobject/utils/constants"; +import { + generatePatientName, + generatePhoneNumber, + generateRandomAddress, +} from "../../pageobject/utils/constants"; const yearOfBirth = "2001"; @@ -15,38 +21,21 @@ const calculateAge = () => { return currentYear - parseInt(yearOfBirth); }; -const getRelativeDateString = (deltaDays = 0) => { - const date = new Date(); - if (deltaDays) { - date.setDate(date.getDate() + deltaDays); - } - return date - .toLocaleDateString("en-IN", { - day: "2-digit", - month: "2-digit", - year: "numeric", - }) - .replace(/\//g, ""); -}; - describe("Patient Creation with consultation", () => { const loginPage = new LoginPage(); const patientPage = new PatientPage(); const patientTransfer = new PatientTransfer(); const patientInsurance = new PatientInsurance(); const patientMedicalHistory = new PatientMedicalHistory(); + const patientConsultationPage = new PatientConsultationPage(); const phone_number = generatePhoneNumber(); const age = calculateAge(); const patientFacility = "Dummy Facility 40"; const patientDateOfBirth = "01012001"; - const patientMenstruationStartDate = getRelativeDateString(-10); - const patientDateOfDelivery = getRelativeDateString(-20); - const patientOneName = "Great Napolean 14"; + const patientOneName = generatePatientName(); const patientOneGender = "Male"; const patientOneUpdatedGender = "Female"; - const patientOneAddress = `149/J, 3rd Block, - Aluva - Ernakulam, Kerala - 682001`; + const patientOneAddress = generateRandomAddress(true); const patientOnePincode = "682001"; const patientOneState = "Kerala"; const patientOneDistrict = "Ernakulam"; @@ -115,14 +104,11 @@ describe("Patient Creation with consultation", () => { it("Create a new patient with all field in registration form and no consultation", () => { patientPage.createPatientWithData(newPatientData); - // Verify the patient details patientPage.clickCancelButton(); - cy.wait(3000); - patientPage.savePatientUrl(); + // Verify the patient details patientPage.verifyPatientDashboardDetails( patientOneGender, age, - patientOneName, phone_number, phone_number, yearOfBirth, @@ -150,21 +136,15 @@ describe("Patient Creation with consultation", () => { patientPage.verifyPatientNameList(patientOneName); }); - it("Edit the patient details with no consultation and verify", () => { - patientPage.interceptFacilities(); - patientPage.visitUpdatePatientUrl(); - patientPage.verifyStatusCode(); - patientPage.patientformvisibility(); - // change the gender to female and input data to related changed field - cy.wait(3000); + it("Edit the patient details and verify its reflection", () => { + const patientName = "Dummy Patient Two"; + patientPage.visitPatient(patientName); + patientConsultationPage.clickPatientDetails(); + patientPage.clickPatientUpdateDetails(); patientPage.selectPatientGender(patientOneUpdatedGender); patientPage.typePatientDateOfBirth(patientDateOfBirth); - patientPage.clickPatientAntenatalStatusYes(); - patientPage.typeLastMenstruationStartDate(patientMenstruationStartDate); - patientPage.clickPatientPostPartumStatusYes(); - patientPage.typeDateOfDelivery(patientDateOfDelivery); patientPage.selectPatientBloodGroup(patientOneUpdatedBloodGroup); - // Edit the patient consultation , select none medical history and multiple health ID + // select none medical history and add multiple health ID patientMedicalHistory.clickNoneMedicialHistory(); patientInsurance.clickAddInsruanceDetails(); patientInsurance.typePatientInsuranceDetail( @@ -197,28 +177,13 @@ describe("Patient Creation with consultation", () => { patientOneSecondInsurerName, ); patientPage.clickUpdatePatient(); - cy.wait(3000); patientPage.verifyPatientUpdated(); - patientPage.visitPatientUrl(); - // Verify Female Gender change reflection, No Medical History and Insurance Details - cy.wait(5000); - patientPage.verifyPatientDashboardDetails( - patientOneUpdatedGender, - age, - patientOneName, - phone_number, - phone_number, - yearOfBirth, - patientOneUpdatedBloodGroup, - patientOccupation, - ); // Verify No medical history patientMedicalHistory.verifyNoSymptosPresent("Diabetes"); // verify insurance details and dedicatd page cy.get("[data-testid=patient-details]") .contains("Member ID") .scrollIntoView(); - cy.wait(2000); patientInsurance.verifyPatientPolicyDetails( patientOneFirstSubscriberId, patientOneFirstPolicyId, @@ -249,7 +214,7 @@ describe("Patient Creation with consultation", () => { // allow the transfer button of a patient patientTransfer.clickAllowPatientTransferButton(); // Verify the patient error message for the same facility - cy.awaitUrl("/patients"); + cy.visit("/patients"); patientPage.createPatient(); patientPage.selectFacility(patientTransferFacility); patientPage.patientformvisibility(); diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index d93707617ff..80520b73919 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -1,27 +1,21 @@ import FacilityHome from "pageobject/Facility/FacilityHome"; -import ManageUserPage from "pageobject/Users/ManageUserPage"; -import UserProfilePage from "pageobject/Users/UserProfilePage"; import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; +import { ManageUserPage } from "../../pageobject/Users/ManageUserPage"; import { UserCreationPage } from "../../pageobject/Users/UserCreation"; import { UserPage } from "../../pageobject/Users/UserSearch"; -import { - generateEmergencyPhoneNumber, - generatePhoneNumber, -} from "../../pageobject/utils/constants"; +import { generatePhoneNumber } from "../../pageobject/utils/constants"; describe("User Creation", () => { const userPage = new UserPage(); const loginPage = new LoginPage(); - const userProfilePage = new UserProfilePage(); - const manageUserPage = new ManageUserPage(); const userCreationPage = new UserCreationPage(); + const manageUserPage = new ManageUserPage(); const facilityPage = new FacilityPage(); const facilityHome = new FacilityHome(); const phoneNumber = generatePhoneNumber(); - const emergencyPhoneNumber = generateEmergencyPhoneNumber(); const fillFacilityName = "Dummy Facility 40"; const makeId = (length: number) => { let result = ""; @@ -54,14 +48,6 @@ describe("User Creation", () => { "Please select the local body", ]; - const EXPECTED_PROFILE_ERROR_MESSAGES = [ - "This field is required", - "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"; @@ -74,9 +60,6 @@ describe("User Creation", () => { 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(() => { @@ -90,55 +73,6 @@ describe("User Creation", () => { cy.awaitUrl("/users"); }); - it("Update the existing user profile and verify its reflection", () => { - 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", () => { - manageUserPage.navigateToProfile(); - userProfilePage.clickEditProfileButton(); - userCreationPage.clearFirstName(); - userCreationPage.clearLastName(); - userProfilePage.clearPhoneNumber(); - userProfilePage.clearAltPhoneNumber(); - userProfilePage.clearWorkingHours(); - userProfilePage.clickUpdateButton(); - cy.verifyErrorMessages(EXPECTED_PROFILE_ERROR_MESSAGES); - }); - it("create new user and verify reflection", () => { userCreationPage.clickAddUserButton(); userCreationPage.selectFacility(homeFacility); @@ -147,20 +81,20 @@ describe("User Creation", () => { userCreationPage.typeConfirmPassword(password); userCreationPage.selectHomeFacility(homeFacility); userPage.typeInPhoneNumber(phoneNumber); - userProfilePage.typeDateOfBirth(newUserDob); + manageUserPage.editDateOfBirth(newUserDob); userCreationPage.selectUserType(role); - userProfilePage.typeQualification(qualification); - userProfilePage.typeDoctorYoE(experience); - userProfilePage.typeMedicalCouncilRegistration(regNo); + manageUserPage.editQualification(qualification, false); + manageUserPage.editDoctorYoE(experience, false); + manageUserPage.editMedicalCouncilRegistration(regNo, false); userPage.typeInFirstName(newUserFirstName); userPage.typeInLastName(newUserLastName); - userProfilePage.typeEmail(email); + manageUserPage.editEmail(email, false); userCreationPage.selectGender(gender); userCreationPage.selectState(state); userCreationPage.selectDistrict(district); - cy.intercept("POST", "/api/v1/users/add_user/").as("createUser"); + userCreationPage.interceptCreateUser(); userCreationPage.clickSaveUserButton(); - cy.wait("@createUser").its("response.statusCode").should("eq", 201); + userCreationPage.verifyCreateUser(); cy.verifyNotification("User added successfully"); userPage.typeInSearchInput(username); userPage.checkUsernameText(username); @@ -178,6 +112,7 @@ describe("User Creation", () => { }); it("view user redirection from facility page", () => { + loginPage.ensureLoggedIn(); facilityHome.navigateToFacilityHomepage(); facilityHome.typeFacilitySearch(fillFacilityName); advanceFilters.verifyFilterBadgePresence( diff --git a/cypress/e2e/users_spec/UsersManage.cy.ts b/cypress/e2e/users_spec/UsersManage.cy.ts index 8d7ac8695c6..0d670d8c230 100644 --- a/cypress/e2e/users_spec/UsersManage.cy.ts +++ b/cypress/e2e/users_spec/UsersManage.cy.ts @@ -1,6 +1,7 @@ import * as dayjs from "dayjs"; import FacilityHome from "pageobject/Facility/FacilityHome"; import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; +import { generatePhoneNumber } from "pageobject/utils/constants"; import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; @@ -50,28 +51,48 @@ describe("Manage User", () => { }); */ it("edit a nurse user's basic information and verify its reflection", () => { + const basicInfoErrorMessages = [ + "First Name is required", + "Last Name is required", + ]; + const modifiedFirstName = "Devo"; + const modifiedLastName = "Districto"; + const modifiedRawDOB = "11081999"; + const modifiedGender = "Female"; + const modifiedFormattedDOB = "11/08/1999"; userPage.typeInSearchInput(nurseUsername); userPage.checkUsernameText(nurseUsername); manageUserPage.clickMoreDetailsButton(nurseUsername); manageUserPage.verifyMoreDetailsPage(); - manageUserPage.clickBasicInfoViewButton(); + manageUserPage.clickBaicInfoViewButton(); manageUserPage.clickBasicInfoEditButton(); manageUserPage.clearUserBasicInfo(); - manageUserPage.clickSubmit(); - manageUserPage.verifyErrorText("First Name is required"); - manageUserPage.verifyErrorText("Last Name is required"); - manageUserPage.editUserBasicInfo("Devo", "Districto", "11081999", "Female"); - manageUserPage.clickSubmit(); - manageUserPage.clickBasicInfoViewButton(); + manageUserPage.clickUserInfoSubmitButton(); + cy.verifyErrorMessages(basicInfoErrorMessages); + manageUserPage.editUserBasicInfo( + modifiedFirstName, + modifiedLastName, + modifiedRawDOB, + modifiedGender, + ); + manageUserPage.clickUserInfoSubmitButton(); + manageUserPage.userInfoUpdateSuccessNotification(); + manageUserPage.clickBaicInfoViewButton(); manageUserPage.verifyEditUserDetails( - "Devo", - "Districto", - "11/08/1999", - "Female", + modifiedFirstName, + modifiedLastName, + modifiedFormattedDOB, + modifiedGender, ); }); it("edit a nurse user's contact information and verify its reflection", () => { + const contactInfoErrorMessages = [ + "Please enter a valid email address", + "Please enter valid phone number", + ]; + const modifiedEmail = "dev@gmail.com"; + const modifiedPhone = generatePhoneNumber(); userPage.typeInSearchInput(nurseUsername); userPage.checkUsernameText(nurseUsername); manageUserPage.clickMoreDetailsButton(nurseUsername); @@ -79,16 +100,18 @@ describe("Manage User", () => { manageUserPage.clickContactInfoViewButton(); manageUserPage.clickContactInfoEditButton(); manageUserPage.clearUserContactInfo(); - manageUserPage.clickSubmit(); - manageUserPage.verifyErrorText("Please enter a valid email address"); - manageUserPage.verifyErrorText("Please enter valid phone number"); - manageUserPage.editUserContactInfo("dev@gmail.com", "6234343435"); - manageUserPage.clickSubmit(); + manageUserPage.clickUserInfoSubmitButton(); + cy.verifyErrorMessages(contactInfoErrorMessages); + manageUserPage.editUserContactInfo(modifiedEmail, modifiedPhone); + manageUserPage.clickUserInfoSubmitButton(); + manageUserPage.userInfoUpdateSuccessNotification(); manageUserPage.clickContactInfoViewButton(); - manageUserPage.verifyEditUserContactInfo("dev@gmail.com", "6234343435"); + manageUserPage.verifyEditUserContactInfo(modifiedEmail, modifiedPhone); }); it("edit a nurse user's professional information and verify its reflection", () => { + const qualificationErrorMessages = ["Qualification is required"]; + const qualification = "Msc"; userPage.typeInSearchInput(nurseUsername); userPage.checkUsernameText(nurseUsername); manageUserPage.clickMoreDetailsButton(nurseUsername); @@ -100,16 +123,28 @@ describe("Manage User", () => { manageUserPage.verifyYoeAndCouncilRegistrationDoesntExist(); manageUserPage.clickProfessionalInfoEditButton(); manageUserPage.clearDoctorOrNurseProfessionalInfo(false); - manageUserPage.clickSubmit(); - manageUserPage.verifyErrorText("Qualification is required"); - manageUserPage.editUserProfessionalInfo("Msc"); - manageUserPage.clickSubmit(); + manageUserPage.clickUserInfoSubmitButton(); + cy.verifyErrorMessages(qualificationErrorMessages); + manageUserPage.editUserProfessionalInfo(qualification); + manageUserPage.clickUserInfoSubmitButton(); + manageUserPage.userInfoUpdateSuccessNotification(); manageUserPage.clickProfessionalInfoViewButton(); - manageUserPage.verifyEditUserProfessionalInfo("Msc"); + manageUserPage.verifyEditUserProfessionalInfo(qualification); }); it("edit a doctor user's professional information and verify its reflection", () => { // Should have qualification, years of experience and medical council registration + const qualificationErrorMessages = [ + "Qualification is required", + "Years of experience is required", + "Medical Council Registration is required", + ]; + const qualification = "Msc"; + const yoe = "120"; + const modifiedYoe = "10"; + const medicalRegistrationNumber = "1234567890"; + const experienceCommencedOn = dayjs().subtract(10, "year"); + const formattedDate = dayjs(experienceCommencedOn).format("YYYY-MM-DD"); userPage.typeInSearchInput(usernameToLinkFacilitydoc1); userPage.checkUsernameText(usernameToLinkFacilitydoc1); manageUserPage.clickMoreDetailsButton(usernameToLinkFacilitydoc1); @@ -119,25 +154,28 @@ describe("Manage User", () => { manageUserPage.verifyYoeAndCouncilRegistrationExist(); manageUserPage.clickProfessionalInfoEditButton(); manageUserPage.clearDoctorOrNurseProfessionalInfo(true); - manageUserPage.clickSubmit(); - manageUserPage.verifyErrorText("Qualification is required"); - manageUserPage.verifyErrorText("Years of experience is required"); - manageUserPage.verifyErrorText("Medical Council Registration is required"); - manageUserPage.editUserProfessionalInfo("Msc", "120", "1234567890"); - manageUserPage.clickSubmit(); - manageUserPage.verifyErrorText( - "Please enter a valid number between 0 and 100.", + manageUserPage.clickUserInfoSubmitButton(); + cy.verifyErrorMessages(qualificationErrorMessages); + manageUserPage.editUserProfessionalInfo( + qualification, + yoe, + medicalRegistrationNumber, ); + manageUserPage.clickUserInfoSubmitButton(); + cy.verifyErrorMessages(["Please enter a valid number between 0 and 100."]); manageUserPage.clearDoctorOrNurseProfessionalInfo(true); - manageUserPage.editUserProfessionalInfo("Msc", "10", "1234567890"); - manageUserPage.clickSubmit(); + manageUserPage.editUserProfessionalInfo( + qualification, + modifiedYoe, + medicalRegistrationNumber, + ); + manageUserPage.clickUserInfoSubmitButton(); + manageUserPage.userInfoUpdateSuccessNotification(); manageUserPage.clickProfessionalInfoViewButton(); - const experienceCommencedOn = dayjs().subtract(10, "year"); - const formattedDate = dayjs(experienceCommencedOn).format("YYYY-MM-DD"); manageUserPage.verifyEditUserProfessionalInfo( - "Msc", + qualification, formattedDate, - "1234567890", + medicalRegistrationNumber, ); }); @@ -151,7 +189,7 @@ describe("Manage User", () => { userPage.checkUsernameText(doctorUsername); manageUserPage.clickMoreDetailsButton(doctorUsername); manageUserPage.verifyMoreDetailsPage(false); - manageUserPage.verifyUsername(doctorUsername); + cy.verifyContentPresence("#view-username", [doctorUsername]); manageUserPage.verifyBasicInfoEditButtonNotExist(); manageUserPage.verifyContactInfoEditButtonNotExist(); manageUserPage.verifyProfessionalInfoEditButtonNotExist(); @@ -189,9 +227,11 @@ describe("Manage User", () => { userPage.checkUsernameText(nurseUsername); manageUserPage.clickMoreDetailsButton(nurseUsername); manageUserPage.verifyMoreDetailsPage(); - manageUserPage.clickPasswordEditButton(); + cy.verifyAndClickElement("#change-edit-password-button", "Change Password"); manageUserPage.changePassword("Coronasafe@123", "Coronasafe@1233"); - manageUserPage.clickSubmit(); + cy.clickSubmitButton(); + cy.verifyNotification("Password updated successfully"); + cy.closeNotification(); loginPage.ensureLoggedIn(); loginPage.clickSignOutBtn(); loginPage.loginManuallyAsNurse("Coronasafe@1233"); @@ -201,9 +241,11 @@ describe("Manage User", () => { userPage.checkUsernameText(nurseUsername); manageUserPage.clickMoreDetailsButton(nurseUsername); manageUserPage.verifyMoreDetailsPage(); - manageUserPage.clickPasswordEditButton(); + cy.verifyAndClickElement("#change-edit-password-button", "Change Password"); manageUserPage.changePassword("Coronasafe@1233", "Coronasafe@123"); - manageUserPage.clickSubmit(); + cy.clickSubmitButton(); + cy.verifyNotification("Password updated successfully"); + cy.closeNotification(); loginPage.ensureLoggedIn(); loginPage.clickSignOutBtn(); loginPage.loginManuallyAsDistrictAdmin(); @@ -217,7 +259,7 @@ describe("Manage User", () => { manageUserPage.verifyMoreDetailsPage(); manageUserPage.verifyDeleteButtonVisible(); manageUserPage.clickDeleteButton(); - manageUserPage.clickSubmit(); + cy.clickSubmitButton("Delete"); cy.verifyNotification("User Deleted Successfully"); cy.closeNotification(); userPage.typeInSearchInput(doctorToDelete); @@ -230,26 +272,14 @@ describe("Manage User", () => { userPage.checkUsernameText(usernameforworkinghour); manageUserPage.clickMoreDetailsButton(usernameforworkinghour); manageUserPage.verifyMoreDetailsPage(); + manageUserPage.interceptLinkedSkillTab(); manageUserPage.clickLinkedSkillTab(); - cy.wait(500); + manageUserPage.verifyLinkedSkillResponse(); manageUserPage.verifyLinkedSkillsTabPage(); manageUserPage.selectSkillFromDropdown(linkedskill); + manageUserPage.interceptAddSkill(); manageUserPage.clickAddSkillButton(usernameforworkinghour); - cy.wait(500); - manageUserPage.assertSkillInAddedUserSkills(linkedskill); - cy.wait(500); - manageUserPage.navigateToProfile(); - cy.verifyContentPresence("#username-profile-details", [ - usernameforworkinghour, - ]); - manageUserPage.assertSkillInAlreadyLinkedSkills(linkedskill); - // unlink the skill - manageUserPage.navigateToManageUser(); - userPage.typeInSearchInput(usernameforworkinghour); - userPage.checkUsernameText(usernameforworkinghour); - manageUserPage.clickMoreDetailsButton(usernameforworkinghour); - manageUserPage.verifyMoreDetailsPage(); - manageUserPage.clickLinkedSkillTab(); + manageUserPage.verifyAddSkillResponse(); manageUserPage.assertSkillInAddedUserSkills(linkedskill); manageUserPage.clickUnlinkSkill(); manageUserPage.verifyUnlinkSkillModal(); @@ -282,7 +312,7 @@ describe("Manage User", () => { manageUserPage.assertSkillIndoctorconnect(linkedskill); }); - it("add working hour for a user and verify its reflection in card and user profile", () => { + it("add working hour and video connect link for a user and verify its reflection in card and user profile", () => { // verify qualification and yoe and council registration fields are not present // verify field error and add working hour userPage.typeInSearchInput(usernameforworkinghour); @@ -290,23 +320,28 @@ describe("Manage User", () => { manageUserPage.clickMoreDetailsButton(usernameforworkinghour); manageUserPage.verifyMoreDetailsPage(); manageUserPage.verifyProfileTabPage(); - manageUserPage.clickProfessionalInfoViewButton(); + cy.verifyAndClickElement("#professional-info-view-button", "View"); manageUserPage.verifyQualificationDoesntExist(); manageUserPage.verifyYoeAndCouncilRegistrationDoesntExist(); - manageUserPage.clickProfessionalInfoEditButton(); + cy.verifyAndClickElement("#professional-info-edit-button", "Edit"); manageUserPage.clearProfessionalInfo(); - manageUserPage.typeInWeeklyWorkingHours("200"); - manageUserPage.clickSubmit(); - manageUserPage.verifyErrorText( + manageUserPage.editWeeklyWorkingHours("200"); + cy.clickSubmitButton(); + cy.verifyErrorMessages([ "Average weekly working hours must be a number between 0 and 168", - ); + ]); manageUserPage.clearProfessionalInfo(); - manageUserPage.typeInWeeklyWorkingHours(workinghour); - manageUserPage.clickSubmit(); - // verify the data is reflected in the page - manageUserPage.verifyWorkingHours(workinghour); - manageUserPage.navigateToProfile(); - manageUserPage.verifyProfileWorkingHours(workinghour); + manageUserPage.editHoursAndVideoConnectLink( + workinghour, + "https://www.example.com", + ); + cy.clickSubmitButton(); + cy.verifyNotification("User details updated successfully"); + cy.closeNotification(); + manageUserPage.verifyHoursAndVideoConnectLink( + workinghour, + "https://www.example.com", + ); }); it("linking and unlinking facility for multiple users, and confirm reflection in user cards and doctor connect", () => { @@ -356,7 +391,7 @@ describe("Manage User", () => { manageUserPage.clickLinkFacility(); manageUserPage.clickLinkedFacilitySettings(); manageUserPage.clickUnlinkFacilityButton(); - manageUserPage.clickSubmit(); + cy.clickSubmitButton("Unlink"); manageUserPage.linkedfacilitylistnotvisible(); // Go to particular facility doctor connect and all user-id are reflected based on there access // Path will be facility page to patient page then doctor connect button diff --git a/cypress/e2e/users_spec/UsersProfile.cy.ts b/cypress/e2e/users_spec/UsersProfile.cy.ts deleted file mode 100644 index 551fba4c0f1..00000000000 --- a/cypress/e2e/users_spec/UsersProfile.cy.ts +++ /dev/null @@ -1,82 +0,0 @@ -import FacilityHome from "pageobject/Facility/FacilityHome"; - -import LoginPage from "../../pageobject/Login/LoginPage"; -import ManageUserPage from "../../pageobject/Users/ManageUserPage"; -import UserProfilePage from "../../pageobject/Users/UserProfilePage"; - -describe("Manage User Profile", () => { - const loginPage = new LoginPage(); - const userProfilePage = new UserProfilePage(); - const manageUserPage = new ManageUserPage(); - const facilityHome = new FacilityHome(); - - const date_of_birth = "01011999"; - const gender = "Male"; - const email = "test@example.com"; - const phone = "8899887788"; - const workinghours = "8"; - const qualification = "MBBS"; - const doctorYoE = "10"; - const medicalCouncilRegistration = "1234567890"; - - const facilitySearch = "Dummy Facility 40"; - - before(() => { - loginPage.loginByRole("devDoctor"); - cy.saveLocalStorage(); - }); - - beforeEach(() => { - cy.restoreLocalStorage(); - cy.clearLocalStorage(/filters--.+/); - cy.awaitUrl("/user/profile"); - }); - - it("Set Dob, Gender, Email, Phone and Working Hours for a user and verify its reflection in user profile", () => { - userProfilePage.clickEditProfileButton(); - - userProfilePage.typeDateOfBirth(date_of_birth); - userProfilePage.selectGender(gender); - userProfilePage.typeEmail(email); - 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.assertDateOfBirth("01/01/1999"); - userProfilePage.assertGender(gender); - userProfilePage.assertEmail(email); - userProfilePage.assertPhoneNumber(phone); - userProfilePage.assertAltPhoneNumber(phone); - userProfilePage.assertWorkingHours(workinghours); - }); - - it("Adding video connect link for a user and verify its reflection in user profile and doctor connect", () => { - // verify the user doesn't have any video connect link - userProfilePage.assertVideoConnectLink("-"); - // Link a new video connect link and ensure it is under video connect link - userProfilePage.clickEditProfileButton(); - userProfilePage.typeVideoConnectLink("https://www.example.com"); - userProfilePage.clickUpdateButton(); - userProfilePage.assertVideoConnectLink("https://www.example.com"); - // Edit the video connect link and ensure it is updated - userProfilePage.clickEditProfileButton(); - userProfilePage.typeVideoConnectLink("https://www.test.com"); - userProfilePage.clickUpdateButton(); - userProfilePage.assertVideoConnectLink("https://www.test.com"); - // Go to particular facility doctor connect and verify the video connect link is present - facilityHome.navigateToFacilityHomepage(); - facilityHome.typeFacilitySearch(facilitySearch); - facilityHome.assertFacilityInCard(facilitySearch); - manageUserPage.clickFacilityPatients(); - manageUserPage.clickDoctorConnectButton(); - manageUserPage.assertVideoConnectLink("Dev Doctor", "https://www.test.com"); - }); - - afterEach(() => { - cy.saveLocalStorage(); - }); -}); diff --git a/cypress/pageobject/Asset/AssetHome.ts b/cypress/pageobject/Asset/AssetHome.ts index e127b785100..1c3cdd20399 100644 --- a/cypress/pageobject/Asset/AssetHome.ts +++ b/cypress/pageobject/Asset/AssetHome.ts @@ -69,7 +69,7 @@ export class AssetHome { selectAssetImportButton(action: "click" | "verifyNotExist"): void { const selector = "[data-testid=import-asset-button]"; if (action === "click") { - cy.get(selector).click(); + cy.get(selector).scrollIntoView().should("be.visible").click(); } else if (action === "verifyNotExist") { cy.get(selector).should("not.exist"); } @@ -77,13 +77,13 @@ export class AssetHome { selectJsonExportButton() { cy.intercept("GET", "**/api/v1/asset/?**json=true**").as("getJsonexport"); - cy.get("#export-json-option").click(); + cy.get("#export-json-option").should("be.visible").click(); cy.wait("@getJsonexport").its("response.statusCode").should("eq", 200); } selectCsvExportButton() { cy.intercept("GET", "**/api/v1/asset/?**csv=true**").as("getCsvexport"); - cy.get("#export-csv-option").click(); + cy.get("#export-csv-option").should("be.visible").click(); cy.wait("@getCsvexport").its("response.statusCode").should("eq", 200); } diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index 9776433e523..f03fa65ce8b 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -21,7 +21,7 @@ export interface FacilityData { class FacilityPage { visitCreateFacilityPage() { cy.intercept("GET", "**/facility/create").as("getCreateFacilities"); - cy.visit("/facility/create"); + cy.awaitUrl("/facility/create"); cy.wait("@getCreateFacilities") .its("response.statusCode") .should("eq", 200); @@ -44,8 +44,7 @@ class FacilityPage { } selectWard(ward: string) { - cy.get("div#ward button").click(); - cy.get("[role='option']").contains(ward).click(); + advanceFilters.selectWard(ward); } typeFacilityAddress(address: string, clearBeforeTyping: boolean = false) { @@ -56,13 +55,24 @@ class FacilityPage { phoneNumber: string, clearBeforeTyping: boolean = false, ) { - cy.typeIntoField("#phone_number", phoneNumber, { clearBeforeTyping }); + cy.typeIntoField("#phone_number", phoneNumber, { + clearBeforeTyping, + skipVerification: true, + }); } clickSaveFacilityButton() { cy.verifyAndClickElement("#submit", "Save Facility"); } + interceptFacility() { + cy.intercept("POST", "**/api/v1/facility/").as("postFacility"); + } + + verifyErrorFacility() { + cy.wait("@postFacility").its("response.statusCode").should("eq", 403); + } + verifyFacilityCreatedNotification() { cy.verifyNotification("Facility added successfully"); cy.closeNotification(); @@ -159,8 +169,7 @@ class FacilityPage { cy.get("#facility-location-button").click(); cy.wait("@mapApi").its("response.statusCode").should("eq", 200); cy.get("input#pac-input").type(location).type("{enter}"); - cy.wait(2000); - cy.get("div#map-close").click(); + cy.get("div#map-close").should("be.visible").click(); } fillMiddleWareAddress(url: string) { @@ -202,21 +211,27 @@ class FacilityPage { cy.url().should("include", "/assets?facility="); } + interceptManageInventoryItem() { + cy.intercept("GET", "/api/v1/items/**").as("getItems"); + } + clickManageInventory() { cy.contains("Manage Inventory").click(); } + verifyManageInventoryItem() { + cy.wait("@getItems").its("response.statusCode").should("eq", 200); + } + fillInventoryDetails(name: string, status: string, quantity: string) { - cy.wait(2000); - cy.get("div#id").click(); - cy.get("div#id ul li").contains(name).click(); cy.get("div#isIncoming").click(); cy.get("div#isIncoming ul li").contains(status).click(); + cy.get("div#id").click(); + cy.get("div#id ul li").contains(name).click(); cy.get("[name='quantity']").type(quantity); } fillInventoryMinimumDetails(name: string, quantity: string) { - cy.wait(2000); cy.get("div#id").click(); cy.get("div#id ul li").contains(name).click(); cy.get("[name='quantity']").type(quantity); @@ -262,39 +277,12 @@ class FacilityPage { .should("eq", 201); } - getStateElement() { - return cy.get("#state"); - } - - getDistrictElement() { - return cy.get("#district"); - } - selectStateOnPincode(stateName: string) { - this.getStateElement() - .scrollIntoView() - .wait(2000) - .should("be.visible") - .then(($element) => { - const text = $element.text(); - if (!text.includes(stateName)) { - this.getStateElement().click(); - cy.get("li[role=option]").contains(stateName).click(); - } - }); + advanceFilters.selectState(stateName); } selectDistrictOnPincode(districtName: string) { - this.getDistrictElement().as("district").scrollIntoView().wait(2000); - cy.get("@district") - .should("be.visible") - .then(($element) => { - const text = $element.text(); - if (!text.includes(districtName)) { - this.getDistrictElement().click(); - cy.get("li[role=option]").contains(districtName).click(); - } - }); + advanceFilters.selectDistrict(districtName); } verifyPpeQuantity(text: string) { @@ -317,10 +305,20 @@ class FacilityPage { cy.get(badgeClass).contains(text).should("exist"); } - clickAddMinimumQuanitity() { + interceptMinimumQuantity() { + cy.intercept("GET", "**/api/v1/facility/*/min_quantity/**").as( + "getMinQuantity", + ); + } + + clickAddMinimumQuantity() { cy.get("#add-minimum-quantity").click(); } + verifyMinimumQuantity() { + cy.wait("@getMinQuantity").its("response.statusCode").should("eq", 200); + } + clickUpdateMinimumQuantity() { cy.get("#update-minimum-quantity").first().click(); } diff --git a/cypress/pageobject/Hcx/HcxClaims.ts b/cypress/pageobject/Hcx/HcxClaims.ts index b93862e6d13..883303d1af6 100644 --- a/cypress/pageobject/Hcx/HcxClaims.ts +++ b/cypress/pageobject/Hcx/HcxClaims.ts @@ -3,7 +3,7 @@ export class HcxClaims { cy.get("#select-insurance-policy", { timeout: 10000 }) .should("be.visible") .and("not.be.disabled"); - cy.clickAndSelectOption("#select-insurance-policy", policy); + cy.clickAndSelectOption("#select-insurance-policy", policy, true); } verifyPolicyEligibility() { diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index edd8ae135a4..25f02892cdf 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -1,6 +1,5 @@ export class PatientConsultationPage { selectConsultationStatus(status: string) { - cy.wait(5000); cy.get("#route_to_facility").scrollIntoView(); cy.get("#route_to_facility").should("be.visible"); cy.clickAndSelectOption("#route_to_facility", status); @@ -66,7 +65,7 @@ export class PatientConsultationPage { } clickPatientDetails() { - cy.verifyAndClickElement("#consultationpage-header", "Patient Details"); + cy.verifyAndClickElement("#patient-details", "Patient Details"); } typePatientIllnessHistory(history: string) { @@ -104,13 +103,15 @@ export class PatientConsultationPage { } clickEditConsultationButton() { - cy.get("#consultation-buttons").scrollIntoView(); - cy.get("button").contains("Manage Patient").click(); - cy.verifyAndClickElement( - "#consultation-buttons", - "Edit Consultation Details", - ); - cy.wait(3000); + cy.clickAndSelectOption("#show-more", "Edit Consultation Details", true); + } + + interceptConsultation() { + cy.intercept("GET", "**/api/v1/consultation/*").as("getConsultation"); + } + + verifyConsultation() { + cy.wait("@getConsultation").its("response.statusCode").should("eq", 200); } interceptPatientDetailsAPI(): void { diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index 635aac3c4d2..d2e66c3d396 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -3,7 +3,6 @@ import FacilityPage from "pageobject/Facility/FacilityCreation"; import PatientMedicalHistory from "./PatientMedicalHistory"; -let patient_url = ""; const facilityPage = new FacilityPage(); const patientMedicalHistory = new PatientMedicalHistory(); @@ -45,10 +44,9 @@ export class PatientPage { cy.get("#patient-search").click().type(patientName); // Type the patient name cy.intercept("GET", "**/api/v1/consultation/**").as("getPatient"); cy.get("#patient-name-list").contains(patientName).click(); - cy.wait(2000); cy.wait("@getPatient").its("response.statusCode").should("eq", 200); - cy.get("#patient-name-consultation") - .should("be.visible") + cy.get("#patient-name-consultation", { timeout: 15000 }) + .should("not.have.class", "hidden") .contains(patientName); } @@ -97,7 +95,7 @@ export class PatientPage { } typePatientAge(age: string) { - cy.clickAndSelectOption("#patientAge", "Age"); + cy.clickAndSelectOption("#patientAge", "Age", true); cy.clickSubmitButton("Confirm"); cy.get("#age").clear().type(age); } @@ -140,7 +138,9 @@ export class PatientPage { } clickCancelButton() { + cy.intercept("GET", "**/api/v1/patient/*/").as("getPatient"); cy.get("#cancel").click(); + cy.wait("@getPatient"); } selectPatientGender(gender: string) { @@ -174,26 +174,20 @@ export class PatientPage { cy.url().should("include", "/patient"); } - savePatientUrl() { - cy.url().then((url) => { - patient_url = url; - }); - } - - visitPatientUrl() { - cy.visit(patient_url); - } - - visitConsultationPage() { - cy.visit(patient_url + "/consultation"); - } - clickUpdatePatient() { cy.intercept("PUT", "**/api/v1/patient/**").as("updatePatient"); cy.get("button").get("[data-testid=submit-button]").click(); cy.wait("@updatePatient").its("response.statusCode").should("eq", 200); } + interceptGetPatient() { + cy.intercept("GET", "**/api/v1/patient/*").as("getPatient"); + } + + verifyGetPatientResponse() { + cy.wait("@getPatient").its("response.statusCode").should("eq", 200); + } + clickCreateConsultationOnPatientPageWithNoConsultation() { cy.get("#create-consultation").should("be.visible").click(); } @@ -209,7 +203,6 @@ export class PatientPage { verifyPatientDashboardDetails( gender: string, age: number, - patientName: string, phoneNumber: string, emergencyPhoneNumber: string, yearOfBirth: string, @@ -221,26 +214,28 @@ export class PatientPage { isPostPartum = false, ) { cy.url().should("include", "/facility/"); - cy.get("[data-testid=patient-dashboard]").then(($dashboard) => { - expect($dashboard).to.contain(gender); - expect($dashboard).to.contain(age); - expect($dashboard).to.contain(patientName); - expect($dashboard).to.contain(phoneNumber); - expect($dashboard).to.contain(emergencyPhoneNumber); - //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); - domesticHealthcareSupport && - expect($dashboard).to.contain(domesticHealthcareSupport); - - if (isAntenatal) { - expect($dashboard).to.contain("Antenatal"); - } - if (isPostPartum) { - expect($dashboard).to.contain("Post-partum"); - } - }); + cy.get("[data-testid=patient-dashboard]") + .should("be.visible") + .then(($dashboard) => { + expect($dashboard).to.contain(gender); + expect($dashboard).to.contain(age); + expect($dashboard).to.contain(phoneNumber); + expect($dashboard).to.contain(emergencyPhoneNumber); + expect($dashboard).to.contain(yearOfBirth); + expect($dashboard).to.contain(bloodGroup); + expect($dashboard).to.contain(occupation); + socioeconomicStatus && + expect($dashboard).to.contain(socioeconomicStatus); + domesticHealthcareSupport && + expect($dashboard).to.contain(domesticHealthcareSupport); + + if (isAntenatal) { + expect($dashboard).to.contain("Antenatal"); + } + if (isPostPartum) { + expect($dashboard).to.contain("Post-partum"); + } + }); } verifyPatientLocationDetails( @@ -262,10 +257,6 @@ export class PatientPage { }); } - visitUpdatePatientUrl() { - cy.visit(patient_url + "/update"); - } - clickPatientUpdateDetails() { cy.verifyAndClickElement("#update-patient-details", "Edit Profile"); } diff --git a/cypress/pageobject/Patient/PatientDischarge.ts b/cypress/pageobject/Patient/PatientDischarge.ts index 70a6d550887..58805255088 100644 --- a/cypress/pageobject/Patient/PatientDischarge.ts +++ b/cypress/pageobject/Patient/PatientDischarge.ts @@ -6,7 +6,19 @@ class PatientDischarge { } selectDischargeReason(reason: string) { - cy.clickAndSelectOption("#discharge_reason", reason); + if (reason == "Recovered") { + cy.intercept("GET", "**/api/v1/consultation/*/prescriptions/*").as( + "getPrescriptions", + ); + cy.clickAndSelectOption("#discharge_reason", reason); + cy.wait("@getPrescriptions").its("response.statusCode").should("eq", 200); + } else if (reason == "Referred") { + cy.intercept("GET", "**/api/v1/getallfacilities/**").as("getFacilities"); + cy.clickAndSelectOption("#discharge_reason", reason); + cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); + } else { + cy.clickAndSelectOption("#discharge_reason", reason); + } } typeDischargeNote(note: string) { @@ -24,6 +36,16 @@ class PatientDischarge { typeDoctorName(doctorName: string) { cy.get("#death_confirmed_by").type(doctorName); } + + interceptDischargePatient() { + cy.intercept("POST", "**/api/v1/consultation/*/discharge_patient/").as( + "postDischarge", + ); + } + + verifyDischargePatient() { + cy.wait("@postDischarge").its("response.statusCode").should("eq", 200); + } } export default PatientDischarge; diff --git a/cypress/pageobject/Patient/PatientDoctorNotes.ts b/cypress/pageobject/Patient/PatientDoctorNotes.ts index 157f35d47d9..f1ac6b87bc5 100644 --- a/cypress/pageobject/Patient/PatientDoctorNotes.ts +++ b/cypress/pageobject/Patient/PatientDoctorNotes.ts @@ -5,14 +5,15 @@ export class PatientDoctorNotes { } addDiscussionNotes(notes: string) { - cy.wait(2000); cy.get("#discussion_notes_textarea").scrollIntoView(); cy.get("#discussion_notes_textarea").click().type(notes); } selectNurseDiscussion() { cy.get("#patient-note-tab-Nurses").scrollIntoView(); + cy.intercept("GET", "/api/v1/patient/*/notes/*").as("getPatientNotes"); cy.get("#patient-note-tab-Nurses").click(); + cy.wait("@getPatientNotes").its("response.statusCode").should("eq", 200); } verifyDiscussionMessage(text: string) { diff --git a/cypress/pageobject/Patient/PatientFileupload.ts b/cypress/pageobject/Patient/PatientFileupload.ts index c70170a744d..140d5ca993b 100644 --- a/cypress/pageobject/Patient/PatientFileupload.ts +++ b/cypress/pageobject/Patient/PatientFileupload.ts @@ -23,9 +23,9 @@ export class PatientFileUpload { cy.wait(2000); cy.get("#start-recording").click(); cy.wait(2000); - cy.get("#stop-recording").click(); - cy.wait(1000); - cy.get("#save-recording").click(); + cy.get("#stop-recording").should("be.enabled").click(); + cy.wait(2000); + cy.get("#save-recording").should("be.enabled").click(); } clickUploadAudioFile() { diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts index 45c1924e1a3..bcaf6695d96 100644 --- a/cypress/pageobject/Patient/PatientLogupdate.ts +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -2,7 +2,14 @@ class PatientLogupdate { clickLogupdate() { cy.get("#log-update").scrollIntoView(); cy.verifyAndClickElement("#log-update", "Log Update"); - cy.wait(2000); + } + + interceptConsultationBed() { + cy.intercept("GET", "**/api/v1/consultationbed/*").as("getBed"); + } + + verifyConsultationBed() { + cy.wait("@getBed").its("response.statusCode").should("eq", 200); } clickSwitchBed() { @@ -13,23 +20,44 @@ class PatientLogupdate { cy.clickAndSelectOption("#rounds_type", roundType); } + verifyRoundType(roundType: string) { + cy.get("#rounds_type", { timeout: 10000 }) + .should("be.visible") + .should("contain.text", roundType); + } + selectBed(bed: string) { cy.typeAndSelectOption("input[name='bed']", bed); + cy.intercept("POST", "**/api/v1/consultationbed/").as( + "postConsultationBed", + ); cy.get("#update-switchbed").click(); - cy.wait(2000); + cy.wait("@postConsultationBed") + .its("response.statusCode") + .should("eq", 201); } selectPatientCategory(category: string) { cy.clickAndSelectOption("#patientCategory", category); } - typePhysicalExamination(examination: string) { - cy.get("#physical_examination_info").click().type(examination); - cy.get("#physical_examination_info").should("contain", examination); + verifyPatientCategory(category: string) { + cy.get("#patientCategory", { timeout: 10000 }) + .should("be.visible") + .should("contain.text", category); + } + + typePhysicalExamination( + examination: string, + clearBeforeTyping: boolean = false, + ) { + cy.typeIntoField("#physical_examination_info", examination, { + clearBeforeTyping, + }); } - typeOtherDetails(details: string) { - cy.get("#other_details").click().type(details); + typeOtherDetails(details: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#other_details", details, { clearBeforeTyping }); } typeAndMultiSelectSymptoms(input: string, symptoms: string[]) { @@ -42,59 +70,78 @@ class PatientLogupdate { cy.get("#add-symptom").click(); } - typeSystolic(systolic: string) { - cy.get("#systolic").click().type(systolic); + typeSystolic(systolic: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#systolic", systolic, { clearBeforeTyping }); } - typeDiastolic(diastolic: string) { - cy.get("#diastolic").click().type(diastolic); + typeDiastolic(diastolic: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#diastolic", diastolic, { clearBeforeTyping }); } - typePulse(pulse: string) { - cy.get("#pulse").click().type(pulse); + typePulse(pulse: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#pulse", pulse, { clearBeforeTyping }); } - typeTemperature(temperature: string) { - cy.get("#temperature").click().type(temperature); + typeTemperature(temperature: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#temperature", temperature, { clearBeforeTyping }); } - typeRespiratory(respiratory: string) { - cy.get("#resp").click().type(respiratory); + typeRespiratory(respiratory: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#resp", respiratory, { clearBeforeTyping }); } - typeSpo2(spo: string) { - cy.get("#ventilator_spo2").click().type(spo); + typeSpo2(spo: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#ventilator_spo2", spo, { clearBeforeTyping }); } selectRhythm(rhythm: string) { cy.clickAndSelectOption("#rhythm", rhythm); } - typeRhythm(rhythm: string) { - cy.get("#rhythm_detail").click().type(rhythm); + typeRhythm(rhythm: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#rhythm_detail", rhythm, { clearBeforeTyping }); + } + + interceptDailyRounds() { + cy.intercept("GET", "**/api/v1/consultation/*/daily_rounds/*/").as( + "getDailyRounds", + ); + } + + verifyDailyRounds() { + cy.wait("@getDailyRounds").its("response.statusCode").should("eq", 200); + } + + interceptpatchDailyRounds() { + cy.intercept("PATCH", "**/api/v1/consultation/*/daily_rounds/*/").as( + "patchDailyRounds", + ); + } + + verifypatchDailyRounds() { + cy.wait("@patchDailyRounds").its("response.statusCode").should("eq", 200); } clickLogUpdateViewDetails(element: string, patientCategory: string) { cy.get(element).scrollIntoView(); cy.verifyContentPresence(element, [patientCategory]); + this.interceptDailyRounds(); cy.get(element).first().contains("View Details").click(); - cy.wait(3000); + this.verifyDailyRounds(); } clickLogUpdateUpdateLog(element: string, patientCategory: string) { cy.get(element).scrollIntoView(); cy.verifyContentPresence(element, [patientCategory]); + this.interceptDailyRounds(); cy.get(element).first().contains("Update Log").click(); - cy.wait(3000); + this.verifyDailyRounds(); } clickUpdateDetail() { + this.interceptDailyRounds(); cy.verifyAndClickElement("#consultation-preview", "Update Log"); - cy.wait(3000); - } - - clearIntoElementById(elementId) { - cy.get(elementId).click().clear(); + this.verifyDailyRounds(); } clickVitals() { @@ -106,8 +153,8 @@ class PatientLogupdate { cy.get("#bilateral_air_entry-option-false").click(); } - typeEtco2(etco2: string) { - cy.get("#etco2-range-input").type(etco2); + typeEtco2(etco2: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#etco2-range-input", etco2, { clearBeforeTyping }); } selectOxygenSupport() { @@ -118,36 +165,48 @@ class PatientLogupdate { cy.get("#ventilator_oxygen_modality-option-NON_REBREATHING_MASK").click(); } - typeOxygenFlowRate(flowRate: string) { - cy.get("#oxygen_flow_rate-range-input").type(flowRate); + typeOxygenFlowRate(flowRate: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#oxygen_flow_rate-range-input", flowRate, { + clearBeforeTyping, + }); } - typeVentilatorSpo2(spo2: string) { - cy.get("#ventilator_spo2-range-input").type(spo2); + typeVentilatorSpo2(spo2: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#ventilator_spo2-range-input", spo2, { + clearBeforeTyping, + }); } selectCriticalCareSection(sectionName: string) { cy.contains("button", sectionName).click(); } - typeBloodSugar(bloodSugar: string) { - cy.get("#blood_sugar_level-range-input").type(bloodSugar); + typeBloodSugar(bloodSugar: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#blood_sugar_level-range-input", bloodSugar, { + clearBeforeTyping, + }); } - typeInsulinDosage(insulinDosage: string) { - cy.get("#insulin_intake_dose-range-input").type(insulinDosage); + typeInsulinDosage(insulinDosage: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#insulin_intake_dose-range-input", insulinDosage, { + clearBeforeTyping, + }); } clickGoBackConsultation() { cy.get("#back-to-consultation").click(); } - typeFluidBalance(fluid: string) { - cy.get("#dialysis_fluid_balance-range-input").type(fluid); + typeFluidBalance(fluid: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#dialysis_fluid_balance-range-input", fluid, { + clearBeforeTyping, + }); } - typeNetBalance(netBalance: string) { - cy.get("#dialysis_net_balance-range-input").type(netBalance); + typeNetBalance(netBalance: string, clearBeforeTyping: boolean = false) { + cy.typeIntoField("#dialysis_net_balance-range-input", netBalance, { + clearBeforeTyping, + }); } } export default PatientLogupdate; diff --git a/cypress/pageobject/Patient/PatientMedicalHistory.ts b/cypress/pageobject/Patient/PatientMedicalHistory.ts index bf2296b4471..94c06790fbf 100644 --- a/cypress/pageobject/Patient/PatientMedicalHistory.ts +++ b/cypress/pageobject/Patient/PatientMedicalHistory.ts @@ -34,7 +34,7 @@ class PatientMedicalHistory { patientSymptoms7: string, ) { cy.get("a").contains("Health Profile").click(); - cy.wait(2000); + cy.url().should("include", "/health-profile"); cy.get("[data-test-id=patient-health-profile]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(patientPresentHealth); diff --git a/cypress/pageobject/Patient/PatientPrescription.ts b/cypress/pageobject/Patient/PatientPrescription.ts index d801b360aba..89b14efc7b7 100644 --- a/cypress/pageobject/Patient/PatientPrescription.ts +++ b/cypress/pageobject/Patient/PatientPrescription.ts @@ -61,8 +61,7 @@ export class PatientPrescription { } enterDiscontinueReason(reason: string) { - cy.wait(2000); - cy.get("#discontinuedReason").type(reason); + cy.get("#discontinuedReason").should("be.visible").type(reason); } enterAdministerDosage(dosage: string) { @@ -81,6 +80,16 @@ export class PatientPrescription { cy.clickAndSelectOption("#frequency", frequency); } + interceptPrescriptions() { + cy.intercept("GET", "**/api/v1/consultation/*/prescriptions/*").as( + "getPrescriptions", + ); + } + + verifyPrescription() { + cy.wait("@getPrescriptions").its("response.statusCode").should("eq", 200); + } + clickReturnToDashboard() { cy.verifyAndClickElement( "[data-testid='return-to-patient-dashboard']", diff --git a/cypress/pageobject/Patient/PatientTransfer.ts b/cypress/pageobject/Patient/PatientTransfer.ts index 0bdd55e9880..5ad49b40b5f 100644 --- a/cypress/pageobject/Patient/PatientTransfer.ts +++ b/cypress/pageobject/Patient/PatientTransfer.ts @@ -19,13 +19,11 @@ class PatientTransfer { clickTransferSubmitButton() { cy.get("#submit-transferpatient").click(); - cy.wait(2000); } clickConsultationCancelButton() { cy.get("#cancel").scrollIntoView(); cy.get("#cancel").click(); - cy.wait(2000); } clickAllowPatientTransferButton() { diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index 24c056a70eb..a4821466861 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -8,7 +8,9 @@ export class ManageUserPage { } selectSkillFromDropdown(skill: string) { + cy.intercept("GET", "/api/v1/skill/*").as("getSkills"); cy.typeAndSelectOption("input[name='skill']", skill); + cy.wait("@getSkills").its("response.statusCode").should("eq", 200); } assertLinkedFacility(facilityName: string) { @@ -52,22 +54,6 @@ export class ManageUserPage { cy.get("#link-facility").click(); } - clickSubmit() { - cy.get("#submit").click(); - } - - verifyErrorText(expectedError: string) { - cy.get(".error-text").first().scrollIntoView(); - cy.get(".error-text") - .should("be.visible") - .then(($elements) => { - const errorTextArray = Array.from($elements).map( - (el) => el.textContent, - ); - expect(errorTextArray).to.include(expectedError); - }); - } - clearUserBasicInfo() { cy.get("input[name='first_name']").click().clear(); cy.get("input[name='last_name']").click().clear(); @@ -79,11 +65,35 @@ export class ManageUserPage { dateOfBirth: string, gender: string, ) { - cy.get("input[name='first_name']").click().type(fName); - cy.get("input[name='last_name']").click().type(lName); + this.editFirstName(fName); + this.editLastName(lName); + this.editDateOfBirth(dateOfBirth); + this.editGender(gender); + } + + clickUserInfoSubmitButton() { + cy.clickSubmitButton("Submit"); + } + + userInfoUpdateSuccessNotification() { + cy.verifyNotification("User details updated successfully"); + cy.closeNotification(); + } + + editFirstName(fName: string, clearBeforeTyping = true) { + cy.typeIntoField("#first_name", fName, { clearBeforeTyping }); + } + + editLastName(lName: string, clearBeforeTyping = true) { + cy.typeIntoField("#last_name", lName, { clearBeforeTyping }); + } + + editDateOfBirth(dateOfBirth: string) { cy.clickAndTypeDate("#date_of_birth", dateOfBirth); - cy.get("#gender").click(); - cy.get("[role='option']").contains(gender).click(); + } + + editGender(gender: string) { + cy.clickAndSelectOption("#gender", gender); } verifyEditUserDetails( @@ -92,10 +102,10 @@ export class ManageUserPage { dateOfBirth: string, gender: string, ) { - cy.get("#view-first_name").should("contain.text", fName); - cy.get("#view-last_name").should("contain.text", lName); - cy.get("#view-date_of_birth").should("contain.text", dateOfBirth); - cy.get("#view-gender").should("contain.text", gender); + cy.verifyContentPresence("#view-first_name", [fName]); + cy.verifyContentPresence("#view-last_name", [lName]); + cy.verifyContentPresence("#view-date_of_birth", [dateOfBirth]); + cy.verifyContentPresence("#view-gender", [gender]); } clearUserContactInfo() { @@ -105,15 +115,30 @@ export class ManageUserPage { } editUserContactInfo(email: string, phoneNumber: string) { - cy.get("input[name='email']").click().type(email); - cy.get("input[name='phone_number']").click().type(phoneNumber); + this.editEmail(email); + this.editPhoneNumber(phoneNumber); + } + + editEmail(email: string, clearBeforeTyping = true) { + cy.typeIntoField("input[name='email']", email, { clearBeforeTyping }); + } + + editPhoneNumber( + phoneNumber: string, + clearBeforeTyping = true, + skipVerification = true, + ) { + cy.typeIntoField("input[name='phone_number']", phoneNumber, { + clearBeforeTyping, + skipVerification, + }); cy.get("input[name='phone_number_is_whatsapp']").should("be.checked"); } verifyEditUserContactInfo(email: string, phoneNumber: string) { - cy.get("#view-email").should("contain.text", email); - cy.get("#view-phone_number").should("contain.text", phoneNumber); - cy.get("#view-whatsapp_number").should("contain.text", phoneNumber); + cy.verifyContentPresence("#view-email", [email]); + cy.verifyContentPresence("#view-phone_number", [phoneNumber]); + cy.verifyContentPresence("#view-whatsapp_number", [phoneNumber]); } clearDoctorOrNurseProfessionalInfo(yoeAndCouncilRegistration: boolean) { @@ -126,82 +151,119 @@ export class ManageUserPage { } } + editQualification(qualification: string, clearBeforeTyping = true) { + cy.typeIntoField("input[name='qualification']", qualification, { + clearBeforeTyping, + }); + } + + editDoctorYoE(doctorYoE: string, clearBeforeTyping = true) { + cy.typeIntoField( + "input[name='doctor_experience_commenced_on']", + doctorYoE, + { + clearBeforeTyping, + }, + ); + } + + editMedicalCouncilRegistration( + medicalCouncilRegistration: string, + clearBeforeTyping = true, + ) { + cy.typeIntoField( + "input[name='doctor_medical_council_registration']", + medicalCouncilRegistration, + { + clearBeforeTyping, + }, + ); + } + clearProfessionalInfo() { cy.get("input[name='weekly_working_hours']").scrollIntoView(); cy.get("input[name='weekly_working_hours']").click().clear(); cy.get("input[name='video_connect_link']").click().clear(); } + editWeeklyWorkingHours(weeklyWorkingHours: string, clearBeforeTyping = true) { + cy.get("input[name='weekly_working_hours']").scrollIntoView(); + cy.typeIntoField("input[name='weekly_working_hours']", weeklyWorkingHours, { + clearBeforeTyping, + }); + } + + editVideoConnectLink(videoConnectLink: string, clearBeforeTyping = true) { + cy.typeIntoField("input[name='video_connect_link']", videoConnectLink, { + clearBeforeTyping, + }); + } + editUserProfessionalInfo( qualification: string, yearsOfExperience?: string, medicalCouncilRegistration?: string, ) { - cy.get("input[name='qualification']").click().type(qualification); + this.editQualification(qualification); if (yearsOfExperience) { - cy.get("input[name='doctor_experience_commenced_on']") - .click() - .type(yearsOfExperience); + this.editDoctorYoE(yearsOfExperience); } if (medicalCouncilRegistration) { - cy.get("input[name='doctor_medical_council_registration']") - .click() - .type(medicalCouncilRegistration); + this.editMedicalCouncilRegistration(medicalCouncilRegistration); } } + editHoursAndVideoConnectLink( + weeklyWorkingHours: string, + videoConnectLink: string, + ) { + this.editWeeklyWorkingHours(weeklyWorkingHours); + this.editVideoConnectLink(videoConnectLink); + } + verifyEditUserProfessionalInfo( qualification: string, yearsOfExperience?: string, medicalCouncilRegistration?: string, ) { - cy.get("#view-qualification").should("contain.text", qualification); + cy.verifyContentPresence("#view-qualification", [qualification]); if (yearsOfExperience) { - cy.get("#view-years_of_experience").should( - "contain.text", + cy.verifyContentPresence("#view-years_of_experience", [ yearsOfExperience, - ); + ]); } if (medicalCouncilRegistration) { - cy.get("#view-doctor_medical_council_registration").should( - "contain.text", + cy.verifyContentPresence("#view-doctor_medical_council_registration", [ medicalCouncilRegistration, - ); + ]); } } + verifyHoursAndVideoConnectLink( + weeklyWorkingHours: string, + videoConnectLink: string, + ) { + cy.get("#view-average_weekly_working_hours").scrollIntoView(); + cy.verifyContentPresence("#view-average_weekly_working_hours", [ + weeklyWorkingHours, + ]); + cy.verifyContentPresence("#view-video_conference_link", [videoConnectLink]); + } + verifyPasswordEditButtonNotExist() { cy.get("#change-edit-password-button").should("not.exist"); } changePassword(oldPassword: string, newPassword: string) { - cy.get("input[name='old_password']").click().type(oldPassword); - cy.get("input[name='new_password_1']").click().type(newPassword); - cy.get("input[name='new_password_2']").click().type(newPassword); - } - - typeInWeeklyWorkingHours(hours: string) { - cy.get("input[name='weekly_working_hours']").scrollIntoView(); - cy.get("input[name='weekly_working_hours']").click().type(hours); - } - - navigateToProfile() { - cy.intercept("GET", "**/api/v1/users/**").as("getUsers"); - cy.get("#user-profile-name").click(); - cy.get("#profile-button").click(); - cy.wait("@getUsers").its("response.statusCode").should("eq", 200); - } - - verifyWorkingHours(expectedHours: string) { - cy.verifyContentPresence("#view-average_weekly_working_hours", [ - expectedHours, - ] as string[]); - } - - verifyProfileWorkingHours(expectedHours: string) { - cy.verifyContentPresence("#averageworkinghour-profile-details", [ - expectedHours, - ] as string[]); + cy.typeIntoField("input[name='old_password']", oldPassword, { + clearBeforeTyping: true, + }); + cy.typeIntoField("input[name='new_password_1']", newPassword, { + clearBeforeTyping: true, + }); + cy.typeIntoField("input[name='new_password_2']", newPassword, { + clearBeforeTyping: true, + }); } navigateToManageUser() { @@ -213,6 +275,14 @@ export class ManageUserPage { cy.get("#facility-patients").click(); } + interceptLinkedSkillTab() { + cy.intercept("GET", "**/api/v1/users/*/skill").as("getUserSkill"); + } + + verifyLinkedSkillResponse() { + cy.wait("@getUserSkill").its("response.statusCode").should("eq", 200); + } + clickLinkedSkillTab() { cy.get("#skills").click(); } @@ -227,71 +297,45 @@ export class ManageUserPage { cy.wait("@getUserDetails"); } - verifyMoreDetailsPage(hasPermissions = true) { - cy.get("#username").should("be.visible"); - cy.get("#role").should("be.visible"); - cy.get("#usermanagement_tab_nav").should("be.visible"); - cy.get("#profile").should("be.visible"); - if (hasPermissions) { - cy.get("#facilities").should("be.visible"); - cy.get("#skills").should("be.visible"); - } - cy.get("#view-username").scrollIntoView(); - cy.get("#view-username").should("be.visible"); - } - - verifyChangeAvatarButtonVisible() { - cy.get("#change-avatar").should("be.visible"); - } - - clickChangeAvatarButton() { - cy.get("#change-avatar").click(); + clickBasicInfoEditButton() { + cy.verifyAndClickElement("#basic-info-edit-button", "Edit"); } - clickBasicInfoViewButton() { - cy.get("#basic-info-view-button").scrollIntoView(); - cy.get("#basic-info-view-button").should("be.visible"); - cy.get("#basic-info-view-button").click(); + clickBaicInfoViewButton() { + cy.verifyAndClickElement("#basic-info-view-button", "View"); } - clickBasicInfoEditButton() { - cy.get("#basic-info-edit-button").scrollIntoView(); - cy.get("#basic-info-edit-button").should("be.visible"); - cy.get("#basic-info-edit-button").click(); + clickContactInfoEditButton() { + cy.verifyAndClickElement("#contact-info-edit-button", "Edit"); } clickContactInfoViewButton() { - cy.get("#contact-info-view-button").scrollIntoView(); - cy.get("#contact-info-view-button").should("be.visible"); - cy.get("#contact-info-view-button").click(); - } - - clickContactInfoEditButton() { - cy.get("#contact-info-edit-button").scrollIntoView(); - cy.get("#contact-info-edit-button").should("be.visible"); - cy.get("#contact-info-edit-button").click(); + cy.verifyAndClickElement("#contact-info-view-button", "View"); } clickProfessionalInfoViewButton() { - cy.get("#professional-info-view-button").scrollIntoView(); - cy.get("#professional-info-view-button").should("be.visible"); - cy.get("#professional-info-view-button").click(); + cy.verifyAndClickElement("#professional-info-view-button", "View"); } clickProfessionalInfoEditButton() { - cy.get("#professional-info-edit-button").scrollIntoView(); - cy.get("#professional-info-edit-button").should("be.visible"); - cy.get("#professional-info-edit-button").click(); + cy.verifyAndClickElement("#professional-info-edit-button", "Edit"); } - clickPasswordEditButton() { - cy.get("#change-edit-password-button").scrollIntoView(); - cy.get("#change-edit-password-button").should("be.visible"); - cy.get("#change-edit-password-button").click(); + verifyMoreDetailsPage(hasPermissions = true) { + cy.get("#username").should("be.visible"); + cy.get("#role").should("be.visible"); + cy.get("#usermanagement_tab_nav").should("be.visible"); + cy.get("#profile").should("be.visible"); + if (hasPermissions) { + cy.get("#facilities").should("be.visible"); + cy.get("#skills").should("be.visible"); + } + cy.get("#view-username").scrollIntoView(); + cy.get("#view-username").should("be.visible"); } verifyQualificationDoesntExist() { - cy.get("input[name='qualification']").should("not.exist"); + cy.get("#view-qualification").should("not.exist"); } verifyQualificationExist() { @@ -308,10 +352,6 @@ export class ManageUserPage { cy.get("#view-doctor_medical_council_registration").should("be.visible"); } - verifyUsername(username: string) { - cy.get("#view-username").should("contain", username); - } - verifyBasicInfoEditButtonNotExist() { cy.get("#basic-info-edit-button").should("not.exist"); } @@ -328,14 +368,6 @@ export class ManageUserPage { cy.get("#user-edit-form").should("be.visible"); } - verifyDoctorQualification() { - cy.get("#view-qualification").should("be.visible"); - } - - verifyDoctorQualificationDoesNotExist() { - cy.get("#view-qualification").should("not.exist"); - } - verifyLinkedSkillsTabPage() { cy.get("#select-skill").scrollIntoView(); cy.get("#select-skill").should("be.visible"); @@ -361,13 +393,14 @@ export class ManageUserPage { clickAddSkillButton(username: string) { cy.intercept("GET", `**/api/v1/users/${username}/skill/**`).as("getSkills"); cy.get("#add-skill-button").click(); - cy.wait("@getSkills").its("response.statusCode").should("eq", 200); } - assertSkillInAlreadyLinkedSkills(skillName: string) { - cy.get("#already-linked-skills") - .contains(skillName) - .should("have.length", 1); + interceptAddSkill() { + cy.intercept("GET", "**/api/v1/users/*/skill").as("getUserSkills"); + } + + verifyAddSkillResponse() { + cy.wait("@getUserSkills").its("response.statusCode").should("eq", 200); } assertSkillIndoctorconnect(skillName: string) { @@ -393,10 +426,6 @@ export class ManageUserPage { cy.get("#added-user-skills").should("contain", skillName); } - assertSkillNotInAddedUserSkills(skillName: string) { - cy.get("#added-user-skills").should("not.contain", skillName); - } - assertDoctorConnectVisibility(realName: string) { cy.get('*[id="doctor-connect-home-doctor"]').should( "contain.text", @@ -407,18 +436,6 @@ export class ManageUserPage { realName, ); } - - assertVideoConnectLink(docName: string, link: string) { - cy.get("ul#options") - .find("li") - .contains(docName) - .within(() => { - cy.get("a").should(($a) => { - const hrefs = $a.map((i, el) => Cypress.$(el).attr("href")).get(); - expect(hrefs).to.include(link); - }); - }); - } } export default ManageUserPage; diff --git a/cypress/pageobject/Users/UserCreation.ts b/cypress/pageobject/Users/UserCreation.ts index 26eaa088e23..527cd82fa1e 100644 --- a/cypress/pageobject/Users/UserCreation.ts +++ b/cypress/pageobject/Users/UserCreation.ts @@ -48,4 +48,12 @@ export class UserCreationPage { clickSaveUserButton() { cy.clickSubmitButton("Submit"); } + + interceptCreateUser() { + cy.intercept("POST", "/api/v1/users/add_user/").as("createUser"); + } + + verifyCreateUser() { + cy.wait("@createUser").its("response.statusCode").should("eq", 201); + } } diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts deleted file mode 100644 index 882be0b7b9b..00000000000 --- a/cypress/pageobject/Users/UserProfilePage.ts +++ /dev/null @@ -1,97 +0,0 @@ -export default class UserProfilePage { - assertVideoConnectLink(link: string) { - cy.get("#videoconnectlink-profile-details").should("contain.text", link); - } - - clickEditProfileButton() { - cy.get("#edit-cancel-profile-button").click(); - } - - typeVideoConnectLink(link: string) { - cy.get("#video_connect_link").click().clear().type(link); - } - - clickUpdateButton() { - cy.clickSubmitButton("Update"); - } - - 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) { - cy.get("#gender").click(); - cy.get("#gender-option-" + gender).click(); - } - - typeEmail(email: string) { - cy.get("#email").click().clear().type(email); - } - - typePhoneNumber(phone: string) { - cy.get("#phoneNumber").click().clear().type(phone); - } - - typeWhatsappNumber(phone: string) { - cy.get("#altPhoneNumber").click().clear().type(phone); - } - - typeWorkingHours(workingHours: string) { - cy.get("#weekly_working_hours").click().clear().type(workingHours); - } - - typeQualification = (qualification: string) => { - cy.get("#qualification").click().clear().type(qualification); - }; - - typeDoctorYoE = (doctorYoE: string) => { - cy.get("#doctor_experience_commenced_on").click().clear().type(doctorYoE); - }; - - typeMedicalCouncilRegistration = (medicalCouncilRegistration: string) => { - cy.get("#doctor_medical_council_registration") - .click() - .clear() - .type(medicalCouncilRegistration); - }; - - assertDateOfBirth(dob: string) { - cy.get("#date_of_birth-profile-details").should("contain.text", dob); - } - - assertGender(gender: string) { - cy.get("#gender-profile-details").should("contain.text", gender); - } - - assertEmail(email: string) { - cy.get("#emailid-profile-details").should("contain.text", email); - } - - assertPhoneNumber(phone: string) { - cy.get("#contactno-profile-details").should("contain.text", phone); - } - - assertAltPhoneNumber(phone: string) { - cy.get("#whatsapp-profile-details").should("contain.text", phone); - } - - assertWorkingHours(workingHours: string) { - cy.get("#averageworkinghour-profile-details").should( - "contain.text", - workingHours, - ); - } -} diff --git a/cypress/pageobject/utils/advanceFilterHelpers.ts b/cypress/pageobject/utils/advanceFilterHelpers.ts index 22925fc2c23..14e3ab41bda 100644 --- a/cypress/pageobject/utils/advanceFilterHelpers.ts +++ b/cypress/pageobject/utils/advanceFilterHelpers.ts @@ -4,17 +4,25 @@ export const advanceFilters = { }, selectState(state: string) { + cy.wait(1000); cy.clickAndSelectOption("#state", state); }, selectDistrict(district: string) { + cy.wait(1000); cy.clickAndSelectOption("#district", district); }, selectLocalBody(localBody: string) { + cy.wait(1000); cy.clickAndSelectOption("#local_body", localBody); }, + selectWard(ward: string) { + cy.wait(1000); + cy.clickAndSelectOption("#ward", ward); + }, + applySelectedFilter() { cy.verifyAndClickElement("#apply-filter", "Apply"); }, diff --git a/cypress/pageobject/utils/constants.ts b/cypress/pageobject/utils/constants.ts index 90baf3b6d4b..e14e3720231 100644 --- a/cypress/pageobject/utils/constants.ts +++ b/cypress/pageobject/utils/constants.ts @@ -1,7 +1,17 @@ -function generatePhoneNumber(): string { +function unbiasedRandom(max: number): number { const array = new Uint32Array(1); - window.crypto.getRandomValues(array); - const randomNum = (array[0] % 900000000) + 100000000; + let randomValue; + + do { + window.crypto.getRandomValues(array); + randomValue = array[0]; + } while (randomValue > Math.floor(0xffffffff / max) * max); + + return randomValue % max; +} + +function generatePhoneNumber(): string { + const randomNum = unbiasedRandom(900000000) + 100000000; // Ensure 9-digit range return "9" + randomNum.toString(); } @@ -31,9 +41,9 @@ function generateFacilityName(): string { "Ernakulam", ]; const identifiers = [ - () => Math.floor(Math.random() * 100), // Numeric IDs - () => `Zone-${Math.floor(Math.random() * 10)}`, // Zone IDs - () => `Block-${String.fromCharCode(65 + Math.floor(Math.random() * 26))}`, // Alphabetic Blocks + () => unbiasedRandom(100), // Numeric IDs + () => `Zone-${unbiasedRandom(10)}`, // Zone IDs + () => `Block-${String.fromCharCode(65 + unbiasedRandom(26))}`, // Alphabetic Blocks ]; const suffixes = [ "Meta", @@ -46,21 +56,18 @@ function generateFacilityName(): string { "Hospital", ]; - const randomPrefix = prefixes[Math.floor(Math.random() * prefixes.length)]; - const randomLocation = - locations[Math.floor(Math.random() * locations.length)]; - const randomIdentifier = - identifiers[Math.floor(Math.random() * identifiers.length)](); - const randomSuffix = suffixes[Math.floor(Math.random() * suffixes.length)]; + const randomPrefix = prefixes[unbiasedRandom(prefixes.length)]; + const randomLocation = locations[unbiasedRandom(locations.length)]; + const randomIdentifier = identifiers[unbiasedRandom(identifiers.length)](); + const randomSuffix = suffixes[unbiasedRandom(suffixes.length)]; - // Randomize the format of the name const formats = [ `${randomPrefix} ${randomLocation}-${randomIdentifier} ${randomSuffix}`, `${randomLocation} ${randomPrefix} ${randomSuffix}`, `${randomPrefix} ${randomLocation} ${randomSuffix}`, ]; - return formats[Math.floor(Math.random() * formats.length)]; + return formats[unbiasedRandom(formats.length)]; } function generateRandomAddress(multiline: boolean = false): string { @@ -89,17 +96,14 @@ function generateRandomAddress(multiline: boolean = false): string { ]; const districts = ["Kochi", "Ernakulam"]; const states = ["Kerala"]; - const pincode = Math.floor(682000 + Math.random() * 1000).toString(); // Generate random pincodes in the 682XXX range. + const pincode = (682000 + unbiasedRandom(1000)).toString(); - const randomLocality = - localities[Math.floor(Math.random() * localities.length)]; + const randomLocality = localities[unbiasedRandom(localities.length)]; const randomNeighborhood = - neighborhoods[Math.floor(Math.random() * neighborhoods.length)]; - const randomDistrict = - districts[Math.floor(Math.random() * districts.length)]; - const randomState = states[Math.floor(Math.random() * states.length)]; + neighborhoods[unbiasedRandom(neighborhoods.length)]; + const randomDistrict = districts[unbiasedRandom(districts.length)]; + const randomState = states[unbiasedRandom(states.length)]; - // Create address components const addressParts = [ randomNeighborhood, randomLocality, @@ -108,15 +112,65 @@ function generateRandomAddress(multiline: boolean = false): string { `Pincode: ${pincode}`, ]; - // Return address as single line or multiline - // If 'multiline' is false, return address as a single line - // If 'multiline' is true, return address with each component on a new line return multiline ? addressParts.join("\n") : addressParts.join(", "); } +function generatePatientName(): string { + const firstNames = [ + "John", + "Jane", + "Michael", + "Sarah", + "David", + "Emma", + "James", + "Olivia", + "Robert", + "Sophia", + "William", + "Isabella", + "Benjamin", + "Mia", + "Daniel", + "Charlotte", + "Lucas", + "Amelia", + "Ethan", + "Harper", + ]; + const lastNames = [ + "Smith", + "Johnson", + "Williams", + "Brown", + "Jones", + "Miller", + "Davis", + "Garcia", + "Rodriguez", + "Wilson", + "Martinez", + "Hernandez", + "Lopez", + "Gonzalez", + "Perez", + "Taylor", + "Anderson", + "Thomas", + "Jackson", + "White", + ]; + + const randomFirstName = firstNames[unbiasedRandom(firstNames.length)]; + const randomLastName = lastNames[unbiasedRandom(lastNames.length)]; + + return `${randomFirstName} ${randomLastName}`; +} + export { generatePhoneNumber, generateEmergencyPhoneNumber, generateFacilityName, generateRandomAddress, + generatePatientName, }; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index f5bbcf42290..54bc3be2666 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -133,12 +133,12 @@ Cypress.Commands.add("clickCancelButton", (buttonText = "Cancel") => { Cypress.Commands.add( "typeAndSelectOption", - (element: string, referance: string) => { + (element: string, reference: string) => { cy.get(element) .click() - .type(referance) + .type(reference) .then(() => { - cy.get("[role='option']").contains(referance).click(); + cy.get("[role='option']").contains(reference).click(); }); }, ); @@ -175,11 +175,17 @@ Cypress.Commands.add( Cypress.Commands.add( "clickAndSelectOption", - (element: string, reference: string) => { + (element: string, reference: string, skipVerification: boolean = false) => { cy.get(element) .click() .then(() => { cy.get("[role='option']").contains(reference).click(); + }) + .then(() => { + // Skip verification if skipVerification is true + if (!skipVerification) { + cy.get(element).should("contain", reference); + } }); }, ); @@ -247,15 +253,20 @@ Cypress.Commands.add( ( selector: string, value: string, - options: { clearBeforeTyping?: boolean } = {}, + options: { clearBeforeTyping?: boolean; skipVerification?: boolean } = {}, ) => { - const { clearBeforeTyping = false } = options; + const { clearBeforeTyping = false, skipVerification = false } = options; const inputField = cy.get(selector); if (clearBeforeTyping) { - inputField.clear(); // Clear the input field + inputField.clear(); // Clear the input field if specified } - inputField.click().type(value); // Click and type the new value + inputField.scrollIntoView().should("be.visible").click().type(value); + + // Conditionally skip verification based on the skipVerification flag + if (!skipVerification) { + inputField.should("have.value", value); // Verify the value if skipVerification is false + } }, ); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index fa01326698c..59620bf8a6d 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -34,6 +34,7 @@ declare global { clickAndSelectOption( element: string, reference: string, + skipVerification?: boolean, ): Chainable; verifyAndClickElement( element: string, @@ -49,7 +50,7 @@ declare global { typeIntoField( selector: string, value: string, - options?: { clearBeforeTyping?: boolean }, + options?: { clearBeforeTyping?: boolean; skipVerification?: boolean }, ): Chainable; } } diff --git a/package-lock.json b/package-lock.json index d3057b01be0..ebd6b08c090 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "@hookform/resolvers": "^3.9.1", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", - "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.1", @@ -28,20 +28,20 @@ "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-tooltip": "^1.1.4", - "@sentry/browser": "^8.42.0", - "@tanstack/react-query": "^5.62.3", + "@radix-ui/react-tooltip": "^1.1.6", + "@sentry/browser": "^8.45.1", + "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.7", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", - "browserslist": "^4.24.2", + "browserslist": "^4.24.3", "browserslist-useragent-regexp": "^4.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.4", "cross-env": "^7.0.3", - "cypress": "^13.15.2", + "cypress": "^13.17.0", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -60,12 +60,12 @@ "react-hook-form": "^7.54.1", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", - "react-pdf": "^9.1.1", + "react-pdf": "^9.2.1", "react-webcam": "^7.2.0", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", - "xlsx": "^0.18.5" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@julr/vite-plugin-validate-env": "^1.1.1", @@ -2876,8 +2876,10 @@ "version": "1.0.11", "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, "license": "BSD-3-Clause", "optional": true, + "peer": true, "dependencies": { "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", @@ -3747,24 +3749,25 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", - "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz", + "integrity": "sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==", + "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", "@radix-ui/react-focus-guards": "1.1.1", - "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.1", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.6.0" + "react-remove-scroll": "^2.6.1" }, "peerDependencies": { "@types/react": "*", @@ -4375,21 +4378,21 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz", - "integrity": "sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.6.tgz", + "integrity": "sha512-TLB5D8QLExS1uDn7+wH/bjEmRurNMTzNrtq7IjaS4kjion9NtzsTGkvR5+i7yc9q01Pi2KMM2cN3f8UG4IvvXA==", "license": "MIT", "dependencies": { - "@radix-ui/primitive": "1.1.0", - "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/primitive": "1.1.1", + "@radix-ui/react-compose-refs": "1.1.1", "@radix-ui/react-context": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.3", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-popper": "1.2.0", - "@radix-ui/react-portal": "1.1.2", - "@radix-ui/react-presence": "1.1.1", - "@radix-ui/react-primitive": "2.0.0", - "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-popper": "1.2.1", + "@radix-ui/react-portal": "1.1.3", + "@radix-ui/react-presence": "1.1.2", + "@radix-ui/react-primitive": "2.0.1", + "@radix-ui/react-slot": "1.1.1", "@radix-ui/react-use-controllable-state": "1.1.0", "@radix-ui/react-visually-hidden": "1.1.0" }, @@ -4954,50 +4957,50 @@ ] }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.42.0.tgz", - "integrity": "sha512-xzgRI0wglKYsPrna574w1t38aftuvo44gjOKFvPNGPnYfiW9y4m+64kUz3JFbtanvOrKPcaITpdYiB4DeJXEbA==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.45.1.tgz", + "integrity": "sha512-sZwtP3zAzDsjUS7WkMW5VGbvSl7hGKTMc8gAJbpEsrybMxllIP13zzMRwpeFF11RnnvbrZ/FtAeX58Mvj0jahA==", "license": "MIT", "dependencies": { - "@sentry/core": "8.42.0" + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.42.0.tgz", - "integrity": "sha512-dkIw5Wdukwzngg5gNJ0QcK48LyJaMAnBspqTqZ3ItR01STi6Z+6+/Bt5XgmrvDgRD+FNBinflc5zMmfdFXXhvw==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.45.1.tgz", + "integrity": "sha512-zCKptzki4SLnG+s8je8dgnppOKFjiiO4GVBc4fh7uL8zjNPBnxW8wK4SrPfAEKVYaHUzkKc5vixwUqcpmfLLGw==", "license": "MIT", "dependencies": { - "@sentry/core": "8.42.0" + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.42.0.tgz", - "integrity": "sha512-oNcJEBlDfXnRFYC5Mxj5fairyZHNqlnU4g8kPuztB9G5zlsyLgWfPxzcn1ixVQunth2/WZRklDi4o1ZfyHww7w==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.45.1.tgz", + "integrity": "sha512-cOA9CodNSR9+hmICDaGIDUvWiwxQxeMHk/esbjB8uAW8HG4CYTG3CTYTZmlmou7DuysfMd4JNuFmDFBj+YU5/A==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.42.0", - "@sentry/core": "8.42.0" + "@sentry-internal/browser-utils": "8.45.1", + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.42.0.tgz", - "integrity": "sha512-XrPErqVhPsPh/oFLVKvz7Wb+Fi2J1zCPLeZCxWqFuPWI2agRyLVu0KvqJyzSpSrRAEJC/XFzuSVILlYlXXSfgA==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.45.1.tgz", + "integrity": "sha512-qiPg6XwOwkiMMe/8Qf3EhXCqkSlSnWLlorYngIbdkV2klbWjd7vKnqkFJF4PnaS0g7kkZr7nh+MdzpyLyuj2Mw==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "8.42.0", - "@sentry/core": "8.42.0" + "@sentry-internal/replay": "8.45.1", + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" @@ -5056,25 +5059,25 @@ } }, "node_modules/@sentry/browser": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.42.0.tgz", - "integrity": "sha512-lStrEk609KJHwXfDrOgoYVVoFFExixHywxSExk7ZDtwj2YPv6r6Y1gogvgr7dAZj7jWzadHkxZ33l9EOSJBfug==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.45.1.tgz", + "integrity": "sha512-/KvYhQSRg8m9kotG8h9FrfXCWRlebrvdfXKjj1oE9SyZ2LmR8Ze9AcEw1qzsBsa1F1D/a5FQbUJahSoLBkaQPA==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.42.0", - "@sentry-internal/feedback": "8.42.0", - "@sentry-internal/replay": "8.42.0", - "@sentry-internal/replay-canvas": "8.42.0", - "@sentry/core": "8.42.0" + "@sentry-internal/browser-utils": "8.45.1", + "@sentry-internal/feedback": "8.45.1", + "@sentry-internal/replay": "8.45.1", + "@sentry-internal/replay-canvas": "8.45.1", + "@sentry/core": "8.45.1" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/core": { - "version": "8.42.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.42.0.tgz", - "integrity": "sha512-ac6O3pgoIbU6rpwz6LlwW0wp3/GAHuSI0C5IsTgIY6baN8rOBnlAtG6KrHDDkGmUQ2srxkDJu9n1O6Td3cBCqw==", + "version": "8.45.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.45.1.tgz", + "integrity": "sha512-1fGmkr0paZshh38mD29c4CfkRkgFoYDaAGyDLoGYfTbEph/lU8RHB2HWzN93McqNdMEhl1DRRyqIasUZoPlqSA==", "license": "MIT", "engines": { "node": ">=14.18" @@ -5485,9 +5488,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.62.7", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.7.tgz", - "integrity": "sha512-fgpfmwatsrUal6V+8EC2cxZIQVl9xvL7qYa03gsdsCy985UTUlS4N+/3hCzwR0PclYDqisca2AqR1BVgJGpUDA==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.62.8.tgz", + "integrity": "sha512-4fV31vDsUyvNGrKIOUNPrZztoyL187bThnoQOvAXEVlZbSiuPONpfx53634MKKdvsDir5NyOGm80ShFaoHS/mw==", "license": "MIT", "funding": { "type": "github", @@ -5505,12 +5508,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.62.7", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.7.tgz", - "integrity": "sha512-+xCtP4UAFDTlRTYyEjLx0sRtWyr5GIk7TZjZwBu4YaNahi3Rt2oMyRqfpfVrtwsqY2sayP4iXVCwmC+ZqqFmuw==", + "version": "5.62.8", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.62.8.tgz", + "integrity": "sha512-8TUstKxF/fysHonZsWg/hnlDVgasTdHx6Q+f1/s/oPKJBJbKUWPZEHwLTMOZgrZuroLMiqYKJ9w69Abm8mWP0Q==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.62.7" + "@tanstack/query-core": "5.62.8" }, "funding": { "type": "github", @@ -6473,8 +6476,10 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/acorn": { "version": "8.13.0", @@ -6523,21 +6528,14 @@ "node": ">=0.4.0" } }, - "node_modules/adler-32": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz", - "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "debug": "4" }, @@ -6676,8 +6674,10 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/arch": { "version": "2.2.0", @@ -6704,8 +6704,10 @@ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" @@ -7141,6 +7143,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", @@ -7197,9 +7211,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", - "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "version": "4.24.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.3.tgz", + "integrity": "sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==", "funding": [ { "type": "opencollective", @@ -7216,9 +7230,9 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001669", - "electron-to-chromium": "^1.5.41", - "node-releases": "^2.0.18", + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.1" }, "bin": { @@ -7342,9 +7356,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001669", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001669.tgz", - "integrity": "sha512-DlWzFDJqstqtIVx1zeSpIMLjunf5SmwOw0N2Ck/QSQdS8PLS4+9HrLaYei4w8BIAL7IB/UEDu889d8vhCTPA0w==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "funding": [ { "type": "opencollective", @@ -7365,9 +7379,11 @@ "version": "2.11.2", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz", "integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "@mapbox/node-pre-gyp": "^1.0.0", "nan": "^2.17.0", @@ -7394,19 +7410,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/cfb": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz", - "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==", - "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "crc-32": "~1.2.0" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -7528,8 +7531,10 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "engines": { "node": ">=10" } @@ -7672,15 +7677,6 @@ "react-dom": "^18 || ^19 || ^19.0.0-rc" } }, - "node_modules/codepage": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", - "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -7703,8 +7699,10 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "bin": { "color-support": "bin.js" } @@ -7749,7 +7747,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/concat-stream": { @@ -7772,8 +7770,10 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/console.table": { "version": "0.10.0", @@ -7860,18 +7860,6 @@ } } }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -7965,10 +7953,11 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "13.15.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz", - "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==", + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", + "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", "hasInstallScript": true, + "license": "MIT", "dependencies": { "@cypress/request": "^3.0.6", "@cypress/xvfb": "^1.2.4", @@ -8273,6 +8262,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -8358,8 +8357,10 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/dependency-tree": { "version": "11.0.1", @@ -8719,9 +8720,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.41", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.41.tgz", - "integrity": "sha512-dfdv/2xNjX0P8Vzme4cfzHqnPm5xsZXwsolTYr0eyW18IUmNyG08vL+fttvinTfhKfIKdRoqkDIC9e9iWQCNYQ==", + "version": "1.5.74", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.74.tgz", + "integrity": "sha512-ck3//9RC+6oss/1Bh9tiAVFy5vfSKbRHAFh7Z3/eTRkEqJeWgymloShB17Vg3Z4nmDNp35vAd1BZ6CMW4Wt6Iw==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -9607,6 +9608,16 @@ "node": ">=4" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -10029,15 +10040,6 @@ "node": ">= 6" } }, - "node_modules/frac": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz", - "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -10052,6 +10054,13 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT", + "optional": true + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -10071,8 +10080,10 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0" }, @@ -10084,8 +10095,10 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -10097,14 +10110,16 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true, + "dev": true, "license": "ISC" }, "node_modules/fsevents": { @@ -10164,8 +10179,10 @@ "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.2", @@ -10317,6 +10334,13 @@ "assert-plus": "^1.0.0" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT", + "optional": true + }, "node_modules/glob": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", @@ -10580,8 +10604,10 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/hasown": { "version": "2.0.2", @@ -10701,8 +10727,10 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -10892,7 +10920,7 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -10940,15 +10968,6 @@ "node": ">= 0.10" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/is-alphabetical": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", @@ -12765,8 +12784,10 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "semver": "^6.0.0" }, @@ -12781,8 +12802,10 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "bin": { "semver": "bin/semver.js" } @@ -14419,8 +14442,10 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, "license": "MIT", "optional": true, + "peer": true, "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" @@ -14433,8 +14458,10 @@ "version": "3.3.6", "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "yallist": "^4.0.0" }, @@ -14446,15 +14473,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, "license": "MIT", "optional": true, + "peer": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -14462,6 +14493,13 @@ "node": ">=10" } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT", + "optional": true + }, "node_modules/module-definition": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/module-definition/-/module-definition-6.0.0.tgz", @@ -14585,8 +14623,10 @@ "version": "2.22.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "dev": true, "license": "MIT", - "optional": true + "optional": true, + "peer": true }, "node_modules/nanoid": { "version": "3.3.8", @@ -14606,6 +14646,13 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/napi-build-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", + "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==", + "license": "MIT", + "optional": true + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -14621,6 +14668,26 @@ "optional": true, "peer": true }, + "node_modules/node-abi": { + "version": "3.71.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz", + "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==", + "license": "MIT", + "optional": true, + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT", + "optional": true + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -14642,9 +14709,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", - "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, "node_modules/node-source-walk": { @@ -14664,8 +14731,10 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "abbrev": "1" }, @@ -14782,8 +14851,10 @@ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", "deprecated": "This package is no longer supported.", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "are-we-there-yet": "^2.0.0", "console-control-strings": "^1.1.0", @@ -15103,7 +15174,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -15162,9 +15233,9 @@ } }, "node_modules/path2d": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.1.tgz", - "integrity": "sha512-Fl2z/BHvkTNvkuBzYTpTuirHZg6wW9z8+4SND/3mDTEcYbbNKWAy21dz9D3ePNNwrrK8pqZO5vLPZ1hLF6T7XA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/path2d/-/path2d-0.2.2.tgz", + "integrity": "sha512-+vnG6S4dYcYxZd+CZxzXCNKdELYZSKfohrk98yajCo1PtRoDgCTrrwOvK1GT0UoAdVszagDVllQc0U1vaX4NUQ==", "license": "MIT", "optional": true, "engines": { @@ -15183,16 +15254,32 @@ } }, "node_modules/pdfjs-dist": { - "version": "4.4.168", - "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.4.168.tgz", - "integrity": "sha512-MbkAjpwka/dMHaCfQ75RY1FXX3IewBVu6NGZOcxerRFlaBiIkZmUoR0jotX5VUzYZEXAGzSFtknWs5xRKliXPA==", + "version": "4.8.69", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-4.8.69.tgz", + "integrity": "sha512-IHZsA4T7YElCKNNXtiLgqScw4zPd3pG9do8UrznC757gMd7UPeHSL2qwNNMJo4r79fl8oj1Xx+1nh2YkzdMpLQ==", "license": "Apache-2.0", "engines": { "node": ">=18" }, "optionalDependencies": { - "canvas": "^2.11.2", - "path2d": "^0.2.0" + "canvas": "^3.0.0-rc2", + "path2d": "^0.2.1" + } + }, + "node_modules/pdfjs-dist/node_modules/canvas": { + "version": "3.0.0-rc3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.0.0-rc3.tgz", + "integrity": "sha512-LJVkMp4AH7/IRoLvhLS6R09uBt9O3O0mhCYL34AQV/+OC39jmTv22pJTF5Mgfa3V2JnzGl21MVrhEKmtmPtfQA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "simple-get": "^3.0.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" } }, "node_modules/pend": { @@ -15523,6 +15610,88 @@ "postcss": "^8.2.9" } }, + "node_modules/prebuild-install": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz", + "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^1.0.1", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/prebuild-install/node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "optional": true, + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/precinct": { "version": "12.1.2", "resolved": "https://registry.npmjs.org/precinct/-/precinct-12.1.2.tgz", @@ -15870,6 +16039,39 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "optional": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC", + "optional": true + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -16000,9 +16202,9 @@ "license": "MIT" }, "node_modules/react-pdf": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.1.1.tgz", - "integrity": "sha512-Cn3RTJZMqVOOCgLMRXDamLk4LPGfyB2Np3OwQAUjmHIh47EpuGW1OpAA1Z1GVDLoHx4d5duEDo/YbUkDbr4QFQ==", + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/react-pdf/-/react-pdf-9.2.1.tgz", + "integrity": "sha512-AJt0lAIkItWEZRA5d/mO+Om4nPCuTiQ0saA+qItO967DTjmGjnhmF+Bi2tL286mOTfBlF5CyLzJ35KTMaDoH+A==", "license": "MIT", "dependencies": { "clsx": "^2.0.0", @@ -16010,7 +16212,7 @@ "make-cancellable-promise": "^1.3.1", "make-event-props": "^1.6.0", "merge-refs": "^1.3.0", - "pdfjs-dist": "4.4.168", + "pdfjs-dist": "4.8.69", "tiny-invariant": "^1.0.0", "warning": "^4.0.0" }, @@ -16077,20 +16279,20 @@ } }, "node_modules/react-remove-scroll-bar": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz", - "integrity": "sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==", + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { - "react-style-singleton": "^2.2.1", + "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -16099,21 +16301,20 @@ } }, "node_modules/react-style-singleton": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", - "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", - "invariant": "^2.2.4", "tslib": "^2.0.0" }, "engines": { "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -16733,7 +16934,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -16749,7 +16950,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -16761,7 +16962,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -16782,7 +16983,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -17096,8 +17297,10 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/set-function-length": { "version": "1.2.2", @@ -17448,18 +17651,6 @@ "dev": true, "license": "BSD-3-Clause" }, - "node_modules/ssf": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz", - "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==", - "license": "Apache-2.0", - "dependencies": { - "frac": "~1.1.2" - }, - "engines": { - "node": ">=0.8" - } - }, "node_modules/sshpk": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", @@ -18016,8 +18207,10 @@ "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", @@ -18030,12 +18223,51 @@ "node": ">=10" } }, + "node_modules/tar-fs": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", + "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "license": "MIT", + "optional": true, + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC", + "optional": true + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/tar/node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "engines": { "node": ">=8" } @@ -18044,8 +18276,10 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true, "license": "ISC", - "optional": true + "optional": true, + "peer": true }, "node_modules/temp-dir": { "version": "2.0.0", @@ -19032,9 +19266,9 @@ } }, "node_modules/use-callback-ref": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.2.tgz", - "integrity": "sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -19043,8 +19277,8 @@ "node": ">=10" }, "peerDependencies": { - "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "peerDependenciesMeta": { "@types/react": { @@ -20424,30 +20658,14 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dev": true, "license": "ISC", "optional": true, + "peer": true, "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/wmf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz", - "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/word": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz", - "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8" - } - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -21024,19 +21242,10 @@ } }, "node_modules/xlsx": { - "version": "0.18.5", - "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz", - "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==", + "version": "0.20.3", + "resolved": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz", + "integrity": "sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==", "license": "Apache-2.0", - "dependencies": { - "adler-32": "~1.3.0", - "cfb": "~1.2.1", - "codepage": "~1.15.0", - "crc-32": "~1.2.1", - "ssf": "~0.11.2", - "wmf": "~1.0.1", - "word": "~0.3.0" - }, "bin": { "xlsx": "bin/xlsx.njs" }, diff --git a/package.json b/package.json index 8edf7842d55..6d70ba43c9c 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@hookform/resolvers": "^3.9.1", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", - "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.4", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-label": "^2.1.1", @@ -67,20 +67,20 @@ "@radix-ui/react-scroll-area": "^1.2.0", "@radix-ui/react-slot": "^1.1.1", "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-tooltip": "^1.1.4", - "@sentry/browser": "^8.42.0", - "@tanstack/react-query": "^5.62.3", + "@radix-ui/react-tooltip": "^1.1.6", + "@sentry/browser": "^8.45.1", + "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.7", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", - "browserslist": "^4.24.2", + "browserslist": "^4.24.3", "browserslist-useragent-regexp": "^4.1.3", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.4", "cross-env": "^7.0.3", - "cypress": "^13.15.2", + "cypress": "^13.17.0", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -99,12 +99,12 @@ "react-hook-form": "^7.54.1", "react-i18next": "^15.1.3", "react-infinite-scroll-component": "^6.1.0", - "react-pdf": "^9.1.1", + "react-pdf": "^9.2.1", "react-webcam": "^7.2.0", "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", - "xlsx": "^0.18.5" + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@julr/vite-plugin-validate-env": "^1.1.1", diff --git a/public/locale/en.json b/public/locale/en.json index 2ceaf310fac..758ef1965db 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -323,6 +323,7 @@ "ambulance_driver_name": "Name of ambulance driver", "ambulance_number": "Ambulance No", "ambulance_phone_number": "Phone number of Ambulance", + "and_the_status_of_request_is": "and the status of request is", "antenatal": "Antenatal", "any_id": "Enter any ID linked with your ABHA number", "any_id_description": "Currently we support: Aadhaar Number / Mobile Number", @@ -549,6 +550,7 @@ "contribute_github": "Contribute on Github", "copied_to_clipboard": "Copied to clipboard", "copilot_thinking": "Copilot is thinking...", + "copy_phone_number": "Copy Phone Number", "could_not_autofill": "We could not autofill any fields from what you said", "countries_travelled": "Countries travelled", "covid_19_cat_gov": "Covid_19 Clinical Category as per Govt. of Kerala guideline (A/B/C)", @@ -1044,6 +1046,7 @@ "notification_cancelled": "Notification cancelled", "notification_permission_denied": "Notification permission denied", "notification_permission_granted": "Notification permission granted", + "notify": "Notify", "number_of_aged_dependents": "Number of Aged Dependents (Above 60)", "number_of_beds": "Number of beds", "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", @@ -1210,6 +1213,8 @@ "provisional": "Provisional", "qualification": "Qualification", "qualification_required": "Qualification is required", + "quantity_approved": "QUANTITY APPROVED", + "quantity_requested": "Quantity Requested", "raise_consent_request": "Raise a consent request to fetch patient records over ABDM", "ration_card__APL": "APL", "ration_card__BPL": "BPL", @@ -1262,6 +1267,7 @@ "reset_password_note_self": "Enter your current password, then create and confirm your new password", "resource": "Resource", "resource_approving_facility": "Resource approving facility", + "resource_details": "Resource details", "resource_origin_facility": "Origin Facility", "resource_request": "Resource Request", "resource_status": "Resource Status", @@ -1403,7 +1409,9 @@ "target_dosage": "Target Dosage", "test_type": "Type of test done", "tested_on": "Tested on", + "the_request_for_resources_placed_by_yourself_is": "The request for resource (details below) placed by yourself is", "third_party_software_licenses": "Third Party Software Licenses", + "title_of_request": "Title of Request", "titrate_dosage": "Titrate Dosage", "to_be_conducted": "To be conducted", "total_amount": "Total Amount", @@ -1532,9 +1540,10 @@ "view_all_details": "View All Details", "view_asset": "View Assets", "view_cns": "View CNS", + "view_consultation": "View Latest Encounter", "view_consultation_and_log_updates": "View Consultation / Log Updates", "view_details": "View Details", - "view_faciliy": "View Facility", + "view_facility": "View Facility", "view_files": "View Files", "view_patients": "View Patients", "view_update_patient_files": "View/Update patient files", diff --git a/public/locale/hi.json b/public/locale/hi.json index 568ce0499f6..8816f1f2336 100644 --- a/public/locale/hi.json +++ b/public/locale/hi.json @@ -795,7 +795,7 @@ "view_abdm_records": "ABDM रिकॉर्ड देखें", "view_asset": "संपत्तियां देखें", "view_details": "विवरण देखें", - "view_faciliy": "सुविधा देखें", + "view_facility": "सुविधा देखें", "view_patients": "मरीज़ देखें", "view_users": "उपयोगकर्ता देखें", "virtual_nursing_assistant": "वर्चुअल नर्सिंग सहायक", diff --git a/public/locale/kn.json b/public/locale/kn.json index 4907cdd2d8c..65ced04bd3f 100644 --- a/public/locale/kn.json +++ b/public/locale/kn.json @@ -796,7 +796,7 @@ "view_abdm_records": "ABDM ದಾಖಲೆಗಳನ್ನು ವೀಕ್ಷಿಸಿ", "view_asset": "ಸ್ವತ್ತುಗಳನ್ನು ವೀಕ್ಷಿಸಿ", "view_details": "ವಿವರಗಳನ್ನು ವೀಕ್ಷಿಸಿ", - "view_faciliy": "ವೀಕ್ಷಣೆ ಸೌಲಭ್ಯ", + "view_facility": "ವೀಕ್ಷಣೆ ಸೌಲಭ್ಯ", "view_patients": "ರೋಗಿಗಳನ್ನು ವೀಕ್ಷಿಸಿ", "view_users": "ಬಳಕೆದಾರರನ್ನು ವೀಕ್ಷಿಸಿ", "virtual_nursing_assistant": "ವರ್ಚುವಲ್ ನರ್ಸಿಂಗ್ ಸಹಾಯಕ", diff --git a/public/locale/ml.json b/public/locale/ml.json index b875a64dd02..c69f4940657 100644 --- a/public/locale/ml.json +++ b/public/locale/ml.json @@ -796,7 +796,7 @@ "view_abdm_records": "ABDM റെക്കോർഡുകൾ കാണുക", "view_asset": "അസറ്റുകൾ കാണുക", "view_details": "വിശദാംശങ്ങൾ കാണുക", - "view_faciliy": "സൗകര്യം കാണുക", + "view_facility": "സൗകര്യം കാണുക", "view_patients": "രോഗികളെ കാണുക", "view_users": "ഉപയോക്താക്കളെ കാണുക", "virtual_nursing_assistant": "വെർച്വൽ നഴ്സിംഗ് അസിസ്റ്റൻ്റ്", diff --git a/public/locale/ta.json b/public/locale/ta.json index cfe12b5fa6a..8c84339177e 100644 --- a/public/locale/ta.json +++ b/public/locale/ta.json @@ -795,7 +795,7 @@ "view_abdm_records": "ABDM பதிவுகளைப் பார்க்கவும்", "view_asset": "சொத்துக்களைப் பார்க்கவும்", "view_details": "விவரங்களைக் காண்க", - "view_faciliy": "பார்வை வசதி", + "view_facility": "பார்வை வசதி", "view_patients": "நோயாளிகளைப் பார்க்கவும்", "view_users": "பயனர்களைக் காண்க", "virtual_nursing_assistant": "மெய்நிகர் நர்சிங் உதவியாளர்", diff --git a/src/App.tsx b/src/App.tsx index b4d1a1570a9..1d8acbb8e59 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,5 @@ import { + MutationCache, QueryCache, QueryClient, QueryClientProvider, @@ -16,7 +17,7 @@ import AuthUserProvider from "@/Providers/AuthUserProvider"; import HistoryAPIProvider from "@/Providers/HistoryAPIProvider"; import Routers from "@/Routers"; import { FeatureFlagsProvider } from "@/Utils/featureFlags"; -import { handleQueryError } from "@/Utils/request/errorHandler"; +import { handleHttpError } from "@/Utils/request/errorHandler"; import { PubSubProvider } from "./Utils/pubsubContext"; @@ -29,7 +30,10 @@ const queryClient = new QueryClient({ }, }, queryCache: new QueryCache({ - onError: handleQueryError, + onError: handleHttpError, + }), + mutationCache: new MutationCache({ + onError: handleHttpError, }), }); diff --git a/src/Routers/routes/UserRoutes.tsx b/src/Routers/routes/UserRoutes.tsx index cc668e2fee6..826007c3b14 100644 --- a/src/Routers/routes/UserRoutes.tsx +++ b/src/Routers/routes/UserRoutes.tsx @@ -1,7 +1,6 @@ import ManageUsers from "@/components/Users/ManageUsers"; import UserAdd from "@/components/Users/UserAdd"; import UserHome from "@/components/Users/UserHome"; -import UserProfile from "@/components/Users/UserProfile"; import { AppRoutes } from "@/Routers/AppRouter"; @@ -14,7 +13,7 @@ const UserRoutes: AppRoutes = { "/users/:username/:tab": ({ username, tab }) => ( ), - "/user/profile": () => , + "/user/:tab": ({ tab }) => , }; export default UserRoutes; diff --git a/src/Utils/request/README.md b/src/Utils/request/README.md index 3c1279e1554..75dfab6c10f 100644 --- a/src/Utils/request/README.md +++ b/src/Utils/request/README.md @@ -67,10 +67,12 @@ function FacilityDetails({ id }: { id: string }) { - Integrates with our global error handling. ```typescript -interface QueryOptions { +interface APICallOptions { pathParams?: Record; // URL parameters - queryParams?: Record; // Query string parameters + queryParams?: QueryParams; // Query string parameters + body?: TBody; // Request body silent?: boolean; // Suppress error notifications + headers?: HeadersInit; // Additional headers } // Basic usage @@ -100,6 +102,82 @@ are automatically handled. Use the `silent: true` option to suppress error notifications for specific queries. +## Using Mutations with TanStack Query + +For data mutations, we provide a `mutate` utility that works seamlessly with TanStack Query's `useMutation` hook. + +```tsx +import { useMutation } from "@tanstack/react-query"; +import mutate from "@/Utils/request/mutate"; + +function CreatePrescription({ consultationId }: { consultationId: string }) { + const { mutate: createPrescription, isPending } = useMutation({ + mutationFn: mutate(MedicineRoutes.createPrescription, { + pathParams: { consultationId }, + }), + onSuccess: () => { + toast.success("Prescription created successfully"); + }, + }); + + return ( + + ); +} + +// With path parameters and complex payload +function UpdatePatient({ patientId }: { patientId: string }) { + const { mutate: updatePatient } = useMutation({ + mutationFn: mutate(PatientRoutes.update, { + pathParams: { id: patientId }, + silent: true // Optional: suppress error notifications + }) + }); + + const handleSubmit = (data: PatientData) => { + updatePatient(data); + }; + + return ; +} +``` + +### mutate + +`mutate` is our wrapper around the API call functionality that works with TanStack Query's `useMutation`. It: +- Handles request body serialization +- Sets appropriate headers +- Integrates with our global error handling +- Provides TypeScript type safety for your mutation payload + +```typescript +interface APICallOptions { + pathParams?: Record; // URL parameters + queryParams?: QueryParams; // Query string parameters + body?: TBody; // Request body + silent?: boolean; // Suppress error notifications + headers?: HeadersInit; // Additional headers +} + +// Basic usage +useMutation({ + mutationFn: mutate(routes.users.create) +}); + +// With parameters +useMutation({ + mutationFn: mutate(routes.users.update, { + pathParams: { id }, + silent: true // Optional: suppress error notifications + }) +}); +``` + ## Migration Guide & Reference ### Understanding the Transition diff --git a/src/Utils/request/errorHandler.ts b/src/Utils/request/errorHandler.ts index 68d7e4600bb..c5609181f13 100644 --- a/src/Utils/request/errorHandler.ts +++ b/src/Utils/request/errorHandler.ts @@ -1,14 +1,14 @@ import { navigate } from "raviger"; import * as Notifications from "@/Utils/Notifications"; -import { QueryError } from "@/Utils/request/queryError"; +import { HTTPError } from "@/Utils/request/types"; -export function handleQueryError(error: Error) { +export function handleHttpError(error: Error) { if (error.name === "AbortError") { return; } - if (!(error instanceof QueryError)) { + if (!(error instanceof HTTPError)) { Notifications.Error({ msg: error.message || "Something went wrong!" }); return; } @@ -34,7 +34,7 @@ export function handleQueryError(error: Error) { }); } -function isSessionExpired(error: QueryError["cause"]) { +function isSessionExpired(error: HTTPError["cause"]) { return ( // If Authorization header is not valid error?.code === "token_not_valid" || @@ -49,6 +49,6 @@ function handleSessionExpired() { } } -function isBadRequest(error: QueryError) { +function isBadRequest(error: HTTPError) { return error.status === 400 || error.status === 406; } diff --git a/src/Utils/request/mutate.ts b/src/Utils/request/mutate.ts new file mode 100644 index 00000000000..2372920c162 --- /dev/null +++ b/src/Utils/request/mutate.ts @@ -0,0 +1,26 @@ +import { callApi } from "@/Utils/request/query"; +import { APICallOptions, Route } from "@/Utils/request/types"; + +/** + * Creates a TanStack Query compatible mutation function. + * + * Example: + * ```tsx + * const { mutate: createPrescription, isPending } = useMutation({ + * mutationFn: mutate(MedicineRoutes.createPrescription, { + * pathParams: { consultationId }, + * }), + * onSuccess: () => { + * toast.success(t("medication_request_prescribed")); + * }, + * }); + * ``` + */ +export default function mutate( + route: Route, + options?: APICallOptions, +) { + return (variables: TBody) => { + return callApi(route, { ...options, body: variables }); + }; +} diff --git a/src/Utils/request/query.ts b/src/Utils/request/query.ts index 3431f625728..dc79bd874ec 100644 --- a/src/Utils/request/query.ts +++ b/src/Utils/request/query.ts @@ -1,19 +1,18 @@ import careConfig from "@careConfig"; -import { QueryError } from "@/Utils/request/queryError"; import { getResponseBody } from "@/Utils/request/request"; -import { QueryOptions, Route } from "@/Utils/request/types"; +import { APICallOptions, HTTPError, Route } from "@/Utils/request/types"; import { makeHeaders, makeUrl } from "@/Utils/request/utils"; -async function queryRequest( +export async function callApi( { path, method, noAuth }: Route, - options?: QueryOptions, + options?: APICallOptions, ): Promise { const url = `${careConfig.apiUrl}${makeUrl(path, options?.queryParams, options?.pathParams)}`; const fetchOptions: RequestInit = { method, - headers: makeHeaders(noAuth ?? false), + headers: makeHeaders(noAuth ?? false, options?.headers), signal: options?.signal, }; @@ -32,7 +31,7 @@ async function queryRequest( const data = await getResponseBody(res); if (!res.ok) { - throw new QueryError({ + throw new HTTPError({ message: "Request Failed", status: res.status, silent: options?.silent ?? false, @@ -44,13 +43,27 @@ async function queryRequest( } /** - * Creates a TanStack Query compatible request function + * Creates a TanStack Query compatible query function. + * + * Example: + * ```tsx + * const { data, isLoading } = useQuery({ + * queryKey: ["prescription", consultationId], + * queryFn: query(MedicineRoutes.prescription, { + * pathParams: { consultationId }, + * queryParams: { + * limit: 10, + * offset: 0, + * }, + * }), + * }); + * ``` */ export default function query( route: Route, - options?: QueryOptions, + options?: APICallOptions, ) { return ({ signal }: { signal: AbortSignal }) => { - return queryRequest(route, { ...options, signal }); + return callApi(route, { ...options, signal }); }; } diff --git a/src/Utils/request/queryError.ts b/src/Utils/request/queryError.ts deleted file mode 100644 index cdfad312ef4..00000000000 --- a/src/Utils/request/queryError.ts +++ /dev/null @@ -1,24 +0,0 @@ -type QueryErrorCause = Record | undefined; - -export class QueryError extends Error { - status: number; - silent: boolean; - cause?: QueryErrorCause; - - constructor({ - message, - status, - silent, - cause, - }: { - message: string; - status: number; - silent: boolean; - cause?: Record; - }) { - super(message, { cause }); - this.status = status; - this.silent = silent; - this.cause = cause; - } -} diff --git a/src/Utils/request/types.ts b/src/Utils/request/types.ts index 20f095ae6fd..a53a28fb0b0 100644 --- a/src/Utils/request/types.ts +++ b/src/Utils/request/types.ts @@ -35,12 +35,44 @@ export interface RequestOptions { silent?: boolean; } -export interface QueryOptions { - pathParams?: Record; - queryParams?: Record; +export interface APICallOptions { + pathParams?: Record; + queryParams?: QueryParams; body?: TBody; silent?: boolean; signal?: AbortSignal; + headers?: HeadersInit; +} + +type HTTPErrorCause = Record | undefined; + +export class HTTPError extends Error { + status: number; + silent: boolean; + cause?: HTTPErrorCause; + + constructor({ + message, + status, + silent, + cause, + }: { + message: string; + status: number; + silent: boolean; + cause?: Record; + }) { + super(message, { cause }); + this.status = status; + this.silent = silent; + this.cause = cause; + } +} + +declare module "@tanstack/react-query" { + interface Register { + defaultError: HTTPError; + } } export interface PaginatedResponse { diff --git a/src/Utils/request/uploadFile.ts b/src/Utils/request/uploadFile.ts index 005eeaf92aa..ea603e1754f 100644 --- a/src/Utils/request/uploadFile.ts +++ b/src/Utils/request/uploadFile.ts @@ -3,7 +3,7 @@ import { Dispatch, SetStateAction } from "react"; import * as Notification from "@/Utils/Notifications"; import { handleUploadPercentage } from "@/Utils/request/utils"; -const uploadFile = ( +const uploadFile = async ( url: string, file: File | FormData, reqMethod: string, @@ -11,41 +11,52 @@ const uploadFile = ( onLoad: (xhr: XMLHttpRequest) => void, setUploadPercent: Dispatch> | null, onError: () => void, -) => { - const xhr = new XMLHttpRequest(); - xhr.open(reqMethod, url); +): Promise => { + return new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open(reqMethod, url); - Object.entries(headers).forEach(([key, value]) => { - xhr.setRequestHeader(key, value); - }); + Object.entries(headers).forEach(([key, value]) => { + xhr.setRequestHeader(key, value); + }); - xhr.onload = () => { - onLoad(xhr); - if (400 <= xhr.status && xhr.status <= 499) { - const error = JSON.parse(xhr.responseText); - if (typeof error === "object" && !Array.isArray(error)) { - Object.values(error).forEach((msg) => { - Notification.Error({ msg: msg || "Something went wrong!" }); - }); + xhr.onload = () => { + onLoad(xhr); + if (400 <= xhr.status && xhr.status <= 499) { + let error; + try { + error = JSON.parse(xhr.responseText); + } catch { + error = xhr.responseText; + } + if (typeof error === "object" && !Array.isArray(error)) { + Object.values(error).forEach((msg) => { + Notification.Error({ msg: msg || "Something went wrong!" }); + }); + } else { + Notification.Error({ msg: error || "Something went wrong!" }); + } + reject(new Error("Client error")); } else { - Notification.Error({ msg: error || "Something went wrong!" }); + resolve(); } + }; + + if (setUploadPercent != null) { + xhr.upload.onprogress = (event: ProgressEvent) => { + handleUploadPercentage(event, setUploadPercent); + }; } - }; - if (setUploadPercent != null) { - xhr.upload.onprogress = (event: ProgressEvent) => { - handleUploadPercentage(event, setUploadPercent); + xhr.onerror = () => { + Notification.Error({ + msg: "Network Failure. Please check your internet connectivity.", + }); + onError(); + reject(new Error("Network error")); }; - } - xhr.onerror = () => { - Notification.Error({ - msg: "Network Failure. Please check your internet connectivity.", - }); - onError(); - }; - xhr.send(file); + xhr.send(file); + }); }; - export default uploadFile; diff --git a/src/Utils/request/utils.ts b/src/Utils/request/utils.ts index 8fd7bc96bea..26d69672f53 100644 --- a/src/Utils/request/utils.ts +++ b/src/Utils/request/utils.ts @@ -50,28 +50,25 @@ const ensurePathNotMissingReplacements = (path: string) => { } }; -export function makeHeaders(noAuth: boolean) { - const headers = new Headers({ - "Content-Type": "application/json", - Accept: "application/json", - }); +export function makeHeaders(noAuth: boolean, additionalHeaders?: HeadersInit) { + const headers = new Headers(additionalHeaders); - if (!noAuth) { - const token = getAuthorizationHeader(); + headers.set("Content-Type", "application/json"); + headers.append("Accept", "application/json"); - if (token) { - headers.append("Authorization", token); - } + const authorizationHeader = getAuthorizationHeader(); + if (authorizationHeader && !noAuth) { + headers.append("Authorization", authorizationHeader); } return headers; } export function getAuthorizationHeader() { - const bearerToken = localStorage.getItem(LocalStorageKeys.accessToken); + const accessToken = localStorage.getItem(LocalStorageKeys.accessToken); - if (bearerToken) { - return `Bearer ${bearerToken}`; + if (accessToken) { + return `Bearer ${accessToken}`; } return null; diff --git a/src/Utils/utils.ts b/src/Utils/utils.ts index 5588e048bd2..f9e0e14577a 100644 --- a/src/Utils/utils.ts +++ b/src/Utils/utils.ts @@ -3,6 +3,7 @@ import { PatientModel } from "@/components/Patient/models"; import { AREACODES, IN_LANDLINE_AREA_CODES } from "@/common/constants"; import phoneCodesJson from "@/common/static/countryPhoneAndFlags.json"; +import * as Notification from "@/Utils/Notifications"; import dayjs from "@/Utils/dayjs"; interface ApacheParams { @@ -561,3 +562,12 @@ export function omitBy>( Object.entries(obj).filter(([_, value]) => !predicate(value)), ) as Partial; } + +export const copyToClipboard = async (content: string) => { + try { + await navigator.clipboard.writeText(content); + Notification.Success({ msg: "Copied to clipboard" }); + } catch (err) { + Notification.Error({ msg: "Copying is not allowed" }); + } +}; diff --git a/src/components/Assets/AssetWarrantyCard.tsx b/src/components/Assets/AssetWarrantyCard.tsx index ebaaca7e6c8..9f37394957a 100644 --- a/src/components/Assets/AssetWarrantyCard.tsx +++ b/src/components/Assets/AssetWarrantyCard.tsx @@ -31,7 +31,7 @@ export default function AssetWarrantyCard(props: { asset: AssetData }) { }, [isCopied]); return ( -
+
{asset.manufacturer}
@@ -78,7 +78,10 @@ export default function AssetWarrantyCard(props: { asset: AssetData }) { ["Phone", asset.support_phone, "l-phone"], ["Email", asset.support_email, "l-envelope"], ].map((item) => ( -
+
{item[1] && ( <>
diff --git a/src/components/Common/AuthorizedButton.tsx b/src/components/Common/AuthorizedButton.tsx new file mode 100644 index 00000000000..2b611a0c9f2 --- /dev/null +++ b/src/components/Common/AuthorizedButton.tsx @@ -0,0 +1,19 @@ +import { Button, ButtonProps } from "@headlessui/react"; + +import AuthorizedChild from "@/CAREUI/misc/AuthorizedChild"; + +import { AuthorizedElementProps } from "@/Utils/AuthorizeFor"; + +export const AuthorizedButton: React.FC< + AuthorizedElementProps & ButtonProps +> = ({ authorizeFor = () => true, ...props }) => { + return ( + + {({ isAuthorized }) => ( + + )} + + ); +}; diff --git a/src/components/Common/AvatarEditModal.tsx b/src/components/Common/AvatarEditModal.tsx index bcc61819fde..dc044264c4d 100644 --- a/src/components/Common/AvatarEditModal.tsx +++ b/src/components/Common/AvatarEditModal.tsx @@ -114,20 +114,25 @@ const AvatarEditModal = ({ }; const uploadAvatar = async () => { - if (!selectedFile) { - closeModal(); - return; - } + try { + if (!selectedFile) { + closeModal(); + return; + } - setIsProcessing(true); - setIsCaptureImgBeingUploaded(true); - await handleUpload(selectedFile, () => { - setSelectedFile(undefined); - setPreview(undefined); - setPreviewImage(null); + setIsProcessing(true); + setIsCaptureImgBeingUploaded(true); + await handleUpload(selectedFile, () => { + setSelectedFile(undefined); + setPreview(undefined); + setPreviewImage(null); + setIsCaptureImgBeingUploaded(false); + setIsProcessing(false); + }); + } finally { setIsCaptureImgBeingUploaded(false); setIsProcessing(false); - }); + } }; const deleteAvatar = async () => { diff --git a/src/components/Common/Breadcrumbs.tsx b/src/components/Common/Breadcrumbs.tsx index c2c4aa57446..f55edd4ccf6 100644 --- a/src/components/Common/Breadcrumbs.tsx +++ b/src/components/Common/Breadcrumbs.tsx @@ -3,7 +3,19 @@ import { useState } from "react"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbList, + BreadcrumbSeparator, +} from "@/components/ui/breadcrumb"; import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; import useAppHistory from "@/hooks/useAppHistory"; @@ -69,76 +81,84 @@ export default function Breadcrumbs({ className={classNames("text-sm font-normal", crumb.style)} >
- - {isLastItem ? ( - {crumb.name} - ) : ( - - )} + + {isLastItem && {crumb.name}}
); }; return ( - + ); } diff --git a/src/components/Common/DateInputV2.tsx b/src/components/Common/DateInputV2.tsx index 20027a004d2..26c6a702c50 100644 --- a/src/components/Common/DateInputV2.tsx +++ b/src/components/Common/DateInputV2.tsx @@ -89,15 +89,18 @@ const DateInputV2: React.FC = ({ ); break; case "month": - setDatePickerHeaderDate((prev) => - dayjs(prev).subtract(1, "year").toDate(), - ); + setDatePickerHeaderDate((prev) => { + const newDate = dayjs(prev).subtract(1, "year").toDate(); + if (min && newDate < min) { + return new Date(min.getFullYear(), min.getMonth(), 1); + } + return newDate; + }); break; case "year": - setDatePickerHeaderDate((prev) => - dayjs(prev).subtract(1, "year").toDate(), - ); - setYear((prev) => dayjs(prev).subtract(10, "year").toDate()); + if (!min || year.getFullYear() - 10 >= min.getFullYear()) { + setYear((prev) => dayjs(prev).subtract(10, "year").toDate()); + } break; } }; @@ -108,11 +111,18 @@ const DateInputV2: React.FC = ({ setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "month").toDate()); break; case "month": - setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); + setDatePickerHeaderDate((prev) => { + const newDate = dayjs(prev).add(1, "year").toDate(); + if (max && newDate > max) { + return new Date(max.getFullYear(), max.getMonth(), 1); + } + return newDate; + }); break; case "year": - setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); - setYear((prev) => dayjs(prev).add(10, "year").toDate()); + if (!max || year.getFullYear() + 10 <= max.getFullYear()) { + setYear((prev) => dayjs(prev).add(10, "year").toDate()); + } break; } }; @@ -209,6 +219,33 @@ const DateInputV2: React.FC = ({ return true; }; + const isMonthWithinConstraints = (month: number) => { + const year = datePickerHeaderDate.getFullYear(); + + if (min && year < min.getFullYear()) return false; + if (max && year > max.getFullYear()) return false; + + const firstDay = new Date(year, month, 1); + const lastDay = new Date(year, month + 1, 0); + if (min && lastDay < min) return false; + if (max && firstDay > max) return false; + + return true; + }; + + const isYearWithinConstraints = (year: number) => { + if (min && year < min.getFullYear()) return false; + if (max && year > max.getFullYear()) return false; + + const yearStart = new Date(year, 0, 1); + const yearEnd = new Date(year, 11, 31); + + if (min && yearEnd < min) return false; + if (max && yearStart > max) return false; + + return true; + }; + const isSelectedMonth = (month: number) => month === datePickerHeaderDate.getMonth(); @@ -216,25 +253,48 @@ const DateInputV2: React.FC = ({ year === datePickerHeaderDate.getFullYear(); const setMonthValue = (month: number) => () => { - setDatePickerHeaderDate( - new Date( + if (isMonthWithinConstraints(month)) { + const lastDayOfMonth = new Date( datePickerHeaderDate.getFullYear(), - month, - datePickerHeaderDate.getDate(), - ), - ); - setType("date"); + month + 1, + 0, + ).getDate(); + const newDate = Math.min(datePickerHeaderDate.getDate(), lastDayOfMonth); + setDatePickerHeaderDate( + new Date(datePickerHeaderDate.getFullYear(), month, newDate), + ); + setType("date"); + } else { + Notification.Error({ + msg: outOfLimitsErrorMessage ?? "Cannot select month out of range", + }); + } }; - + //min and max setting for year const setYearValue = (year: number) => () => { - setDatePickerHeaderDate( - new Date( + if (isYearWithinConstraints(year)) { + const newDate = new Date( year, datePickerHeaderDate.getMonth(), datePickerHeaderDate.getDate(), - ), - ); - setType("date"); + ); + if (min && year === min.getFullYear() && newDate < min) { + setDatePickerHeaderDate( + new Date(min.getFullYear(), min.getMonth(), min.getDate()), + ); + } else if (max && year === max.getFullYear() && newDate > max) { + setDatePickerHeaderDate( + new Date(max.getFullYear(), max.getMonth(), max.getDate()), + ); + } else { + setDatePickerHeaderDate(newDate); + } + setType("date"); + } else { + Notification.Error({ + msg: outOfLimitsErrorMessage ?? "Cannot select year out of range", + }); + } }; useEffect(() => { @@ -288,7 +348,7 @@ const DateInputV2: React.FC = ({ const right = popOverX > viewportWidth - (allowTime ? 420 : 300); const top = popOverY > viewportHeight - 400; - return `${right ? "md:-translate-x-1/2" : ""} ${top ? "md:-translate-y-[calc(100%+50px)]" : ""}`; + return `${right ? "sm:-translate-x-1/2" : ""} ${top ? "md:-translate-y-[calc(100%+50px)]" : ""} ${right ? "max-sm:-translate-x-1/2" : ""} ${top ? "max-sm:-translate-y-[calc(100%+50px)]" : ""}`.trim(); }; const domValue = useValueInjectionObserver({ @@ -331,6 +391,7 @@ const DateInputV2: React.FC = ({ data-scribe-ignore className={`cui-input-base cursor-pointer disabled:cursor-not-allowed ${className}`} placeholder={placeholder ?? t("select_date")} + title={placeholder} value={value ? dayjs(value).format(dateFormat) : ""} />
@@ -371,23 +432,62 @@ const DateInputV2: React.FC = ({
- + {type === "date" && ( + + )} + {type === "month" && ( + + )} + + {type === "year" && ( + + )}
{type === "date" && ( @@ -411,23 +511,62 @@ const DateInputV2: React.FC = ({

- + {type === "date" && ( + + )} + {type === "month" && ( + + )} + + {type === "year" && ( + + )}
{type === "date" && ( @@ -510,10 +649,12 @@ const DateInputV2: React.FC = ({ key={i} id={`month-${i}`} className={classNames( - "w-1/4 cursor-pointer rounded-lg px-2 py-4 text-center text-sm font-semibold", - value && isSelectedMonth(i) - ? "bg-primary-500 text-white" - : "text-secondary-700 hover:bg-secondary-300", + "w-1/4 rounded-lg px-2 py-4 text-center text-sm font-semibold", + isSelectedMonth(i) + ? "bg-primary-500 text-white cursor-pointer" + : isMonthWithinConstraints(i) + ? "text-secondary-700 hover:bg-secondary-300 cursor-pointer" + : "!text-secondary-400 !cursor-not-allowed", )} onClick={setMonthValue(i)} > @@ -533,16 +674,18 @@ const DateInputV2: React.FC = ({ {Array(12) .fill(null) .map((_, i) => { - const y = year.getFullYear() - 11 + i; + const y = year.getFullYear() - 10 + i; return (
diff --git a/src/components/Common/Sidebar/Sidebar.tsx b/src/components/Common/Sidebar/Sidebar.tsx index 423d0d6f18b..e6d9edbc2dd 100644 --- a/src/components/Common/Sidebar/Sidebar.tsx +++ b/src/components/Common/Sidebar/Sidebar.tsx @@ -6,12 +6,7 @@ import { useTranslation } from "react-i18next"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; import SlideOver from "@/CAREUI/interactive/SlideOver"; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip"; +import { TooltipComponent, TooltipProvider } from "@/components/ui/tooltip"; import { ShrinkedSidebarItem, @@ -243,24 +238,19 @@ const ToggleShrink = ({ shrinked, toggle }: ToggleShrinkProps) => { const { t } = useTranslation(); return ( - - - - - -

{shrinked ? t("expand_sidebar") : t("collapse_sidebar")}

-
-
+ + +
); }; diff --git a/src/components/Facility/ConsultationCard.tsx b/src/components/Facility/ConsultationCard.tsx index a2ede9f2667..d67926a3c6b 100644 --- a/src/components/Facility/ConsultationCard.tsx +++ b/src/components/Facility/ConsultationCard.tsx @@ -30,6 +30,7 @@ export const ConsultationCard = (props: ConsultationProps) => { : !itemData.current_bed ? t("assign_bed") : t("switch_bed"); + return ( <> { )}
- -
- {dayjs(itemData.created_date).format("DD/MM/YYYY")} +
+
+ +
+ {dayjs(itemData.created_date).format("DD/MM/YYYY")} +
+
+ + {/* Patient Status Section */} +
+
+ {t("patient_status")} +
+ +
{itemData.is_kasp && ( diff --git a/src/components/Facility/DischargedPatientsList.tsx b/src/components/Facility/DischargedPatientsList.tsx index 4fb8910f7a1..74bb6d95626 100644 --- a/src/components/Facility/DischargedPatientsList.tsx +++ b/src/components/Facility/DischargedPatientsList.tsx @@ -1,5 +1,5 @@ import { Link, navigate } from "raviger"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import CountBlock from "@/CAREUI/display/Count"; @@ -11,13 +11,11 @@ import PaginatedList from "@/CAREUI/misc/PaginatedList"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; +import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; import SortDropdownMenu from "@/components/Common/SortDropdown"; import Tabs from "@/components/Common/Tabs"; import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; import { ICD11DiagnosisModel } from "@/components/Facility/models"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import SearchInput from "@/components/Form/SearchInput"; import { DIAGNOSES_FILTER_LABELS, DiagnosesFilterKey, @@ -52,16 +50,79 @@ const DischargedPatientsList = ({ pathParams: { id: facility_external_id }, }); - const { qParams, updateQuery, advancedFilter, FilterBadges, updatePage } = - useFilters({ - limit: 12, - cacheBlacklist: [ - "name", - "patient_no", - "phone_number", - "emergency_phone_number", - ], - }); + const { + qParams, + updateQuery, + advancedFilter, + FilterBadges, + updatePage, + clearSearch, + } = useFilters({ + limit: 12, + cacheBlacklist: [ + "name", + "patient_no", + "phone_number", + "emergency_phone_number", + ], + }); + + const searchOptions = [ + { + key: "name", + label: "Name", + type: "text" as const, + placeholder: "search_by_patient_name", + value: qParams.name || "", + shortcutKey: "n", + }, + { + key: "patient_no", + label: "IP/OP No", + type: "text" as const, + placeholder: "search_by_patient_no", + value: qParams.patient_no || "", + shortcutKey: "u", + }, + { + key: "phone_number", + label: "Phone Number", + type: "phone" as const, + placeholder: "Search_by_phone_number", + value: qParams.phone_number || "", + shortcutKey: "p", + }, + { + key: "emergency_contact_number", + label: "Emergency Contact Phone Number", + type: "phone" as const, + placeholder: "search_by_emergency_phone_number", + value: qParams.emergency_phone_number || "", + shortcutKey: "e", + }, + ]; + + const handleSearch = useCallback( + (key: string, value: string) => { + const isValidPhoneNumber = (val: string) => + val.length >= 13 || val === ""; + + const updatedQuery = { + phone_number: + key === "phone_number" && isValidPhoneNumber(value) + ? value + : undefined, + name: key === "name" ? value : undefined, + patient_no: key === "patient_no" ? value : undefined, + emergency_phone_number: + key === "emergency_contact_number" && isValidPhoneNumber(value) + ? value + : undefined, + }; + updateQuery(updatedQuery); + }, + [updateQuery], + ); useEffect(() => { if (!qParams.phone_number && phone_number.length >= 13) { @@ -200,56 +261,11 @@ const DischargedPatientsList = ({ }); }; - const queryField = (name: string, defaultValue?: T) => { - return { - name, - value: qParams[name] || defaultValue, - onChange: (e: FieldChangeEvent) => updateQuery({ [e.name]: e.value }), - className: "grow w-full mb-2", - }; - }; const [diagnoses, setDiagnoses] = useState([]); const [phone_number, setPhoneNumber] = useState(""); - const [phoneNumberError, setPhoneNumberError] = useState(""); const [emergency_phone_number, setEmergencyPhoneNumber] = useState(""); - const [emergencyPhoneNumberError, setEmergencyPhoneNumberError] = - useState(""); const [count, setCount] = useState(0); - - const setPhoneNum = (phone_number: string) => { - setPhoneNumber(phone_number); - if (phone_number.length >= 13) { - setPhoneNumberError(""); - updateQuery({ phone_number }); - return; - } - - if (phone_number === "+91" || phone_number === "") { - setPhoneNumberError(""); - qParams.phone_number && updateQuery({ phone_number: null }); - return; - } - - setPhoneNumberError("Enter a valid number"); - }; - - const setEmergencyPhoneNum = (emergency_phone_number: string) => { - setEmergencyPhoneNumber(emergency_phone_number); - if (emergency_phone_number.length >= 13) { - setEmergencyPhoneNumberError(""); - updateQuery({ emergency_phone_number }); - return; - } - - if (emergency_phone_number === "+91" || emergency_phone_number === "") { - setEmergencyPhoneNumberError(""); - qParams.emergency_phone_number && - updateQuery({ emergency_phone_number: null }); - return; - } - - setEmergencyPhoneNumberError("Enter a valid number"); - }; + const [isLoading, setIsLoading] = useState(false); return ( } > -
-
-
- -
-
-
-
-
- - -
-
- setPhoneNum(e.value)} - error={phoneNumberError} - types={["mobile", "landline"]} - /> - setEmergencyPhoneNum(e.value)} - error={emergencyPhoneNumberError} - types={["mobile", "landline"]} - /> -
-
+
+
+
+
setCount(query.data?.count || 0)} + queryCB={(query) => { + setCount(query.data?.count || 0); + setIsLoading(query.loading); + }} initialPage={qParams.page} onPageChange={updatePage} > diff --git a/src/components/Facility/DoctorVideoSlideover.tsx b/src/components/Facility/DoctorVideoSlideover.tsx index 61c7a63c17b..98058fd4bfb 100644 --- a/src/components/Facility/DoctorVideoSlideover.tsx +++ b/src/components/Facility/DoctorVideoSlideover.tsx @@ -1,4 +1,5 @@ -import React, { useState } from "react"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; import CareIcon, { IconName } from "@/CAREUI/icons/CareIcon"; import SlideOver from "@/CAREUI/interactive/SlideOver"; @@ -17,6 +18,7 @@ import routes from "@/Utils/request/api"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; import { classNames, + copyToClipboard, formatName, isUserOnline, relativeTime, @@ -238,6 +240,9 @@ function UserListItem({ user }: { user: UserAnnotatedWithGroup }) { } } + const { t } = useTranslation(); + const [copied, setCopied] = useState(false); + return (
{ + onClick={(e) => { e.stopPropagation(); - await navigator.clipboard.writeText( - user?.alt_phone_number || "", - ); + copyToClipboard(user?.alt_phone_number || ""); + setCopied(true); + setTimeout(() => setCopied(false), 2500); }} > - Copy Phone number + {t("copy_phone_number")} - + {user.alt_phone_number} diff --git a/src/components/Facility/FacilityCard.tsx b/src/components/Facility/FacilityCard.tsx index fbebe8a8d0a..be00a21ece0 100644 --- a/src/components/Facility/FacilityCard.tsx +++ b/src/components/Facility/FacilityCard.tsx @@ -6,8 +6,11 @@ import { useTranslation } from "react-i18next"; import Chip from "@/CAREUI/display/Chip"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import { Button } from "@/components/ui/button"; +import { TooltipComponent, TooltipProvider } from "@/components/ui/tooltip"; + import { Avatar } from "@/components/Common/Avatar"; -import ButtonV2, { Cancel, Submit } from "@/components/Common/ButtonV2"; +import { Cancel, Submit } from "@/components/Common/ButtonV2"; import DialogModal from "@/components/Common/Dialog"; import { FacilityModel } from "@/components/Facility/models"; import TextAreaFormField from "@/components/Form/FormFields/TextAreaFormField"; @@ -98,33 +101,44 @@ export const FacilityCard = (props: { > {facility.name} -
0.85 ? "justify-center rounded-md border border-red-600 bg-red-500 p-1 font-bold text-white" : "text-secondary-700"}`} - > - - {t("live_patients_total_beds")} - {" "} - -
- {t("occupancy")}: {facility.patient_count} /{" "} - {facility.bed_count}{" "} -
-
+ + +
+ 0.85 + ? "justify-center rounded-md border border-red-600 bg-red-500 p-1 font-bold text-white" + : "text-secondary-700" + }`} + > + +
+ {t("occupancy")}: {facility.patient_count} /{" "} + {facility.bed_count} +
+
+
+
- - - {t("view_cns")} - + + + {t("view_cns")} + +
@@ -222,38 +236,81 @@ export const FacilityCard = (props: {
{["DistrictAdmin", "StateAdmin"].includes(userType) && ( - setNotifyModalFor(facility.id)} - > - - Notify - + + + + + )} - - - - {t("view_faciliy")} - - - - - {t("view_patients")} - + + + + + + + + + + {/*
*/}
diff --git a/src/components/Facility/FacilityHome.tsx b/src/components/Facility/FacilityHome.tsx index 1808a1087ee..7881a43123d 100644 --- a/src/components/Facility/FacilityHome.tsx +++ b/src/components/Facility/FacilityHome.tsx @@ -28,11 +28,7 @@ import { FieldLabel } from "@/components/Form/FormFields/FormField"; import useAuthUser from "@/hooks/useAuthUser"; import useSlug from "@/hooks/useSlug"; -import { - FACILITY_FEATURE_TYPES, - LocalStorageKeys, - USER_TYPES, -} from "@/common/constants"; +import { FACILITY_FEATURE_TYPES, USER_TYPES } from "@/common/constants"; import { PLUGIN_Component } from "@/PluginEngine"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; @@ -42,6 +38,7 @@ import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import uploadFile from "@/Utils/request/uploadFile"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { getAuthorizationHeader } from "@/Utils/request/utils"; import { sleep } from "@/Utils/utils"; import { patientRegisterAuth } from "../Patient/PatientRegister"; @@ -121,10 +118,7 @@ export const FacilityHome = ({ facilityId }: Props) => { url, formData, "POST", - { - Authorization: - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), - }, + { Authorization: getAuthorizationHeader() }, async (xhr: XMLHttpRequest) => { if (xhr.status === 200) { await sleep(1000); diff --git a/src/components/Facility/PatientNotesList.tsx b/src/components/Facility/PatientNotesList.tsx index dcf84e8b708..8db68395744 100644 --- a/src/components/Facility/PatientNotesList.tsx +++ b/src/components/Facility/PatientNotesList.tsx @@ -58,18 +58,10 @@ const PatientNotesList = (props: PatientNotesProps) => { }; useEffect(() => { - if (reload) { + if (reload || thread) { fetchNotes(); } - }, [reload]); - - useEffect(() => { - fetchNotes(); - }, [thread]); - - useEffect(() => { - setReload(true); - }, []); + }, [reload, thread]); const handleNext = () => { if (state.cPage < state.totalPages) { diff --git a/src/components/Form/FormFields/AutocompleteMultiselect.tsx b/src/components/Form/FormFields/AutocompleteMultiselect.tsx index 3c537dffa32..8dad2f3ff0d 100644 --- a/src/components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/components/Form/FormFields/AutocompleteMultiselect.tsx @@ -171,16 +171,18 @@ export const AutocompleteMutliSelect = ( {!props.disabled && ( -
+
val.option) ? "-top-5" : ""}`} + > {props.isLoading ? ( ) : ( )}
diff --git a/src/components/Form/FormFields/DateFormField.tsx b/src/components/Form/FormFields/DateFormField.tsx index e867c24dd51..a3d26fa5554 100644 --- a/src/components/Form/FormFields/DateFormField.tsx +++ b/src/components/Form/FormFields/DateFormField.tsx @@ -37,7 +37,10 @@ const DateFormField = (props: Props) => { return ( ( onClick={() => handleDelete(props.item.id)} className="w-full text-xl text-red-500 hover:text-red-700 disabled:grayscale md:w-auto" > - {" "} + {t("remove")} )} diff --git a/src/components/Kanban/Board.tsx b/src/components/Kanban/Board.tsx index 337f98f1caf..d311cdd17bd 100644 --- a/src/components/Kanban/Board.tsx +++ b/src/components/Kanban/Board.tsx @@ -4,12 +4,13 @@ import { Droppable, OnDragEndResponder, } from "@hello-pangea/dnd"; -import { ReactNode, RefObject, useEffect, useRef, useState } from "react"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { ReactNode, RefObject, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import request from "@/Utils/request/request"; +import { callApi } from "@/Utils/request/query"; import { QueryRoute } from "@/Utils/request/types"; import { QueryOptions } from "@/Utils/request/useQuery"; @@ -57,7 +58,7 @@ export default function KanbanBoard(
-
+
{props.sections.map((section, i) => ( @@ -74,6 +75,12 @@ export default function KanbanBoard( ); } +interface QueryResponse { + results: T[]; + next: string | null; + count: number; +} + export function KanbanSection( props: Omit, "sections" | "onDragEnd"> & { section: KanbanBoardProps["sections"][number]; @@ -81,107 +88,105 @@ export function KanbanSection( }, ) { const { section } = props; - const [offset, setOffset] = useState(0); - const [pages, setPages] = useState([]); - const [fetchingNextPage, setFetchingNextPage] = useState(false); - const [hasMore, setHasMore] = useState(true); - const [totalCount, setTotalCount] = useState(); - - const options = section.fetchOptions(section.id); const sectionRef = useRef(null); const defaultLimit = 14; const { t } = useTranslation(); - // should be replaced with useInfiniteQuery when we move over to react query - - const fetchNextPage = async (refresh: boolean = false) => { - if (!refresh && (fetchingNextPage || !hasMore)) return; - if (refresh) setPages([]); - const offsetToUse = refresh ? 0 : offset; - setFetchingNextPage(true); - const res = await request(options.route, { - ...options.options, - query: { ...options.options?.query, offsetToUse, limit: defaultLimit }, - }); - const newPages = refresh ? [] : [...pages]; - const page = Math.floor(offsetToUse / defaultLimit); - if (res.error) return; - newPages[page] = (res.data as any).results; - setPages(newPages); - setHasMore(!!(res.data as any)?.next); - setTotalCount((res.data as any)?.count); - setOffset(offsetToUse + defaultLimit); - setFetchingNextPage(false); + const fetchPage = async ({ pageParam = 0 }) => { + const options = section.fetchOptions(section.id); + try { + const data = await callApi(options.route, { + ...options.options, + queryParams: { + ...options.options?.query, + offset: pageParam, + limit: defaultLimit, + }, + }); + return data as QueryResponse; + } catch (error) { + console.error("Error fetching section data:", error); + return { results: [], next: null, count: 0 }; + } }; - const items = pages.flat(); + const { + data, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + isLoading, + refetch, + } = useInfiniteQuery({ + queryKey: [section.id], + queryFn: fetchPage, + getNextPageParam: (lastPage, pages) => { + if (!lastPage.next) return undefined; + return pages.length * defaultLimit; + }, + initialPageParam: 0, + }); - useEffect(() => { - const onBoardReachEnd = async () => { - const sectionElementHeight = - sectionRef.current?.getBoundingClientRect().height; - const scrolled = props.boardRef.current?.scrollTop; - // if user has scrolled 3/4th of the current items - if ( - scrolled && - sectionElementHeight && - scrolled > sectionElementHeight * (3 / 4) - ) { - fetchNextPage(); - } - }; - - props.boardRef.current?.addEventListener("scroll", onBoardReachEnd); - return () => - props.boardRef.current?.removeEventListener("scroll", onBoardReachEnd); - }, [props.boardRef, fetchingNextPage, hasMore]); + const items = data?.pages?.flatMap((page) => page.results || []) ?? []; + const totalCount = data?.pages[0]?.count ?? 0; useEffect(() => { - fetchNextPage(true); - }, [props.section]); + refetch(); + }, [section.id, refetch]); return ( - {(provided) => ( + {(provided, _snapshot) => (
{section.title}
- {typeof totalCount === "undefined" ? "..." : totalCount} + {isLoading ? "..." : totalCount}
-
- {!fetchingNextPage && totalCount === 0 && ( +
{ + const target = e.target as HTMLDivElement; + if ( + target.scrollTop + target.clientHeight >= + target.scrollHeight - 100 + ) { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + } + }} + > + {!isLoading && items.length === 0 && (
{t("no_results_found")}
)} - {items - .filter((item) => item) - .map((item, i) => ( - - {(provided) => ( -
- {props.itemRender(item)} -
- )} -
- ))} - {fetchingNextPage && ( + {items.map((item, index) => ( + + {(provided, _snapshot) => ( +
+ {props.itemRender(item)} +
+ )} +
+ ))} + {provided.placeholder} + {isFetchingNextPage && (
)}
diff --git a/src/components/Patient/DailyRounds.tsx b/src/components/Patient/DailyRounds.tsx index f0c78522e40..cf43a5adab9 100644 --- a/src/components/Patient/DailyRounds.tsx +++ b/src/components/Patient/DailyRounds.tsx @@ -411,7 +411,7 @@ export const DailyRounds = (props: any) => { ); } else { navigate( - `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/log_updates/${obj.id}/update`, + `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/log_updates/${obj.id}/critical_care/update`, ); } } diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx index e5bed5abfee..95811be7361 100644 --- a/src/components/Patient/PatientDetailsTab/Demography.tsx +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -3,27 +3,26 @@ 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 AuthorizedChild from "@/CAREUI/misc/AuthorizedChild"; import { Button } from "@/components/ui/button"; +import { InsuranceDetailsCard } from "@/components/Patient/InsuranceDetailsCard"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; +import { parseOccupation } from "@/components/Patient/PatientHome"; +import { AssignedToObjectModel } from "@/components/Patient/models"; + import useAuthUser from "@/hooks/useAuthUser"; import { GENDER_TYPES } from "@/common/constants"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; import useTanStackQueryInstead 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(); @@ -64,9 +63,7 @@ export const Demography = (props: PatientProps) => { const { data: insuranceDetials } = useTanStackQueryInstead( routes.hcx.policies.list, { - query: { - patient: id, - }, + query: { patient: id }, }, ); @@ -358,23 +355,6 @@ export const Demography = (props: PatientProps) => {
-
-
- {t("patient_status")} -
-
- -
-
{({ isAuthorized }) => ( diff --git a/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx b/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx index b81009efa74..9ad679f8e9a 100644 --- a/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx +++ b/src/components/Patient/PatientDetailsTab/EncounterHistory.tsx @@ -1,56 +1,20 @@ -import React, { useEffect, useState } from "react"; +import React 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 { PatientProps } from "@/components/Patient/PatientDetailsTab"; -import useAuthUser from "@/hooks/useAuthUser"; - -import { triggerGoal } from "@/Integrations/Plausible"; import routes from "@/Utils/request/api"; -import useTanStackQueryInstead 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 { patientData, id, refetch } = props; const { t } = useTranslation(); - const { loading: isLoading, refetch } = useTanStackQueryInstead( - 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 ( { const { patientData, facilityId, id } = props; diff --git a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx index 7ee828868fc..b16059abe46 100644 --- a/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx +++ b/src/components/Patient/PatientDetailsTab/ImmunisationRecords.tsx @@ -5,18 +5,17 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import { Button } from "@/components/ui/button"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; +import { PatientModel } from "@/components/Patient/models"; import { UserModel } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; import { ADMIN_USER_TYPES } from "@/common/constants"; +import * as Notification from "@/Utils/Notifications"; 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; diff --git a/src/components/Patient/PatientDetailsTab/Notes.tsx b/src/components/Patient/PatientDetailsTab/Notes.tsx index 646e97d3bd5..4fccf7a1119 100644 --- a/src/components/Patient/PatientDetailsTab/Notes.tsx +++ b/src/components/Patient/PatientDetailsTab/Notes.tsx @@ -1,5 +1,5 @@ import { t } from "i18next"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -11,6 +11,7 @@ import { PatientNotesModel, } from "@/components/Facility/models"; import AutoExpandingTextInputFormField from "@/components/Form/FormFields/AutoExpandingTextInputFormField"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; import useAuthUser from "@/hooks/useAuthUser"; import { useMessageListener } from "@/hooks/useMessageListener"; @@ -18,19 +19,13 @@ import { useMessageListener } from "@/hooks/useMessageListener"; import { PATIENT_NOTES_THREADS } from "@/common/constants"; import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import * as Notification from "@/Utils/Notifications"; 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 PatientNotes = (props: PatientProps) => { + const { patientData, id: patientId, facilityId } = props; const authUser = useAuthUser(); const [thread, setThread] = useState( @@ -39,7 +34,6 @@ const PatientNotes = (props: PatientNotesProps) => { : PATIENT_NOTES_THREADS.Doctors, ); - const [patientActive, setPatientActive] = useState(true); const [noteField, setNoteField] = useState(""); const [reload, setReload] = useState(false); const [reply_to, setReplyTo] = useState( @@ -84,26 +78,6 @@ const PatientNotes = (props: PatientNotesProps) => { } }; - 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 ( @@ -161,7 +135,7 @@ const PatientNotes = (props: PatientNotesProps) => { errorClassName="hidden" innerClassName="pr-10" placeholder={t("notes_placeholder")} - disabled={!patientActive} + disabled={!patientData.is_active} /> { className="absolute right-2" ghost size="small" - disabled={!patientActive} + disabled={!patientData.is_active} authorizeFor={NonReadOnlyUsers} > diff --git a/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx b/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx index b9f63da5512..6bd1bb5bbb7 100644 --- a/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx +++ b/src/components/Patient/PatientDetailsTab/ShiftingHistory.tsx @@ -4,6 +4,8 @@ import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; import ButtonV2 from "@/components/Common/ButtonV2"; +import { PatientProps } from "@/components/Patient/PatientDetailsTab"; +import { PatientModel } from "@/components/Patient/models"; import { formatFilter } from "@/components/Resource/ResourceCommons"; import ShiftingTable from "@/components/Shifting/ShiftingTable"; @@ -13,9 +15,6 @@ import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; import routes from "@/Utils/request/api"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { PatientProps } from "."; -import { PatientModel } from "../models"; - const ShiftingHistory = (props: PatientProps) => { const { patientData, facilityId, id } = props; const { t } = useTranslation(); diff --git a/src/components/Patient/PatientDetailsTab/index.tsx b/src/components/Patient/PatientDetailsTab/index.tsx index 6f4b7ecc982..55c292438ca 100644 --- a/src/components/Patient/PatientDetailsTab/index.tsx +++ b/src/components/Patient/PatientDetailsTab/index.tsx @@ -1,15 +1,16 @@ -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 ShiftingHistory from "./ShiftingHistory"; +import EncounterHistory from "@/components/Patient/PatientDetailsTab//EncounterHistory"; +import { HealthProfileSummary } from "@/components/Patient/PatientDetailsTab//HealthProfileSummary"; +import { ImmunisationRecords } from "@/components/Patient/PatientDetailsTab//ImmunisationRecords"; +import PatientNotes from "@/components/Patient/PatientDetailsTab//Notes"; +import ShiftingHistory from "@/components/Patient/PatientDetailsTab//ShiftingHistory"; +import { Demography } from "@/components/Patient/PatientDetailsTab/Demography"; +import { PatientModel } from "@/components/Patient/models"; export interface PatientProps { facilityId: string; id: string; patientData: PatientModel; + refetch: () => void; } export const patientTabs = [ diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx index 253bd823ac0..b50c73ece88 100644 --- a/src/components/Patient/PatientHome.tsx +++ b/src/components/Patient/PatientHome.tsx @@ -2,6 +2,12 @@ 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 { Button } from "@/components/ui/button"; + +import { AuthorizedButton } from "@/components/Common/AuthorizedButton"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; import UserAutocomplete from "@/components/Common/UserAutocompleteFormField"; @@ -13,16 +19,14 @@ import { OCCUPATION_TYPES, } 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 useTanStackQueryInstead 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 useTanStackQueryInstead from "../../Utils/request/useQuery"; import { formatDateTime, formatName, @@ -33,7 +37,6 @@ import { 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"; @@ -215,29 +218,55 @@ export const PatientHome = (props: {
- {patientData?.is_active && - (!patientData?.last_consultation || - patientData?.last_consultation?.discharge_date) && ( -
- - navigate( - `/facility/${patientData?.facility}/patient/${id}/consultation`, - ) - } - > - - - {t("add_consultation")} - - -
+ {facilityId === + patientData.facility_object?.id.toString() && + patientData?.is_active && ( + <> + {patientData?.last_consultation && + !patientData?.last_consultation.discharge_date ? ( +
+ +
+ ) : ( +
+ +
+ )} + )}
@@ -363,7 +392,7 @@ export const PatientHome = (props: { className="tooltip-text tooltip-bottom flex flex-col text-xs font-medium" role="tooltip" > - {skillsQuery.data?.results.map((skill) => ( + {skillsQuery.data?.results.map((skill: any) => (
  • {skill.skill_object.name}
  • @@ -447,21 +476,21 @@ export const PatientHome = (props: { facilityId={facilityId || ""} id={id} patientData={patientData} + refetch={refetch} /> )}
    -
    +
    {t("actions")}
    - navigate(`/patient/${id}/investigation_reports`) } @@ -473,13 +502,12 @@ export const PatientHome = (props: { /> {t("investigations_summary")} - +
    - navigate( `/facility/${patientData?.facility}/patient/${id}/files`, @@ -490,18 +518,17 @@ export const PatientHome = (props: { {t("view_update_patient_files")} - +
    - {NonReadOnlyUsers && ( + {NonReadOnlyUsers(authUser.user_type) && (
    - setOpenAssignVolunteerDialog(true)} disabled={false} authorizeFor={NonReadOnlyUsers} className="w-full bg-white font-semibold text-green-800 hover:bg-secondary-200" - size="large" > {" "} @@ -509,15 +536,14 @@ export const PatientHome = (props: { ? t("update_volunteer") : t("assign_to_volunteer")} - +
    )}
    - - +
    @@ -657,7 +683,7 @@ export const PatientHome = (props: { {patientData.last_consultation?.new_discharge_reason === DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id && (
    - {t("death_report")} - +
    )}
    diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index ad2c5314d22..f117c43b65e 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -662,7 +662,7 @@ export default function PatientInfoCard(props: PatientInfoCardProps) { ].map( (action: any, i) => action[3] && ( -
    +
    - diff --git a/src/components/Resource/ResourceDetails.tsx b/src/components/Resource/ResourceDetails.tsx index 5aefdf394a1..c7d83536c31 100644 --- a/src/components/Resource/ResourceDetails.tsx +++ b/src/components/Resource/ResourceDetails.tsx @@ -1,9 +1,12 @@ import { navigate } from "raviger"; import { useState } from "react"; +import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; +import PrintPreview from "@/CAREUI/misc/PrintPreview"; + +import { Button } from "@/components/ui/button"; -import ButtonV2 from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; import CommentSection from "@/components/Resource/ResourceCommentSection"; @@ -14,6 +17,7 @@ import { classNames, formatDateTime, formatName } from "@/Utils/utils"; export default function ResourceDetails(props: { id: string }) { const [isPrintMode, setIsPrintMode] = useState(false); + const { t } = useTranslation(); const { data, loading } = useTanStackQueryInstead(routes.getResourceDetails, { pathParams: { id: props.id }, onResponse: ({ res, data }) => { @@ -63,12 +67,15 @@ export default function ResourceDetails(props: { id: string }) {
    {" "} - Date and Time:{" "} + {t("date_and_time")}:{" "} {formatDateTime(data.created_date)}
    - Unique Id: + + {" "} + {t("unique_id")}:{" "} + {data.id}
    @@ -96,50 +103,50 @@ export default function ResourceDetails(props: { id: string }) { data.status === "ON HOLD" ? (
    - The request for resource (details below) placed by yourself is{" "} + {t("the_request_for_resources_placed_by_yourself_is")}{" "} {data.status}
    ) : data.status === "APPROVED" ? (
    - The request for resource (details below) placed by yourself is{" "} + {t("the_request_for_resources_placed_by_yourself_is")}{" "} {data.status}
    ) : (
    - The request for resource (details below) placed by yourself is{" "} + {t("the_request_for_resources_placed_by_yourself_is")}{" "} APPROVED - and the status of request is{" "} + {t("and_the_status_of_request_is")}{" "} {data.status}
    )}
    - Title of Request:{" "} + {t("title_of_request")}:{" "} {data.title || "--"}
    - Reason of Request{" "} + {t("request_reason")}:{" "} {data.reason || "--"}
    - Quantity Requested:{" "} + {t("quantity_requested")}:{" "} {data.requested_quantity}
    - QUANTITY APPROVED:{" "} + {t("quantity_approved")}:{" "} {data.assigned_quantity}
    @@ -188,186 +195,191 @@ export default function ResourceDetails(props: { id: string }) { } return ( - +
    {isPrintMode ? (
    -
    - window.print()}> - Print - Approval Letter - - setIsPrintMode(false)} variant="secondary"> - Close - -
    - {ApprovalLetter(data)} + + {ApprovalLetter(data)} +
    ) : ( -
    -
    - setIsPrintMode(true)}> - Approval - Letter - -
    - {data.assigned_to_object && ( -
    -
    -
    -

    - - Assigned to: {formatName(data.assigned_to_object)} -{" "} - {data.assigned_to_object.user_type} - -

    -
    -
    + +
    +
    +
    - )} -
    -
    -
    {data.title || "--"}
    - - Update Status/Details - -
    - -
    -
    - Status: - - {data.status} - -
    -
    - - Category:{" "} - - {data.category || "--"} -
    -
    - - Subcategory:{" "} - - {data.sub_category || "--"} -
    -
    - - Required Quantity:{" "} - - {data.requested_quantity || "--"} -
    -
    - - Contact person at the current facility:{" "} - - {data.refering_facility_contact_name || "--"} -
    -
    - - Approved Quantity:{" "} - - {data.assigned_quantity} + {data.assigned_to_object && ( +
    +
    +
    +

    + + Assigned to: {formatName(data.assigned_to_object)} -{" "} + {data.assigned_to_object.user_type} + +

    +
    +
    -
    - - Contact person number:{" "} - - {data.refering_facility_contact_number ? ( - - {data.refering_facility_contact_number} + )} +
    + -
    - - {" "} - Is emergency:{" "} - - - {" "} - {data.emergency ? "yes" : "no"} - +
    -
    -
    Reason:
    -
    {data.reason || "--"}
    +
    +
    + + Status:{" "} + + + {data.status} + +
    +
    + + Category:{" "} + + {data.category || "--"} +
    +
    + + Subcategory:{" "} + + {data.sub_category || "--"} +
    +
    + + Required Quantity:{" "} + + {data.requested_quantity || "--"} +
    +
    + + Contact person at the current facility:{" "} + + {data.refering_facility_contact_name || "--"} +
    +
    + + Approved Quantity:{" "} + + {data.assigned_quantity} +
    +
    + + Contact person number:{" "} + + {data.refering_facility_contact_number ? ( + + {data.refering_facility_contact_number} + + ) : ( + "--" + )} +
    +
    + + {" "} + Is emergency:{" "} + + + {" "} + {data.emergency ? "yes" : "no"} + +
    + +
    +
    Reason:
    +
    {data.reason || "--"}
    +
    -
    -

    Audit Log

    +

    Audit Log

    -
    -
    -
    - Created -
    -
    -
    - {data.created_by_object && formatName(data.created_by_object)} +
    +
    +
    + Created
    -
    - {data.created_date && formatDateTime(data.created_date)} +
    +
    + {data.created_by_object && + formatName(data.created_by_object)} +
    +
    + {data.created_date && formatDateTime(data.created_date)} +
    -
    -
    -
    - Last Edited -
    -
    -
    - {formatName(data.last_edited_by_object)} +
    +
    + Last Edited
    -
    - {data.modified_date && formatDateTime(data.modified_date)} +
    +
    + {formatName(data.last_edited_by_object)} +
    +
    + {data.modified_date && formatDateTime(data.modified_date)} +
    -
    -
    -
    -

    Origin Facility

    - - {showFacilityCard(data.origin_facility_object)} -
    -
    -

    Resource Approving Facility

    +
    +
    +

    Origin Facility

    - {showFacilityCard(data.approving_facility_object)} -
    - {data.assigned_facility_object && ( + {showFacilityCard(data.origin_facility_object)} +
    -

    Request Fulfilling Facility

    +

    Resource Approving Facility

    - {showFacilityCard(data.assigned_facility_object)} + {showFacilityCard(data.approving_facility_object)}
    - )} -
    -
    -

    Comments

    - + {data.assigned_facility_object && ( +
    +

    Request Fulfilling Facility

    + + {showFacilityCard(data.assigned_facility_object)} +
    + )} +
    +
    +

    Comments

    + +
    -
    + )} - +
    ); } diff --git a/src/components/Resource/ResourceList.tsx b/src/components/Resource/ResourceList.tsx index 2b44599f120..0175c9d63ea 100644 --- a/src/components/Resource/ResourceList.tsx +++ b/src/components/Resource/ResourceList.tsx @@ -9,7 +9,7 @@ import { Button } from "@/components/ui/button"; import { ExportButton } from "@/components/Common/Export"; import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; +import PageTitle from "@/components/Common/PageTitle"; import { ResourceModel } from "@/components/Facility/models"; import SearchInput from "@/components/Form/SearchInput"; import BadgesList from "@/components/Resource/ResourceBadges"; @@ -194,45 +194,55 @@ export default function ListView() { }; return ( - { - const { data } = await request(routes.downloadResourceRequests, { - query: { ...appliedFilters, csv: true }, - }); - return data ?? null; - }} - filenamePrefix="resource_requests" - /> - } - breadcrumbs={false} - options={ - <> -
    -
    - updateQuery({ [e.name]: e.value })} - placeholder={t("search_resource")} - /> -
    +
    +
    +
    + { + const { data } = await request( + routes.downloadResourceRequests, + { + query: { ...appliedFilters, csv: true }, + }, + ); + return data ?? null; + }} + filenamePrefix="resource_requests" + /> + } + breadcrumbs={false} + /> +
    + +
    + updateQuery({ [e.name]: e.value })} + placeholder={t("search_resource")} + className="w-full md:w-60" + /> -
    - advancedFilter.setShow(true)} />
    - - } - > +
    +
    @@ -282,6 +292,6 @@ export default function ListView() { showResourceStatus={true} key={window.location.search} /> - +
    ); } diff --git a/src/components/Shifting/ShiftingBoard.tsx b/src/components/Shifting/ShiftingBoard.tsx index cd9f4300fd4..068820d3311 100644 --- a/src/components/Shifting/ShiftingBoard.tsx +++ b/src/components/Shifting/ShiftingBoard.tsx @@ -124,7 +124,11 @@ export default function BoardView() { />
    - diff --git a/src/components/Shifting/ShiftingList.tsx b/src/components/Shifting/ShiftingList.tsx index 3bf071f1cc2..e9051b1449b 100644 --- a/src/components/Shifting/ShiftingList.tsx +++ b/src/components/Shifting/ShiftingList.tsx @@ -8,7 +8,7 @@ import { Button } from "@/components/ui/button"; import { ExportButton } from "@/components/Common/Export"; import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; +import PageTitle from "@/components/Common/PageTitle"; import SearchInput from "@/components/Form/SearchInput"; import BadgesList from "@/components/Shifting/ShiftingBadges"; import { formatFilter } from "@/components/Shifting/ShiftingCommons"; @@ -49,46 +49,52 @@ export default function ListView() { }); return ( - { - const { data } = await request(routes.downloadShiftRequests, { - query: { ...formatFilter(qParams), csv: true }, - }); - return data ?? null; - }} - filenamePrefix="shift_requests" - /> - } - breadcrumbs={false} - options={ - <> -
    +
    +
    +
    + { + const { data } = await request(routes.downloadShiftRequests, { + query: { ...formatFilter(qParams), csv: true }, + }); + return data ?? null; + }} + filenamePrefix="shift_requests" + /> + } + breadcrumbs={false} + /> +
    +
    + updateQuery({ [e.name]: e.value })} + placeholder={t("search_patient")} + className="w-full md:w-60" + /> -
    - updateQuery({ [e.name]: e.value })} - placeholder={t("search_patient")} - /> -
    - -
    - advancedFilter.setShow(true)} />
    - - } - > +
    +
    +
    @@ -121,6 +127,6 @@ export default function ListView() { {...advancedFilter} key={window.location.search} /> - +
    ); } diff --git a/src/components/Shifting/ShiftingTable.tsx b/src/components/Shifting/ShiftingTable.tsx index de186a13d56..f7d75e13db2 100644 --- a/src/components/Shifting/ShiftingTable.tsx +++ b/src/components/Shifting/ShiftingTable.tsx @@ -134,8 +134,8 @@ export default function ShiftingTable(props: {
    -
    -
    +
    +
    -
    +
    -
    +
    {shift.origin_facility_object?.name}
    @@ -197,17 +197,17 @@ export default function ShiftingTable(props: {
    -
    +
    {shift.assigned_facility_external || shift.assigned_facility_object?.name || t("yet_to_be_decided")}
    -
    +
    navigate(`/shifting/${shift.external_id}`)} variant="secondary" @@ -219,7 +219,7 @@ export default function ShiftingTable(props: { {shift.status === "COMPLETED" && shift.assigned_facility && (
    )}
    - +
    @@ -274,6 +277,7 @@ export default function LinkedFacilities({ onClick={() => handleOnClick("clear_home_facility", homeFacility) } + disabled={authUser.user_type == "Volunteer"} title={t("clear_home_facility")} aria-label={t("clear_home_facility")} > diff --git a/src/components/Users/ManageUsers.tsx b/src/components/Users/ManageUsers.tsx index 0851a186728..d28a0aeea52 100644 --- a/src/components/Users/ManageUsers.tsx +++ b/src/components/Users/ManageUsers.tsx @@ -7,13 +7,8 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; import ButtonV2 from "@/components/Common/ButtonV2"; -import CircularProgress from "@/components/Common/CircularProgress"; -import { FacilitySelect } from "@/components/Common/FacilitySelect"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; -import Pagination from "@/components/Common/Pagination"; -import { FacilityModel } from "@/components/Facility/models"; -import UnlinkFacilityDialog from "@/components/Users/UnlinkFacilityDialog"; import UserFilter from "@/components/Users/UserFilter"; import UserListView from "@/components/Users/UserListAndCard"; @@ -22,11 +17,8 @@ import useFilters from "@/hooks/useFilters"; import { USER_TYPES } from "@/common/constants"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { classNames } from "@/Utils/utils"; export default function ManageUsers() { const { t } = useTranslation(); @@ -183,314 +175,3 @@ export default function ManageUsers() { ); } - -export function UserFacilities(props: { user: any }) { - const { t } = useTranslation(); - const { user } = props; - const username = user.username; - const limit = 20; - const [isLoading, setIsLoading] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const [offset, setOffset] = useState(0); - const [totalCount, setTotalCount] = useState(0); - const [facility, setFacility] = useState(null); - const [unlinkFacilityData, setUnlinkFacilityData] = useState<{ - show: boolean; - userName: string; - facility?: FacilityModel; - isHomeFacility: boolean; - }>({ show: false, userName: "", facility: undefined, isHomeFacility: false }); - const authUser = useAuthUser(); - const hideUnlinkFacilityModal = () => { - setUnlinkFacilityData({ - show: false, - facility: undefined, - userName: "", - isHomeFacility: false, - }); - }; - - const { - data: userFacilities, - loading: userFacilitiesLoading, - refetch: refetchUserFacilities, - } = useTanStackQueryInstead(routes.userListFacility, { - pathParams: { username }, - query: { - limit, - offset, - }, - onResponse: ({ res, data }) => { - if (res?.status === 200 && data) { - setTotalCount(data.count); - } - }, - }); - - const handlePagination = (page: number, limit: number) => { - const offset = (page - 1) * limit; - setCurrentPage(page); - setOffset(offset); - }; - - const updateHomeFacility = async (username: string, facility: any) => { - setIsLoading(true); - const { res } = await request(routes.partialUpdateUser, { - pathParams: { username }, - body: { home_facility: facility.id.toString() }, - }); - if (!res?.ok) { - Notification.Error({ - msg: "Error while updating Home facility", - }); - } else { - user.home_facility_object = facility; - Notification.Success({ - msg: "Home Facility updated successfully", - }); - } - await refetchUserFacilities(); - setIsLoading(false); - }; - - const handleUnlinkFacilitySubmit = async () => { - setIsLoading(true); - if (unlinkFacilityData.isHomeFacility) { - const { res } = await request(routes.clearHomeFacility, { - pathParams: { username }, - }); - - if (!res?.ok) { - Notification.Error({ - msg: "Error while clearing home facility", - }); - } else { - user.home_facility_object = null; - Notification.Success({ - msg: "Home Facility cleared successfully", - }); - } - } else { - const { res } = await request(routes.deleteUserFacility, { - pathParams: { username }, - body: { facility: unlinkFacilityData?.facility?.id?.toString() }, - }); - if (!res?.ok) { - Notification.Error({ - msg: "Error while unlinking home facility", - }); - } else { - Notification.Success({ - msg: "Facility unlinked successfully", - }); - } - } - await refetchUserFacilities(); - hideUnlinkFacilityModal(); - setIsLoading(false); - }; - - const addFacility = async (username: string, facility: any) => { - setIsLoading(true); - const { res } = await request(routes.addUserFacility, { - pathParams: { username }, - body: { facility: facility.id.toString() }, - }); - - if (!res?.ok) { - Notification.Error({ - msg: "Error while linking facility", - }); - } else { - Notification.Success({ - msg: "Facility linked successfully", - }); - } - await refetchUserFacilities(); - setIsLoading(false); - setFacility(null); - }; - - return ( -
    - {unlinkFacilityData.show && ( - - )} - -
    - - addFacility(username, facility)} - > - {t("add")} - -
    -
    - - {isLoading || userFacilitiesLoading ? ( -
    - -
    - ) : ( -
    - {/* Home Facility section */} - {user?.home_facility_object && ( -
    -
    -
    - {user?.home_facility_object?.name} - - - Home Facility - - {(["DistrictAdmin", "StateAdmin"].includes( - authUser.user_type, - ) || - username === authUser.username) && ( -
    - -
    - )} -
    -
    -
    - )} - - {/* Linked Facilities section */} - {!!userFacilities?.results.length && ( -
    -
    - {userFacilities.results.map( - (facility: FacilityModel, i: number) => { - if (user?.home_facility_object?.id === facility.id) { - // skip if it's a home facility - return null; - } - return ( -
    -
    - {facility.name} - {(["DistrictAdmin", "StateAdmin"].includes( - authUser.user_type, - ) || - username === authUser.username) && ( -
    - {authUser.user_type !== "Nurse" && ( - - )} - -
    - )} -
    -
    - ); - }, - )} -
    - {totalCount > limit && ( -
    - -
    - )} -
    - )} - {!user?.home_facility_object && !userFacilities?.results.length && ( -
    -
    - No linked facilities -
    -

    - {t("no_linked_facilities")} -

    -
    - )} -
    - )} -
    - ); -} diff --git a/src/components/Users/UserAddEditForm.tsx b/src/components/Users/UserAddEditForm.tsx index 00793ce8fff..52317d17efa 100644 --- a/src/components/Users/UserAddEditForm.tsx +++ b/src/components/Users/UserAddEditForm.tsx @@ -258,7 +258,7 @@ const UserAddEditForm = (props: UserProps) => { ...fieldMappings, user_type: formData.user_type, password: formData.password, - facilities: formData.facilities ? formData.facilities : undefined, + facilities: formData.facilities ?? undefined, home_facility: formData.home_facility ?? undefined, username: formData.username, state: formData.state, @@ -1265,7 +1265,9 @@ const UserAddEditForm = (props: UserProps) => { defaults={userData ? state.form : initForm} validate={validateForm} onCancel={editUser ? handleCancel : () => goBack()} - onSubmit={editUser ? handleEditSubmit : handleSubmit} + onSubmit={() => + editUser ? handleEditSubmit(state.form) : handleSubmit(state.form) + } onDraftRestore={(newState) => { dispatch({ type: "set_state", state: newState }); }} diff --git a/src/components/Users/UserAvatar.tsx b/src/components/Users/UserAvatar.tsx index db3620b34aa..9930a2e35b7 100644 --- a/src/components/Users/UserAvatar.tsx +++ b/src/components/Users/UserAvatar.tsx @@ -9,30 +9,34 @@ import Loading from "@/components/Common/Loading"; import useAuthUser from "@/hooks/useAuthUser"; -import { LocalStorageKeys } from "@/common/constants"; - import * as Notification from "@/Utils/Notifications"; import { showAvatarEdit } from "@/Utils/permissions"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import uploadFile from "@/Utils/request/uploadFile"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; +import { getAuthorizationHeader } from "@/Utils/request/utils"; import { formatDisplayName, sleep } from "@/Utils/utils"; -export default function UserAvatar({ username }: { username: string }) { +export default function UserAvatar({ + username, + refetchUserData, +}: { + username: string; + refetchUserData?: () => void; +}) { const { t } = useTranslation(); const [editAvatar, setEditAvatar] = useState(false); const authUser = useAuthUser(); - const { - data: userData, - loading: isLoading, - refetch: refetchUserData, - } = useTanStackQueryInstead(routes.getUserDetails, { - pathParams: { - username: username, + const { data: userData, loading: isLoading } = useTanStackQueryInstead( + routes.getUserDetails, + { + pathParams: { + username: username, + }, }, - }); + ); if (isLoading || !userData) { return ; @@ -43,18 +47,15 @@ export default function UserAvatar({ username }: { username: string }) { formData.append("profile_picture", file); const url = `${careConfig.apiUrl}/api/v1/users/${userData.username}/profile_picture/`; - uploadFile( + await uploadFile( url, formData, "POST", - { - Authorization: - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), - }, + { Authorization: getAuthorizationHeader() }, async (xhr: XMLHttpRequest) => { if (xhr.status === 200) { await sleep(1000); - refetchUserData(); + refetchUserData?.(); Notification.Success({ msg: t("avatar_updated_success") }); setEditAvatar(false); } @@ -72,7 +73,7 @@ export default function UserAvatar({ username }: { username: string }) { }); if (res?.ok) { Notification.Success({ msg: "Profile picture deleted" }); - await refetchUserData(); + refetchUserData?.(); setEditAvatar(false); } else { onError(); diff --git a/src/components/Users/UserHome.tsx b/src/components/Users/UserHome.tsx index 8c12ad2c561..f441e66370d 100644 --- a/src/components/Users/UserHome.tsx +++ b/src/components/Users/UserHome.tsx @@ -38,6 +38,8 @@ export default function UserHome(props: UserHomeProps) { if (!username) { username = authUser.username; } + const loggedInUser = username === authUser.username; + const urlPrefix = loggedInUser ? "/user" : `/users/${username}`; const { loading, refetch: refetchUserDetails } = useTanStackQueryInstead( routes.getUserDetails, @@ -96,7 +98,11 @@ export default function UserHome(props: UserHomeProps) { <>
    {t(`USERMANAGEMENT_TAB__${p}`)} diff --git a/src/components/Users/UserProfile.tsx b/src/components/Users/UserProfile.tsx deleted file mode 100644 index 4dd98635510..00000000000 --- a/src/components/Users/UserProfile.tsx +++ /dev/null @@ -1,1034 +0,0 @@ -import careConfig from "@careConfig"; -import { FormEvent, useEffect, useReducer, useState } from "react"; -import { useTranslation } from "react-i18next"; - -import CareIcon from "@/CAREUI/icons/CareIcon"; - -import AvatarEditModal from "@/components/Common/AvatarEditModal"; -import AvatarEditable from "@/components/Common/AvatarEditable"; -import ButtonV2, { Submit } from "@/components/Common/ButtonV2"; -import LanguageSelector from "@/components/Common/LanguageSelector"; -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; -import UpdatableApp, { checkForUpdate } from "@/components/Common/UpdatableApp"; -import { PhoneNumberValidator } from "@/components/Form/FieldValidators"; -import DateFormField from "@/components/Form/FormFields/DateFormField"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; -import TextFormField from "@/components/Form/FormFields/TextFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import { validateRule } from "@/components/Users/UserAddEditForm"; -import { - GenderType, - SkillModel, - UpdatePasswordForm, -} from "@/components/Users/models"; - -import useAuthUser, { useAuthContext } from "@/hooks/useAuthUser"; - -import { GENDER_TYPES, LocalStorageKeys } from "@/common/constants"; -import { validateEmailAddress } from "@/common/validation"; - -import * as Notification from "@/Utils/Notifications"; -import dayjs from "@/Utils/dayjs"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import uploadFile from "@/Utils/request/uploadFile"; -import useTanStackQueryInstead from "@/Utils/request/useQuery"; -import { - dateQueryString, - formatDate, - formatDisplayName, - isValidUrl, - parsePhoneNumber, - sleep, -} from "@/Utils/utils"; - -type EditForm = { - firstName: string; - lastName: string; - date_of_birth: Date | null | string; - gender: GenderType; - email: string; - video_connect_link: string | undefined; - phoneNumber: string; - altPhoneNumber: string; - user_type: string | undefined; - qualification: string | undefined; - doctor_experience_commenced_on: number | string | undefined; - doctor_medical_council_registration: string | undefined; - weekly_working_hours: string | null | undefined; -}; -type ErrorForm = { - firstName: string; - lastName: string; - date_of_birth: string | null; - gender: string; - email: string; - video_connect_link: string | undefined; - phoneNumber: string; - altPhoneNumber: string; - user_type: string | undefined; - qualification: string | undefined; - doctor_experience_commenced_on: number | string | undefined; - doctor_medical_council_registration: string | undefined; - weekly_working_hours: string | undefined; -}; -type State = { - form: EditForm; - errors: ErrorForm; -}; -type Action = - | { type: "set_form"; form: EditForm } - | { type: "set_error"; errors: ErrorForm }; - -const initForm: EditForm = { - firstName: "", - lastName: "", - date_of_birth: null, - gender: "Male", - video_connect_link: "", - email: "", - phoneNumber: "", - altPhoneNumber: "", - user_type: "", - qualification: undefined, - doctor_experience_commenced_on: undefined, - doctor_medical_council_registration: undefined, - weekly_working_hours: undefined, -}; - -const initError: ErrorForm = Object.assign( - {}, - ...Object.keys(initForm).map((k) => ({ [k]: "" })), -); - -const initialState: State = { - form: { ...initForm }, - errors: { ...initError }, -}; - -const editFormReducer = (state: State, action: Action) => { - switch (action.type) { - case "set_form": { - return { - ...state, - form: action.form, - }; - } - case "set_error": { - return { - ...state, - errors: action.errors, - }; - } - } -}; - -export default function UserProfile() { - const { t } = useTranslation(); - const { signOut, refetchUser } = useAuthContext(); - const [states, dispatch] = useReducer(editFormReducer, initialState); - const [editAvatar, setEditAvatar] = useState(false); - const [updateStatus, setUpdateStatus] = useState({ - isChecking: false, - isUpdateAvailable: false, - }); - const [dirty, setDirty] = useState(false); - - const authUser = useAuthUser(); - - const [changePasswordForm, setChangePasswordForm] = useState<{ - username: string; - old_password: string; - new_password_1: string; - new_password_2: string; - }>({ - username: authUser.username, - old_password: "", - new_password_1: "", - new_password_2: "", - }); - - const [changePasswordErrors] = useState<{ - old_password: string; - password_confirmation: string; - }>({ - old_password: "", - password_confirmation: "", - }); - - const [showEdit, setShowEdit] = useState(false); - - useEffect(() => { - const formData: EditForm = { - firstName: authUser.first_name, - lastName: authUser.last_name, - date_of_birth: authUser.date_of_birth || null, - gender: authUser.gender || "Male", - email: authUser.email, - video_connect_link: authUser.video_connect_link, - phoneNumber: authUser.phone_number?.toString() || "", - altPhoneNumber: authUser.alt_phone_number?.toString() || "", - user_type: authUser.user_type, - qualification: authUser.qualification, - doctor_experience_commenced_on: dayjs().diff( - dayjs(authUser.doctor_experience_commenced_on), - "years", - ), - doctor_medical_council_registration: - authUser.doctor_medical_council_registration, - weekly_working_hours: authUser.weekly_working_hours, - }; - dispatch({ - type: "set_form", - form: formData, - }); - setDirty(false); - }, [authUser]); - - const { data: skillsView, loading: isSkillsLoading } = - useTanStackQueryInstead(routes.userListSkill, { - pathParams: { username: authUser.username }, - }); - - const validatePassword = (password: string) => { - const rules = [ - { - test: (p: string) => p.length >= 8, - message: "Password should be at least 8 characters long", - }, - { - test: (p: string) => p !== p.toUpperCase(), - message: "Password should contain at least 1 lowercase letter", - }, - { - test: (p: string) => p !== p.toLowerCase(), - message: "Password should contain at least 1 uppercase letter", - }, - { - test: (p: string) => /\d/.test(p), - message: "Password should contain at least 1 number", - }, - ]; - return rules.map((rule) => - validateRule(rule.test(password), rule.message, !password), - ); - }; - - const validateNewPassword = (password: string) => { - if ( - password.length < 8 || - !/\d/.test(password) || - password === password.toUpperCase() || - password === password.toLowerCase() - ) { - return false; - } - return true; - }; - - const validateForm = () => { - const errors = { ...initError }; - let invalidForm = false; - Object.keys(states.form).forEach((field) => { - switch (field) { - case "firstName": - case "lastName": - case "gender": - if (!states.form[field]) { - errors[field] = t("field_required"); - invalidForm = true; - } - return; - case "date_of_birth": - if (!states.form[field]) { - errors[field] = "Enter a valid date of birth"; - invalidForm = true; - } else if ( - !dayjs(states.form[field]).isValid() || - dayjs(states.form[field]).isAfter(dayjs().subtract(17, "year")) - ) { - errors[field] = "Enter a valid date of birth"; - invalidForm = true; - } - return; - case "phoneNumber": - // eslint-disable-next-line no-case-declarations - const phoneNumber = parsePhoneNumber(states.form[field]); - - // eslint-disable-next-line no-case-declarations - let is_valid = false; - if (phoneNumber) { - is_valid = PhoneNumberValidator()(phoneNumber) === undefined; - } - - if (!states.form[field] || !is_valid) { - errors[field] = "Please enter valid phone number"; - invalidForm = true; - } - return; - case "altPhoneNumber": - // eslint-disable-next-line no-case-declarations - let alt_is_valid = false; - if (states.form[field] && states.form[field] !== "+91") { - const altPhoneNumber = parsePhoneNumber(states.form[field]); - if (altPhoneNumber) { - alt_is_valid = - PhoneNumberValidator(["mobile"])(altPhoneNumber) === undefined; - } - } - - if ( - states.form[field] && - states.form[field] !== "+91" && - !alt_is_valid - ) { - errors[field] = "Please enter valid mobile number"; - invalidForm = true; - } - return; - case "email": - if (!states.form[field]) { - errors[field] = t("field_required"); - invalidForm = true; - } else if (!validateEmailAddress(states.form[field])) { - errors[field] = "Enter a valid email address"; - invalidForm = true; - } - return; - case "doctor_experience_commenced_on": - if (states.form.user_type === "Doctor" && !states.form[field]) { - errors[field] = t("field_required"); - invalidForm = true; - } else if ( - (states.form.user_type === "Doctor" && - Number(states.form.doctor_experience_commenced_on) >= 100) || - Number(states.form.doctor_experience_commenced_on) < 0 - ) { - errors[field] = - "Doctor experience should be at least 0 years and less than 100 years."; - invalidForm = true; - } - return; - case "qualification": - if ( - (states.form.user_type === "Doctor" || - states.form.user_type === "Nurse") && - !states.form[field] - ) { - errors[field] = t("field_required"); - invalidForm = true; - } - return; - case "doctor_medical_council_registration": - if (states.form.user_type === "Doctor" && !states.form[field]) { - errors[field] = t("field_required"); - invalidForm = true; - } - return; - case "weekly_working_hours": - if ( - states.form[field] && - (Number(states.form[field]) < 0 || - Number(states.form[field]) > 168 || - !/^\d+$/.test(states.form[field] ?? "")) - ) { - errors[field] = - "Average weekly working hours must be a number between 0 and 168"; - invalidForm = true; - } - return; - case "video_connect_link": - if (states.form[field]) { - if (isValidUrl(states.form[field]) === false) { - errors[field] = "Please enter a valid url"; - invalidForm = true; - } - } - return; - } - }); - dispatch({ type: "set_error", errors }); - return !invalidForm; - }; - - const handleFieldChange = (event: FieldChangeEvent) => { - dispatch({ - type: "set_form", - form: { ...states.form, [event.name]: event.value }, - }); - setDirty(true); - }; - - const getDate = (value: any) => - value && dayjs(value).isValid() && dayjs(value).toDate(); - - const fieldProps = (name: string) => { - return { - name, - id: name, - value: (states.form as any)[name], - onChange: handleFieldChange, - error: (states.errors as any)[name], - }; - }; - - const handleSubmit = async (e: FormEvent) => { - e.preventDefault(); - const validForm = validateForm(); - if (validForm) { - const data = { - username: authUser.username, - first_name: states.form.firstName, - last_name: states.form.lastName, - email: states.form.email, - video_connect_link: states.form.video_connect_link, - phone_number: parsePhoneNumber(states.form.phoneNumber) ?? "", - alt_phone_number: parsePhoneNumber(states.form.altPhoneNumber) ?? "", - gender: states.form.gender, - date_of_birth: dateQueryString(states.form.date_of_birth), - qualification: - states.form.user_type === "Doctor" || - states.form.user_type === "Nurse" - ? states.form.qualification - : undefined, - doctor_experience_commenced_on: - states.form.user_type === "Doctor" - ? dayjs() - .subtract( - parseInt( - (states.form.doctor_experience_commenced_on as string) ?? - "0", - ), - "years", - ) - .format("YYYY-MM-DD") - : undefined, - doctor_medical_council_registration: - states.form.user_type === "Doctor" - ? states.form.doctor_medical_council_registration - : undefined, - weekly_working_hours: - states.form.weekly_working_hours && - states.form.weekly_working_hours !== "" - ? states.form.weekly_working_hours - : null, - }; - const { res } = await request(routes.partialUpdateUser, { - pathParams: { username: authUser.username }, - body: data, - }); - if (res?.ok) { - Notification.Success({ - msg: "Details updated successfully", - }); - await refetchUser(); - setShowEdit(false); - } - } - }; - - const isLoading = isSkillsLoading; - - if (isLoading) { - return ; - } - - const checkUpdates = async () => { - setUpdateStatus({ ...updateStatus, isChecking: true }); - await new Promise((resolve) => setTimeout(resolve, 500)); - if ((await checkForUpdate()) != null) { - setUpdateStatus({ - isUpdateAvailable: true, - isChecking: false, - }); - } else { - setUpdateStatus({ - isUpdateAvailable: false, - isChecking: false, - }); - Notification.Success({ - msg: "No update available", - }); - } - }; - - const changePassword = async (e: any) => { - e.preventDefault(); - //validating form - if ( - changePasswordForm.new_password_1 !== changePasswordForm.new_password_2 - ) { - Notification.Error({ - msg: "Passwords are different in new password and confirmation password column.", - }); - } else if (!validateNewPassword(changePasswordForm.new_password_1)) { - Notification.Error({ - msg: "Entered New Password is not valid, please check!", - }); - } else if ( - changePasswordForm.new_password_1 === changePasswordForm.old_password - ) { - Notification.Error({ - msg: "New password is same as old password, Please enter a different new password.", - }); - } else { - const form: UpdatePasswordForm = { - old_password: changePasswordForm.old_password, - username: authUser.username, - new_password: changePasswordForm.new_password_1, - }; - const { res, data, error } = await request(routes.updatePassword, { - body: form, - }); - if (res?.ok) { - Notification.Success({ msg: data?.message }); - } else if (!error) { - Notification.Error({ - msg: "There was some error. Please try again in some time.", - }); - } - setChangePasswordForm({ - ...changePasswordForm, - new_password_1: "", - new_password_2: "", - old_password: "", - }); - } - }; - - const handleAvatarUpload = async (file: File, onError: () => void) => { - const formData = new FormData(); - formData.append("profile_picture", file); - const url = `${careConfig.apiUrl}/api/v1/users/${authUser.username}/profile_picture/`; - - uploadFile( - url, - formData, - "POST", - { - Authorization: - "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), - }, - async (xhr: XMLHttpRequest) => { - if (xhr.status === 200) { - await sleep(1000); - refetchUser(); - Notification.Success({ msg: "Profile picture updated." }); - setEditAvatar(false); - } else { - onError(); - } - }, - null, - () => { - onError(); - }, - ); - }; - - const handleAvatarDelete = async (onError: () => void) => { - const { res } = await request(routes.deleteProfilePicture, { - pathParams: { username: authUser.username }, - }); - if (res?.ok) { - Notification.Success({ msg: "Profile picture deleted" }); - await refetchUser(); - setEditAvatar(false); - } else { - onError(); - } - }; - - return ( - - setEditAvatar(false)} - /> -
    -
    -

    - {t("local_body")}, {t("district")}, {t("state")}{" "} - {t("are_non_editable_fields")}. -

    -
    - setEditAvatar(!editAvatar)} - className="h-20 w-20" - /> -
    -

    - {authUser.first_name} {authUser.last_name} -

    -

    - @{authUser.username} -

    -
    -
    -
    - setShowEdit(!showEdit)} - type="button" - id="edit-cancel-profile-button" - > - {showEdit ? t("cancel") : t("edit_user_profile")} - - - - {t("sign_out")} - -
    -
    -
    - {!showEdit && !isLoading && ( -
    -
    -
    -
    - {t("username")} -
    -
    - {authUser.username || "-"} -
    -
    -
    -
    - {t("phone_number")} -
    -
    - {authUser.phone_number || "-"} -
    -
    - -
    -
    - {t("whatsapp_number")} -
    -
    - {authUser.alt_phone_number || "-"} -
    -
    -
    -
    - {t("email")} -
    -
    - {authUser.email || "-"} -
    -
    -
    -
    - {t("first_name")} -
    -
    - {authUser.first_name || "-"} -
    -
    -
    -
    - {t("last_name")} -
    -
    - {authUser.last_name || "-"} -
    -
    -
    -
    - {t("date_of_birth")} -
    -
    - {authUser.date_of_birth - ? formatDate(authUser.date_of_birth) - : "-"} -
    -
    -
    -
    - {t("access_level")} -
    -
    - - {authUser.user_type || "-"} -
    -
    -
    -
    - {t("gender")} -
    -
    - {authUser.gender || "-"} -
    -
    -
    -
    - {t("local_body")} -
    -
    - {authUser.local_body_object?.name || "-"} -
    -
    -
    -
    - {t("district")} -
    -
    - {authUser.district_object?.name || "-"} -
    -
    -
    -
    - {t("state")} -
    -
    - {authUser.state_object?.name || "-"} -
    -
    -
    -
    - {t("skills")} -
    -
    -
    - {skillsView?.results?.length - ? skillsView.results?.map((skill: SkillModel) => { - return ( - -

    - {skill.skill_object.name} -

    -
    - ); - }) - : "-"} -
    -
    -
    -
    -
    - {t("average_weekly_working_hours")} -
    -
    - {authUser.weekly_working_hours ?? "-"} -
    -
    - -
    -
    - )} - {showEdit && ( -
    -
    -
    -
    -
    - - - - o.text} - optionValue={(o) => o.text} - options={GENDER_TYPES} - /> - - - - {(states.form.user_type === "Doctor" || - states.form.user_type === "Nurse") && ( - - )} - {states.form.user_type === "Doctor" && ( - <> - - - - )} - - -
    -
    -
    - -
    -
    -
    -
    -
    -
    -
    - - setChangePasswordForm({ - ...changePasswordForm, - old_password: e.value, - }) - } - error={changePasswordErrors.old_password} - required - /> -
    - { - setChangePasswordForm({ - ...changePasswordForm, - new_password_1: e.value, - }); - }} - required - /> -
    - {validatePassword(changePasswordForm.new_password_1)} -
    -
    -
    - { - setChangePasswordForm({ - ...changePasswordForm, - new_password_2: e.value, - }); - }} - /> - {changePasswordForm.new_password_2.length > 0 && ( -
    - {validateRule( - changePasswordForm.new_password_1 === - changePasswordForm.new_password_2, - "Confirm password should match the new password", - !changePasswordForm.new_password_2, - )} -
    - )} -
    -
    -
    -
    - -
    -
    -
    -
    - )} -
    -
    - -
    -
    -
    -

    - {t("language_selection")} -

    -

    - {t("set_your_local_language")} -

    -
    -
    -
    - -
    -
    -
    -
    -
    -

    - {t("software_update")} -

    -

    - {t("check_for_available_update")} -

    -
    -
    -
    - {updateStatus.isChecking ? ( - // While checking for updates - -
    - - {t("checking_for_update")} -
    -
    - ) : updateStatus.isUpdateAvailable ? ( - // When an update is available - - -
    - - {t("update_available")} -
    -
    -
    - ) : ( - // Default state to check for updates - -
    - - {t("check_for_update")} -
    -
    - )} -
    -
    -
    - ); -} diff --git a/src/components/Users/UserSoftwareUpdate.tsx b/src/components/Users/UserSoftwareUpdate.tsx new file mode 100644 index 00000000000..5cc685f575d --- /dev/null +++ b/src/components/Users/UserSoftwareUpdate.tsx @@ -0,0 +1,72 @@ +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Button } from "@/components/ui/button"; + +import UpdatableApp, { checkForUpdate } from "@/components/Common/UpdatableApp"; + +import * as Notification from "@/Utils/Notifications"; + +export default function UserSoftwareUpdate() { + const [updateStatus, setUpdateStatus] = useState({ + isChecking: false, + isUpdateAvailable: false, + }); + const { t } = useTranslation(); + + const checkUpdates = async () => { + setUpdateStatus({ ...updateStatus, isChecking: true }); + await new Promise((resolve) => setTimeout(resolve, 500)); + if ((await checkForUpdate()) != null) { + setUpdateStatus({ + isUpdateAvailable: true, + isChecking: false, + }); + } else { + setUpdateStatus({ + isUpdateAvailable: false, + isChecking: false, + }); + Notification.Success({ + msg: "No update available", + }); + } + }; + + return ( + <> + {updateStatus.isChecking ? ( + // While checking for updates + + ) : updateStatus.isUpdateAvailable ? ( + // When an update is available + + + + ) : ( + // Default state to check for updates + + )} + + ); +} diff --git a/src/components/Users/UserSummary.tsx b/src/components/Users/UserSummary.tsx index f75aa066e65..9b703deddbd 100644 --- a/src/components/Users/UserSummary.tsx +++ b/src/components/Users/UserSummary.tsx @@ -15,6 +15,7 @@ import { UserProfessionalInfoView, } from "@/components/Users/UserEditDetails"; import UserResetPassword from "@/components/Users/UserResetPassword"; +import UserSoftwareUpdate from "@/components/Users/UserSoftwareUpdate"; import { BasicInfoDetails, ContactInfoDetails, @@ -200,12 +201,20 @@ export default function UserSummaryTab({ /> )} {authUser.username === userData.username && ( - + <> + + + )} {deletePermitted && (
    diff --git a/src/components/ui/breadcrumb.tsx b/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000000..de505bc5833 --- /dev/null +++ b/src/components/ui/breadcrumb.tsx @@ -0,0 +1,118 @@ +import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons"; +import { Slot } from "@radix-ui/react-slot"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode; + } +>(({ ...props }, ref) =>