diff --git a/.cursorrules b/.cursorrules index 2eed3afe459..94ebea1bd57 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,23 +1,35 @@ -Care is a React Typescript Project, built with Vite and styled with TailwindCSS. +You are an expert in TypeScript, React, Shadcn UI, Tailwind. -Care uses a Plugin Architecture. Apps are installed in /apps. +Key Principles -Care uses a custom useQuery hook to fetch data from the API. APIs are defined in the api.tsx file +- Write concise, technical TypeScript code with accurate examples. +- Use functional and declarative programming patterns; avoid classes. +- Prefer iteration and modularization over code duplication. +- Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). -Here's an example of how to use the useQuery hook to fetch data from the API: +Naming Conventions -``` -useQuery from "@/common/hooks/useQuery"; -const { data, loading, error } = useQuery(routes.getFacilityUsers, { - facility_id: "1", -}); +- Use lowercase with dashes for directories (e.g., components/auth-wizard). +- Favor named exports for components. -request from "@/common/utils/request"; -const { res } = await request(routes.partialUpdateAsset, { - pathParams: { external_id: assetId }, - body: data, -}); -``` +TypeScript Usage +- Use TypeScript for all code; prefer interfaces over types. +- Avoid enums; use maps instead. +- Use functional components with TypeScript interfaces. +Syntax and Formatting +- Use the "function" keyword for pure functions. +- Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements. +- Use declarative JSX. + +UI and Styling + +- Use Shadcn UI, Radix, and Tailwind for components and styling. +- Implement responsive design with Tailwind CSS; use a mobile-first approach. + +General Guidelines + +- Care uses a custom useQuery hook to fetch data from the API. (Docs @ /Utils/request/useQuery) +- APIs are defined in the api.tsx file. diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..2125666142e --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto \ No newline at end of file diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 506c2e56f66..3abf95cd4b0 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -18,8 +18,9 @@ jobs: containers: [1, 2, 3, 4] env: REACT_CARE_API_URL: http://localhost:9000 - REACT_ENABLED_APPS: "ohcnetwork/care_hcx_fe@main" + REACT_ENABLED_APPS: "ohcnetwork/care_hcx_fe@main,ohcnetwork/care_abdm_fe@main" REACT_ENABLE_HCX: true + REACT_ENABLE_ABDM: true steps: - name: Checkout 📥 uses: actions/checkout@v3 @@ -135,4 +136,4 @@ jobs: if: ${{ failure() && steps.pr_origin.outputs.is_forked == 'true' }} with: name: cypress-videos - path: cypress/videos \ No newline at end of file + path: cypress/videos diff --git a/.github/workflows/notify-non-core-qn.yml b/.github/workflows/notify-non-core-qn.yml index 7aa59e6e868..07533c320c5 100644 --- a/.github/workflows/notify-non-core-qn.yml +++ b/.github/workflows/notify-non-core-qn.yml @@ -1,37 +1,39 @@ name: Notify Core Team on Non-Core Questions + on: issue_comment: types: [created] + permissions: issues: write pull-requests: write + jobs: notify_core_team: runs-on: ubuntu-latest env: - ALLOWED_USERNAMES: ${{ vars.ALLOWED_USERNAMES }} - QUESTION_KEYWORDS: ${{ vars.QUESTION_KEYWORDS }} - QUESTION_LABELS: ${{ vars.QUESTION_LABELS }} - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + ALLOWED_USERNAMES: ${{ vars.ALLOWED_USERNAMES || '' }} + QUESTION_KEYWORDS: ${{ vars.QUESTION_KEYWORDS || '' }} + QUESTION_LABELS: ${{ vars.QUESTION_LABELS || '' }} + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK || '' }} + steps: - name: Check and Notify - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - console.log('Script started'); - const isOrgMember = (commenter, allowedUsers) => { - return allowedUsers.split(',').map(u => u.trim()).includes(commenter); - }; - const containsQuestionKeywords = (text, keywords) => { - return keywords.split(',').map(k => k.trim()).some(keyword => + const isOrgMember = (commenter, allowedUsers) => + allowedUsers.split(',').map(u => u.trim()).includes(commenter); + + const containsQuestionKeywords = (text, keywords) => + keywords.split(',').map(k => k.trim()).some(keyword => text.toLowerCase().includes(keyword.toLowerCase()) ); - }; + const addLabelsToIssue = async (github, context, labelsString) => { - const labels = labelsString.split(',').map(label => label.trim()).filter(label => label); + const labels = labelsString.split(',').map(label => label.trim()).filter(Boolean); if (labels.length > 0) { - console.log('Adding labels:', labels); await github.rest.issues.addLabels({ owner: context.repo.owner, repo: context.repo.repo, @@ -40,48 +42,47 @@ jobs: }); } }; - const sendSlackNotification = async (webhook, payload) => { + + const sendSlackNotification = async (webhook, commentUrl) => { + const payload = { commentUrl }; const response = await fetch(webhook, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); + throw new Error(`Slack notification failed with status: ${response.status}`); } }; + + const isBot = async (github, commenter) => { + try { + const { data: user } = await github.rest.users.getByUsername({ username: commenter }); + return user.type === 'Bot'; + } catch { + return false; + } + }; + const commenter = context.payload.comment.user.login; - console.log('Commenter:', commenter); - if (!isOrgMember(commenter, process.env.ALLOWED_USERNAMES)) { - const commentBody = context.payload.comment.body; - const sanitizedComment = commentBody - ?.replace(/[^\w\s?]/gi, '') - .toLowerCase(); - console.log('Comment body:', sanitizedComment); - if (containsQuestionKeywords(sanitizedComment, process.env.QUESTION_KEYWORDS)) { - try { - console.log('Adding labels to the issue'); - await addLabelsToIssue(github, context, process.env.QUESTION_LABELS); - console.log('Labels added successfully'); - const issueUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/issues/${context.payload.issue.number}`; - const issueTitle = context.payload.issue.title; - const issueNumber = context.payload.issue.number; - console.log('Issue URL:', issueUrl); - console.log('Issue Title:', issueTitle); - console.log('Issue Number:', issueNumber); - const payload = { - link: issueUrl, - Question: commentBody, - "issue-number": issueNumber, - title: issueTitle, - user: commenter - }; - await sendSlackNotification(process.env.SLACK_WEBHOOK, payload); - console.log('Slack notification sent successfully'); - } catch (error) { - console.error('Workflow failed:', error.message); - core.setFailed(`Workflow failed: ${error.message}`); + const allowedUsers = process.env.ALLOWED_USERNAMES; + const keywords = process.env.QUESTION_KEYWORDS; + const labels = process.env.QUESTION_LABELS; + const webhook = process.env.SLACK_WEBHOOK; + + if (await isBot(github, commenter)) return; + + if (allowedUsers && !isOrgMember(commenter, allowedUsers)) { + const commentBody = context.payload.comment.body.trim(); + const filteredCommentBody = commentBody.split('\n').filter(line => !line.startsWith('>')).join('\n'); + + if (keywords && containsQuestionKeywords(filteredCommentBody, keywords)) { + if (labels) { + await addLabelsToIssue(github, context, labels); + } + if (webhook) { + const commentUrl = context.payload.comment.html_url; + await sendSlackNotification(webhook, commentUrl); } } } - console.log('Script ended'); diff --git a/.github/workflows/thank-you.yml b/.github/workflows/thank-you.yml index 192f71d1a64..b91642dd24f 100644 --- a/.github/workflows/thank-you.yml +++ b/.github/workflows/thank-you.yml @@ -18,26 +18,20 @@ jobs: uses: actions/github-script@v6.3.3 with: script: | - const thankyouNote = 'Your efforts have helped advance digital healthcare and TeleICU systems. :rocket: Thank you for taking the time out to make CARE better. We hope you continue to innovate and contribute; your impact is immense! :raised_hands:' + const thankyouNote = 'Your efforts have helped advance digital healthcare and TeleICU systems. :rocket: Thank you for taking the time out to make CARE better. We hope you continue to innovate and contribute; your impact is immense! :raised_hands:'; const options = { issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - } + }; - const result = await github.rest.issues.get({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - }) - - const { assignees, user } = result.data + const { data : { assignees, user } } = await github.rest.issues.get(options); - const assignees_tagged = assignees.map((user) => '@' + user.login).join(' ') - const owner_tagged = '@' + user.login + const taggedUsers = [...new Set( + assignees.map(u => "@"+u.login).concat("@"+user.login) + )].join(" ") - if (assignees.length == 0) { - await github.rest.issues.createComment({ ...options, body: `${owner_tagged} ${thankyouNote}` }) - } else { - await github.rest.issues.createComment({ ...options, body: `${assignees_tagged} ${owner_tagged} ${thankyouNote}` }) - } + await github.rest.issues.createComment({ + ...options, + body: `${taggedUsers} ${thankyouNote}` + }); diff --git a/crowdin.yml b/crowdin.yml index 0ab1a042711..fdcb6bcd32d 100644 --- a/crowdin.yml +++ b/crowdin.yml @@ -1,5 +1,6 @@ files: - - source: /src/Locale/en/*.json - translation: /src/Locale/%two_letters_code%/%original_file_name% + - source: /public/locale/{{lang}}.json + translation: /public/locale/%two_letters_code%/%original_file_name% bundles: - 2 + diff --git a/cypress/e2e/assets_spec/AssetHomepage.cy.ts b/cypress/e2e/assets_spec/AssetHomepage.cy.ts index dfb3249c243..e19f885db72 100644 --- a/cypress/e2e/assets_spec/AssetHomepage.cy.ts +++ b/cypress/e2e/assets_spec/AssetHomepage.cy.ts @@ -2,112 +2,128 @@ import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; import { pageNavigation } from "pageobject/utils/paginationHelpers"; import { v4 as uuidv4 } from "uuid"; -import { AssetPage } from "../../pageobject/Asset/AssetCreation"; import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; -import { AssetQRScanPage } from "../../pageobject/Asset/AssetQRScan"; -import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; +import { AssetHome } from "../../pageobject/Asset/AssetHome"; import LoginPage from "../../pageobject/Login/LoginPage"; - -describe("Asset Tab", () => { - const assetSearchPage = new AssetSearchPage(); - const assetQRScanPage = new AssetQRScanPage(); - const assetFilters = new AssetFilters(); - const assetPage = new AssetPage(); - const loginPage = new LoginPage(); - const assetName = "Dummy Camera 10"; - const qrCode = uuidv4(); - const serialNumber = Math.floor(Math.random() * 10 ** 10).toString(); - - before(() => { - loginPage.loginAsDistrictAdmin(); - cy.saveLocalStorage(); - }); - - beforeEach(() => { - cy.restoreLocalStorage(); - cy.clearLocalStorage(/filters--.+/); - cy.awaitUrl("/assets"); - }); - - // search for a element - - it("Search Asset Name/QR_ID/Serial_number", () => { - assetSearchPage.typeSearchKeyword(assetName); - assetSearchPage.pressEnter(); - assetSearchPage.verifyBadgeContent(assetName); - assetSearchPage.clickAssetByName(assetName); - assetSearchPage.clickUpdateButton(); - assetSearchPage.clearAndTypeQRCode(qrCode); - assetSearchPage.clearAndTypeSerialNumber(serialNumber); - assetSearchPage.clickAssetSubmitButton(); - assetSearchPage.visitAssetsPage(); - assetSearchPage.typeSearchKeyword(qrCode); - assetSearchPage.pressEnter(); - assetSearchPage.verifyAssetListContains(assetName); - assetSearchPage.verifyBadgeContent(qrCode); - assetSearchPage.typeSearchKeyword(serialNumber); - assetSearchPage.verifyAssetListContains(assetName); - assetSearchPage.verifyBadgeContent(serialNumber); - }); - - // scan a asset qr code - - it("Scan Asset QR", () => { - assetQRScanPage.scanAssetQR(); - }); - - // filter the asset and verify the badges are there - - it("Filter Asset", () => { - assetFilters.filterAssets( - "Dummy Facility 40", - "ACTIVE", - "ONVIF Camera", - "Camera Loc", - ); - advanceFilters.clickAdvancedFiltersButton(); - assetFilters.clickslideoverbackbutton(); // to verify the back button doesn't clear applied filters - assetFilters.assertFacilityText("Dummy Facility 40"); - assetFilters.assertAssetClassText("ONVIF"); - assetFilters.assertStatusText("ACTIVE"); - assetFilters.assertLocationText("Camera Loc"); - advanceFilters.clickAdvancedFiltersButton(); - assetFilters.clearFilters(); - }); - - // Verify the pagination in the page - - it("Next/Previous Page", () => { - pageNavigation.navigateToNextPage(); - pageNavigation.verifyCurrentPageNumber(2); - pageNavigation.navigateToPreviousPage(); - pageNavigation.verifyCurrentPageNumber(1); - }); - - it("Import new asset", () => { - assetPage.selectassetimportbutton(); - assetPage.selectImportOption(); - assetPage.selectImportFacility("Dummy Facility 40"); - assetPage.importAssetFile(); - assetPage.selectImportLocation("Camera Loc"); - assetPage.clickImportAsset(); - }); - - it("verify imported asset", () => { - assetSearchPage.typeSearchKeyword("New Test Asset"); - assetSearchPage.pressEnter(); - assetSearchPage.verifyAssetIsPresent("New Test Asset"); - }); - - it("Export asset", () => { - assetPage.selectassetimportbutton(); - cy.wait(2000); - assetPage.selectJsonExportButton(); - assetPage.selectassetimportbutton(); - assetPage.selectCsvExportButton(); - }); - - afterEach(() => { - cy.saveLocalStorage(); +import { nonAdminRoles } from "../../pageobject/utils/userConfig"; + +const rolesToTest: Array<"districtAdmin" | (typeof nonAdminRoles)[number]> = [ + "districtAdmin", + ...nonAdminRoles, +]; + +rolesToTest.forEach((role) => { + describe(`Asset Tab Tests for Role: ${role}`, () => { + const assetHome = new AssetHome(); + const assetFilters = new AssetFilters(); + const loginPage = new LoginPage(); + const assetName = "Dummy Camera 10"; + const assetStatus = "ACTIVE"; + const assetClass = "ONVIF"; + const assetLocation = "Camera Loc"; + const facilityName = "Dummy Facility 40"; + const newImportAssetName = "New Test Asset"; + const qrCode = uuidv4(); + const serialNumber = Math.floor(Math.random() * 10 ** 10).toString(); + + before(() => { + loginPage.loginByRole(role); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/assets"); + }); + + it("Search Asset Name/QR_ID/Serial_number", () => { + assetHome.typeAssetSearch(assetName); + advanceFilters.verifyFilterBadgePresence( + "Name/Serial No./QR ID", + assetName, + true, + ); + assetHome.clickAssetByName(assetName); + assetHome.clickAssetDetailsUpdateButton(); + assetHome.clearAndTypeQRCode(qrCode); + assetHome.clearAndTypeSerialNumber(serialNumber); + assetHome.clickAssetUpdateSubmitButton(); + assetHome.navigateToAssetsPage(); + assetHome.typeAssetSearch(qrCode); + assetHome.verifyAssetListContains(assetName); + advanceFilters.verifyFilterBadgePresence( + "Name/Serial No./QR ID", + qrCode, + true, + ); + assetHome.typeAssetSearch(serialNumber); + assetHome.verifyAssetListContains(assetName); + advanceFilters.verifyFilterBadgePresence( + "Name/Serial No./QR ID", + serialNumber, + true, + ); + }); + + it("Scan Asset QR", () => { + assetHome.scanAssetQR(); + }); + + it("Advance Filter Asset", () => { + advanceFilters.clickAdvancedFiltersButton(); + advanceFilters.typeFacilityName(facilityName); + assetFilters.filterAssets(assetStatus, assetClass, assetLocation); + advanceFilters.applySelectedFilter(); + advanceFilters.clickAdvancedFiltersButton(); + advanceFilters.clickslideoverbackbutton(); // to verify the back button doesn't clear applied filters + advanceFilters.verifyFilterBadgePresence("Facility", facilityName, true); + advanceFilters.verifyFilterBadgePresence("Asset Class", assetClass, true); + advanceFilters.verifyFilterBadgePresence("Status", assetStatus, true); + advanceFilters.verifyFilterBadgePresence("Location", assetLocation, true); + advanceFilters.clickAdvancedFiltersButton(); + advanceFilters.clickClearAdvanceFilters(); + }); + + it("Next/Previous Page", () => { + pageNavigation.navigateToNextPage(); + pageNavigation.verifyCurrentPageNumber(2); + pageNavigation.navigateToPreviousPage(); + pageNavigation.verifyCurrentPageNumber(1); + }); + + it("Import new asset and verify its presence", () => { + if (role === "districtAdmin") { + assetHome.selectAssetImportButton("click"); + assetHome.selectImportOption(); + assetHome.selectImportFacility(facilityName); + assetHome.importAssetFile(); + assetHome.selectImportLocation(assetLocation); + assetHome.clickImportAsset(); + cy.verifyNotification("Assets imported successfully"); + cy.closeNotification(); + assetHome.navigateToAssetsPage(); + assetHome.typeAssetSearch(newImportAssetName); + assetHome.verifyAssetIsPresent(newImportAssetName); + } else { + assetHome.selectAssetImportButton("verifyNotExist"); + } + }); + + it("Export the list of assets in CSV & Json", () => { + if (role === "districtAdmin") { + assetHome.selectAssetImportButton("click"); + cy.wait(2000); + assetHome.selectJsonExportButton(); + assetHome.selectAssetImportButton("click"); + assetHome.selectCsvExportButton(); + } else { + assetHome.selectAssetImportButton("verifyNotExist"); + } + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); }); }); diff --git a/cypress/e2e/assets_spec/AssetsCreation.cy.ts b/cypress/e2e/assets_spec/AssetsCreation.cy.ts index 61c6fe9b517..820ac4231a9 100644 --- a/cypress/e2e/assets_spec/AssetsCreation.cy.ts +++ b/cypress/e2e/assets_spec/AssetsCreation.cy.ts @@ -1,18 +1,18 @@ import { v4 as uuidv4 } from "uuid"; import { AssetPage } from "../../pageobject/Asset/AssetCreation"; -import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; +import { AssetHome } from "../../pageobject/Asset/AssetHome"; import LoginPage from "../../pageobject/Login/LoginPage"; describe("Asset", () => { const assetPage = new AssetPage(); - const assetSearchPage = new AssetSearchPage(); + const assetHome = new AssetHome(); const loginPage = new LoginPage(); const phone_number = "9999999999"; const serialNumber = Math.floor(Math.random() * 10 ** 10).toString(); before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -43,21 +43,21 @@ describe("Asset", () => { const qr_id_1 = uuidv4(); - assetPage.enterAssetDetails( - "New Test Asset 1", - "Test Description", - "Working", - qr_id_1, - "Manufacturer's Name", - "2025-12-25", - "Customer Support's Name", - phone_number, - "email@support.com", - "Vendor's Name", - serialNumber, - "25122021", - "Test note for asset creation!", - ); + assetPage.enterAssetDetails({ + name: "New Test Asset 1", + description: "Test Description", + workingStatus: "Working", + qrId: qr_id_1, + manufacturer: "Manufacturer's Name", + warranty: "2025-12-25", + supportName: "Customer Support's Name", + supportPhone: phone_number, + supportEmail: "email@support.com", + vendorName: "Vendor's Name", + serialNumber: serialNumber, + lastServicedOn: "25122021", + notes: "Test note for asset creation!", + }); assetPage.clickCreateAddMore(); assetPage.verifySuccessNotification("Asset created successfully"); @@ -66,30 +66,29 @@ describe("Asset", () => { assetPage.selectLocation("Camera Loc"); assetPage.selectAssetClass("ONVIF Camera"); - assetPage.enterAssetDetails( - "New Test Asset 2", - "Test Description", - "Working", - qr_id_2, - "Manufacturer's Name", - "2025-12-25", - "Customer Support's Name", - phone_number, - "email@support.com", - "Vendor's Name", - serialNumber, - "25122021", - "Test note for asset creation!", - ); + assetPage.enterAssetDetails({ + name: "New Test Asset 2", + description: "Test Description", + workingStatus: "Working", + qrId: qr_id_2, + manufacturer: "Manufacturer's Name", + warranty: "2025-12-25", + supportName: "Customer Support's Name", + supportPhone: phone_number, + supportEmail: "email@support.com", + vendorName: "Vendor's Name", + serialNumber: serialNumber, + lastServicedOn: "25122021", + notes: "Test note for asset creation!", + }); assetPage.interceptAssetCreation(); assetPage.clickCreateAsset(); assetPage.verifyAssetCreation(); assetPage.verifySuccessNotification("Asset created successfully"); - assetSearchPage.typeSearchKeyword("New Test Asset 2"); - assetSearchPage.pressEnter(); - assetSearchPage.verifyAssetIsPresent("New Test Asset 2"); + assetHome.typeAssetSearch("New Test Asset 2"); + assetHome.verifyAssetIsPresent("New Test Asset 2"); }); it("Edit an Asset", () => { @@ -114,9 +113,8 @@ describe("Asset", () => { }); it("Verify Editted Asset", () => { - assetSearchPage.typeSearchKeyword("New Test Asset Edited"); - assetSearchPage.pressEnter(); - assetSearchPage.verifyAssetIsPresent("New Test Asset Edited"); + assetHome.typeAssetSearch("New Test Asset Edited"); + assetHome.verifyAssetIsPresent("New Test Asset Edited"); }); it("Configure an asset", () => { @@ -141,26 +139,26 @@ describe("Asset", () => { const qr_id_1 = uuidv4(); - assetPage.enterAssetDetails( - "New Test Asset Vital", - "Test Description", - "Working", - qr_id_1, - "Manufacturer's Name", - "2025-12-25", - "Customer Support's Name", - phone_number, - "email@support.com", - "Vendor's Name", - serialNumber, - "25122021", - "Test note for asset creation!", - ); + assetPage.enterAssetDetails({ + name: "New Test Asset Vital", + description: "Test Description", + workingStatus: "Working", + qrId: qr_id_1, + manufacturer: "Manufacturer's Name", + warranty: "2025-12-25", + supportName: "Customer Support's Name", + supportPhone: phone_number, + supportEmail: "email@support.com", + vendorName: "Vendor's Name", + serialNumber: serialNumber, + lastServicedOn: "25122021", + notes: "Test note for asset creation!", + }); + assetPage.interceptAssetCreation(); assetPage.clickCreateAsset(); assetPage.verifyAssetCreation(); - assetSearchPage.typeSearchKeyword("New Test Asset Vital"); - assetSearchPage.pressEnter(); + assetHome.typeAssetSearch("New Test Asset Vital"); assetPage.openCreatedAsset(); assetPage.configureVitalAsset("Host name", "192.168.1.20"); assetPage.clickConfigureVital(); diff --git a/cypress/e2e/assets_spec/AssetsManage.cy.ts b/cypress/e2e/assets_spec/AssetsManage.cy.ts index 17ee99bdb62..32dc5791bd3 100644 --- a/cypress/e2e/assets_spec/AssetsManage.cy.ts +++ b/cypress/e2e/assets_spec/AssetsManage.cy.ts @@ -1,6 +1,8 @@ +import FacilityHome from "pageobject/Facility/FacilityHome"; +import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; + import { AssetPage } from "../../pageobject/Asset/AssetCreation"; -import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; -import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; +import { AssetHome } from "../../pageobject/Asset/AssetHome"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; @@ -14,15 +16,15 @@ describe("Asset", () => { const assetPage = new AssetPage(); const loginPage = new LoginPage(); const facilityPage = new FacilityPage(); - const assetSearchPage = new AssetSearchPage(); - const assetFilters = new AssetFilters(); + const assetHome = new AssetHome(); + const facilityHome = new FacilityHome(); const fillFacilityName = "Dummy Facility 40"; const assetname = "Dummy Camera"; const locationName = "Dummy Location 1"; const initiallocationName = "Camera Location"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -33,10 +35,13 @@ describe("Asset", () => { }); it("Verify Asset Warranty Expiry Label", () => { - assetSearchPage.typeSearchKeyword(assetname); - assetSearchPage.pressEnter(); - assetSearchPage.verifyBadgeContent(assetname); - assetSearchPage.clickAssetByName(assetname); + assetHome.typeAssetSearch(assetname); + advanceFilters.verifyFilterBadgePresence( + "Name/Serial No./QR ID", + assetname, + true, + ); + assetHome.clickAssetByName(assetname); assetPage.clickupdatedetailbutton(); assetPage.scrollintoWarrantyDetails(); assetPage.enterWarrantyExpiryDate(addDaysToDate(100)); // greater than 3 months @@ -60,10 +65,13 @@ describe("Asset", () => { }); it("Create & Edit a service history and verify reflection", () => { - assetSearchPage.typeSearchKeyword(assetname); - assetSearchPage.pressEnter(); - assetSearchPage.verifyBadgeContent(assetname); - assetSearchPage.clickAssetByName(assetname); + assetHome.typeAssetSearch(assetname); + advanceFilters.verifyFilterBadgePresence( + "Name/Serial No./QR ID", + assetname, + true, + ); + assetHome.clickAssetByName(assetname); assetPage.clickupdatedetailbutton(); assetPage.scrollintonotes(); assetPage.enterAssetNotes("Dummy Notes"); @@ -82,10 +90,13 @@ describe("Asset", () => { }); it("Create a asset transaction and verify history", () => { - assetSearchPage.typeSearchKeyword(assetname); - assetSearchPage.pressEnter(); - assetSearchPage.verifyBadgeContent(assetname); - assetSearchPage.clickAssetByName(assetname); + assetHome.typeAssetSearch(assetname); + advanceFilters.verifyFilterBadgePresence( + "Name/Serial No./QR ID", + assetname, + true, + ); + assetHome.clickAssetByName(assetname); assetPage.clickupdatedetailbutton(); assetPage.clickassetlocation(locationName); assetPage.clickUpdateAsset(); @@ -94,10 +105,13 @@ describe("Asset", () => { }); it("Verify Facility Asset Page Redirection", () => { - cy.visit("/facility"); - assetSearchPage.typeSearchKeyword(fillFacilityName); - assetSearchPage.pressEnter(); - facilityPage.verifyFacilityBadgeContent(fillFacilityName); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch(fillFacilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + fillFacilityName, + true, + ); facilityPage.visitAlreadyCreatedFacility(); facilityPage.clickManageFacilityDropdown(); facilityPage.clickCreateAssetFacilityOption(); @@ -106,7 +120,11 @@ describe("Asset", () => { facilityPage.clickManageFacilityDropdown(); facilityPage.clickviewAssetFacilityOption(); facilityPage.verifyfacilityviewassetredirection(); - assetFilters.assertFacilityText(fillFacilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility", + fillFacilityName, + true, + ); facilityPage.verifyassetfacilitybackredirection(); }); diff --git a/cypress/e2e/facility_spec/FacilityCreation.cy.ts b/cypress/e2e/facility_spec/FacilityCreation.cy.ts index 5fd988480fc..78ce17d8b9e 100644 --- a/cypress/e2e/facility_spec/FacilityCreation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityCreation.cy.ts @@ -1,8 +1,10 @@ +import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; + import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; -import { UserCreationPage } from "../../pageobject/Users/UserCreation"; +import { nonAdminRoles } from "../../pageobject/utils/userConfig"; describe("Facility Creation", () => { let facilityUrl1: string; @@ -10,7 +12,6 @@ describe("Facility Creation", () => { const loginPage = new LoginPage(); const facilityHome = new FacilityHome(); const manageUserPage = new ManageUserPage(); - const userCreationPage = new UserCreationPage(); const facilityFeature = [ "CT Scan", "X-Ray", @@ -60,7 +61,7 @@ describe("Facility Creation", () => { const facilityType = "Primary Health Centres"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -72,15 +73,19 @@ describe("Facility Creation", () => { it("Verify Facility Triage Function", () => { // mandatory field error throw - manageUserPage.typeFacilitySearch(facilityName2); - facilityPage.verifyFacilityBadgeContent(facilityName2); - manageUserPage.assertFacilityInCard(facilityName2); + facilityHome.typeFacilitySearch(facilityName2); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName2, + true, + ); + facilityHome.assertFacilityInCard(facilityName2); facilityHome.verifyURLContains(facilityName2); facilityPage.visitAlreadyCreatedFacility(); facilityPage.scrollToFacilityTriage(); facilityPage.clickAddFacilityTriage(); manageUserPage.clickSubmit(); - userCreationPage.verifyErrorMessages(triageErrorMessage); + cy.verifyErrorMessages(triageErrorMessage); // create a entry and verify reflection facilityPage.fillEntryDate(triageDate); facilityPage.fillTriageEntryFields( @@ -116,7 +121,7 @@ describe("Facility Creation", () => { // create facility with multiple capacity and verify form error message for facility form facilityPage.visitCreateFacilityPage(); facilityPage.submitForm(); - userCreationPage.verifyErrorMessages(facilityErrorMessage); + cy.verifyErrorMessages(facilityErrorMessage); facilityPage.fillFacilityName(facilityName); facilityPage.selectFacilityType(facilityType); facilityPage.clickfacilityfeatureoption(); @@ -237,10 +242,14 @@ describe("Facility Creation", () => { .contains(facilityNumber) .should("be.visible"); // verify the facility homepage - cy.visit("/facility"); - manageUserPage.typeFacilitySearch(facilityName); - facilityPage.verifyFacilityBadgeContent(facilityName); - manageUserPage.assertFacilityInCard(facilityName); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch(facilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName, + true, + ); + facilityHome.assertFacilityInCard(facilityName); facilityHome.verifyURLContains(facilityName); }); @@ -259,12 +268,12 @@ describe("Facility Creation", () => { // add no bed capacity and verify form error message facilityPage.isVisibleselectBedType(); facilityPage.saveAndExitBedCapacityForm(); - userCreationPage.verifyErrorMessages(bedErrorMessage); + cy.verifyErrorMessages(bedErrorMessage); facilityPage.clickcancelbutton(); // add no doctor capacity and verify form error message facilityPage.isVisibleAreaOfSpecialization(); facilityPage.clickdoctorcapacityaddmore(); - userCreationPage.verifyErrorMessages(doctorErrorMessage); + cy.verifyErrorMessages(doctorErrorMessage); facilityPage.clickcancelbutton(); cy.url().then((newUrl) => { facilityUrl1 = newUrl; @@ -335,13 +344,8 @@ describe("Facility Creation", () => { }); it("Access Restriction for Non-Admin Users to facility creation page", () => { - const nonAdminLoginMethods = [ - loginPage.loginAsDevDoctor.bind(loginPage), - loginPage.loginAsStaff.bind(loginPage), - ]; - - nonAdminLoginMethods.forEach((loginMethod) => { - loginMethod(); + nonAdminRoles.forEach((role) => { + loginPage.loginByRole(role); cy.visit("/facility/create"); facilityPage.verifyErrorNotification( "You don't have permission to perform this action. Contact the admin", diff --git a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts index f4c7aaa90a0..6f01baac714 100644 --- a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts @@ -6,7 +6,6 @@ import FacilityHome from "../../pageobject/Facility/FacilityHome"; import FacilityNotify from "../../pageobject/Facility/FacilityNotify"; import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; -import { UserPage } from "../../pageobject/Users/UserSearch"; import { advanceFilters } from "../../pageobject/utils/advanceFilterHelpers"; describe("Facility Homepage Function", () => { @@ -15,7 +14,6 @@ describe("Facility Homepage Function", () => { const facilityNotify = new FacilityNotify(); const facilityPage = new FacilityPage(); const manageUserPage = new ManageUserPage(); - const userPage = new UserPage(); const facilitiesAlias = "downloadFacilitiesCSV"; const doctorsAlias = "downloadDoctorsCSV"; const triagesAlias = "downloadTriagesCSV"; @@ -29,7 +27,7 @@ describe("Facility Homepage Function", () => { const notificationMessage = "Test Notification"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -41,9 +39,13 @@ describe("Facility Homepage Function", () => { it("Verify the Facility card button redirection", () => { // view cns button - manageUserPage.typeFacilitySearch(facilityName); - facilityPage.verifyFacilityBadgeContent(facilityName); - manageUserPage.assertFacilityInCard(facilityName); + facilityHome.typeFacilitySearch(facilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName, + true, + ); + facilityHome.assertFacilityInCard(facilityName); facilityHome.clickViewCnsButton(); facilityHome.verifyCnsUrl(); facilityHome.navigateBack(); @@ -68,17 +70,21 @@ describe("Facility Homepage Function", () => { advanceFilters.selectLocalBody(localBody); advanceFilters.selectFacilityType(facilityType); advanceFilters.applySelectedFilter(); - facilityPage.verifyStateBadgeContent(stateName); - facilityPage.verifyDistrictBadgeContent(district); - facilityPage.verifyLocalBodyBadgeContent(localBody); - facilityPage.verifyFacilityTypeBadgeContent(facilityType); - manageUserPage.assertFacilityInCard(facilityName); + advanceFilters.verifyFilterBadgePresence("State", stateName, true); + advanceFilters.verifyFilterBadgePresence("District", district, true); + advanceFilters.verifyFilterBadgePresence( + "Facility type", + facilityType, + true, + ); + advanceFilters.verifyFilterBadgePresence("Local Body", localBody, true); + facilityHome.assertFacilityInCard(facilityName); advanceFilters.clickAdvancedFiltersButton(); advanceFilters.clickClearAdvanceFilters(); - userPage.verifyDataTestIdNotVisible("State"); - userPage.verifyDataTestIdNotVisible("District"); - userPage.verifyDataTestIdNotVisible("Facility type"); - userPage.verifyDataTestIdNotVisible("Local Body"); + advanceFilters.verifyFilterBadgePresence("State", "", false); + advanceFilters.verifyFilterBadgePresence("District", "", false); + advanceFilters.verifyFilterBadgePresence("Facility type", "", false); + advanceFilters.verifyFilterBadgePresence("Local Body", "", false); }); it("Search a facility in homepage and pagination", () => { @@ -88,9 +94,13 @@ describe("Facility Homepage Function", () => { pageNavigation.navigateToPreviousPage(); pageNavigation.verifyCurrentPageNumber(1); // search for a facility - manageUserPage.typeFacilitySearch(facilityName); - facilityPage.verifyFacilityBadgeContent(facilityName); - manageUserPage.assertFacilityInCard(facilityName); + facilityHome.typeFacilitySearch(facilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName, + true, + ); + facilityHome.assertFacilityInCard(facilityName); facilityHome.verifyURLContains(facilityName); }); @@ -124,9 +134,13 @@ describe("Facility Homepage Function", () => { advanceFilters.selectLocalBody(localBody); advanceFilters.applySelectedFilter(); // go to cns page in the facility details page - manageUserPage.typeFacilitySearch(facilityName); - facilityPage.verifyFacilityBadgeContent(facilityName); - manageUserPage.assertFacilityInCard(facilityName); + facilityHome.typeFacilitySearch(facilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName, + true, + ); + facilityHome.assertFacilityInCard(facilityName); facilityHome.clickViewFacilityDetails(); facilityHome.clickFacilityCnsButton(); facilityHome.verifyCnsUrl(); @@ -140,13 +154,17 @@ describe("Facility Homepage Function", () => { it("Verify Notice Board Functionality", () => { // search facility and verify it's loaded or not - manageUserPage.interceptFacilitySearchReq(); - manageUserPage.typeFacilitySearch(facilityName); - manageUserPage.verifyFacilitySearchReq(); + facilityHome.interceptFacilitySearchReq(); + facilityHome.typeFacilitySearch(facilityName); + facilityHome.verifyFacilitySearchReq(); // verify facility name and card reflection facilityNotify.verifyUrlContains("Dummy+Facility+40"); - facilityPage.verifyFacilityBadgeContent(facilityName); - manageUserPage.assertFacilityInCard(facilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName, + true, + ); + facilityHome.assertFacilityInCard(facilityName); // send notification to a facility facilityHome.clickFacilityNotifyButton(); facilityNotify.verifyFacilityName(facilityName); @@ -163,7 +181,7 @@ describe("Facility Homepage Function", () => { cy.clickSubmitButton("Notify"); facilityNotify.verifyErrorMessage(notificationErrorMsg); // close pop-up and verify - facilityHome.verifyAndCloseNotifyModal(); + cy.clickCancelButton("Cancel"); // signout as district admin and login as a Nurse loginPage.ensureLoggedIn(); loginPage.clickSignOutBtn(); diff --git a/cypress/e2e/facility_spec/FacilityInventory.cy.ts b/cypress/e2e/facility_spec/FacilityInventory.cy.ts index a9f32984efc..b3f77479763 100644 --- a/cypress/e2e/facility_spec/FacilityInventory.cy.ts +++ b/cypress/e2e/facility_spec/FacilityInventory.cy.ts @@ -9,7 +9,7 @@ describe("Inventory Management Section", () => { const inventoryName = "PPE"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/facility_spec/FacilityLocation.cy.ts b/cypress/e2e/facility_spec/FacilityLocation.cy.ts index 9ac85e9ba4c..1ae61e5ecb0 100644 --- a/cypress/e2e/facility_spec/FacilityLocation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityLocation.cy.ts @@ -1,18 +1,19 @@ +import { AssetHome } from "pageobject/Asset/AssetHome"; +import LoginPage from "pageobject/Login/LoginPage"; +import { generatePhoneNumber } from "pageobject/utils/constants"; import { pageNavigation } from "pageobject/utils/paginationHelpers"; import { v4 as uuidv4 } from "uuid"; import { AssetPage } from "../../pageobject/Asset/AssetCreation"; -import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityHome from "../../pageobject/Facility/FacilityHome"; import FacilityLocation from "../../pageobject/Facility/FacilityLocation"; -import { UserCreationPage } from "../../pageobject/Users/UserCreation"; describe("Location Management Section", () => { const assetPage = new AssetPage(); - const userCreationPage = new UserCreationPage(); - const facilityPage = new FacilityPage(); const facilityLocation = new FacilityLocation(); const facilityHome = new FacilityHome(); + const assetHome = new AssetHome(); + const loginPage = new LoginPage(); const EXPECTED_LOCATION_ERROR_MESSAGES = [ "Name is required", @@ -22,30 +23,21 @@ describe("Location Management Section", () => { "Please enter a name", "Please select a bed type", ]; - const locationName = "Test-location"; - const locationNameTwo = "Test-location-2"; const locationDescription = "Test Description"; const locationType = "WARD"; const locationMiddleware = "dev_middleware.coronasafe.live"; - const locationModifiedName = "Test Modified location"; const locationModifiedDescription = "Test Modified Description"; const locationModifiedType = "ICU"; const locationModifiedMiddleware = "dev-middleware.coronasafe.live"; - const bedName = "Test-Bed"; const bedDescrption = "test description"; const bedType = "ICU"; const bedStatus = "Vacant"; - const bedModifiedName = "test modified bed"; const bedModifiedDescrption = "test modified description"; const bedModifiedType = "Isolation"; - const numberOfBeds = 10; - const numberOfModifiedBeds = 25; - const qr_id_1 = uuidv4(); - const phone_number = "9999999999"; - const serialNumber = Math.floor(Math.random() * 10 ** 10).toString(); + const phone_number = generatePhoneNumber(); before(() => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -54,229 +46,226 @@ describe("Location Management Section", () => { cy.restoreLocalStorage(); cy.clearLocalStorage(/filters--.+/); cy.awaitUrl("/"); - facilityLocation.loadLocationManagementPage("Dummy Shifting Center"); }); - it("Add a Bed to facility location along with duplication and deleting a bed", () => { - // mandatory field verification in bed creation - cy.get("body").then(($body) => { - if ($body.find("#manage-bed-button:visible").length) { - // If the '#manage-bed-button' is visible - facilityLocation.clickManageBedButton(); - } else { - // If the '#manage-bed-button' is not visible - facilityLocation.clickAddNewLocationButton(); - facilityPage.fillFacilityName(locationName); - facilityLocation.selectLocationType(locationType); - assetPage.clickassetupdatebutton(); - facilityLocation.clickNotification(); - facilityLocation.clickManageBedButton(); - } + it("Delete location with linked assets", () => { + const locationName = `ICU-${uuidv4().substring(0, 2)}`; + const facilityName = "Dummy Facility 13"; + const assetName = "Test Asset linked to location"; + // Select a new facility + facilityLocation.navigateToFacilityLocationManagement(facilityName); + // Create a new location + facilityLocation.clickAddNewLocationButton(); + facilityLocation.fillLocationDetails( + locationName, + undefined, + locationType, + undefined, + ); + facilityLocation.clickAddLocationButton(); + facilityLocation.verifyAddLocationSuccessfulMesssage(); + // create asset and link it to location + assetHome.navigateToAssetsPage(); + assetPage.createAsset(); + assetPage.selectFacility(facilityName); + assetPage.selectLocation(locationName); + assetPage.enterAssetDetails({ + name: assetName, + workingStatus: "Working", + supportPhone: phone_number, }); - facilityLocation.clickAddBedButton(); - assetPage.clickassetupdatebutton(); - userCreationPage.verifyErrorMessages(EXPECTED_BED_ERROR_MESSAGES); - // create a new single bed and verify - facilityLocation.enterBedName(bedName); - facilityLocation.enterBedDescription(bedDescrption); - facilityLocation.selectBedType(bedType); - assetPage.clickassetupdatebutton(); - // Verify the bed creation - facilityLocation.verifyBedNameBadge(bedName); - facilityLocation.verifyBedBadge(bedType); - facilityLocation.verifyBedBadge(bedStatus); - // Try to create duplication bed and verify the error - facilityLocation.clickAddBedButton(); - facilityLocation.enterBedName(bedName); - facilityLocation.selectBedType(bedType); assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification( - "Name - Bed with same name already exists in location", + cy.verifyNotification("Asset created successfully"); + cy.closeNotification(); + // Select a new facility + facilityHome.navigateToFacilityHomepage(); + facilityLocation.navigateToFacilityLocationManagement(facilityName); + facilityLocation.clickDeleteLocation(locationName); + cy.clickSubmitButton("Confirm"); + cy.verifyNotification("Cannot delete a Location with associated Assets"); + cy.closeNotification(); + + // delete asset + facilityLocation.clickManageAssets(); + assetPage.openCreatedAsset(); + assetPage.deleteAsset(); + cy.verifyNotification("Asset deleted successfully"); + cy.closeNotification(); + + // delete location + facilityHome.navigateToFacilityHomepage(); + facilityLocation.navigateToFacilityLocationManagement(facilityName); + facilityLocation.clickDeleteLocation(locationName); + cy.clickSubmitButton("Confirm"); + cy.verifyNotification(`Location ${locationName} deleted successfully`); + cy.closeNotification(); + }); + + it("Delete location with linked beds", () => { + const locationName = `ICU-${uuidv4().substring(0, 2)}`; + const facilityName = "Dummy Facility 12"; + const bedName = `Bed-${uuidv4().substring(0, 2)}`; + // Select a new facility + facilityLocation.navigateToFacilityLocationManagement(facilityName); + // Create a new location with a bed + facilityLocation.clickAddNewLocationButton(); + facilityLocation.fillLocationDetails( + locationName, + undefined, + locationType, + undefined, ); - facilityHome.verifyAndCloseNotifyModal(); - // edit the created bed - facilityLocation.clickEditBedButton(); - facilityLocation.enterBedName(bedModifiedName); - facilityLocation.enterBedDescription(bedModifiedDescrption); - facilityLocation.selectBedType(bedModifiedType); - assetPage.clickassetupdatebutton(); - // verify the modification - facilityLocation.verifyBedNameBadge(bedModifiedName); - facilityLocation.verifyBedBadge(bedModifiedType); - facilityLocation.verifyBedBadge(bedStatus); - facilityLocation.closeNotification(); + facilityLocation.clickAddLocationButton(); + facilityLocation.verifyAddLocationSuccessfulMesssage(); + // Create a new bed to the location + facilityLocation.clickManageBedButton(locationName); + facilityLocation.clickAddBedButton(); + facilityLocation.fillBedForm(bedName, undefined, bedType, undefined); + facilityLocation.clickSubmitBedsButton(); + facilityLocation.verifyAddSingleBedSuccessfulMesssage(); + // Now try to delete the location with bed in it + facilityLocation.fetchAndNavigateToLocationPage(); + facilityLocation.clickDeleteLocation(locationName); + cy.clickSubmitButton("Confirm"); + cy.verifyNotification("Cannot delete a Location with associated Beds"); + cy.closeNotification(); + + // delete bed + facilityLocation.clickManageBedPopup(); + facilityLocation.deleteBedWithName(bedName); + cy.clickSubmitButton("Delete"); + cy.verifyNotification("Bed deleted successfully"); + cy.closeNotification(); + + // delete location + facilityLocation.fetchAndNavigateToLocationPage(); + facilityLocation.clickDeleteLocation(locationName); + cy.clickSubmitButton("Confirm"); + cy.verifyNotification(`Location ${locationName} deleted successfully`); + cy.closeNotification(); }); it("Adds Location to a facility and modify it", () => { + const locationName = `ICU-${uuidv4().substring(0, 2)}`; + const locationModifiedName = `ICU-${uuidv4().substring(0, 2)}}`; + const facilityName = "Dummy Facility 11"; + // Select a new facility + facilityLocation.navigateToFacilityLocationManagement(facilityName); // add a new location form mandatory error facilityLocation.clickAddNewLocationButton(); - assetPage.clickassetupdatebutton(); - userCreationPage.verifyErrorMessages(EXPECTED_LOCATION_ERROR_MESSAGES); + facilityLocation.clickAddLocationButton(); + cy.verifyErrorMessages(EXPECTED_LOCATION_ERROR_MESSAGES); // create a new location - facilityPage.fillFacilityName(locationNameTwo); - facilityLocation.fillDescription(locationDescription); - facilityLocation.selectLocationType(locationType); - facilityLocation.fillMiddlewareAddress(locationMiddleware); - assetPage.clickassetupdatebutton(); - facilityLocation.clickNotification(); + facilityLocation.fillLocationDetails( + locationName, + locationDescription, + locationType, + locationMiddleware, + ); + facilityLocation.clickAddLocationButton(); + facilityLocation.verifyAddLocationSuccessfulMesssage(); // verify the reflection - facilityLocation.verifyLocationName(locationNameTwo); + facilityLocation.verifyLocationName(locationName); facilityLocation.verifyLocationType(locationType); facilityLocation.verifyLocationDescription(locationDescription); facilityLocation.verifyLocationMiddleware(locationMiddleware); - // modify the existing data + // verify the duplicate location error message + facilityLocation.clickAddNewLocationButton(); + facilityLocation.fillLocationDetails( + locationName, + undefined, + locationType, + undefined, + ); + facilityLocation.clickAddLocationButton(); + cy.verifyNotification( + "Name - Asset location with this name and facility already exists.", + ); + cy.closeNotification(); + facilityLocation.closeAddLocationForm(); facilityLocation.clickEditLocationButton(); - facilityPage.fillFacilityName(locationModifiedName); - facilityLocation.fillDescription(locationModifiedDescription); - facilityLocation.selectLocationType(locationModifiedType); - facilityLocation.fillMiddlewareAddress(locationModifiedMiddleware); - assetPage.clickassetupdatebutton(); + facilityLocation.fillLocationDetails( + locationModifiedName, + locationModifiedDescription, + locationModifiedType, + locationModifiedMiddleware, + ); + facilityLocation.clickUpdateLocationButton(); + facilityLocation.verifyEditLocationSuccessfulMessage(); // verify the reflection facilityLocation.verifyLocationName(locationModifiedName); facilityLocation.verifyLocationType(locationModifiedType); facilityLocation.verifyLocationDescription(locationModifiedDescription); facilityLocation.verifyLocationMiddleware(locationModifiedMiddleware); - facilityLocation.closeNotification(); }); - it("Multiple Bed to a facility location and delete a bed", () => { - // create multiple bed and verify - facilityLocation.clickManageBedButton(); + it("Add single & Multiple Bed to facility location along with duplication and deleting a bed", () => { + const locationName = `ICU-${uuidv4().substring(0, 2)}`; + const facilityName = "Dummy Request Fulfilment Center"; + const bedName = `Bed-${uuidv4().substring(0, 2)}`; + const duplicateBedName = `Duplicate-${uuidv4().substring(0, 2)}`; + const multipleBedName = `Multi-${uuidv4().substring(0, 2)}}`; + const numberOfBeds = 25; + // Select a new facility + facilityLocation.navigateToFacilityLocationManagement(facilityName); + // Create a new location and Bed into it + facilityLocation.clickAddNewLocationButton(); + facilityLocation.fillLocationDetails( + locationName, + undefined, + locationType, + undefined, + ); + facilityLocation.clickAddLocationButton(); + facilityLocation.verifyAddLocationSuccessfulMesssage(); + // Verify the error message of beds creation form + facilityLocation.clickManageBedButton(locationName); facilityLocation.clickAddBedButton(); - facilityLocation.enterBedName(bedName); - facilityLocation.enterBedDescription(bedDescrption); - facilityLocation.selectBedType(bedType); - facilityLocation.setMultipleBeds(numberOfBeds); - assetPage.clickassetupdatebutton(); - facilityLocation.clickNotification(); - // verify the bed creation + facilityLocation.clickSubmitBedsButton(); + cy.verifyErrorMessages(EXPECTED_BED_ERROR_MESSAGES); + // Add a new bed to the location + facilityLocation.fillBedForm(bedName, bedDescrption, bedType, undefined); + facilityLocation.clickSubmitBedsButton(); + facilityLocation.verifyAddSingleBedSuccessfulMesssage(); + // Verify the bed creation + facilityLocation.verifyBedNameBadge(bedName); facilityLocation.verifyBedBadge(bedType); facilityLocation.verifyBedBadge(bedStatus); - facilityLocation.verifyIndividualBedName(bedName, numberOfBeds); - // delete a bed and verify it - facilityLocation.deleteFirstBed(); - facilityLocation.deleteBedRequest(); - assetPage.clickassetupdatebutton(); - facilityLocation.deleteBedRequest(); - facilityLocation.closeNotification(); - }); - - it("Add Multiple Bed to a facility location and verify pagination", () => { - // bed creation - facilityLocation.clickManageBedButton(); + // Try to create duplication bed and verify the error facilityLocation.clickAddBedButton(); - facilityLocation.enterBedName(bedModifiedName); - facilityLocation.enterBedDescription(bedModifiedDescrption); - facilityLocation.selectBedType(bedModifiedType); - facilityLocation.setMultipleBeds(numberOfModifiedBeds); - assetPage.clickassetupdatebutton(); - // pagination - pageNavigation.navigateToNextPage(); - pageNavigation.navigateToPreviousPage(); - facilityLocation.closeNotification(); - }); - - it("Delete location", () => { - facilityLocation.clickAddNewLocationButton(); - facilityLocation.enterLocationName("Test Location"); - facilityLocation.selectLocationType("OTHER"); - assetPage.clickassetupdatebutton(); - facilityLocation.deleteLocation("Test Location"); - assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification( - "Location Test Location deleted successfully", + facilityLocation.fillBedForm(bedName, undefined, bedType, undefined); + facilityLocation.clickSubmitBedsButton(); + cy.verifyNotification( + "Name - Bed with same name already exists in location", ); - facilityLocation.closeNotification(); - }); - - it("Delete location with linked beds", () => { - facilityLocation.clickAddNewLocationButton(); - facilityLocation.enterLocationName("Test Location with Beds"); - facilityLocation.selectLocationType("OTHER"); - cy.clickSubmitButton("Add Location"); - cy.verifyNotification("Location created successfully"); - cy.closeNotification(); - facilityLocation.clickManageBedButton(); - facilityLocation.clickAddBedButton(); - facilityLocation.enterBedName("Bed 1"); - facilityLocation.selectBedType("Regular"); - cy.clickSubmitButton("Add Bed(s)"); - cy.verifyNotification("1 Bed created successfully"); cy.closeNotification(); - facilityLocation.loadLocationManagementPage("Dummy Shifting Center"); - facilityLocation.deleteLocation("Test Location with Beds"); - assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification( - "Cannot delete a Location with associated Beds", - ); - facilityLocation.closeNotification(); - - // delete bed - facilityLocation.clickManageBeds(); - facilityLocation.deleteFirstBed(); - assetPage.clickassetupdatebutton(); - facilityLocation.closeNotification(); - - // delete location - facilityLocation.loadLocationManagementPage("Dummy Shifting Center"); - facilityLocation.deleteLocation("Test Location with Beds"); - assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification( - "Location Test Location with Beds deleted successfully", - ); - facilityLocation.closeNotification(); - }); - - it("Delete location with linked assets", () => { - facilityLocation.clickAddNewLocationButton(); - facilityLocation.enterLocationName("Test Location with linked Assets"); - facilityLocation.selectLocationType("OTHER"); - assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification("Location created successfully"); - facilityLocation.closeNotification(); - // create asset and link it to location - cy.awaitUrl("/assets"); - assetPage.createAsset(); - assetPage.selectFacility("Dummy Shifting Center"); - assetPage.selectLocation("Test Location with linked Assets"); - assetPage.enterAssetDetails( - "Test Asset linked to Facility", - "Test Description", - "Working", - qr_id_1, - "Manufacturer's Name", - "2025-12-25", - "Customer Support's Name", - phone_number, - "email@support.com", - "Vendor's Name", - serialNumber, - "25122021", - "Test note for asset creation!", + facilityLocation.closeAddLocationForm(); + // edit the newly created existing bed + facilityLocation.clickEditBedButton(bedName); + facilityLocation.fillBedForm( + duplicateBedName, + bedModifiedDescrption, + bedModifiedType, + undefined, ); - assetPage.clickassetupdatebutton(); - facilityLocation.loadLocationManagementPage("Dummy Shifting Center"); - facilityLocation.deleteLocation("Test Location with linked Assets"); - assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification( - "Cannot delete a Location with associated Assets", - ); - facilityLocation.closeNotification(); - - // delete asset - facilityLocation.clickManageAssets(); - assetPage.openCreatedAsset(); - assetPage.deleteAsset(); - facilityLocation.closeNotification(); - - // delete location - facilityLocation.loadLocationManagementPage("Dummy Shifting Center"); - facilityLocation.deleteLocation("Test Location with linked Assets"); - assetPage.clickassetupdatebutton(); - facilityLocation.verifyNotification( - "Location Test Location with linked Assets deleted successfully", + facilityLocation.clickUpdateBedButton(); + facilityLocation.verifyEditBedSuccessfulMessage(); + // verify the modification + facilityLocation.verifyBedNameBadge(duplicateBedName); + facilityLocation.verifyBedBadge(bedModifiedType); + facilityLocation.verifyBedBadge(bedStatus); + // Create Multiple Bed + facilityLocation.clickAddBedButton(); + facilityLocation.fillBedForm( + multipleBedName, + bedDescrption, + bedType, + numberOfBeds, ); - facilityLocation.closeNotification(); + facilityLocation.clickSubmitBedsButton(); + // Verify Pagination in the page + pageNavigation.navigateToNextPage(); + pageNavigation.navigateToPreviousPage(); }); afterEach(() => { diff --git a/cypress/e2e/facility_spec/FacilityManage.cy.ts b/cypress/e2e/facility_spec/FacilityManage.cy.ts index 02c14958ccf..9f1523768c1 100644 --- a/cypress/e2e/facility_spec/FacilityManage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityManage.cy.ts @@ -1,3 +1,6 @@ +import FacilityHome from "pageobject/Facility/FacilityHome"; +import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; + import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import FacilityManage from "../../pageobject/Facility/FacilityManage"; import LoginPage from "../../pageobject/Login/LoginPage"; @@ -6,16 +9,18 @@ describe("Facility Manage Functions", () => { const loginPage = new LoginPage(); const facilityManage = new FacilityManage(); const facilityPage = new FacilityPage(); + const facilityHome = new FacilityHome(); const facilityName = "Dummy Facility 40"; const facilityMiddlewareUpdateButton = "Update"; const facilityMiddleware = "dev-middleware.coronasafe.live"; const facilityUpdatedMiddleware = "updated.coronasafe.live"; const facilityMiddlewareSuccessfullNotification = "Facility middleware updated successfully"; - // const facilityHfridUpdateButton = "Link Health Facility"; - // const facilityHfridToastNotificationText = /Health Facility config updated successfully|Health ID registration failed/; - // const facilityHfrId = "IN180000018"; - // const facilityUpdatedHfrId = uuidv4(); + const facilityHfridUpdateButton = "Link Health Facility"; + const facilityHfridToastNotificationText = + /Health Facility config updated successfully|Health ID registration failed/; + const facilityHfrId = "IN180000018"; + const facilityUpdatedHfrId = "IN180000020"; const doctorCapacity = "5"; const doctorModifiedCapacity = "7"; const totalCapacity = "100"; @@ -24,7 +29,7 @@ describe("Facility Manage Functions", () => { const currentUpdatedOccupied = "100"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -33,8 +38,12 @@ describe("Facility Manage Functions", () => { cy.restoreLocalStorage(); cy.clearLocalStorage(/filters--.+/); cy.awaitUrl("/"); - facilityPage.typeFacilitySearch(facilityName); - facilityPage.verifyFacilityBadgeContent(facilityName); + facilityHome.typeFacilitySearch(facilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + facilityName, + true, + ); facilityPage.visitAlreadyCreatedFacility(); }); @@ -76,37 +85,36 @@ describe("Facility Manage Functions", () => { facilityManage.verifyMiddlewareAddressValue(facilityUpdatedMiddleware); }); - // TODO: enable this test after configuring testing specs for plugs - // it("Configure Facility Health ID", () => { - // facilityPage.clickManageFacilityDropdown(); - // facilityManage.clickFacilityConfigureButton(); - // // verify mandatory field error message - // facilityManage.clearHfrId(); - // facilityManage.clickButtonWithText(facilityHfridUpdateButton); - // facilityManage.checkErrorMessageVisibility( - // "Health Facility Id is required", - // ); - // // add facility health ID and verify notification - // facilityManage.typeHfrId(facilityHfrId); - // facilityManage.clickButtonWithText(facilityHfridUpdateButton); - // facilityManage.verifySuccessMessageVisibilityAndContent( - // facilityHfridToastNotificationText, - // true, - // ); - // // update the existing middleware - // facilityPage.clickManageFacilityDropdown(); - // facilityManage.clickFacilityConfigureButton(); - // facilityManage.typeHfrId(facilityUpdatedHfrId); - // facilityManage.clickButtonWithText(facilityHfridUpdateButton); - // facilityManage.verifySuccessMessageVisibilityAndContent( - // facilityHfridToastNotificationText, - // true, - // ); - // // verify its reflection - // facilityPage.clickManageFacilityDropdown(); - // facilityManage.clickFacilityConfigureButton(); - // facilityManage.verifyHfrIdValue(facilityUpdatedHfrId); - // }); + it("Configure Facility Health ID", () => { + facilityPage.clickManageFacilityDropdown(); + facilityManage.clickFacilityConfigureButton(); + // verify mandatory field error message + facilityManage.clearHfrId(); + facilityManage.clickButtonWithText(facilityHfridUpdateButton); + facilityManage.checkErrorMessageVisibility( + "Health Facility Id is required", + ); + // add facility health ID and verify notification + facilityManage.typeHfrId(facilityHfrId); + facilityManage.clickButtonWithText(facilityHfridUpdateButton); + facilityManage.verifySuccessMessageVisibilityAndContent( + facilityHfridToastNotificationText, + true, + ); + // update the existing middleware + facilityPage.clickManageFacilityDropdown(); + facilityManage.clickFacilityConfigureButton(); + facilityManage.typeHfrId(facilityUpdatedHfrId); + facilityManage.clickButtonWithText(facilityHfridUpdateButton); + facilityManage.verifySuccessMessageVisibilityAndContent( + facilityHfridToastNotificationText, + true, + ); + // verify its reflection + facilityPage.clickManageFacilityDropdown(); + facilityManage.clickFacilityConfigureButton(); + facilityManage.verifyHfrIdValue(facilityUpdatedHfrId); + }); it("Modify doctor capacity in Facility detail page", () => { // Add a doctor capacity diff --git a/cypress/e2e/hcx_spec/HcxClaims.cy.ts b/cypress/e2e/hcx_spec/HcxClaims.cy.ts index b698bb7ffd8..ccaa628febb 100644 --- a/cypress/e2e/hcx_spec/HcxClaims.cy.ts +++ b/cypress/e2e/hcx_spec/HcxClaims.cy.ts @@ -18,7 +18,7 @@ describe("HCX Claims configuration and approval workflow", () => { const patientInsurerName = "Demo Payor"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientBedManagement.cy.ts b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts index 94f0869db9b..fbc31ff9698 100644 --- a/cypress/e2e/patient_spec/PatientBedManagement.cy.ts +++ b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts @@ -15,7 +15,7 @@ describe("Patient swtich bed functionality", () => { const switchPatientTwo = "Dummy Patient Seven"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index 3eacd9b718f..4191d63b3e2 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -38,7 +38,7 @@ describe("Patient Consultation in multiple combination", () => { const patientIpNumber = `${Math.floor(Math.random() * 90 + 10)}/${Math.floor(Math.random() * 9000 + 1000)}`; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts b/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts index 9e234c860da..b7ef6936804 100644 --- a/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationDischarge.cy.ts @@ -20,7 +20,7 @@ describe("Patient Discharge based on multiple reason", () => { const doctorName = "Custom Doctor"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts b/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts index 474af286b21..9dd43da4e13 100644 --- a/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts +++ b/cypress/e2e/patient_spec/PatientDoctorConnect.cy.ts @@ -13,7 +13,7 @@ describe("Patient Doctor Connect in consultation page", () => { const teleIcuUser = "Tester Doctor"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientDoctorNotes.cy.ts b/cypress/e2e/patient_spec/PatientDoctorNotes.cy.ts index d227d68ddcb..4fe19329af1 100644 --- a/cypress/e2e/patient_spec/PatientDoctorNotes.cy.ts +++ b/cypress/e2e/patient_spec/PatientDoctorNotes.cy.ts @@ -14,7 +14,7 @@ describe("Patient Discussion notes in the consultation page", () => { const discussionNotesSuccessMessage = "Note added successfully"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientFileUpload.cy.ts b/cypress/e2e/patient_spec/PatientFileUpload.cy.ts index dd3098163ec..ebf85d2cfad 100644 --- a/cypress/e2e/patient_spec/PatientFileUpload.cy.ts +++ b/cypress/e2e/patient_spec/PatientFileUpload.cy.ts @@ -18,7 +18,7 @@ function runTests( const patientNameTwo = "Dummy Patient Four"; const patientNameThree = "Dummy Patient Five"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -63,7 +63,7 @@ function runTests( it("User-level Based Permission for File Modification", () => { // Login as Nurse 1 - loginPage.login("dummynurse1", "Coronasafe@123"); + loginPage.loginByRole("nurse"); cy.reload(); // Visit the patient details page patientPage.visitPatient(patientNameThree); @@ -85,13 +85,13 @@ function runTests( cy.closeNotification(); patientFileUpload.verifyUploadFilePresence(newFileName); // Login as Nurse 2 - loginPage.login("dummynurse2", "Coronasafe@123"); + loginPage.loginByRole("nurse2"); cy.reload(); // Verify the file edit option is not available patientFileUpload.verifyUploadFilePresence(newFileName); patientFileUpload.verifyFileRenameOption(false); // Login as District Admin - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.reload(); // Verify the file edit option is available patientFileUpload.verifyUploadFilePresence(newFileName); diff --git a/cypress/e2e/patient_spec/PatientHomepage.cy.ts b/cypress/e2e/patient_spec/PatientHomepage.cy.ts index e2c32098f1c..a120e282a2d 100644 --- a/cypress/e2e/patient_spec/PatientHomepage.cy.ts +++ b/cypress/e2e/patient_spec/PatientHomepage.cy.ts @@ -27,7 +27,7 @@ describe("Patient Homepage present functionalities", () => { const patientToDateBadge = "2023-12-07"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -157,7 +157,7 @@ describe("Patient Homepage present functionalities", () => { patientHome.verifyPatientExportRequest(); }); - it("Verify the functionality of the patient tab pagination", () => { + it("Test Pagination on Patient List Page", () => { let firstPatientPageOne: string; cy.get('[data-cy="patient"]') .first() @@ -165,6 +165,7 @@ describe("Patient Homepage present functionalities", () => { .then((patientOne: string) => { firstPatientPageOne = patientOne.trim(); pageNavigation.navigateToNextPage(); + cy.wait(2000); pageNavigation.verifyCurrentPageNumber(2); cy.get('[data-cy="patient"]') .first() diff --git a/cypress/e2e/patient_spec/PatientInvestigation.cy.ts b/cypress/e2e/patient_spec/PatientInvestigation.cy.ts index 350614d0c9a..27d68aa257f 100644 --- a/cypress/e2e/patient_spec/PatientInvestigation.cy.ts +++ b/cypress/e2e/patient_spec/PatientInvestigation.cy.ts @@ -10,7 +10,7 @@ describe("Patient Investigation Creation from Patient consultation page", () => const patientName = "Dummy Patient Thirteen"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts index cfb739c8343..ec5aef250dc 100644 --- a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts +++ b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts @@ -41,7 +41,7 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const domicilaryPatient = "Dummy Patient Eleven"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientPrescription.cy.ts b/cypress/e2e/patient_spec/PatientPrescription.cy.ts index 96e6e23b1aa..61f8067eea0 100644 --- a/cypress/e2e/patient_spec/PatientPrescription.cy.ts +++ b/cypress/e2e/patient_spec/PatientPrescription.cy.ts @@ -15,7 +15,7 @@ const medicineIndicator = "Test Indicator"; describe("Patient Medicine Administration", () => { before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index c3647ceac3a..a7c869401cd 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -40,10 +40,12 @@ describe("Patient Creation with consultation", () => { const patientDateOfBirth = "01012001"; const patientMenstruationStartDate = getRelativeDateString(-10); const patientDateOfDelivery = getRelativeDateString(-20); - const patientOneName = "Patient With No Consultation"; + const patientOneName = "Great Napolean 14"; const patientOneGender = "Male"; const patientOneUpdatedGender = "Female"; - const patientOneAddress = "Test Patient Address"; + const patientOneAddress = `149/J, 3rd Block, + Aluva + Ernakulam, Kerala - 682001`; const patientOnePincode = "682001"; const patientOneState = "Kerala"; const patientOneDistrict = "Ernakulam"; @@ -68,7 +70,7 @@ describe("Patient Creation with consultation", () => { const patientOccupation = "Student"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -130,6 +132,7 @@ describe("Patient Creation with consultation", () => { "Middle Class", "Family member", ); + patientMedicalHistory.verifyPatientMedicalDetails( patientOnePresentHealth, patientOneOngoingMedication, @@ -214,11 +217,9 @@ describe("Patient Creation with consultation", () => { patientMedicalHistory.verifyNoSymptosPresent("Diabetes"); // verify insurance details and dedicatd page cy.get("[data-testid=patient-details]") - .contains("member id") + .contains("Member ID") .scrollIntoView(); cy.wait(2000); - patientInsurance.clickPatientInsuranceViewDetail(); - cy.wait(3000); patientInsurance.verifyPatientPolicyDetails( patientOneFirstSubscriberId, patientOneFirstPolicyId, diff --git a/cypress/e2e/resource_spec/ResourcesAdvanceFilters.cy.ts b/cypress/e2e/resource_spec/ResourcesAdvanceFilters.cy.ts index aafc03680ee..33e11b4987a 100644 --- a/cypress/e2e/resource_spec/ResourcesAdvanceFilters.cy.ts +++ b/cypress/e2e/resource_spec/ResourcesAdvanceFilters.cy.ts @@ -1,6 +1,10 @@ +import LoginPage from "pageobject/Login/LoginPage"; + +const loginPage = new LoginPage(); + describe("Resource filter", () => { before(() => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts b/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts index 299d753d720..23077a71ed8 100644 --- a/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts +++ b/cypress/e2e/resource_spec/ResourcesHomepage.cy.ts @@ -1,3 +1,5 @@ +import FacilityHome from "pageobject/Facility/FacilityHome"; + import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import LoginPage from "../../pageobject/Login/LoginPage"; import ResourcePage from "../../pageobject/Resource/ResourcePage"; @@ -7,10 +9,11 @@ describe("Resource Page", () => { const loginPage = new LoginPage(); const resourcePage = new ResourcePage(); const facilityPage = new FacilityPage(); + const facilityHome = new FacilityHome(); const phone_number = "9999999999"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -39,8 +42,8 @@ describe("Resource Page", () => { }); it("Create a resource request", () => { - cy.visit("/facility"); - cy.get("#search").click().type("dummy facility 40"); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch("dummy facility 40"); cy.intercept("GET", "**/api/v1/facility/**").as("loadFacilities"); cy.get("#facility-details").click(); cy.wait("@loadFacilities").its("response.statusCode").should("eq", 200); diff --git a/cypress/e2e/sample_test_spec/SampleTestAdvanceFilters.cy.ts b/cypress/e2e/sample_test_spec/SampleTestAdvanceFilters.cy.ts index ad2e57b7433..562eb22a75e 100644 --- a/cypress/e2e/sample_test_spec/SampleTestAdvanceFilters.cy.ts +++ b/cypress/e2e/sample_test_spec/SampleTestAdvanceFilters.cy.ts @@ -1,6 +1,10 @@ +import LoginPage from "pageobject/Login/LoginPage"; + +const loginPage = new LoginPage(); + describe("Sample Filter", () => { before(() => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/sample_test_spec/SampleTestHomepage.cy.ts b/cypress/e2e/sample_test_spec/SampleTestHomepage.cy.ts index 98d69ccff41..491da5ee7ad 100644 --- a/cypress/e2e/sample_test_spec/SampleTestHomepage.cy.ts +++ b/cypress/e2e/sample_test_spec/SampleTestHomepage.cy.ts @@ -1,6 +1,10 @@ +import LoginPage from "pageobject/Login/LoginPage"; + +const loginPage = new LoginPage(); + describe("Sample List", () => { before(() => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts b/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts index da4d8aabd20..a4b26d328e5 100644 --- a/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts +++ b/cypress/e2e/sample_test_spec/SampleTestRequest.cy.ts @@ -24,7 +24,7 @@ describe("Sample Test", () => { const sampleTestResult = "Awaiting"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/shifting_spec/ShiftingAdvanceFilters.cy.ts b/cypress/e2e/shifting_spec/ShiftingAdvanceFilters.cy.ts index 01c1cbebd3a..1a773350b2d 100644 --- a/cypress/e2e/shifting_spec/ShiftingAdvanceFilters.cy.ts +++ b/cypress/e2e/shifting_spec/ShiftingAdvanceFilters.cy.ts @@ -1,12 +1,14 @@ +import LoginPage from "pageobject/Login/LoginPage"; import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; import ShiftingPage from "../../pageobject/Shift/ShiftFilters"; describe("Shifting section filter", () => { const shiftingPage = new ShiftingPage(); + const loginPage = new LoginPage(); before(() => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/shifting_spec/ShiftingHomepage.cy.ts b/cypress/e2e/shifting_spec/ShiftingHomepage.cy.ts index a0a637f97f1..a94de68591d 100644 --- a/cypress/e2e/shifting_spec/ShiftingHomepage.cy.ts +++ b/cypress/e2e/shifting_spec/ShiftingHomepage.cy.ts @@ -1,6 +1,10 @@ +import LoginPage from "pageobject/Login/LoginPage"; + +const loginPage = new LoginPage(); + describe("Shifting Page", () => { before(() => { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index f495a136d97..947d5b6ce87 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -1,4 +1,8 @@ -import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; +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 { UserCreationPage } from "../../pageobject/Users/UserCreation"; @@ -11,13 +15,15 @@ import { describe("User Creation", () => { const userPage = new UserPage(); const loginPage = new LoginPage(); + const userProfilePage = new UserProfilePage(); + const manageUserPage = new ManageUserPage(); const userCreationPage = new UserCreationPage(); const facilityPage = new FacilityPage(); - const assetSearchPage = new AssetSearchPage(); - const phone_number = generatePhoneNumber(); - const emergency_phone_number = generateEmergencyPhoneNumber(); + const facilityHome = new FacilityHome(); + const phoneNumber = generatePhoneNumber(); + const emergencyPhoneNumber = generateEmergencyPhoneNumber(); const fillFacilityName = "Dummy Facility 40"; - const makeid = (length: number) => { + const makeId = (length: number) => { let result = ""; const characters = "abcdefghijklmnopqrstuvwxyz0123456789"; const charactersLength = characters.length; @@ -26,8 +32,8 @@ describe("User Creation", () => { } return result; }; - const username = makeid(8); - const alreadylinkedusersviews = [ + const username = makeId(8); + const alreadyLinkedUsersViews = [ "devdoctor", "devstaff2", "devdistrictadmin", @@ -53,9 +59,28 @@ describe("User Creation", () => { "This field is required", "Please enter valid phone number", ]; + const userName = "devdistrictadmin"; + const firstName = "District Editted"; + const lastName = "Cypress"; + const gender = "Male"; + const email = "test@test.com"; + const password = "Test@123"; + const qualification = "MBBS"; + const experience = "2"; + const regNo = "123456789"; + const newUserFirstName = "cypress test"; + const newUserLastName = "staff user"; + const state = "Kerala"; + const district = "Ernakulam"; + const role = "Doctor"; + const homeFacility = "Dummy Shifting Center"; + const weeklyWorkingHrs = "14"; + const dob = "01011998"; + const formattedDob = "01/01/1998"; + const newUserDob = "25081999"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -66,135 +91,107 @@ describe("User Creation", () => { }); it("Update the existing user profile and verify its reflection", () => { - userCreationPage.clickElementById("user-profile-name"); - userCreationPage.clickElementById("profile-button"); - userCreationPage.verifyElementContainsText( - "username-profile-details", - "devdistrictadmin", - ); - userCreationPage.clickElementById("edit-cancel-profile-button"); - userCreationPage.typeIntoElementByIdPostClear( - "firstName", - "District Editted", - ); - userCreationPage.typeIntoElementByIdPostClear("lastName", "Cypress"); - userCreationPage.selectDropdownOption("gender", "Male"); - userCreationPage.typeIntoElementByIdPostClear("phoneNumber", phone_number); - userCreationPage.typeIntoElementByIdPostClear( - "altPhoneNumber", - emergency_phone_number, - ); - userCreationPage.typeIntoElementByIdPostClear("email", "test@test.com"); - userCreationPage.typeIntoElementByIdPostClear("weekly_working_hours", "14"); - userCreationPage.typeIntoElementByIdPostClearDob( - "date_of_birth", - "01011998", - ); - userCreationPage.clickElementById("submit"); - userCreationPage.verifyElementContainsText( - "contactno-profile-details", - "+91" + phone_number, - ); - userCreationPage.verifyElementContainsText( - "whatsapp-profile-details", - "+91" + emergency_phone_number, - ); - userCreationPage.verifyElementContainsText( - "firstname-profile-details", - "District Editted", - ); - userCreationPage.verifyElementContainsText( - "lastname-profile-details", - "Cypress", - ); - userCreationPage.verifyElementContainsText( - "date_of_birth-profile-details", - "01/01/1998", - ); - userCreationPage.verifyElementContainsText( - "emailid-profile-details", - "test@test.com", - ); - userCreationPage.verifyElementContainsText( - "gender-profile-details", - "Male", - ); - userCreationPage.verifyElementContainsText( - "averageworkinghour-profile-details", - "14", - ); + manageUserPage.navigateToProfile(); + cy.verifyContentPresence("#username-profile-details", [userName]); + userProfilePage.clickEditProfileButton(); + userCreationPage.clearFirstName(); + userCreationPage.typeFirstName(firstName); + userCreationPage.clearLastName(); + userCreationPage.typeLastName(lastName); + userProfilePage.selectGender(gender); + userProfilePage.clearPhoneNumber(); + userProfilePage.typePhoneNumber(phoneNumber); + userProfilePage.clearAltPhoneNumber(); + userProfilePage.typeWhatsappNumber(emergencyPhoneNumber); + userProfilePage.clearEmail(); + userProfilePage.typeEmail(email); + userProfilePage.clearWorkingHours(); + userProfilePage.typeWorkingHours(weeklyWorkingHrs); + userProfilePage.typeDateOfBirth(dob); + cy.intercept("PATCH", "/api/v1/users/*").as("updateUser"); + userProfilePage.clickUpdateButton(); + cy.wait("@updateUser").its("response.statusCode").should("eq", 200); + cy.verifyContentPresence("#contactno-profile-details", [ + "+91" + phoneNumber, + ]); + cy.verifyContentPresence("#whatsapp-profile-details", [ + "+91" + emergencyPhoneNumber, + ]); + cy.verifyContentPresence("#firstname-profile-details", [firstName]); + cy.verifyContentPresence("#lastname-profile-details", [lastName]); + cy.verifyContentPresence("#date_of_birth-profile-details", [formattedDob]); + cy.verifyContentPresence("#emailid-profile-details", [email]); + cy.verifyContentPresence("#gender-profile-details", [gender]); + cy.verifyContentPresence("#averageworkinghour-profile-details", [ + weeklyWorkingHrs, + ]); }); it("Update the existing user profile Form Mandatory File Error", () => { - userCreationPage.clickElementById("user-profile-name"); - userCreationPage.clickElementById("profile-button"); - userCreationPage.clickElementById("edit-cancel-profile-button"); - userCreationPage.clearIntoElementById("firstName"); - userCreationPage.clearIntoElementById("lastName"); - userCreationPage.clearIntoElementById("phoneNumber"); - userCreationPage.clearIntoElementById("altPhoneNumber"); - userCreationPage.clearIntoElementById("weekly_working_hours"); - userCreationPage.clickElementById("submit"); - userCreationPage.verifyErrorMessages(EXPECTED_PROFILE_ERROR_MESSAGES); + 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.clickElementById("addUserButton"); - userCreationPage.selectFacility("Dummy Shifting Center"); - userCreationPage.typeIntoElementById("username", username); - userCreationPage.typeIntoElementById("password", "Test@123"); - userCreationPage.selectHomeFacility("Dummy Shifting Center"); - userCreationPage.typeIntoElementById("phone_number", phone_number); - userCreationPage.setInputDate("date_of_birth", "25081999"); - userCreationPage.selectDropdownOption("user_type", "Doctor"); - userCreationPage.typeIntoElementById("c_password", "Test@123"); - userCreationPage.typeIntoElementById("qualification", "MBBS"); - userCreationPage.typeIntoElementById("doctor_experience_commenced_on", "2"); - userCreationPage.typeIntoElementById( - "doctor_medical_council_registration", - "123456789", - ); - userCreationPage.typeIntoElementById("first_name", "cypress test"); - userCreationPage.typeIntoElementById("last_name", "staff user"); - userCreationPage.typeIntoElementById("email", "test@test.com"); - userCreationPage.selectDropdownOption("gender", "Male"); - userCreationPage.selectDropdownOption("state", "Kerala"); - userCreationPage.selectDropdownOption("district", "Ernakulam"); - userCreationPage.clickElementById("submit"); - userCreationPage.verifyNotification("User added successfully"); + userCreationPage.clickAddUserButton(); + userCreationPage.selectFacility(homeFacility); + userCreationPage.typeUserName(username); + userCreationPage.typePassword(password); + userCreationPage.typeConfirmPassword(password); + userCreationPage.selectHomeFacility(homeFacility); + userPage.typeInPhoneNumber(phoneNumber); + userProfilePage.typeDateOfBirth(newUserDob); + userCreationPage.selectUserType(role); + userProfilePage.typeQualification(qualification); + userProfilePage.typeDoctorYoE(experience); + userProfilePage.typeMedicalCouncilRegistration(regNo); + userPage.typeInFirstName(newUserFirstName); + userPage.typeInLastName(newUserLastName); + userProfilePage.typeEmail(email); + userCreationPage.selectGender(gender); + userCreationPage.selectState(state); + userCreationPage.selectDistrict(district); + cy.intercept("POST", "/api/v1/users/add_user/").as("createUser"); + userCreationPage.clickSaveUserButton(); + cy.wait("@createUser").its("response.statusCode").should("eq", 201); + cy.verifyNotification("User added successfully"); userPage.typeInSearchInput(username); userPage.checkUsernameText(username); - userCreationPage.verifyElementContainsText("name", "cypress test"); - userCreationPage.verifyElementContainsText("role", "Doctor"); - userCreationPage.verifyElementContainsText("district", "Ernakulam"); - userCreationPage.verifyElementContainsText( - "home_facility", - "Dummy Shifting Center", - ); - userCreationPage.verifyElementContainsText("qualification", "MBBS"); - userCreationPage.verifyElementContainsText("doctor-experience", "2"); - userCreationPage.verifyElementContainsText( - "medical-council-registration", - "123456789", - ); + cy.verifyContentPresence("#name", [newUserFirstName]); + cy.verifyContentPresence("#role", [role]); + cy.verifyContentPresence("#district", [district]); + cy.verifyContentPresence("#home_facility", [homeFacility]); + cy.verifyContentPresence("#qualification", [qualification]); + cy.verifyContentPresence("#doctor-experience", [experience]); + cy.verifyContentPresence("#medical-council-registration", [regNo]); }); it("create new user form throwing mandatory field error", () => { - userCreationPage.clickElementById("addUserButton"); - userCreationPage.clickElementById("submit"); - cy.wait(2000); - userCreationPage.verifyErrorMessages(EXPECTED_ERROR_MESSAGES); + userCreationPage.clickAddUserButton(); + userCreationPage.clickSaveUserButton(); + cy.get(".error-text", { timeout: 10000 }).should("be.visible"); + cy.verifyErrorMessages(EXPECTED_ERROR_MESSAGES); }); it("view user redirection from facility page", () => { - cy.visit("/facility"); - assetSearchPage.typeSearchKeyword(fillFacilityName); - assetSearchPage.pressEnter(); - facilityPage.verifyFacilityBadgeContent(fillFacilityName); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch(fillFacilityName); + advanceFilters.verifyFilterBadgePresence( + "Facility/District Name", + fillFacilityName, + true, + ); facilityPage.visitAlreadyCreatedFacility(); facilityPage.clickManageFacilityDropdown(); facilityPage.clickViewUsersOption(); - userPage.verifyMultipleBadgesWithSameId(alreadylinkedusersviews); + userPage.verifyMultipleBadgesWithSameId(alreadyLinkedUsersViews); }); afterEach(() => { diff --git a/cypress/e2e/users_spec/UsersHomepage.cy.ts b/cypress/e2e/users_spec/UsersHomepage.cy.ts index b32bb859e4e..8d86482645b 100644 --- a/cypress/e2e/users_spec/UsersHomepage.cy.ts +++ b/cypress/e2e/users_spec/UsersHomepage.cy.ts @@ -20,7 +20,7 @@ describe("User Homepage", () => { const doctorUserName = "dev-doctor2"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -43,30 +43,28 @@ describe("User Homepage", () => { advanceFilters.applySelectedFilter(); userPage.checkUsernameText(nurseUserName); // Verify the badges related to the data - userPage.verifyDataTestIdText("First Name", `First Name: ${firstName}`); - userPage.verifyDataTestIdText("Last Name", `Last Name: ${lastName}`); - userPage.verifyDataTestIdText( - "Phone Number", - `Phone Number: +91${phoneNumber}`, - ); - userPage.verifyDataTestIdText( + advanceFilters.verifyFilterBadgePresence("First Name", firstName, true); + advanceFilters.verifyFilterBadgePresence("Last Name", lastName, true); + advanceFilters.verifyFilterBadgePresence("Phone Number", phoneNumber, true); + advanceFilters.verifyFilterBadgePresence( "WhatsApp no.", - `WhatsApp no.: +91${altPhoneNumber}`, + altPhoneNumber, + true, ); - userPage.verifyDataTestIdText("Role", `Role: ${role}`); - userPage.verifyDataTestIdText( + advanceFilters.verifyFilterBadgePresence("Role", role, true); + advanceFilters.verifyFilterBadgePresence( "Home Facility", - `Home Facility: ${homeFacility}`, + homeFacility, + true, ); advanceFilters.clickAdvancedFiltersButton(); advanceFilters.clickClearAdvanceFilters(); - userPage.verifyDataTestIdNotVisible("First Name"); - userPage.verifyDataTestIdNotVisible("Last Name"); - userPage.verifyDataTestIdNotVisible("Phone Number"); - userPage.verifyDataTestIdNotVisible("WhatsApp no."); - userPage.verifyDataTestIdNotVisible("Role"); - userPage.verifyDataTestIdNotVisible("Home Facility"); - userPage.verifyDataTestIdNotVisible("District"); + advanceFilters.verifyFilterBadgePresence("First Name", "", false); + advanceFilters.verifyFilterBadgePresence("Last Name", "", false); + advanceFilters.verifyFilterBadgePresence("Phone Number", "", false); + advanceFilters.verifyFilterBadgePresence("WhatsApp no.", "", false); + advanceFilters.verifyFilterBadgePresence("Role", "", false); + advanceFilters.verifyFilterBadgePresence("Home Facility", "", false); }); it("Search by username", () => { diff --git a/cypress/e2e/users_spec/UsersManage.cy.ts b/cypress/e2e/users_spec/UsersManage.cy.ts index 40d436be0a6..41557e87756 100644 --- a/cypress/e2e/users_spec/UsersManage.cy.ts +++ b/cypress/e2e/users_spec/UsersManage.cy.ts @@ -1,21 +1,21 @@ +import FacilityHome from "pageobject/Facility/FacilityHome"; import { advanceFilters } from "pageobject/utils/advanceFilterHelpers"; import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; -import { UserCreationPage } from "../../pageobject/Users/UserCreation"; import { UserPage } from "../../pageobject/Users/UserSearch"; describe("Manage User", () => { const loginPage = new LoginPage(); const userPage = new UserPage(); const manageUserPage = new ManageUserPage(); + const facilityHome = new FacilityHome(); const usernameToLinkFacilitydoc1 = "dummydoctor4"; const usernameToLinkFacilitydoc2 = "dummydoctor5"; const usernameToLinkFacilitydoc3 = "dummydoctor6"; const usernameToLinkSkill = "devdoctor"; const firstNameUserSkill = "Dev"; const lastNameUserSkill = "Doctor"; - const userCreationPage = new UserCreationPage(); const usernameforworkinghour = "devdistrictadmin"; const usernamerealname = "Dummy Doctor"; const facilitytolinkusername = "Dummy Shifting Center"; @@ -24,7 +24,7 @@ describe("Manage User", () => { const linkedskill = "General Medicine"; before(() => { - loginPage.loginAsDistrictAdmin(); + loginPage.loginByRole("districtAdmin"); cy.saveLocalStorage(); }); @@ -48,10 +48,9 @@ describe("Manage User", () => { manageUserPage.clickCloseSlideOver(); cy.wait(5000); manageUserPage.navigateToProfile(); - userCreationPage.verifyElementContainsText( - "username-profile-details", + cy.verifyContentPresence("#username-profile-details", [ usernameforworkinghour, - ); + ]); manageUserPage.assertSkillInAlreadyLinkedSkills(linkedskill); }); @@ -71,9 +70,9 @@ describe("Manage User", () => { manageUserPage.assertSkillInAddedUserSkills(linkedskill); manageUserPage.clickCloseSlideOver(); // verifying the doctor connect - manageUserPage.navigateToFacility(); - manageUserPage.typeFacilitySearch(facilitytolinkskill); - manageUserPage.assertFacilityInCard(facilitytolinkskill); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch(facilitytolinkskill); + facilityHome.assertFacilityInCard(facilitytolinkskill); manageUserPage.clickFacilityPatients(); manageUserPage.clickDoctorConnectButton(); manageUserPage.assertSkillIndoctorconnect(linkedskill); @@ -137,9 +136,9 @@ describe("Manage User", () => { manageUserPage.clickCloseSlideOver(); // 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 - manageUserPage.navigateToFacility(); - manageUserPage.typeFacilitySearch(facilitytolinkusername); - manageUserPage.assertFacilityInCard(facilitytolinkusername); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch(facilitytolinkusername); + facilityHome.assertFacilityInCard(facilitytolinkusername); manageUserPage.clickFacilityPatients(); manageUserPage.clickDoctorConnectButton(); manageUserPage.assertDoctorConnectVisibility(usernamerealname); diff --git a/cypress/e2e/users_spec/UsersProfile.cy.ts b/cypress/e2e/users_spec/UsersProfile.cy.ts index 35771024180..551fba4c0f1 100644 --- a/cypress/e2e/users_spec/UsersProfile.cy.ts +++ b/cypress/e2e/users_spec/UsersProfile.cy.ts @@ -1,3 +1,5 @@ +import FacilityHome from "pageobject/Facility/FacilityHome"; + import LoginPage from "../../pageobject/Login/LoginPage"; import ManageUserPage from "../../pageobject/Users/ManageUserPage"; import UserProfilePage from "../../pageobject/Users/UserProfilePage"; @@ -6,6 +8,7 @@ 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"; @@ -19,7 +22,7 @@ describe("Manage User Profile", () => { const facilitySearch = "Dummy Facility 40"; before(() => { - loginPage.loginAsDevDoctor(); + loginPage.loginByRole("devDoctor"); cy.saveLocalStorage(); }); @@ -32,25 +35,22 @@ describe("Manage User Profile", () => { it("Set Dob, Gender, Email, Phone and Working Hours for a user and verify its reflection in user profile", () => { userProfilePage.clickEditProfileButton(); - userProfilePage.typedate_of_birth(date_of_birth); + userProfilePage.typeDateOfBirth(date_of_birth); userProfilePage.selectGender(gender); userProfilePage.typeEmail(email); - userProfilePage.typePhone(phone); - userProfilePage.typeWhatsApp(phone); + userProfilePage.typePhoneNumber(phone); + userProfilePage.typeWhatsappNumber(phone); userProfilePage.typeWorkingHours(workinghours); userProfilePage.typeQualification(qualification); userProfilePage.typeDoctorYoE(doctorYoE); userProfilePage.typeMedicalCouncilRegistration(medicalCouncilRegistration); - userProfilePage.clickUpdateButton(); - cy.verifyNotification("Details updated successfully"); - - userProfilePage.assertdate_of_birth("01/01/1999"); + userProfilePage.assertDateOfBirth("01/01/1999"); userProfilePage.assertGender(gender); userProfilePage.assertEmail(email); - userProfilePage.assertPhone(phone); - userProfilePage.assertWhatsApp(phone); + userProfilePage.assertPhoneNumber(phone); + userProfilePage.assertAltPhoneNumber(phone); userProfilePage.assertWorkingHours(workinghours); }); @@ -68,9 +68,9 @@ describe("Manage User Profile", () => { userProfilePage.clickUpdateButton(); userProfilePage.assertVideoConnectLink("https://www.test.com"); // Go to particular facility doctor connect and verify the video connect link is present - manageUserPage.navigateToFacility(); - manageUserPage.typeFacilitySearch(facilitySearch); - manageUserPage.assertFacilityInCard(facilitySearch); + facilityHome.navigateToFacilityHomepage(); + facilityHome.typeFacilitySearch(facilitySearch); + facilityHome.assertFacilityInCard(facilitySearch); manageUserPage.clickFacilityPatients(); manageUserPage.clickDoctorConnectButton(); manageUserPage.assertVideoConnectLink("Dev Doctor", "https://www.test.com"); diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index ec631768154..7a37f65dff7 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -31,39 +31,68 @@ export class AssetPage { }); } - enterAssetDetails( - name: string, - description: string, - workingStatus: string, - qrId: string, - manufacturer: string, - warranty: string, - supportName: string, - supportPhone: string, - supportEmail: string, - vendorName: string, - serialNumber: string, - lastServicedOn: string, - notes: string, - ) { - cy.get("[data-testid=asset-name-input] input").type(name); - cy.get("[data-testid=asset-description-input] textarea").type(description); - cy.get("[data-testid=asset-working-status-input] li") - .contains(workingStatus) - .click(); - cy.get("[data-testid=asset-qr-id-input] input").type(qrId); - cy.get("[data-testid=asset-manufacturer-input] input").type(manufacturer); - cy.get("[data-testid=asset-warranty-input] input").type(warranty); - cy.get("[data-testid=asset-support-name-input] input").type(supportName); - cy.get("#customer-support-phone-div").type(supportPhone); - cy.get("[data-testid=asset-support-email-input] input").type(supportEmail); - cy.get("[data-testid=asset-vendor-name-input] input").type(vendorName); - cy.get("[data-testid=asset-serial-number-input] input").type(serialNumber); - cy.clickAndTypeDate( - "[data-testid=asset-last-serviced-on-input]", - lastServicedOn, - ); - cy.get("[data-testid=asset-notes-input] textarea").type(notes); + enterAssetDetails({ + name, + description, + workingStatus, + qrId, + manufacturer, + warranty, + supportName, + supportPhone, + supportEmail, + vendorName, + serialNumber, + lastServicedOn, + notes, + }: { + name?: string; + description?: string; + workingStatus?: string; + qrId?: string; + manufacturer?: string; + warranty?: string; + supportName?: string; + supportPhone?: string; + supportEmail?: string; + vendorName?: string; + serialNumber?: string; + lastServicedOn?: string; + notes?: string; + }) { + if (name) cy.get("[data-testid=asset-name-input] input").type(name); + if (description) + cy.get("[data-testid=asset-description-input] textarea").type( + description, + ); + if (workingStatus) + cy.get("[data-testid=asset-working-status-input] li") + .contains(workingStatus) + .click(); + if (qrId) cy.get("[data-testid=asset-qr-id-input] input").type(qrId); + if (manufacturer) + cy.get("[data-testid=asset-manufacturer-input] input").type(manufacturer); + if (warranty) + cy.get("[data-testid=asset-warranty-input] input").type(warranty); + if (supportName) + cy.get("[data-testid=asset-support-name-input] input").type(supportName); + if (supportPhone) cy.get("#customer-support-phone-div").type(supportPhone); + if (supportEmail) + cy.get("[data-testid=asset-support-email-input] input").type( + supportEmail, + ); + if (vendorName) + cy.get("[data-testid=asset-vendor-name-input] input").type(vendorName); + if (serialNumber) + cy.get("[data-testid=asset-serial-number-input] input").type( + serialNumber, + ); + if (lastServicedOn) + cy.clickAndTypeDate( + "[data-testid=asset-last-serviced-on-input]", + lastServicedOn, + ); + if (notes) cy.get("[data-testid=asset-notes-input] textarea").type(notes); } interceptAssetCreation() { @@ -208,51 +237,6 @@ export class AssetPage { ); } - selectImportFacility(facilityName: string) { - cy.get("input[name='facilities']") - .type(facilityName) - .then(() => { - cy.get("[role='option']").contains(facilityName).click(); - }); - } - - selectassetimportbutton() { - cy.get("[data-testid=import-asset-button]").click(); - } - - selectJsonExportButton() { - cy.intercept("GET", "**/api/v1/asset/?**json=true**").as("getJsonexport"); - cy.get("#export-json-option").click(); - cy.wait("@getJsonexport").its("response.statusCode").should("eq", 200); - } - - selectCsvExportButton() { - cy.intercept("GET", "**/api/v1/asset/?**csv=true**").as("getCsvexport"); - cy.get("#export-csv-option").click(); - cy.wait("@getCsvexport").its("response.statusCode").should("eq", 200); - } - - selectImportOption() { - cy.get(".import-assets-button").click(); - } - - importAssetFile() { - cy.get("[data-testid=import-file]") - .selectFile("cypress/fixtures/sample-asset.xlsx", { force: true }) - .wait(100); - } - - selectImportLocation(locationName: string) { - cy.get("[data-testid=select-import-location]").click(); - cy.get("li[role=option]").contains(locationName).click(); - } - - clickImportAsset() { - cy.intercept("POST", "**/api/v1/asset/").as("importAsset"); - cy.get("#submit").contains("Import").click(); - cy.wait("@importAsset").its("response.statusCode").should("eq", 201); - } - clickupdatedetailbutton() { cy.get("[data-testid=asset-update-button]").click(); } diff --git a/cypress/pageobject/Asset/AssetFilters.ts b/cypress/pageobject/Asset/AssetFilters.ts index 7e7b5013d5d..b562841a598 100644 --- a/cypress/pageobject/Asset/AssetFilters.ts +++ b/cypress/pageobject/Asset/AssetFilters.ts @@ -1,68 +1,7 @@ export class AssetFilters { - filterAssets( - facilityName: string, - assetStatus: string, - assetClass: string, - assetLocation: string, - ) { - cy.contains("Advanced Filters").click(); - cy.get("input[name='Facilities']") - .click() - .type(facilityName) - .then(() => { - cy.get("[role='option']").contains(facilityName).click(); - }); - cy.get("#asset-status") - .click() - .then(() => { - cy.get("[role='option']").contains(assetStatus).click(); - }); - cy.get("#asset-class") - .click() - .then(() => { - cy.get("[role='option']").contains(assetClass).click(); - }); - cy.get("#Facilities-location") - .click() - .type(assetLocation) - .then(() => { - cy.get("[role='option']").contains(assetLocation).click(); - }); - cy.contains("Apply").click(); - } - clearFilters() { - cy.intercept("GET", "**/api/v1/asset/**").as("clearAssets"); - cy.get("#clear-filter").click(); - cy.wait("@clearAssets").its("response.statusCode").should("eq", 200); - cy.location("pathname").should("match", /\/assets$/); - cy.url().then((url) => { - const queryParams = new URL(url).searchParams; - let allEmpty = true; - const blacklistedKeys = ["page", "limit", "offset"]; - - queryParams.forEach((value, key) => { - if (value !== "" && !blacklistedKeys.includes(key)) { - allEmpty = false; - } - }); - - expect(allEmpty).to.be.true; - }); - } - clickslideoverbackbutton() { - cy.get("#close-slide-over").click(); - } - // Assertions - assertFacilityText(text: string) { - cy.get("[data-testid=Facility]").should("contain", text); - } - assertAssetClassText(text: string) { - cy.get("[data-testid='Asset Class']").should("contain", text); - } - assertStatusText(text: string) { - cy.get("[data-testid=Status]").should("contain", text); - } - assertLocationText(text: string) { - cy.get("[data-testid=Location]").should("contain", text); + filterAssets(assetStatus: string, assetClass: string, assetLocation: string) { + cy.clickAndSelectOption("#asset-status", assetStatus); + cy.clickAndSelectOption("#asset-class", assetClass); + cy.typeAndSelectOption("#Facilities-location", assetLocation); } } diff --git a/cypress/pageobject/Asset/AssetHome.ts b/cypress/pageobject/Asset/AssetHome.ts new file mode 100644 index 00000000000..e127b785100 --- /dev/null +++ b/cypress/pageobject/Asset/AssetHome.ts @@ -0,0 +1,112 @@ +export class AssetHome { + clickAssetByName(assetName: string) { + cy.intercept("GET", "**/api/v1/asset/**").as("clearAssets"); + cy.get("[data-testid='created-asset-list']").contains(assetName).click(); + cy.wait("@clearAssets").its("response.statusCode").should("eq", 200); + } + + typeAssetSearch(name: string) { + cy.get("#asset-search").click().type(name); + } + + verifyAssetIsPresent(assetName: string) { + cy.get("[data-testid=created-asset-list]") + .first() + .should("contain", assetName); + } + + clickAssetDetailsUpdateButton() { + cy.verifyAndClickElement("[data-testid='asset-update-button']", "Update"); + } + + clearAndTypeQRCode(qrCode: string) { + cy.get("#qr_code_id").clear(); + cy.get("#qr_code_id").click().type(qrCode); + } + + clearAndTypeSerialNumber(serialNumber: string) { + cy.get("#serial-number").clear(); + cy.get("#serial-number").click().type(serialNumber); + } + + clickAssetSubmitButton() { + cy.intercept("GET", "**/api/v1/asset/**").as("getAssets"); + cy.get("#submit").click(); + cy.wait("@getAssets").its("response.statusCode").should("eq", 200); + } + + clickAssetUpdateSubmitButton() { + cy.intercept("GET", "**/api/v1/asset/**").as("getAssets"); + cy.clickSubmitButton("Update"); + cy.wait("@getAssets").its("response.statusCode").should("eq", 200); + } + + navigateToAssetsPage() { + cy.visit("/assets"); + } + + verifyAssetListContains(dummyCameraText: string) { + cy.get("[data-testid='created-asset-list']").should( + "contain", + dummyCameraText, + ); + } + + scanAssetQR() { + cy.contains("Scan Asset QR").click().wait(1000); + cy.get("video").should("exist"); + cy.get("button").contains("Close Scanner").should("exist").click(); + } + + selectImportFacility(facilityName: string) { + cy.get("input[name='facilities']") + .type(facilityName) + .then(() => { + cy.get("[role='option']").contains(facilityName).click(); + }); + } + + selectAssetImportButton(action: "click" | "verifyNotExist"): void { + const selector = "[data-testid=import-asset-button]"; + if (action === "click") { + cy.get(selector).click(); + } else if (action === "verifyNotExist") { + cy.get(selector).should("not.exist"); + } + } + + selectJsonExportButton() { + cy.intercept("GET", "**/api/v1/asset/?**json=true**").as("getJsonexport"); + cy.get("#export-json-option").click(); + cy.wait("@getJsonexport").its("response.statusCode").should("eq", 200); + } + + selectCsvExportButton() { + cy.intercept("GET", "**/api/v1/asset/?**csv=true**").as("getCsvexport"); + cy.get("#export-csv-option").click(); + cy.wait("@getCsvexport").its("response.statusCode").should("eq", 200); + } + + selectImportOption() { + cy.get(".import-assets-button").click(); + } + + importAssetFile() { + cy.get("[data-testid=import-file]") + .selectFile("cypress/fixtures/sample-asset.xlsx", { force: true }) + .wait(100); + } + + selectImportLocation(locationName: string) { + cy.clickAndSelectOption( + "[data-testid=select-import-location]", + locationName, + ); + } + + clickImportAsset() { + cy.intercept("POST", "**/api/v1/asset/").as("importAsset"); + cy.clickSubmitButton("Import"); + cy.wait("@importAsset").its("response.statusCode").should("eq", 201); + } +} diff --git a/cypress/pageobject/Asset/AssetPagination.ts b/cypress/pageobject/Asset/AssetPagination.ts deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/cypress/pageobject/Asset/AssetQRScan.ts b/cypress/pageobject/Asset/AssetQRScan.ts deleted file mode 100644 index a88f378f02f..00000000000 --- a/cypress/pageobject/Asset/AssetQRScan.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class AssetQRScanPage { - scanAssetQR() { - cy.contains("Scan Asset QR").click().wait(1000); - cy.get("video").should("exist"); - cy.get("button").contains("Close Scanner").should("exist").click(); - } -} diff --git a/cypress/pageobject/Asset/AssetSearch.ts b/cypress/pageobject/Asset/AssetSearch.ts deleted file mode 100644 index 653c7bdd89c..00000000000 --- a/cypress/pageobject/Asset/AssetSearch.ts +++ /dev/null @@ -1,60 +0,0 @@ -export class AssetSearchPage { - typeSearchKeyword(keyword: string) { - cy.get("#search").click().clear(); - cy.get("#search").click().type(keyword); - } - - pressEnter() { - cy.get("[name='search']").type("{enter}"); - } - - clickAssetByName(assetName: string) { - cy.intercept("GET", "**/api/v1/asset/**").as("clearAssets"); - cy.get("[data-testid='created-asset-list']").contains(assetName).click(); - cy.wait("@clearAssets").its("response.statusCode").should("eq", 200); - } - - verifyBadgeContent(expectedText: string) { - cy.get("[data-testid='Name/Serial No./QR ID']").should( - "contain", - expectedText, - ); - } - - verifyAssetIsPresent(assetName: string) { - cy.get("[data-testid=created-asset-list]") - .first() - .should("contain", assetName); - } - - clickUpdateButton() { - cy.get("[data-testid='asset-update-button']").contains("Update").click(); - } - - clearAndTypeQRCode(qrCode: string) { - cy.get("#qr_code_id").clear(); - cy.get("#qr_code_id").click().type(qrCode); - } - - clearAndTypeSerialNumber(serialNumber: string) { - cy.get("#serial-number").clear(); - cy.get("#serial-number").click().type(serialNumber); - } - - clickAssetSubmitButton() { - cy.intercept("GET", "**/api/v1/asset/**").as("getAssets"); - cy.get("#submit").click(); - cy.wait("@getAssets").its("response.statusCode").should("eq", 200); - } - - visitAssetsPage() { - cy.visit("/assets"); - } - - verifyAssetListContains(dummyCameraText: string) { - cy.get("[data-testid='created-asset-list']").should( - "contain", - dummyCameraText, - ); - } -} diff --git a/cypress/pageobject/Facility/FacilityCreation.ts b/cypress/pageobject/Facility/FacilityCreation.ts index 0fddc3f9799..4c064956fcc 100644 --- a/cypress/pageobject/Facility/FacilityCreation.ts +++ b/cypress/pageobject/Facility/FacilityCreation.ts @@ -9,11 +9,6 @@ class FacilityPage { .should("eq", 200); } - typeFacilitySearch(facilityName: string) { - cy.get("#search").click().clear(); - cy.get("#search").click().type(facilityName); - } - selectFacilityType(facility: string) { advanceFilters.selectFacilityType(facility); } @@ -313,29 +308,6 @@ class FacilityPage { cy.get("h1.text-3xl.font-bold", { timeout: 10000 }).should("be.visible"); } - verifyFacilityBadgeContent(expectedText: string) { - cy.get("[data-testid='Facility/District Name']").should( - "contain", - expectedText, - ); - } - - verifyStateBadgeContent(expectedText: string) { - cy.get("[data-testid='State']").should("contain", expectedText); - } - - verifyDistrictBadgeContent(expectedText: string) { - cy.get("[data-testid='District']").should("contain", expectedText); - } - - verifyLocalBodyBadgeContent(expectedText: string) { - cy.get("[data-testid='Local Body']").should("contain", expectedText); - } - - verifyFacilityTypeBadgeContent(expectedText: string) { - cy.get("[data-testid='Facility type']").should("contain", expectedText); - } - verifyfacilitycreateassetredirection() { cy.url().should("include", "/assets/new"); } diff --git a/cypress/pageobject/Facility/FacilityHome.ts b/cypress/pageobject/Facility/FacilityHome.ts index 30f51052370..fe8585b48be 100644 --- a/cypress/pageobject/Facility/FacilityHome.ts +++ b/cypress/pageobject/Facility/FacilityHome.ts @@ -1,7 +1,6 @@ class FacilityHome { // Selectors exportButton = "#export-button"; - searchButton = "#search"; menuItem = "[role='menuitem']"; // Operations @@ -10,8 +9,24 @@ class FacilityHome { cy.get(this.exportButton).click(); } - clickSearchButton() { - cy.get(this.searchButton).click(); + navigateToFacilityHomepage() { + cy.visit("/facility"); + } + + assertFacilityInCard(facilityName: string) { + cy.get("#facility-name-card").should("contain", facilityName); + } + + interceptFacilitySearchReq() { + cy.intercept("GET", "**/api/v1/facility/**").as("searchFacility"); + } + + verifyFacilitySearchReq() { + cy.wait("@searchFacility").its("response.statusCode").should("eq", 200); + } + + typeFacilitySearch(facilityName: string) { + cy.get("#facility-search").click().clear().type(facilityName); } clickMenuItem(itemName: string) { @@ -76,11 +91,6 @@ class FacilityHome { cy.get('[data-test-id="occupancy-badge"]').should("be.visible"); } - verifyAndCloseNotifyModal() { - cy.get("#cancel").should("be.visible"); - cy.get("#cancel").click(); - } - navigateBack() { cy.go(-1); } diff --git a/cypress/pageobject/Facility/FacilityLocation.ts b/cypress/pageobject/Facility/FacilityLocation.ts index 31bc30290f7..9cb23f9fba0 100644 --- a/cypress/pageobject/Facility/FacilityLocation.ts +++ b/cypress/pageobject/Facility/FacilityLocation.ts @@ -1,37 +1,81 @@ -class FacilityLocation { - loadLocationManagementPage(name: string) { - cy.awaitUrl("/"); - cy.intercept("GET", "**/api/v1/facility/**").as("getFacilities"); - cy.get("[id='facility-name-card']").contains(name).click(); - cy.wait("@getFacilities").its("response.statusCode").should("eq", 200); - cy.get("h1.text-3xl.font-bold", { timeout: 10000 }).should("be.visible"); - cy.get("#manage-facility-dropdown button").should("be.visible"); - cy.get("[id='manage-facility-dropdown']").scrollIntoView().click(); - cy.get("[id=location-management]").click(); - } +import FacilityPage from "./FacilityCreation"; +import FacilityHome from "./FacilityHome"; - closeNotification() { - cy.get(".pnotify") - .should("exist") - .each(($div) => { - cy.wrap($div).click(); - }); +const facilityHome = new FacilityHome(); +const facilityPage = new FacilityPage(); + +class FacilityLocation { + navigateToFacilityLocationManagement(facilityName: string) { + facilityHome.typeFacilitySearch(facilityName); + facilityHome.assertFacilityInCard(facilityName); + facilityHome.clickViewFacilityDetails(); + facilityPage.clickManageFacilityDropdown(); + this.clickFacilityLocationManagement(); + } + + fillLocationDetails( + name?: string, + description?: string, + type?: string, + middleware?: string, + ) { + if (name) this.typeLocationName(name); + if (description) this.fillDescription(description); + if (type) this.selectLocationType(type); + if (middleware) this.fillMiddlewareAddress(middleware); + } + + fetchAndNavigateToLocationPage() { + cy.url().then((currentUrl) => { + const baseUrl = currentUrl.split("/location/")[0]; + const locationUrl = `${baseUrl}/location/`; + cy.visit(locationUrl); + }); + } + + fillBedForm( + bedName?: string, + bedDescription?: string, + bedType?: string, + numberOfBeds?: number, + ) { + if (bedName) this.enterBedName(bedName); + if (bedDescription) { + this.enterBedDescription(bedDescription); + } + if (bedType) this.selectBedType(bedType); + if (numberOfBeds && numberOfBeds > 1) { + this.setMultipleBeds(numberOfBeds); + } } clickAddNewLocationButton() { cy.get("#add-new-location").click(); } + typeLocationName(locationName: string) { + cy.get("#location-name").type(locationName); + } + clickFacilityLocationManagement() { cy.get("[id=location-management]").click(); } + clickAddLocationButton() { + cy.clickSubmitButton("Add Location"); + } + clickEditLocationButton() { cy.get("#edit-location-button").click(); } - clickEditBedButton() { - cy.get("#edit-bed-button").click(); + clickEditBedButton(cardText: string) { + cy.get("#bed-cards") + .contains(cardText) + .parents("#bed-cards") + .within(() => { + cy.get("#edit-bed-button").click(); + }); } fillDescription(description: string) { @@ -42,13 +86,8 @@ class FacilityLocation { cy.get("div").contains(name).click(); } - enterLocationName(name: string) { - cy.get("input[id=name]").type(name); - } - selectLocationType(type: string) { - cy.get("#location-type").click(); - cy.get("li[role=option]").contains(type).click(); + cy.clickAndSelectOption("#location-type", type); } fillMiddlewareAddress(address: string) { @@ -63,10 +102,6 @@ class FacilityLocation { cy.get("#location-type").contains(type); } - verifyNotification(message: string) { - cy.get(".pnotify-container").should("contain", message).and("be.visible"); - } - verifyLocationDescription(description: string) { cy.get("#view-location-description").contains(description); } @@ -75,16 +110,49 @@ class FacilityLocation { cy.get("#view-location-middleware").contains(middleware); } - clickManageBedButton() { - cy.get("#manage-bed-button").first().click(); + clickManageBedButton(cardText: string) { + cy.get("#location-cards") + .contains(cardText) + .parents("#location-cards") + .within(() => { + cy.get("#manage-bed-button").click(); + }); + } + + clickManageBedPopup() { + cy.get("#manage-beds").click(); } clickAddBedButton() { - cy.get("#add-new-bed").click(); + cy.verifyAndClickElement("#add-new-bed", "Add New Bed(s)"); + } + + clickSubmitBedsButton() { + cy.clickSubmitButton("Add Bed(s)"); + } + + closeAddLocationForm() { + cy.clickCancelButton("Cancel"); + } + + verifyAddLocationSuccessfulMesssage() { + cy.verifyNotification("Location created successfully"); + cy.closeNotification(); } - clickNotification() { - cy.get(".pnotify-container").click(); + verifyEditBedSuccessfulMessage() { + cy.verifyNotification("Bed updated successfully"); + cy.closeNotification(); + } + + verifyEditLocationSuccessfulMessage() { + cy.verifyNotification("Location updated successfully"); + cy.closeNotification(); + } + + verifyAddSingleBedSuccessfulMesssage() { + cy.verifyNotification("1 Bed created successfully"); + cy.closeNotification(); } enterBedName(name: string) { @@ -95,6 +163,14 @@ class FacilityLocation { cy.get("#bed-description").clear().click().type(description); } + clickUpdateBedButton() { + cy.clickSubmitButton("Update Bed"); + } + + clickUpdateLocationButton() { + cy.clickSubmitButton("Update Location"); + } + selectBedType(type: string) { cy.get("#bed-type").click(); cy.get("li[role=option]").contains(type).click(); @@ -136,16 +212,22 @@ class FacilityLocation { cy.get("#manage-assets").click(); } - deleteLocation(name: string) { - cy.contains("div", name) - .should("exist") - .then(($div) => { - $div.parents("div").eq(2).find("button#delete-location-button").click(); + clickDeleteLocation(cardText: string) { + cy.get("#location-cards") + .contains(cardText) + .parents("#location-cards") + .within(() => { + cy.verifyAndClickElement("#delete-location-button", "Delete"); }); } - deleteFirstBed() { - cy.get("#delete-bed-button").first().click(); + deleteBedWithName(text: string) { + cy.get("#bed-cards") + .contains(text) + .parents("#bed-cards") + .within(() => { + cy.get("#delete-bed-button").click(); + }); } deleteBedRequest() { diff --git a/cypress/pageobject/Login/LoginPage.ts b/cypress/pageobject/Login/LoginPage.ts index 714004023f7..481c6ec3045 100644 --- a/cypress/pageobject/Login/LoginPage.ts +++ b/cypress/pageobject/Login/LoginPage.ts @@ -1,16 +1,12 @@ -// LoginPage.ts +import { users } from "../utils/userConfig"; class LoginPage { - loginAsDistrictAdmin(): void { - cy.loginByApi("devdistrictadmin", "Coronasafe@123"); - } - - loginAsDevDoctor(): void { - cy.loginByApi("devdoctor", "Coronasafe@123"); - } - - loginAsStaff(): void { - cy.loginByApi("staffdev", "Coronasafe@123"); + loginByRole(role: keyof typeof users): void { + const user = users[role]; + if (!user) { + throw new Error(`Role "${role}" is not defined in userConfig`); + } + cy.loginByApi(user.username, user.password); } loginManuallyAsDistrictAdmin(isCorrectCredentials: boolean = true): void { @@ -29,10 +25,6 @@ class LoginPage { cy.clickSubmitButton("Login"); } - login(username: string, password: string): void { - cy.loginByApi(username, password); - } - ensureLoggedIn(): void { cy.get("#user-profile-name").click(); cy.get("#sign-out-button").scrollIntoView(); diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index c8046a39a9e..edd8ae135a4 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -122,9 +122,10 @@ export class PatientConsultationPage { } clickViewConsultationButton() { + cy.get("a").contains("Encounters").click(); cy.verifyAndClickElement( "#view_consultation_and_log_updates", - "View Consultation / Log Updates", + "View Updates", ); } diff --git a/cypress/pageobject/Patient/PatientCreation.ts b/cypress/pageobject/Patient/PatientCreation.ts index a38b8ab6a5c..febf31b33b5 100644 --- a/cypress/pageobject/Patient/PatientCreation.ts +++ b/cypress/pageobject/Patient/PatientCreation.ts @@ -11,7 +11,8 @@ export class PatientPage { } visitPatient(patientName: string) { - cy.get("#name").click().type(patientName); + cy.get('[data-test-id="patient-search__name"]').click(); + cy.get("#patient-search").click().type(patientName); // Type the patient name cy.intercept("GET", "**/api/v1/consultation/**").as("getPatient"); cy.get("#patient-name-list").contains(patientName).click(); cy.wait(2000); @@ -66,7 +67,8 @@ export class PatientPage { } typePatientNameList(patientName: string) { - cy.get("#name").click().type(patientName); + cy.get('[data-test-id="patient-search__name"]').click(); + cy.get("#patient-search").click().type(patientName); } typePatientAddress(address: string) { @@ -181,7 +183,7 @@ export class PatientPage { expect($dashboard).to.contain(patientName); expect($dashboard).to.contain(phoneNumber); expect($dashboard).to.contain(emergencyPhoneNumber); - expect($dashboard).to.contain(yearOfBirth); + //expect($dashboard).to.contain(yearOfBirth); //Commented out because new proposed UI does not have DOB. Can change later. expect($dashboard).to.contain(bloodGroup); expect($dashboard).to.contain(occupation); socioeconomicStatus && expect($dashboard).to.contain(socioeconomicStatus); @@ -221,7 +223,7 @@ export class PatientPage { } clickPatientUpdateDetails() { - cy.verifyAndClickElement("#update-patient-details", "Update Details"); + cy.verifyAndClickElement("#update-patient-details", "Edit Profile"); } interceptFacilities() { diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts index 68287bfae41..45c1924e1a3 100644 --- a/cypress/pageobject/Patient/PatientLogupdate.ts +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -89,7 +89,7 @@ class PatientLogupdate { } clickUpdateDetail() { - cy.verifyAndClickElement("#consultation-preview", "Update Details"); + cy.verifyAndClickElement("#consultation-preview", "Update Log"); cy.wait(3000); } diff --git a/cypress/pageobject/Patient/PatientMedicalHistory.ts b/cypress/pageobject/Patient/PatientMedicalHistory.ts index 93fdd1b38b3..bf2296b4471 100644 --- a/cypress/pageobject/Patient/PatientMedicalHistory.ts +++ b/cypress/pageobject/Patient/PatientMedicalHistory.ts @@ -33,7 +33,9 @@ class PatientMedicalHistory { patientSymptoms6: string, patientSymptoms7: string, ) { - cy.get("[data-testid=patient-details]").then(($dashboard) => { + cy.get("a").contains("Health Profile").click(); + cy.wait(2000); + cy.get("[data-test-id=patient-health-profile]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(patientPresentHealth); expect($dashboard).to.contain(patientOngoingMedication); diff --git a/cypress/pageobject/Patient/PatientPredefined.ts b/cypress/pageobject/Patient/PatientPredefined.ts index a3eb41cb86c..445fbaf9b31 100644 --- a/cypress/pageobject/Patient/PatientPredefined.ts +++ b/cypress/pageobject/Patient/PatientPredefined.ts @@ -19,7 +19,9 @@ class PatientPredefined { patientPage.typePatientDateOfBirth("01012001"); patientPage.typePatientName("Patient With Predefined Data"); patientPage.selectPatientGender("Male"); - patientPage.typePatientAddress("Test Patient Address"); + patientPage.typePatientAddress( + "149/J, 3rd Block, Aluva, Ernakulam - 682001", + ); facilityPage.fillPincode("682001"); facilityPage.selectStateOnPincode("Kerala"); facilityPage.selectDistrictOnPincode("Ernakulam"); diff --git a/cypress/pageobject/Sample/SampleTestCreate.ts b/cypress/pageobject/Sample/SampleTestCreate.ts index cd2e93e5072..445d08732c3 100644 --- a/cypress/pageobject/Sample/SampleTestCreate.ts +++ b/cypress/pageobject/Sample/SampleTestCreate.ts @@ -1,5 +1,6 @@ export class SampleTestPage { visitSampleRequestPage(): void { + cy.get("a").contains("Service Request").click(); cy.verifyAndClickElement("#sample-request-btn", "Request Sample Test"); cy.url().should("include", "/sample-test"); } @@ -60,6 +61,7 @@ export class SampleTestPage { fastTrack: string, sampleTestResult: string, ): void { + cy.get("a").contains("Service Request").click(); cy.verifyContentPresence("#sample-test-status", [sampleTestStatus]); cy.verifyContentPresence("#sample-test-type", [sampleTestType]); cy.verifyContentPresence("#sample-test-fast-track", [fastTrack]); diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index a3a6e72fbc3..01871acd97d 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -90,27 +90,6 @@ export class ManageUserPage { ); } - navigateToFacility() { - cy.visit("/facility"); - } - - typeFacilitySearch(facilityName: string) { - cy.get("#search").click().clear(); - cy.get("#search").click().type(facilityName); - } - - interceptFacilitySearchReq() { - cy.intercept("GET", "**/api/v1/facility/**").as("searchFacility"); - } - - verifyFacilitySearchReq() { - cy.wait("@searchFacility").its("response.statusCode").should("eq", 200); - } - - assertFacilityInCard(facilityName: string) { - cy.get("#facility-name-card").should("contain", facilityName); - } - clickFacilityPatients() { cy.get("#facility-patients").should("be.visible"); cy.get("#facility-patients").click(); diff --git a/cypress/pageobject/Users/UserCreation.ts b/cypress/pageobject/Users/UserCreation.ts index 906c07e797a..d8d386402b7 100644 --- a/cypress/pageobject/Users/UserCreation.ts +++ b/cypress/pageobject/Users/UserCreation.ts @@ -1,79 +1,51 @@ // UserCreation.ts export class UserCreationPage { - clickElementById(elementId: string) { - cy.get("#" + elementId).click(); + clickAddUserButton() { + cy.verifyAndClickElement("#addUserButton", "Add New User"); } - - typeIntoElementById(elementId: string, value: string) { - cy.get("#" + elementId) - .click() - .type(value); + typeUserName(username: string) { + cy.get("#username").click().type(username); } - - typeIntoElementByIdPostClear(elementId: string, value: string) { - cy.get("#" + elementId) - .click() - .clear() - .click() - .type(value); + typeFirstName(firstName: string) { + cy.get("#firstName").click().type(firstName); } - typeIntoElementByIdPostClearDob(elementId: string, value: string) { - cy.clickAndTypeDate("#" + elementId, value); + typeLastName(lastName: string) { + cy.get("#lastName").click().type(lastName); } - clearIntoElementById(elementId: string) { - cy.get("#" + elementId) - .click() - .clear(); + typePassword(password: string) { + cy.get("#password").click().type(password); } - - typeIntoInputByName(inputName: string, value: string) { - cy.get("input[name='" + inputName + "']") - .click() - .type(value); + typeConfirmPassword(password: string) { + cy.get("#c_password").click().type(password); } - - selectOptionContainingText(text: string) { - cy.get("[role='option']").contains(text).click(); + clearFirstName() { + cy.get("#firstName").click().clear(); } - - verifyNotification(message: string) { - cy.verifyNotification(message); + clearLastName() { + cy.get("#lastName").click().clear(); } - - selectFacility(name: string) { - this.typeIntoInputByName("facilities", name); - this.selectOptionContainingText(name); - cy.get("input[name='facilities'] + button") - .find("#dropdown-toggle") - .click(); + selectUserType(role: string) { + cy.clickAndSelectOption("#user_type", role); } - selectHomeFacility(name: string) { - this.clickElementById("home_facility"); - this.selectOptionContainingText(name); + cy.clickAndSelectOption("#home_facility", name); } - setInputDate(dateElementId: string, dateValue: string) { - cy.clickAndTypeDate("#" + dateElementId, dateValue); + selectGender(gender: string) { + cy.clickAndSelectOption("#gender", gender); } - - selectDropdownOption(dropdownId: string, optionText: string) { - this.clickElementById(dropdownId); - this.selectOptionContainingText(optionText); + selectState(state: string) { + cy.clickAndSelectOption("#state", state); + } + selectDistrict(district: string) { + cy.clickAndSelectOption("#district", district); } - verifyElementContainsText(elementId: string, expectedText: string) { - cy.get("#" + elementId).should("contain.text", expectedText); + selectFacility(name: string) { + cy.typeAndSelectOption("input[name='facilities']", name); } - verifyErrorMessages(errorMessages: string[]) { - cy.get(".error-text").then(($errors) => { - const displayedErrorMessages = $errors - .map((_, el) => Cypress.$(el).text()) - .get(); - errorMessages.forEach((errorMessage) => { - expect(displayedErrorMessages).to.include(errorMessage); - }); - }); + clickSaveUserButton() { + cy.clickSubmitButton("Save User"); } } diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts index 3744c5a5d82..882be0b7b9b 100644 --- a/cypress/pageobject/Users/UserProfilePage.ts +++ b/cypress/pageobject/Users/UserProfilePage.ts @@ -12,11 +12,24 @@ export default class UserProfilePage { } clickUpdateButton() { - cy.get("#submit").click(); + cy.clickSubmitButton("Update"); } - typedate_of_birth(date_of_birth: string) { - cy.clickAndTypeDate("#date_of_birth", date_of_birth); + typeDateOfBirth(dob: string) { + cy.clickAndTypeDate("#date_of_birth", dob); + } + + clearPhoneNumber() { + cy.get("#phoneNumber").click().clear(); + } + clearAltPhoneNumber() { + cy.get("#altPhoneNumber").click().clear(); + } + clearWorkingHours() { + cy.get("#weekly_working_hours").click().clear(); + } + clearEmail() { + cy.get("#email").click().clear(); } selectGender(gender: string) { @@ -28,16 +41,16 @@ export default class UserProfilePage { cy.get("#email").click().clear().type(email); } - typePhone(phone: string) { + typePhoneNumber(phone: string) { cy.get("#phoneNumber").click().clear().type(phone); } - typeWhatsApp(phone: string) { + typeWhatsappNumber(phone: string) { cy.get("#altPhoneNumber").click().clear().type(phone); } - typeWorkingHours(workinghours: string) { - cy.get("#weekly_working_hours").click().clear().type(workinghours); + typeWorkingHours(workingHours: string) { + cy.get("#weekly_working_hours").click().clear().type(workingHours); } typeQualification = (qualification: string) => { @@ -55,11 +68,8 @@ export default class UserProfilePage { .type(medicalCouncilRegistration); }; - assertdate_of_birth(date_of_birth: string) { - cy.get("#date_of_birth-profile-details").should( - "contain.text", - date_of_birth, - ); + assertDateOfBirth(dob: string) { + cy.get("#date_of_birth-profile-details").should("contain.text", dob); } assertGender(gender: string) { @@ -70,18 +80,18 @@ export default class UserProfilePage { cy.get("#emailid-profile-details").should("contain.text", email); } - assertPhone(phone: string) { + assertPhoneNumber(phone: string) { cy.get("#contactno-profile-details").should("contain.text", phone); } - assertWhatsApp(phone: string) { + assertAltPhoneNumber(phone: string) { cy.get("#whatsapp-profile-details").should("contain.text", phone); } - assertWorkingHours(workinghours: string) { + assertWorkingHours(workingHours: string) { cy.get("#averageworkinghour-profile-details").should( "contain.text", - workinghours, + workingHours, ); } } diff --git a/cypress/pageobject/Users/UserSearch.ts b/cypress/pageobject/Users/UserSearch.ts index 6b727aa2040..9d996abd83f 100644 --- a/cypress/pageobject/Users/UserSearch.ts +++ b/cypress/pageobject/Users/UserSearch.ts @@ -68,14 +68,6 @@ export class UserPage { cy.typeAndSelectOption("input[name='home_facility']", facility); } - verifyDataTestIdText(testId: string, text: string) { - cy.get(`[data-testid="${testId}"]`).contains(text).should("be.visible"); - } - - verifyDataTestIdNotVisible(testId: string) { - cy.get(`[data-testid="${testId}"]`).should("not.be.visible"); - } - verifyMultipleBadgesWithSameId(alreadylinkedusersviews: string[]) { cy.get("#user-view-name").then(($elements) => { const userViews = $elements diff --git a/cypress/pageobject/utils/advanceFilterHelpers.ts b/cypress/pageobject/utils/advanceFilterHelpers.ts index a624311c197..22925fc2c23 100644 --- a/cypress/pageobject/utils/advanceFilterHelpers.ts +++ b/cypress/pageobject/utils/advanceFilterHelpers.ts @@ -23,7 +23,28 @@ export const advanceFilters = { cy.clickAndSelectOption("#facility_type", facilityType); }, + typeFacilityName(facilityName: string) { + cy.typeAndSelectOption("input[name='Facilities']", facilityName); + }, + + clickslideoverbackbutton() { + cy.get("#close-slide-over").click(); + }, + clickClearAdvanceFilters() { cy.verifyAndClickElement("#clear-filter", "Clear"); }, + + verifyFilterBadgePresence( + badgeTestId: string, + text: string, + visible: boolean = true, + ) { + const badgeElement = cy.get(`[data-testid="${badgeTestId}"]`); + if (visible) { + badgeElement.contains(text).should("be.visible"); + } else { + badgeElement.should("not.be.visible"); + } + }, }; diff --git a/cypress/pageobject/utils/constants.ts b/cypress/pageobject/utils/constants.ts index 08411e84b6a..053d0561ce8 100644 --- a/cypress/pageobject/utils/constants.ts +++ b/cypress/pageobject/utils/constants.ts @@ -1,7 +1,10 @@ -export function generatePhoneNumber() { - return "9" + Math.floor(100000000 + Math.random() * 900000000).toString(); +export function generatePhoneNumber(): string { + const array = new Uint32Array(1); + window.crypto.getRandomValues(array); + const randomNum = (array[0] % 900000000) + 100000000; + return "9" + randomNum.toString(); } -export function generateEmergencyPhoneNumber() { - return "9" + Math.floor(100000000 + Math.random() * 900000000).toString(); +export function generateEmergencyPhoneNumber(): string { + return generatePhoneNumber(); } diff --git a/cypress/pageobject/utils/userConfig.ts b/cypress/pageobject/utils/userConfig.ts new file mode 100644 index 00000000000..270fbfba10a --- /dev/null +++ b/cypress/pageobject/utils/userConfig.ts @@ -0,0 +1,8 @@ +export const users = { + districtAdmin: { username: "devdistrictadmin", password: "Coronasafe@123" }, + devDoctor: { username: "devdoctor", password: "Coronasafe@123" }, + nurse: { username: "dummynurse1", password: "Coronasafe@123" }, + nurse2: { username: "dummynurse2", password: "Coronasafe@123" }, +}; + +export const nonAdminRoles = ["devDoctor", "nurse"] as const; diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index d97bb52732f..0268beebc42 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -229,3 +229,15 @@ Cypress.Commands.add("verifyContentPresence", (selector, texts) => { }); }); }); + +Cypress.Commands.add("verifyErrorMessages", (errorMessages: string[]) => { + const selector = ".error-text"; // Static selector + cy.get(selector).then(($errors) => { + const displayedErrorMessages = $errors + .map((_, el) => Cypress.$(el).text()) + .get(); + errorMessages.forEach((errorMessage) => { + expect(displayedErrorMessages).to.include(errorMessage); + }); + }); +}); diff --git a/cypress/support/index.ts b/cypress/support/index.ts index 6429ef5710f..831ee71a5bc 100644 --- a/cypress/support/index.ts +++ b/cypress/support/index.ts @@ -45,6 +45,7 @@ declare global { selector: string, texts: string[], ): Chainable; + verifyErrorMessages(errorMessages: string[]): Chainable; } } } diff --git a/package-lock.json b/package-lock.json index 63af1164ef0..dfa66181bd5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,12 +19,16 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-tooltip": "^1.1.3", - "@sentry/browser": "^8.37.1", + "@radix-ui/react-tooltip": "^1.1.4", + "@sentry/browser": "^8.42.0", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -32,8 +36,9 @@ "browserslist-useragent-regexp": "^4.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "^1.0.0", "cross-env": "^7.0.3", - "cypress": "^13.15.2", + "cypress": "^13.16.1", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -54,7 +59,7 @@ "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.1.1", "react-webcam": "^7.2.0", - "tailwind-merge": "^2.5.4", + "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", "xlsx": "^0.18.5" @@ -92,7 +97,7 @@ "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "glob": "^11.0.0", - "husky": "^9.1.6", + "husky": "^9.1.7", "jsdom": "^25.0.1", "lint-staged": "^15.2.10", "local-cypress": "^1.2.6", @@ -114,25 +119,6 @@ "node": ">=22.11.0" } }, - "apps/care_livekit_fe": { - "name": "care-livekit", - "version": "0.0.1", - "extraneous": true, - "license": "ISC", - "dependencies": { - "@livekit/components-react": "^2.6.2", - "@livekit/components-styles": "^1.1.3", - "livekit-client": "^2.5.5" - }, - "devDependencies": { - "react": "^18.0.0", - "react-dom": "^18.0.0" - }, - "peerDependencies": { - "react": "18.3.1", - "react-dom": "18.3.1" - } - }, "node_modules/@actions/core": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz", @@ -3626,6 +3612,11 @@ "validator": "^13.9.0" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz", + "integrity": "sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", @@ -3726,6 +3717,41 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.0.tgz", @@ -3838,12 +3864,12 @@ } }, "node_modules/@radix-ui/react-icons": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.1.tgz", - "integrity": "sha512-QvYompk0X+8Yjlo/Fv4McrzxohDdM5GgLHyQcPpcsPvlOSXCGFjdbuyGL5dzRbg0GpknAjQJJZzdiRK7iWVuFQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", + "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", "license": "MIT", "peerDependencies": { - "react": "^16.x || ^17.x || ^18.x || ^19.x" + "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "node_modules/@radix-ui/react-id": { @@ -3864,6 +3890,28 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz", + "integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.2.tgz", @@ -3904,6 +3952,42 @@ } } }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.2.tgz", + "integrity": "sha512-u2HRUyWW+lOiA2g0Le0tMmT55FGOEWHwPFt1EPfbLly7uXQExFo5duNKqG2DzmFXIdqOeNd+TpE8baHWJCyP9w==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-popper": "1.2.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.0.tgz", @@ -4068,6 +4152,36 @@ } } }, + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.0.tgz", + "integrity": "sha512-q2jMBdsJ9zB7QG6ngQNzNwlvxLQqONyL58QbEGwuyRZZb/ARQwk3uQVbCF7GvQVOtV6EU/pDxAw3zRzJZI3rpQ==", + "dependencies": { + "@radix-ui/number": "1.1.0", + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-direction": "1.1.0", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz", @@ -4121,9 +4235,9 @@ } }, "node_modules/@radix-ui/react-tooltip": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.3.tgz", - "integrity": "sha512-Z4w1FIS0BqVFI2c1jZvb/uDVJijJjJ2ZMuPV81oVgTZ7g3BZxobplnMVvXtFWgtozdvYJ+MFWtwkM5S2HnAong==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.1.4.tgz", + "integrity": "sha512-QpObUH/ZlpaO4YgHSaYzrLO2VuO+ZBFFgGzjMUPwtiYnAzzNNDPJeEGRrT7qNOrWm/Jr08M1vlp+vTHtnSQ0Uw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", @@ -4682,58 +4796,50 @@ ] }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.37.1.tgz", - "integrity": "sha512-OSR/V5GCsSCG7iapWtXCT/y22uo3HlawdEgfM1NIKk1mkP15UyGQtGEzZDdih2H+SNuX1mp9jQLTjr5FFp1A5w==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.42.0.tgz", + "integrity": "sha512-xzgRI0wglKYsPrna574w1t38aftuvo44gjOKFvPNGPnYfiW9y4m+64kUz3JFbtanvOrKPcaITpdYiB4DeJXEbA==", "license": "MIT", "dependencies": { - "@sentry/core": "8.37.1", - "@sentry/types": "8.37.1", - "@sentry/utils": "8.37.1" + "@sentry/core": "8.42.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.37.1.tgz", - "integrity": "sha512-Se25NXbSapgS2S+JssR5YZ48b3OY4UGmAuBOafgnMW91LXMxRNWRbehZuNUmjjHwuywABMxjgu+Yp5uJDATX+g==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.42.0.tgz", + "integrity": "sha512-dkIw5Wdukwzngg5gNJ0QcK48LyJaMAnBspqTqZ3ItR01STi6Z+6+/Bt5XgmrvDgRD+FNBinflc5zMmfdFXXhvw==", "license": "MIT", "dependencies": { - "@sentry/core": "8.37.1", - "@sentry/types": "8.37.1", - "@sentry/utils": "8.37.1" + "@sentry/core": "8.42.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.37.1.tgz", - "integrity": "sha512-E/Plhisk/pXJjOdOU12sg8m/APTXTA21iEniidP6jW3/+O0tD/H/UovEqa4odNTqxPMa798xHQSQNt5loYiaLA==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.42.0.tgz", + "integrity": "sha512-oNcJEBlDfXnRFYC5Mxj5fairyZHNqlnU4g8kPuztB9G5zlsyLgWfPxzcn1ixVQunth2/WZRklDi4o1ZfyHww7w==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.37.1", - "@sentry/core": "8.37.1", - "@sentry/types": "8.37.1", - "@sentry/utils": "8.37.1" + "@sentry-internal/browser-utils": "8.42.0", + "@sentry/core": "8.42.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.37.1.tgz", - "integrity": "sha512-1JLAaPtn1VL5vblB0BMELFV0D+KUm/iMGsrl4/JpRm0Ws5ESzQl33DhXVv1IX/ZAbx9i14EjR7MG9+Hj70tieQ==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.42.0.tgz", + "integrity": "sha512-XrPErqVhPsPh/oFLVKvz7Wb+Fi2J1zCPLeZCxWqFuPWI2agRyLVu0KvqJyzSpSrRAEJC/XFzuSVILlYlXXSfgA==", "license": "MIT", "dependencies": { - "@sentry-internal/replay": "8.37.1", - "@sentry/core": "8.37.1", - "@sentry/types": "8.37.1", - "@sentry/utils": "8.37.1" + "@sentry-internal/replay": "8.42.0", + "@sentry/core": "8.42.0" }, "engines": { "node": ">=14.18" @@ -4792,32 +4898,26 @@ } }, "node_modules/@sentry/browser": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.37.1.tgz", - "integrity": "sha512-5ym+iGiIpjIKKpMWi9S3/tXh9xneS+jqxwRTJqed3cb8i4ydfMAAP8sM3U8xMCWWABpWyIUW+fpewC0tkhE1aQ==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.42.0.tgz", + "integrity": "sha512-lStrEk609KJHwXfDrOgoYVVoFFExixHywxSExk7ZDtwj2YPv6r6Y1gogvgr7dAZj7jWzadHkxZ33l9EOSJBfug==", "license": "MIT", "dependencies": { - "@sentry-internal/browser-utils": "8.37.1", - "@sentry-internal/feedback": "8.37.1", - "@sentry-internal/replay": "8.37.1", - "@sentry-internal/replay-canvas": "8.37.1", - "@sentry/core": "8.37.1", - "@sentry/types": "8.37.1", - "@sentry/utils": "8.37.1" + "@sentry-internal/browser-utils": "8.42.0", + "@sentry-internal/feedback": "8.42.0", + "@sentry-internal/replay": "8.42.0", + "@sentry-internal/replay-canvas": "8.42.0", + "@sentry/core": "8.42.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/core": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.37.1.tgz", - "integrity": "sha512-82csXby589iDupM3VgCHJeWZagUyEEaDnbFcoZ/Z91QX2Sjq8FcF5OsforoXjw09i0XTFqlkFAnQVpDBmMXcpQ==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.42.0.tgz", + "integrity": "sha512-ac6O3pgoIbU6rpwz6LlwW0wp3/GAHuSI0C5IsTgIY6baN8rOBnlAtG6KrHDDkGmUQ2srxkDJu9n1O6Td3cBCqw==", "license": "MIT", - "dependencies": { - "@sentry/types": "8.37.1", - "@sentry/utils": "8.37.1" - }, "engines": { "node": ">=14.18" } @@ -4929,27 +5029,6 @@ "node": ">=8" } }, - "node_modules/@sentry/types": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.37.1.tgz", - "integrity": "sha512-ryMOTROLSLINKFEbHWvi7GigNrsQhsaScw2NddybJGztJQ5UhxIGESnxGxWCufBmWFDwd7+5u0jDPCVUJybp7w==", - "license": "MIT", - "engines": { - "node": ">=14.18" - } - }, - "node_modules/@sentry/utils": { - "version": "8.37.1", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.37.1.tgz", - "integrity": "sha512-Qtn2IfpII12K17txG/ZtTci35XYjYi4CxbQ3j7nXY7toGv/+MqPXwV5q2i9g94XaSXlE5Wy9/hoCZoZpZs/djA==", - "license": "MIT", - "dependencies": { - "@sentry/types": "8.37.1" - }, - "engines": { - "node": ">=14.18" - } - }, "node_modules/@surma/rollup-plugin-off-main-thread": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", @@ -7392,6 +7471,366 @@ "node": ">=6" } }, + "node_modules/cmdk": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.0.0.tgz", + "integrity": "sha512-gDzVf0a09TvoJ5jnuPvygTB77+XdOSwEmJ88L6XPFPlv7T3RxbP9jgenfylrAMD0+Le1aO0nVjQUzl2g+vjz5Q==", + "dependencies": { + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-compose-refs": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", + "integrity": "sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-primitive": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", + "integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", + "integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/cmdk/node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/codepage": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz", @@ -7685,9 +8124,9 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "13.15.2", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.2.tgz", - "integrity": "sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==", + "version": "13.16.1", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.1.tgz", + "integrity": "sha512-17FtCaz0cx7ssWYKXzGB0Vub8xHwpVPr+iPt2fHhLMDhVAPVrplD+rTQsZUsfb19LVBn5iwkEUFjQ1yVVJXsLA==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -10449,9 +10888,9 @@ "license": "Unlicense" }, "node_modules/husky": { - "version": "9.1.6", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.6.tgz", - "integrity": "sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, "license": "MIT", "bin": { @@ -12335,8 +12774,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", @@ -17629,9 +18067,9 @@ "license": "MIT" }, "node_modules/tailwind-merge": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", - "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", + "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==", "license": "MIT", "funding": { "type": "github", @@ -20862,4 +21300,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index 020d73f5e65..e07e45edaeb 100644 --- a/package.json +++ b/package.json @@ -58,12 +58,16 @@ "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-icons": "^1.3.1", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-scroll-area": "^1.2.0", + "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-toast": "^1.2.2", - "@radix-ui/react-tooltip": "^1.1.3", - "@sentry/browser": "^8.37.1", + "@radix-ui/react-tooltip": "^1.1.4", + "@sentry/browser": "^8.42.0", "@yudiel/react-qr-scanner": "^2.0.8", "bowser": "^2.11.0", "browser-image-compression": "^2.0.2", @@ -71,8 +75,9 @@ "browserslist-useragent-regexp": "^4.1.3", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "cmdk": "^1.0.0", "cross-env": "^7.0.3", - "cypress": "^13.15.2", + "cypress": "^13.16.1", "dayjs": "^1.11.13", "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", @@ -93,7 +98,7 @@ "react-infinite-scroll-component": "^6.1.0", "react-pdf": "^9.1.1", "react-webcam": "^7.2.0", - "tailwind-merge": "^2.5.4", + "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "use-keyboard-shortcut": "^1.1.6", "xlsx": "^0.18.5" @@ -131,7 +136,7 @@ "eslint-plugin-react": "^7.37.2", "eslint-plugin-react-hooks": "^5.0.0", "glob": "^11.0.0", - "husky": "^9.1.6", + "husky": "^9.1.7", "jsdom": "^25.0.1", "lint-staged": "^15.2.10", "local-cypress": "^1.2.6", diff --git a/public/locale/en.json b/public/locale/en.json index 5faa645b133..f86d78fa3aa 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -274,6 +274,7 @@ "accept_all": "Accept All", "access_level": "Access Level", "action_irreversible": "This action is irreversible", + "actions": "Actions", "active": "Active", "active_prescriptions": "Active Prescriptions", "add": "Add", @@ -281,7 +282,10 @@ "add_attachments": "Add Attachments", "add_beds": "Add Bed(s)", "add_beds_to_configure_presets": "Add beds to this location to configure presets for them.", + "add_consultation": "Add consultation", + "add_consultation_update": "Add Consultation Update", "add_details_of_patient": "Add Details of Patient", + "add_insurance_details": "Add Insurance Details", "add_location": "Add Location", "add_new_beds": "Add New Bed(s)", "add_new_user": "Add New User", @@ -301,11 +305,14 @@ "administered_on": "Administered on", "administration_dosage_range_error": "Dosage should be between start and target dosage", "administration_notes": "Administration Notes", + "admitted": "Admitted", + "admitted_on": "Admitted On", "advanced_filters": "Advanced Filters", "age": "Age", "all_changes_have_been_saved": "All changes have been saved", "all_details": "All Details", "allergies": "Allergies", + "allow_transfer": "Allow Transfer", "allowed_formats_are": "Allowed formats are", "already_a_member": "Already a member?", "ambulance_driver_name": "Name of ambulance driver", @@ -316,6 +323,7 @@ "any_id_description": "Currently we support: Aadhaar Number / Mobile Number", "any_other_comments": "Any other comments", "apply": "Apply", + "approve": "Approve", "approved_by_district_covid_control_room": "Approved by District COVID Control Room", "approving_facility": "Name of Approving Facility", "archive": "Archive", @@ -332,8 +340,14 @@ "asset_qr_id": "Asset QR ID", "asset_type": "Asset Type", "assets": "Assets", + "assign": "Assign", + "assign_a_volunteer_to": "Assign a volunteer to {{name}}", + "assign_bed": "Assign Bed", + "assign_to_volunteer": "Assign to a Volunteer", + "assigned_doctor": "Assigned Doctor", "assigned_facility": "Facility assigned", "assigned_to": "Assigned to", + "assigned_volunteer": "Assigned Volunteer", "async_operation_warning": "This operation may take some time. Please check back later.", "atypical_presentation_details": "Atypical presentation details", "audio__allow_permission": "Please allow microphone permission in site settings", @@ -365,6 +379,7 @@ "bed_capacity": "Bed Capacity", "bed_created_notification_one": "{{count}} Bed created successfully", "bed_created_notification_other": "{{count}} Beds created successfully", + "bed_history": "Bed History", "bed_not_linked_to_camera": "This bed has not been linked to this camera.", "bed_search_placeholder": "Search by beds name", "bed_type": "Bed Type", @@ -397,6 +412,7 @@ "central_nursing_station": "Central Nursing Station", "change_file": "Change File", "change_password": "Change Password", + "chat_on_whatsapp": "Chat on Whatsapp", "check_eligibility": "Check Eligibility", "check_for_available_update": "Check for available update", "check_for_update": "Check for Update", @@ -493,6 +509,7 @@ "consent_request_rejected": "Patient has rejected the consent request", "consent_request_waiting_approval": "Waiting for the Patient to approve the consent request", "consent_requested_successfully": "Consent requested successfully!", + "consultation_history": "Consultation History", "consultation_missing_warning": "You have not created a consultation for the patient in", "consultation_not_filed": "You have not filed a consultation for this patient yet.", "consultation_not_filed_description": "Please file a consultation for this patient to continue.", @@ -516,6 +533,7 @@ "countries_travelled": "Countries travelled", "covid_19_cat_gov": "Covid_19 Clinical Category as per Govt. of Kerala guideline (A/B/C)", "covid_19_death_reporting_form_1": "Covid-19 Death Reporting : Form 1", + "covid_details": "Covid Details", "create": "Create", "create_abha_address": "Create ABHA Address", "create_add_more": "Create & Add More", @@ -533,6 +551,7 @@ "created_date": "Created Date", "created_on": "Created On", "csv_file_in_the_specified_format": "Select a CSV file in the specified format", + "current_address": "Current Address", "current_password": "Current Password", "customer_support_email": "Customer Support Email", "customer_support_name": "Customer Support Name", @@ -543,16 +562,19 @@ "date_and_time": "Date and Time", "date_declared_positive": "Date of declaring positive", "date_of_admission": "Date of Admission", - "date_of_birth": "Date of birth", + "date_of_birth": "Date of Birth", "date_of_positive_covid_19_swab": "Date of Positive Covid 19 Swab", "date_of_result": "Covid confirmation date", + "date_of_return": "Date of Return", "date_of_test": "Date of sample collection for Covid testing", "days": "Days", + "death_report": "Death Report", "delete": "Delete", "delete_facility": "Delete Facility", "delete_item": "Delete {{name}}", "delete_record": "Delete Record", "deleted_successfully": "{{name}} deleted successfully", + "demography": "Demography", "denied_on": "Denied On", "describe_why_the_asset_is_not_working": "Describe why the asset is not working", "description": "Description", @@ -573,6 +595,7 @@ "diastolic": "Diastolic", "differential": "Differential", "differential_diagnosis": "Differential diagnosis", + "disable_transfer": "Disable Transfer", "discard": "Discard", "discharge": "Discharge", "discharge_from_care": "Discharge from CARE", @@ -580,6 +603,7 @@ "discharge_summary": "Discharge Summary", "discharge_summary_not_ready": "Discharge summary is not ready yet.", "discharged": "Discharged", + "discharged_on": "Discharged On", "discharged_patients": "Discharged Patients", "discharged_patients_empty": "No discharged patients present in this facility", "disclaimer": "Disclaimer", @@ -614,6 +638,7 @@ "edit_policy": "Edit Insurance Policy", "edit_policy_description": "Add or edit patient's insurance details", "edit_prescriptions": "Edit Prescriptions", + "edit_profile": "Edit Profile", "edit_user_profile": "Edit Profile", "edited_by": "Edited by", "edited_on": "Edited on", @@ -627,7 +652,11 @@ "email_discharge_summary_description": "Enter your valid email address to receive the discharge summary", "email_success": "We will be sending an email shortly. Please check your inbox.", "emergency": "Emergency", + "emergency_contact": "Emergency Contact", "emergency_contact_number": "Emergency Contact Number", + "emergency_contact_person_name": "Emergency Contact Person Name", + "emergency_contact_person_name_volunteer": "Emergency Contact Person Name (Volunteer)", + "emergency_contact_volunteer": "Emergency Contact (Volunteer)", "empty_date_time": "--:-- --; --/--/----", "encounter_date_field_label__A": "Date & Time of Admission to the Facility", "encounter_date_field_label__DC": "Date & Time of Domiciliary Care commencement", @@ -643,6 +672,7 @@ "encounter_suggestion__OP": "Out-patient visit", "encounter_suggestion__R": "Consultation", "encounter_suggestion_edit_disallowed": "Not allowed to switch to this option in edit consultation", + "encounters": "Encounters", "end_datetime": "End Date/Time", "enter_aadhaar_number": "Enter a 12-digit Aadhaar ID", "enter_aadhaar_otp": "Enter OTP sent to the registered mobile with Aadhaar", @@ -664,11 +694,13 @@ "events": "Events", "expand_sidebar": "Expand Sidebar", "expected_burn_rate": "Expected Burn Rate", + "expired": "Expired", "expired_on": "Expired On", "expires_on": "Expires On", "facilities": "Facilities", "facility": "Facility", "facility_consent_requests_page_title": "Patient Consent List", + "facility_district_name": "Facility/District Name", "facility_name": "Facility Name", "facility_preference": "Facility preference", "facility_search_placeholder": "Search by Facility / District Name", @@ -721,6 +753,7 @@ "granted_on": "Granted On", "has_domestic_healthcare_support": "Has domestic healthcare support?", "has_sari": "Has SARI (Severe Acute Respiratory illness)?", + "health-profile": "Health Profile", "health_facility__config_registration_error": "Health ID registration failed", "health_facility__config_update_error": "Health Facility config update failed", "health_facility__config_update_success": "Health Facility config updated successfully", @@ -757,12 +790,17 @@ "i_declare": "I hereby declare that:", "icd11_as_recommended": "As per ICD-11 recommended by WHO", "icmr_specimen_referral_form": "ICMR Specimen Referral Form", + "immunisation-records": "Immunisation", "incomplete_patient_details_warning": "Patient details are incomplete. Please update the details before proceeding.", "inconsistent_dosage_units_error": "Dosage units must be same", "indian_mobile": "Indian Mobile", "indicator": "Indicator", "inidcator_event": "Indicator Event", "instruction_on_titration": "Instruction on titration", + "insurance__insurer_id": "Insurer ID", + "insurance__insurer_name": "Insurer Name", + "insurance__member_id": "Member ID", + "insurance__policy_name": "Policy ID / Policy Name", "insurer_name_required": "Insurer Name is required", "international_mobile": "International Mobile", "invalid_asset_id_msg": "Oops! The asset ID you entered does not appear to be valid.", @@ -771,6 +809,7 @@ "invalid_link_msg": "It appears that the password reset link you have used is either invalid or expired. Please request a new password reset link.", "invalid_password": "Password doesn't meet the requirements", "invalid_password_reset_link": "Invalid password reset link", + "invalid_patient_data": "Invalid Patient Data", "invalid_phone": "Please enter valid phone number", "invalid_phone_number": "Invalid Phone Number", "invalid_pincode": "Invalid Pincode", @@ -789,6 +828,8 @@ "investigations__result": "Result", "investigations__unit": "Unit", "investigations_suggested": "Investigations Suggested", + "investigations_summary": "Investigations Summary", + "ip_encounter": "IP Encounter", "is": "Is", "is_antenatal": "Is Antenatal", "is_atypical_presentation": "Is Atypical presentation", @@ -796,21 +837,26 @@ "is_emergency": "Is emergency", "is_emergency_case": "Is emergency case", "is_it_upshift": "is it upshift", + "is_pregnant": "Is pregnant", "is_this_an_emergency": "Is this an emergency?", "is_this_an_upshift": "Is this an upshift?", "is_unusual_course": "Is unusual course", "is_up_shift": "Is up shift", "is_upshift_case": "Is upshift case", "is_vaccinated": "Whether vaccinated", + "kasp_enabled_date": "{{kasp_string}} enabled date", "label": "Label", "landline": "Indian landline", "language_selection": "Language Selection", "last_administered": "Last administered", + "last_discharge_reason": "Last Discharge Reason", "last_edited": "Last Edited", "last_modified": "Last Modified", "last_name": "Last Name", "last_online": "Last Online", "last_serviced_on": "Last Serviced On", + "last_updated_by": "Last updated by", + "last_vaccinated_on": "Last Vaccinated on", "latitude_invalid": "Latitude must be between -90 and 90", "left": "Left", "length": "Length ({{unit}})", @@ -859,6 +905,7 @@ "max_size_for_image_uploaded_should_be": "Max size for image uploaded should be", "measured_after": "Measured after", "measured_before": "Measured before", + "medical": "Medical", "medical_council_registration": "Medical Council Registration", "medical_worker": "Medical Worker", "medicine": "Medicine", @@ -869,6 +916,7 @@ "middleware_hostname": "Middleware Hostname", "middleware_hostname_example": "e.g. example.ohc.network", "middleware_hostname_sourced_from": "Middleware hostname sourced from {{ source }}", + "min_char_length_error": "Must be at least {{ min_length }} characters", "min_password_len_8": "Minimum password length 8", "min_time_bw_doses": "Min. time b/w doses", "minimize": "Minimize", @@ -898,10 +946,13 @@ "next_sessions": "Next Sessions", "no": "No", "no_attachments_found": "This communication has no attachments.", + "no_bed_asset_linked_allocated": "No bed/asset linked allocated", "no_bed_types_found": "No Bed Types found", "no_beds_available": "No beds available", "no_changes": "No changes", "no_changes_made": "No changes made", + "no_consultation_filed": "No consultation filed", + "no_consultation_history": "No consultation history available", "no_consultation_updates": "No consultation updates", "no_data_found": "No data found", "no_duplicate_facility": "You should not create duplicate facilities", @@ -914,6 +965,7 @@ "no_linked_facilities": "No Linked Facilities", "no_log_update_delta": "No changes since previous log update", "no_log_updates": "No log updates found", + "no_medical_history_available": "No Medical History Available", "no_notices_for_you": "No notices for you.", "no_patients_found": "No Patients Found", "no_patients_to_show": "No patients to show.", @@ -923,6 +975,7 @@ "no_records_found": "No Records Found", "no_remarks": "No remarks", "no_results_found": "No Results Found", + "no_social_profile_details_available": "No Social Profile Details Available", "no_staff": "No staff found", "no_tests_taken": "No tests taken", "no_treating_physicians_available": "This facility does not have any home facility doctors. Please contact your admin.", @@ -942,6 +995,7 @@ "number_of_beds": "Number of beds", "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", "number_of_chronic_diseased_dependents": "Number Of Chronic Diseased Dependents", + "number_of_covid_vaccine_doses": "Number of Covid vaccine doses", "nursing_care": "Nursing Care", "nursing_information": "Nursing Information", "nutrition": "Nutrition", @@ -951,6 +1005,8 @@ "on": "On", "ongoing_medications": "Ongoing Medications", "only_indian_mobile_numbers_supported": "Currently only Indian numbers are supported", + "op_encounter": "OP Encounter", + "op_file_closed": "OP file closed", "open": "Open", "open_camera": "Open Camera", "open_live_monitoring": "Open Live Monitoring", @@ -973,6 +1029,11 @@ "password_reset_success": "Password Reset successfully", "password_sent": "Password Reset Email Sent", "patient": "Patient", + "patient-notes": "Notes", + "patient__general-info": "General Info", + "patient__insurance-details": "Insurance Details", + "patient__social-profile": "Social Profile", + "patient__volunteer-contact": "Volunteer Contact", "patient_address": "Patient Address", "patient_body": "Patient Body", "patient_category": "Patient Category", @@ -996,6 +1057,8 @@ "patient_notes_thread__Doctors": "Doctor's Discussions", "patient_notes_thread__Nurses": "Nurse's Discussions", "patient_phone_number": "Patient Phone Number", + "patient_profile": "Patient Profile", + "patient_profile_created_by": "Patient profile created by", "patient_registration__address": "Address", "patient_registration__age": "Age", "patient_registration__comorbidities": "Comorbidities", @@ -1008,12 +1071,15 @@ "patient_status": "Patient Status", "patient_transfer_birth_match_note": "Note: Year of birth must match the patient to process the transfer request.", "patients": "Patients", + "permanent_address": "Permanent Address", + "permission_denied": "You do not have permission to perform this action", "personal_information": "Personal Information", "phone": "Phone", "phone_no": "Phone no.", "phone_number": "Phone Number", "phone_number_at_current_facility": "Phone Number of Contact person at current Facility", "pincode": "Pincode", + "please_assign_bed_to_patient": "Please assign a bed to this patient", "please_enter_a_reason_for_the_shift": "Please enter a reason for the shift.", "please_select_a_facility": "Please select a facility", "please_select_breathlessness_level": "Please select Breathlessness Level", @@ -1035,6 +1101,7 @@ "policy__subscriber_id__example": "SUB001", "policy_id_required": "Policy Id or Policy Name is required", "position": "Position", + "post_partum": "Post-partum", "post_your_comment": "Post Your Comment", "powered_by": "Powered By", "preferred_facility_type": "Preferred Facility Type", @@ -1049,6 +1116,7 @@ "prescriptions__medicine": "Medicine", "prescriptions__route": "Route", "prescriptions__start_date": "Prescribed On", + "present_health": "Present Health", "preset_deleted": "Preset deleted", "preset_name_placeholder": "Specify an identifiable name for the new preset", "preset_updated": "Preset updated", @@ -1099,9 +1167,11 @@ "req_atleast_one_lowercase": "Require at least one lower case letter", "req_atleast_one_symbol": "Require at least one symbol", "req_atleast_one_uppercase": "Require at least one upper case", + "request-sample-test": "Service Request", "request_consent": "Request Consent", "request_description": "Description of Request", "request_description_placeholder": "Type your description here", + "request_sample_test": "Request Sample Test", "request_title": "Request Title", "request_title_placeholder": "Type your title here", "required": "Required", @@ -1125,6 +1195,8 @@ "return_to_login": "Return to Login", "return_to_password_reset": "Return to Password Reset", "return_to_patient_dashboard": "Return to Patient Dashboard", + "review_before": "Review Before", + "review_missed": "Review Missed", "revoked_on": "Revoked On", "right": "Right", "route": "Route", @@ -1143,6 +1215,10 @@ "scan_asset_qr": "Scan Asset QR!", "scribe__reviewing_field": "Reviewing field {{currentField}} / {{totalFields}}", "scribe_error": "Could not autofill fields", + "search_by_emergency_contact_phone_number": "Search by Emergency Contact Phone Number", + "search_by_patient_name": "Search by Patient Name", + "search_by_patient_no": "Search by Patient Number", + "search_by_phone_number": "Search by Phone Number", "search_by_username": "Search by username", "search_for_facility": "Search for Facility", "search_icd11_placeholder": "Search for ICD-11 Diagnoses", @@ -1172,6 +1248,8 @@ "send_otp_error": "Failed to send OTP. Please try again later.", "send_otp_success": "OTP has been sent to the respective mobile number", "send_reset_link": "Send Reset Link", + "send_sample_to_collection_centre_description": "Are you sure you want to send the sample to Collection Centre?", + "send_sample_to_collection_centre_title": "Send sample to collection centre", "serial_number": "Serial Number", "serviced_on": "Serviced on", "session_expired": "Session Expired", @@ -1180,6 +1258,8 @@ "set_your_local_language": "Set your local language", "settings_and_filters": "Settings and Filters", "severity_of_breathlessness": "Severity of Breathlessness", + "sex": "Sex", + "shift": "Shift Patient", "shift_request_updated_successfully": "Shift request updated successfully", "shifting": "Shifting", "shifting_approval_facility": "Shifting approval facility", @@ -1187,6 +1267,7 @@ "shifting_approving_facility_can_not_be_empty": "Shifting approving facility can not be empty.", "shifting_deleted": "Shifting record has been deleted successfully.", "shifting_details": "Shifting details", + "shifting_history": "Shifting History", "shifting_status": "Shifting status", "show_abha_profile": "Show ABHA Profile", "show_all": "Show all", @@ -1196,6 +1277,7 @@ "show_unread_notifications": "Show Unread", "sign_out": "Sign Out", "skills": "Skills", + "social_profile": "Social Profile", "socioeconomic_status": "Socioeconomic status", "software_update": "Software Update", "something_went_wrong": "Something went wrong..!", @@ -1224,9 +1306,11 @@ "subscription_error": "Subscription Error", "subscription_failed": "Subscription Failed", "suggested_investigations": "Suggested Investigations", + "suggestion": "Suggestion", "summary": "Summary", "support": "Support", "switch": "Switch", + "switch_bed": "Switch Bed", "switch_camera_is_not_available": "Switch camera is not available.", "symptoms": "Symptoms", "systolic": "Systolic", @@ -1239,11 +1323,15 @@ "to_be_conducted": "To be conducted", "total_amount": "Total Amount", "total_beds": "Total Beds", + "total_patients": "Total Patients", "total_staff": "Total Staff", "total_users": "Total Users", "transcript_edit_info": "You can update this if we made an error", "transcript_information": "This is what we heard", + "transfer_allowed": "Transfer Allowed", + "transfer_blocked": "Transfer Blocked", "transfer_in_progress": "TRANSFER IN PROGRESS", + "transfer_status_updated": "Transfer Status Updated", "transfer_to_receiving_facility": "Transfer to receiving facility", "travel_within_last_28_days": "Domestic/international Travel (within last 28 days)", "treating_doctor": "Treating Doctor", @@ -1283,6 +1371,7 @@ "update_facility_middleware_success": "Facility middleware updated successfully", "update_log": "Update Log", "update_patient_details": "Update Patient Details", + "update_preset": "Update Preset", "update_preset_position_to_current": "Update preset's position to camera's current position", "update_record": "Update Record", "update_record_for_asset": "Update record for asset", @@ -1297,6 +1386,7 @@ "upload_headings__patient": "Upload New Patient File", "upload_headings__sample_report": "Upload Sample Report", "upload_headings__supporting_info": "Upload Supporting Info", + "upload_report": "Upload Report", "uploading": "Uploading", "use_existing_abha_address": "Use Existing ABHA Address", "user_deleted_successfuly": "User Deleted Successfuly", @@ -1304,6 +1394,8 @@ "username": "Username", "users": "Users", "vacant": "Vacant", + "vaccinated": "Vaccinated", + "vaccine_name": "Vaccine name", "vehicle_preference": "Vehicle preference", "vendor_name": "Vendor Name", "ventilator_interface": "Respiratory Support Type", @@ -1319,21 +1411,30 @@ "verify_otp_success": "OTP has been verified successfully.", "verify_patient_identifier": "Please verify the patient identifier", "verify_using": "Verify Using", + "video_call": "Video Call", "video_conference_link": "Video Conference Link", "view": "View", "view_abdm_records": "View ABDM Records", + "view_all_details": "View All Details", "view_asset": "View Assets", "view_cns": "View CNS", "view_consultation_and_log_updates": "View Consultation / Log Updates", "view_details": "View Details", "view_faciliy": "View Facility", + "view_files": "View Files", "view_patients": "View Patients", + "view_update_patient_files": "View/Update patient files", + "view_updates": "View Updates", "view_users": "View Users", + "village": "Village", "virtual_nursing_assistant": "Virtual Nursing Assistant", "vitals": "Vitals", "vitals_monitor": "Vitals Monitor", "vitals_present": "Vitals Monitor present", "voice_autofill": "Voice Autofill", + "volunteer_assigned": "Volunteer assigned successfuly", + "volunteer_contact": "Volunteer Contact", + "volunteer_unassigned": "Volunteer unassigned successfuly", "ward": "Ward", "warranty_amc_expiry": "Warranty / AMC Expiry", "what_facility_assign_the_patient_to": "What facility would you like to assign the patient to", @@ -1341,6 +1442,7 @@ "why_the_asset_is_not_working": "Why the asset is not working?", "width": "Width ({{unit}})", "working_status": "Working Status", + "year_of_birth": "Year of Birth", "years": "years", "years_of_experience": "Years of Experience", "years_of_experience_of_the_doctor": "Years of Experience of the Doctor", diff --git a/public/locale/hi.json b/public/locale/hi.json index 0eb0cc28cfd..b3c172021dd 100644 --- a/public/locale/hi.json +++ b/public/locale/hi.json @@ -567,6 +567,7 @@ "patient_name": "मरीज का नाम", "patient_no": "ओपी/आईपी संख्या", "patient_phone_number": "मरीज़ का फ़ोन नंबर", + "patient_profile": "रोगी प्रोफ़ाइल", "patient_registration__address": "पता", "patient_registration__age": "आयु", "patient_registration__comorbidities": "comorbidities", diff --git a/public/locale/kn.json b/public/locale/kn.json index be18432e0ce..faf50da3a23 100644 --- a/public/locale/kn.json +++ b/public/locale/kn.json @@ -568,6 +568,7 @@ "patient_name": "ರೋಗಿಯ ಹೆಸರು", "patient_no": "OP/IP ಸಂ", "patient_phone_number": "ರೋಗಿಯ ಫೋನ್ ಸಂಖ್ಯೆ", + "patient_profile": "ರೋಗಿ ಪ್ರೊಫೈಲ್", "patient_registration__address": "ವಿಳಾಸ", "patient_registration__age": "ವಯಸ್ಸು", "patient_registration__comorbidities": "ಸಹವರ್ತಿ ರೋಗಗಳು", diff --git a/public/locale/ml.json b/public/locale/ml.json index cf381c32c0e..33dadcf9fb7 100644 --- a/public/locale/ml.json +++ b/public/locale/ml.json @@ -567,6 +567,7 @@ "patient_name": "രോഗിയുടെ പേര്", "patient_no": "OP/IP നമ്പർ", "patient_phone_number": "രോഗിയുടെ ഫോൺ നമ്പർ", + "patient_profile": "രോഗിയുടെ പ്രൊഫൈൽ", "patient_registration__address": "വിലാസം", "patient_registration__age": "പ്രായം", "patient_registration__comorbidities": "കോമോർബിഡിറ്റികൾ", diff --git a/public/locale/mr.json b/public/locale/mr.json index 06e9c5bbbde..543278bf867 100644 --- a/public/locale/mr.json +++ b/public/locale/mr.json @@ -49,6 +49,7 @@ "new_password": "नवीन पासवर्ड", "no_duplicate_facility": "बनावट हॉस्पिटल सुविधा मुळीच तयार करू नका", "no_facilities": "कोणतेही हॉस्पिटल नाही", + "patient_profile": "रुग्ण प्रोफाइल", "password": "पासवर्ड", "password_mismatch": "\"पासवर्ड\" आणि \"पासवर्डची खात्री करा\" दोन्ही सारखे हवेत.", "password_reset_failure": "पासवर्ड रिसेट झाला नाही", @@ -62,5 +63,36 @@ "send_reset_link": "रीसेट लिंक पाठवा", "sign_out": "साइन आउट", "something_wrong": "काहीतरी चूक झाली! पुन्हा प्रयत्न करा", - "username": "युजरनेम" + "username": "युजरनेम", + "general_info": "सामान्य माहिती", + "phone": "फोन", + "date_of_birth": "जन्म तारीख", + "sex": "लिंग", + "emergency_contact": "आपत्कालीन संपर्क", + "emergency_contact_person_name": "आपत्कालीन संपर्क व्यक्तीचे नाव", + "covid_details": "कोविड तपशील", + "number_of_covid_vaccine_doses": "कोविड लसीचे डोस", + "vaccine_name": "लस नाव", + "last_vaccinated_on": "शेवटचा लसीकरणाचा दिवस", + "countries_travelled": "प्रवास केलेले देश", + "date_of_return": "परतण्याची तारीख", + "social_profile": "सामाजिक प्रोफाइल", + "no_social_profile_details_available": "कोणतीही सामाजिक प्रोफाइल माहिती उपलब्ध नाही", + "location": "स्थान", + "current_address": "सध्याचा पत्ता", + "permanent_address": "स्थायी पत्ता", + "nationality": "राष्ट्रीयत्व", + "state": "राज्य", + "local_body": "स्थानिक संस्था", + "ward": "वॉर्ड", + "village": "गाव", + "volunteer_contact": "स्वयंसेवक संपर्क", + "emergency_contact_volunteer": "आपत्कालीन संपर्क (स्वयंसेवक)", + "emergency_contact_person_name_volunteer": "आपत्कालीन संपर्क व्यक्तीचे नाव (स्वयंसेवक)", + "medical": "वैद्यकीय", + "no_medical_history_available": "कोणताही वैद्यकीय इतिहास उपलब्ध नाही", + "present_health": "सध्याचे आरोग्य", + "ongoing_medications": "चालू औषधे", + "allergies": "अॅलर्जी", + "is_pregnant": "गर्भवती आहे" } diff --git a/src/CAREUI/display/Chip.tsx b/src/CAREUI/display/Chip.tsx index a2bacf8bd61..e501bf26f90 100644 --- a/src/CAREUI/display/Chip.tsx +++ b/src/CAREUI/display/Chip.tsx @@ -26,7 +26,7 @@ export default function Chip({ + {secondaryPath && ( + + )} ); } diff --git a/src/CAREUI/icons/DuoTonePaths.json b/src/CAREUI/icons/DuoTonePaths.json new file mode 100644 index 00000000000..663f71a7dba --- /dev/null +++ b/src/CAREUI/icons/DuoTonePaths.json @@ -0,0 +1,72 @@ +{ + "d-people": [ + 24, + "M14.6,10.9c.7.7,1.66,1.1,2.65,1.1s1.95-.4,2.65-1.1c.7-.7,1.1-1.66,1.1-2.65s-.4-1.95-1.1-2.65c-.7-.7-1.66-1.1-2.65-1.1s-1.95.4-2.65,1.1c-.7.7-1.1,1.66-1.1,2.65s.4,1.95,1.1,2.65ZM16.5,19.5c0,.52-.1,1.03-.32,1.5h6.32s1.5,0,1.5-1.5-1.5-6-7.5-6c-.98-.01-1.96.13-2.9.42,1.88,1.45,2.9,3.55,2.9,5.58Z", + true, + null, + "M13.5,21s1.5,0,1.5-1.5-1.5-6-7.5-6S0,18,0,19.5s1.5,1.5,1.5,1.5h12ZM7.5,12c-1.19,0-2.34-.47-3.18-1.32-.84-.84-1.32-1.99-1.32-3.18s.47-2.34,1.32-3.18c.84-.84,1.99-1.32,3.18-1.32s2.34.47,3.18,1.32c.84.84,1.32,1.99,1.32,3.18s-.47,2.34-1.32,3.18c-.84.84-1.99,1.32-3.18,1.32Z" + ], + "d-bell": [ + 24, + "M13.62,22.62c-.56.56-1.33.88-2.12.88s-1.56-.32-2.12-.88c-.56-.56-.88-1.33-.88-2.12h6c0,.8-.32,1.56-.88,2.12Z", + true, + null, + "M12.99,1.65c.02-.21,0-.42-.07-.62-.07-.2-.17-.38-.31-.54-.14-.16-.31-.28-.5-.36-.19-.09-.4-.13-.61-.13s-.42.04-.61.13c-.19.09-.36.21-.5.36-.14.16-.25.34-.31.54-.07.2-.09.41-.07.62-1.7.34-3.22,1.26-4.32,2.6-1.09,1.34-1.69,3.02-1.69,4.75,0,1.65-.75,9-3,10.5h21c-2.25-1.5-3-8.85-3-10.5,0-3.63-2.58-6.66-6.01-7.35Z" + ], + "d-book-open": [ + 24, + "M11.75,3.02c1.57-.95,2.95-1.46,4.45-1.73,1.55-.27,3.12-.29,4.7-.29.56,0,.84,0,1.06.11.19.1.34.25.44.44.11.21.11.5.11,1.06v8.96c0,1.59,0,2.39-.29,3-.27.57-.63.97-1.18,1.29-.58.35-1.5.43-3.33.6-.42.04-.83.09-1.24.16-1.73.3-3.25,1.09-4.71,2.03V3.02ZM2.75,18.51c-.41,0-.75.34-.75.75s.34.75.75.75c1.97,0,3.4.12,4.68.47,1.27.35,2.45.93,3.91,1.9.25.17.58.17.83,0,1.46-.97,2.63-1.55,3.91-1.9,1.28-.35,2.7-.47,4.68-.47.41,0,.75-.34.75-.75s-.34-.75-.75-.75c-2.03,0-3.6.13-5.07.53-1.34.37-2.56.95-3.93,1.83-1.37-.88-2.59-1.46-3.93-1.83-1.47-.4-3.05-.53-5.07-.53Z", + true, + null, + "M11.75,3.02c-1.57-.95-2.95-1.46-4.45-1.73-1.55-.27-3.12-.29-4.7-.29-.56,0-.84,0-1.06.11-.19.1-.34.25-.44.44-.11.21-.11.5-.11,1.06v8.96c0,1.59,0,2.39.29,3,.27.57.63.97,1.18,1.29.58.35,1.5.43,3.33.6.42.04.83.09,1.24.16,1.73.3,3.25,1.09,4.71,2.03V3.02Z" + ], + "d-patient": [ + 24, + "M12.5,9.66c2.67,0,4.83-2.16,4.83-4.83S15.17,0,12.5,0s-4.83,2.16-4.83,4.83,2.16,4.83,4.83,4.83Z", + true, + null, + "M19.48,12.64c-.26-.26-.55-.5-.86-.69-.26-.17-.53-.31-.82-.43l-1.61,11.62h-4.15l4.07-12.01c-.1,0-.2,0-.31,0h-6.61c-1.39,0-2.69.54-3.67,1.52-.98.98-1.52,2.29-1.52,3.67v4.51c0,1.27,1.04,2.31,2.31,2.31h4.82l-.1.29c-.05.13-.02.28.06.39.08.11.21.18.35.18h5.13c.22,0,.4-.16.43-.37l.07-.49v-.06c1.04-.23,1.81-1.15,1.81-2.25,0-.86-.47-1.63-1.21-2.03l.12-.9c1.18.49,1.95,1.64,1.95,2.93,0,.91-.38,1.73-1,2.31h2.25v-6.82c0-1.39-.54-2.69-1.52-3.67h0ZM7.67,18.52c-.24,0-.43-.19-.43-.43v-1.77c0-.24.19-.43.43-.43s.43.19.43.43v1.34h4.89l-.29.87h-5.02Z" + ], + "d-calendar": [ + 24, + "M7,2c.55,0,1,.45,1,1v1h7v-1c0-.55.45-1,1-1s1,.45,1,1v1h2.5c1.38,0,2.5,1.12,2.5,2.5v2.5H1v-2.5c0-1.38,1.12-2.5,2.5-2.5h2.5v-1c0-.55.45-1,1-1ZM5,13c-.55,0-1,.45-1,1s.45,1,1,1h.01c.55,0,1-.45,1-1s-.45-1-1-1h-.01ZM5,17c-.55,0-1,.45-1,1s.45,1,1,1h.01c.55,0,1-.45,1-1s-.45-1-1-1h-.01ZM9,14c0-.55.45-1,1-1h.01c.55,0,1,.45,1,1s-.45,1-1,1h-.01c-.55,0-1-.45-1-1ZM10,17c-.55,0-1,.45-1,1s.45,1,1,1h.01c.55,0,1-.45,1-1s-.45-1-1-1h-.01ZM14,14c0-.55.45-1,1-1h.01c.55,0,1,.45,1,1s-.45,1-1,1h-.01c-.55,0-1-.45-1-1Z", + true, + null, + "M1,20v-11h21v11c0,1.66-1.34,3-3,3H4c-1.66,0-3-1.34-3-3Z" + ], + "d-folder": [ + 24, + "M2,10v-5c0-1.66,1.34-3,3-3h4.31c.62,0,1.2.29,1.58.78l1.81,2.34c.19.24.48.39.79.39h6.51c1.1,0,2,.9,2,2v2.5c0,1.1-.9,2-2,2H4c-1.1,0-2-.9-2-2Z", + true, + null, + "M2.24,19.04l-.2-14c-.02-1.67,1.33-3.04,3-3.04h4.23c.61,0,1.19.28,1.57.76l1.88,2.38c.18.22.44.36.73.38l6.46.37c1.05.06,1.87.92,1.89,1.97l.16,11.1c.02,1.67-1.33,3.04-3,3.04H5.24c-1.64,0-2.98-1.32-3-2.96Z" + ], + "d-ambulance": [ + 24, + "M15.44,7.06c0-.25.1-.49.28-.66.18-.18.42-.28.66-.28s.49.1.66.28c.18.18.28.42.28.66v.95h-1.89v-.95ZM19.41,9.05l1.29,2.53,1.91,1.02c.32.17.59.44.79.75.18.31.3.68.3,1.05v.23h0c.08,0,.16.03.23.08.06.06.08.14.08.23v1.47c0,.08-.03.16-.1.23-.06.06-.14.08-.23.08h0v.04c0,.07-.01.14-.04.21s-.07.13-.13.18-.11.1-.18.13-.14.04-.21.04h-2.22c-.01-.66-.3-1.3-.78-1.77s-1.12-.72-1.78-.72-1.31.25-1.78.72c-.48.47-.76,1.1-.78,1.77h-1.26v-8.96h3.73c.24,0,.47.07.66.18.2.13.37.3.47.51h.03ZM15.75,13.09h1.24s.11-.03.14-.06c.04-.04.06-.08.06-.14s-.03-.11-.06-.14c-.04-.04-.08-.06-.14-.06h-1.24s-.11.03-.14.06c-.04.04-.06.08-.06.14s.03.11.06.14c.04.04.08.06.14.06ZM15.54,9.25v2.83h4.31l-1.31-2.47c-.06-.11-.14-.2-.24-.25s-.23-.1-.34-.1h-2.42ZM5.31,15.38c-1.12,0-2.03.91-2.03,2.03s.91,2.03,2.03,2.03,2.03-.91,2.03-2.03-.91-2.03-2.03-2.03ZM5.31,18.23c-.45,0-.82-.37-.82-.82s.37-.82.82-.82.82.37.82.82-.37.82-.82.82ZM18.39,15.38c-1.12,0-2.03.91-2.03,2.03s.91,2.03,2.03,2.03,2.03-.91,2.03-2.03-.91-2.03-2.03-2.03ZM18.39,18.23c-.45,0-.82-.37-.82-.82s.37-.82.82-.82.82.37.82.82-.37.82-.82.82Z", + true, + null, + "M.34,14.62V5.91c0-.1.01-.2.06-.28s.1-.17.17-.24.16-.13.24-.17.18-.06.28-.06h2.09c0-.16.07-.3.17-.41.11-.11.25-.17.41-.17h6.36c.16,0,.3.06.41.17s.17.25.17.41h2.5c.2,0,.4.08.54.23s.23.34.23.54v11.4h-6.09c-.01-.66-.3-1.3-.78-1.77s-1.12-.72-1.78-.72-1.31.25-1.78.72c-.48.47-.76,1.1-.78,1.77h-1.67c-.17,0-.34-.06-.48-.17s-.23-.25-.27-.42h-.03c-.08,0-.16-.03-.23-.08-.06-.06-.08-.14-.08-.23v-1.47c0-.08.03-.16.08-.23.06-.06.23-.08.23-.08l.03-.03ZM5.27,11.4h1.33v1.33h1.45v-1.33h1.33v-1.45h-1.33v-1.33h-1.45v1.33h-1.33v1.45Z" + ], + "d-microscope": [ + 24, + "M2.9,5.43c-.21-.21-.21-.55,0-.76l3.29-3.29c.21-.21.55-.21.76,0l6.07,6.07c.21.21.21.55,0,.76l-3.29,3.29c-.21.21-.55.21-.76,0L2.9,5.43ZM4.97,22.33c0-.3.24-.54.54-.54h10.55c.3,0,.54.24.54.54v1.13c0,.3-.24.54-.54.54H5.5c-.3,0-.54-.24-.54-.54v-1.13ZM15.29,15.68c-.3,0-.54.24-.54.54v.76c0,.3.24.54.54.54h6.88c.3,0,.54-.24.54-.54v-.76c0-.3-.24-.54-.54-.54h-6.88Z", + true, + null, + "M1.67,2.43c-.21.21-.21.55,0,.76l1.35,1.35,3.03-3.03L4.7.16c-.21-.21-.55-.21-.76,0L1.67,2.43ZM19.55,17.52c-.71,1.07-1.67,1.99-2.82,2.71-1.15.71-2.47,1.2-3.86,1.43-1.39.23-2.82.19-4.19-.12s-2.65-.86-3.75-1.64c-1.1-.78-1.99-1.75-2.62-2.86-.63-1.11-.97-2.32-1.01-3.56-.04-1.24.22-2.46.78-3.6.55-1.14,1.38-2.16,2.43-2.99l1.55,1.5c-.8.63-1.43,1.41-1.85,2.28-.42.87-.63,1.81-.59,2.75.03.94.3,1.87.77,2.72.48.85,1.16,1.59,2,2.18.84.59,1.82,1.02,2.86,1.25,1.05.23,2.14.26,3.2.09,1.06-.17,2.07-.54,2.95-1.09.88-.54,1.62-1.25,2.16-2.07l2,1.01ZM9.86,11.37l3.03-3.03,1.35,1.35c.21.21.21.55,0,.76l-2.27,2.27c-.21.21-.55.21-.76,0l-1.35-1.35Z" + ], + "d-notice-board": [ + 24, + "M.97,16.66c-.19,0-.38.08-.52.22-.14.14-.22.32-.22.52s.08.38.22.52c.14.14.32.22.52.22h4.21l-1.24,4.97c-.04.19,0,.38.09.55.1.16.26.28.45.33.19.05.38.02.55-.08.17-.1.29-.25.34-.44l.6-2.38h12.09l.6,2.38c.05.19.17.34.34.44.17.1.36.13.55.08.19-.05.35-.16.45-.33.1-.16.13-.36.09-.55l-1.24-4.97h4.21c.19,0,.38-.08.52-.22s.22-.32.22-.52-.08-.38-.22-.52-.32-.22-.52-.22H.97ZM6.32,19.61l.37-1.47h10.62l.37,1.47H6.32Z", + true, + null, + "M12.66.41c-.06-.12-.15-.22-.27-.3-.12-.07-.25-.11-.39-.11s-.27.04-.39.11c-.12.07-.21.17-.27.3l-.53,1.06H3.91c-.58,0-1.15.23-1.56.65s-.65.97-.65,1.56v11.03h20.59V3.68c0-.58-.23-1.15-.65-1.56-.41-.41-.97-.65-1.56-.65h-6.9l-.53-1.06Z" + ], + "d-hospital": [ + 24, + "M12.7,10.42h1.15v1.41h-1.15v1.15h-1.41v-1.15h-1.15v-1.41h1.15v-1.15h1.41v1.15ZM6.24,10.74v10.61c0,.29-.24.53-.53.53H1.03c-.29,0-.53-.24-.53-.53v-10.61c0-.29.24-.53.53-.53h4.68c.29,0,.53.24.53.53ZM4.96,14.98c0-.29-.24-.53-.53-.53h-2.12c-.29,0-.53.24-.53.53v2.12c0,.29.24.53.53.53h2.12c.29,0,.53-.24.53-.53v-2.12ZM23.5,10.74v10.61c0,.29-.24.53-.53.53h-4.68c-.29,0-.53-.24-.53-.53v-10.61c0-.29.24-.53.53-.53h4.68c.29,0,.53.24.53.53ZM22.22,14.98c0-.29-.24-.53-.53-.53h-2.12c-.29,0-.53.24-.53.53v2.12c0,.29.24.53.53.53h2.12c.29,0,.53-.24.53-.53v-2.12Z", + true, + null, + "M7.3,6.9v14.45c0,.29.24.53.53.53h1.5c.29,0,.53-.24.53-.53v-4.88h4.28v4.88c0,.29.24.53.53.53h1.5c.29,0,.53-.24.53-.53v-11.5s0-2.94,0-2.94c0-.29-.24-.53-.53-.53H7.83c-.29,0-.53.24-.53.53ZM12.02,13.94c-1.54,0-2.82-1.24-2.82-2.82s1.24-2.82,2.82-2.82,2.82,1.24,2.82,2.82-1.28,2.82-2.82,2.82ZM17.3,5.31H6.74c-.31,0-.56-.25-.56-.56v-2.07c0-.31.25-.56.56-.56h10.56c.31,0,.56.25.56.56v2.07c0,.31-.25.56-.56.56Z" + ] +} \ No newline at end of file diff --git a/src/CAREUI/misc/PrintPreview.tsx b/src/CAREUI/misc/PrintPreview.tsx index 53eda1339ac..daf79ecbb34 100644 --- a/src/CAREUI/misc/PrintPreview.tsx +++ b/src/CAREUI/misc/PrintPreview.tsx @@ -8,7 +8,8 @@ import { ZoomTransform, } from "@/CAREUI/interactive/Zoom"; -import ButtonV2 from "@/components/Common/ButtonV2"; +import { Button } from "@/components/ui/button"; + import Page from "@/components/Common/Page"; import useBreakpoints from "@/hooks/useBreakpoints"; @@ -30,10 +31,10 @@ export default function PrintPreview(props: Props) {
- +
diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 125ee8c9055..8ba9460e34b 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -111,7 +111,7 @@ export default function AppRouter() { return ( -
+
<>
{" "} @@ -121,7 +121,7 @@ export default function AppRouter() {
-
+
@@ -91,32 +91,26 @@ export default function Breadcrumbs({ +
{
+
+ + - - - {showPatientNotesPopup && ( - - )} -
+ )} ); }; diff --git a/src/components/Facility/DischargedPatientsList.tsx b/src/components/Facility/DischargedPatientsList.tsx index f3010db7046..b489a799b1d 100644 --- a/src/components/Facility/DischargedPatientsList.tsx +++ b/src/components/Facility/DischargedPatientsList.tsx @@ -288,7 +288,7 @@ const DischargedPatientsList = ({ text="Discharged Patients" count={count} loading={facilityQuery.loading} - icon="l-user-injured" + icon="d-patient" className="pb-12" />
diff --git a/src/components/Facility/FacilityList.tsx b/src/components/Facility/FacilityList.tsx index 6570bf7ea58..164632e1601 100644 --- a/src/components/Facility/FacilityList.tsx +++ b/src/components/Facility/FacilityList.tsx @@ -12,7 +12,6 @@ import Page from "@/components/Common/Page"; import { FacilityCard } from "@/components/Facility/FacilityCard"; import FacilityFilter from "@/components/Facility/FacilityFilter"; import { FacilityModel } from "@/components/Facility/models"; -import SearchInput from "@/components/Form/SearchInput"; import useAuthUser from "@/hooks/useAuthUser"; import useFilters from "@/hooks/useFilters"; @@ -22,6 +21,8 @@ import { FACILITY_TYPES } from "@/common/constants"; import routes from "@/Utils/request/api"; import useQuery from "@/Utils/request/useQuery"; +import SearchByMultipleFields from "../Common/SearchByMultipleFields"; + export const FacilityList = () => { const { qParams, @@ -30,6 +31,7 @@ export const FacilityList = () => { FilterBadges, advancedFilter, resultsPerPage, + clearSearch, } = useFilters({ limit: 14, cacheBlacklist: ["search"], @@ -153,49 +155,59 @@ export const FacilityList = () => { breadcrumbs={false} hideBack options={ - +
+ advancedFilter.setShow(true)} /> + +
} > -
+
+ updateQuery({ search: value })} + clearSearch={clearSearch} /> -
- updateQuery({ [e.name]: e.value })} - placeholder={t("facility_search_placeholder")} - /> - advancedFilter.setShow(true)} /> -
diff --git a/src/components/Facility/FacilityUsers.tsx b/src/components/Facility/FacilityUsers.tsx index c8682b516f8..2f3393d9aa2 100644 --- a/src/components/Facility/FacilityUsers.tsx +++ b/src/components/Facility/FacilityUsers.tsx @@ -278,7 +278,7 @@ export default function FacilityUsers(props: any) { text={t("total_users")} count={facilityUserData.count} loading={isLoading} - icon="l-user-injured" + icon="d-people" className="flex-1" /> )} diff --git a/src/components/Facility/LocationManagement.tsx b/src/components/Facility/LocationManagement.tsx index 12e70d1fa14..2f80a879651 100644 --- a/src/components/Facility/LocationManagement.tsx +++ b/src/components/Facility/LocationManagement.tsx @@ -239,7 +239,10 @@ const Location = ({ const totalBeds = bedsQuery.data?.count; return ( -
+
diff --git a/src/components/Files/AudioCaptureDialog.tsx b/src/components/Files/AudioCaptureDialog.tsx index 5512fad9dc6..d198ac7db16 100644 --- a/src/components/Files/AudioCaptureDialog.tsx +++ b/src/components/Files/AudioCaptureDialog.tsx @@ -1,5 +1,5 @@ import { Link } from "raviger"; -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -26,6 +26,7 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const { show, onHide, onCapture, autoRecord = false } = props; const [status, setStatus] = useState(null); + const mediaStreamRef = useRef(null); const { t } = useTranslation(); const { audioURL, resetRecording, startRecording, stopRecording } = @@ -42,7 +43,8 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const handleStartRecording = () => { navigator.mediaDevices .getUserMedia({ audio: true }) - .then(() => { + .then((stream) => { + mediaStreamRef.current = stream; setStatus("RECORDING"); startRecording(); timer.start(); @@ -105,6 +107,13 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { if (autoRecord && show && status === "RECORDING") { handleStartRecording(); } + + return () => { + if (mediaStreamRef.current) { + mediaStreamRef.current.getTracks().forEach((track) => track.stop()); + mediaStreamRef.current = null; + } + }; }, [autoRecord, status, show]); return ( diff --git a/src/components/Files/CameraCaptureDialog.tsx b/src/components/Files/CameraCaptureDialog.tsx index 91b158bcb44..f81d854c559 100644 --- a/src/components/Files/CameraCaptureDialog.tsx +++ b/src/components/Files/CameraCaptureDialog.tsx @@ -7,7 +7,7 @@ import CareIcon from "@/CAREUI/icons/CareIcon"; import ButtonV2, { Submit } from "@/components/Common/ButtonV2"; import DialogModal from "@/components/Common/Dialog"; -import useWindowDimensions from "@/hooks/useWindowDimensions"; +import useBreakpoints from "@/hooks/useBreakpoints"; import * as Notify from "@/Utils/Notifications"; @@ -15,38 +15,61 @@ export interface CameraCaptureDialogProps { show: boolean; onHide: () => void; onCapture: (file: File, fileName: string) => void; + onResetCapture: () => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { - const { show, onHide, onCapture } = props; + const { show, onHide, onCapture, onResetCapture } = props; + const isLaptopScreen = useBreakpoints({ lg: true, default: false }); - const [cameraFacingFront, setCameraFacingFront] = useState(true); + const [cameraFacingMode, setCameraFacingMode] = useState( + isLaptopScreen ? "user" : "environment", + ); const [previewImage, setPreviewImage] = useState(null); const webRef = useRef(null); const videoConstraints = { width: { ideal: 4096 }, height: { ideal: 2160 }, - facingMode: "user", + facingMode: cameraFacingMode, }; useEffect(() => { if (!show) return; - navigator.mediaDevices.getUserMedia({ video: true }).catch(() => { - Notify.Warn({ - msg: t("camera_permission_denied"), + let stream: MediaStream | null = null; + + navigator.mediaDevices + .getUserMedia({ video: { facingMode: cameraFacingMode } }) + .then((mediaStream) => { + stream = mediaStream; + }) + .catch(() => { + Notify.Warn({ + msg: t("camera_permission_denied"), + }); + onHide(); }); - onHide(); - }); - }, [show]); - const handleSwitchCamera = useCallback(() => { - const supportedConstraints = - navigator.mediaDevices.getSupportedConstraints(); - if ( - !isLaptopScreen && - typeof supportedConstraints.facingMode === "string" && - (supportedConstraints.facingMode as string).includes("environment") - ) { - setCameraFacingFront((prevState) => !prevState); + + return () => { + if (stream) { + stream.getTracks().forEach((track) => { + track.stop(); + }); + } + }; + }, [show, cameraFacingMode, onHide]); + + const handleSwitchCamera = useCallback(async () => { + const devices = await navigator.mediaDevices.enumerateDevices(); + const videoInputs = devices.filter( + (device) => device.kind === "videoinput", + ); + const backCamera = videoInputs.some((device) => + device.label.toLowerCase().includes("back"), + ); + if (!isLaptopScreen && backCamera) { + setCameraFacingMode((prevMode) => + prevMode === "environment" ? "user" : "environment", + ); } else { Notify.Warn({ msg: t("switch_camera_is_not_available"), @@ -54,10 +77,6 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { } }, []); - const { width } = useWindowDimensions(); - const LaptopScreenBreakpoint = 640; - const isLaptopScreen = width >= LaptopScreenBreakpoint ? true : false; - const captureImage = () => { setPreviewImage(webRef.current.getScreenshot()); const canvas = webRef.current.getCanvas(); @@ -70,10 +89,6 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { }); }; - const cameraFacingMode = cameraFacingFront - ? "user" - : { exact: "environment" }; - return ( { setPreviewImage(null); + onResetCapture(); }} className="m-2" > @@ -169,6 +185,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { variant="secondary" onClick={() => { setPreviewImage(null); + onResetCapture(); onHide(); }} className="m-2" @@ -207,6 +224,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { { setPreviewImage(null); + onResetCapture(); }} > {t("retake")} @@ -228,6 +246,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { variant="secondary" onClick={() => { setPreviewImage(null); + onResetCapture(); onHide(); }} > diff --git a/src/components/Files/FileUpload.tsx b/src/components/Files/FileUpload.tsx index d98df131863..50ab3ffdb01 100644 --- a/src/components/Files/FileUpload.tsx +++ b/src/components/Files/FileUpload.tsx @@ -69,6 +69,8 @@ export interface StateInterface { isZoomInDisabled: boolean; isZoomOutDisabled: boolean; rotation: number; + id?: string; + associating_id?: string; } export const FileUpload = (props: FileUploadProps) => { @@ -208,8 +210,15 @@ export const FileUpload = (props: FileUploadProps) => { type, onArchive: refetchAll, onEdit: refetchAll, + uploadedFiles: + fileQuery?.data?.results + .slice() + .reverse() + .map((file) => ({ + ...file, + associating_id: associatedId, + })) || [], }); - const dischargeSummaryFileManager = useFileManager({ type: "DISCHARGE_SUMMARY", onArchive: refetchAll, @@ -244,7 +253,6 @@ export const FileUpload = (props: FileUploadProps) => { id: "record-audio", }, ]; - return (
{fileUpload.Dialogues} diff --git a/src/components/Form/FormFields/PhoneNumberFormField.tsx b/src/components/Form/FormFields/PhoneNumberFormField.tsx index f96854f4a4a..0fd6f914035 100644 --- a/src/components/Form/FormFields/PhoneNumberFormField.tsx +++ b/src/components/Form/FormFields/PhoneNumberFormField.tsx @@ -1,5 +1,6 @@ import { Popover, PopoverButton, PopoverPanel } from "@headlessui/react"; import { useCallback, useEffect, useMemo, useState } from "react"; +import React from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -29,136 +30,144 @@ import { const phoneCodes: Record = phoneCodesJson; interface Props extends FormFieldBaseProps { + onError?: (error: FieldError) => void; + hideHelp?: boolean; types: PhoneNumberType[]; placeholder?: string; autoComplete?: string; disableValidation?: boolean; } -export default function PhoneNumberFormField(props: Props) { - const field = useFormFieldPropsResolver(props); - const [error, setError] = useState(); - const [country, setCountry] = useState({ - flag: "🇮🇳", - name: "India", - code: "91", - }); - const validator = useMemo( - () => PhoneNumberValidator(props.types), - [props.types], - ); +const PhoneNumberFormField = React.forwardRef( + (props, ref) => { + const field = useFormFieldPropsResolver(props); + const [error, setError] = useState(); + const [country, setCountry] = useState({ + flag: "🇮🇳", + name: "India", + code: "91", + }); + const validator = useMemo( + () => PhoneNumberValidator(props.types), + [props.types], + ); - const validate = useMemo( - () => (value: string | undefined, event: "blur" | "change") => { - if (!value || props.disableValidation) { - return; - } + const validate = useMemo( + () => (value: string | undefined, event: "blur" | "change") => { + if (!value || props.disableValidation) { + return; + } - const newError = validator(value); + const newError = validator(value); - if (!newError) { - return; - } else if (event === "blur") { - return newError; - } - }, - [props.disableValidation], - ); + if (!newError) { + return; + } else if (event === "blur") { + return newError; + } + }, + [props.disableValidation], + ); - const setValue = useCallback( - (value: string) => { - value = value.replaceAll(/[^0-9+]/g, ""); - if (value.length > 12 && value.startsWith("+910")) { - value = "+91" + value.slice(4); - } + const setValue = useCallback( + (value: string) => { + value = value.replaceAll(/[^0-9+]/g, ""); + if (value.length > 12 && value.startsWith("+910")) { + value = "+91" + value.slice(4); + } - const error = validate(value, "change"); - field.handleChange(value); + const error = validate(value, "change"); + field.handleChange(value); - setError(error); - }, - [field, validate], - ); - - const handleCountryChange = (value: CountryData): void => { - setCountry(value); - setValue(conditionPhoneCode(value.code)); - }; - - useEffect(() => { - if (field.value && field.value.length > 0) { - if (field.value.startsWith("1800")) { - setCountry({ flag: "📞", name: "Support", code: "1800" }); - return; - } - if (field.value === "+") { - setCountry({ flag: "🌍", name: "Other", code: "+" }); - return; + setError(error); + }, + [field, validate, error], + ); + useEffect(() => { + if (props.onError) { + props.onError(error); } - setCountry(phoneCodes[getCountryCode(field.value)!]); - } - }, [setValue]); + }, [error]); + const handleCountryChange = (value: CountryData): void => { + setCountry(value); + setValue(conditionPhoneCode(value.code)); + }; - return ( - - ), - }} - > -
- - {({ open }: { open: boolean }) => { - return ( - <> - -
- - {country?.flag ?? "🇮🇳"} - - -
-
- setValue(e.target.value)} - disabled={field.disabled} - onBlur={() => setError(validate(field.value, "blur"))} - /> - - {({ close }) => ( - - )} - - - ); - }} -
-
-
- ); -} + useEffect(() => { + if (field.value && field.value.length > 0) { + if (field.value.startsWith("1800")) { + setCountry({ flag: "📞", name: "Support", code: "1800" }); + return; + } + if (field.value === "+") { + setCountry({ flag: "🌍", name: "Other", code: "+" }); + return; + } + setCountry(phoneCodes[getCountryCode(field.value)!]); + } + }, [setValue]); + return ( + + )), + }} + > +
+ + {({ open }: { open: boolean }) => { + return ( + <> + +
+ {country?.flag} + +
+
+ setValue(e.target.value)} + disabled={field.disabled} + onBlur={() => setError(validate(field.value, "blur"))} + ref={ref} + /> + + {({ close }) => ( + + )} + + + ); + }} +
+
+
+ ); + }, +); const PhoneNumberTypesHelp = (props: { types: PhoneNumberType[] }) => { const { t } = useTranslation(); @@ -175,7 +184,6 @@ const PhoneNumberTypesHelp = (props: { types: PhoneNumberType[] }) => {
); }; - const conditionPhoneCode = (code: string) => { code = code.split(" ")[0]; return code.startsWith("+") ? code : "+" + code; @@ -280,3 +288,4 @@ const CountryCodesList = ({
); }; +export default PhoneNumberFormField; diff --git a/src/components/LogUpdate/CriticalCarePreview.tsx b/src/components/LogUpdate/CriticalCarePreview.tsx index e131e43f405..e9f4c697d82 100644 --- a/src/components/LogUpdate/CriticalCarePreview.tsx +++ b/src/components/LogUpdate/CriticalCarePreview.tsx @@ -1,10 +1,13 @@ +import { Link } from "raviger"; import React, { useEffect } from "react"; import { useTranslation } from "react-i18next"; import Card from "@/CAREUI/display/Card"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Button } from "@/components/ui/button"; import { meanArterialPressure } from "@/components/Common/BloodPressureFormField"; -import ButtonV2 from "@/components/Common/ButtonV2"; import Loading from "@/components/Common/Loading"; import { ABGAnalysisFields } from "@/components/LogUpdate/Sections/ABGAnalysis"; import { IOBalanceSections } from "@/components/LogUpdate/Sections/IOBalance"; @@ -49,14 +52,27 @@ export default function CriticalCarePreview(props: Props) { return (
-
- + +
diff --git a/src/components/Notifications/NotificationsList.tsx b/src/components/Notifications/NotificationsList.tsx index f9b65b35201..dd25eb799bf 100644 --- a/src/components/Notifications/NotificationsList.tsx +++ b/src/components/Notifications/NotificationsList.tsx @@ -490,7 +490,7 @@ export default function NotificationsList({ text={t("Notifications")} id="notification-slide-btn" do={() => setOpen(!open)} - icon={} + icon={} badgeCount={unreadCount} handleOverflow={handleOverflow} /> diff --git a/src/components/Patient/DailyRoundListDetails.tsx b/src/components/Patient/DailyRoundListDetails.tsx index ec568ba56d2..2a2b76d26b2 100644 --- a/src/components/Patient/DailyRoundListDetails.tsx +++ b/src/components/Patient/DailyRoundListDetails.tsx @@ -1,7 +1,11 @@ +import { Link } from "raviger"; import { useState } from "react"; import { useTranslation } from "react-i18next"; -import ButtonV2 from "@/components/Common/ButtonV2"; +import CareIcon from "@/CAREUI/icons/CareIcon"; + +import { Button } from "@/components/ui/button"; + import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; import { DailyRoundsModel } from "@/components/Patient/models"; @@ -50,11 +54,14 @@ export const DailyRoundListDetails = (props: any) => {
- - Update Details - +
diff --git a/src/components/Patient/DailyRounds.tsx b/src/components/Patient/DailyRounds.tsx index 4e8573d6273..f0c78522e40 100644 --- a/src/components/Patient/DailyRounds.tsx +++ b/src/components/Patient/DailyRounds.tsx @@ -59,7 +59,6 @@ import request from "@/Utils/request/request"; import { formatDateTime } from "@/Utils/utils"; import { scrollTo } from "@/Utils/utils"; -import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; import TextFormField from "../Form/FormFields/TextFormField"; export const DailyRounds = (props: any) => { @@ -704,27 +703,32 @@ export const DailyRounds = (props: any) => { ]} /> - @@ -790,36 +794,6 @@ export const DailyRounds = (props: any) => { ]} /> - - { data-testid="patient-details" > {insuranceDetials?.results.map((data: HCXPolicyModel) => ( - + ))} )} diff --git a/src/components/Patient/InsuranceDetailsCard.tsx b/src/components/Patient/InsuranceDetailsCard.tsx index 62098fc6a07..6685e7a99a2 100644 --- a/src/components/Patient/InsuranceDetailsCard.tsx +++ b/src/components/Patient/InsuranceDetailsCard.tsx @@ -1,79 +1,53 @@ -import { navigate } from "raviger"; +import { useTranslation } from "react-i18next"; -import ButtonV2 from "@/components/Common/ButtonV2"; import { HCXPolicyModel } from "@/components/HCX/models"; interface InsuranceDetails { - data?: HCXPolicyModel; - showViewAllDetails?: boolean; + data: HCXPolicyModel; } -export const InsuranceDetialsCard = (props: InsuranceDetails) => { - const { data, showViewAllDetails } = props; +export const InsuranceDetailsCard = (props: InsuranceDetails) => { + const { data } = props; + + const { t } = useTranslation(); return (
-
-
- Policy Details -
- {data ? ( -
-
-
- Member ID -
-
- {data.subscriber_id || ""} -
+
+
+
+
+ {t("insurance__member_id")}
-
-
- Policy ID / Policy Name -
-
- {data.policy_id || ""} -
+
+ {data.subscriber_id || ""}
-
-
- Insurer ID -
-
- {data.insurer_id || ""} -
+
+
+
+ {t("insurance__policy_name")}
-
-
- Insurer Name -
-
- {data.insurer_name || ""} -
+
+ {data.policy_id || ""}
- {showViewAllDetails && ( -
-
- { - navigate( - `/facility/${data.patient_object?.facility_object?.id}/patient/${data.patient_object?.id}/insurance`, - ); - }} - className="h-auto whitespace-pre-wrap border border-secondary-500 bg-white text-black hover:bg-secondary-300" - > - View All Details - -
-
- )}
- ) : ( -
- No Insurance Details Available +
+
+ {t("insurance__insurer_id")} +
+
+ {data.insurer_id || ""} +
- )} +
+
+ {t("insurance__insurer_name")} +
+
+ {data.insurer_name || ""} +
+
+
); diff --git a/src/components/Patient/ManagePatients.tsx b/src/components/Patient/ManagePatients.tsx index 0664532b0a1..6fba4d6e734 100644 --- a/src/components/Patient/ManagePatients.tsx +++ b/src/components/Patient/ManagePatients.tsx @@ -1,38 +1,16 @@ import dayjs from "dayjs"; import { Link, navigate } from "raviger"; -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import Chip from "@/CAREUI/display/Chip"; -import CountBlock from "@/CAREUI/display/Count"; -import FilterBadge from "@/CAREUI/display/FilterBadge"; -import RecordMeta from "@/CAREUI/display/RecordMeta"; -import CareIcon from "@/CAREUI/icons/CareIcon"; -import { AdvancedFilterButton } from "@/CAREUI/interactive/FiltersSlideover"; - import { Avatar } from "@/components/Common/Avatar"; import ButtonV2 from "@/components/Common/ButtonV2"; import { ExportMenu } from "@/components/Common/Export"; import Loading from "@/components/Common/Loading"; import Page from "@/components/Common/Page"; +import SearchByMultipleFields from "@/components/Common/SearchByMultipleFields"; import SortDropdownMenu from "@/components/Common/SortDropdown"; import Tabs from "@/components/Common/Tabs"; -import { ICD11DiagnosisModel } from "@/components/Diagnosis/types"; -import { getDiagnosesByIds } from "@/components/Diagnosis/utils"; -import FacilitiesSelectDialogue from "@/components/ExternalResult/FacilitiesSelectDialogue"; -import DoctorVideoSlideover from "@/components/Facility/DoctorVideoSlideover"; -import { FacilityModel, PatientCategory } from "@/components/Facility/models"; -import { PhoneNumberValidator } from "@/components/Form/FieldValidators"; -import PhoneNumberFormField from "@/components/Form/FormFields/PhoneNumberFormField"; -import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import SearchInput from "@/components/Form/SearchInput"; -import { - DIAGNOSES_FILTER_LABELS, - DiagnosesFilterKey, - FILTER_BY_DIAGNOSES_KEYS, -} from "@/components/Patient/DiagnosesFilter"; -import PatientFilter from "@/components/Patient/PatientFilter"; -import { isPatientMandatoryDataFilled } from "@/components/Patient/Utils"; import useAuthUser from "@/hooks/useAuthUser"; import useFilters from "@/hooks/useFilters"; @@ -49,17 +27,36 @@ import { } from "@/common/constants"; import { parseOptionId } from "@/common/utils"; -import { triggerGoal } from "@/Integrations/Plausible"; -import * as Notification from "@/Utils/Notifications"; import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import useQuery from "@/Utils/request/useQuery"; + +import Chip from "../../CAREUI/display/Chip"; +import CountBlock from "../../CAREUI/display/Count"; +import FilterBadge from "../../CAREUI/display/FilterBadge"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; +import { triggerGoal } from "../../Integrations/Plausible"; +import * as Notification from "../../Utils/Notifications"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; import { formatPatientAge, humanizeStrings, isAntenatal, parsePhoneNumber, -} from "@/Utils/utils"; +} from "../../Utils/utils"; +import { ICD11DiagnosisModel } from "../Diagnosis/types"; +import { getDiagnosesByIds } from "../Diagnosis/utils"; +import FacilitiesSelectDialogue from "../ExternalResult/FacilitiesSelectDialogue"; +import DoctorVideoSlideover from "../Facility/DoctorVideoSlideover"; +import { FacilityModel, PatientCategory } from "../Facility/models"; +import { + DIAGNOSES_FILTER_LABELS, + DiagnosesFilterKey, + FILTER_BY_DIAGNOSES_KEYS, +} from "./DiagnosesFilter"; +import PatientFilter from "./PatientFilter"; +import { isPatientMandatoryDataFilled } from "./Utils"; interface TabPanelProps { children?: ReactNode; @@ -93,6 +90,7 @@ export const PatientManager = () => { Pagination, FilterBadges, resultsPerPage, + clearSearch, } = useFilters({ limit: 12, cacheBlacklist: [ @@ -109,30 +107,6 @@ export const PatientManager = () => { const [diagnoses, setDiagnoses] = useState([]); const [showDialog, setShowDialog] = useState<"create" | "list-discharged">(); const [showDoctors, setShowDoctors] = useState(false); - const [phoneNumber, _setPhoneNumber] = useState(""); - const [emergencyPhoneNumber, _setEmergencyPhoneNumber] = useState(""); - - const setPhoneNumber = (value: string) => { - _setPhoneNumber(value); - const error = PhoneNumberValidator()(value); - if (!error) { - updateQuery({ phone_number: value }); - } - if ((value === "+91" || value === "") && qParams.phone_number) { - updateQuery({ phone_number: null }); - } - }; - - const setEmergencyPhoneNumber = (value: string) => { - _setEmergencyPhoneNumber(value); - const error = PhoneNumberValidator()(value); - if (!error) { - updateQuery({ emergency_phone_number: value }); - } - if ((value === "+91" || value === "") && qParams.emergency_phone_number) { - updateQuery({ emergency_phone_number: null }); - } - }; const tabValue = qParams.last_consultation__new_discharge_reason || @@ -319,14 +293,6 @@ export const PatientManager = () => { const { loading: isLoading, data } = useQuery(routes.patientList, { query: params, - onResponse: () => { - if (!params.phone_number) { - _setPhoneNumber("+91"); - } - if (!params.emergency_phone_number) { - _setEmergencyPhoneNumber("+91"); - } - }, }); const getTheCategoryFromId = () => { @@ -779,22 +745,74 @@ export const PatientManager = () => { ); } - const queryField = (name: string, defaultValue?: T) => { - return { - name, - value: qParams[name] || defaultValue, - onChange: (e: FieldChangeEvent) => updateQuery({ [e.name]: e.value }), - }; - }; - const onlyAccessibleFacility = permittedFacilities?.count === 1 ? permittedFacilities.results[0] : null; + const searchOptions = [ + { + key: "phone_number", + label: "Phone Number", + type: "phone" as const, + placeholder: "Search_by_phone_number", + value: qParams.phone_number || "", + shortcutKey: "p", + }, + { + key: "name", + label: "Name", + type: "text" as const, + placeholder: "search_by_patient_name", + value: qParams.name || "", + shortcutKey: "n", + }, + { + key: "patient_no", + label: "IP/OP No", + type: "text" as const, + placeholder: "search_by_patient_no", + value: qParams.patient_no || "", + shortcutKey: "u", + }, + { + key: "emergency_contact_number", + label: "Emergency Contact Phone Number", + type: "phone" as const, + placeholder: "search_by_emergency_phone_number", + value: qParams.emergency_phone_number || "", + shortcutKey: "e", + }, + ]; + + const handleSearch = useCallback( + (key: string, value: string) => { + const updatedQuery = { + phone_number: + key === "phone_number" + ? value.length >= 13 || value === "" + ? value + : undefined + : undefined, + name: key === "name" ? value : undefined, + patient_no: key === "patient_no" ? value : undefined, + emergency_phone_number: + key === "emergency_contact_number" + ? value.length >= 13 || value === "" + ? value + : undefined + : undefined, + }; + + updateQuery(updatedQuery); + }, + [updateQuery], + ); + return (
@@ -973,59 +991,23 @@ export const PatientManager = () => { }} /> -
-
-
- -
-
-
-
-
- - -
-
- setPhoneNumber(e.value)} - types={["mobile", "landline"]} - className="w-full grow" - error={((phoneNumber || "+91") === "+91" && "") || undefined} - /> - setEmergencyPhoneNumber(e.value)} - types={["mobile", "landline"]} - className="w-full" - error={ - ((emergencyPhoneNumber || "+91") === "+91" && "") || undefined - } - /> -
-
+
+
+
+ +
{ />
- +
{managePatients}
diff --git a/src/components/Patient/PatientConsentRecords.tsx b/src/components/Patient/PatientConsentRecords.tsx index 162dcb2ff77..cbeb56b894f 100644 --- a/src/components/Patient/PatientConsentRecords.tsx +++ b/src/components/Patient/PatientConsentRecords.tsx @@ -112,6 +112,10 @@ export default function PatientConsentRecords(props: { crumbsReplacements={{ [facilityId]: { name: patient?.facility_object?.name }, [patientId]: { name: patient?.name }, + consultation: { + name: "Consultation", + uri: `/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/update`, + }, [consultationId]: { name: patient?.last_consultation?.suggestion === "A" @@ -121,7 +125,7 @@ export default function PatientConsentRecords(props: { : patient?.last_consultation?.suggestion_text, }, }} - backUrl={`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/`} + backUrl={`/facility/${facilityId}/patient/${patientId}/consultation/${consultationId}/update`} > {fileUpload.Dialogues} {fileManager.Dialogues} diff --git a/src/components/Patient/PatientDetailsTab/Demography.tsx b/src/components/Patient/PatientDetailsTab/Demography.tsx new file mode 100644 index 00000000000..b9d2b8fece4 --- /dev/null +++ b/src/components/Patient/PatientDetailsTab/Demography.tsx @@ -0,0 +1,461 @@ +import dayjs from "dayjs"; +import { navigate } from "raviger"; +import { Fragment, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; + +import Chip from "@/CAREUI/display/Chip"; +import CareIcon from "@/CAREUI/icons/CareIcon"; +import AuthorizedChild from "@/CAREUI/misc/AuthorizedChild"; + +import { Button } from "@/components/ui/button"; + +import useAuthUser from "@/hooks/useAuthUser"; + +import { GENDER_TYPES } from "@/common/constants"; + +import { NonReadOnlyUsers } from "@/Utils/AuthorizeFor"; +import routes from "@/Utils/request/api"; +import useQuery from "@/Utils/request/useQuery"; +import { formatName, formatPatientAge } from "@/Utils/utils"; + +import { PatientProps } from "."; +import * as Notification from "../../../Utils/Notifications"; +import { InsuranceDetailsCard } from "../InsuranceDetailsCard"; +import { parseOccupation } from "../PatientHome"; +import { AssignedToObjectModel } from "../models"; + +export const Demography = (props: PatientProps) => { + const { patientData, facilityId, id } = props; + const authUser = useAuthUser(); + const { t } = useTranslation(); + const [_assignedVolunteerObject, setAssignedVolunteerObject] = + useState(); + + const [activeSection, setActiveSection] = useState(null); + + useEffect(() => { + setAssignedVolunteerObject(patientData.assigned_to_object); + + const observedSections: Element[] = []; + const sections = document.querySelectorAll("div[id]"); + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setActiveSection(entry.target.id); + } + }); + }, + { + threshold: 0.6, + }, + ); + + sections.forEach((section) => { + observer.observe(section); + observedSections.push(section); + }); + + return () => { + observedSections.forEach((section) => observer.unobserve(section)); + }; + }, [patientData.assigned_to_object]); + + const { data: insuranceDetials } = useQuery(routes.hcx.policies.list, { + query: { + patient: id, + }, + }); + + const patientGender = GENDER_TYPES.find( + (i) => i.id === patientData.gender, + )?.text; + + const scrollToSection = (sectionId: string) => { + const section = document.getElementById(sectionId); + if (section) { + section.scrollIntoView({ behavior: "smooth" }); + } + }; + + const handleEditClick = (sectionId: string) => { + navigate( + `/facility/${facilityId}/patient/${id}/update?section=${sectionId}`, + ); + }; + + const hasEditPermission = () => { + const showAllFacilityUsers = ["DistrictAdmin", "StateAdmin"]; + if ( + !showAllFacilityUsers.includes(authUser.user_type) && + authUser.home_facility_object?.id !== patientData.facility + ) { + Notification.Error({ + msg: "Oops! Non-Home facility users don't have permission to perform this action.", + }); + return false; + } + return true; + }; + + const EmergencyContact = (props: { number?: string; name?: string }) => ( +
+
+ {/* Emergency Contact Section */} +
+
+ {t("emergency_contact")} +
+ +
+ + {props.number && ( + + )} +
+
+ +
+
+ {t("emergency_contact_person_name")} +
+
+ {props.name || "-"} +
+
+
+
+ ); + + const withPermissionCheck = (action: () => void) => () => { + if (!hasEditPermission()) { + Notification.Error({ + msg: t("permission_denied"), + }); + return; + } + action(); + }; + + type Data = { + id: string; + hidden?: boolean; + allowEdit?: boolean; + details: (React.ReactNode | { label: string; value: React.ReactNode })[]; + }; + + const data: Data[] = [ + { + id: "general-info", + allowEdit: true, + details: [ + { label: t("full_name"), value: patientData.name }, + { + label: t("phone_number"), + value: ( +
+ ), + }, + { + label: t( + patientData.date_of_birth ? "date_of_birth" : "year_of_birth", + ), + value: patientData.date_of_birth ? ( + <> + {dayjs(patientData.date_of_birth).format("DD MMM YYYY")} ( + {formatPatientAge(patientData, true)}) + + ) : ( + <> + {patientData.year_of_birth} ({formatPatientAge(patientData, true)} + ) + + ), + }, + { + label: t("sex"), + value: patientGender, + }, + , + { + label: t("current_address"), + value: patientData.address, + }, + { + label: t("permanent_address"), + value: patientData.permanent_address, + }, + { + label: t("nationality"), + value: patientData.nationality, + }, + { + label: t("state"), + value: patientData.state, + }, + { + label: t("district"), + value: patientData.district_object?.name, + }, + { + label: t("local_body"), + value: patientData.local_body_object?.name, + }, + { + label: t("ward"), + value: ( + <> + {(patientData.ward_object && + patientData.ward_object.number + + ", " + + patientData.ward_object.name) || + "-"} + + ), + }, + { + label: t("village"), + value: patientData.village, + }, + ], + }, + { + id: "social-profile", + allowEdit: true, + details: [ + { + label: t("occupation"), + value: parseOccupation(patientData.meta_info?.occupation), + }, + { + label: t("ration_card_category"), + value: + !!patientData.ration_card_category && + t(`ration_card__${patientData.ration_card_category}`), + }, + { + label: t("socioeconomic_status"), + value: + patientData.meta_info?.socioeconomic_status && + t( + `SOCIOECONOMIC_STATUS__${patientData.meta_info?.socioeconomic_status}`, + ), + }, + { + label: t("domestic_healthcare_support"), + value: + patientData.meta_info?.domestic_healthcare_support && + t( + `DOMESTIC_HEALTHCARE_SUPPORT__${patientData.meta_info?.domestic_healthcare_support}`, + ), + }, + ], + }, + { + id: "volunteer-contact", + hidden: !patientData.assigned_to_object, + details: [ + , + ], + }, + { + id: "insurance-details", + details: [ +
+ {insuranceDetials?.results.map((insurance) => ( + + ))} + {!!insuranceDetials?.results && + insuranceDetials.results.length === 0 && ( +
+ {t("no_data_found")} +
+ )} + +
, + ], + }, + ]; + + return ( +
+
+
+ {data + .filter((s) => !s.hidden) + .map((subtab, i) => ( + + ))} +
+ +
+
+
+
+ {t("patient_status")} +
+
+ +
+
+
+ + {({ isAuthorized }) => ( + + )} + +
+
+ {/*
+ {[ + { label: t("abha_number"), value: "-" }, + { label: t("abha_address"), value: "-" }, + ].map((info, i) => ( +
+

+ {info.label}: +

+

+ {info.value} +

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

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

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

{t("covid_details")}

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

+ {t("sample_test_history")} +

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

+ {t("shifting_history")} +

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

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

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

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

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

+ {patientData.name} +

+

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

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

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

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

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

-

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

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

+ {t("facility")}: +

+

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

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

+ {t("treating_doctor")}: +

+
+

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

+ + {!!skillsQuery.data?.results?.length && + formatSkills(skillsQuery.data?.results)} + {(skillsQuery.data?.results?.length || 0) > 3 && ( +
    + {skillsQuery.data?.results.map((skill) => ( +
  • + {skill.skill_object.name} +
  • + ))} +
+ )} +
)} - {patientData.is_vaccinated && - patientData.last_vaccinated_date && ( -
-
- Last Vaccinated on -
-
- {formatDateTime(patientData.last_vaccinated_date)} -
-
- )} - {patientData.countries_travelled && - !!patientData.countries_travelled.length && ( -
-
- Countries travelled -
-
- {patientData.countries_travelled.join(", ")} -
-
- )} - {patientData.meta_info?.occupation && ( -
-
- {t("occupation")} -
-
- {parseOccupation(patientData.meta_info.occupation)} + {patientData?.last_consultation?.assigned_to_object && ( +
+

+ {t("assigned_doctor")}: +

+
+

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

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

+ {t("assigned_volunteer")}: +

+

+ {formatName(patientData.assigned_to_object)} +

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

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

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

- Investigations Summary -

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

- View/Upload Patient Files -

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

- Shift Patient -

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

- Request Sample Test -

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

- View Patient Notes -

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

- Assign to a volunteer -

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

- Consultation History -

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

- Sample Test History -

- - {(_, query) => ( -
- - - - -
-
- No Sample Test History Available -
-
-
- > - {(item) => ( - - )} - -
- -
-
- )} -
-
); }; diff --git a/src/components/Patient/PatientInfoCard.tsx b/src/components/Patient/PatientInfoCard.tsx index e415d928a0d..486e8b103fd 100644 --- a/src/components/Patient/PatientInfoCard.tsx +++ b/src/components/Patient/PatientInfoCard.tsx @@ -538,7 +538,7 @@ export default function PatientInfoCard(props: PatientInfoCardProps) { )} {!!consultation?.discharge_date && (
-
+
Discharge Reason
diff --git a/src/components/Patient/PatientRegister.tsx b/src/components/Patient/PatientRegister.tsx index 2a15416e337..5828517adf9 100644 --- a/src/components/Patient/PatientRegister.tsx +++ b/src/components/Patient/PatientRegister.tsx @@ -2,7 +2,7 @@ import careConfig from "@careConfig"; import { startCase, toLower } from "lodash-es"; import { debounce } from "lodash-es"; import { navigate } from "raviger"; -import { useCallback, useReducer, useRef, useState } from "react"; +import { useCallback, useEffect, useReducer, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; @@ -232,6 +232,25 @@ export const PatientRegister = (props: PatientRegisterProps) => { const headerText = !id ? "Add Details of Patient" : "Update Patient Details"; const buttonText = !id ? "Add Patient" : "Save Details"; + useEffect(() => { + const getQueryParams = () => { + const params = new URLSearchParams(window.location.search); + return { + section: params.get("section"), + }; + }; + + const { section } = getQueryParams(); + if (section) { + setTimeout(() => { + const element = document.getElementById(section); + if (element) { + element.scrollIntoView({ behavior: "smooth" }); + } + }, 2000); + } + }, []); + const fetchDistricts = useCallback(async (id: number) => { if (id > 0) { setIsDistrictLoading(true); @@ -421,12 +440,16 @@ export const PatientRegister = (props: PatientRegisterProps) => { Object.keys(form).forEach((field) => { let phoneNumber, emergency_phone_number; switch (field) { - case "address": - case "name": - if (!validateName(form[field])) { - errors[field] = "Please enter valid name"; + case "name": { + const requiredError = RequiredFieldValidator()(form[field]); + if (requiredError) { + errors[field] = requiredError; + } else if (!validateName(form[field])) { + errors[field] = t("min_char_length_error", { min_length: 3 }); } return; + } + case "address": case "gender": errors[field] = RequiredFieldValidator()(form[field]); return; @@ -1398,7 +1421,7 @@ export const PatientRegister = (props: PatientRegisterProps) => {
{field("nationality").value === "India" && ( -
+
{
)} -
+
{
-
+

Medical History

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

Insurance Details diff --git a/src/components/Patient/SampleDetails.tsx b/src/components/Patient/SampleDetails.tsx index c5282a65c1f..abf099a6e7d 100644 --- a/src/components/Patient/SampleDetails.tsx +++ b/src/components/Patient/SampleDetails.tsx @@ -300,6 +300,7 @@ export const SampleDetails = ({ id }: DetailRoute) => { return ( {

{t("sample_test_history")}

{sampleDetails?.flow && - sampleDetails.flow.map((flow: FlowModel) => renderFlow(flow))} + sampleDetails.flow.map((flow: FlowModel) => ( +
+ {renderFlow(flow)} +
+ ))}
void; handleApproval: (status: number, sample: SampleTestModel) => void; diff --git a/src/components/Patient/SampleViewAdmin.tsx b/src/components/Patient/SampleViewAdmin.tsx index 37d66bfb815..dcb98d9f28c 100644 --- a/src/components/Patient/SampleViewAdmin.tsx +++ b/src/components/Patient/SampleViewAdmin.tsx @@ -346,7 +346,7 @@ export default function SampleViewAdmin() { text="Total Samples Taken" count={sampeleData?.count || 0} loading={isLoading} - icon="l-thermometer" + icon="d-microscope" className="flex-1" />

diff --git a/src/components/Patient/UpdateStatusDialog.tsx b/src/components/Patient/UpdateStatusDialog.tsx index 9414ba9bb95..e9a767847aa 100644 --- a/src/components/Patient/UpdateStatusDialog.tsx +++ b/src/components/Patient/UpdateStatusDialog.tsx @@ -1,31 +1,28 @@ -import { useEffect, useReducer, useState } from "react"; +import { useEffect, useReducer } from "react"; import { useTranslation } from "react-i18next"; import CareIcon from "@/CAREUI/icons/CareIcon"; -import { Submit } from "@/components/Common/ButtonV2"; +import { Button } from "@/components/ui/button"; + import ConfirmDialog from "@/components/Common/ConfirmDialog"; import { LinearProgressWithLabel } from "@/components/Files/FileUpload"; import CheckBoxFormField from "@/components/Form/FormFields/CheckBoxFormField"; import { SelectFormField } from "@/components/Form/FormFields/SelectFormField"; import TextFormField from "@/components/Form/FormFields/TextFormField"; import { FieldChangeEvent } from "@/components/Form/FormFields/Utils"; -import { - CreateFileResponse, - SampleTestModel, -} from "@/components/Patient/models"; + +import useFileUpload from "@/hooks/useFileUpload"; import { - HEADER_CONTENT_TYPES, SAMPLE_FLOW_RULES, SAMPLE_TEST_RESULT, SAMPLE_TEST_STATUS, } from "@/common/constants"; import * as Notification from "@/Utils/Notifications"; -import routes from "@/Utils/request/api"; -import request from "@/Utils/request/request"; -import uploadFile from "@/Utils/request/uploadFile"; + +import { SampleTestModel } from "./models"; interface Props { sample: SampleTestModel; @@ -34,7 +31,6 @@ interface Props { } const statusChoices = [...SAMPLE_TEST_STATUS]; - const statusFlow = { ...SAMPLE_FLOW_RULES }; const initForm: any = { @@ -60,17 +56,16 @@ const updateStatusReducer = (state = initialState, action: any) => { return state; } }; - const UpdateStatusDialog = (props: Props) => { const { t } = useTranslation(); const { sample, handleOk, handleCancel } = props; const [state, dispatch] = useReducer(updateStatusReducer, initialState); - const [file, setfile] = useState(); - const [contentType, setcontentType] = useState(""); - const [uploadPercent, setUploadPercent] = useState(0); - const [uploadStarted, setUploadStarted] = useState(false); - const [uploadDone, setUploadDone] = useState(false); + const fileUpload = useFileUpload({ + type: "SAMPLE_MANAGEMENT", + allowedExtensions: ["pdf", "jpg", "jpeg", "png"], + allowNameFallback: true, + }); const currentStatus = SAMPLE_TEST_STATUS.find( (i) => i.text === sample.status, ); @@ -104,79 +99,26 @@ const UpdateStatusDialog = (props: Props) => { dispatch({ type: "set_form", form }); }; - const uploadfile = (data: CreateFileResponse) => { - const url = data.signed_url; - const internal_name = data.internal_name; - - const f = file; - if (f === undefined) return; - const newFile = new File([f], `${internal_name}`); - - uploadFile( - url, - newFile, - "PUT", - { - "Content-Type": contentType, - "Content-disposition": "inline", - }, - (xhr: XMLHttpRequest) => { - if (xhr.status >= 200 && xhr.status < 300) { - setUploadStarted(false); - setUploadDone(true); - request(routes.editUpload, { - pathParams: { - id: data.id, - fileType: "SAMPLE_MANAGEMENT", - associatingId: sample.id?.toString() ?? "", - }, - body: { upload_completed: true }, - }); - Notification.Success({ msg: "File Uploaded Successfully" }); + const handleUpload = async () => { + if (fileUpload.files.length > 0) { + if (!fileUpload.fileNames[0]) { + Notification.Error({ + msg: "Please enter a file name before uploading", + }); + return; + } + if (sample.id) { + await fileUpload.handleFileUpload(sample.id); + if (!fileUpload.error) { + return; } else { - setUploadStarted(false); + Notification.Error({ msg: `Upload failed: ${fileUpload.error}` }); } - }, - setUploadPercent, - () => { - setUploadStarted(false); - }, - ); - }; - - const onFileChange = (e: React.ChangeEvent) => { - if (e.target.files == null) { - throw new Error("Error finding e.target.files"); - } - setfile(e.target.files[0]); - const fileName = e.target.files[0].name; - const ext: string = fileName.split(".")[1]; - setcontentType( - HEADER_CONTENT_TYPES[ext as keyof typeof HEADER_CONTENT_TYPES], - ); - return e.target.files[0]; - }; - const handleUpload = async () => { - const f = file; - if (f === undefined) return; - const category = "UNSPECIFIED"; - const name = f.name; - setUploadStarted(true); - setUploadDone(false); - - const { data } = await request(routes.createUpload, { - body: { - original_name: name, - file_type: "SAMPLE_MANAGEMENT", - name: `${sample.patient_name} Sample Report`, - associating_id: sample.id ?? "", - file_category: category, - mime_type: contentType, - }, - }); - - if (data) { - uploadfile(data); + } else { + Notification.Error({ msg: "Sample ID is missing" }); + } + } else { + Notification.Error({ msg: "No file selected for upload" }); } }; @@ -218,32 +160,58 @@ const UpdateStatusDialog = (props: Props) => { onChange={handleChange} /> - Upload Report : + {t("upload_report")}: - {uploadStarted ? ( - - ) : ( -
-