diff --git a/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts b/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts index 846b937998b..a3b4906e0ca 100644 --- a/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts @@ -117,14 +117,8 @@ describe("Patient Discharge based on multiple reason", () => { patientDischarge.interceptDischargePatient(); cy.clickSubmitButton("Acknowledge & Submit"); patientDischarge.verifyDischargePatient(); + cy.verifyNotification("Patient Discharged Successfully"); // 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/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index 38ad0c907c4..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); - userProfilePage.interceptUpdateUsers(); - userProfilePage.clickUpdateButton(); - userProfilePage.verifyUpdateUsersResponse(); - 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,14 +81,14 @@ 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); @@ -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 b1968ed2b4c..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); @@ -239,19 +281,6 @@ describe("Manage User", () => { manageUserPage.clickAddSkillButton(usernameforworkinghour); manageUserPage.verifyAddSkillResponse(); manageUserPage.assertSkillInAddedUserSkills(linkedskill); - 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.assertSkillInAddedUserSkills(linkedskill); manageUserPage.clickUnlinkSkill(); manageUserPage.verifyUnlinkSkillModal(); manageUserPage.clickConfirmUnlinkSkill(); @@ -283,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); @@ -291,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", () => { @@ -357,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/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index 027357a0321..a4821466861 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -54,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(); @@ -81,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( @@ -94,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() { @@ -107,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) { @@ -128,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() { @@ -237,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() { @@ -318,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"); } @@ -338,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"); @@ -380,11 +402,6 @@ export class ManageUserPage { verifyAddSkillResponse() { cy.wait("@getUserSkills").its("response.statusCode").should("eq", 200); } - assertSkillInAlreadyLinkedSkills(skillName: string) { - cy.get("#already-linked-skills") - .contains(skillName) - .should("have.length", 1); - } assertSkillIndoctorconnect(skillName: string) { cy.get("#doctor-connect-home-doctor") @@ -409,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", @@ -423,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/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts deleted file mode 100644 index 50959bb7cf7..00000000000 --- a/cypress/pageobject/Users/UserProfilePage.ts +++ /dev/null @@ -1,105 +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); - } - - interceptUpdateUsers() { - cy.intercept("PATCH", "/api/v1/users/*").as("updateUser"); - } - - verifyUpdateUsersResponse() { - cy.wait("@updateUser").its("response.statusCode").should("eq", 200); - } - - 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/package-lock.json b/package-lock.json index dc7abe4ad16..0c68d4ed99d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.45.1", - "@tanstack/react-query": "^5.62.3", + "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.7", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", @@ -58,7 +58,7 @@ "react-google-recaptcha": "^3.1.0", "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", @@ -2865,8 +2865,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", @@ -5805,9 +5807,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", @@ -5825,12 +5827,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", @@ -6793,8 +6795,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", @@ -6847,8 +6851,10 @@ "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" }, @@ -6987,8 +6993,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", @@ -7015,8 +7023,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" @@ -7452,6 +7462,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", @@ -7676,9 +7698,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", @@ -7826,8 +7850,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" } @@ -7992,8 +8018,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" } @@ -8038,7 +8066,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": { @@ -8061,8 +8089,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", @@ -8551,6 +8581,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", @@ -8636,8 +8676,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", @@ -9885,6 +9927,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", @@ -10321,6 +10373,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", @@ -10340,8 +10399,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" }, @@ -10353,8 +10414,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" }, @@ -10366,14 +10429,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": { @@ -10433,8 +10498,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", @@ -10586,6 +10653,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", @@ -10849,8 +10923,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", @@ -10970,8 +11046,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" @@ -11161,7 +11239,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", @@ -13025,8 +13103,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" }, @@ -13041,8 +13121,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" } @@ -14679,8 +14761,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" @@ -14693,8 +14777,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" }, @@ -14706,15 +14792,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" }, @@ -14722,6 +14812,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", @@ -14845,8 +14942,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", @@ -14866,6 +14965,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", @@ -14881,6 +14987,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", @@ -14924,8 +15050,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" }, @@ -15042,8 +15170,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", @@ -15363,7 +15493,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" @@ -15422,9 +15552,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": { @@ -15443,16 +15573,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": { @@ -15783,6 +15929,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", @@ -16130,6 +16358,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", @@ -16244,9 +16505,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", @@ -16254,7 +16515,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" }, @@ -16976,7 +17237,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" @@ -16992,7 +17253,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", @@ -17004,7 +17265,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", @@ -17025,7 +17286,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" @@ -17339,8 +17600,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", @@ -18247,8 +18510,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", @@ -18261,12 +18526,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" } @@ -18275,8 +18579,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", @@ -20655,8 +20961,10 @@ "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" } diff --git a/package.json b/package.json index a32904af6b7..3bc1009db80 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@radix-ui/react-toast": "^1.2.2", "@radix-ui/react-tooltip": "^1.1.6", "@sentry/browser": "^8.45.1", - "@tanstack/react-query": "^5.62.3", + "@tanstack/react-query": "^5.62.8", "@tanstack/react-query-devtools": "^5.62.7", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", @@ -97,7 +97,7 @@ "react-google-recaptcha": "^3.1.0", "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", diff --git a/public/locale/en.json b/public/locale/en.json index b9d5c60539c..758ef1965db 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1046,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", @@ -1539,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/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/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/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 8debee7ac7b..26c6a702c50 100644 --- a/src/components/Common/DateInputV2.tsx +++ b/src/components/Common/DateInputV2.tsx @@ -348,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({ 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/FacilityCard.tsx b/src/components/Facility/FacilityCard.tsx index 2f6ecec204b..be00a21ece0 100644 --- a/src/components/Facility/FacilityCard.tsx +++ b/src/components/Facility/FacilityCard.tsx @@ -6,10 +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"; @@ -103,6 +104,7 @@ export const FacilityCard = (props: {
- - - {t("view_cns")} - + + + {t("view_cns")} + +
@@ -233,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/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/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx index d6e1379cd7e..95811be7361 100644 --- a/src/components/Patient/PatientDetailsTab/Demography.tsx +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -3,7 +3,6 @@ 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"; @@ -356,23 +355,6 @@ export const Demography = (props: PatientProps) => {
-
-
- {t("patient_status")} -
-
- -
-
{({ isAuthorized }) => ( diff --git a/src/components/Patient/PatientHome.tsx b/src/components/Patient/PatientHome.tsx index 1c992445706..b50c73ece88 100644 --- a/src/components/Patient/PatientHome.tsx +++ b/src/components/Patient/PatientHome.tsx @@ -5,19 +5,11 @@ import { useTranslation } from "react-i18next"; import Chip from "@/CAREUI/display/Chip"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import { Avatar } from "@/components/Common/Avatar"; -import ButtonV2 from "@/components/Common/ButtonV2"; +import { Button } from "@/components/ui/button"; + +import { AuthorizedButton } from "@/components/Common/AuthorizedButton"; import ConfirmDialog from "@/components/Common/ConfirmDialog"; -import Loading from "@/components/Common/Loading"; -import Page from "@/components/Common/Page"; import UserAutocomplete from "@/components/Common/UserAutocompleteFormField"; -import { patientTabs } from "@/components/Patient/PatientDetailsTab"; -import { isPatientMandatoryDataFilled } from "@/components/Patient/Utils"; -import { - AssignedToObjectModel, - PatientModel, -} from "@/components/Patient/models"; -import { SkillModel, UserBareMinimum } from "@/components/Users/models"; import useAuthUser from "@/hooks/useAuthUser"; @@ -34,6 +26,7 @@ import dayjs from "@/Utils/dayjs"; import routes from "@/Utils/request/api"; import request from "@/Utils/request/request"; import useTanStackQueryInstead from "@/Utils/request/useQuery"; + import { formatDateTime, formatName, @@ -42,7 +35,14 @@ import { isAntenatal, isPostPartum, relativeDate, -} from "@/Utils/utils"; +} from "../../Utils/utils"; +import { Avatar } from "../Common/Avatar"; +import Loading from "../Common/Loading"; +import Page from "../Common/Page"; +import { SkillModel, UserBareMinimum } from "../Users/models"; +import { patientTabs } from "./PatientDetailsTab"; +import { isPatientMandatoryDataFilled } from "./Utils"; +import { AssignedToObjectModel, PatientModel } from "./models"; export const parseOccupation = (occupation: string | undefined) => { return OCCUPATION_TYPES.find((i) => i.value === occupation)?.text; @@ -218,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 ? ( +
+ +
+ ) : ( +
+ +
+ )} + )}
@@ -366,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}
  • @@ -456,16 +482,15 @@ export const PatientHome = (props: {
    -
    +
    {t("actions")}
    - navigate(`/patient/${id}/investigation_reports`) } @@ -477,13 +502,12 @@ export const PatientHome = (props: { /> {t("investigations_summary")} - +
    - navigate( `/facility/${patientData?.facility}/patient/${id}/files`, @@ -494,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" > {" "} @@ -513,15 +536,14 @@ export const PatientHome = (props: { ? t("update_volunteer") : t("assign_to_volunteer")} - +
    )}
    - - +
    @@ -661,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/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 bdc81cb986d..d5046dac087 100644 --- a/src/components/Users/ManageUsers.tsx +++ b/src/components/Users/ManageUsers.tsx @@ -7,8 +7,6 @@ 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"; @@ -24,11 +22,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(); @@ -230,314 +225,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/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 786e569c4db..00000000000 --- a/src/components/Users/UserProfile.tsx +++ /dev/null @@ -1,1032 +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 } 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 { getAuthorizationHeader } from "@/Utils/request/utils"; -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: getAuthorizationHeader() }, - 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) =>