From 38e9d461c1a5bea1fba707a88b915e5c19dfe03a Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Fri, 4 Oct 2024 23:22:29 +0530 Subject: [PATCH 1/2] migrated boards to the KanbanBoard component --- .example.env | 1 - .github/runner-files/jwks.b64.txt | 1 + .github/workflows/cypress.yaml | 3 + README.md | 4 + care.config.ts | 4 - cypress.config.ts | 1 + cypress/e2e/assets_spec/AssetHomepage.cy.ts | 1 + .../e2e/facility_spec/FacilityCreation.cy.ts | 4 +- .../e2e/facility_spec/FacilityHomepage.cy.ts | 21 +- .../e2e/facility_spec/FacilityManage.cy.ts | 2 +- .../patient_spec/PatientBedManagement.cy.ts | 56 +++ .../PatientConsultationCreation.cy.ts | 4 +- .../e2e/patient_spec/PatientHomepage.cy.ts | 42 ++ .../e2e/patient_spec/PatientLogUpdate.cy.ts | 232 +++++----- .../patient_spec/PatientPrescription.cy.ts | 3 +- .../patient_spec/PatientRegistration.cy.ts | 55 ++- cypress/e2e/users_spec/UsersCreation.cy.ts | 5 +- cypress/e2e/users_spec/UsersManage.cy.ts | 1 - cypress/e2e/users_spec/UsersProfile.cy.ts | 4 +- cypress/pageobject/Facility/FacilityHome.ts | 5 +- cypress/pageobject/Patient/PatientHome.ts | 14 + .../pageobject/Patient/PatientInsurance.ts | 42 +- .../pageobject/Patient/PatientLogupdate.ts | 6 +- cypress/pageobject/Users/ManageUserPage.ts | 10 +- cypress/pageobject/Users/UserProfilePage.ts | 4 +- nginx/nginx.conf | 19 +- package-lock.json | 420 +++++++++--------- package.json | 13 +- public/config.json | 28 -- public/env.json | 0 src/App.tsx | 11 +- src/CAREUI/display/Timeline.tsx | 2 +- src/CAREUI/icons/CareIcon.tsx | 3 +- src/Common/constants.tsx | 42 +- src/Common/hooks/useAsyncOptions.ts | 114 ----- src/Common/hooks/useExport.tsx | 30 +- src/Common/hooks/useFilters.tsx | 14 +- src/Common/hooks/useSlug.ts | 3 + src/Components/Assets/AssetsList.tsx | 27 +- src/Components/CameraFeed/CameraFeed.tsx | 16 +- src/Components/Common/Export.tsx | 53 ++- src/Components/Common/FacilitySelect.tsx | 6 + .../MonitorAssetPopover.tsx} | 22 +- .../PMJAYProcedurePackageAutocomplete.tsx | 35 +- src/Components/Common/Sidebar/Sidebar.tsx | 11 +- src/Components/Common/SortDropdown.tsx | 2 +- .../ConsultationDiagnosisBuilder.tsx | 3 +- src/Components/ExternalResult/ResultList.tsx | 13 +- src/Components/Facility/AddBedForm.tsx | 3 +- src/Components/Facility/BedCapacity.tsx | 16 +- .../Facility/CentralNursingStation.tsx | 2 +- .../Facility/ConsultationClaims.tsx | 70 +-- .../Facility/ConsultationDetails/index.tsx | 12 +- .../ConsultationDoctorNotes/index.tsx | 71 +-- .../Consultations/BedActivityTimeline.tsx | 4 +- .../Facility/Consultations/Beds.tsx | 2 +- .../Facility/CoverImageEditModal.tsx | 19 +- src/Components/Facility/DischargeModal.tsx | 141 +++--- src/Components/Facility/DoctorNote.tsx | 21 +- .../Facility/DoctorNoteReplyPreviewCard.tsx | 64 +++ .../Facility/FacilityBedCapacity.tsx | 9 +- src/Components/Facility/FacilityBlock.tsx | 34 ++ src/Components/Facility/FacilityCard.tsx | 9 +- src/Components/Facility/FacilityCreate.tsx | 15 +- src/Components/Facility/FacilityHome.tsx | 47 +- src/Components/Facility/HospitalList.tsx | 14 +- .../Facility/PatientConsultationNotesList.tsx | 12 +- src/Components/Facility/PatientNoteCard.tsx | 37 +- src/Components/Facility/PatientNotesList.tsx | 10 +- .../Facility/PatientNotesSlideover.tsx | 72 +-- .../Facility/SpokeFacilityEditor.tsx | 154 +++++++ src/Components/Facility/models.tsx | 105 ++++- src/Components/Files/AudioCaptureDialog.tsx | 11 +- src/Components/Files/CameraCaptureDialog.tsx | 4 +- src/Components/Files/FileUpload.tsx | 23 +- src/Components/Form/AutoCompleteAsync.tsx | 7 +- .../Form/FormFields/Autocomplete.tsx | 1 + .../FormFields/AutocompleteMultiselect.tsx | 1 + src/Components/Form/ModelCrudEditor.tsx | 153 +++++++ src/Components/HCX/ClaimCard.tsx | 55 +++ src/Components/HCX/ClaimCardCommunication.tsx | 300 +++++++++++++ ...{ClaimDetailCard.tsx => ClaimCardInfo.tsx} | 84 ++-- src/Components/HCX/ClaimCreatedModal.tsx | 30 +- src/Components/HCX/ClaimsItemsBuilder.tsx | 153 ++++--- src/Components/HCX/CreateClaimCard.tsx | 180 ++++---- .../HCX/InsuranceDetailsBuilder.tsx | 70 +-- src/Components/HCX/InsurerAutocomplete.tsx | 29 +- .../HCX/PatientInsuranceDetailsEditor.tsx | 73 ++- src/Components/HCX/PolicyEligibilityCheck.tsx | 160 ++++--- src/Components/HCX/models.ts | 26 +- src/Components/Kanban/Board.tsx | 190 ++++++++ src/Components/LogUpdate/Sections/Vitals.tsx | 2 +- .../Medicine/PrescriptionDetailCard.tsx | 2 +- .../Notifications/NotificationsList.tsx | 26 +- src/Components/Patient/DailyRounds.tsx | 1 + src/Components/Patient/InsuranceDetails.tsx | 21 +- src/Components/Patient/ManagePatients.tsx | 25 +- .../Patient/PatientConsentRecordBlock.tsx | 1 + .../Patient/PatientConsentRecords.tsx | 11 +- src/Components/Patient/PatientFilter.tsx | 40 +- src/Components/Patient/PatientHome.tsx | 36 +- src/Components/Patient/PatientInfoCard.tsx | 1 + src/Components/Patient/PatientNotes.tsx | 65 +-- src/Components/Patient/PatientRegister.tsx | 62 +-- src/Components/Patient/SampleViewAdmin.tsx | 8 +- src/Components/Patient/models.tsx | 1 + src/Components/Resource/ListView.tsx | 9 +- src/Components/Resource/ResourceBoard.tsx | 296 ------------ src/Components/Resource/ResourceBoardView.tsx | 174 ++++++-- src/Components/Scribe/Scribe.tsx | 13 +- src/Components/Shifting/BoardView.tsx | 328 +++++++++----- src/Components/Shifting/ListView.tsx | 11 +- src/Components/Shifting/ShiftingBoard.tsx | 400 ----------------- src/Components/Users/ManageUsers.tsx | 40 +- src/Components/Users/UserAdd.tsx | 45 +- src/Components/Users/UserProfile.tsx | 44 +- src/Components/Users/models.tsx | 6 +- .../VitalsMonitor/VitalsMonitorFooter.tsx | 7 +- src/Integrations/index.tsx | 4 +- src/Locale/TRANSLATION_CONTRIBUTION.md | 22 +- src/Locale/en/Bed.json | 4 +- src/Locale/en/Common.json | 33 +- src/Locale/en/Consultation.json | 1 + src/Locale/en/Facility.json | 6 +- src/Locale/en/FileUpload.json | 4 +- src/Locale/en/HCX.json | 63 +++ src/Locale/en/Notifications.json | 4 +- src/Locale/en/Resource.json | 4 +- src/Locale/en/Shifting.json | 2 +- src/Locale/en/SortOptions.json | 18 - src/Locale/en/index.js | 6 +- src/Locale/hi/Common.json | 20 +- src/Locale/hi/Facility.json | 1 - src/Locale/hi/Notifications.json | 4 +- src/Locale/hi/SortOptions.json | 18 - src/Locale/kn/Common.json | 20 +- src/Locale/kn/Facility.json | 1 - src/Locale/kn/Notifications.json | 4 +- src/Locale/kn/SortOptions.json | 18 - src/Locale/kn/index.js | 26 ++ src/Locale/ml/Common.json | 20 +- src/Locale/ml/Facility.json | 1 - src/Locale/ml/Notifications.json | 4 +- src/Locale/ml/SortOptions.json | 18 - src/Locale/ta/Common.json | 20 +- src/Locale/ta/Facility.json | 1 - src/Locale/ta/Notifications.json | 4 +- src/Locale/ta/SortOptions.json | 18 - src/Redux/actions.tsx | 130 ------ src/Redux/api.tsx | 390 ++++++++++------ src/Routers/AppRouter.tsx | 2 +- src/Routers/routes/HCXRoutes.tsx | 8 +- src/Routers/routes/ResourceRoutes.tsx | 8 +- src/Routers/routes/ShiftingRoutes.tsx | 8 +- src/Utils/Notifications.js | 1 + src/Utils/featureFlags.tsx | 78 ++++ src/Utils/request/uploadFile.ts | 14 + src/Utils/useFileUpload.tsx | 216 +++++---- 158 files changed, 3972 insertions(+), 2832 deletions(-) create mode 100644 .github/runner-files/jwks.b64.txt create mode 100644 cypress/e2e/patient_spec/PatientBedManagement.cy.ts create mode 100644 cypress/e2e/patient_spec/PatientHomepage.cy.ts create mode 100644 cypress/pageobject/Patient/PatientHome.ts delete mode 100644 public/config.json delete mode 100644 public/env.json delete mode 100644 src/Common/hooks/useAsyncOptions.ts rename src/Components/{VitalsMonitor/VitalsMonitorAssetPopover.tsx => Common/MonitorAssetPopover.tsx} (82%) create mode 100644 src/Components/Facility/DoctorNoteReplyPreviewCard.tsx create mode 100644 src/Components/Facility/FacilityBlock.tsx create mode 100644 src/Components/Facility/SpokeFacilityEditor.tsx create mode 100644 src/Components/Form/ModelCrudEditor.tsx create mode 100644 src/Components/HCX/ClaimCard.tsx create mode 100644 src/Components/HCX/ClaimCardCommunication.tsx rename src/Components/HCX/{ClaimDetailCard.tsx => ClaimCardInfo.tsx} (67%) create mode 100644 src/Components/Kanban/Board.tsx delete mode 100644 src/Components/Resource/ResourceBoard.tsx delete mode 100644 src/Components/Shifting/ShiftingBoard.tsx create mode 100644 src/Locale/en/HCX.json delete mode 100644 src/Locale/en/SortOptions.json delete mode 100644 src/Locale/hi/SortOptions.json delete mode 100644 src/Locale/kn/SortOptions.json delete mode 100644 src/Locale/ml/SortOptions.json delete mode 100644 src/Locale/ta/SortOptions.json create mode 100644 src/Utils/featureFlags.tsx diff --git a/.example.env b/.example.env index dd4a8865878..127db15e710 100644 --- a/.example.env +++ b/.example.env @@ -69,7 +69,6 @@ REACT_STILL_WATCHING_PROMPT_DURATION= # Feature flags REACT_ENABLE_HCX=true REACT_ENABLE_ABDM=true -REACT_ENABLE_SCRIBE=true REACT_WARTIME_SHIFTING=true # JWT token refresh interval (in milliseconds) (default: 5 minutes) diff --git a/.github/runner-files/jwks.b64.txt b/.github/runner-files/jwks.b64.txt new file mode 100644 index 00000000000..189ca3ec08e --- /dev/null +++ b/.github/runner-files/jwks.b64.txt @@ -0,0 +1 @@ +eyJrZXlzIjogW3sibiI6ICJ4X21fNGNKQ3NHTHN4WkFIa2VCbFZQa2ZqNFNSckdHN3UySERFM3VLX3dFNERhTWhHQ2lxTXFsaTFDM2pxSE5JTVhuWV9ab1M5R3pHbnJpdGg1UUZGVDlLMGtBdF9YaXBJNnV1djcwOWtOV2FFNXZrYks4VlFRcFd2UFp4NUJSeTBGay0wU2lxZG1xOTNJRXdUTnNTLUpETnRXQm5VX0F1cjU0UXptQmI3SmhfOGttRDAtaHhROVZVejRpSUU3QTlySU5vQXNHSHhfdGtuNXd6YmpPR3F2Q3JqRi1RWXo0OGJXVkZzVVliNkFqUlFrZER2RWVwQlpNSHZsNVUxQlZxOVdRTTJGTmRUR0tJb3ZYeDRuTDFybUVONHpxbEpqWmc2bEZiODVuOUhkc01VblFiSXhkUjVlVl8wWmNVaHBrQWc0NldrejRZWkxMNm5NaGR0RmFTMXciLCAiZSI6ICJBUUFCIiwgImQiOiAiRXo4T3dDZ2xxZnREWlhwSXVEbjhGck1KWGhNNHZfb0NDdlZNUko1QjBPd3BuR3BrWDRKZWF4VFJYYkZ5OVQzdkowX2VXZjRQcl9XZUloMk5HZnpkaGw5NmtJUzd5R2JxQkhSY0U3a2ZhVWFkbHlDTVdnZDV5TEk1aWVOQUw5N2w4X1o2N0w5NHRIX3VlUF80Q1pXV0hGVTNieXJ4bHVzSld6NmZ5SFVPczlVSDFxV284V1RNOVp0RndqV0cxOWpkUXZ1RkQ0Z0x4UEJYZEk5ZV9zdTNwbmZHR0ZkN0xfMDFwcEFkQ2Y5eHhNTWFEel8wZ0xXM0NENFhnWU1rS2d3eDdKWlVlN0VaWFhyU0lETzFZN202MmF4NEpZRU5pSDFiaVlwQk15dmVfM3FzdnRQOWR4eVg2VkFROGZFNVdnOTBCcThsd1F0NzROWmgzMHhDY0dDNHFRIiwgInAiOiAiOXhXeXNjbVExT1FRcEptWmkzZDR5Tk5qT29Ja3lQVDRyZVRBNlJJdW5PSjVDUVFiVE1BVlpzUmsxa05rQ2R1cGVQU1ZnNWVXNktpOXZIV1M3b2NnclVEYzJTRkw0RWtNY21UR2lHRnh1YVIzNHRMTXpUQlJEVVFxZnR6WGxraGRJWmdKenJCT2h4NEEyZ1FQTnRrNkNVSEJWRGRncng0RmR3c21SaElSOU9rIiwgInEiOiAienpEb2FvclpqZ2ZPeDVHckFJZ3R3SGVhM29vQTlqbkFSeDdvM1V2bHprY3p1eC1DY1JORGppS3duaGxzVEROWFFWRTBXUlpWa0ZTQ0JVU3JSS1dLd0JGcnFVQzhlMkxwQVpBb0ZTQ0dqdllIMi1hdGUxeEhxc0NGY09OVlVYM1JXcFd4OFQ4RGhSNGpfaTRKN3g0d2VHc1hkRHRpelY2eHlZMmNHSjltY2I4IiwgImRwIjogInpUV3FLZHA4ZlRQRlZzOXpKTV9lOHZ3TnA2UTdKT1BBUGJ5Rk00MjBSUHdiQmdfeEZIZGJ6dlJCdzJwSkJaNzRTOHJtLWxuR0xna25QQVJ5T2NUa3NMXzBMQ2xwT1NleVBMZlI0NmI2cXZJYjE3aTMtNXFyVmxkTTZfeEMyVF9VaVhnYWZSMFV1MGVCOFlfNWl0WXpTMGpmWmpCd0RrRGl6UkhuZ2I2MFJ6RSIsICJkcSI6ICJYZHdoSGNyaV9ZV3A5aHlXWV9wTkI2am5Qck16OWxkNU5IN2JMUTBxQVFXZWVNR3dmUHNtR21pNnJCU0dUQXJpRjFQckxBU0RKSXcwRHFEcUdZSUkxalBPR3ZHWnNTZkF1SldPb3V1R0taTnBRZ1JCU09Zb0RVR0Q4Zno2ZEoxVHp2NkxpdWRwOTg4TXJTUThHZGdLU3pMd2dCWTdEeUE3MkR2UG9CUHQtODgiLCAicWkiOiAiSlF0UUJERXdnQVVMcWJFRWoybmdyXy02aV9pUmdRWmJTQ0hXNGdpZG5fdHlwdUJWS2R0ZW1jT3J6M3NnTnk0ekZrZElTbUZfbmdnbFlJb080TjNza1NNUXdGY1B0S1kzWUVlT2ZGNzMzRDZaTVJtSTFTby12QU54YVBDUkREbnUwTWk1TnUzbS02SkhGSFF5RDU2ZTFsQUlwUS1lakpuTWR6MXQ0aUplbEFzIiwgImt0eSI6ICJSU0EiLCAia2lkIjogIlhjckcyVS0tR3B4SVhORXpKOWNWREdRaEUzeXlIamREMDN1aDZmYXpRX2siLCAiYWxnIjogIlJTMjU2In1dfQ== \ No newline at end of file diff --git a/.github/workflows/cypress.yaml b/.github/workflows/cypress.yaml index 14a15d30ee8..500dbd92be0 100644 --- a/.github/workflows/cypress.yaml +++ b/.github/workflows/cypress.yaml @@ -45,9 +45,12 @@ jobs: cd care echo DISABLE_RATELIMIT=True >> docker/.prebuilt.env echo "CORS_ALLOWED_ORIGINS=\"[\\\"http://localhost:4000\\\"]\"" >> docker/.prebuilt.env + echo JWKS_BASE64=\"$(cat ../.github/runner-files/jwks.b64.txt)\" >> docker/.prebuilt.env make docker_config_file=docker-compose.pre-built.yaml up make docker_config_file=docker-compose.pre-built.yaml load-dummy-data cd .. + env: + JWKS_BASE64: ${{ secrets.JWKS_BASE64 }} - name: Wait for care to be up ♻ uses: nick-fields/retry@v2 diff --git a/README.md b/README.md index fd5328150f2..73ce06815d1 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,10 @@ Authenticate to staging API with any of the following credentials - Once the code review is done, the PR will be marked with a "Needs Testing" label where it'll be queued for QA testing. - Once tested, the PR would be marked with a "Tested" label and would be queued for merge. +### Translations + +All strings must be encased in i18n translations. New translation strings must be specified in `src`->`Locale`->`en`. Do not add translations for languages other than english through pull requests. Other language translations can be contributed through [Crowdin](https://crowdin.com/project/ohccarefe) + ### Testing To ensure the quality of our pull requests, we use a variety of tools: diff --git a/care.config.ts b/care.config.ts index 4341a03dd48..e3effeca1b0 100644 --- a/care.config.ts +++ b/care.config.ts @@ -103,10 +103,6 @@ const careConfig = { abdm: { enabled: (env.REACT_ENABLE_ABDM ?? "true") === "true", }, - - scribe: { - enabled: env.REACT_ENABLE_SCRIBE === "true", - }, } as const; export default careConfig; diff --git a/cypress.config.ts b/cypress.config.ts index f7302869051..0c939ce50b3 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -34,5 +34,6 @@ export default defineConfig({ }, env: { API_URL: process.env.REACT_CARE_API_URL ?? "http://localhost:9000", + ENABLE_HCX: process.env.REACT_ENABLE_HCX ?? false, }, }); diff --git a/cypress/e2e/assets_spec/AssetHomepage.cy.ts b/cypress/e2e/assets_spec/AssetHomepage.cy.ts index 0f6bfe4da2c..f462bda4327 100644 --- a/cypress/e2e/assets_spec/AssetHomepage.cy.ts +++ b/cypress/e2e/assets_spec/AssetHomepage.cy.ts @@ -98,6 +98,7 @@ describe("Asset Tab", () => { it("Export asset", () => { assetPage.selectassetimportbutton(); + cy.wait(2000); assetPage.selectjsonexportbutton(); assetPage.selectassetimportbutton(); assetPage.selectcsvexportbutton(); diff --git a/cypress/e2e/facility_spec/FacilityCreation.cy.ts b/cypress/e2e/facility_spec/FacilityCreation.cy.ts index 57735a9dcde..918e926d107 100644 --- a/cypress/e2e/facility_spec/FacilityCreation.cy.ts +++ b/cypress/e2e/facility_spec/FacilityCreation.cy.ts @@ -142,7 +142,7 @@ describe("Facility Creation", () => { facilityPage.submitForm(); cy.closeNotification(); // create multiple bed capacity and verify card reflection - facilityPage.selectBedType("Oxygen beds"); + facilityPage.selectBedType("Oxygen Supported Bed"); facilityPage.fillTotalCapacity(bedCapacity); facilityPage.fillCurrentlyOccupied(bedOccupancy); facilityPage.clickbedcapcityaddmore(); @@ -216,7 +216,7 @@ describe("Facility Creation", () => { facilityPage.fillPhoneNumber(facilityNumber); facilityPage.submitForm(); // add the bed capacity - facilityPage.selectBedType("Oxygen beds"); + facilityPage.selectBedType("Oxygen Supported Bed"); facilityPage.fillTotalCapacity(oxygenCapacity); facilityPage.fillCurrentlyOccupied(oxygenExpected); facilityPage.saveAndExitBedCapacityForm(); diff --git a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts index 11b3ec6f37f..e2a12b01a05 100644 --- a/cypress/e2e/facility_spec/FacilityHomepage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityHomepage.cy.ts @@ -15,7 +15,6 @@ describe("Facility Homepage Function", () => { const userPage = new UserPage(); const assetPagination = new AssetPagination(); const facilitiesAlias = "downloadFacilitiesCSV"; - const capacitiesAlias = "downloadCapacitiesCSV"; const doctorsAlias = "downloadDoctorsCSV"; const triagesAlias = "downloadTriagesCSV"; const facilityName = "Dummy Facility 40"; @@ -91,30 +90,26 @@ describe("Facility Homepage Function", () => { }); it("Verify Facility Export Functionality", () => { - // Download the Facilities CSV + // Verify Facility Export facilityHome.csvDownloadIntercept(facilitiesAlias, ""); facilityHome.clickExportButton(); facilityHome.clickMenuItem("Facilities"); facilityHome.verifyDownload(facilitiesAlias); - facilityHome.clickSearchButton(); // to avoid flaky test, as sometimes, the test is unable to focus on the object - // Download the Capacities CSV - facilityHome.csvDownloadIntercept(capacitiesAlias, "&capacity"); - facilityHome.clickExportButton(); - facilityHome.clickMenuItem("Capacities"); - facilityHome.verifyDownload(capacitiesAlias); - facilityHome.clickSearchButton(); - // Download the Doctors CSV + // Verify Doctor Export facilityHome.csvDownloadIntercept(doctorsAlias, "&doctors"); facilityHome.clickExportButton(); facilityHome.clickMenuItem("Doctors"); facilityHome.verifyDownload(doctorsAlias); - facilityHome.clickSearchButton(); - // Download the Triages CSV + // Verify Triage Export facilityHome.csvDownloadIntercept(triagesAlias, "&triage"); facilityHome.clickExportButton(); facilityHome.clickMenuItem("Triages"); facilityHome.verifyDownload(triagesAlias); - facilityHome.clickSearchButton(); + }); + + it("Verify Capacity Export Functionality", () => { + facilityHome.clickExportButton(); + facilityHome.clickMenuItem("Capacities"); }); it("Verify Facility Detail page redirection to CNS and Live Minitoring ", () => { diff --git a/cypress/e2e/facility_spec/FacilityManage.cy.ts b/cypress/e2e/facility_spec/FacilityManage.cy.ts index e4f4ba40ff1..c53943733e9 100644 --- a/cypress/e2e/facility_spec/FacilityManage.cy.ts +++ b/cypress/e2e/facility_spec/FacilityManage.cy.ts @@ -138,7 +138,7 @@ describe("Facility Manage Functions", () => { it("Modify bed capacity in Facility detail page", () => { // add multiple new bed capacity facilityManage.clickFacilityAddBedTypeButton(); - facilityPage.selectBedType("Oxygen beds"); + facilityPage.selectBedType("Isolation Bed"); facilityPage.fillTotalCapacity(totalCapacity); facilityPage.fillCurrentlyOccupied(currentOccupied); facilityPage.saveAndExitBedCapacityForm(); diff --git a/cypress/e2e/patient_spec/PatientBedManagement.cy.ts b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts new file mode 100644 index 00000000000..d9453806c9f --- /dev/null +++ b/cypress/e2e/patient_spec/PatientBedManagement.cy.ts @@ -0,0 +1,56 @@ +import LoginPage from "../../pageobject/Login/LoginPage"; +import { PatientPage } from "../../pageobject/Patient/PatientCreation"; +import PatientLogupdate from "../../pageobject/Patient/PatientLogupdate"; +import { PatientConsultationPage } from "../../pageobject/Patient/PatientConsultation"; + +describe("Patient swtich bed functionality", () => { + const loginPage = new LoginPage(); + const patientPage = new PatientPage(); + const patientLogupdate = new PatientLogupdate(); + const patientConsultationPage = new PatientConsultationPage(); + const switchBedOne = "Dummy Bed 4"; + const switchBedTwo = "Dummy Bed 1"; + const switchBedThree = "Dummy Bed 3"; + const switchPatientOne = "Dummy Patient 6"; + const switchPatientTwo = "Dummy Patient 7"; + + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Assign a bed for a admitted patient from update consultation page", () => { + // Open the update consultation page and assign a bed + patientPage.visitPatient(switchPatientTwo); + patientConsultationPage.clickEditConsultationButton(); + patientLogupdate.selectBed(switchBedThree); + // verify the notification + cy.verifyNotification("Bed allocated successfully"); + }); + + it("Assign a bed for a admitted patient from patient dashboard", () => { + // Assign a bed to a patient + patientPage.visitPatient(switchPatientOne); + patientLogupdate.clickSwitchBed(); + patientLogupdate.selectBed(switchBedOne); + cy.verifyNotification("Bed allocated successfully"); + // Clear the bed and reassign + patientLogupdate.clickSwitchBed(); + cy.get("#clear-button").click(); + patientLogupdate.selectBed(switchBedTwo); + cy.verifyNotification("Bed allocated successfully"); + // verify the card is reflected + patientLogupdate.clickSwitchBed(); + cy.verifyContentPresence("#previousbed-list", [switchBedOne, switchBedTwo]); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts index a27d560e1a0..6f9bf2b03e0 100644 --- a/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts +++ b/cypress/e2e/patient_spec/PatientConsultationCreation.cy.ts @@ -35,7 +35,7 @@ describe("Patient Consultation in multiple combination", () => { const patientWeight = "70"; const patientHeight = "170"; const medicineOne = "DOLOLUP"; - const patientIpNumber = Math.random().toString(36).substring(7); + const patientIpNumber = `${Math.floor(Math.random() * 90 + 10)}/${Math.floor(Math.random() * 9000 + 1000)}`; before(() => { loginPage.loginAsDisctrictAdmin(); @@ -94,7 +94,7 @@ describe("Patient Consultation in multiple combination", () => { cy.submitButton("Create Consultation"); // the above submit should fail as IP number is missing patientConsultationPage.typePatientNumber(patientIpNumber); - patientConsultationPage.selectBed("Dummy Bed 1"); + patientConsultationPage.selectBed("Dummy Bed 6"); cy.submitButton("Create Consultation"); cy.verifyNotification("Consultation created successfully"); // Below code for the prescription module only present while creating a new consultation diff --git a/cypress/e2e/patient_spec/PatientHomepage.cy.ts b/cypress/e2e/patient_spec/PatientHomepage.cy.ts new file mode 100644 index 00000000000..c1575057fe4 --- /dev/null +++ b/cypress/e2e/patient_spec/PatientHomepage.cy.ts @@ -0,0 +1,42 @@ +import LoginPage from "../../pageobject/Login/LoginPage"; +import PatientHome from "../../pageobject/Patient/PatientHome"; + +describe("Patient Homepage present functionalities", () => { + const loginPage = new LoginPage(); + const patientHome = new PatientHome(); + + before(() => { + loginPage.loginAsDisctrictAdmin(); + cy.saveLocalStorage(); + }); + + beforeEach(() => { + cy.restoreLocalStorage(); + cy.clearLocalStorage(/filters--.+/); + cy.awaitUrl("/patients"); + }); + + it("Verify the functionality of the patient tab pagination", () => { + let firstPatientPageOne: string; + cy.get('[data-cy="patient"]') + .first() + .invoke("text") + .then((patientOne: string) => { + firstPatientPageOne = patientOne.trim(); + patientHome.clickNextPage(); + patientHome.verifySecondPageUrl(); + cy.get('[data-cy="patient"]') + .first() + .invoke("text") + .then((patientTwo: string) => { + const firstPatientPageTwo = patientTwo.trim(); + expect(firstPatientPageOne).not.to.eq(firstPatientPageTwo); + patientHome.clickPreviousPage(); + }); + }); + }); + + afterEach(() => { + cy.saveLocalStorage(); + }); +}); diff --git a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts index 923d9410657..7faaeed5a9f 100644 --- a/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts +++ b/cypress/e2e/patient_spec/PatientLogUpdate.cy.ts @@ -12,15 +12,14 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const patientLogupdate = new PatientLogupdate(); const patientInvestigation = new PatientInvestigation(); const patientPrescription = new PatientPrescription(); - const domicilaryPatient = "Dummy Patient 11"; const patientCategory = "Moderate"; const additionalSymptoms = "Fever"; const physicalExamination = "physical examination details"; const otherExamination = "Other"; - const patientSystolic = "119"; - const patientDiastolic = "150"; - const patientModifiedSystolic = "120"; - const patientModifiedDiastolic = "145"; + const patientSystolic = "149"; + const patientDiastolic = "119"; + const patientModifiedSystolic = "145"; + const patientModifiedDiastolic = "120"; const patientPulse = "152"; const patientTemperature = "96.6"; const patientRespiratory = "140"; @@ -33,6 +32,13 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { const patientInsulinDosage = "56"; const patientFluidBalance = "500"; const patientNetBalance = "1000"; + const patientOne = "Dummy Patient 9"; + const bedOne = "Dummy Bed 5"; + const patientTwo = "Dummy Patient 10"; + const bedTwo = "Dummy Bed 2"; + const patientThree = "Dummy Patient 8"; + const bedThree = "Dummy Bed 3"; + const domicilaryPatient = "Dummy Patient 11"; before(() => { loginPage.loginAsDisctrictAdmin(); @@ -45,15 +51,110 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { cy.awaitUrl("/patients"); }); - it("Create a basic critical care log update for a admitted patient and edit it", () => { - patientPage.visitPatient("Dummy Patient 10"); + it("Create a new TeleIcu log update for a domicilary care patient", () => { + patientPage.visitPatient(domicilaryPatient); + patientConsultationPage.clickEditConsultationButton(); + patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); + cy.submitButton("Update Consultation"); + cy.verifyNotification("Consultation updated successfully"); + cy.closeNotification(); + patientLogupdate.clickLogupdate(); + patientLogupdate.typePhysicalExamination(physicalExamination); + patientLogupdate.selectRoundType("Tele-medicine Log"); + patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.typeOtherDetails(otherExamination); + patientLogupdate.selectSymptomsDate("01012024"); + patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); + patientLogupdate.typeSystolic(patientSystolic); + patientLogupdate.typeDiastolic(patientDiastolic); + patientLogupdate.typePulse(patientPulse); + patientLogupdate.typeTemperature(patientTemperature); + patientLogupdate.typeRespiratory(patientRespiratory); + patientLogupdate.typeSpo2(patientSpo2); + patientLogupdate.selectRhythm(patientRhythmType); + patientLogupdate.typeRhythm(patientRhythm); + cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); + cy.submitButton("Save"); + cy.verifyNotification("Tele-medicine Log created successfully"); + }); + + it("Create a new Progress log update for a admitted patient and edit it", () => { + patientPage.visitPatient(patientOne); patientLogupdate.clickLogupdate(); cy.verifyNotification("Please assign a bed to the patient"); - patientLogupdate.selectBed("Dummy Bed 2"); + patientLogupdate.selectBed(bedOne); cy.closeNotification(); patientLogupdate.clickLogupdate(); + // Only will be using random non-unique progress note fields + patientLogupdate.selectRoundType("Progress Note"); patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.selectSymptomsDate("01012024"); + patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); + patientLogupdate.typeTemperature(patientTemperature); + // add diagnosis + patientConsultationPage.selectPatientDiagnosis( + "1A06", + "add-icd11-diagnosis-as-differential", + ); + // add a investigation for the patient + patientInvestigation.clickAddInvestigation(); + patientInvestigation.selectInvestigation("Vitals (GROUP)"); + patientInvestigation.clickInvestigationCheckbox(); + patientInvestigation.selectInvestigationFrequency("6"); + // add a medicine for the patient + patientPrescription.clickAddPrescription(); + patientPrescription.interceptMedibase(); + patientPrescription.selectMedicinebox(); + patientPrescription.selectMedicine("DOLO"); + patientPrescription.enterDosage("4"); + patientPrescription.selectDosageFrequency("Twice daily"); + cy.submitButton("Submit"); + cy.verifyNotification("Medicine prescribed"); + cy.closeNotification(); + // Submit the doctors log update + cy.submitButton("Save and Continue"); + cy.verifyNotification("Progress Note created successfully"); + cy.closeNotification(); + // modify the relevant critical care log update + patientLogupdate.selectCriticalCareSection("Neurological Monitoring"); + cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); + cy.get("#left_pupil_light_reaction-option-FIXED").click(); + cy.submitButton("Update Details"); + cy.verifyNotification( + "Neurological Monitoring details succesfully updated.", + ); + cy.closeNotification(); + // Final Submission of the form + cy.submitButton("Complete"); + cy.verifyNotification("Progress Note Log Update filed successfully"); + cy.closeNotification(); + // Verify the data reflection + cy.contains("button", "Daily Rounds").click(); + patientLogupdate.clickLogUpdateViewDetails( + "#dailyround-entry", + patientCategory, + ); + cy.verifyContentPresence("#consultation-preview", [ + patientCategory, + patientTemperature, + ]); + // verify the edit functionality + patientLogupdate.clickUpdateDetail(); + patientLogupdate.typeSystolic(patientModifiedSystolic); + patientLogupdate.typeDiastolic(patientModifiedDiastolic); + cy.submitButton("Continue"); + cy.verifyNotification("Progress Note updated successfully"); + }); + + it("Create a basic critical care log update for a admitted patient and edit it", () => { + patientPage.visitPatient(patientTwo); + patientLogupdate.clickLogupdate(); + cy.verifyNotification("Please assign a bed to the patient"); + patientLogupdate.selectBed(bedTwo); + cy.closeNotification(); + patientLogupdate.clickLogupdate(); patientLogupdate.selectRoundType("Detailed Update"); + patientLogupdate.selectPatientCategory(patientCategory); cy.submitButton("Save and Continue"); cy.verifyNotification("Detailed Update created successfully"); cy.closeNotification(); @@ -127,88 +228,19 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { ]); }); - it("Create a new Progress log update for a admitted patient and edit it", () => { - patientPage.visitPatient("Dummy Patient 12"); + it("Create a new Normal update for a admission patient and verify its reflection in cards", () => { + patientPage.visitPatient(patientThree); patientLogupdate.clickLogupdate(); cy.verifyNotification("Please assign a bed to the patient"); - patientLogupdate.selectBed("Dummy Bed 4"); - cy.closeNotification(); - patientLogupdate.clickLogupdate(); - // Only will be using random non-unique progress note fields - patientLogupdate.selectPatientCategory(patientCategory); - patientLogupdate.selectRoundType("Progress Note"); - patientLogupdate.selectSymptomsDate("01012024"); - patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.typeTemperature(patientTemperature); - // add diagnosis - patientConsultationPage.selectPatientDiagnosis( - "1A06", - "add-icd11-diagnosis-as-differential", - ); - // add a investigation for the patient - patientInvestigation.clickAddInvestigation(); - patientInvestigation.selectInvestigation("Vitals (GROUP)"); - patientInvestigation.clickInvestigationCheckbox(); - patientInvestigation.selectInvestigationFrequency("6"); - // add a medicine for the patient - patientPrescription.clickAddPrescription(); - patientPrescription.interceptMedibase(); - patientPrescription.selectMedicinebox(); - patientPrescription.selectMedicine("DOLO"); - patientPrescription.enterDosage("4"); - patientPrescription.selectDosageFrequency("Twice daily"); - cy.submitButton("Submit"); - cy.verifyNotification("Medicine prescribed"); - cy.closeNotification(); - // Submit the doctors log update - cy.submitButton("Save and Continue"); - cy.verifyNotification("Progress Note created successfully"); - cy.closeNotification(); - // modify the relevant critical care log update - patientLogupdate.selectCriticalCareSection("Neurological Monitoring"); - cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); - cy.get("#left_pupil_light_reaction-option-FIXED").click(); - cy.submitButton("Update Details"); - cy.verifyNotification( - "Neurological Monitoring details succesfully updated.", - ); - cy.closeNotification(); - // Final Submission of the form - cy.submitButton("Complete"); - cy.verifyNotification("Progress Note Log Update filed successfully"); - cy.closeNotification(); - // Verify the data reflection - cy.contains("button", "Daily Rounds").click(); - patientLogupdate.clickLogUpdateViewDetails( - "#dailyround-entry", - patientCategory, - ); - cy.verifyContentPresence("#consultation-preview", [ - patientCategory, - patientTemperature, - ]); - // verify the edit functionality - patientLogupdate.clickUpdateDetail(); - patientLogupdate.typeSystolic(patientModifiedSystolic); - patientLogupdate.typeDiastolic(patientModifiedDiastolic); - cy.submitButton("Continue"); - cy.verifyNotification("Progress Note updated successfully"); - }); - - it("Create a new TeleIcu log update for a domicilary care patient", () => { - patientPage.visitPatient("Dummy Patient 11"); - patientConsultationPage.clickEditConsultationButton(); - patientConsultationPage.selectPatientSuggestion("Domiciliary Care"); - cy.submitButton("Update Consultation"); - cy.verifyNotification("Consultation updated successfully"); + patientLogupdate.selectBed(bedThree); cy.closeNotification(); patientLogupdate.clickLogupdate(); patientLogupdate.typePhysicalExamination(physicalExamination); - patientLogupdate.selectRoundType("Tele-medicine Log"); + patientLogupdate.selectPatientCategory(patientCategory); patientLogupdate.typeOtherDetails(otherExamination); patientLogupdate.selectSymptomsDate("01012024"); patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.selectPatientCategory(patientCategory); + patientLogupdate.clickAddSymptom(); patientLogupdate.typeSystolic(patientSystolic); patientLogupdate.typeDiastolic(patientDiastolic); patientLogupdate.typePulse(patientPulse); @@ -219,7 +251,11 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { patientLogupdate.typeRhythm(patientRhythm); cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); cy.submitButton("Save"); - cy.verifyNotification("Tele-medicine Log created successfully"); + cy.wait(2000); + cy.verifyNotification("Brief Update created successfully"); + // Verify the card content + cy.get("#basic-information").scrollIntoView(); + cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]); }); it("Create a new Normal Log update for a domicilary care patient and edit it", () => { @@ -283,36 +319,6 @@ describe("Patient Log Update in Normal, Critical and TeleIcu", () => { ]); }); - it("Create a new Normal update for a admission patient and verify its reflection in cards", () => { - patientPage.visitPatient("Dummy Patient 13"); - patientLogupdate.clickLogupdate(); - cy.verifyNotification("Please assign a bed to the patient"); - patientLogupdate.selectBed("Dummy Bed 6"); - cy.closeNotification(); - patientLogupdate.clickLogupdate(); - patientLogupdate.typePhysicalExamination(physicalExamination); - patientLogupdate.typeOtherDetails(otherExamination); - patientLogupdate.selectSymptomsDate("01012024"); - patientLogupdate.typeAndMultiSelectSymptoms("fe", ["Fever"]); - patientLogupdate.clickAddSymptom(); - patientLogupdate.selectPatientCategory(patientCategory); - patientLogupdate.typeSystolic(patientSystolic); - patientLogupdate.typeDiastolic(patientDiastolic); - patientLogupdate.typePulse(patientPulse); - patientLogupdate.typeTemperature(patientTemperature); - patientLogupdate.typeRespiratory(patientRespiratory); - patientLogupdate.typeSpo2(patientSpo2); - patientLogupdate.selectRhythm(patientRhythmType); - patientLogupdate.typeRhythm(patientRhythm); - cy.get("#consciousness_level-option-RESPONDS_TO_PAIN").click(); - cy.submitButton("Save"); - cy.wait(2000); - cy.verifyNotification("Brief Update created successfully"); - // Verify the card content - cy.get("#basic-information").scrollIntoView(); - cy.verifyContentPresence("#encounter-symptoms", [additionalSymptoms]); - }); - it("Create a Normal Log update to verify MEWS Score Functionality", () => { patientPage.visitPatient(domicilaryPatient); patientConsultationPage.clickEditConsultationButton(); diff --git a/cypress/e2e/patient_spec/PatientPrescription.cy.ts b/cypress/e2e/patient_spec/PatientPrescription.cy.ts index a150bfa6e31..86ca4122082 100644 --- a/cypress/e2e/patient_spec/PatientPrescription.cy.ts +++ b/cypress/e2e/patient_spec/PatientPrescription.cy.ts @@ -5,7 +5,7 @@ import { PatientPage } from "../../pageobject/Patient/PatientCreation"; const patientPrescription = new PatientPrescription(); const loginPage = new LoginPage(); const patientPage = new PatientPage(); -const medicineNameOne = "DOLO"; +const medicineNameOne = "AGCON"; const medicineNameTwo = "FDEP PLUS"; const medicineBaseDosage = "4"; const medicineTargetDosage = "9"; @@ -113,6 +113,7 @@ describe("Patient Medicine Administration", () => { cy.closeNotification(); // Administer the medicine in edit form patientPrescription.clickAdministerButton(); + cy.wait(2000); patientPrescription.enterAdministerDosage(medicineBaseDosage); patientPrescription.enterAdministerNotes(medicineAdministerNote); cy.submitButton("Administer Medicine"); diff --git a/cypress/e2e/patient_spec/PatientRegistration.cy.ts b/cypress/e2e/patient_spec/PatientRegistration.cy.ts index ef92c2e9bd1..589b60f1830 100644 --- a/cypress/e2e/patient_spec/PatientRegistration.cy.ts +++ b/cypress/e2e/patient_spec/PatientRegistration.cy.ts @@ -8,6 +8,7 @@ import PatientInsurance from "../../pageobject/Patient/PatientInsurance"; import PatientMedicalHistory from "../../pageobject/Patient/PatientMedicalHistory"; const yearOfBirth = "2001"; +const isHCXEnabled = Cypress.env("ENABLE_HCX"); const calculateAge = () => { const currentYear = new Date().getFullYear(); @@ -180,16 +181,21 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneFirstPolicyId, ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_id", - patientOneFirstInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneFirstInsuranceId, - "insurer_name", - patientOneFirstInsurerName, - ); + if (isHCXEnabled) { + patientInsurance.selectInsurer("test"); + } else { + patientInsurance.typePatientInsuranceDetail( + patientOneFirstInsuranceId, + "insurer_id", + patientOneFirstInsurerId, + ); + patientInsurance.typePatientInsuranceDetail( + patientOneFirstInsuranceId, + "insurer_name", + patientOneFirstInsurerName, + ); + } + patientInsurance.clickAddInsruanceDetails(); patientInsurance.typePatientInsuranceDetail( patientOneSecondInsuranceId, @@ -201,16 +207,21 @@ describe("Patient Creation with consultation", () => { "policy_id", patientOneSecondPolicyId, ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_id", - patientOneSecondInsurerId, - ); - patientInsurance.typePatientInsuranceDetail( - patientOneSecondInsuranceId, - "insurer_name", - patientOneSecondInsurerName, - ); + if (isHCXEnabled) { + patientInsurance.selectInsurer("Care"); + } else { + patientInsurance.typePatientInsuranceDetail( + patientOneSecondInsuranceId, + "insurer_id", + patientOneSecondInsurerId, + ); + patientInsurance.typePatientInsuranceDetail( + patientOneSecondInsuranceId, + "insurer_name", + patientOneSecondInsurerName, + ); + } + patientPage.clickUpdatePatient(); cy.wait(3000); patientPage.verifyPatientUpdated(); @@ -239,7 +250,9 @@ describe("Patient Creation with consultation", () => { patientOneFirstPolicyId, patientOneFirstInsurerId, patientOneFirstInsurerName, + isHCXEnabled, ); + patientInsurance.clickPatientInsuranceViewDetail(); cy.wait(3000); patientInsurance.verifyPatientPolicyDetails( @@ -247,12 +260,14 @@ describe("Patient Creation with consultation", () => { patientOneFirstPolicyId, patientOneFirstInsurerId, patientOneFirstInsurerName, + isHCXEnabled, ); patientInsurance.verifyPatientPolicyDetails( patientOneSecondSubscriberId, patientOneSecondPolicyId, patientOneSecondInsurerId, patientOneSecondInsurerName, + isHCXEnabled, ); }); diff --git a/cypress/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index cc3a8250971..e245dd271ac 100644 --- a/cypress/e2e/users_spec/UsersCreation.cy.ts +++ b/cypress/e2e/users_spec/UsersCreation.cy.ts @@ -149,7 +149,7 @@ describe("User Creation", () => { userCreationPage.setInputDate("date_of_birth", "date-input", "25081999"); userCreationPage.selectDropdownOption("user_type", "Doctor"); userCreationPage.typeIntoElementById("c_password", "Test@123"); - userCreationPage.typeIntoElementById("doctor_qualification", "MBBS"); + userCreationPage.typeIntoElementById("qualification", "MBBS"); userCreationPage.typeIntoElementById("doctor_experience_commenced_on", "2"); userCreationPage.typeIntoElementById( "doctor_medical_council_registration", @@ -172,7 +172,7 @@ describe("User Creation", () => { "home_facility", "Dummy Shifting Center", ); - userCreationPage.verifyElementContainsText("doctor-qualification", "MBBS"); + userCreationPage.verifyElementContainsText("qualification", "MBBS"); userCreationPage.verifyElementContainsText("doctor-experience", "2"); userCreationPage.verifyElementContainsText( "medical-council-registration", @@ -183,6 +183,7 @@ describe("User Creation", () => { it("create new user form throwing mandatory field error", () => { userCreationPage.clickElementById("addUserButton"); userCreationPage.clickElementById("submit"); + cy.wait(2000); userCreationPage.verifyErrorMessages(EXPECTED_ERROR_MESSAGES); }); diff --git a/cypress/e2e/users_spec/UsersManage.cy.ts b/cypress/e2e/users_spec/UsersManage.cy.ts index 9c339f4b8e3..0d3a757df0f 100644 --- a/cypress/e2e/users_spec/UsersManage.cy.ts +++ b/cypress/e2e/users_spec/UsersManage.cy.ts @@ -132,7 +132,6 @@ describe("Manage User", () => { manageUserPage.clickLinkFacility(); manageUserPage.clickUnlinkFacilityButton(); manageUserPage.clickSubmit(); - manageUserPage.assertnotLinkedFacility; manageUserPage.linkedfacilitylistnotvisible(); manageUserPage.clickCloseSlideOver(); // Go to particular facility doctor connect and all user-id are reflected based on there access diff --git a/cypress/e2e/users_spec/UsersProfile.cy.ts b/cypress/e2e/users_spec/UsersProfile.cy.ts index 2672cccad7e..63fd71f5793 100644 --- a/cypress/e2e/users_spec/UsersProfile.cy.ts +++ b/cypress/e2e/users_spec/UsersProfile.cy.ts @@ -12,7 +12,7 @@ describe("Manage User Profile", () => { const email = "test@example.com"; const phone = "+918899887788"; const workinghours = "8"; - const doctorQualification = "MBBS"; + const qualification = "MBBS"; const doctorYoE = "10"; const medicalCouncilRegistration = "1234567890"; @@ -40,7 +40,7 @@ describe("Manage User Profile", () => { userProfilePage.typePhone(phone); userProfilePage.typeWhatsApp(phone); userProfilePage.typeWorkingHours(workinghours); - userProfilePage.typeDoctorQualification(doctorQualification); + userProfilePage.typeQualification(qualification); userProfilePage.typeDoctorYoE(doctorYoE); userProfilePage.typeMedicalCouncilRegistration(medicalCouncilRegistration); diff --git a/cypress/pageobject/Facility/FacilityHome.ts b/cypress/pageobject/Facility/FacilityHome.ts index b10368717a6..956c374ada4 100644 --- a/cypress/pageobject/Facility/FacilityHome.ts +++ b/cypress/pageobject/Facility/FacilityHome.ts @@ -6,6 +6,7 @@ class FacilityHome { // Operations clickExportButton() { + cy.get(this.exportButton).scrollIntoView(); cy.get(this.exportButton).click(); } @@ -89,7 +90,9 @@ class FacilityHome { } verifyDownload(alias: string) { - cy.wait(`@${alias}`).its("response.statusCode").should("eq", 200); + cy.wait(`@${alias}`, { timeout: 60000 }) + .its("response.statusCode") + .should("eq", 200); } getURL() { diff --git a/cypress/pageobject/Patient/PatientHome.ts b/cypress/pageobject/Patient/PatientHome.ts new file mode 100644 index 00000000000..94801cd4bb8 --- /dev/null +++ b/cypress/pageobject/Patient/PatientHome.ts @@ -0,0 +1,14 @@ +class PatientHome { + clickNextPage() { + cy.get("#next-pages").click(); + } + + verifySecondPageUrl() { + cy.url().should("include", "/patients?page=2"); + } + + clickPreviousPage() { + cy.get("#prev-pages").click(); + } +} +export default PatientHome; diff --git a/cypress/pageobject/Patient/PatientInsurance.ts b/cypress/pageobject/Patient/PatientInsurance.ts index 60eaefffa44..bdd571e9d0c 100644 --- a/cypress/pageobject/Patient/PatientInsurance.ts +++ b/cypress/pageobject/Patient/PatientInsurance.ts @@ -9,6 +9,34 @@ class PatientInsurance { }); } + selectInsurer(insurer: string) { + cy.intercept("GET", "**/api/v1/hcx/payors/**", { + statusCode: 200, + body: [ + { + name: "test payor 2", + code: "testpayor2.swasthmock@swasth-hcx-staging", + }, + { + name: "Care Payor", + code: "khavinshankar.gmail@swasth-hcx-staging", + }, + { + name: "Alliance", + code: "hcxdemo.yopmail@swasth-hcx-staging", + }, + ], + }).as("getInsurer"); + cy.get("[name='insurer']") + .last() + .click() + .type(insurer) + .then(() => { + cy.wait("@getInsurer"); + cy.get("[role='option']").contains(insurer).click(); + }); + } + clickPatientInsuranceViewDetail() { cy.get("#insurance-view-details").scrollIntoView(); cy.get("#insurance-view-details").click(); @@ -18,13 +46,21 @@ class PatientInsurance { cy.get("[data-testid=add-insurance-button]").click(); } - verifyPatientPolicyDetails(subscriberId, policyId, insurerId, insurerName) { + verifyPatientPolicyDetails( + subscriberId, + policyId, + insurerId, + insurerName, + isHcxEnabled, + ) { cy.get("[data-testid=patient-details]").then(($dashboard) => { cy.url().should("include", "/facility/"); expect($dashboard).to.contain(subscriberId); expect($dashboard).to.contain(policyId); - expect($dashboard).to.contain(insurerId); - expect($dashboard).to.contain(insurerName); + if (!isHcxEnabled) { + expect($dashboard).to.contain(insurerId); + expect($dashboard).to.contain(insurerName); + } }); } } diff --git a/cypress/pageobject/Patient/PatientLogupdate.ts b/cypress/pageobject/Patient/PatientLogupdate.ts index 8a4c11ab25b..add3fbb0590 100644 --- a/cypress/pageobject/Patient/PatientLogupdate.ts +++ b/cypress/pageobject/Patient/PatientLogupdate.ts @@ -5,13 +5,17 @@ class PatientLogupdate { cy.wait(2000); } + clickSwitchBed() { + cy.get("#switch-bed").click(); + } + selectRoundType(roundType: string) { cy.clickAndSelectOption("#rounds_type", roundType); } selectBed(bed: string) { cy.searchAndSelectOption("input[name='bed']", bed); - cy.submitButton("Update"); + cy.get("#update-switchbed").click(); cy.wait(2000); } diff --git a/cypress/pageobject/Users/ManageUserPage.ts b/cypress/pageobject/Users/ManageUserPage.ts index c57ecbf172f..41d41d218d4 100644 --- a/cypress/pageobject/Users/ManageUserPage.ts +++ b/cypress/pageobject/Users/ManageUserPage.ts @@ -142,8 +142,14 @@ export class ManageUserPage { } assertDoctorConnectVisibility(realName) { - cy.get("#doctor-connect-home-doctor").should("contain.text", realName); - cy.get("#doctor-connect-remote-doctor").should("contain.text", realName); + cy.get('*[id="doctor-connect-home-doctor"]').should( + "contain.text", + realName, + ); + cy.get('*[id="doctor-connect-remote-doctor"]').should( + "contain.text", + realName, + ); } assertVideoConnectLink(docName: string, link: string) { diff --git a/cypress/pageobject/Users/UserProfilePage.ts b/cypress/pageobject/Users/UserProfilePage.ts index c3de5035dc7..20fd1911c49 100644 --- a/cypress/pageobject/Users/UserProfilePage.ts +++ b/cypress/pageobject/Users/UserProfilePage.ts @@ -42,8 +42,8 @@ export default class UserProfilePage { cy.get("#weekly_working_hours").click().clear().type(workinghours); } - typeDoctorQualification = (doctorQualification: string) => { - cy.get("#doctor_qualification").click().clear().type(doctorQualification); + typeQualification = (qualification: string) => { + cy.get("#qualification").click().clear().type(qualification); }; typeDoctorYoE = (doctorYoE: string) => { diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 40a878bcee2..595873a4be2 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,17 +1,18 @@ server { - listen 80; + root /usr/share/nginx/html; - location / { - root /usr/share/nginx/html; - index index.html index.htm; - try_files $uri $uri/ /index.html; + include mime.types; + types { + text/javascript js mjs; } - error_page 500 502 503 504 /50x.html; - - location = /50x.html { - root /usr/share/nginx/html; + location = /index.html { + expires 6h; } + location / { + expires 7d; + try_files $uri $uri/ /index.html; + } } diff --git a/package-lock.json b/package-lock.json index 553224ffc27..af81beee132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,10 @@ "@googlemaps/react-wrapper": "^1.1.35", "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.1.2", + "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", - "@sentry/browser": "^8.29.0", + "@sentry/browser": "^8.33.0", "@yudiel/react-qr-scanner": "^2.0.0-beta.3", "axios": "^1.7.7", "bowser": "^2.11.0", @@ -23,7 +24,7 @@ "browserslist-useragent-regexp": "^4.1.3", "cross-env": "^7.0.3", "dayjs": "^1.11.11", - "echarts": "^5.5.0", + "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", "events": "^3.3.0", "hi-profiles": "^1.0.6", @@ -35,12 +36,9 @@ "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-dnd-scrolling": "^1.3.8", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", - "react-i18next": "^13.0.1", + "react-i18next": "^15.0.2", "react-infinite-scroll-component": "^6.1.0", "react-markdown": "^8.0.7", "react-pdf": "^9.1.0", @@ -56,6 +54,7 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", + "@types/cypress": "^1.1.3", "@types/events": "^3.0.3", "@types/google.maps": "^3.55.8", "@types/lodash-es": "^4.17.12", @@ -68,7 +67,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.19", - "cypress": "^13.14.1", + "cypress": "^13.14.2", "cypress-localstorage-commands": "^2.2.5", "cypress-split": "^1.23.2", "eslint-config-prettier": "^9.1.0", @@ -1868,9 +1867,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.1.tgz", - "integrity": "sha512-+BIznRzyqBf+2wCTxcKE3wDjfGeCoVE61KSHGpkzqrLi8qxqFwBeUFyId2cxkTmm55fzDGnm0+yCxaxygrLUnQ==", + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -2596,6 +2595,51 @@ "react-dom": "^18" } }, + "node_modules/@hello-pangea/dnd": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/@hello-pangea/dnd/-/dnd-17.0.0.tgz", + "integrity": "sha512-LDDPOix/5N0j5QZxubiW9T0M0+1PR0rTDWeZF5pu1Tz91UQnuVK4qQ/EjY83Qm2QeX0eM8qDXANfDh3VVqtR4Q==", + "dependencies": { + "@babel/runtime": "^7.25.6", + "css-box-model": "^1.2.1", + "memoize-one": "^6.0.0", + "raf-schd": "^4.0.3", + "react-redux": "^9.1.2", + "redux": "^5.0.1", + "use-memo-one": "^1.1.3" + }, + "peerDependencies": { + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@hello-pangea/dnd/node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/@hello-pangea/dnd/node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, "node_modules/@humanwhocodes/config-array": { "version": "0.11.14", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", @@ -3348,21 +3392,6 @@ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" } }, - "node_modules/@react-dnd/asap": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz", - "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A==" - }, - "node_modules/@react-dnd/invariant": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz", - "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw==" - }, - "node_modules/@react-dnd/shallowequal": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz", - "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==" - }, "node_modules/@react-stately/utils": { "version": "3.10.2", "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.2.tgz", @@ -3764,178 +3793,178 @@ ] }, "node_modules/@sentry-internal/browser-utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.29.0.tgz", - "integrity": "sha512-6HpyQkaqPvK6Lnigjlarq/LDYgXT2OBNf24RK7z0ipJSxSIpmtelfzHbnwWYnypNDXfTDdPm97fZEenQHryYJA==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.33.0.tgz", + "integrity": "sha512-zwjmD+XI3pgxxiqKGLXYDGSd+zfO7az9zzbLn1le8Vv9cRL2lZyMLcwiwEaTpwz3B0pPONeDZMT8+bzMGRs8zw==", "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.33.0", + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/browser-utils/node_modules/@sentry/core": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.29.0.tgz", - "integrity": "sha512-scMbZaJ0Ov8NPgWn86EdjhyTLrhvRVbTxjg0imJAvhIvRbblH3xyqye/17Qnk2fOp8TNDOl7TBZHi0NCFQ5HUw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.33.0.tgz", + "integrity": "sha512-618PQGHQLBVCpAq1s+e/rpIUaLUnj19IPUgn97rUGXLLna8ETIAoyQoG70wz4q9niw4Z4GlS5kZNrael2O3+2w==", "dependencies": { - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/browser-utils/node_modules/@sentry/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.29.0.tgz", - "integrity": "sha512-j4gX3ctzgD4xVWllXAhm6M+kHFEvrFoUPFq60X/pgkjsWCocGuhtNfB0rW43ICG8hCnlz8IYl7O7b8V8qY7SPg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.33.0.tgz", + "integrity": "sha512-V/A+72ZdnfGtXeXIpz1kUo3LRdq3WKEYYFUR2RKpCdPh9yeOrHq6u/rmzTWx49+om0yhZN+JhVoxDzt75UoFRg==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/browser-utils/node_modules/@sentry/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-nb93/m3SjQChQJFqJj3oNW3Rz/12yrT7jypTCire3c2hpYWG2uR5n8VY9UUMTA6HLNvdom6tckK7p3bXGXlF0w==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-TdwtGdevJij2wq2x/hDUr+x5TXt47ZhWxZ8zluai/lnIDTUB3Xs/L9yHtj1J+H9hr8obkMASE9IanUrWXzrP6Q==", "dependencies": { - "@sentry/types": "8.29.0" + "@sentry/types": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.29.0.tgz", - "integrity": "sha512-yAL5YMEFk4XaeVRUGEguydahRzaQrNPAaWRv6k+XRzCv9CGBhxb14KXQc9X/penlauMFcDfgelCPKcTqcf6wDw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.33.0.tgz", + "integrity": "sha512-KSW/aiNgmJc8PDl2NsM+ONvGure4tPaluj7O1Nw+947Dh8W6CJnQ9srB7xPyoYYWyQW8Hyl1vzxY9W0J+fjlhA==", "dependencies": { - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/core": "8.33.0", + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback/node_modules/@sentry/core": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.29.0.tgz", - "integrity": "sha512-scMbZaJ0Ov8NPgWn86EdjhyTLrhvRVbTxjg0imJAvhIvRbblH3xyqye/17Qnk2fOp8TNDOl7TBZHi0NCFQ5HUw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.33.0.tgz", + "integrity": "sha512-618PQGHQLBVCpAq1s+e/rpIUaLUnj19IPUgn97rUGXLLna8ETIAoyQoG70wz4q9niw4Z4GlS5kZNrael2O3+2w==", "dependencies": { - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback/node_modules/@sentry/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.29.0.tgz", - "integrity": "sha512-j4gX3ctzgD4xVWllXAhm6M+kHFEvrFoUPFq60X/pgkjsWCocGuhtNfB0rW43ICG8hCnlz8IYl7O7b8V8qY7SPg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.33.0.tgz", + "integrity": "sha512-V/A+72ZdnfGtXeXIpz1kUo3LRdq3WKEYYFUR2RKpCdPh9yeOrHq6u/rmzTWx49+om0yhZN+JhVoxDzt75UoFRg==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/feedback/node_modules/@sentry/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-nb93/m3SjQChQJFqJj3oNW3Rz/12yrT7jypTCire3c2hpYWG2uR5n8VY9UUMTA6HLNvdom6tckK7p3bXGXlF0w==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-TdwtGdevJij2wq2x/hDUr+x5TXt47ZhWxZ8zluai/lnIDTUB3Xs/L9yHtj1J+H9hr8obkMASE9IanUrWXzrP6Q==", "dependencies": { - "@sentry/types": "8.29.0" + "@sentry/types": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.29.0.tgz", - "integrity": "sha512-Xgv/eYucsm7GaGKms2ClQ02NpD07MxjoTjp1/vYZm0H4Q08dVphVZrQp7hL1oX/VD9mb5SFyyKuuIRqIu7S8RA==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.33.0.tgz", + "integrity": "sha512-GFBaDA4yhlEf3wTXOVXnJVG/diuKxeqZuXcuhsAwJb+YcFR0NhgsRn3wIGuYOZZF8GBXzx9PFnb9yIuFgx5Nbw==", "dependencies": { - "@sentry-internal/browser-utils": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry-internal/browser-utils": "8.33.0", + "@sentry/core": "8.33.0", + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.29.0.tgz", - "integrity": "sha512-W2YbZRvp2lYC50V51fNLcnoIiK1Km4vSc+v6SL7c//lv2qpyumoUAAIDKY+14s8Lgt1RsR6rfZhfheD4O/6WSQ==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.33.0.tgz", + "integrity": "sha512-9fEhMP+gQYQrtn/SQd1Vd7U7emTSGBpLKc5h5f0iV0yDmjYAhNVbq4RgPTYAgnBEcdVo3qgboL6UIz9Dv+dYRQ==", "dependencies": { - "@sentry-internal/replay": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry-internal/replay": "8.33.0", + "@sentry/core": "8.33.0", + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/core": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.29.0.tgz", - "integrity": "sha512-scMbZaJ0Ov8NPgWn86EdjhyTLrhvRVbTxjg0imJAvhIvRbblH3xyqye/17Qnk2fOp8TNDOl7TBZHi0NCFQ5HUw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.33.0.tgz", + "integrity": "sha512-618PQGHQLBVCpAq1s+e/rpIUaLUnj19IPUgn97rUGXLLna8ETIAoyQoG70wz4q9niw4Z4GlS5kZNrael2O3+2w==", "dependencies": { - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.29.0.tgz", - "integrity": "sha512-j4gX3ctzgD4xVWllXAhm6M+kHFEvrFoUPFq60X/pgkjsWCocGuhtNfB0rW43ICG8hCnlz8IYl7O7b8V8qY7SPg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.33.0.tgz", + "integrity": "sha512-V/A+72ZdnfGtXeXIpz1kUo3LRdq3WKEYYFUR2RKpCdPh9yeOrHq6u/rmzTWx49+om0yhZN+JhVoxDzt75UoFRg==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay-canvas/node_modules/@sentry/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-nb93/m3SjQChQJFqJj3oNW3Rz/12yrT7jypTCire3c2hpYWG2uR5n8VY9UUMTA6HLNvdom6tckK7p3bXGXlF0w==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-TdwtGdevJij2wq2x/hDUr+x5TXt47ZhWxZ8zluai/lnIDTUB3Xs/L9yHtj1J+H9hr8obkMASE9IanUrWXzrP6Q==", "dependencies": { - "@sentry/types": "8.29.0" + "@sentry/types": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay/node_modules/@sentry/core": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.29.0.tgz", - "integrity": "sha512-scMbZaJ0Ov8NPgWn86EdjhyTLrhvRVbTxjg0imJAvhIvRbblH3xyqye/17Qnk2fOp8TNDOl7TBZHi0NCFQ5HUw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.33.0.tgz", + "integrity": "sha512-618PQGHQLBVCpAq1s+e/rpIUaLUnj19IPUgn97rUGXLLna8ETIAoyQoG70wz4q9niw4Z4GlS5kZNrael2O3+2w==", "dependencies": { - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay/node_modules/@sentry/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.29.0.tgz", - "integrity": "sha512-j4gX3ctzgD4xVWllXAhm6M+kHFEvrFoUPFq60X/pgkjsWCocGuhtNfB0rW43ICG8hCnlz8IYl7O7b8V8qY7SPg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.33.0.tgz", + "integrity": "sha512-V/A+72ZdnfGtXeXIpz1kUo3LRdq3WKEYYFUR2RKpCdPh9yeOrHq6u/rmzTWx49+om0yhZN+JhVoxDzt75UoFRg==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry-internal/replay/node_modules/@sentry/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-nb93/m3SjQChQJFqJj3oNW3Rz/12yrT7jypTCire3c2hpYWG2uR5n8VY9UUMTA6HLNvdom6tckK7p3bXGXlF0w==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-TdwtGdevJij2wq2x/hDUr+x5TXt47ZhWxZ8zluai/lnIDTUB3Xs/L9yHtj1J+H9hr8obkMASE9IanUrWXzrP6Q==", "dependencies": { - "@sentry/types": "8.29.0" + "@sentry/types": "8.33.0" }, "engines": { "node": ">=14.18" @@ -3956,48 +3985,48 @@ } }, "node_modules/@sentry/browser": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.29.0.tgz", - "integrity": "sha512-aKTy4H/3RI0q9LIeepesjWGlGNeh4HGFfwQjzHME8gcWCQ5LSlzYX4U+hu2yp7r1Jfd9MUTFfOuuLih2HGLGsQ==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.33.0.tgz", + "integrity": "sha512-qu/g20ZskywEU8BWc4Fts1kXFFBtw1vS+XvPq7Ta9zCeRG5dlXhhYDVQ4/v4nAL/cs0o6aLCq73m109CFF0Kig==", "dependencies": { - "@sentry-internal/browser-utils": "8.29.0", - "@sentry-internal/feedback": "8.29.0", - "@sentry-internal/replay": "8.29.0", - "@sentry-internal/replay-canvas": "8.29.0", - "@sentry/core": "8.29.0", - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry-internal/browser-utils": "8.33.0", + "@sentry-internal/feedback": "8.33.0", + "@sentry-internal/replay": "8.33.0", + "@sentry-internal/replay-canvas": "8.33.0", + "@sentry/core": "8.33.0", + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/browser/node_modules/@sentry/core": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.29.0.tgz", - "integrity": "sha512-scMbZaJ0Ov8NPgWn86EdjhyTLrhvRVbTxjg0imJAvhIvRbblH3xyqye/17Qnk2fOp8TNDOl7TBZHi0NCFQ5HUw==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.33.0.tgz", + "integrity": "sha512-618PQGHQLBVCpAq1s+e/rpIUaLUnj19IPUgn97rUGXLLna8ETIAoyQoG70wz4q9niw4Z4GlS5kZNrael2O3+2w==", "dependencies": { - "@sentry/types": "8.29.0", - "@sentry/utils": "8.29.0" + "@sentry/types": "8.33.0", + "@sentry/utils": "8.33.0" }, "engines": { "node": ">=14.18" } }, "node_modules/@sentry/browser/node_modules/@sentry/types": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.29.0.tgz", - "integrity": "sha512-j4gX3ctzgD4xVWllXAhm6M+kHFEvrFoUPFq60X/pgkjsWCocGuhtNfB0rW43ICG8hCnlz8IYl7O7b8V8qY7SPg==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.33.0.tgz", + "integrity": "sha512-V/A+72ZdnfGtXeXIpz1kUo3LRdq3WKEYYFUR2RKpCdPh9yeOrHq6u/rmzTWx49+om0yhZN+JhVoxDzt75UoFRg==", "engines": { "node": ">=14.18" } }, "node_modules/@sentry/browser/node_modules/@sentry/utils": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.29.0.tgz", - "integrity": "sha512-nb93/m3SjQChQJFqJj3oNW3Rz/12yrT7jypTCire3c2hpYWG2uR5n8VY9UUMTA6HLNvdom6tckK7p3bXGXlF0w==", + "version": "8.33.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.33.0.tgz", + "integrity": "sha512-TdwtGdevJij2wq2x/hDUr+x5TXt47ZhWxZ8zluai/lnIDTUB3Xs/L9yHtj1J+H9hr8obkMASE9IanUrWXzrP6Q==", "dependencies": { - "@sentry/types": "8.29.0" + "@sentry/types": "8.33.0" }, "engines": { "node": ">=14.18" @@ -4425,6 +4454,16 @@ "@types/node": "*" } }, + "node_modules/@types/cypress": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@types/cypress/-/cypress-1.1.3.tgz", + "integrity": "sha512-OXe0Gw8LeCflkG1oPgFpyrYWJmEKqYncBsD/J0r17r0ETx/TnIGDNLwXt/pFYSYuYTpzcq1q3g62M9DrfsBL4g==", + "deprecated": "This is a stub types definition for cypress (https://cypress.io). cypress provides its own type definitions, so you don't need @types/cypress installed!", + "dev": true, + "dependencies": { + "cypress": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -6121,9 +6160,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001603", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001603.tgz", - "integrity": "sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q==", + "version": "1.0.30001663", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz", + "integrity": "sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA==", "funding": [ { "type": "opencollective", @@ -6611,6 +6650,14 @@ "node": ">=8" } }, + "node_modules/css-box-model": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/css-box-model/-/css-box-model-1.2.1.tgz", + "integrity": "sha512-a7Vr4Q/kd/aw96bnJG332W9V9LkJO69JRcaCYDUqjp6/z0w6VcZjgAcTbgFxEPfBgdnAwlh3iwu+hLopa+flJw==", + "dependencies": { + "tiny-invariant": "^1.0.6" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -6629,9 +6676,9 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/cypress": { - "version": "13.14.1", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.1.tgz", - "integrity": "sha512-Wo+byPmjps66hACEH5udhXINEiN3qS3jWNGRzJOjrRJF3D0+YrcP2LVB1T7oYaVQM/S+eanqEvBWYc8cf7Vcbg==", + "version": "13.14.2", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.2.tgz", + "integrity": "sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==", "dev": true, "hasInstallScript": true, "dependencies": { @@ -7213,16 +7260,6 @@ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", "dev": true }, - "node_modules/dnd-core": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz", - "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==", - "dependencies": { - "@react-dnd/asap": "^5.0.1", - "@react-dnd/invariant": "^4.0.1", - "redux": "^4.2.0" - } - }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -7262,12 +7299,12 @@ } }, "node_modules/echarts": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.0.tgz", - "integrity": "sha512-rNYnNCzqDAPCr4m/fqyUFv7fD9qIsd50S6GDFgO1DxZhncCsNsG7IfUlAlvZe5oSEQxtsjnHiUuppzccry93Xw==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.5.1.tgz", + "integrity": "sha512-Fce8upazaAXUVUVsjgV6mBnGuqgO+JNDlcgF79Dksy4+wgGpQB2lmYoO4TSweFg/mZITdpGHomw/cNBJZj1icA==", "dependencies": { "tslib": "2.3.0", - "zrender": "5.5.0" + "zrender": "5.6.0" } }, "node_modules/echarts-for-react": { @@ -11024,11 +11061,6 @@ "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", "dev": true }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -12071,6 +12103,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==" + }, "node_modules/merge-refs": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/merge-refs/-/merge-refs-1.3.0.tgz", @@ -13592,7 +13629,8 @@ "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "dev": true }, "node_modules/picocolors": { "version": "1.0.0", @@ -14183,13 +14221,10 @@ "integrity": "sha512-twwRO/ilhlG/FIgYeKGFqyHhoEhqgnKVkcmqMKi2r524gz3ZbDTcyFt38E9xjJI2vT+KbRNHVbnJ/e0I25Azwg==", "dev": true }, - "node_modules/raf": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", - "integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==", - "dependencies": { - "performance-now": "^2.1.0" - } + "node_modules/raf-schd": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/raf-schd/-/raf-schd-4.0.3.tgz", + "integrity": "sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==" }, "node_modules/ramda": { "version": "0.29.0", @@ -14255,59 +14290,6 @@ "react": "^15.3.0 || 16 || 17 || 18" } }, - "node_modules/react-dnd": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz", - "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==", - "dependencies": { - "@react-dnd/invariant": "^4.0.1", - "@react-dnd/shallowequal": "^4.0.1", - "dnd-core": "^16.0.1", - "fast-deep-equal": "^3.1.3", - "hoist-non-react-statics": "^3.3.2" - }, - "peerDependencies": { - "@types/hoist-non-react-statics": ">= 3.3.1", - "@types/node": ">= 12", - "@types/react": ">= 16", - "react": ">= 16.14" - }, - "peerDependenciesMeta": { - "@types/hoist-non-react-statics": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-dnd-html5-backend": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz", - "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==", - "dependencies": { - "dnd-core": "^16.0.1" - } - }, - "node_modules/react-dnd-scrolling": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/react-dnd-scrolling/-/react-dnd-scrolling-1.3.8.tgz", - "integrity": "sha512-XcvmrTsQrPNry7jkY8RkNAZ2HZIiLiUzOSUltkW6eOhNjkKfivnPEhkj9hZf1JP4C32zxDsjN26xoBy7puZXoA==", - "dependencies": { - "hoist-non-react-statics": "3.x", - "lodash.throttle": "^4.1.1", - "prop-types": "15.x", - "raf": "^3.4.1" - }, - "peerDependencies": { - "react": "16.x || 17.x || 18.x", - "react-dnd": "10.x || 11.x || 14.x || 15.x || 16.x", - "react-dom": "16.x || 17.x || 18.x" - } - }, "node_modules/react-dom": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", @@ -14333,11 +14315,11 @@ } }, "node_modules/react-i18next": { - "version": "13.5.0", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-13.5.0.tgz", - "integrity": "sha512-CFJ5NDGJ2MUyBohEHxljOq/39NQ972rh1ajnadG9BjTk+UXbHLq4z5DKEbEQBDoIhUmmbuS/fIMJKo6VOax1HA==", + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", + "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==", "dependencies": { - "@babel/runtime": "^7.22.5", + "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" }, "peerDependencies": { @@ -17825,6 +17807,14 @@ "react-dom": ">=16.8" } }, + "node_modules/use-memo-one": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-memo-one/-/use-memo-one-1.1.3.tgz", + "integrity": "sha512-g66/K7ZQGYrI6dy8GLpVcMsBp4s17xNkYJVSMvTEevGy3nDxHOfE6z8BVE22+5G5x7t3+bhzrlTDB7ObrEE0cQ==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/use-sync-external-store": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", @@ -19206,9 +19196,9 @@ } }, "node_modules/zrender": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.5.0.tgz", - "integrity": "sha512-O3MilSi/9mwoovx77m6ROZM7sXShR/O/JIanvzTwjN3FORfLSr81PsUGd7jlaYOeds9d8tw82oP44+3YucVo+w==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz", + "integrity": "sha512-uzgraf4njmmHAbEUxMJ8Oxg+P3fT04O+9p7gY+wJRVxo8Ge+KmYv0WJev945EH4wFuc4OY2NLXz46FZrWS9xJg==", "dependencies": { "tslib": "2.3.0" } @@ -19228,4 +19218,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index cc2c7b955c2..1fd08c89d06 100644 --- a/package.json +++ b/package.json @@ -48,9 +48,10 @@ "@googlemaps/react-wrapper": "^1.1.35", "@googlemaps/typescript-guards": "^2.0.3", "@headlessui/react": "^2.1.2", + "@hello-pangea/dnd": "^17.0.0", "@pnotify/core": "^5.2.0", "@pnotify/mobile": "^5.2.0", - "@sentry/browser": "^8.29.0", + "@sentry/browser": "^8.33.0", "@yudiel/react-qr-scanner": "^2.0.0-beta.3", "axios": "^1.7.7", "bowser": "^2.11.0", @@ -58,7 +59,7 @@ "browserslist-useragent-regexp": "^4.1.3", "cross-env": "^7.0.3", "dayjs": "^1.11.11", - "echarts": "^5.5.0", + "echarts": "^5.5.1", "echarts-for-react": "^3.0.2", "events": "^3.3.0", "hi-profiles": "^1.0.6", @@ -70,12 +71,9 @@ "raviger": "^4.1.2", "react": "18.3.1", "react-copy-to-clipboard": "^5.1.0", - "react-dnd": "^16.0.1", - "react-dnd-html5-backend": "^16.0.1", - "react-dnd-scrolling": "^1.3.8", "react-dom": "18.3.1", "react-google-recaptcha": "^3.1.0", - "react-i18next": "^13.0.1", + "react-i18next": "^15.0.2", "react-infinite-scroll-component": "^6.1.0", "react-markdown": "^8.0.7", "react-pdf": "^9.1.0", @@ -91,6 +89,7 @@ "@tailwindcss/container-queries": "^0.1.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.13", + "@types/cypress": "^1.1.3", "@types/events": "^3.0.3", "@types/google.maps": "^3.55.8", "@types/lodash-es": "^4.17.12", @@ -103,7 +102,7 @@ "@typescript-eslint/eslint-plugin": "^7.18.0", "@vitejs/plugin-react-swc": "^3.6.0", "autoprefixer": "^10.4.19", - "cypress": "^13.14.1", + "cypress": "^13.14.2", "cypress-localstorage-commands": "^2.2.5", "cypress-split": "^1.23.2", "eslint-config-prettier": "^9.1.0", diff --git a/public/config.json b/public/config.json deleted file mode 100644 index 678e218070c..00000000000 --- a/public/config.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "github_url": "https://github.com/ohcnetwork", - "ohcn_url": "https://ohc.network?ref=care", - "site_url": "care.ohc.network", - "analytics_server_url": "https://plausible.10bedicu.in", - "header_logo": { - "light": "https://cdn.ohc.network/header_logo.png", - "dark": "https://cdn.ohc.network/header_logo.png" - }, - "main_logo": { - "light": "https://cdn.ohc.network/light-logo.svg", - "dark": "https://cdn.ohc.network/black-logo.svg" - }, - "gmaps_api_key": "AIzaSyDsBAc3y7deI5ZO3NtK5GuzKwtUzQNJNUk", - "gov_data_api_key": "579b464db66ec23bdd000001cdd3946e44ce4aad7209ff7b23ac571b", - "recaptcha_site_key": "6LdvxuQUAAAAADDWVflgBqyHGfq-xmvNJaToM0pN", - "sentry_dsn": "https://8801155bd0b848a09de9ebf6f387ebc8@sentry.io/5183632", - "sentry_environment": "staging", - "kasp_enabled": false, - "kasp_string": "KASP", - "kasp_full_string": "Karunya Arogya Suraksha Padhathi", - "sample_format_asset_import": "/asset-import-template.xlsx", - "sample_format_external_result_import": "/External-Results-Template.csv", - "enable_abdm": true, - "enable_hcx": false, - "enable_scribe": false, - "min_encounter_date": "2020-01-01" -} \ No newline at end of file diff --git a/public/env.json b/public/env.json deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/App.tsx b/src/App.tsx index 2e7f185f80b..6c6d5255b4d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,11 @@ import { Suspense } from "react"; import Routers from "./Routers"; import ThemedFavicon from "./CAREUI/misc/ThemedFavicon"; -import Intergrations from "./Integrations"; +import Integrations from "./Integrations"; import Loading from "./Components/Common/Loading"; import HistoryAPIProvider from "./Providers/HistoryAPIProvider"; import AuthUserProvider from "./Providers/AuthUserProvider"; +import { FeatureFlagsProvider } from "./Utils/featureFlags"; const App = () => { return ( @@ -12,12 +13,14 @@ const App = () => { }> - + + + {/* Integrations */} - - + + ); diff --git a/src/CAREUI/display/Timeline.tsx b/src/CAREUI/display/Timeline.tsx index 7fcb57f56ea..ee21b337724 100644 --- a/src/CAREUI/display/Timeline.tsx +++ b/src/CAREUI/display/Timeline.tsx @@ -26,7 +26,7 @@ const TimelineContext = createContext(""); export default function Timeline({ className, children, name }: TimelineProps) { return ( -
+
    {children} diff --git a/src/CAREUI/icons/CareIcon.tsx b/src/CAREUI/icons/CareIcon.tsx index 90a59d972cb..3bb67629bc2 100644 --- a/src/CAREUI/icons/CareIcon.tsx +++ b/src/CAREUI/icons/CareIcon.tsx @@ -1,8 +1,7 @@ +import iconData from "./UniconPaths.json"; import { transformIcons } from "./icon"; import { useEffect } from "react"; -import iconData from "./UniconPaths.json"; - export type IconName = keyof typeof iconData; export interface CareIconProps { diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index c2ddf842665..70b5bc78e23 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -1,4 +1,7 @@ -import { PatientCategory } from "../Components/Facility/models"; +import { + PatientCategory, + SpokeRelationship, +} from "../Components/Facility/models"; import { SortOption } from "../Components/Common/SortDropdown"; import { dateQueryString } from "../Utils/utils"; import { IconName } from "../CAREUI/icons/CareIcon"; @@ -8,7 +11,6 @@ import { ConsentHIType, ConsentPurpose, } from "../Components/ABDM/types/consent"; -import careConfig from "@careConfig"; export const RESULTS_PER_PAGE_LIMIT = 14; export const PAGINATION_LIMIT = 36; @@ -217,30 +219,7 @@ export const DISCHARGED_PATIENT_SORT_OPTIONS: SortOption[] = [ { isAscending: false, value: "-name" }, ]; -const { kasp } = careConfig; - -const KASP_BED_TYPES = kasp.enabled - ? [ - { id: 40, text: kasp.string + " Ordinary Beds" }, - { id: 60, text: kasp.string + " Oxygen beds" }, - { id: 50, text: kasp.string + " ICU (ICU without ventilator)" }, - { id: 70, text: kasp.string + " ICU (ICU with ventilator)" }, - ] - : []; - -export const BED_TYPES: OptionsType[] = [ - { id: 1, text: "Ordinary Beds" }, - { id: 150, text: "Oxygen beds" }, - { id: 10, text: "ICU (ICU without ventilator)" }, - { id: 20, text: "Ventilator (ICU with ventilator)" }, - { id: 30, text: "Covid Ordinary Beds" }, - { id: 120, text: "Covid Oxygen beds" }, - { id: 110, text: "Covid ICU (ICU without ventilator)" }, - { id: 100, text: "Covid Ventilators (ICU with ventilator)" }, - ...KASP_BED_TYPES, - { id: 2, text: "Hostel" }, - { id: 3, text: "Single Room with Attached Bathroom" }, -]; +export const BED_TYPES = [100, 200, 300, 400, 500]; export const DOCTOR_SPECIALIZATION: Array = [ { id: 1, text: "General Medicine" }, @@ -1541,6 +1520,17 @@ export const DEFAULT_ALLOWED_EXTENSIONS = [ "application/vnd.oasis.opendocument.spreadsheet,application/pdf", ]; +export const SPOKE_RELATION_TYPES = [ + { + text: "Regular", + value: SpokeRelationship.REGULAR, + }, + { + text: "Tele ICU", + value: SpokeRelationship.TELE_ICU, + }, +]; + export const HumanBodyPaths = { anterior: [ { diff --git a/src/Common/hooks/useAsyncOptions.ts b/src/Common/hooks/useAsyncOptions.ts deleted file mode 100644 index a81209f34f0..00000000000 --- a/src/Common/hooks/useAsyncOptions.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { debounce } from "lodash-es"; -import { useMemo, useState } from "react"; -import { useDispatch } from "react-redux"; -import { mergeQueryOptions } from "../../Utils/utils"; - -interface IUseAsyncOptionsArgs { - debounceInterval?: number; - queryResponseExtractor?: (data: any) => any; -} - -/** - * Deprecated. This is no longer needed as `useQuery` with `mergeQueryOptions` - * can be reused for this. - * - * Hook to implement async autocompletes with ease and typesafety. - * - * See `DiagnosisSelectFormField` for usage. - * - * **Example usage:** - * ```jsx - * const { fetchOptions, isLoading, options } = useAsyncOptions("id"); - * - * return ( - * fetchOptions(action({ query }))} - * optionValue={(option) => option} - * ... - * /> - * ); - * ``` - */ -export function useAsyncOptions>( - uniqueKey: keyof T, - args?: IUseAsyncOptionsArgs, -) { - const dispatch = useDispatch(); - const [queryOptions, setQueryOptions] = useState([]); - const [isLoading, setIsLoading] = useState(true); - - const fetchOptions = useMemo( - () => - debounce(async (action: any) => { - setIsLoading(true); - const res = await dispatch(action); - if (res?.data) - setQueryOptions( - args?.queryResponseExtractor?.(res.data) ?? (res.data as T[]), - ); - setIsLoading(false); - }, args?.debounceInterval ?? 300), - [dispatch, args?.debounceInterval], - ); - - const mergeValueWithQueryOptions = (selected?: T[]) => { - return mergeQueryOptions( - selected ?? [], - queryOptions, - (obj) => obj[uniqueKey], - ); - }; - - return { - /** - * Merges query options and selected options. - * - * **Example usage:** - * ```jsx - * const { isLoading } = useAsyncOptions("id"); - * - * - * ``` - */ - fetchOptions, - - /** - * Merges query options and selected options. - * - * **Example usage:** - * ```jsx - * const { options } = useAsyncOptions("id"); - * - * fetchOptions(action({ query }))} - * ... - * /> - * ``` - */ - isLoading, - - /** - * Merges query options and selected options. - * - * **Example usage:** - * ```jsx - * const { options } = useAsyncOptions("id"); - * - * - * ``` - */ - options: mergeValueWithQueryOptions, - }; -} diff --git a/src/Common/hooks/useExport.tsx b/src/Common/hooks/useExport.tsx index e7a76036b07..1e7540e6525 100644 --- a/src/Common/hooks/useExport.tsx +++ b/src/Common/hooks/useExport.tsx @@ -1,9 +1,7 @@ import dayjs from "../../Utils/dayjs"; import { useState } from "react"; -import { useDispatch } from "react-redux"; export default function useExport() { - const dispatch: any = useDispatch(); const [isExporting, setIsExporting] = useState(false); const getTimestamp = () => { @@ -16,17 +14,17 @@ export default function useExport() { const exportCSV = async ( filenamePrefix: string, - action: any, + getData: () => Promise, parse = (data: string) => data, ) => { setIsExporting(true); const filename = `${filenamePrefix}_${getTimestamp()}.csv`; - const res = await dispatch(action); - if (res.status === 200) { + const data = await getData(); + if (data) { const a = document.createElement("a"); - const blob = new Blob([parse(res.data)], { + const blob = new Blob([parse(data)], { type: "text/csv", }); a.href = URL.createObjectURL(blob); @@ -39,15 +37,15 @@ export default function useExport() { const exportJSON = async ( filenamePrefix: string, - action: any, + getData: () => Promise<{ results: object[] } | null>, parse = (data: string) => data, ) => { setIsExporting(true); - const res = await dispatch(action); - if (res.status === 200) { + const data = await getData(); + if (data?.results.length) { const a = document.createElement("a"); - const blob = new Blob([parse(JSON.stringify(res.data.results))], { + const blob = new Blob([parse(JSON.stringify(data.results))], { type: "application/json", }); a.href = URL.createObjectURL(blob); @@ -59,7 +57,7 @@ export default function useExport() { }; const exportFile = ( - action: any, + action: () => Promise<{ results: object[] } | string | null>, filePrefix = "export", type = "csv", parse = (data: string) => data, @@ -68,13 +66,17 @@ export default function useExport() { switch (type) { case "csv": - exportCSV(filePrefix, action(), parse); + exportCSV(filePrefix, action as Parameters[1], parse); break; case "json": - exportJSON(filePrefix, action(), parse); + exportJSON( + filePrefix, + action as Parameters[1], + parse, + ); break; default: - exportCSV(filePrefix, action(), parse); + exportCSV(filePrefix, action as Parameters[1], parse); } }; diff --git a/src/Common/hooks/useFilters.tsx b/src/Common/hooks/useFilters.tsx index a4b924edcdb..a6974a5a43b 100644 --- a/src/Common/hooks/useFilters.tsx +++ b/src/Common/hooks/useFilters.tsx @@ -6,6 +6,7 @@ import PaginationComponent from "../../Components/Common/Pagination"; import { classNames, humanizeStrings } from "../../Utils/utils"; import FiltersCache from "../../Utils/FiltersCache"; import careConfig from "@careConfig"; +import { triggerGoal } from "../../Integrations/Plausible"; export type FilterState = Record; @@ -42,6 +43,17 @@ export default function useFilters({ ) => { query = FiltersCache.utils.clean(query); _setQueryParams(query, options); + + // For each of the newly applied filters (additional filters compared to + // previously applied ones), trigger a plausible goal "Advanced filter + // applied" with the applied filter's query key and current location as tags. + Object.keys(query).forEach((filter) => + triggerGoal("Advanced filter applied", { + filter, + location: location.pathname, + }), + ); + updateCache(query); }; @@ -109,7 +121,7 @@ export default function useFilters({ return { name, paramKey, - value: qParams[paramKey] && t("SortOptions." + qParams[paramKey]), + value: qParams[paramKey] && t("SORT_OPTIONS__" + qParams[paramKey]), }; }, value(name: string, paramKey: FilterBadgeProps["paramKey"], value: string) { diff --git a/src/Common/hooks/useSlug.ts b/src/Common/hooks/useSlug.ts index 78d2a567e37..a92407e73e5 100644 --- a/src/Common/hooks/useSlug.ts +++ b/src/Common/hooks/useSlug.ts @@ -31,6 +31,9 @@ export const useSlugs = (...prefix: string[]) => { const findSlug = (segments: string[], prefix: string, fallback?: string) => { const index = segments.findIndex((segment) => segment === prefix); if (index === -1) { + if (fallback) { + return fallback; + } throw new Error( `Prefix "${prefix}" not found in path "${segments.join("/")}"`, ); diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index fa609e0e3b8..72da2dad360 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -1,6 +1,5 @@ import { Scanner } from "@yudiel/react-qr-scanner"; import * as Notification from "../../Utils/Notifications.js"; -import { listAssets } from "../../Redux/actions"; import { assetClassProps, AssetData } from "./AssetTypes"; import { useState, useEffect, lazy } from "react"; import { Link, navigate } from "raviger"; @@ -317,13 +316,12 @@ const AssetsList = () => { }, { label: "Export Assets (JSON)", - action: () => - authorizedForImportExport && - listAssets({ - ...qParams, - json: true, - limit: totalCount, - }), + action: async () => { + const { data } = await request(routes.listAssets, { + query: { ...qParams, json: true, limit: totalCount }, + }); + return data ?? null; + }, type: "json", filePrefix: `assets_${facility?.name ?? "all"}`, options: { @@ -334,13 +332,12 @@ const AssetsList = () => { }, { label: "Export Assets (CSV)", - action: () => - authorizedForImportExport && - listAssets({ - ...qParams, - csv: true, - limit: totalCount, - }), + action: async () => { + const { data } = await request(routes.listAssets, { + query: { ...qParams, csv: true, limit: totalCount }, + }); + return data ?? null; + }, type: "csv", filePrefix: `assets_${facility?.name ?? "all"}`, options: { diff --git a/src/Components/CameraFeed/CameraFeed.tsx b/src/Components/CameraFeed/CameraFeed.tsx index 863a6bbac8a..e49a63f7028 100644 --- a/src/Components/CameraFeed/CameraFeed.tsx +++ b/src/Components/CameraFeed/CameraFeed.tsx @@ -12,6 +12,7 @@ import useFullscreen from "../../Common/hooks/useFullscreen"; import useBreakpoints from "../../Common/hooks/useBreakpoints"; import { GetPresetsResponse } from "./routes"; import VideoPlayer from "./videoPlayer"; +import MonitorAssetPopover from "../Common/MonitorAssetPopover"; interface Props { children?: React.ReactNode; @@ -145,10 +146,10 @@ export default function CameraFeed(props: Props) { ); return ( -
    +
    { if (playerStatus !== "playing") { return "bg-black text-zinc-400"; @@ -185,6 +186,10 @@ export default function CameraFeed(props: Props) { {props.asset.name} + {!isIOS && (
    -
    +
    {/* Notifications */} {playerStatus === "playing" && } @@ -250,7 +255,7 @@ export default function CameraFeed(props: Props) { { setPlayedOn(new Date()); setState("playing"); @@ -266,6 +271,7 @@ export default function CameraFeed(props: Props) { }} onError={props.onStreamError} /> + {inlineControls && playerStatus === "playing" && controls}
    {!inlineControls && ( diff --git a/src/Components/Common/Export.tsx b/src/Components/Common/Export.tsx index 72d5f447e57..fb1aa812633 100644 --- a/src/Components/Common/Export.tsx +++ b/src/Components/Common/Export.tsx @@ -6,6 +6,8 @@ import DropdownMenu, { import ButtonV2 from "../../Components/Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; import useExport from "../../Common/hooks/useExport"; +import { Route } from "../../Utils/request/types"; +import request from "../../Utils/request/request"; interface ExportItem { options?: DropdownItemProps; @@ -13,7 +15,8 @@ interface ExportItem { filePrefix?: string; label: string; parse?: (data: string) => string; - action?: any; + action?: Parameters["exportFile"]>[0]; + route?: Route; } interface ExportMenuProps { @@ -27,7 +30,8 @@ interface ExportButtonProps { tooltip?: string | undefined; tooltipClassName?: string; type?: "csv" | "json"; - action?: any; + action?: Parameters["exportFile"]>[0]; + route?: Route; parse?: (data: string) => string; filenamePrefix: string; } @@ -45,9 +49,18 @@ export const ExportMenu = ({ return ( - exportFile(item.action, item.filePrefix, item.type, item.parse) - } + onClick={() => { + let action = item.action; + if (item.route) { + action = async () => { + const { data } = await request(item.route!); + return data ?? null; + }; + } + if (action) { + exportFile(action, item.filePrefix, item.type, item.parse); + } + }} border ghost className="py-2.5" @@ -69,9 +82,18 @@ export const ExportMenu = ({ {exportItems.map((item) => ( - exportFile(item.action, item.filePrefix, item.type, item.parse) - } + onClick={() => { + let action = item.action; + if (item.route) { + action = async () => { + const { data } = await request(item.route!); + return data ?? null; + }; + } + if (action) { + exportFile(action, item.filePrefix, item.type, item.parse); + } + }} {...item.options} > {item.label} @@ -94,9 +116,18 @@ export const ExportButton = ({ <> - exportFile(props.action, props.filenamePrefix, type, parse) - } + onClick={() => { + let action = props.action; + if (props.route) { + action = async () => { + const { data } = await request(props.route!); + return data ?? null; + }; + } + if (action) { + exportFile(action, props.filenamePrefix, type, parse); + } + }} className="tooltip mx-2 p-4 text-lg text-secondary-800 disabled:bg-transparent disabled:text-secondary-500" variant="secondary" ghost diff --git a/src/Components/Common/FacilitySelect.tsx b/src/Components/Common/FacilitySelect.tsx index d718ef3e781..d91b9a7f8fc 100644 --- a/src/Components/Common/FacilitySelect.tsx +++ b/src/Components/Common/FacilitySelect.tsx @@ -23,6 +23,8 @@ interface FacilitySelectProps { selected?: FacilityModel | FacilityModel[] | null; setSelected: (selected: FacilityModel | FacilityModel[] | null) => void; allowNone?: boolean; + placeholder?: string; + filter?: (facilities: FacilityModel) => boolean; } export const FacilitySelect = (props: FacilitySelectProps) => { @@ -44,6 +46,8 @@ export const FacilitySelect = (props: FacilitySelectProps) => { allowNone = false, freeText = false, errors = "", + placeholder, + filter, } = props; const facilitySearch = useCallback( @@ -82,6 +86,7 @@ export const FacilitySelect = (props: FacilitySelectProps) => { return ( { compareBy="id" className={className} error={errors} + filter={filter} /> ); }; diff --git a/src/Components/VitalsMonitor/VitalsMonitorAssetPopover.tsx b/src/Components/Common/MonitorAssetPopover.tsx similarity index 82% rename from src/Components/VitalsMonitor/VitalsMonitorAssetPopover.tsx rename to src/Components/Common/MonitorAssetPopover.tsx index 7ee5bf6c898..5293c0a6cd4 100644 --- a/src/Components/VitalsMonitor/VitalsMonitorAssetPopover.tsx +++ b/src/Components/Common/MonitorAssetPopover.tsx @@ -10,13 +10,15 @@ import { Transition, } from "@headlessui/react"; -interface VitalsMonitorAssetPopoverProps { +interface MonitorAssetPopoverProps { asset?: AssetData; + className?: string; } -const VitalsMonitorAssetPopover = ({ +const MonitorAssetPopover = ({ asset, -}: VitalsMonitorAssetPopoverProps) => { + className, +}: MonitorAssetPopoverProps) => { const { t } = useTranslation(); return ( @@ -35,9 +37,9 @@ const VitalsMonitorAssetPopover = ({ leaveFrom="opacity-100 translate-y-0" leaveTo="opacity-0 translate-y-1" > - +
    -
    +
    {asset?.name}

    -

    Middleware Hostname:

    +

    + {t("middleware_hostname")}: +

    {asset?.resolved_middleware?.hostname}

    -

    Local IP Address:

    +

    + {t("local_ipaddress")}: +

    {asset?.meta?.local_ip_address}

    @@ -81,4 +87,4 @@ const VitalsMonitorAssetPopover = ({ ); }; -export default VitalsMonitorAssetPopover; +export default MonitorAssetPopover; diff --git a/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx b/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx index 1fee5599220..0da23ac3d18 100644 --- a/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx +++ b/src/Components/Common/PMJAYProcedurePackageAutocomplete.tsx @@ -1,13 +1,16 @@ -import { useAsyncOptions } from "../../Common/hooks/useAsyncOptions"; -import { listPMJYPackages } from "../../Redux/actions"; -import { Autocomplete } from "../Form/FormFields/Autocomplete"; -import FormField from "../Form/FormFields/FormField"; import { FormFieldBaseProps, useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; -type PMJAYPackageItem = { +import { Autocomplete } from "../Form/FormFields/Autocomplete"; +import FormField from "../Form/FormFields/FormField"; +import routes from "../../Redux/api"; +import { useState } from "react"; +import useQuery from "../../Utils/request/useQuery"; +import { mergeQueryOptions } from "../../Utils/utils"; + +export type PMJAYPackageItem = { name?: string; code?: string; price?: number; @@ -19,8 +22,11 @@ type Props = FormFieldBaseProps; export default function PMJAYProcedurePackageAutocomplete(props: Props) { const field = useFormFieldPropsResolver(props); - const { fetchOptions, isLoading, options } = - useAsyncOptions("code"); + const [query, setQuery] = useState(""); + + const { data, loading } = useQuery(routes.hcx.claims.listPMJYPackages, { + query: { query, limit: 10 }, + }); return ( @@ -30,19 +36,20 @@ export default function PMJAYProcedurePackageAutocomplete(props: Props) { disabled={field.disabled} value={field.value} onChange={field.handleChange} - options={options(field.value ? [field.value] : []).map((o) => { - // TODO: update backend to return price as number instead - return { + options={mergeQueryOptions( + (field.value ? [field.value] : []).map((o) => ({ ...o, price: o.price && parseFloat(o.price?.toString().replaceAll(",", "")), - }; - })} + })), + data ?? [], + (obj) => obj.code, + )} optionLabel={optionLabel} optionDescription={optionDescription} optionValue={(option) => option} - onQuery={(query) => fetchOptions(listPMJYPackages(query))} - isLoading={isLoading} + onQuery={setQuery} + isLoading={loading} /> ); diff --git a/src/Components/Common/Sidebar/Sidebar.tsx b/src/Components/Common/Sidebar/Sidebar.tsx index 52e5e0680fa..e18c17bcb68 100644 --- a/src/Components/Common/Sidebar/Sidebar.tsx +++ b/src/Components/Common/Sidebar/Sidebar.tsx @@ -70,7 +70,7 @@ const StatelessSidebar = ({ const [lastIndicatorPosition, setLastIndicatorPosition] = useState(0); const [isOverflowVisible, setOverflowVisisble] = useState(false); - useEffect(() => { + const updateIndicator = () => { if (!indicatorRef.current) return; const index = NavItems.findIndex((item) => item.to === activeLink); const navItemCount = NavItems.length + (careConfig.urls.dashboard ? 2 : 1); // +2 for notification and dashboard @@ -94,8 +94,17 @@ const StatelessSidebar = ({ } else { indicatorRef.current.style.display = "none"; } + }; + + useEffect(() => { + updateIndicator(); }, [activeLink, lastIndicatorPosition]); + useEffect(() => { + addEventListener("resize", updateIndicator); + return () => removeEventListener("resize", updateIndicator); + }, []); + const handleOverflow = (value: boolean) => { setOverflowVisisble(value); }; diff --git a/src/Components/Common/SortDropdown.tsx b/src/Components/Common/SortDropdown.tsx index 8ffd09ba269..b54edffa595 100644 --- a/src/Components/Common/SortDropdown.tsx +++ b/src/Components/Common/SortDropdown.tsx @@ -41,7 +41,7 @@ export default function SortDropdownMenu(props: Props) { /> } > - {t("SortOptions." + value)} + {t("SORT_OPTIONS__" + value)} ))} diff --git a/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx b/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx index 043654929a8..1e77a1d7c79 100644 --- a/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx +++ b/src/Components/Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder.tsx @@ -80,11 +80,12 @@ interface EditDiagnosesProps { className?: string; value: ConsultationDiagnosis[]; suggestions?: ICD11DiagnosisModel[]; + consultationId?: string; onUpdate?: (diagnoses: ConsultationDiagnosis[]) => void; } export const EditDiagnosesBuilder = (props: EditDiagnosesProps) => { - const consultation = useSlug("consultation"); + const consultation = useSlug("consultation", props.consultationId); const [diagnoses, setDiagnoses] = useState(props.value); const [prefill, setPrefill] = useState(); diff --git a/src/Components/ExternalResult/ResultList.tsx b/src/Components/ExternalResult/ResultList.tsx index abd77e0e4ac..74c4655104d 100644 --- a/src/Components/ExternalResult/ResultList.tsx +++ b/src/Components/ExternalResult/ResultList.tsx @@ -1,7 +1,6 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { navigate } from "raviger"; import { lazy, useState } from "react"; -import { externalResultList } from "../../Redux/actions"; import ListFilter from "./ListFilter"; import FacilitiesSelectDialogue from "./FacilitiesSelectDialogue"; import { FacilityModel } from "../Facility/models"; @@ -19,6 +18,7 @@ import { parsePhoneNumber } from "../../Utils/utils"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import ExternalResultImportModal from "./ExternalResultImportModal"; +import request from "../../Utils/request/request"; const Loading = lazy(() => import("../Common/Loading")); @@ -254,11 +254,12 @@ export default function ResultList() { : []), { label: "Export Results", - action: () => - externalResultList( - { ...qParams, csv: true }, - "externalResultList", - ), + action: async () => { + const { data } = await request(routes.externalResultList, { + query: { ...qParams, csv: true }, + }); + return data ?? null; + }, filePrefix: "external_results", options: { icon: , diff --git a/src/Components/Facility/AddBedForm.tsx b/src/Components/Facility/AddBedForm.tsx index 0a612fa776b..047549bd652 100644 --- a/src/Components/Facility/AddBedForm.tsx +++ b/src/Components/Facility/AddBedForm.tsx @@ -127,7 +127,8 @@ export const AddBedForm = ({ facilityId, locationId, bedId }: Props) => { const { res } = await request(routes.createFacilityBed, { body: { ...data, facility: facilityId, location: locationId }, }); - res?.ok && onSuccess("Bed(s) created successfully"); + res?.ok && + onSuccess(t("bed_created_notification", { count: numberOfBeds })); } }; diff --git a/src/Components/Facility/BedCapacity.tsx b/src/Components/Facility/BedCapacity.tsx index a4437823d5d..7d36190c441 100644 --- a/src/Components/Facility/BedCapacity.tsx +++ b/src/Components/Facility/BedCapacity.tsx @@ -1,14 +1,14 @@ import { useEffect, useReducer, useState } from "react"; import * as Notification from "../../Utils/Notifications.js"; -import { CapacityModal } from "./models"; +import { CapacityModal, OptionsType } from "./models"; import TextFormField from "../Form/FormFields/TextFormField"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import { BED_TYPES } from "../../Common/constants"; import routes from "../../Redux/api"; import request from "../../Utils/request/request"; import { useTranslation } from "react-i18next"; -import { BED_TYPES } from "../../Common/constants.js"; interface BedCapacityProps extends CapacityModal { facilityId: string; @@ -52,8 +52,10 @@ export const BedCapacity = (props: BedCapacityProps) => { const { t } = useTranslation(); const { facilityId, handleClose, handleUpdate, className, id } = props; const [state, dispatch] = useReducer(bedCountReducer, initialState); - const [bedTypes, setBedTypes] = useState(BED_TYPES); const [isLastOptionType, setIsLastOptionType] = useState(false); + const [bedTypes, setBedTypes] = useState( + BED_TYPES.map((o) => ({ id: o, text: t(`bed_type__${o}`) })), + ); const [isLoading, setIsLoading] = useState(false); const headerText = !id ? "Add Bed Capacity" : "Edit Bed Capacity"; @@ -77,10 +79,11 @@ export const BedCapacity = (props: BedCapacityProps) => { // disable existing bed types const updatedBedTypes = BED_TYPES.map((type) => { const isExisting = existingData.find( - (i: CapacityModal) => i.room_type === type.id, + (i: CapacityModal) => i.room_type === type, ); return { - ...type, + id: type, + text: t(`bed_type__${type}`), disabled: !!isExisting, }; }); @@ -111,7 +114,8 @@ export const BedCapacity = (props: BedCapacityProps) => { useEffect(() => { const lastBedType = - bedTypes.filter((i) => i.disabled).length === BED_TYPES.length - 1; + bedTypes.filter((i: OptionsType) => i.disabled).length === + BED_TYPES.length - 1; setIsLastOptionType(lastBedType); }, [bedTypes]); diff --git a/src/Components/Facility/CentralNursingStation.tsx b/src/Components/Facility/CentralNursingStation.tsx index 178bd2bc25c..fbbdf24d7f2 100644 --- a/src/Components/Facility/CentralNursingStation.tsx +++ b/src/Components/Facility/CentralNursingStation.tsx @@ -145,7 +145,7 @@ export default function CentralNursingStation({ facilityId }: Props) { value={qParams.ordering || "bed__name"} onChange={({ value }) => updateQuery({ ordering: value })} options={SORT_OPTIONS} - optionLabel={({ value }) => t("SortOptions." + value)} + optionLabel={({ value }) => t("SORT_OPTIONS__" + value)} optionIcon={({ isAscending }) => ( (); - const [claims, setClaims] = useState(); +}: IConsultationClaimsProps) { + const { t } = useTranslation(); + const [isCreateLoading, setIsCreateLoading] = useState(false); - const fetchClaims = useCallback(async () => { - const res = await dispatch( - HCXActions.claims.list({ + const { data: claimsResult, refetch: refetchClaims } = useQuery( + routes.hcx.claims.list, + { + query: { ordering: "-modified_date", consultation: consultationId, - }), - ); + }, + onResponse: (res) => { + if (!isCreateLoading) return; - if (res.data && res.data.results) { - setClaims(res.data.results); - if (isCreateLoading) - Notification.Success({ msg: "Fetched Claim Approval Results" }); - } else { - if (isCreateLoading) - Notification.Success({ msg: "Error Fetched Claim Approval Results" }); - } - setIsCreateLoading(false); - }, [dispatch, consultationId]); + if (res.data?.results) { + Notification.Success({ + msg: t("claim__fetched_claim_approval_results"), + }); + return; + } - useEffect(() => { - fetchClaims(); - }, [fetchClaims]); + Notification.Error({ + msg: t("claim__error_fetching_claim_approval_results"), + }); + }, + }, + ); useMessageListener((data) => { if ( @@ -53,14 +55,14 @@ export default function ConsultationClaims({ (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && data.message === "success" ) { - fetchClaims(); + refetchClaims(); } }); return (
    { navigate( @@ -81,9 +83,9 @@ export default function ConsultationClaims({
    - {claims?.map((claim) => ( + {claimsResult?.results.map((claim) => (
    - +
    ))}
    diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 911e62d5627..cac822d76c4 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -4,7 +4,6 @@ import { getConsultation, getPatient, listAssetBeds, - listShiftRequests, } from "../../../Redux/actions"; import { statusType, useAbortableEffect } from "../../../Common/utils"; import { lazy, useCallback, useState } from "react"; @@ -179,12 +178,11 @@ export const ConsultationDetails = (props: any) => { setAbhaNumberData(abhaNumberData); // Get shifting data - const shiftingRes = await dispatch( - listShiftRequests({ patient: id }, "shift-list-call"), - ); - if (shiftingRes?.data?.results) { - const data = shiftingRes.data.results; - setActiveShiftingData(data); + const shiftRequestsQuery = await request(routes.listShiftRequests, { + query: { patient: id }, + }); + if (shiftRequestsQuery.data?.results) { + setActiveShiftingData(shiftRequestsQuery.data.results); } } else { navigate("/not-found"); diff --git a/src/Components/Facility/ConsultationDoctorNotes/index.tsx b/src/Components/Facility/ConsultationDoctorNotes/index.tsx index cf7d9c61ef9..3d334ba2cb6 100644 --- a/src/Components/Facility/ConsultationDoctorNotes/index.tsx +++ b/src/Components/Facility/ConsultationDoctorNotes/index.tsx @@ -6,7 +6,7 @@ import CareIcon from "../../../CAREUI/icons/CareIcon"; import { NonReadOnlyUsers } from "../../../Utils/AuthorizeFor"; import { useMessageListener } from "../../../Common/hooks/useMessageListener"; import PatientConsultationNotesList from "../PatientConsultationNotesList.js"; -import { PatientNoteStateType } from "../models.js"; +import { PatientNoteStateType, PaitentNotesReplyModel } from "../models.js"; import routes from "../../../Redux/api.js"; import request from "../../../Utils/request/request.js"; import useQuery from "../../../Utils/request/useQuery.js"; @@ -15,6 +15,7 @@ import { classNames, isAppleDevice, keysOf } from "../../../Utils/utils.js"; import AutoExpandingTextInputFormField from "../../Form/FormFields/AutoExpandingTextInputFormField.js"; import { PATIENT_NOTES_THREADS } from "../../../Common/constants.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; +import DoctorNoteReplyPreviewCard from "../DoctorNoteReplyPreviewCard.js"; import { t } from "i18next"; interface ConsultationDoctorNotesProps { @@ -39,6 +40,9 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { const [facilityName, setFacilityName] = useState(""); const [patientName, setPatientName] = useState(""); const [focused, setFocused] = useState(false); + const [reply_to, setReplyTo] = useState( + undefined, + ); const initialData: PatientNoteStateType = { notes: [], @@ -65,6 +69,7 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { note: noteField, thread, consultation: consultationId, + reply_to: reply_to?.id, }, }); @@ -73,6 +78,7 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { setState({ ...state, cPage: 1 }); setNoteField(""); setReload(true); + setReplyTo(undefined); } }; @@ -145,36 +151,41 @@ const ConsultationDoctorNotes = (props: ConsultationDoctorNotesProps) => { reload={reload} setReload={setReload} thread={thread} + setReplyTo={setReplyTo} /> - -
    - setNoteField(e.value)} - className="w-full grow" - innerClassName="pr-10" - errorClassName="hidden" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - - -
    + setReplyTo(undefined)} + > +
    + setNoteField(e.value)} + className="w-full grow" + innerClassName="pr-10" + errorClassName="hidden" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + /> + + + +
    +
    ); diff --git a/src/Components/Facility/Consultations/BedActivityTimeline.tsx b/src/Components/Facility/Consultations/BedActivityTimeline.tsx index 3b19b12329a..d9db520192a 100644 --- a/src/Components/Facility/Consultations/BedActivityTimeline.tsx +++ b/src/Components/Facility/Consultations/BedActivityTimeline.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next"; import Chip from "../../../CAREUI/display/Chip"; import Timeline, { TimelineEvent, @@ -204,6 +205,7 @@ const BedTitleSuffix = ({ isLastNode?: boolean; prevBed?: CurrentBed; }) => { + const { t } = useTranslation(); return (
    @@ -215,7 +217,7 @@ const BedTitleSuffix = ({ {bed.bed_object.id === prevBed?.bed_object.id ? "Asset changed in" + " " : "Transferred to" + " "} - {`${bed.bed_object.name} (${bed.bed_object.bed_type}) in ${bed.bed_object.location_object?.name}`} + {`${bed.bed_object.name} (${t(bed.bed_object.bed_type!)}) in ${bed.bed_object.location_object?.name}`} {bed.end_date === null && ( {
    - + Update diff --git a/src/Components/Facility/CoverImageEditModal.tsx b/src/Components/Facility/CoverImageEditModal.tsx index bade873c5fa..d65a1d0ebfd 100644 --- a/src/Components/Facility/CoverImageEditModal.tsx +++ b/src/Components/Facility/CoverImageEditModal.tsx @@ -13,7 +13,6 @@ import Webcam from "react-webcam"; import { FacilityModel } from "./models"; import useWindowDimensions from "../../Common/hooks/useWindowDimensions"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import * as Notification from "../../Utils/Notifications.js"; import { useTranslation } from "react-i18next"; import { LocalStorageKeys } from "../../Common/constants"; import DialogModal from "../Common/Dialog"; @@ -130,25 +129,17 @@ const CoverImageEditModal = ({ "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), }, async (xhr: XMLHttpRequest) => { + setIsProcessing(false); if (xhr.status === 200) { Success({ msg: "Cover image updated." }); - setIsProcessing(false); setIsCaptureImgBeingUploaded(false); await sleep(1000); onSave?.(); closeModal(); - } else { - Notification.Error({ - msg: "Something went wrong!", - }); - setIsProcessing(false); } }, null, () => { - Notification.Error({ - msg: "Network Failure. Please check your internet connectivity.", - }); setIsProcessing(false); }, ); @@ -167,10 +158,6 @@ const CoverImageEditModal = ({ closeModal(); }; - const hasImage = !!(preview || facility.read_cover_image_url); - const imgSrc = - preview || `${facility.read_cover_image_url}?requested_on=${Date.now()}`; - const dragProps = useDragAndDrop(); const onDrop = (e: React.DragEvent) => { e.preventDefault(); @@ -200,11 +187,11 @@ const CoverImageEditModal = ({
    {!isCameraOpen ? (
    - {hasImage ? ( + {preview || facility.read_cover_image_url ? ( <>
    {facility.name} diff --git a/src/Components/Facility/DischargeModal.tsx b/src/Components/Facility/DischargeModal.tsx index ac86ff02c55..5573fe883d0 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -1,32 +1,35 @@ import * as Notification from "../../Utils/Notifications"; import { Cancel, Submit } from "../Common/components/ButtonV2"; -import { useCallback, useEffect, useState } from "react"; +import { useEffect, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import ClaimDetailCard from "../HCX/ClaimDetailCard"; +import CircularProgress from "../Common/components/CircularProgress"; +import ClaimCard from "../HCX/ClaimCard"; import { ConsultationModel } from "./models"; import CreateClaimCard from "../HCX/CreateClaimCard"; import { DISCHARGE_REASONS } from "../../Common/constants"; import DialogModal from "../Common/Dialog"; +import { FacilityModel } from "./models"; +import { FacilitySelect } from "../Common/FacilitySelect"; +import { FieldError } from "../Form/FieldValidators"; import { FieldLabel } from "../Form/FormFields/FormField"; -import { HCXActions } from "../../Redux/actions"; import { HCXClaimModel } from "../HCX/models"; +import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; +import dayjs from "../../Utils/dayjs"; import { dischargePatient } from "../../Redux/actions"; import { useDispatch } from "react-redux"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; -import PrescriptionBuilder from "../Medicine/PrescriptionBuilder"; -import CircularProgress from "../Common/components/CircularProgress"; -import { FacilitySelect } from "../Common/FacilitySelect"; -import { FacilityModel } from "./models"; -import dayjs from "../../Utils/dayjs"; -import { FieldError } from "../Form/FieldValidators"; +import useQuery from "../../Utils/request/useQuery"; import { useTranslation } from "react-i18next"; import useConfirmedAction from "../../Common/hooks/useConfirmedAction"; import ConfirmDialog from "../Common/ConfirmDialog"; +import routes from "../../Redux/api"; +import { EditDiagnosesBuilder } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder"; +import Loading from "../Common/Loading"; import careConfig from "@careConfig"; interface PreDischargeFormInterface { @@ -81,6 +84,34 @@ const DischargeModal = ({ const [facility, setFacility] = useState(referred_to); const [errors, setErrors] = useState({}); + const { refetch: refetchLatestClaim } = useQuery(routes.hcx.claims.list, { + query: { + consultation: consultationData.id, + ordering: "-modified_date", + use: "claim", + outcome: "complete", + limit: 1, + }, + onResponse: (res) => { + if (!isCreateClaimLoading) return; + + setIsCreateClaimLoading(false); + + if (res?.data?.results?.length !== 0) { + setLatestClaim(res?.data?.results[0]); + Notification.Success({ + msg: t("claim__fetched_claim_approval_results"), + }); + return; + } + + setLatestClaim(undefined); + Notification.Success({ + msg: t("claim__error_fetching_claim_approval_results"), + }); + }, + }); + useEffect(() => { setPreDischargeForm((prev) => ({ ...prev, @@ -94,41 +125,21 @@ const DischargeModal = ({ setFacility(referred_to); }, [referred_to]); + const initialDiagnoses = useQuery(routes.getConsultation, { + pathParams: { id: consultationData.id ?? "" }, + prefetch: !!consultationData.id, + }).data?.diagnoses; + const discharge_reason = new_discharge_reason ?? preDischargeForm.new_discharge_reason; - const fetchLatestClaim = useCallback(async () => { - const res = await dispatch( - HCXActions.claims.list({ - ordering: "-modified_date", - use: "claim", - consultation: consultationData.id, - }), - ); - - if (res?.data?.results?.length > 0) { - setLatestClaim(res.data.results[0]); - if (isCreateClaimLoading) - Notification.Success({ msg: "Fetched Claim Approval Results" }); - } else { - setLatestClaim(undefined); - if (isCreateClaimLoading) - Notification.Success({ msg: "Error Fetched Claim Approval Results" }); - } - setIsCreateClaimLoading(false); - }, [consultationData.id, dispatch]); - - useEffect(() => { - fetchLatestClaim(); - }, [fetchLatestClaim]); - useMessageListener((data) => { if ( data.type === "MESSAGE" && (data.from === "claim/on_submit" || data.from === "preauth/on_submit") && data.message === "success" ) { - fetchLatestClaim(); + refetchLatestClaim(); } }); @@ -204,6 +215,10 @@ const DischargeModal = ({ const confirmationRequired = encounterDuration.asDays() >= 30; + if (initialDiagnoses == null) { + return ; + } + return ( <>
    )} - i.text == "Expired")?.id - } - label={ - { - "3": "Cause of death", - "1": "Discharged Advice", - }[discharge_reason ?? 0] ?? "Notes" - } - name="discharge_notes" - value={preDischargeForm.discharge_notes} - onChange={(e) => - setPreDischargeForm((prev) => ({ - ...prev, - discharge_notes: e.value, - })) - } - error={errors?.discharge_notes} - /> + + {discharge_reason !== + DISCHARGE_REASONS.find((i) => i.text == "Expired")?.id && ( +
    + {t("diagnosis_at_discharge")} + +
    + )} + {discharge_reason === DISCHARGE_REASONS.find((i) => i.text == "Recovered")?.id && ( <> @@ -383,13 +389,34 @@ const DischargeModal = ({ /> )}
    + i.text == "Expired")?.id + } + label={ + { + "3": "Cause of death", + "1": "Discharged Advice", + }[discharge_reason ?? 0] ?? "Notes" + } + name="discharge_notes" + value={preDischargeForm.discharge_notes} + onChange={(e) => + setPreDischargeForm((prev) => ({ + ...prev, + discharge_notes: e.value, + })) + } + error={errors?.discharge_notes} + /> {careConfig.hcx.enabled && ( // TODO: if policy and approved pre-auth exists

    Claim Insurance

    {latestClaim ? ( - + ) : ( void; disableEdit?: boolean; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; } const DoctorNote = (props: DoctorNoteProps) => { - const { state, handleNext, setReload, disableEdit } = props; + const { state, handleNext, setReload, disableEdit, setReplyTo } = props; return (
    { scrollableTarget="patient-notes-list" > {state.notes.map((note) => ( - + parentNote={note.reply_to_object} + > + + ))} ) : ( diff --git a/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx b/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx new file mode 100644 index 00000000000..470f05f2bfe --- /dev/null +++ b/src/Components/Facility/DoctorNoteReplyPreviewCard.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import { PaitentNotesReplyModel } from "./models"; +import { USER_TYPES_MAP } from "../../Common/constants"; +import { formatDateTime, relativeDate } from "../../Utils/utils"; + +interface Props { + parentNote: PaitentNotesReplyModel | undefined; + children: React.ReactNode; + cancelReply?: () => void; +} + +const DoctorNoteReplyPreviewCard = ({ + parentNote, + children, + cancelReply, +}: Props) => { + return ( +
    + {parentNote ? ( +
    +
    +
    +
    +
    + + {parentNote.created_by_object?.first_name || "Unknown"}{" "} + {parentNote.created_by_object?.last_name} + + {parentNote.user_type && ( + + {`(${USER_TYPES_MAP[parentNote.user_type]})`} + + )} +
    +
    +
    + + {formatDateTime(parentNote.created_date)} + + Created {relativeDate(parentNote.created_date, true)} +
    +
    +
    + {cancelReply && ( +
    + Cancel +
    + )} +
    +
    {parentNote.note}
    +
    +
    {children}
    +
    + ) : ( +
    {children}
    + )} +
    + ); +}; + +export default DoctorNoteReplyPreviewCard; diff --git a/src/Components/Facility/FacilityBedCapacity.tsx b/src/Components/Facility/FacilityBedCapacity.tsx index 77481fa8716..a293787afce 100644 --- a/src/Components/Facility/FacilityBedCapacity.tsx +++ b/src/Components/Facility/FacilityBedCapacity.tsx @@ -1,4 +1,5 @@ import { useState } from "react"; +import { BED_TYPES } from "../../Common/constants"; import routes from "../../Redux/api"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; import useQuery from "../../Utils/request/useQuery"; @@ -7,9 +8,11 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { BedCapacity } from "./BedCapacity"; import BedTypeCard from "./BedTypeCard"; import CareIcon from "../../CAREUI/icons/CareIcon"; -import { BED_TYPES } from "../../Common/constants"; +import { useTranslation } from "react-i18next"; export const FacilityBedCapacity = (props: any) => { + const { t } = useTranslation(); + const [bedCapacityModalOpen, setBedCapacityModalOpen] = useState(false); const capacityQuery = useQuery(routes.getCapacity, { @@ -45,7 +48,7 @@ export const FacilityBedCapacity = (props: any) => { /> {BED_TYPES.map((x) => { const res = capacityQuery.data?.results.find((data) => { - return data.room_type === x.id; + return data.room_type === x; }); if ( res && @@ -64,7 +67,7 @@ export const FacilityBedCapacity = (props: any) => { bedCapacityId={res.id} key={`bed_${res.id}`} room_type={res.room_type} - label={x.text} + label={t(`bed_type__${x}`)} used={res.current_capacity} total={res.total_capacity} lastUpdated={res.modified_date} diff --git a/src/Components/Facility/FacilityBlock.tsx b/src/Components/Facility/FacilityBlock.tsx new file mode 100644 index 00000000000..64c0a24d78d --- /dev/null +++ b/src/Components/Facility/FacilityBlock.tsx @@ -0,0 +1,34 @@ +import { Link } from "raviger"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { FacilityModel } from "./models"; + +export default function FacilityBlock(props: { facility: FacilityModel }) { + const { facility } = props; + + return ( + +
    + {facility.read_cover_image_url ? ( + + ) : ( + <> + + + )} +
    +
    + {facility.name} +

    + {facility.address} {facility.local_body_object?.name} +

    +
    + + ); +} diff --git a/src/Components/Facility/FacilityCard.tsx b/src/Components/Facility/FacilityCard.tsx index c7fbf728dc9..f20d7c5d774 100644 --- a/src/Components/Facility/FacilityCard.tsx +++ b/src/Components/Facility/FacilityCard.tsx @@ -68,12 +68,12 @@ export const FacilityCard = (props: { facility: any; userType: any }) => { )} -
    +
    -
    +
    {(facility.read_cover_image_url && ( { > {facility.name} @@ -109,6 +109,7 @@ export const FacilityCard = (props: { facility: any; userType: any }) => { href={`/facility/${facility.id}/cns`} border ghost + className="mt-2 sm:mt-0" > import("../Common/Loading")); @@ -247,7 +248,7 @@ export const FacilityCreate = (props: FacilityProps) => { }, ); - useQuery(routes.getPermittedFacility, { + const facilityQuery = useQuery(routes.getPermittedFacility, { pathParams: { id: facilityId!, }, @@ -564,7 +565,7 @@ export const FacilityCreate = (props: FacilityProps) => { /> {BED_TYPES.map((x) => { const res = capacityData.find((data) => { - return data.room_type === x.id; + return data.room_type === x; }); if (res) { const removeCurrentBedType = (bedTypeId: number | undefined) => { @@ -579,7 +580,7 @@ export const FacilityCreate = (props: FacilityProps) => { bedCapacityId={res.id} key={`bed_${res.id}`} room_type={res.room_type} - label={x.text} + label={t(`bed_type__${x}`)} used={res.current_capacity || 0} total={res.total_capacity || 0} lastUpdated={res.modified_date} @@ -850,6 +851,14 @@ export const FacilityCreate = (props: FacilityProps) => { required types={["mobile", "landline"]} /> +
    +

    {t("spokes")}

    + {facilityId && ( + + )} +
    { const { t } = useTranslation(); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [editCoverImage, setEditCoverImage] = useState(false); - const [coverImageEdited, setCoverImageEdited] = useState(false); const authUser = useAuthUser(); const { @@ -93,6 +93,13 @@ export const FacilityHome = ({ facilityId }: Props) => { }); }; + const spokesQuery = useQuery(routes.getFacilitySpokes, { + pathParams: { + id: facilityId, + }, + silent: true, + }); + if (isLoading) { return ; } @@ -123,20 +130,11 @@ export const FacilityHome = ({ facilityId }: Props) => { ); const CoverImage = () => ( - <> - {facilityData?.name} - {coverImageEdited && ( -
    - - {t("cover_image_updated_note")} - -
    - )} - + {facilityData?.name} ); return ( @@ -161,10 +159,7 @@ export const FacilityHome = ({ facilityId }: Props) => { /> { - facilityFetch(); - setCoverImageEdited(true); - }} + onSave={() => facilityFetch()} onClose={() => setEditCoverImage(false)} onDelete={() => facilityFetch()} facility={facilityData ?? ({} as FacilityModel)} @@ -289,6 +284,20 @@ export const FacilityHome = ({ facilityId }: Props) => { />
    + {!!spokesQuery.data?.results.length && ( +
    +
    +

    + {t("spokes")} +

    +
    + {spokesQuery.data?.results.map((spoke) => ( + + ))} +
    +
    +
    + )}
    diff --git a/src/Components/Facility/HospitalList.tsx b/src/Components/Facility/HospitalList.tsx index 893d54cd698..ca67fd0eb2c 100644 --- a/src/Components/Facility/HospitalList.tsx +++ b/src/Components/Facility/HospitalList.tsx @@ -1,9 +1,3 @@ -import { - downloadFacility, - downloadFacilityCapacity, - downloadFacilityDoctors, - downloadFacilityTriage, -} from "../../Redux/actions"; import { lazy, useEffect } from "react"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import CountBlock from "../../CAREUI/display/Count"; @@ -160,22 +154,22 @@ export const HospitalList = () => { exportItems={[ { label: "Facilities", - action: downloadFacility, + route: routes.downloadFacility, filePrefix: "facilities", }, { label: "Capacities", - action: downloadFacilityCapacity, + route: routes.downloadFacilityCapacity, filePrefix: "capacities", }, { label: "Doctors", - action: downloadFacilityDoctors, + route: routes.downloadFacilityDoctors, filePrefix: "doctors", }, { label: "Triages", - action: downloadFacilityTriage, + route: routes.downloadFacilityTriage, filePrefix: "triages", }, ]} diff --git a/src/Components/Facility/PatientConsultationNotesList.tsx b/src/Components/Facility/PatientConsultationNotesList.tsx index f81ef122f6c..15238ff189f 100644 --- a/src/Components/Facility/PatientConsultationNotesList.tsx +++ b/src/Components/Facility/PatientConsultationNotesList.tsx @@ -14,12 +14,21 @@ interface PatientNotesProps { setReload?: (value: boolean) => void; disableEdit?: boolean; thread: PatientNotesModel["thread"]; + setReplyTo?: (value: PatientNotesModel | undefined) => void; } const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientConsultationNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, disableEdit, thread } = props; + const { + state, + setState, + reload, + setReload, + disableEdit, + thread, + setReplyTo, + } = props; const consultationId = useSlug("consultation") ?? ""; const [isLoading, setIsLoading] = useState(true); @@ -95,6 +104,7 @@ const PatientConsultationNotesList = (props: PatientNotesProps) => { handleNext={handleNext} setReload={setReload} disableEdit={disableEdit} + setReplyTo={setReplyTo} /> ); }; diff --git a/src/Components/Facility/PatientNoteCard.tsx b/src/Components/Facility/PatientNoteCard.tsx index 7d2a8c6eb70..9ddfbc009e6 100644 --- a/src/Components/Facility/PatientNoteCard.tsx +++ b/src/Components/Facility/PatientNoteCard.tsx @@ -23,10 +23,12 @@ const PatientNoteCard = ({ note, setReload, disableEdit, + setReplyTo, }: { note: PatientNotesModel; setReload: any; disableEdit?: boolean; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; }) => { const patientId = useSlug("patient"); const [isEditing, setIsEditing] = useState(false); @@ -128,19 +130,28 @@ const PatientNoteCard = ({ ) }
    - - {!disableEdit && - note.created_by_object.id === authUser.id && - !isEditing && ( - { - setIsEditing(true); - }} - > - - - )} +
    + {!disableEdit && + note.created_by_object.id === authUser.id && + !isEditing && ( + { + setIsEditing(true); + }} + > + + + )} + { + setReplyTo && setReplyTo(note); + }} + > + + +
    {
    diff --git a/src/Components/Facility/PatientNotesList.tsx b/src/Components/Facility/PatientNotesList.tsx index 4e1d0a5a7a6..bbd037e866c 100644 --- a/src/Components/Facility/PatientNotesList.tsx +++ b/src/Components/Facility/PatientNotesList.tsx @@ -14,12 +14,13 @@ interface PatientNotesProps { reload?: boolean; setReload?: any; thread: PatientNotesModel["thread"]; + setReplyTo?: (reply_to: PatientNotesModel | undefined) => void; } const pageSize = RESULTS_PER_PAGE_LIMIT; const PatientNotesList = (props: PatientNotesProps) => { - const { state, setState, reload, setReload, thread } = props; + const { state, setState, reload, setReload, thread, setReplyTo } = props; const [isLoading, setIsLoading] = useState(true); @@ -83,7 +84,12 @@ const PatientNotesList = (props: PatientNotesProps) => { } return ( - + ); }; diff --git a/src/Components/Facility/PatientNotesSlideover.tsx b/src/Components/Facility/PatientNotesSlideover.tsx index d7847c3add7..52f99aee763 100644 --- a/src/Components/Facility/PatientNotesSlideover.tsx +++ b/src/Components/Facility/PatientNotesSlideover.tsx @@ -8,11 +8,12 @@ import { useMessageListener } from "../../Common/hooks/useMessageListener"; import PatientConsultationNotesList from "./PatientConsultationNotesList"; import request from "../../Utils/request/request"; import routes from "../../Redux/api"; -import { PatientNoteStateType } from "./models"; +import { PatientNoteStateType, PaitentNotesReplyModel } from "./models"; import useKeyboardShortcut from "use-keyboard-shortcut"; import AutoExpandingTextInputFormField from "../Form/FormFields/AutoExpandingTextInputFormField.js"; import useAuthUser from "../../Common/hooks/useAuthUser"; import { PATIENT_NOTES_THREADS } from "../../Common/constants.js"; +import DoctorNoteReplyPreviewCard from "./DoctorNoteReplyPreviewCard.js"; import useNotificationSubscriptionState from "../../Common/hooks/useNotificationSubscriptionState.js"; import { Link } from "raviger"; import { t } from "i18next"; @@ -36,6 +37,9 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { const [patientActive, setPatientActive] = useState(true); const [reload, setReload] = useState(false); const [focused, setFocused] = useState(false); + const [reply_to, setReplyTo] = useState( + undefined, + ); useEffect(() => { if (notificationSubscriptionState === "unsubscribed") { @@ -79,6 +83,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { note: noteField, consultation: consultationId, thread, + reply_to: reply_to?.id, }, }); if (res?.status === 201) { @@ -86,6 +91,7 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setNoteField(""); setState({ ...state, cPage: 1 }); setReload(true); + setReplyTo(undefined); } }; @@ -216,36 +222,42 @@ export default function PatientNotesSlideover(props: PatientNotesProps) { setReload={setReload} disableEdit={!patientActive} thread={thread} + setReplyTo={setReplyTo} /> -
    - setNoteField(e.value)} - className="w-full grow" - errorClassName="hidden" - innerClassName="pr-10" - placeholder={t("notes_placeholder")} - disabled={!patientActive} - onFocus={() => setFocused(true)} - onBlur={() => setFocused(false)} - /> - - - -
    + setReplyTo(undefined)} + > +
    + setNoteField(e.value)} + className="w-full grow" + errorClassName="hidden" + innerClassName="pr-10" + placeholder={t("notes_placeholder")} + disabled={!patientActive} + onFocus={() => setFocused(true)} + onBlur={() => setFocused(false)} + /> + + + +
    +
    )}
    diff --git a/src/Components/Facility/SpokeFacilityEditor.tsx b/src/Components/Facility/SpokeFacilityEditor.tsx new file mode 100644 index 00000000000..197d68da2bf --- /dev/null +++ b/src/Components/Facility/SpokeFacilityEditor.tsx @@ -0,0 +1,154 @@ +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; +import useQuery from "../../Utils/request/useQuery"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import { + FacilityModel, + FacilitySpokeErrors, + FacilitySpokeModel, + FacilitySpokeRequest, + SpokeRelationship, +} from "./models"; +import ModelCrudEditor from "../Form/ModelCrudEditor"; +import { FacilitySelect } from "../Common/FacilitySelect"; +import { useEffect, useState } from "react"; +import { SPOKE_RELATION_TYPES } from "../../Common/constants"; +import FacilityBlock from "./FacilityBlock"; +import { useTranslation } from "react-i18next"; + +export interface SpokeFacilityEditorProps { + facility: Omit & { id: string }; +} + +export default function SpokeFacilityEditor(props: SpokeFacilityEditorProps) { + const { facility } = props; + + const { t } = useTranslation(); + + const spokesQuery = useQuery(routes.getFacilitySpokes, { + pathParams: { + id: facility.id, + }, + }); + + const spokes = spokesQuery.data?.results; + + const createSpoke = (body: FacilitySpokeRequest) => + request(routes.createFacilitySpoke, { + body, + pathParams: { + id: facility.id, + }, + onResponse: ({ res }) => { + if (res?.ok) { + spokesQuery.refetch(); + } + }, + }); + + const deleteSpoke = (spokeFacilityId: string) => + request(routes.deleteFacilitySpoke, { + pathParams: { + id: facility.id, + spoke_id: spokeFacilityId, + }, + onResponse: ({ res }) => { + if (res?.ok) { + spokesQuery.refetch(); + } + }, + }); + + const updateSpoke = (spokeFacilityId: string, body: FacilitySpokeRequest) => + request(routes.updateFacilitySpokes, { + pathParams: { + id: facility.id, + spoke_id: spokeFacilityId, + }, + body, + onResponse: ({ res }) => { + if (res?.ok) { + spokesQuery.refetch(); + } + }, + }); + + const FormRender = ( + item: FacilitySpokeModel | FacilitySpokeRequest, + setItem: (item: FacilitySpokeModel | FacilitySpokeRequest) => void, + processing: boolean, + ) => { + const [selectedFacility, setSelectedFacility] = useState(); + + useEffect(() => { + setItem({ ...item, spoke: selectedFacility?.id }); + }, [selectedFacility]); + + return ( +
    + {"id" in item ? ( +
    + +
    + ) : ( + + v && !Array.isArray(v) && setSelectedFacility(v) + } + errors="" + className="w-full" + disabled={processing} + filter={(f) => + !!f.id && + facility.id !== f.id && + !spokes?.flatMap((s) => s.spoke_object.id).includes(f.id) + } + /> + )} + v.text} + optionValue={(v) => v.value} + value={item.relationship} + onChange={(v) => setItem({ ...item, relationship: v.value })} + errorClassName="hidden" + className="w-full shrink-0 md:w-auto" + disabled={processing} + /> +
    + ); + }; + + return ( + <> + + items={spokes} + onCreate={createSpoke} + onUpdate={updateSpoke} + onDelete={deleteSpoke} + loading={spokesQuery.loading} + errors={{}} + emptyText={"No Spokes"} + empty={{ + spoke: "", + relationship: SpokeRelationship.REGULAR, + }} + createText="Add Spoke" + allowCreate={(item) => !item.relationship || !item.spoke} + > + {FormRender} + + + ); +} diff --git a/src/Components/Facility/models.tsx b/src/Components/Facility/models.tsx index 723f0af1946..8103ebb6729 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -1,24 +1,28 @@ +import { AssetData, AssetLocationType } from "../Assets/AssetTypes"; import { CONSENT_PATIENT_CODE_STATUS_CHOICES, CONSENT_TYPE_CHOICES, ConsultationSuggestionValue, DISCHARGE_REASONS, PATIENT_NOTES_THREADS, + SHIFTING_CHOICES_PEACETIME, UserRole, } from "../../Common/constants"; -import { AssetData, AssetLocationType } from "../Assets/AssetTypes"; -import { RouteToFacility } from "../Common/RouteToFacilitySelect"; -import { InvestigationType } from "../Common/prescription-builder/InvestigationBuilder"; -import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; +import { FeatureFlag } from "../../Utils/featureFlags"; import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; import { AssignedToObjectModel, BloodPressure, DailyRoundsModel, + FacilityNameModel, FileUploadModel, + PatientModel, } from "../Patient/models"; import { EncounterSymptom } from "../Symptoms/types"; -import { UserBareMinimum } from "../Users/models"; +import { UserBareMinimum, UserModel } from "../Users/models"; +import { InvestigationType } from "../Common/prescription-builder/InvestigationBuilder"; +import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; +import { RouteToFacility } from "../Common/RouteToFacilitySelect"; export interface LocalBodyModel { id: number; @@ -79,8 +83,33 @@ export interface FacilityModel { local_body?: number; ward?: number; pincode?: string; + facility_flags?: FeatureFlag[]; + latitude?: string; + longitude?: string; + kasp_empanelled?: boolean; + patient_count?: string; + bed_count?: string; +} + +export enum SpokeRelationship { + REGULAR = 1, + TELE_ICU = 2, +} + +export interface FacilitySpokeModel { + id: string; + hub_object: FacilityNameModel; + spoke_object: FacilityNameModel; + relationship: SpokeRelationship; } +export interface FacilitySpokeRequest { + spoke?: string; + relationship?: SpokeRelationship; +} + +export interface FacilitySpokeErrors {} + export interface CapacityModal { id?: number; room_type?: number; @@ -273,8 +302,6 @@ export interface CurrentBed { meta: Record; } -// Voluntarily made as `type` for it to achieve type-safety when used with -// `useAsyncOptions` export type ICD11DiagnosisModel = { id: string; label: string; @@ -548,6 +575,14 @@ export interface PatientNotesEditModel { note: string; } +export interface PaitentNotesReplyModel { + id: string; + note: string; + user_type?: UserRole | "RemoteSpecialist"; + created_by_object: BaseUserModel; + created_date: string; +} + export interface PatientNotesModel { id: string; note: string; @@ -558,6 +593,7 @@ export interface PatientNotesModel { created_date: string; last_edited_by?: BaseUserModel; last_edited_date?: string; + reply_to_object?: PaitentNotesReplyModel; } export interface PatientNoteStateType { @@ -581,13 +617,7 @@ export type IUserFacilityRequest = { facility: string; }; -export type FacilityRequest = Omit & { - latitude?: string; - longitude?: string; - kasp_empanelled?: boolean; - patient_count?: string; - bed_count?: string; -}; +export type FacilityRequest = Omit; export type InventorySummaryResponse = { id: string; @@ -648,3 +678,50 @@ export type PatientTransferResponse = { date_of_birth: string; facility_object: BaseFacilityModel; }; + +export interface ShiftingModel { + assigned_facility: string; + assigned_facility_external: string | null; + assigned_facility_object: FacilityModel; + created_date: string; + emergency: boolean; + external_id: string; + id: string; + modified_date: string; + origin_facility_object: FacilityModel; + patient: string; + patient_object: PatientModel; + shifting_approving_facility_object: FacilityModel | null; + status: (typeof SHIFTING_CHOICES_PEACETIME)[number]["text"]; + assigned_to_object?: UserModel; +} + +export interface ResourceModel { + approving_facility: string | null; + approving_facility_object: FacilityModel | null; + assigned_facility: string | null; + assigned_facility_object: FacilityModel | null; + assigned_quantity: number; + assigned_to: string | null; + assigned_to_object: UserModel | null; + category: string; + created_by: number; + created_by_object: UserModel; + created_date: string; + emergency: boolean; + id: string; + is_assigned_to_user: boolean; + last_edited_by: number; + last_edited_by_object: UserModel; + modified_date: string; + origin_facility: string; + origin_facility_object: FacilityModel; + priority: number | null; + reason: string; + refering_facility_contact_name: string; + refering_facility_contact_number: string; + requested_quantity: number; + status: string; + sub_category: string; + title: string; +} diff --git a/src/Components/Files/AudioCaptureDialog.tsx b/src/Components/Files/AudioCaptureDialog.tsx index d686022a5b6..097de0088aa 100644 --- a/src/Components/Files/AudioCaptureDialog.tsx +++ b/src/Components/Files/AudioCaptureDialog.tsx @@ -8,7 +8,7 @@ import { t } from "i18next"; export interface AudioCaptureDialogProps { show: boolean; onHide: () => void; - onCapture: (file: File) => void; + onCapture: (file: File, fileName: string) => void; autoRecord?: boolean; } @@ -56,14 +56,11 @@ export default function AudioCaptureDialog(props: AudioCaptureDialogProps) { const handleSubmit = async () => { const response = await fetch(audioURL); const blob = await response.blob(); - const file = new File( - [blob], - `recording_${new Date().toISOString().replaceAll(".", "_").replaceAll(":", "_")}.mp3`, - { type: "audio/mpeg" }, - ); + const fileName = `recording_${new Date().toISOString().replaceAll(".", "_").replaceAll(":", "_")}.mp3`; + const file = new File([blob], fileName, { type: "audio/mpeg" }); resetRecording(); onHide(); - onCapture(file); + onCapture(file, fileName); }; useEffect(() => { diff --git a/src/Components/Files/CameraCaptureDialog.tsx b/src/Components/Files/CameraCaptureDialog.tsx index ee7d7da177c..96de547eebe 100644 --- a/src/Components/Files/CameraCaptureDialog.tsx +++ b/src/Components/Files/CameraCaptureDialog.tsx @@ -9,7 +9,7 @@ import useWindowDimensions from "../../Common/hooks/useWindowDimensions"; export interface CameraCaptureDialogProps { show: boolean; onHide: () => void; - onCapture: (file: File) => void; + onCapture: (file: File, fileName: string) => void; } export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { @@ -41,7 +41,7 @@ export default function CameraCaptureDialog(props: CameraCaptureDialogProps) { const myFile = new File([blob], `capture.${extension}`, { type: blob.type, }); - onCapture(myFile); + onCapture(myFile, `capture.${extension}`); }); }; diff --git a/src/Components/Files/FileUpload.tsx b/src/Components/Files/FileUpload.tsx index 2e3d6ea58b4..1701a632472 100644 --- a/src/Components/Files/FileUpload.tsx +++ b/src/Components/Files/FileUpload.tsx @@ -195,6 +195,7 @@ export const FileUpload = (props: FileUploadProps) => { "ods", "pdf", ], + allowNameFallback: false, onUpload: refetchAll, }); @@ -250,16 +251,16 @@ export const FileUpload = (props: FileUploadProps) => { isAuthorized ? ( <>

    {UPLOAD_HEADING[type]}

    - {fileUpload.file ? ( + {fileUpload.files[0] ? (
    - {fileUpload.file.name} + {fileUpload.files[0].name}
    )}
    - {(fileQuery?.data?.results || []).length > RESULTS_PER_PAGE_LIMIT && ( + {(fileQuery?.data?.count ?? 0) > RESULTS_PER_PAGE_LIMIT && (
    diff --git a/src/Components/Form/AutoCompleteAsync.tsx b/src/Components/Form/AutoCompleteAsync.tsx index 1cd07a93df1..f362918dfc2 100644 --- a/src/Components/Form/AutoCompleteAsync.tsx +++ b/src/Components/Form/AutoCompleteAsync.tsx @@ -36,6 +36,7 @@ interface Props { required?: boolean; onBlur?: () => void; onFocus?: () => void; + filter?: (data: any) => boolean; } const AutoCompleteAsync = (props: Props) => { @@ -56,6 +57,7 @@ const AutoCompleteAsync = (props: Props) => { disabled = false, required = false, error, + filter, } = props; const [data, setData] = useState([]); const [query, setQuery] = useState(""); @@ -69,7 +71,9 @@ const AutoCompleteAsync = (props: Props) => { () => debounce(async (query: string) => { setLoading(true); - const data = (await fetchData(query)) || []; + const data = ((await fetchData(query)) || [])?.filter((d: any) => + filter ? filter(d) : true, + ); if (showNOptions !== undefined) { setData(data.slice(0, showNOptions)); @@ -93,6 +97,7 @@ const AutoCompleteAsync = (props: Props) => { onChange={onChange} by={compareBy} multiple={multiple as any} + immediate >
    diff --git a/src/Components/Form/FormFields/Autocomplete.tsx b/src/Components/Form/FormFields/Autocomplete.tsx index f1c52853e37..a829c3373d2 100644 --- a/src/Components/Form/FormFields/Autocomplete.tsx +++ b/src/Components/Form/FormFields/Autocomplete.tsx @@ -171,6 +171,7 @@ export const Autocomplete = (props: AutocompleteProps) => {
    value?.label || ""} diff --git a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx index a727db8b749..d8bde2dba03 100644 --- a/src/Components/Form/FormFields/AutocompleteMultiselect.tsx +++ b/src/Components/Form/FormFields/AutocompleteMultiselect.tsx @@ -110,6 +110,7 @@ export const AutocompleteMutliSelect = ( return (
    { + onCreate?: (req: TReq) => Promise; + onUpdate?: (itemId: string, req: TReq) => Promise; + onDelete?: (itemId: string) => Promise; + items?: TRes[]; + children: ( + item: TRes | TReq, + setItem: (item: TRes | TReq) => void, + processing: boolean, + errors?: TErr, + ) => React.ReactNode; + loading: boolean; + errors: TErr; + emptyText?: React.ReactNode; + empty: TReq; + createText?: React.ReactNode; + allowCreate?: (item: TReq) => boolean; +} + +export default function ModelCrudEditor( + props: ModelCrudEditorProps, +) { + const { t } = useTranslation(); + + const { + onCreate, + onUpdate, + onDelete, + items, + children, + loading, + errors, + emptyText, + empty, + createText, + allowCreate, + } = props; + const [creating, setCreating] = useState(false); + const [updating, setUpdating] = useState(null); + + const handleUpdate = async (itemId: string, item: TReq) => { + if (!onUpdate) return; + setUpdating(itemId); + await onUpdate(itemId, item); + setUpdating(null); + }; + + const handleDelete = async (itemId: string) => { + if (!onDelete) return; + setUpdating(itemId); + await onDelete(itemId); + setUpdating(null); + }; + + const handleCreate = async (item: TReq) => { + if (!onCreate) return; + setCreating(true); + await onCreate(item); + setCreating(false); + }; + + type FormProps = + | { + type: "creating"; + item: TReq; + } + | { + type: "updating"; + item: TRes; + }; + + const Form = (props: FormProps) => { + const [item, setItem] = useState(props.item); + const processing = + props.type === "creating" ? creating : props.item.id === updating; + + useEffect(() => { + if ( + props.type === "updating" && + JSON.stringify(item) !== JSON.stringify(props.item) + ) { + const timeout = setTimeout(() => { + handleUpdate(props.item.id, item as TReq); + }, 1000); + return () => clearTimeout(timeout); + } + }, [item]); + + return ( +
    + {children(item, setItem, processing, errors)} + {props.type === "creating" && ( + handleCreate(item as TReq)} + > + {createText || "Create"} + + )} + {props.type === "updating" && onDelete && ( + + )} +
    + ); + }; + + return ( +
    +
      + {items?.map((item, i) => ( +
    • + +
    • + ))} + + {items?.length === 0 && ( +
      + {emptyText} +
      + )} +
    +
    + +
    +
    + ); +} diff --git a/src/Components/HCX/ClaimCard.tsx b/src/Components/HCX/ClaimCard.tsx new file mode 100644 index 00000000000..b45e7b3d503 --- /dev/null +++ b/src/Components/HCX/ClaimCard.tsx @@ -0,0 +1,55 @@ +import { useLayoutEffect, useRef, useState } from "react"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ClaimCardCommunication from "./ClaimCardCommunication"; +import ClaimCardInfo from "./ClaimCardInfo"; +import { HCXClaimModel } from "./models"; + +interface IProps { + claim: HCXClaimModel; +} + +export default function ClaimCard({ claim }: IProps) { + const [showMessages, setShowMessages] = useState(false); + const [containerDimensions, setContainerDimensions] = useState({ + width: 0, + height: 0, + }); + const cardContainerRef = useRef(null); + + useLayoutEffect(() => { + if (cardContainerRef.current) { + setContainerDimensions({ + width: cardContainerRef.current.offsetWidth, + height: cardContainerRef.current.offsetHeight, + }); + } + }, [cardContainerRef]); + + return ( + <> +
    + setShowMessages((prev) => !prev)} + /> +
    + {showMessages ? ( +
    + +
    + ) : ( +
    + +
    + )} + + ); +} diff --git a/src/Components/HCX/ClaimCardCommunication.tsx b/src/Components/HCX/ClaimCardCommunication.tsx new file mode 100644 index 00000000000..ff15ad7bb66 --- /dev/null +++ b/src/Components/HCX/ClaimCardCommunication.tsx @@ -0,0 +1,300 @@ +import * as Notification from "../../Utils/Notifications"; + +import { HCXClaimModel, HCXCommunicationModel } from "./models"; + +import ButtonV2 from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import { FileUploadModel } from "../Patient/models"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { classNames } from "../../Utils/utils"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useFileUpload from "../../Utils/useFileUpload"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; + +interface IProps { + claim: HCXClaimModel; +} + +export default function ClaimCardCommunication({ claim }: IProps) { + const { t } = useTranslation(); + const [inputText, setInputText] = useState(""); + const [isSendingCommunication, setIsSendingCommunication] = useState(false); + + const { + Input, + files, + error, + removeFile, + clearFiles, + handleFileUpload, + validateFiles, + } = useFileUpload({ + multiple: true, + type: "COMMUNICATION", + allowedExtensions: [".pdf", ".jpg", ".jpeg", ".png"], + }); + + const { data: communicationsResult, refetch: refetchCommunications } = + useQuery(routes.hcx.communications.list, { + query: { + claim: claim.id, + ordering: "-created_date", + }, + }); + + const handleSubmit = async () => { + if (!claim.id) return; + + if (!validateFiles()) return; + + setIsSendingCommunication(true); + + const { res, data } = await request(routes.hcx.communications.create, { + body: { + claim: claim.id, + content: [ + { + type: "text", + data: inputText, + }, + ], + }, + }); + + if (res?.status === 201 && data) { + await handleFileUpload(data.id as string); + + const { res } = await request(routes.hcx.communications.send, { + body: { + communication: data.id, + }, + }); + + if (res?.ok) { + Notification.Success({ msg: t("communication__sent_to_hcx") }); + + await refetchCommunications(); + + setInputText(""); + clearFiles(); + } + } + + setIsSendingCommunication(false); + }; + + return ( +
    + + +
    +
    +
    + {files.map((file, i) => ( +
    +
    + {file.type.includes("image") ? ( + {file.name} + ) : ( +
    + +
    + )} +
    +
    +

    {file.name}

    +
    +

    + {(file.size / 1024).toFixed(2)} KB +

    + +
    +
    +
    + ))} +
    + setInputText(e.value)} + placeholder={t("enter_message")} + rows={1} + className="-mb-3 flex-1" + /> +
    +
    + +
    + + {t("send_message")} + +
    + {error && ( +

    {error}

    + )} +
    + ); +} + +interface ICommunicationChatInterfaceProps { + communications: HCXCommunicationModel[]; +} + +function CommunicationChatInterface({ + communications, +}: ICommunicationChatInterfaceProps) { + return ( +
    + {communications?.map((communication) => ( + + ))} +
    + ); +} + +interface ICommunicationChatMessageProps { + communication: HCXCommunicationModel; +} + +function CommunicationChatMessage({ + communication, +}: ICommunicationChatMessageProps) { + const { t } = useTranslation(); + const [attachments, setAttachments] = useState( + null, + ); + const [isFetchingAttachments, setIsFetchingAttachments] = useState(false); + const [isDownloadingAttachment, setIsDownloadingAttachment] = useState(false); + + return ( +
    + {communication.content?.map((message) => ( +

    + {message.data} +

    + ))} + {attachments ? ( +
    + {attachments.length === 0 ? ( +

    + {t("no_attachments_found")} +

    + ) : ( + attachments.map((attachment) => ( +
    +
    +
    + +
    +
    +
    +

    {attachment.name}

    + +
    +
    + )) + )} +
    + ) : ( + + )} +
    + ); +} diff --git a/src/Components/HCX/ClaimDetailCard.tsx b/src/Components/HCX/ClaimCardInfo.tsx similarity index 67% rename from src/Components/HCX/ClaimDetailCard.tsx rename to src/Components/HCX/ClaimCardInfo.tsx index ee6671cc90a..1b348a28f4f 100644 --- a/src/Components/HCX/ClaimDetailCard.tsx +++ b/src/Components/HCX/ClaimCardInfo.tsx @@ -1,35 +1,45 @@ import { classNames, formatCurrency, formatDateTime } from "../../Utils/utils"; -import { HCXClaimModel } from "../HCX/models"; + +import { HCXClaimModel } from "./models"; +import { useTranslation } from "react-i18next"; interface IProps { claim: HCXClaimModel; } -export default function ClaimDetailCard({ claim }: IProps) { +const claimStatus = { + PENDING: "pending", + APPROVED: "approved", + REJECTED: "rejected", +}; + +export default function ClaimCardInfo({ claim }: IProps) { + const { t } = useTranslation(); + const status = - claim.outcome === "Processing Complete" + claim.outcome === "Complete" ? claim.error_text - ? "Rejected" - : "Approved" - : "Pending"; + ? claimStatus.REJECTED + : claimStatus.APPROVED + : claimStatus.PENDING; return ( -
    -
    + <> +

    #{claim.id?.slice(0, 5)}

    - Created on{" "} -

    -
    +
    {claim.use && ( {claim.use} @@ -38,39 +48,45 @@ export default function ClaimDetailCard({ claim }: IProps) { - {status} + {t(`claim__status__${status}`)}
    -
    +

    {claim.policy_object?.policy_id || "NA"}

    -

    Policy ID

    +

    {t("policy__policy_id")}

    {claim.policy_object?.subscriber_id || "NA"}

    -

    Subscriber ID

    +

    + {t("policy__subscriber_id")} +

    - {claim.policy_object?.insurer_id || "NA"} + {claim.policy_object?.insurer_id?.split("@").shift() || "NA"}

    -

    Insurer ID

    +

    + {t("policy__insurer_id")} +

    {claim.policy_object?.insurer_name || "NA"}

    -

    Insurer Name

    +

    + {t("policy__insurer_name")} +

    @@ -81,7 +97,7 @@ export default function ClaimDetailCard({ claim }: IProps) { scope="col" className="py-3.5 pl-6 pr-3 text-left text-sm font-semibold text-secondary-900 sm:pl-0" > - Items + {t("claim__items")} @@ -89,7 +105,7 @@ export default function ClaimDetailCard({ claim }: IProps) { scope="col" className="py-3.5 pl-3 pr-6 text-right text-sm font-semibold text-secondary-900 sm:pr-0" > - Price + {t("claim__item__price")} @@ -115,15 +131,9 @@ export default function ClaimDetailCard({ claim }: IProps) { - Total Claim Amount - - - Total Claim Amount + {t("claim__total_claim_amount")} {claim.total_claim_amount && @@ -135,15 +145,9 @@ export default function ClaimDetailCard({ claim }: IProps) { - Total Amount Approved - - - Total Amount Approved + {t("claim__total_approved_amount")} {claim.total_amount_approved @@ -159,6 +163,6 @@ export default function ClaimDetailCard({ claim }: IProps) { {claim.error_text}
    )} -
    + ); } diff --git a/src/Components/HCX/ClaimCreatedModal.tsx b/src/Components/HCX/ClaimCreatedModal.tsx index 6565c0c11be..41d58aa7df4 100644 --- a/src/Components/HCX/ClaimCreatedModal.tsx +++ b/src/Components/HCX/ClaimCreatedModal.tsx @@ -1,12 +1,14 @@ -import { useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXActions } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; -import { Submit } from "../Common/components/ButtonV2"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; import DialogModal from "../Common/Dialog"; import { FileUpload } from "../Files/FileUpload"; import { HCXClaimModel } from "./models"; +import { Submit } from "../Common/components/ButtonV2"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface Props { claim: HCXClaimModel; @@ -15,7 +17,8 @@ interface Props { } export default function ClaimCreatedModal({ claim, ...props }: Props) { - const dispatch = useDispatch(); + const { t } = useTranslation(); + const [isMakingClaim, setIsMakingClaim] = useState(false); const { use } = claim; @@ -23,8 +26,11 @@ export default function ClaimCreatedModal({ claim, ...props }: Props) { const handleSubmit = async () => { setIsMakingClaim(true); - const res = await dispatch(HCXActions.makeClaim(claim.id ?? "")); - if (res.data) { + const { res } = await request(routes.hcx.claims.makeClaim, { + body: { claim: claim.id }, + }); + + if (res?.ok) { Notification.Success({ msg: `${use} requested` }); props.onClose(); } @@ -35,8 +41,8 @@ export default function ClaimCreatedModal({ claim, ...props }: Props) { @@ -44,8 +50,8 @@ export default function ClaimCreatedModal({ claim, ...props }: Props) { )} {isMakingClaim - ? `Requesting ${use === "Claim" ? "Claim" : "Pre-Authorization"}...` - : `Request ${use === "Claim" ? "Claim" : "Pre-Authorization"}`} + ? t("claim__requesting_claim") + : t("claim__request_claim")} } > diff --git a/src/Components/HCX/ClaimsItemsBuilder.tsx b/src/Components/HCX/ClaimsItemsBuilder.tsx index 73ca2305030..d636eb1a528 100644 --- a/src/Components/HCX/ClaimsItemsBuilder.tsx +++ b/src/Components/HCX/ClaimsItemsBuilder.tsx @@ -1,20 +1,24 @@ -import CareIcon from "../../CAREUI/icons/CareIcon"; -import ButtonV2 from "../Common/components/ButtonV2"; -import PMJAYProcedurePackageAutocomplete from "../Common/PMJAYProcedurePackageAutocomplete"; -import AutocompleteFormField from "../Form/FormFields/Autocomplete"; -import FormField, { FieldLabel } from "../Form/FormFields/FormField"; -import TextFormField from "../Form/FormFields/TextFormField"; import { FieldChangeEvent, FormFieldBaseProps, useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; -import { ITEM_CATEGORIES } from "./constants"; +import FormField, { FieldLabel } from "../Form/FormFields/FormField"; + +import AutocompleteFormField from "../Form/FormFields/Autocomplete"; +import ButtonV2 from "../Common/components/ButtonV2"; +import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXItemModel } from "./models"; +import { ITEM_CATEGORIES } from "./constants"; +import PMJAYProcedurePackageAutocomplete from "../Common/PMJAYProcedurePackageAutocomplete"; +import TextFormField from "../Form/FormFields/TextFormField"; +import { useTranslation } from "react-i18next"; type Props = FormFieldBaseProps; export default function ClaimsItemsBuilder(props: Props) { + const { t } = useTranslation(); + const field = useFormFieldPropsResolver(props); const handleUpdate = (index: number) => { @@ -59,7 +63,7 @@ export default function ClaimsItemsBuilder(props: Props) { >
    - Item {index + 1} + {t("claim__item")} {index + 1} {!props.disabled && ( - Delete + {t("remove")} )}
    -
    +
    o.display} optionValue={(o) => o.code} @@ -89,71 +93,66 @@ export default function ClaimsItemsBuilder(props: Props) { disabled={props.disabled} errorClassName="hidden" /> - {obj.category === "HBP" && !obj.id ? ( - <> - - - ) : ( - <> - o.code} - // optionDescription={(o) => o.name || ""} - // optionValue={(o) => o.code} - onChange={handleUpdate(index)} - value={obj.id} - disabled={props.disabled} - errorClassName="hidden" - /> - o.name || o.code} - // optionDescription={(o) => o.code} - // optionValue={(o) => o.name || o.code} - disabled={props.disabled} - errorClassName="hidden" - // options={PROCEDURES} - /> - - handleUpdate(index)({ - name: event.name, - value: parseFloat(event.value), - }) - } - disabled={props.disabled} - errorClassName="hidden" - /> - - )} + +
    + {obj.category === "HBP" && !obj.id ? ( + <> + + + ) : ( + <> + + + + handleUpdate(index)({ + name: event.name, + value: parseFloat(event.value), + }) + } + disabled={props.disabled} + errorClassName="hidden" + /> + + )} +
    ); diff --git a/src/Components/HCX/CreateClaimCard.tsx b/src/Components/HCX/CreateClaimCard.tsx index 46a02cf7f86..b4701aadf7a 100644 --- a/src/Components/HCX/CreateClaimCard.tsx +++ b/src/Components/HCX/CreateClaimCard.tsx @@ -1,18 +1,22 @@ -import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { getConsultation, HCXActions } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; -import { classNames, formatCurrency } from "../../Utils/utils"; + import ButtonV2, { Submit } from "../Common/components/ButtonV2"; +import { HCXClaimModel, HCXItemModel, HCXPolicyModel } from "./models"; +import { classNames, formatCurrency } from "../../Utils/utils"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; +import ClaimCreatedModal from "./ClaimCreatedModal"; import ClaimsItemsBuilder from "./ClaimsItemsBuilder"; -import { HCXClaimModel, HCXPolicyModel, HCXItemModel } from "./models"; -import HCXPolicyEligibilityCheck from "./PolicyEligibilityCheck"; import DialogModal from "../Common/Dialog"; +import HCXPolicyEligibilityCheck from "./PolicyEligibilityCheck"; import PatientInsuranceDetailsEditor from "./PatientInsuranceDetailsEditor"; -import ClaimCreatedModal from "./ClaimCreatedModal"; import { ProcedureType } from "../Common/prescription-builder/ProcedureBuilder"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface Props { consultationId: string; @@ -29,7 +33,8 @@ export default function CreateClaimCard({ isCreating, use = "preauthorization", }: Props) { - const dispatch = useDispatch(); + const { t } = useTranslation(); + const [showAddPolicy, setShowAddPolicy] = useState(false); const [policy, setPolicy] = useState(); const [items, setItems] = useState(); @@ -37,60 +42,66 @@ export default function CreateClaimCard({ const [createdClaim, setCreatedClaim] = useState(); const [use_, setUse_] = useState(use); - useEffect(() => { - async function autoFill() { - const latestApprovedPreAuthsRes = await dispatch( - HCXActions.preauths.list(consultationId), - ); + const { res: consultationRes, data: consultationData } = useQuery( + routes.getConsultation, + { pathParams: { id: consultationId }, prefetch: !!consultationId }, + ); - if (latestApprovedPreAuthsRes.data?.results?.length) { - // TODO: offload outcome filter to server side once payer server is back - const latestApprovedPreAuth = ( - latestApprovedPreAuthsRes.data.results as HCXClaimModel[] - ).find((o) => o.outcome === "Processing Complete"); - if (latestApprovedPreAuth) { - setPolicy(latestApprovedPreAuth.policy_object); - setItems(latestApprovedPreAuth.items ?? []); - return; - } - } - - const res = await dispatch(getConsultation(consultationId as any)); - - if (res.data && Array.isArray(res.data.procedure)) { - setItems( - res.data.procedure.map((obj: ProcedureType) => { - return { - id: obj.procedure, - name: obj.procedure, - price: 0.0, - category: "900000", // provider's packages - }; - }), - ); - } else { - setItems([]); - } + const autoFill = async (policy?: HCXPolicyModel) => { + if (!policy) { + setItems([]); + return; } - autoFill(); - }, [consultationId, dispatch]); + const { res, data: latestApprovedPreAuth } = await request( + routes.hcx.claims.list, + { + query: { + consultation: consultationId, + policy: policy.id, + ordering: "-modified_date", + use: "preauthorization", + outcome: "complete", + limit: 1, + }, + }, + ); + + if (res?.ok && latestApprovedPreAuth?.results.length !== 0) { + setItems(latestApprovedPreAuth?.results[0].items ?? []); + return; + } + if (consultationRes?.ok && Array.isArray(consultationData?.procedure)) { + setItems( + consultationData.procedure.map((obj: ProcedureType) => { + return { + id: obj.procedure ?? "", + name: obj.procedure ?? "", + price: 0.0, + category: "900000", // provider's packages + }; + }), + ); + } else { + setItems([]); + } + }; const validate = () => { if (!policy) { - Notification.Error({ msg: "Please select a policy" }); + Notification.Error({ msg: t("select_policy") }); return false; } - if (policy?.outcome !== "Processing Complete") { - Notification.Error({ msg: "Please select an eligible policy" }); + if (policy?.outcome !== "Complete") { + Notification.Error({ msg: t("select_eligible_policy") }); return false; } if (!items || items.length === 0) { - setItemsError("Please add at least one item"); + setItemsError(t("claim__item__add_at_least_one")); return false; } if (items?.some((p) => !p.id || !p.name || p.price === 0 || !p.category)) { - setItemsError("Please fill all the item details"); + setItemsError(t("claim__item__fill_all_details")); return false; } @@ -102,22 +113,23 @@ export default function CreateClaimCard({ setIsCreating(true); - const res = await dispatch( - HCXActions.claims.create({ + const { res, data } = await request(routes.hcx.claims.create, { + body: { policy: policy?.id, items, consultation: consultationId, - use, - }), - ); + use: use_, + }, + silent: true, + }); - if (res.data) { + if (res?.ok && data) { setItems([]); setItemsError(undefined); setPolicy(undefined); - setCreatedClaim(res.data); + setCreatedClaim(data); } else { - Notification.Error({ msg: "Failed to create pre-authorization" }); + Notification.Error({ msg: t(`claim__failed_to_create_${use_}`) }); } setIsCreating(false); @@ -133,10 +145,10 @@ export default function CreateClaimCard({ /> )} setShowAddPolicy(false)} - description="Add or edit patient's insurance details" + description={t("edit_policy_description")} className="w-full max-w-screen-md" > setShowAddPolicy(false)} /> + {/* Check Insurance Policy Eligibility */}
    -
    -

    - Check Insurance Policy Eligibility -

    - setShowAddPolicy(true)} ghost border> +
    +

    {t("check_policy_eligibility")}

    + setShowAddPolicy(true)} + ghost + border + > - Edit Patient Insurance Details + {t("edit_policy")}
    { + setPolicy(policy); + autoFill(policy); + }} />
    {/* Procedures */}
    -

    Items

    +

    {t("claim__items")}

    - Add Item + {t("claim__add_item")}
    - Select a policy to add items + {t("select_policy_to_add_items")} setItems(value)} error={itemsError} /> -
    - {"Total Amount: "} +
    + {t("total_amount")} :{" "} {items ? ( {formatCurrency( @@ -208,29 +227,34 @@ export default function CreateClaimCard({
    -
    +
    setUse_(value)} position="below" + className="w-52 max-sm:w-full" optionLabel={(value) => value.label} optionValue={(value) => value.id as "preauthorization" | "claim"} /> {isCreating && } {isCreating - ? `Creating ${use === "claim" ? "Claim" : "Pre-Authorization"}...` - : "Proceed"} + ? t(`claim__creating_${use_}`) + : t(`claim__create_${use_}`)}
    diff --git a/src/Components/HCX/InsuranceDetailsBuilder.tsx b/src/Components/HCX/InsuranceDetailsBuilder.tsx index 2c51d3b90d3..834e3a66953 100644 --- a/src/Components/HCX/InsuranceDetailsBuilder.tsx +++ b/src/Components/HCX/InsuranceDetailsBuilder.tsx @@ -4,21 +4,24 @@ import { useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; import FormField, { FieldLabel } from "../Form/FormFields/FormField"; -import { HCXPolicyModel } from "./models"; + import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import { HCXPolicyModel } from "./models"; +import InsurerAutocomplete from "./InsurerAutocomplete"; import TextFormField from "../Form/FormFields/TextFormField"; -import { useDispatch } from "react-redux"; -import { HCXActions } from "../../Redux/actions"; import { classNames } from "../../Utils/utils"; -import InsurerAutocomplete from "./InsurerAutocomplete"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { useTranslation } from "react-i18next"; import careConfig from "@careConfig"; type Props = FormFieldBaseProps & { gridView?: boolean }; export default function InsuranceDetailsBuilder(props: Props) { + const { t } = useTranslation(); + const field = useFormFieldPropsResolver(props); - const dispatch = useDispatch(); const handleUpdate = (index: number) => { return (event: FieldChangeEvent) => { @@ -41,15 +44,25 @@ export default function InsuranceDetailsBuilder(props: Props) { }; const handleRemove = (index: number) => { - return () => { - field.handleChange( - (props.value || [])?.filter((obj, i) => { - if (obj.id && i === index) { - dispatch(HCXActions.policies.delete(obj.id)); - } - return i !== index; - }), - ); + return async () => { + const updatedPolicies = [...(props.value || [])]; + const policyToRemove = updatedPolicies[index]; + + if (policyToRemove?.id) { + try { + await request(routes.hcx.policies.delete, { + pathParams: { external_id: policyToRemove.id }, + }); + + updatedPolicies.splice(index, 1); + field.handleChange(updatedPolicies); + } catch (error) { + console.error("Failed to delete the policy", error); + } + } else { + updatedPolicies.splice(index, 1); + field.handleChange(updatedPolicies); + } }; }; @@ -58,7 +71,7 @@ export default function InsuranceDetailsBuilder(props: Props) {
      {props.value?.length === 0 && ( - No insurance details added + {t("no_policy_added")} )} {props.value?.map((policy, index) => ( @@ -93,6 +106,7 @@ const InsuranceDetailEditCard = ({ handleRemove: () => void; gridView?: boolean; }) => { + const { t } = useTranslation(); const seletedInsurer = policy.insurer_id && policy.insurer_name ? { code: policy.insurer_id, name: policy.insurer_name } @@ -101,9 +115,9 @@ const InsuranceDetailEditCard = ({ return (
      - Policy + {t("policy")} - Delete + {t("remove")}
      @@ -119,25 +133,25 @@ const InsuranceDetailEditCard = ({ {careConfig.hcx.enabled ? ( handleUpdates({ @@ -150,15 +164,15 @@ const InsuranceDetailEditCard = ({ <> diff --git a/src/Components/HCX/InsurerAutocomplete.tsx b/src/Components/HCX/InsurerAutocomplete.tsx index b673b37feab..1105642629f 100644 --- a/src/Components/HCX/InsurerAutocomplete.tsx +++ b/src/Components/HCX/InsurerAutocomplete.tsx @@ -1,12 +1,15 @@ -import { useAsyncOptions } from "../../Common/hooks/useAsyncOptions"; -import { HCXActions } from "../../Redux/actions"; -import { Autocomplete } from "../Form/FormFields/Autocomplete"; -import FormField from "../Form/FormFields/FormField"; import { FormFieldBaseProps, useFormFieldPropsResolver, } from "../Form/FormFields/Utils"; +import { Autocomplete } from "../Form/FormFields/Autocomplete"; +import FormField from "../Form/FormFields/FormField"; +import routes from "../../Redux/api"; +import { mergeQueryOptions } from "../../Utils/utils"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; + export type InsurerOptionModel = { name: string; code: string; @@ -18,8 +21,12 @@ type Props = FormFieldBaseProps & { export default function InsurerAutocomplete(props: Props) { const field = useFormFieldPropsResolver(props); - const { fetchOptions, isLoading, options } = - useAsyncOptions("code"); + + const [query, setQuery] = useState(""); + + const { data, loading } = useQuery(routes.hcx.policies.listPayors, { + query: { query, limit: 10 }, + }); return ( @@ -31,12 +38,16 @@ export default function InsurerAutocomplete(props: Props) { placeholder={props.placeholder} value={field.value} onChange={field.handleChange} - options={options(props.value && [props.value])} + options={mergeQueryOptions( + field.value ? [field.value] : [], + data ?? [], + (obj) => obj.code, + )} optionLabel={(option) => option.name} optionDescription={(option) => option.code} optionValue={(option) => option} - onQuery={(query) => fetchOptions(HCXActions.payors.list(query))} - isLoading={isLoading} + onQuery={setQuery} + isLoading={loading} /> ); diff --git a/src/Components/HCX/PatientInsuranceDetailsEditor.tsx b/src/Components/HCX/PatientInsuranceDetailsEditor.tsx index 2515c98c53e..78de8ef201d 100644 --- a/src/Components/HCX/PatientInsuranceDetailsEditor.tsx +++ b/src/Components/HCX/PatientInsuranceDetailsEditor.tsx @@ -1,12 +1,14 @@ -import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXActions } from "../../Redux/actions"; -import * as Notifications from "../../Utils/Notifications"; import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; -import InsuranceDetailsBuilder from "./InsuranceDetailsBuilder"; + +import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXPolicyModel } from "./models"; import HCXPolicyValidator from "./validators"; +import InsuranceDetailsBuilder from "./InsuranceDetailsBuilder"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; interface Props { patient: string; @@ -19,34 +21,24 @@ export default function PatientInsuranceDetailsEditor({ onSubmitted, onCancel, }: Props) { - const dispatch = useDispatch(); - const [insuranceDetails, setInsuranceDetails] = useState(); + const { t } = useTranslation(); + + const [insuranceDetails, setInsuranceDetails] = useState( + [], + ); const [insuranceDetailsError, setInsuranceDetailsError] = useState(); const [isUpdating, setIsUpdating] = useState(false); - useEffect(() => { - const fetchPatientInsuranceDetails = async () => { - const res = await dispatch(HCXActions.policies.list({ patient })); - if (res && res.data) { + useQuery(routes.hcx.policies.list, { + query: { patient }, + onResponse(res) { + if (res?.res?.ok && res.data) { if (res.data.results.length) { setInsuranceDetails(res.data.results); - } else { - setInsuranceDetails([ - { - subscriber_id: "", - policy_id: "", - insurer_id: "", - insurer_name: "", - }, - ]); } - } else { - Notifications.Error({ msg: "Something went wrong " }); } - }; - - fetchPatientInsuranceDetails(); - }, [dispatch, patient]); + }, + }); const handleSubmit = async () => { // Validate @@ -62,22 +54,19 @@ export default function PatientInsuranceDetailsEditor({ await Promise.all( insuranceDetails.map(async (obj) => { const policy: HCXPolicyModel = { ...obj, patient }; - const policyRes = await (policy.id - ? dispatch(HCXActions.policies.update(policy.id, policy)) - : dispatch(HCXActions.policies.create(policy))); - - const eligibilityCheckRes = await dispatch( - HCXActions.checkEligibility(policyRes.data.id), - ); - if (eligibilityCheckRes.status === 200) { - Notifications.Success({ msg: "Checking Policy Eligibility..." }); - } else { - Notifications.Error({ msg: "Something Went Wrong..." }); - } + policy.id + ? await request(routes.hcx.policies.update, { + pathParams: { external_id: policy.id }, + body: policy, + }) + : await request(routes.hcx.policies.create, { + body: policy, + }); }), ); setIsUpdating(false); onSubmitted?.(); + onCancel?.(); }; return ( @@ -112,7 +101,7 @@ export default function PatientInsuranceDetailsEditor({ } > - Add Insurance Details + {t("add_policy")}
      @@ -120,10 +109,10 @@ export default function PatientInsuranceDetailsEditor({ {isUpdating ? ( <> - Updating... + {t("updating")} ) : ( - "Update" + {t("update")} )}
      diff --git a/src/Components/HCX/PolicyEligibilityCheck.tsx b/src/Components/HCX/PolicyEligibilityCheck.tsx index 883e771ba18..1d951bd0638 100644 --- a/src/Components/HCX/PolicyEligibilityCheck.tsx +++ b/src/Components/HCX/PolicyEligibilityCheck.tsx @@ -1,12 +1,16 @@ -import { useCallback, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import { HCXActions } from "../../Redux/actions"; +import * as Notification from "../../Utils/Notifications.js"; + +import { useEffect, useState } from "react"; + import ButtonV2 from "../Common/components/ButtonV2"; -import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import CareIcon from "../../CAREUI/icons/CareIcon"; import { HCXPolicyModel } from "./models"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import request from "../../Utils/request/request.js"; +import routes from "../../Redux/api"; import { useMessageListener } from "../../Common/hooks/useMessageListener"; -import * as Notification from "../../Utils/Notifications.js"; +import useQuery from "../../Utils/request/useQuery"; +import { useTranslation } from "react-i18next"; interface Props { className?: string; @@ -19,128 +23,107 @@ export default function HCXPolicyEligibilityCheck({ patient, onEligiblePolicySelected, }: Props) { - const dispatch = useDispatch(); - const [insuranceDetails, setInsuranceDetails] = useState(); - const [policy, setPolicy] = useState(); - const [eligibility, setEligibility] = useState< - Record - >({}); - const [isChecking, setIsChecking] = useState(false); - - const fetchPatientInsuranceDetails = useCallback(async () => { - setInsuranceDetails(undefined); - setEligibility({}); - - const res = await dispatch(HCXActions.policies.list({ patient })); - - if (res.data?.results) { - const results = res.data.results as HCXPolicyModel[]; - setInsuranceDetails(results); - setEligibility( - results.reduce?.((acc: any, policy: HCXPolicyModel) => { - if (policy.outcome) - acc[policy.id ?? ""] = - !policy.error_text && policy.outcome === "Processing Complete"; - return acc; - }, {}), - ); - setIsChecking(false); - } - }, [patient, dispatch]); + const { t } = useTranslation(); - useEffect(() => { - fetchPatientInsuranceDetails(); - }, [fetchPatientInsuranceDetails]); + const [selectedPolicy, setSelectedPolicy] = useState(); + const [isCheckingEligibility, setIsCheckingEligibility] = useState(false); + + const { + refetch, + data: policiesResponse, + loading, + } = useQuery(routes.hcx.policies.list, { query: { patient } }); useMessageListener((data) => { if ( data.type === "MESSAGE" && data.from === "coverageelegibility/on_check" ) { - fetchPatientInsuranceDetails(); + refetch(); } }); useEffect(() => { - if (policy && eligibility[policy]) { - const eligiblePolicy = insuranceDetails?.find((p) => p.id === policy); - onEligiblePolicySelected(eligiblePolicy); - } else { - onEligiblePolicySelected(undefined); - } - }, [policy, insuranceDetails, eligibility, onEligiblePolicySelected]); + onEligiblePolicySelected( + isPolicyEligible(selectedPolicy) ? selectedPolicy : undefined, + ); + }, [selectedPolicy]); // eslint-disable-line react-hooks/exhaustive-deps const checkEligibility = async () => { - if (!policy) return; + if (!selectedPolicy || isPolicyEligible()) return; - // Skip checking eligibility if we already know the policy is eligible - if (eligibility[policy]) return; + setIsCheckingEligibility(true); - setIsChecking(true); + const { res } = await request(routes.hcx.policies.checkEligibility, { + body: { policy: selectedPolicy.id }, + }); - const res = await dispatch(HCXActions.checkEligibility(policy)); - if (res.status === 200) { - Notification.Success({ msg: "Checking Policy Eligibility..." }); - } else { - Notification.Error({ msg: "Something Went Wrong..." }); + if (res?.ok) { + Notification.Success({ msg: t("checking_policy_eligibility") }); } + + setIsCheckingEligibility(false); }; return (
      -
      +
      option.id as string} + options={policiesResponse?.results ?? []} + optionValue={(option) => option.id} optionLabel={(option) => option.policy_id} optionSelectedLabel={(option) => - option.id && eligibility[option.id] !== undefined ? ( + option.outcome ? (
      {option.policy_id} - +
      ) : ( option.policy_id ) } optionIcon={(option) => - eligibility[option.id!] !== undefined && ( - + option.outcome && ( + ) } - onChange={({ value }) => setPolicy(value)} - value={policy} + onChange={({ value }) => { + setSelectedPolicy( + policiesResponse?.results.find((policy) => policy.id === value), + ); + }} + value={selectedPolicy?.id} placeholder={ - insuranceDetails - ? insuranceDetails.length - ? "Select a policy to check eligibility" - : "No policies for the patient" - : "Loading..." + loading + ? t("loading") + : policiesResponse?.results.length + ? t("select_policy") + : t("no_policy_found") } - disabled={!insuranceDetails} + disabled={!policiesResponse?.results.length} optionDescription={(option) => (
      - - Member ID - + + {t("policy__subscriber_id")} + {option.subscriber_id} - - Insurer ID - + + {t("policy__insurer_id")} + {option.insurer_id} - - Insurer Name - + + {t("policy__insurer_name")} + {option.insurer_name} @@ -154,17 +137,21 @@ export default function HCXPolicyEligibilityCheck({ )} /> - {isChecking ? ( + {isCheckingEligibility ? ( <> - Checking ... + {t("checking_eligibility")} ) : ( - "Check Eligibility" + t("check_eligibility") )}
      @@ -173,6 +160,8 @@ export default function HCXPolicyEligibilityCheck({ } const EligibilityChip = ({ eligible }: { eligible: boolean }) => { + const { t } = useTranslation(); + return (
      { > - {eligible ? "Eligible" : "Not Eligible"} + {eligible ? t("eligible") : t("not_eligible")}
      ); }; + +const isPolicyEligible = (policy?: HCXPolicyModel) => + policy && !policy.error_text && policy.outcome === "Complete"; diff --git a/src/Components/HCX/models.ts b/src/Components/HCX/models.ts index a8a9812d31d..e75fc28214d 100644 --- a/src/Components/HCX/models.ts +++ b/src/Components/HCX/models.ts @@ -8,7 +8,7 @@ export type HCXPolicyStatus = | "Active" | "Cancelled" | "Draft" - | "Entered in Error"; + | "Entered In Error"; export type HCXPolicyPurpose = | "Auth Requirements" | "Benefits" @@ -16,12 +16,12 @@ export type HCXPolicyPurpose = | "Validation"; export type HCXPolicyOutcome = | "Queued" - | "Processing Complete" + | "Complete" | "Error" | "Partial Processing"; export interface HCXPolicyModel { - id?: string; + id: string; patient?: string; patient_object?: PatientModel; subscriber_id: string; @@ -29,14 +29,26 @@ export interface HCXPolicyModel { insurer_id?: string; insurer_name?: string; status?: HCXPolicyStatus; - priority?: "Immediate" | "Normal" | "Deferred"; - purpose?: "Auth Requirements" | "Benefits" | "Discovery" | "Validation"; - outcome?: "Queued" | "Processing Complete" | "Error" | "Partial Processing"; + priority?: HCXPriority; + purpose?: HCXPolicyPurpose; + outcome?: HCXPolicyOutcome; error_text?: string; created_date?: string; modified_date?: string; } +export interface HCXCommunicationModel { + id?: string; + identifier?: string; + claim?: string; + claim_object?: HCXClaimModel; + content?: { type: string; data: string }[]; + created_by?: string | null; + last_modified_by?: string | null; + created_date?: string; + modified_date?: string; +} + export interface HCXItemModel { id: string; name: string; @@ -44,7 +56,7 @@ export interface HCXItemModel { category?: string; } -export type HCXClaimUse = "Claim" | "Pre-Authorization" | "Pre-Determination"; +export type HCXClaimUse = "Claim" | "Pre Authorization" | "Pre Determination"; export type HCXClaimStatus = HCXPolicyStatus; export type HCXClaimType = | "Institutional" diff --git a/src/Components/Kanban/Board.tsx b/src/Components/Kanban/Board.tsx new file mode 100644 index 00000000000..d2ab5da998f --- /dev/null +++ b/src/Components/Kanban/Board.tsx @@ -0,0 +1,190 @@ +import { + DragDropContext, + Draggable, + Droppable, + OnDragEndResponder, +} from "@hello-pangea/dnd"; +import { ReactNode, RefObject, useEffect, useRef, useState } from "react"; +import { QueryRoute } from "../../Utils/request/types"; +import { QueryOptions } from "../../Utils/request/useQuery"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import request from "../../Utils/request/request"; +import { useTranslation } from "react-i18next"; + +interface KanbanBoardProps { + title?: ReactNode; + onDragEnd: OnDragEndResponder; + sections: { + id: string; + title: ReactNode; + fetchOptions: ( + id: string, + ...args: unknown[] + ) => { + route: QueryRoute; + options?: QueryOptions; + }; + }[]; + itemRender: (item: T) => ReactNode; +} + +export default function KanbanBoard( + props: KanbanBoardProps, +) { + const board = useRef(null); + + return ( +
      +
      +
      {props.title}
      +
      + {[0, 1].map((button, i) => ( + + ))} +
      +
      + +
      +
      + {props.sections.map((section, i) => ( + + key={i} + section={section} + itemRender={props.itemRender} + boardRef={board} + /> + ))} +
      +
      +
      +
      + ); +} + +export function KanbanSection( + props: Omit, "sections" | "onDragEnd"> & { + section: KanbanBoardProps["sections"][number]; + boardRef: RefObject; + }, +) { + const { section } = props; + const [offset, setOffset] = useState(0); + const [pages, setPages] = useState([]); + const [fetchingNextPage, setFetchingNextPage] = useState(false); + const [hasMore, setHasMore] = useState(true); + const [totalCount, setTotalCount] = useState(); + + const options = section.fetchOptions(section.id); + const sectionRef = useRef(null); + const defaultLimit = 14; + const { t } = useTranslation(); + + // should be replaced with useInfiniteQuery when we move over to react query + + const fetchNextPage = async (refresh: boolean = false) => { + if (!refresh && (fetchingNextPage || !hasMore)) return; + if (refresh) setPages([]); + const offsetToUse = refresh ? 0 : offset; + setFetchingNextPage(true); + const res = await request(options.route, { + ...options.options, + query: { ...options.options?.query, offsetToUse, limit: defaultLimit }, + }); + const newPages = refresh ? [] : [...pages]; + const page = Math.floor(offsetToUse / defaultLimit); + if (res.error) return; + newPages[page] = (res.data as any).results; + setPages(newPages); + setHasMore(!!(res.data as any)?.next); + setTotalCount((res.data as any)?.count); + setOffset(offsetToUse + defaultLimit); + setFetchingNextPage(false); + }; + + const items = pages.flat(); + + useEffect(() => { + const onBoardReachEnd = async () => { + const sectionElementHeight = + sectionRef.current?.getBoundingClientRect().height; + const scrolled = props.boardRef.current?.scrollTop; + // if user has scrolled 3/4th of the current items + if ( + scrolled && + sectionElementHeight && + scrolled > sectionElementHeight * (3 / 4) + ) { + fetchNextPage(); + } + }; + + props.boardRef.current?.addEventListener("scroll", onBoardReachEnd); + return () => + props.boardRef.current?.removeEventListener("scroll", onBoardReachEnd); + }, [props.boardRef, fetchingNextPage, hasMore]); + + useEffect(() => { + fetchNextPage(true); + }, [props.section]); + + return ( + + {(provided) => ( +
      +
      +
      +
      {section.title}
      +
      + + {typeof totalCount === "undefined" ? "..." : totalCount} + +
      +
      +
      +
      + {!fetchingNextPage && totalCount === 0 && ( +
      + {t("no_results_found")} +
      + )} + {items + .filter((item) => item) + .map((item, i) => ( + + {(provided) => ( +
      + {props.itemRender(item)} +
      + )} +
      + ))} + {fetchingNextPage && ( +
      + )} +
      +
      + )} + + ); +} diff --git a/src/Components/LogUpdate/Sections/Vitals.tsx b/src/Components/LogUpdate/Sections/Vitals.tsx index 44902bbf0b8..2d533d529e4 100644 --- a/src/Components/LogUpdate/Sections/Vitals.tsx +++ b/src/Components/LogUpdate/Sections/Vitals.tsx @@ -29,7 +29,7 @@ const Vitals = ({ log, onChange }: LogUpdateSectionProps) => {
      onChange({ ventilator_spo2: c.value })} value={log.ventilator_spo2} diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx index 12680f37db8..4f84238afcf 100644 --- a/src/Components/Medicine/PrescriptionDetailCard.tsx +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -226,7 +226,7 @@ export default function PrescriptionDetailCard({ {prescription.notes && ( - + {prescription.notes} )} diff --git a/src/Components/Notifications/NotificationsList.tsx b/src/Components/Notifications/NotificationsList.tsx index 2de3ae7922c..de9618809b4 100644 --- a/src/Components/Notifications/NotificationsList.tsx +++ b/src/Components/Notifications/NotificationsList.tsx @@ -2,7 +2,7 @@ import { navigate } from "raviger"; import { useEffect, useState } from "react"; import Spinner from "../Common/Spinner"; import { NOTIFICATION_EVENTS } from "../../Common/constants"; -import { Error } from "../../Utils/Notifications.js"; +import { Error, Success, Warn } from "../../Utils/Notifications.js"; import { classNames, formatDateTime } from "../../Utils/utils"; import CareIcon, { IconName } from "../../CAREUI/icons/CareIcon"; import * as Sentry from "@sentry/browser"; @@ -194,6 +194,30 @@ export default function NotificationsList({ if (open) document.addEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown); }, [open]); + useEffect(() => { + let intervalId: ReturnType; + if (isSubscribing) { + const checkNotificationPermission = () => { + if (Notification.permission === "denied") { + Warn({ + msg: t("notification_permission_denied"), + }); + setIsSubscribing(false); + clearInterval(intervalId); + } else if (Notification.permission === "granted") { + Success({ + msg: t("notification_permission_granted"), + }); + setIsSubscribing(false); + clearInterval(intervalId); + } + }; + + checkNotificationPermission(); + intervalId = setInterval(checkNotificationPermission, 1000); + } + return () => clearInterval(intervalId); + }, [isSubscribing]); const intialSubscriptionState = async () => { try { diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index c0cd7fe803c..ef1281fb62b 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -542,6 +542,7 @@ export const DailyRounds = (props: any) => { >
      { setDiagnosisSuggestions([]); diff --git a/src/Components/Patient/InsuranceDetails.tsx b/src/Components/Patient/InsuranceDetails.tsx index 41f1da11e89..9f9fe8507ae 100644 --- a/src/Components/Patient/InsuranceDetails.tsx +++ b/src/Components/Patient/InsuranceDetails.tsx @@ -1,11 +1,9 @@ -import { lazy } from "react"; - -import Page from "../Common/components/Page"; - -import useQuery from "../../Utils/request/useQuery"; -import routes from "../../Redux/api"; import { HCXPolicyModel } from "../HCX/models"; import { InsuranceDetialsCard } from "./InsuranceDetailsCard"; +import Page from "../Common/components/Page"; +import { lazy } from "react"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; const Loading = lazy(() => import("../Common/Loading")); @@ -17,11 +15,14 @@ interface IProps { export const InsuranceDetails = (props: IProps) => { const { facilityId, id } = props; - const { data: insuranceDetials, loading } = useQuery(routes.listHCXPolicies, { - query: { - patient: id, + const { data: insuranceDetials, loading } = useQuery( + routes.hcx.policies.list, + { + query: { + patient: id, + }, }, - }); + ); if (loading) { return ; diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 944a5163c96..0ad1b81705b 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -13,7 +13,6 @@ import { import { FacilityModel, PatientCategory } from "../Facility/models"; import { Link, navigate } from "raviger"; import { ReactNode, lazy, useEffect, useState } from "react"; -import { getAllPatient } from "../../Redux/actions"; import { parseOptionId } from "../../Common/utils"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; @@ -53,6 +52,7 @@ import { import { ICD11DiagnosisModel } from "../Diagnosis/types.js"; import { getDiagnosesByIds } from "../Diagnosis/utils.js"; import Tabs from "../Common/components/Tabs.js"; +import request from "../../Utils/request/request.js"; const Loading = lazy(() => import("../Common/Loading")); @@ -276,13 +276,6 @@ export const PatientManager = () => { !durations.every((x) => x === 0); let managePatients: any = null; - - const exportPatients = (isFiltered: boolean) => { - const filters = { ...params, csv: true, facility: qParams.facility }; - if (!isFiltered) delete filters.is_active; - return () => getAllPatient(filters, "downloadPatients"); - }; - const preventDuplicatePatientsDuetoPolicyId = (data: any) => { // Generate a array which contains imforamation of duplicate patient IDs and there respective linenumbers const lines = data.split("\n"); // Split the data into individual lines @@ -721,7 +714,8 @@ export const PatientManager = () => { {patient.last_consultation?.last_daily_round ?.ventilator_interface && patient.last_consultation?.last_daily_round - ?.ventilator_interface !== "UNKNOWN" && ( + ?.ventilator_interface !== "UNKNOWN" && + !patient.last_consultation?.discharge_date && (
      { RESPIRATORY_SUPPORT.find( @@ -922,7 +916,18 @@ export const PatientManager = () => { exportItems={[ { label: "Export Live patients", - action: exportPatients(true), + action: async () => { + const query = { + ...params, + csv: true, + facility: qParams.facility, + }; + delete qParams.is_active; + const { data } = await request(routes.patientList, { + query, + }); + return data ?? null; + }, parse: preventDuplicatePatientsDuetoPolicyId, }, ]} diff --git a/src/Components/Patient/PatientConsentRecordBlock.tsx b/src/Components/Patient/PatientConsentRecordBlock.tsx index 6832de87679..07931380cb6 100644 --- a/src/Components/Patient/PatientConsentRecordBlock.tsx +++ b/src/Components/Patient/PatientConsentRecordBlock.tsx @@ -103,6 +103,7 @@ export default function PatientConsentRecordBlockGroup(props: { file={file} editable={hasEditPermission(file)} associating_id={consentRecord.id} + archivable /> ))}
      diff --git a/src/Components/Patient/PatientConsentRecords.tsx b/src/Components/Patient/PatientConsentRecords.tsx index e277b1d20fc..4ea9a0f294a 100644 --- a/src/Components/Patient/PatientConsentRecords.tsx +++ b/src/Components/Patient/PatientConsentRecords.tsx @@ -38,6 +38,7 @@ export default function PatientConsentRecords(props: { const fileUpload = useFileUpload({ type: "CONSENT_RECORD", allowedExtensions: ["pdf", "jpg", "jpeg", "png"], + allowNameFallback: false, }); const fileManager = useFileManager({ @@ -181,11 +182,11 @@ export default function PatientConsentRecords(props: { fileUpload.setFileName(e.value)} />
      - {fileUpload.file ? ( + {fileUpload.files[0] ? ( <> { @@ -203,7 +204,7 @@ export default function PatientConsentRecords(props: { handleUpload(); } }} - loading={!!fileUpload.progress} + loading={fileUpload.uploading} disabled={ newConsent.type === 2 && newConsent.patient_code_status === 0 @@ -215,8 +216,8 @@ export default function PatientConsentRecords(props: { diff --git a/src/Components/Patient/PatientFilter.tsx b/src/Components/Patient/PatientFilter.tsx index 773a3f333b7..f496a59df6d 100644 --- a/src/Components/Patient/PatientFilter.tsx +++ b/src/Components/Patient/PatientFilter.tsx @@ -343,27 +343,25 @@ export default function PatientFilter(props: any) { />
      - {props.dischargePage || ( -
      - - {props.dischargePage && "Last "}Admitted to (Bed Types) - - o.id} - optionLabel={(o) => o.text} - onChange={(o) => - setFilterState({ - ...filterState, - last_consultation_admitted_bed_type_list: o, - }) - } - /> -
      - )} +
      + + {props.dischargePage && "Last "}Admitted to (Bed Types) + + o.id} + optionLabel={(o) => o.text} + onChange={(o) => + setFilterState({ + ...filterState, + last_consultation_admitted_bed_type_list: o, + }) + } + /> +
      ); diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index 033d0187649..9a9715ba081 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -10,16 +10,21 @@ import { SOCIOECONOMIC_STATUS_CHOICES, VACCINES, } from "../../Common/constants"; +import { DistrictModel, DupPatientModel, WardModel } from "../Facility/models"; import { + FieldError, + PhoneNumberValidator, + RequiredFieldValidator, +} from "../Form/FieldValidators"; +import { FieldErrorText, FieldLabel } from "../Form/FormFields/FormField"; +import { + compareBy, dateQueryString, getPincodeDetails, includesIgnoreCase, parsePhoneNumber, scrollTo, - compareBy, } from "../../Utils/utils"; -import { navigate, useQueryParams } from "raviger"; -import { statusType, useAbortableEffect } from "../../Common/utils"; import { lazy, useCallback, @@ -28,8 +33,11 @@ import { useRef, useState, } from "react"; +import { navigate, useQueryParams } from "raviger"; +import { statusType, useAbortableEffect } from "../../Common/utils"; import AccordionV2 from "../Common/components/AccordionV2"; +import AutocompleteFormField from "../Form/FormFields/Autocomplete.js"; import ButtonV2 from "../Common/components/ButtonV2"; import CareIcon from "../../CAREUI/icons/CareIcon"; import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; @@ -37,43 +45,34 @@ import CollapseV2 from "../Common/components/CollapseV2"; import ConfirmDialog from "../Common/ConfirmDialog"; import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "../Common/Dialog"; -import { DistrictModel, DupPatientModel, WardModel } from "../Facility/models"; import DuplicatePatientDialog from "../Facility/DuplicatePatientDialog"; -import { - FieldError, - PhoneNumberValidator, - RequiredFieldValidator, -} from "../Form/FieldValidators"; -import { FieldErrorText, FieldLabel } from "../Form/FormFields/FormField"; +import Error404 from "../ErrorPages/404"; import Form from "../Form/Form"; +import { FormContextValue } from "../Form/FormContext.js"; import { HCXPolicyModel } from "../HCX/models"; import HCXPolicyValidator from "../HCX/validators"; +import { ILocalBodies } from "../ExternalResult/models.js"; import InsuranceDetailsBuilder from "../HCX/InsuranceDetailsBuilder"; import LinkABHANumberModal from "../ABDM/LinkABHANumberModal"; import { PatientModel, Occupation, PatientMeta } from "./models"; import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import RadioFormField from "../Form/FormFields/RadioFormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import AutocompleteFormField from "../Form/FormFields/Autocomplete.js"; +import SelectMenuV2 from "../Form/SelectMenuV2.js"; import Spinner from "../Common/Spinner"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import TransferPatientDialog from "../Facility/TransferPatientDialog"; +import _ from "lodash"; import countryList from "../../Common/static/countries.json"; import { debounce } from "lodash-es"; - +import request from "../../Utils/request/request.js"; +import routes from "../../Redux/api.js"; import useAppHistory from "../../Common/hooks/useAppHistory"; -import { validatePincode } from "../../Common/validation"; -import { FormContextValue } from "../Form/FormContext.js"; import useAuthUser from "../../Common/hooks/useAuthUser.js"; import useQuery from "../../Utils/request/useQuery.js"; -import routes from "../../Redux/api.js"; -import request from "../../Utils/request/request.js"; -import Error404 from "../ErrorPages/404"; -import SelectMenuV2 from "../Form/SelectMenuV2.js"; -import _ from "lodash"; -import { ILocalBodies } from "../ExternalResult/models.js"; import { useTranslation } from "react-i18next"; +import { validatePincode } from "../../Common/validation"; import careConfig from "@careConfig"; const Loading = lazy(() => import("../Common/Loading")); @@ -481,7 +480,7 @@ export const PatientRegister = (props: PatientRegisterProps) => { [id], ); - useQuery(routes.listHCXPolicies, { + useQuery(routes.hcx.policies.list, { query: { patient: id, }, @@ -818,29 +817,14 @@ export const PatientRegister = (props: PatientRegisterProps) => { insurer_id: obj.insurer_id || undefined, insurer_name: obj.insurer_name || undefined, }; - const { data: policyData } = policy.id - ? await request(routes.updateHCXPolicy, { + policy.id + ? await request(routes.hcx.policies.update, { pathParams: { external_id: policy.id }, body: policy, }) - : await request(routes.createHCXPolicy, { + : await request(routes.hcx.policies.create, { body: policy, }); - - if (careConfig.hcx.enabled && policyData?.id) { - await request(routes.hcxCheckEligibility, { - body: { policy: policyData?.id }, - onResponse: ({ res }) => { - if (res?.ok) { - Notification.Success({ - msg: "Checking Policy Eligibility...", - }); - } else { - Notification.Error({ msg: "Something Went Wrong..." }); - } - }, - }); - } }), ); diff --git a/src/Components/Patient/SampleViewAdmin.tsx b/src/Components/Patient/SampleViewAdmin.tsx index 43430dc8546..ced7e1a96ba 100644 --- a/src/Components/Patient/SampleViewAdmin.tsx +++ b/src/Components/Patient/SampleViewAdmin.tsx @@ -7,7 +7,6 @@ import { SAMPLE_FLOW_RULES, SAMPLE_TYPE_CHOICES, } from "../../Common/constants"; -import { downloadSampleTests } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import { SampleTestModel } from "./models"; import UpdateStatusDialog from "./UpdateStatusDialog"; @@ -314,7 +313,12 @@ export default function SampleViewAdmin() { breadcrumbs={false} componentRight={ downloadSampleTests({ ...qParams })} + action={async () => { + const { data } = await request(routes.getTestSampleList, { + query: { ...qParams, csv: true }, + }); + return data ?? null; + }} parse={parseExportData} filenamePrefix="samples" /> diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index cf35680de11..d9e275fdc4a 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -135,6 +135,7 @@ export interface PatientModel { assigned_to?: { first_name?: string; username?: string; last_name?: string }; assigned_to_object?: AssignedToObjectModel; meta_info?: PatientMeta; + age?: string; } export interface SampleTestModel { diff --git a/src/Components/Resource/ListView.tsx b/src/Components/Resource/ListView.tsx index 128e17c2c46..11362f54891 100644 --- a/src/Components/Resource/ListView.tsx +++ b/src/Components/Resource/ListView.tsx @@ -1,6 +1,5 @@ import { lazy } from "react"; import { navigate } from "raviger"; -import { downloadResourceRequests } from "../../Redux/actions"; import ListFilter from "./ListFilter"; import { formatFilter } from "./Commons"; import BadgesList from "./BadgesList"; @@ -16,6 +15,7 @@ import useQuery from "../../Utils/request/useQuery"; import routes from "../../Redux/api"; import Page from "../Common/components/Page"; import SearchInput from "../Form/SearchInput"; +import request from "../../Utils/request/request"; const Loading = lazy(() => import("../Common/Loading")); @@ -161,7 +161,12 @@ export default function ListView() { hideBack componentRight={ downloadResourceRequests({ ...appliedFilters, csv: 1 })} + action={async () => { + const { data } = await request(routes.downloadResourceRequests, { + query: { ...appliedFilters, csv: true }, + }); + return data ?? null; + }} filenamePrefix="resource_requests" /> } diff --git a/src/Components/Resource/ResourceBoard.tsx b/src/Components/Resource/ResourceBoard.tsx deleted file mode 100644 index 4b32c72eda4..00000000000 --- a/src/Components/Resource/ResourceBoard.tsx +++ /dev/null @@ -1,296 +0,0 @@ -import { useState, useEffect } from "react"; -import { downloadResourceRequests } from "../../Redux/actions"; -import { navigate } from "raviger"; -import { classNames, formatName } from "../../Utils/utils"; -import { useDrag, useDrop } from "react-dnd"; -import { formatDateTime } from "../../Utils/utils"; -import { ExportButton } from "../Common/Export"; -import dayjs from "../../Utils/dayjs"; -import useQuery from "../../Utils/request/useQuery"; -import routes from "../../Redux/api"; -import { PaginatedResponse } from "../../Utils/request/types"; -import { IResource } from "./models"; -import request from "../../Utils/request/request"; -import CareIcon from "../../CAREUI/icons/CareIcon"; - -interface boardProps { - board: string; - filterProp: any; - formatFilter: any; -} - -const renderBoardTitle = (board: string) => board; - -const reduceLoading = (action: string, current: any) => { - switch (action) { - case "MORE": - return { ...current, more: true }; - case "BOARD": - return { ...current, board: true }; - case "COMPLETE": - return { board: false, more: false }; - } -}; - -const ResourceCard = ({ resource }: any) => { - const [{ isDragging }, drag] = useDrag(() => ({ - type: "resource-card", - item: resource, - collect: (monitor) => ({ isDragging: !!monitor.isDragging() }), - })); - - return ( -
      -
      -
      -
      -
      -
      - {resource.title} -
      -
      - {resource.emergency && ( - - Emergency - - )} -
      -
      -
      -
      -
      - -
      - {(resource.origin_facility_object || {}).name} -
      - -
      -
      -
      - -
      - {(resource.approving_facility_object || {}).name} -
      - -
      - {resource.assigned_facility_object && ( -
      -
      - - -
      - {(resource.assigned_facility_object || {}).name || - "Yet to be decided"} -
      - -
      - )} -
      -
      - -
      - {formatDateTime(resource.modified_date) || "--"} -
      - -
      - {resource.assigned_to_object && ( -
      -
      - -
      - {formatName(resource.assigned_to_object)} -{" "} - {resource.assigned_to_object.user_type} -
      - -
      - )} -
      -
      -
      - -
      -
      -
      -
      - ); -}; - -export default function ResourceBoard({ - board, - filterProp, - formatFilter, -}: boardProps) { - const [isLoading, setIsLoading] = useState({ board: "BOARD", more: false }); - const [{ isOver }, drop] = useDrop(() => ({ - accept: "resource-card", - drop: (item: any) => { - if (item.status !== board) { - navigate(`/resource/${item.id}/update?status=${board}`); - } - }, - collect: (monitor) => ({ isOver: !!monitor.isOver() }), - })); - const [offset, setOffSet] = useState(0); - const [data, setData] = useState>(); - - useEffect(() => { - setIsLoading((loading) => reduceLoading("BOARD", loading)); - }, [ - board, - filterProp.title, - filterProp.facility, - filterProp.origin_facility, - filterProp.approving_facility, - filterProp.assigned_facility, - filterProp.emergency, - filterProp.created_date_before, - filterProp.created_date_after, - filterProp.modified_date_before, - filterProp.modified_date_after, - filterProp.ordering, - ]); - - useQuery(routes.listResourceRequests, { - query: formatFilter({ - ...filterProp, - status: board, - }), - onResponse: ({ res, data: listResourceData }) => { - if (res?.ok && listResourceData) { - setData(listResourceData); - } - setIsLoading((loading) => reduceLoading("COMPLETE", loading)); - }, - }); - - const handlePagination = async () => { - setIsLoading((loading) => reduceLoading("MORE", loading)); - setOffSet(offset + 14); - const { res, data: newPageData } = await request( - routes.listResourceRequests, - { - query: formatFilter({ - ...filterProp, - status: board, - offset: offset, - }), - }, - ); - if (res?.ok && newPageData) { - setData((prev) => - prev - ? { ...prev, results: [...prev.results, ...newPageData.results] } - : newPageData, - ); - } - setIsLoading((loading) => reduceLoading("COMPLETE", loading)); - }; - - const boardFilter = (filter: string) => { - return data?.results - .filter(({ status }) => status === filter) - .map((resource: any) => ( - - )); - }; - - return ( -
      -
      -
      -

      - {renderBoardTitle(board)}{" "} - - downloadResourceRequests({ - ...formatFilter({ ...filterProp, status: board }), - csv: 1, - }) - } - filenamePrefix={`resource_requests_${board}`} - /> -

      - - {data?.count || "0"} - -
      -
      -
      - {isLoading.board ? ( -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - ) : data && data?.results.length > 0 ? ( - boardFilter(board) - ) : ( -

      No requests to show.

      - )} - {!isLoading.board && - data && - data?.results.length < (data?.count || 0) && - (isLoading.more ? ( -
      - Loading -
      - ) : ( - - ))} -
      -
      - ); -} diff --git a/src/Components/Resource/ResourceBoardView.tsx b/src/Components/Resource/ResourceBoardView.tsx index bff0282f91a..c215f45970a 100644 --- a/src/Components/Resource/ResourceBoardView.tsx +++ b/src/Components/Resource/ResourceBoardView.tsx @@ -1,10 +1,7 @@ import { lazy, useState } from "react"; -import { navigate } from "raviger"; +import { Link, navigate } from "raviger"; import ListFilter from "./ListFilter"; -import ResourceBoard from "./ResourceBoard"; import { RESOURCE_CHOICES } from "../../Common/constants"; -import { downloadResourceRequests } from "../../Redux/actions"; -import withScrolling from "react-dnd-scrolling"; import BadgesList from "./BadgesList"; import { formatFilter } from "./Commons"; import useFilters from "../../Common/hooks/useFilters"; @@ -15,10 +12,14 @@ import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover" import CareIcon from "../../CAREUI/icons/CareIcon"; import SearchInput from "../Form/SearchInput"; import Tabs from "../Common/components/Tabs"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import KanbanBoard from "../Kanban/Board"; +import { ResourceModel } from "../Facility/models"; +import { classNames, formatDateTime, formatName } from "../../Utils/utils"; +import dayjs from "dayjs"; -const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); -const ScrollingComponent = withScrolling("div"); const resourceStatusOptions = RESOURCE_CHOICES.map((obj) => obj.text); const COMPLETED = ["COMPLETED", "REJECTED"]; @@ -31,7 +32,6 @@ export default function BoardView() { }); const [boardFilter, setBoardFilter] = useState(ACTIVE); // eslint-disable-next-line - const [isLoading, setIsLoading] = useState(false); const appliedFilters = formatFilter(qParams); const { t } = useTranslation(); @@ -41,18 +41,24 @@ export default function BoardView() { }; return ( -
      +
      - downloadResourceRequests({ ...appliedFilters, csv: 1 }) - } + action={async () => { + const { data } = await request( + routes.downloadResourceRequests, + { + query: { ...appliedFilters, csv: true }, + }, + ); + return data ?? null; + }} filenamePrefix="resource_requests" /> } @@ -87,23 +93,135 @@ export default function BoardView() {
      - - -
      - {isLoading ? ( - - ) : ( - boardFilter.map((board) => ( - + title={} + sections={boardFilter.map((board) => ({ + id: board, + title: ( +

      + {board}{" "} + { + const { data } = await request( + routes.downloadResourceRequests, + { + query: { + ...formatFilter({ ...qParams, status: board }), + csv: true, + }, + }, + ); + return data ?? null; + }} + filenamePrefix={`resource_requests_${board}`} /> - )) - )} -

      -
      + + ), + fetchOptions: (id) => ({ + route: routes.listResourceRequests, + options: { + query: formatFilter({ + ...qParams, + status: id, + }), + }, + }), + }))} + onDragEnd={(result) => { + if (result.source.droppableId !== result.destination?.droppableId) + navigate( + `/resource/${result.draggableId}/update?status=${result.destination?.droppableId}`, + ); + }} + itemRender={(resource) => ( +
      +
      +
      +
      +
      + {resource.title} +
      +
      +
      + {resource.emergency && ( + + {t("emergency")} + + )} +
      +
      +
      + {( + [ + { + title: "origin_facility", + icon: "l-plane-departure", + data: resource.origin_facility_object.name, + }, + { + title: "resource_approving_facility", + icon: "l-user-check", + data: resource.approving_facility_object?.name, + }, + { + title: "assigned_facility", + icon: "l-plane-arrival", + data: + resource.assigned_facility_object?.name || + t("yet_to_be_decided"), + }, + { + title: "last_modified", + icon: "l-stopwatch", + data: formatDateTime(resource.modified_date), + className: dayjs() + .subtract(2, "hours") + .isBefore(resource.modified_date) + ? "text-secondary-900" + : "rounded bg-red-500 border border-red-600 text-white w-full font-bold", + }, + { + title: "assigned_to", + icon: "l-user", + data: resource.assigned_to_object + ? formatName(resource.assigned_to_object) + + " - " + + resource.assigned_to_object.user_type + : undefined, + }, + ] as const + ) + .filter((d) => d.data) + .map((datapoint, i) => ( +
      +
      + +
      +
      + {datapoint.data} +
      +
      + ))} +
      +
      +
      + + {t("all_details")} + +
      +
      + )} + />
      ); diff --git a/src/Components/Scribe/Scribe.tsx b/src/Components/Scribe/Scribe.tsx index d952ff7b1b2..384bf519a04 100644 --- a/src/Components/Scribe/Scribe.tsx +++ b/src/Components/Scribe/Scribe.tsx @@ -7,8 +7,8 @@ import * as Notify from "../../Utils/Notifications"; import request from "../../Utils/request/request"; import { UserModel } from "../Users/models"; import useSegmentedRecording from "../../Utils/useSegmentedRecorder"; -import careConfig from "@careConfig"; import uploadFile from "../../Utils/request/uploadFile"; +import { useFeatureFlags } from "../../Utils/featureFlags"; interface FieldOption { id: string | number; @@ -52,6 +52,7 @@ export type ScribeModel = { }; interface ScribeProps { + facilityId: string; form: ScribeForm; existingData?: { [key: string]: any }; onFormUpdate: (fields: any) => void; @@ -62,7 +63,11 @@ const SCRIBE_FILE_TYPES = { SCRIBE: 1, }; -export const Scribe: React.FC = ({ form, onFormUpdate }) => { +export const Scribe: React.FC = ({ + form, + onFormUpdate, + facilityId, +}) => { const [open, setOpen] = useState(false); const [_progress, setProgress] = useState(0); const [stage, setStage] = useState("start"); @@ -80,6 +85,8 @@ export const Scribe: React.FC = ({ form, onFormUpdate }) => { const stageRef = useRef(stage); const [fields, setFields] = useState([]); + const featureFlags = useFeatureFlags(facilityId); + useEffect(() => { const loadFields = async () => { const fields = await form.fields(); @@ -544,7 +551,7 @@ export const Scribe: React.FC = ({ form, onFormUpdate }) => { } } - if (!careConfig.scribe.enabled) return null; + if (!featureFlags.includes("SCRIBE_ENABLED")) return null; return ( diff --git a/src/Components/Shifting/BoardView.tsx b/src/Components/Shifting/BoardView.tsx index f4fb45093aa..20aa6903e80 100644 --- a/src/Components/Shifting/BoardView.tsx +++ b/src/Components/Shifting/BoardView.tsx @@ -7,24 +7,27 @@ import BadgesList from "./BadgesList"; import { ExportButton } from "../Common/Export"; import ListFilter from "./ListFilter"; import SearchInput from "../Form/SearchInput"; -import ShiftingBoard from "./ShiftingBoard"; -import { downloadShiftRequests } from "../../Redux/actions"; import { formatFilter } from "./Commons"; -import { navigate } from "raviger"; +import { Link, navigate } from "raviger"; import useFilters from "../../Common/hooks/useFilters"; -import { lazy, useLayoutEffect, useRef, useState } from "react"; +import { lazy, useState } from "react"; import { useTranslation } from "react-i18next"; -import withScrolling from "react-dnd-scrolling"; import ButtonV2 from "../Common/components/ButtonV2"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import CareIcon from "../../CAREUI/icons/CareIcon"; import Tabs from "../Common/components/Tabs"; import careConfig from "@careConfig"; +import KanbanBoard from "../Kanban/Board"; +import { classNames, formatDateTime, formatName } from "../../Utils/utils"; +import dayjs from "dayjs"; +import ConfirmDialog from "../Common/ConfirmDialog"; +import { ShiftingModel } from "../Facility/models"; +import useAuthUser from "../../Common/hooks/useAuthUser"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; -const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); -const ScrollingComponent = withScrolling("div"); export default function BoardView() { const { qParams, updateQuery, FilterBadges, advancedFilter } = useFilters({ @@ -32,6 +35,26 @@ export default function BoardView() { cacheBlacklist: ["patient_name"], }); + const [modalFor, setModalFor] = useState<{ + externalId?: string; + loading: boolean; + }>({ + externalId: undefined, + loading: false, + }); + + const authUser = useAuthUser(); + + const handleTransferComplete = async (shift: any) => { + setModalFor({ ...modalFor, loading: true }); + await request(routes.completeTransfer, { + pathParams: { externalId: shift.external_id }, + }); + navigate( + `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, + ); + }; + const shiftStatusOptions = careConfig.wartimeShifting ? SHIFTING_CHOICES_WARTIME : SHIFTING_CHOICES_PEACETIME; @@ -54,79 +77,10 @@ export default function BoardView() { ); const [boardFilter, setBoardFilter] = useState(activeBoards); - const [isLoading] = useState(false); const { t } = useTranslation(); - const containerRef = useRef(null); - const [containerHeight, setContainerHeight] = useState(0); - const [isLeftScrollable, setIsLeftScrollable] = useState(false); - const [isRightScrollable, setIsRightScrollable] = useState(false); - - useLayoutEffect(() => { - const container = containerRef.current; - - if (!container) return; - - const handleScroll = () => { - setIsLeftScrollable(container.scrollLeft > 0); - setIsRightScrollable( - container.scrollLeft + container.clientWidth < - container.scrollWidth - 10, - ); - }; - - container.addEventListener("scroll", handleScroll); - - handleScroll(); - - return () => { - container.removeEventListener("scroll", handleScroll); - }; - }, []); - - const handleOnClick = (direction: "right" | "left") => { - const container = containerRef.current; - if (direction === "left" ? !isLeftScrollable : !isRightScrollable) return; - - if (container) { - const scrollAmount = 300; - const currentScrollLeft = container.scrollLeft; - - if (direction === "left") { - container.scrollTo({ - left: currentScrollLeft - scrollAmount, - behavior: "smooth", - }); - } else if (direction === "right") { - container.scrollTo({ - left: currentScrollLeft + scrollAmount, - behavior: "smooth", - }); - } - } - }; - - const renderArrowIcons = (direction: "right" | "left") => { - const isIconEnable = - direction === "left" ? isLeftScrollable : isRightScrollable; - return ( - isIconEnable && ( -
      - handleOnClick(direction)} - /> -
      - ) - ); - }; return ( -
      +
      - downloadShiftRequests({ ...formatFilter(qParams), csv: 1 }) - } + action={async () => { + const { data } = await request(routes.downloadShiftRequests, { + query: { ...formatFilter(qParams), csv: true }, + }); + return data ?? null; + }} filenamePrefix="shift_requests" /> } @@ -177,35 +134,190 @@ export default function BoardView() {
      - - -
      - {isLoading ? ( - - ) : ( - <> - {renderArrowIcons("left")} -
      - {boardFilter.map((board) => ( - - ))} + + title={} + sections={boardFilter.map((board) => ({ + id: board.text, + title: ( +

      + {board.label || board.text}{" "} + { + const { data } = await request(routes.downloadShiftRequests, { + query: { ...formatFilter(qParams), csv: true }, + }); + return data ?? null; + }} + filenamePrefix={`shift_requests_${board}`} + /> +

      + ), + fetchOptions: (id) => ({ + route: routes.listShiftRequests, + options: { + query: formatFilter({ + ...qParams, + status: id, + }), + }, + }), + }))} + onDragEnd={(result) => { + if (result.source.droppableId !== result.destination?.droppableId) + navigate( + `/shifting/${result.draggableId}/update?status=${result.destination?.droppableId}`, + ); + }} + itemRender={(shift) => ( +
      +
      +
      +
      +
      + {shift.patient_object.name} +
      +
      + {shift.patient_object.age} old +
      +
      +
      + {shift.emergency && ( + + {t("emergency")} + + )} +
      - {renderArrowIcons("right")} - - )} -
      - +
      + {( + [ + { + title: "phone_number", + icon: "l-mobile-android", + data: shift.patient_object.phone_number, + }, + { + title: "origin_facility", + icon: "l-plane-departure", + data: shift.origin_facility_object.name, + }, + { + title: "shifting_approving_facility", + icon: "l-user-check", + data: careConfig.wartimeShifting + ? shift.shifting_approving_facility_object?.name + : undefined, + }, + { + title: "assigned_facility", + icon: "l-plane-arrival", + data: + shift.assigned_facility_external || + shift.assigned_facility_object?.name || + t("yet_to_be_decided"), + }, + { + title: "last_modified", + icon: "l-stopwatch", + data: formatDateTime(shift.modified_date), + className: dayjs() + .subtract(2, "hours") + .isBefore(shift.modified_date) + ? "text-secondary-900" + : "rounded bg-red-500 border border-red-600 text-white w-full font-bold", + }, + { + title: "patient_address", + icon: "l-home", + data: shift.patient_object.address, + }, + { + title: "assigned_to", + icon: "l-user", + data: shift.assigned_to_object + ? formatName(shift.assigned_to_object) + + " - " + + shift.assigned_to_object.user_type + : undefined, + }, + { + title: "patient_state", + icon: "l-map-marker", + data: shift.patient_object.state_object?.name, + }, + ] as const + ) + .filter((d) => d.data) + .map((datapoint, i) => ( +
      +
      + +
      +
      + {datapoint.data} +
      +
      + ))} +
      +
      +
      + + {t("all_details")} + + + {shift.status === "COMPLETED" && shift.assigned_facility && ( + <> + + + + setModalFor({ externalId: undefined, loading: false }) + } + action={t("confirm")} + onConfirm={() => handleTransferComplete(shift)} + > +

      + {t("redirected_to_create_consultation")} +

      +
      + + )} +
      +
      + )} + />
      ); diff --git a/src/Components/Shifting/ListView.tsx b/src/Components/Shifting/ListView.tsx index b05795f934d..b40b42dc979 100644 --- a/src/Components/Shifting/ListView.tsx +++ b/src/Components/Shifting/ListView.tsx @@ -1,6 +1,4 @@ -import { downloadShiftRequests } from "../../Redux/actions"; import { lazy, useState } from "react"; - import BadgesList from "./BadgesList"; import ButtonV2 from "../Common/components/ButtonV2"; import ConfirmDialog from "../Common/ConfirmDialog"; @@ -236,9 +234,12 @@ export default function ListView() { hideBack componentRight={ - downloadShiftRequests({ ...formatFilter(qParams), csv: 1 }) - } + action={async () => { + const { data } = await request(routes.downloadShiftRequests, { + query: { ...formatFilter(qParams), csv: true }, + }); + return data ?? null; + }} filenamePrefix="shift_requests" /> } diff --git a/src/Components/Shifting/ShiftingBoard.tsx b/src/Components/Shifting/ShiftingBoard.tsx deleted file mode 100644 index 51f7ddf4fd7..00000000000 --- a/src/Components/Shifting/ShiftingBoard.tsx +++ /dev/null @@ -1,400 +0,0 @@ -import { - Dispatch, - SetStateAction, - useEffect, - useLayoutEffect, - useRef, - useState, -} from "react"; -import { - classNames, - formatDateTime, - formatName, - formatPatientAge, -} from "../../Utils/utils"; -import { downloadShiftRequests } from "../../Redux/actions"; -import { useDrag, useDrop } from "react-dnd"; - -import ButtonV2 from "../Common/components/ButtonV2"; -import ConfirmDialog from "../Common/ConfirmDialog"; -import { navigate } from "raviger"; -import { useTranslation } from "react-i18next"; -import { ExportButton } from "../Common/Export"; -import dayjs from "../../Utils/dayjs"; -import useAuthUser from "../../Common/hooks/useAuthUser"; -import request from "../../Utils/request/request"; -import routes from "../../Redux/api"; -import useQuery from "../../Utils/request/useQuery"; -import { PaginatedResponse } from "../../Utils/request/types"; -import { IShift } from "./models"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import careConfig from "@careConfig"; - -interface boardProps { - board: string; - title?: string; - filterProp: any; - formatFilter: any; - setContainerHeight: Dispatch>; - containerHeight: number; -} - -const ShiftCard = ({ shift, filter }: any) => { - const [modalFor, setModalFor] = useState({ - externalId: undefined, - loading: false, - }); - const [{ isDragging }, drag] = useDrag(() => ({ - type: "shift-card", - item: shift, - collect: (monitor) => ({ isDragging: !!monitor.isDragging() }), - })); - const authUser = useAuthUser(); - const { t } = useTranslation(); - - const handleTransferComplete = async (shift: any) => { - setModalFor({ ...modalFor, loading: true }); - await request(routes.completeTransfer, { - pathParams: { externalId: shift.external_id }, - }); - navigate( - `/facility/${shift.assigned_facility}/patient/${shift.patient}/consultation`, - ); - }; - return ( -
      -
      -
      -
      -
      -
      - {shift.patient_object.name} -{" "} - {formatPatientAge(shift.patient_object, true)} -
      -
      - {shift.emergency && ( - - {t("emergency")} - - )} -
      -
      -
      -
      -
      - -
      - {shift.patient_object.phone_number || ""} -
      - -
      -
      -
      - -
      - {(shift.origin_facility_object || {}).name} -
      - -
      - {careConfig.wartimeShifting && ( -
      -
      - -
      - {(shift.shifting_approving_facility_object || {}).name} -
      - -
      - )} -
      -
      - - -
      - {shift.assigned_facility_external || - shift.assigned_facility_object?.name || - t("yet_to_be_decided")} -
      - -
      - -
      -
      - -
      - {formatDateTime(shift.modified_date) || "--"} -
      - -
      - -
      -
      - -
      - {shift.patient_object.address || "--"} -
      - -
      - - {shift.assigned_to_object && ( -
      -
      - -
      - {formatName(shift.assigned_to_object)} - {" - "} - {shift.assigned_to_object.user_type} -
      - -
      - )} - -
      -
      - -
      - {shift.patient_object.state_object.name || "--"} -
      - -
      -
      -
      - -
      - -
      - {filter === "COMPLETED" && shift.assigned_facility && ( -
      - setModalFor(shift.external_id)} - > - {t("transfer_to_receiving_facility")} - - - - setModalFor({ externalId: undefined, loading: false }) - } - action={t("confirm")} - onConfirm={() => handleTransferComplete(shift)} - > -

      - {t("redirected_to_create_consultation")} -

      -
      -
      - )} -
      -
      -
      - ); -}; - -export default function ShiftingBoard({ - board, - title, - filterProp, - formatFilter, - setContainerHeight, - containerHeight, -}: boardProps) { - const containerRef = useRef(null); - const [offset, setOffSet] = useState(0); - const [pages, setPages] = useState[]>([]); - const [isLoading, setIsLoading] = useState(true); - const [{ isOver }, drop] = useDrop(() => ({ - accept: "shift-card", - drop: (item: any) => { - if (item.status !== board) { - navigate(`/shifting/${item.id}/update?status=${board}`); - } - }, - collect: (monitor) => ({ isOver: !!monitor.isOver() }), - })); - - const query = useQuery(routes.listShiftRequests, { - query: formatFilter({ - ...filterProp, - status: board, - }), - onResponse: ({ res, data: listShiftData }) => { - setIsLoading(false); - if (res?.ok && listShiftData) { - setPages((prev) => [...prev, listShiftData]); - } - }, - }); - - useEffect(() => { - setPages([]); - setIsLoading(true); - query.refetch(); - }, [ - filterProp.facility, - filterProp.origin_facility, - filterProp.shifting_approving_facility, - filterProp.assigned_facility, - filterProp.emergency, - filterProp.is_up_shift, - filterProp.patient_name, - filterProp.created_date_before, - filterProp.created_date_after, - filterProp.modified_date_before, - filterProp.modified_date_after, - filterProp.patient_phone_number, - filterProp.ordering, - filterProp.is_kasp, - filterProp.assigned_to, - filterProp.is_antenatal, - filterProp.breathlessness_level, - ]); - - const handlePagination = async () => { - setIsLoading(true); - setOffSet(offset + 14); - const { res, data: newPageData } = await request(routes.listShiftRequests, { - query: formatFilter({ - ...filterProp, - status: board, - offset: offset, - }), - }); - if (res?.ok && newPageData) { - setPages((prev) => [...prev, newPageData]); - } - setIsLoading(false); - }; - const { t } = useTranslation(); - - const patientFilter = (filter: string) => { - return pages - .flatMap((p) => p.results) - .filter(({ status }) => status === filter) - .map((shift: any) => ( - - )); - }; - - useLayoutEffect(() => { - const container = containerRef.current; - if (container) { - const { height } = container.getBoundingClientRect(); - containerHeight < height && setContainerHeight(height); - } - }, [containerRef.current, pages.flatMap((p) => p.results).length]); - - return ( -
      -
      -
      -

      - {title || board}{" "} - - downloadShiftRequests({ - ...formatFilter({ ...filterProp, status: board }), - csv: 1, - }) - } - filenamePrefix={`shift_requests_${board}`} - /> -

      - - {pages[0] ? pages[0].count : "..."} - -
      -
      -
      - {pages[0]?.count > 0 - ? patientFilter(board) - : !isLoading && ( -

      {t("no_patients_to_show")}

      - )} - {isLoading ? ( -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      -
      - ) : ( - pages.at(-1)?.next && ( - handlePagination()} className="m-2 block"> - Load More - - ) - )} -
      -
      - ); -} diff --git a/src/Components/Users/ManageUsers.tsx b/src/Components/Users/ManageUsers.tsx index a3cb3063577..88069ca5e29 100644 --- a/src/Components/Users/ManageUsers.tsx +++ b/src/Components/Users/ManageUsers.tsx @@ -188,7 +188,6 @@ export default function ManageUsers() { }; let userList: any[] = []; - userListData?.results && userListData.results.length && (userList = userListData.results.map((user: any, idx) => { @@ -283,15 +282,17 @@ export default function ManageUsers() { <>
      - {user.doctor_qualification ? ( + {user.qualification ? ( - {user.doctor_qualification} + {user.qualification} ) : ( - Unknown + + {t("unknown")} + )}
      @@ -307,7 +308,9 @@ export default function ManageUsers() { years ) : ( - Unknown + + {t("unknown")} + )}
      @@ -321,7 +324,9 @@ export default function ManageUsers() { {user.doctor_medical_council_registration} ) : ( - Unknown + + {t("unknown")} + )}
      @@ -335,11 +340,30 @@ export default function ManageUsers() {
      )} +
      + {user.user_type === "Nurse" && ( +
      + + {user.qualification ? ( + + {user.qualification} + + ) : ( + + {t("unknown")} + + )} + +
      + )} {user.created_by && (
      diff --git a/src/Components/Users/UserAdd.tsx b/src/Components/Users/UserAdd.tsx index 86d27bc84d7..73c7fb63c26 100644 --- a/src/Components/Users/UserAdd.tsx +++ b/src/Components/Users/UserAdd.tsx @@ -44,7 +44,6 @@ import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; import { useTranslation } from "react-i18next"; const Loading = lazy(() => import("../Common/Loading")); - interface UserProps { userId?: number; } @@ -72,7 +71,7 @@ type UserForm = { state: number; district: number; local_body: number; - doctor_qualification: string | undefined; + qualification: string | undefined; doctor_experience_commenced_on: string | undefined; doctor_medical_council_registration: string | undefined; }; @@ -95,7 +94,7 @@ const initForm: UserForm = { state: 0, district: 0, local_body: 0, - doctor_qualification: undefined, + qualification: undefined, doctor_experience_commenced_on: undefined, doctor_medical_council_registration: undefined, }; @@ -325,7 +324,12 @@ export const UserAdd = (props: UserProps) => { }, [state.form.phone_number_is_whatsapp, state.form.phone_number]); const setFacility = (selected: FacilityModel | FacilityModel[] | null) => { - setSelectedFacility(selected as FacilityModel[]); + const newSelectedFacilities = selected + ? Array.isArray(selected) + ? selected + : [selected] + : []; + setSelectedFacility(newSelectedFacilities as FacilityModel[]); const form = { ...state.form }; form.facilities = selected ? (selected as FacilityModel[]).map((i) => i.id!) @@ -367,7 +371,16 @@ export const UserAdd = (props: UserProps) => { invalidForm = true; } return; - case "doctor_qualification": + case "qualification": + if ( + (state.form.user_type === "Doctor" || + state.form.user_type === "Nurse") && + !state.form[field] + ) { + errors[field] = t("field_required"); + invalidForm = true; + } + return; case "doctor_medical_council_registration": if (state.form.user_type === "Doctor" && !state.form[field]) { errors[field] = t("field_required"); @@ -548,9 +561,9 @@ export const UserAdd = (props: UserProps) => { : state.form.alt_phone_number, ) ?? "", date_of_birth: dateQueryString(state.form.date_of_birth), - doctor_qualification: - state.form.user_type === "Doctor" - ? state.form.doctor_qualification + qualification: + state.form.user_type === "Doctor" || state.form.user_type == "Nurse" + ? state.form.qualification : undefined, doctor_experience_commenced_on: state.form.user_type === "Doctor" @@ -645,15 +658,17 @@ export const UserAdd = (props: UserProps) => { optionValue={(o) => o.id} /> + {(state.form.user_type === "Doctor" || + state.form.user_type === "Nurse") && ( + + )} {state.form.user_type === "Doctor" && ( <> - - { if (!result || !result.res || !result.data) return; + const formData: EditForm = { firstName: result.data.first_name, lastName: result.data.last_name, @@ -159,7 +160,7 @@ export default function UserProfile() { phoneNumber: result.data.phone_number?.toString() || "", altPhoneNumber: result.data.alt_phone_number?.toString() || "", user_type: result.data.user_type, - doctor_qualification: result.data.doctor_qualification, + qualification: result.data.qualification, doctor_experience_commenced_on: dayjs().diff( dayjs(result.data.doctor_experience_commenced_on), "years", @@ -277,7 +278,16 @@ export default function UserProfile() { invalidForm = true; } return; - case "doctor_qualification": + case "qualification": + if ( + (states.form.user_type === "Doctor" || + states.form.user_type === "Nurse") && + !states.form[field] + ) { + errors[field] = t("field_required"); + invalidForm = true; + } + return; case "doctor_medical_council_registration": if (states.form.user_type === "Doctor" && !states.form[field]) { errors[field] = t("field_required"); @@ -344,9 +354,10 @@ export default function UserProfile() { alt_phone_number: parsePhoneNumber(states.form.altPhoneNumber) ?? "", gender: states.form.gender, date_of_birth: dateQueryString(states.form.date_of_birth), - doctor_qualification: - states.form.user_type === "Doctor" - ? states.form.doctor_qualification + qualification: + states.form.user_type === "Doctor" || + states.form.user_type === "Nurse" + ? states.form.qualification : undefined, doctor_experience_commenced_on: states.form.user_type === "Doctor" @@ -727,15 +738,18 @@ export default function UserProfile() { required type="email" /> + {(states.form.user_type === "Doctor" || + states.form.user_type === "Nurse") && ( + + )} {states.form.user_type === "Doctor" && ( <> - { return (

      {asset?.name}

      - +
      ); }; diff --git a/src/Integrations/index.tsx b/src/Integrations/index.tsx index aeb0399a452..9b2b1e156fd 100644 --- a/src/Integrations/index.tsx +++ b/src/Integrations/index.tsx @@ -1,6 +1,6 @@ import Sentry from "./Sentry"; import Plausible from "./Plausible"; -const Intergrations = { Sentry, Plausible }; +const Integrations = { Sentry, Plausible }; -export default Intergrations; +export default Integrations; diff --git a/src/Locale/TRANSLATION_CONTRIBUTION.md b/src/Locale/TRANSLATION_CONTRIBUTION.md index c262215c643..f3150ae8fc0 100644 --- a/src/Locale/TRANSLATION_CONTRIBUTION.md +++ b/src/Locale/TRANSLATION_CONTRIBUTION.md @@ -1,29 +1,13 @@ -# Contributing Translation +# Contributing Translations -### For adding a new language - -
      +## Adding a new language - Open the Terminal and `cd` to `care_fe/src/Locale` - Run the command `node update_locale.js ` Eg: `node update_locale.js ml` for Malayalam -- The command will create a directory with default locale files and you can start translating them. +- The command will create a directory with default locale files. - After it's done, add the new language to `care_fe/src/Locale/config.ts` file. -### For improving the existing language - -
      - -- Open the Terminal and `cd` to `care_fe/src/Locale` -- Run the command `node update_locale.js ` - Eg: `node update_locale.js ml` for Malayalam -- The command will update the new keys which are yet to be translated. -- You can now start translating or improving it. - ## Note -⚠ - If you are adding a new word, then please add it to the Default Locale (EN) first and then proceed with your language. - -⚠ - After translating, have a look at its appearance. It may be overflowing or cause some UI breaks. Try to adjust the words such that it fits the UI. - ⚠ - Try to separate the translation files for each module like `Facility`, `Patient` and more. Don't dump all the keys in one JSON file. diff --git a/src/Locale/en/Bed.json b/src/Locale/en/Bed.json index 269658be774..327a4533353 100644 --- a/src/Locale/en/Bed.json +++ b/src/Locale/en/Bed.json @@ -9,5 +9,7 @@ "bed_type": "Bed Type", "make_multiple_beds_label": "Do you want to make multiple beds?", "number_of_beds": "Number of beds", - "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100" + "number_of_beds_out_of_range_error": "Number of beds cannot be greater than 100", + "bed_created_notification_one": "{{count}} Bed created successfully", + "bed_created_notification_other": "{{count}} Beds created successfully" } diff --git a/src/Locale/en/Common.json b/src/Locale/en/Common.json index fb36e6a07f8..606113de534 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -175,6 +175,9 @@ "summary": "Summary", "report": "Report", "treating_doctor": "Treating Doctor", + "hubs": "Hub Facilities", + "spokes": "Spoke Facilities", + "add_spoke": "Add Spoke Facility", "ration_card__NO_CARD": "Non-card holder", "ration_card__BPL": "BPL", "ration_card__APL": "APL", @@ -183,6 +186,11 @@ "feed_optimal_experience_for_phones": "For optimal viewing experience, consider rotating your device.", "feed_optimal_experience_for_apple_phones": "For optimal viewing experience, consider rotating your device. Ensure auto-rotate is enabled in your device settings.", "action_irreversible": "This action is irreversible", + "send_message": "Send Message", + "enter_message": "Start typing...", + "see_attachments": "See Attachments", + "no_attachments_found": "This communication has no attachments.", + "fetching": "Fetching", "GENDER__1": "Male", "GENDER__2": "Female", "GENDER__3": "Non-binary", @@ -196,6 +204,7 @@ "live": "Live", "discharged": "Discharged", "archived": "Archived", + "created_on": "Created On", "no_changes_made": "No changes made", "user_deleted_successfuly": "User Deleted Successfuly", "users": "Users", @@ -205,5 +214,25 @@ "delete_item": "Delete {{name}}", "unsupported_browser": "Unsupported Browser", "unsupported_browser_description": "Your browser ({{name}} version {{version}}) is not supported. Please update your browser to the latest version or switch to a supported browser for the best experience.", - "add_remarks": "Add remarks" -} \ No newline at end of file + "add_remarks": "Add remarks", + "SORT_OPTIONS__-created_date": "Latest created date first", + "SORT_OPTIONS__created_date": "Oldest created date first", + "SORT_OPTIONS__-category_severity": "Highest Severity category first", + "SORT_OPTIONS__category_severity": "Lowest Severity category first", + "SORT_OPTIONS__-modified_date": "Latest updated date first", + "SORT_OPTIONS__modified_date": "Oldest updated date first", + "SORT_OPTIONS__facility__name,last_consultation__current_bed__bed__name": "Bed No. 1-N", + "SORT_OPTIONS__facility__name,-last_consultation__current_bed__bed__name": "Bed No. N-1", + "SORT_OPTIONS__-review_time": "Latest review date first", + "SORT_OPTIONS__review_time": "Oldest review date first", + "SORT_OPTIONS__taken_at": "Oldest taken date first", + "SORT_OPTIONS__-taken_at": "Latest taken date first", + "SORT_OPTIONS__name": "Patient name A-Z", + "SORT_OPTIONS__-name": "Patient name Z-A", + "SORT_OPTIONS__bed__name": "Bed No. 1-N", + "SORT_OPTIONS__-bed__name": "Bed No. N-1", + "middleware_hostname": "Middleware Hostname", + "local_ipaddress": "Local IP Address", + "qualification": "Qualification", + "resource": "Resource" +} diff --git a/src/Locale/en/Consultation.json b/src/Locale/en/Consultation.json index d12a6cb16ac..cfde1f1adc9 100644 --- a/src/Locale/en/Consultation.json +++ b/src/Locale/en/Consultation.json @@ -73,6 +73,7 @@ "back_dated_encounter_date_caution": "You are creating an encounter for", "encounter_duration_confirmation": "The duration of this encounter would be", "consultation_notes": "General Instructions (Advice)", + "diagnosis_at_discharge": "Diagnosis at Discharge", "procedure_suggestions": "Procedure Suggestions", "patient_notes_thread__Doctors": "Doctor's Discussions", "patient_notes_thread__Nurses": "Nurse's Discussions" diff --git a/src/Locale/en/Facility.json b/src/Locale/en/Facility.json index e061dee620e..fe23a64f668 100644 --- a/src/Locale/en/Facility.json +++ b/src/Locale/en/Facility.json @@ -100,7 +100,11 @@ "duplicate_patient_record_rejection": "I confirm that the suspect / patient I want to create is not on the list.", "duplicate_patient_record_birth_unknown": "Please contact your district care coordinator, the shifting facility or the patient themselves if you are not sure about the patient's year of birth.", "patient_transfer_birth_match_note": "Note: Year of birth must match the patient to process the transfer request.", - "cover_image_updated_note": "It could take a while to see the updated cover image", + "bed_type__100": "ICU Bed", + "bed_type__200": "Ordinary Bed", + "bed_type__300": "Oxygen Supported Bed", + "bed_type__400": "Isolation Bed", + "bed_type__500": "Others", "available_features": "Available Features", "update_facility": "Update Facility", "configure_facility": "Configure Facility", diff --git a/src/Locale/en/FileUpload.json b/src/Locale/en/FileUpload.json index 93b61943944..4b3097eb9d3 100644 --- a/src/Locale/en/FileUpload.json +++ b/src/Locale/en/FileUpload.json @@ -20,7 +20,9 @@ "file_list_headings__sample_report": "Sample Report", "file_list_headings__supporting_info": "Supporting Info", "file_error__choose_file": "Please choose a file to upload", - "file_error__file_name": "Please enter file name", + "file_error__file_name": "Please give a name for all files!", + "file_error__single_file_name": "Please give a name for the file", + "change_file": "Change File", "file_error__file_size": "Maximum size of files is 100 MB", "file_error__file_type": "Invalid file type \".{{extension}}\" Allowed types: {{allowedExtensions}}", "file_uploaded": "File Uploaded Successfully", diff --git a/src/Locale/en/HCX.json b/src/Locale/en/HCX.json new file mode 100644 index 00000000000..8c5d60c642d --- /dev/null +++ b/src/Locale/en/HCX.json @@ -0,0 +1,63 @@ +{ + "checking_policy_eligibility": "Checking Policy Eligibility", + "check_policy_eligibility": "Check Policy Eligibility", + "add_policy": "Add Insurance Policy", + "edit_policy": "Edit Insurance Policy", + "edit_policy_description": "Add or edit patient's insurance details", + "select_policy": "Select an Insurance Policy", + "select_eligible_policy": "Select an Eligible Insurance Policy", + "no_policy_found": "No Insurance Policy Found for this Patient", + "no_policy_added": "No Insurance Policy Added", + "checking_eligibility": "Checking Eligibility", + "check_eligibility": "Check Eligibility", + "eligible": "Eligible", + "not_eligible": "Not Eligible", + "policy": "Policy", + "policy__subscriber_id": "Member ID", + "policy__subscriber_id__example": "SUB001", + "policy__policy_id": "Policy ID / Policy Name", + "policy__policy_id__example": "POL001", + "policy__insurer": "Insurer", + "policy__insurer__example": "GICOFINDIA", + "policy__insurer_id": "Insurer ID", + "policy__insurer_id__example": "GICOFINDIA", + "policy__insurer_name": "Insurer Name", + "policy__insurer_name__example": "GIC OF INDIA", + "claims": "Claims", + "claim__item": "Item", + "claim__items": "Items", + "claim__add_item": "Add Item", + "claim__item__add_at_least_one": "Add at least one item", + "claim__item__fill_all_details": "Fill all the item details", + "claim__item__category": "Category", + "claim__item__procedure": "Procedure", + "claim__item__id": "ID", + "claim__item__id__example": "PROC001", + "claim__item__name": "Name", + "claim__item__name__example": "Knee Replacement", + "claim__item__price": "Price", + "claim__item__price__example": "100.00", + "claim__failed_to_create_preauthorization": "Failed to create Pre Authorization", + "claim__failed_to_create_claim": "Failed to create Claim", + "claim__creating_preauthorization": "Creating Pre Authorization", + "claim__creating_claim": "Creating Claim", + "claim__create_preauthorization": "Create Pre Authorization", + "claim__create_claim": "Create Claim", + "claim__use": "Use", + "claim__use__preauthorization": "Pre Authorization", + "claim__use__claim": "Claim", + "select_policy_to_add_items": "Select a Policy to Add Items", + "total_amount": "Total Amount", + "claim__status__rejected": "Rejected", + "claim__status__approved": "Approved", + "claim__status__pending": "Pending", + "claim__total_claim_amount": "Total Claim Amount", + "claim__total_approved_amount": "Total Approved Amount", + "communication__sent_to_hcx": "Sent communication to HCX", + "fetched_attachments_successfully": "Fetched attachments successfully", + "add_attachments": "Add Attachments", + "claim__request_claim": "Request Claim", + "claim__requesting_claim": "Requesting Claim", + "claim__fetched_claim_approval_results": "Fetched Claim Approval Results", + "claim__error_fetching_claim_approval_results": "Error Fetching Claim Approval Results" +} diff --git a/src/Locale/en/Notifications.json b/src/Locale/en/Notifications.json index dac9c29730f..9e55faa1371 100644 --- a/src/Locale/en/Notifications.json +++ b/src/Locale/en/Notifications.json @@ -4,6 +4,8 @@ "mark_as_unread": "Mark as Unread", "subscribe": "Subscribe", "subscribe_on_this_device": "Subscribe on this device", + "notification_permission_denied": "Notification permission denied", + "notification_permission_granted": "Notification permission granted", "show_unread_notifications": "Show Unread", "show_all_notifications": "Show All", "filter_by_category": "Filter by category", @@ -19,4 +21,4 @@ "loading": "Loading...", "invalid_asset_id_msg": "Oops! The asset ID you entered does not appear to be valid.", "asset_not_found_msg": "Oops! The asset you are looking for does not exist. Please check the asset id." -} +} \ No newline at end of file diff --git a/src/Locale/en/Resource.json b/src/Locale/en/Resource.json index cc36bdb5d40..76b48971cd6 100644 --- a/src/Locale/en/Resource.json +++ b/src/Locale/en/Resource.json @@ -8,5 +8,7 @@ "required_quantity": "Required Quantity", "request_description": "Description of Request", "request_description_placeholder": "Type your description here", - "search_resource": "Search Resource" + "search_resource": "Search Resource", + "resource_origin_facility": "Origin Facility", + "resource_approving_facility" : "Resource approving facility" } diff --git a/src/Locale/en/Shifting.json b/src/Locale/en/Shifting.json index e1bc1916d89..89ca63eb252 100644 --- a/src/Locale/en/Shifting.json +++ b/src/Locale/en/Shifting.json @@ -24,7 +24,7 @@ "modified_date": "Modified Date", "no_patients_to_show": "No patients to show.", "shifting_status": "Shifting status", - "transfer_to_receiving_facility": "TRANSFER TO RECEIVING FACILITY", + "transfer_to_receiving_facility": "Transfer to receiving facility", "confirm_transfer_complete": "Confirm Transfer Complete!", "mark_transfer_complete_confirmation": "Are you sure you want to mark this transfer as complete? The Origin facility will no longer have access to this patient", "board_view": "Board View", diff --git a/src/Locale/en/SortOptions.json b/src/Locale/en/SortOptions.json deleted file mode 100644 index 29a8d1e8f4d..00000000000 --- a/src/Locale/en/SortOptions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "-created_date": "Latest created date first", - "created_date": "Oldest created date first", - "-category_severity": "Highest Severity category first", - "category_severity": "Lowest Severity category first", - "-modified_date": "Latest updated date first", - "modified_date": "Oldest updated date first", - "facility__name,last_consultation__current_bed__bed__name": "Bed No. 1-N", - "facility__name,-last_consultation__current_bed__bed__name": "Bed No. N-1", - "-review_time": "Latest review date first", - "review_time": "Oldest review date first", - "taken_at": "Oldest taken date first", - "-taken_at": "Latest taken date first", - "name": "Patient name A-Z", - "-name": "Patient name Z-A", - "bed__name": "Bed No. 1-N", - "-bed__name": "Bed No. N-1" -} diff --git a/src/Locale/en/index.js b/src/Locale/en/index.js index 0ad729cadbe..808520605ed 100644 --- a/src/Locale/en/index.js +++ b/src/Locale/en/index.js @@ -9,6 +9,8 @@ import Entities from "./Entities.json"; import ErrorPages from "./ErrorPages.json"; import ExternalResult from "./ExternalResult.json"; import Facility from "./Facility.json"; +import FileUpload from "./FileUpload.json"; +import HCX from "./HCX.json"; import Hub from "./Hub.json"; import LogUpdate from "./LogUpdate.json"; import Medicine from "./Medicine.json"; @@ -16,9 +18,7 @@ import Notifications from "./Notifications.json"; import Patient from "./Patient.json"; import Resource from "./Resource.json"; import Shifting from "./Shifting.json"; -import SortOptions from "./SortOptions.json"; import Users from "./Users.json"; -import FileUpload from "./FileUpload.json"; export default { ...Auth, @@ -41,5 +41,5 @@ export default { ...Users, ...LogUpdate, ...FileUpload, - SortOptions, + ...HCX, }; diff --git a/src/Locale/hi/Common.json b/src/Locale/hi/Common.json index e881bb29d75..daa8781a47f 100644 --- a/src/Locale/hi/Common.json +++ b/src/Locale/hi/Common.json @@ -203,5 +203,23 @@ "deleted_successfully": "{{name}} सफलतापूर्वक हटा दिया गया", "delete_item": "{{name}}मिटाएँ", "unsupported_browser": "असमर्थित ब्राउज़र", - "unsupported_browser_description": "आपका ब्राउज़र ({{name}} संस्करण {{version}}) समर्थित नहीं है। कृपया अपने ब्राउज़र को नवीनतम संस्करण में अपडेट करें या सर्वोत्तम अनुभव के लिए समर्थित ब्राउज़र पर स्विच करें।" + "unsupported_browser_description": "आपका ब्राउज़र ({{name}} संस्करण {{version}}) समर्थित नहीं है। कृपया अपने ब्राउज़र को नवीनतम संस्करण में अपडेट करें या सर्वोत्तम अनुभव के लिए समर्थित ब्राउज़र पर स्विच करें।", + "SORT_OPTIONS__-created_date": "नवीनतम निर्माण तिथि पहले", + "SORT_OPTIONS__created_date": "सबसे पुरानी निर्माण तिथि पहले", + "SORT_OPTIONS__-category_severity": "सर्वोच्च गंभीरता श्रेणी पहले", + "SORT_OPTIONS__category_severity": "सबसे कम गंभीरता वाली श्रेणी पहले", + "SORT_OPTIONS__-modified_date": "नवीनतम अद्यतन तिथि पहले", + "SORT_OPTIONS__modified_date": "सबसे पुरानी अद्यतन तिथि पहले", + "SORT_OPTIONS__facility__name,last_consultation__current_bed__bed__name": "बिस्तर नं. 1-एन", + "SORT_OPTIONS__facility__name,-last_consultation__current_bed__bed__name": "बिस्तर संख्या एन-1", + "SORT_OPTIONS__-review_time": "नवीनतम समीक्षा तिथि पहले", + "SORT_OPTIONS__review_time": "सबसे पुरानी समीक्षा तिथि पहले", + "SORT_OPTIONS__taken_at": "सबसे पुरानी ली गई तारीख पहले", + "SORT_OPTIONS__-taken_at": "नवीनतम ली गई तिथि पहले", + "SORT_OPTIONS__name": "मरीज़ का नाम AZ", + "SORT_OPTIONS__-name": "मरीज का नाम ZA", + "SORT_OPTIONS__bed__name": "बिस्तर नं. 1-एन", + "SORT_OPTIONS__-bed__name": "बिस्तर संख्या एन-1", + "middleware_hostname": "मिडलवेयर होस्टनाम", + "local_ipaddress": "स्थानीय आईपी पता" } \ No newline at end of file diff --git a/src/Locale/hi/Facility.json b/src/Locale/hi/Facility.json index 67032df0cb9..622a6e7ce9f 100644 --- a/src/Locale/hi/Facility.json +++ b/src/Locale/hi/Facility.json @@ -100,7 +100,6 @@ "duplicate_patient_record_rejection": "मैं पुष्टि करता हूं कि जिस संदिग्ध/रोगी की सूची मैं बनाना चाहता हूं वह सूची में नहीं है।", "duplicate_patient_record_birth_unknown": "यदि आप रोगी के जन्म वर्ष के बारे में निश्चित नहीं हैं तो कृपया अपने जिला देखभाल समन्वयक, स्थानांतरण सुविधा या रोगी से संपर्क करें।", "patient_transfer_birth_match_note": "ध्यान दें: स्थानांतरण अनुरोध पर कार्रवाई करने के लिए जन्म का वर्ष मरीज से मेल खाना चाहिए।", - "cover_image_updated_note": "अपडेट की गई कवर छवि देखने में कुछ समय लग सकता है", "available_features": "उपलब्ध सुविधाएँ", "update_facility": "अद्यतन सुविधा", "configure_facility": "सुविधा कॉन्फ़िगर करें", diff --git a/src/Locale/hi/Notifications.json b/src/Locale/hi/Notifications.json index b6526ad8b11..ae311b64d5e 100644 --- a/src/Locale/hi/Notifications.json +++ b/src/Locale/hi/Notifications.json @@ -4,6 +4,8 @@ "mark_as_unread": "अपठित के रूप में चिह्नित करें", "subscribe": "सदस्यता लें", "subscribe_on_this_device": "इस डिवाइस पर सदस्यता लें", + "notification_permission_denied": "सूचना अनुमति नामंजूर", + "notification_permission_granted": "सूचना अनुमति प्रदान की गई", "show_unread_notifications": "अपठित दिखाएँ", "show_all_notifications": "सब दिखाएं", "filter_by_category": "श्रेणी के अनुसार फ़िल्टर करें", @@ -19,4 +21,4 @@ "loading": "लोड हो रहा है...", "invalid_asset_id_msg": "ओह! आपके द्वारा दर्ज की गई संपत्ति आईडी वैध नहीं लगती।", "asset_not_found_msg": "ओह! आप जिस संपत्ति की तलाश कर रहे हैं वह मौजूद नहीं है। कृपया संपत्ति आईडी की जाँच करें।" -} +} \ No newline at end of file diff --git a/src/Locale/hi/SortOptions.json b/src/Locale/hi/SortOptions.json deleted file mode 100644 index 46b319fac69..00000000000 --- a/src/Locale/hi/SortOptions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "-created_date": "नवीनतम निर्माण तिथि पहले", - "created_date": "सबसे पुरानी निर्माण तिथि पहले", - "-category_severity": "सर्वोच्च गंभीरता श्रेणी पहले", - "category_severity": "सबसे कम गंभीरता वाली श्रेणी पहले", - "-modified_date": "नवीनतम अद्यतन तिथि पहले", - "modified_date": "सबसे पुरानी अद्यतन तिथि पहले", - "facility__name,last_consultation__current_bed__bed__name": "बिस्तर नं. 1-एन", - "facility__name,-last_consultation__current_bed__bed__name": "बिस्तर संख्या एन-1", - "-review_time": "नवीनतम समीक्षा तिथि पहले", - "review_time": "सबसे पुरानी समीक्षा तिथि पहले", - "taken_at": "सबसे पुरानी ली गई तारीख पहले", - "-taken_at": "नवीनतम ली गई तिथि पहले", - "name": "मरीज़ का नाम AZ", - "-name": "मरीज का नाम ZA", - "bed__name": "बिस्तर नं. 1-एन", - "-bed__name": "बिस्तर संख्या एन-1" -} diff --git a/src/Locale/kn/Common.json b/src/Locale/kn/Common.json index 6e136b1e906..02b00db4891 100644 --- a/src/Locale/kn/Common.json +++ b/src/Locale/kn/Common.json @@ -203,5 +203,23 @@ "deleted_successfully": "{{name}} ಅನ್ನು ಯಶಸ್ವಿಯಾಗಿ ಅಳಿಸಲಾಗಿದೆ", "delete_item": "{{name}}ಅಳಿಸಿ", "unsupported_browser": "ಬೆಂಬಲಿತವಲ್ಲದ ಬ್ರೌಸರ್", - "unsupported_browser_description": "ನಿಮ್ಮ ಬ್ರೌಸರ್ ({{name}} ಆವೃತ್ತಿ {{version}}) ಬೆಂಬಲಿತವಾಗಿಲ್ಲ. ದಯವಿಟ್ಟು ನಿಮ್ಮ ಬ್ರೌಸರ್ ಅನ್ನು ಇತ್ತೀಚಿನ ಆವೃತ್ತಿಗೆ ನವೀಕರಿಸಿ ಅಥವಾ ಉತ್ತಮ ಅನುಭವಕ್ಕಾಗಿ ಬೆಂಬಲಿತ ಬ್ರೌಸರ್‌ಗೆ ಬದಲಿಸಿ." + "SORT_OPTIONS__unsupported_browser_description": "ನಿಮ್ಮ ಬ್ರೌಸರ್ ({{name}} ಆವೃತ್ತಿ {{version}}) ಬೆಂಬಲಿತವಾಗಿಲ್ಲ. ದಯವಿಟ್ಟು ನಿಮ್ಮ ಬ್ರೌಸರ್ ಅನ್ನು ಇತ್ತೀಚಿನ ಆವೃತ್ತಿಗೆ ನವೀಕರಿಸಿ ಅಥವಾ ಉತ್ತಮ ಅನುಭವಕ್ಕಾಗಿ ಬೆಂಬಲಿತ ಬ್ರೌಸರ್‌ಗೆ ಬದಲಿಸಿ.", + "SORT_OPTIONS__-created_date": "ಇತ್ತೀಚಿಗೆ ರಚಿಸಿದ ದಿನಾಂಕ ಮೊದಲು", + "SORT_OPTIONS__created_date": "ಮೊದಲು ರಚಿಸಿದ ಹಳೆಯ ದಿನಾಂಕ", + "SORT_OPTIONS__-category_severity": "ಅತ್ಯಧಿಕ ತೀವ್ರತೆಯ ವಿಭಾಗ ಮೊದಲು", + "SORT_OPTIONS__category_severity": "ಕಡಿಮೆ ತೀವ್ರತೆಯ ವರ್ಗ ಮೊದಲು", + "SORT_OPTIONS__-modified_date": "ಮೊದಲು ಇತ್ತೀಚಿನ ನವೀಕರಿಸಿದ ದಿನಾಂಕ", + "SORT_OPTIONS__modified_date": "ಹಳೆಯ ನವೀಕರಿಸಿದ ದಿನಾಂಕ ಮೊದಲು", + "SORT_OPTIONS__facility__name,last_consultation__current_bed__bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ 1-N", + "SORT_OPTIONS__facility__name,-last_consultation__current_bed__bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ N-1", + "SORT_OPTIONS__-review_time": "ಇತ್ತೀಚಿನ ವಿಮರ್ಶೆ ದಿನಾಂಕ ಮೊದಲು", + "SORT_OPTIONS__review_time": "ಹಳೆಯ ವಿಮರ್ಶೆ ದಿನಾಂಕ ಮೊದಲು", + "SORT_OPTIONS__taken_at": "ಹಳೆಯ ತೆಗೆದ ದಿನಾಂಕ ಮೊದಲು", + "SORT_OPTIONS__-taken_at": "ಇತ್ತೀಚೆಗೆ ತೆಗೆದುಕೊಂಡ ದಿನಾಂಕ ಮೊದಲು", + "SORT_OPTIONS__name": "ರೋಗಿಯ ಹೆಸರು AZ", + "SORT_OPTIONS__-name": "ರೋಗಿಯ ಹೆಸರು ZA", + "SORT_OPTIONS__bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ 1-N", + "SORT_OPTIONS__-bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ N-1", + "middleware_hostname": "ಮಿಡಲ್ವೇರ್ ಹೋಸ್ಟ್ ಹೆಸರು", + "local_ipaddress": "ಸ್ಥಳೀಯ IP ವಿಳಾಸ" } \ No newline at end of file diff --git a/src/Locale/kn/Facility.json b/src/Locale/kn/Facility.json index 5bc9128ae54..5994770bf92 100644 --- a/src/Locale/kn/Facility.json +++ b/src/Locale/kn/Facility.json @@ -100,7 +100,6 @@ "duplicate_patient_record_rejection": "ನಾನು ರಚಿಸಲು ಬಯಸುವ ಶಂಕಿತ / ರೋಗಿಯು ಪಟ್ಟಿಯಲ್ಲಿಲ್ಲ ಎಂದು ನಾನು ದೃಢೀಕರಿಸುತ್ತೇನೆ.", "duplicate_patient_record_birth_unknown": "ರೋಗಿಯ ಜನ್ಮ ವರ್ಷದ ಬಗ್ಗೆ ನಿಮಗೆ ಖಚಿತವಿಲ್ಲದಿದ್ದರೆ ದಯವಿಟ್ಟು ನಿಮ್ಮ ಜಿಲ್ಲಾ ಆರೈಕೆ ಸಂಯೋಜಕರು, ಸ್ಥಳಾಂತರ ಸೌಲಭ್ಯ ಅಥವಾ ರೋಗಿಯನ್ನು ಸಂಪರ್ಕಿಸಿ.", "patient_transfer_birth_match_note": "ಗಮನಿಸಿ: ವರ್ಗಾವಣೆ ವಿನಂತಿಯನ್ನು ಪ್ರಕ್ರಿಯೆಗೊಳಿಸಲು ಹುಟ್ಟಿದ ವರ್ಷವು ರೋಗಿಗೆ ಹೊಂದಿಕೆಯಾಗಬೇಕು.", - "cover_image_updated_note": "ನವೀಕರಿಸಿದ ಕವರ್ ಚಿತ್ರವನ್ನು ನೋಡಲು ಸ್ವಲ್ಪ ಸಮಯ ತೆಗೆದುಕೊಳ್ಳಬಹುದು", "available_features": "ಲಭ್ಯವಿರುವ ವೈಶಿಷ್ಟ್ಯಗಳು", "update_facility": "ನವೀಕರಣ ಸೌಲಭ್ಯ", "configure_facility": "ಸೌಲಭ್ಯವನ್ನು ಕಾನ್ಫಿಗರ್ ಮಾಡಿ", diff --git a/src/Locale/kn/Notifications.json b/src/Locale/kn/Notifications.json index 48ceadfa4c6..4799e16ed52 100644 --- a/src/Locale/kn/Notifications.json +++ b/src/Locale/kn/Notifications.json @@ -4,6 +4,8 @@ "mark_as_unread": "ಓದದಿರುವಂತೆ ಗುರುತಿಸಿ", "subscribe": "ಚಂದಾದಾರರಾಗಿ", "subscribe_on_this_device": "ಈ ಸಾಧನದಲ್ಲಿ ಚಂದಾದಾರರಾಗಿ", + "notification_permission_denied": "ಅಧಿಸೂಚನೆ ಅನುಮತಿ ನಿರಾಕರಿಸಲಾಗಿದೆ", + "notification_permission_granted": "ಅಧಿಸೂಚನೆ ಅನುಮತಿ ನೀಡಲಾಗಿದೆ", "show_unread_notifications": "ಓದದಿರುವುದನ್ನು ತೋರಿಸಿ", "show_all_notifications": "ಎಲ್ಲವನ್ನೂ ತೋರಿಸು", "filter_by_category": "ವರ್ಗದ ಪ್ರಕಾರ ಫಿಲ್ಟರ್ ಮಾಡಿ", @@ -19,4 +21,4 @@ "loading": "ಲೋಡ್ ಆಗುತ್ತಿದೆ...", "invalid_asset_id_msg": "ಓಹ್! ನೀವು ನಮೂದಿಸಿದ ಸ್ವತ್ತು ಐಡಿ ಮಾನ್ಯವಾಗಿರುವಂತೆ ತೋರುತ್ತಿಲ್ಲ.", "asset_not_found_msg": "ಓಹ್! ನೀವು ಹುಡುಕುತ್ತಿರುವ ಸ್ವತ್ತು ಅಸ್ತಿತ್ವದಲ್ಲಿಲ್ಲ. ದಯವಿಟ್ಟು ಸ್ವತ್ತಿನ ಐಡಿಯನ್ನು ಪರಿಶೀಲಿಸಿ." -} +} \ No newline at end of file diff --git a/src/Locale/kn/SortOptions.json b/src/Locale/kn/SortOptions.json deleted file mode 100644 index 1fb870664f2..00000000000 --- a/src/Locale/kn/SortOptions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "-created_date": "ಇತ್ತೀಚಿಗೆ ರಚಿಸಿದ ದಿನಾಂಕ ಮೊದಲು", - "created_date": "ಮೊದಲು ರಚಿಸಿದ ಹಳೆಯ ದಿನಾಂಕ", - "-category_severity": "ಅತ್ಯಧಿಕ ತೀವ್ರತೆಯ ವಿಭಾಗ ಮೊದಲು", - "category_severity": "ಕಡಿಮೆ ತೀವ್ರತೆಯ ವರ್ಗ ಮೊದಲು", - "-modified_date": "ಮೊದಲು ಇತ್ತೀಚಿನ ನವೀಕರಿಸಿದ ದಿನಾಂಕ", - "modified_date": "ಹಳೆಯ ನವೀಕರಿಸಿದ ದಿನಾಂಕ ಮೊದಲು", - "facility__name,last_consultation__current_bed__bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ 1-N", - "facility__name,-last_consultation__current_bed__bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ N-1", - "-review_time": "ಇತ್ತೀಚಿನ ವಿಮರ್ಶೆ ದಿನಾಂಕ ಮೊದಲು", - "review_time": "ಹಳೆಯ ವಿಮರ್ಶೆ ದಿನಾಂಕ ಮೊದಲು", - "taken_at": "ಹಳೆಯ ತೆಗೆದ ದಿನಾಂಕ ಮೊದಲು", - "-taken_at": "ಇತ್ತೀಚೆಗೆ ತೆಗೆದುಕೊಂಡ ದಿನಾಂಕ ಮೊದಲು", - "name": "ರೋಗಿಯ ಹೆಸರು AZ", - "-name": "ರೋಗಿಯ ಹೆಸರು ZA", - "bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ 1-N", - "-bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ N-1" -} diff --git a/src/Locale/kn/index.js b/src/Locale/kn/index.js index cbf1d10c3b9..5c2b9219ced 100644 --- a/src/Locale/kn/index.js +++ b/src/Locale/kn/index.js @@ -1,11 +1,37 @@ +import Asset from "./Asset.json"; import Auth from "./Auth.json"; +import Bed from "./Bed.json"; import Common from "./Common.json"; +import Consultation from "./Consultation.json"; +import CoverImageEdit from "./CoverImageEdit.json"; +import Diagnosis from "./Diagnosis.json"; import Entities from "./Entities.json"; +import ErrorPages from "./ErrorPages.json"; +import ExternalResult from "./ExternalResult.json"; import Facility from "./Facility.json"; +import FileUpload from "./FileUpload.json"; +import Hub from "./Hub.json"; +import Medicine from "./Medicine.json"; +import Notifications from "./Notifications.json"; +import Shifting from "./Shifting.json"; +import Users from "./Users.json"; export default { ...Auth, + ...Asset, ...Common, + ...Consultation, + ...CoverImageEdit, ...Entities, + ...ErrorPages, + ...ExternalResult, ...Facility, + ...Hub, + ...Medicine, + ...Diagnosis, + ...Notifications, + ...Shifting, + ...Bed, + ...Users, + ...FileUpload, }; diff --git a/src/Locale/ml/Common.json b/src/Locale/ml/Common.json index 7e77900e2cb..0c861324515 100644 --- a/src/Locale/ml/Common.json +++ b/src/Locale/ml/Common.json @@ -203,5 +203,23 @@ "deleted_successfully": "{{name}} വിജയകരമായി ഇല്ലാതാക്കി", "delete_item": "{{name}}ഇല്ലാതാക്കുക", "unsupported_browser": "പിന്തുണയ്‌ക്കാത്ത ബ്രൗസർ", - "unsupported_browser_description": "നിങ്ങളുടെ ബ്രൗസർ ({{name}} പതിപ്പ് {{version}}) പിന്തുണയ്ക്കുന്നില്ല. ഏറ്റവും പുതിയ പതിപ്പിലേക്ക് നിങ്ങളുടെ ബ്രൗസർ അപ്‌ഡേറ്റ് ചെയ്യുക അല്ലെങ്കിൽ മികച്ച അനുഭവത്തിനായി പിന്തുണയ്‌ക്കുന്ന ബ്രൗസറിലേക്ക് മാറുക." + "unsupported_browser_description": "നിങ്ങളുടെ ബ്രൗസർ ({{name}} പതിപ്പ് {{version}}) പിന്തുണയ്ക്കുന്നില്ല. ഏറ്റവും പുതിയ പതിപ്പിലേക്ക് നിങ്ങളുടെ ബ്രൗസർ അപ്‌ഡേറ്റ് ചെയ്യുക അല്ലെങ്കിൽ മികച്ച അനുഭവത്തിനായി പിന്തുണയ്‌ക്കുന്ന ബ്രൗസറിലേക്ക് മാറുക.", + "SORT_OPTIONS__-created_date": "ആദ്യം സൃഷ്ടിച്ച ഏറ്റവും പുതിയ തീയതി", + "SORT_OPTIONS__created_date": "ഏറ്റവും പഴയ സൃഷ്ടിച്ച തീയതി ആദ്യം", + "SORT_OPTIONS__-category_severity": "ഏറ്റവും ഉയർന്ന തീവ്രത വിഭാഗം ആദ്യം", + "SORT_OPTIONS__category_severity": "ഏറ്റവും കുറഞ്ഞ തീവ്രത വിഭാഗം ആദ്യം", + "SORT_OPTIONS__-modified_date": "ഏറ്റവും പുതിയ അപ്ഡേറ്റ് തീയതി ആദ്യം", + "SORT_OPTIONS__modified_date": "ഏറ്റവും പഴയ പുതുക്കിയ തീയതി ആദ്യം", + "SORT_OPTIONS__facility__name,last_consultation__current_bed__bed__name": "ബെഡ് നമ്പർ 1-N", + "SORT_OPTIONS__facility__name,-last_consultation__current_bed__bed__name": "കിടക്ക നമ്പർ N-1", + "SORT_OPTIONS__-review_time": "ഏറ്റവും പുതിയ അവലോകന തീയതി ആദ്യം", + "SORT_OPTIONS__review_time": "ഏറ്റവും പഴയ അവലോകന തീയതി ആദ്യം", + "SORT_OPTIONS__taken_at": "ആദ്യം എടുത്ത ഏറ്റവും പഴയ തീയതി", + "SORT_OPTIONS__-taken_at": "ഏറ്റവും പുതിയ തീയതി ആദ്യം", + "SORT_OPTIONS__name": "രോഗിയുടെ പേര് AZ", + "SORT_OPTIONS__-name": "രോഗിയുടെ പേര് ZA", + "SORT_OPTIONS__bed__name": "ബെഡ് നമ്പർ 1-N", + "SORT_OPTIONS__-bed__name": "കിടക്ക നമ്പർ N-1", + "middleware_hostname": "മിഡിൽവെയർ ഹോസ്റ്റ്നാമം", + "local_ipaddress": "പ്രാദേശിക ഐപി വിലാസം" } \ No newline at end of file diff --git a/src/Locale/ml/Facility.json b/src/Locale/ml/Facility.json index 5a73d4473e5..00eb45d7f1b 100644 --- a/src/Locale/ml/Facility.json +++ b/src/Locale/ml/Facility.json @@ -100,7 +100,6 @@ "duplicate_patient_record_rejection": "ഞാൻ സൃഷ്ടിക്കാൻ ആഗ്രഹിക്കുന്ന സംശയാസ്പദമായ / രോഗി ലിസ്റ്റിൽ ഇല്ലെന്ന് ഞാൻ സ്ഥിരീകരിക്കുന്നു.", "duplicate_patient_record_birth_unknown": "രോഗിയുടെ ജനന വർഷത്തെക്കുറിച്ച് നിങ്ങൾക്ക് ഉറപ്പില്ലെങ്കിൽ നിങ്ങളുടെ ജില്ലാ പരിചരണ കോർഡിനേറ്റർ, ഷിഫ്റ്റിംഗ് സൗകര്യം അല്ലെങ്കിൽ രോഗിയെ ബന്ധപ്പെടുക.", "patient_transfer_birth_match_note": "ശ്രദ്ധിക്കുക: ട്രാൻസ്ഫർ അഭ്യർത്ഥന പ്രോസസ്സ് ചെയ്യുന്നതിന് ജനന വർഷം രോഗിയുമായി പൊരുത്തപ്പെടണം.", - "cover_image_updated_note": "പുതുക്കിയ മുഖചിത്രം കാണാൻ കുറച്ച് സമയമെടുത്തേക്കാം", "available_features": "ലഭ്യമായ സവിശേഷതകൾ", "update_facility": "അപ്ഡേറ്റ് സൗകര്യം", "configure_facility": "സൗകര്യം ക്രമീകരിക്കുക", diff --git a/src/Locale/ml/Notifications.json b/src/Locale/ml/Notifications.json index bc96457ecf3..e27a56aba6b 100644 --- a/src/Locale/ml/Notifications.json +++ b/src/Locale/ml/Notifications.json @@ -4,6 +4,8 @@ "mark_as_unread": "വായിക്കാത്തതായി അടയാളപ്പെടുത്തുക", "subscribe": "സബ്സ്ക്രൈബ് ചെയ്യുക", "subscribe_on_this_device": "ഈ ഉപകരണത്തിൽ സബ്സ്ക്രൈബ് ചെയ്യുക", + "notification_permission_denied": "അറിയിപ്പ് അനുമതി നിഷേധിച്ചു", + "notification_permission_granted": "അറിയിപ്പ് അനുമതി നൽകി", "show_unread_notifications": "വായിക്കാത്തത് കാണിക്കുക", "show_all_notifications": "എല്ലാം കാണിക്കുക", "filter_by_category": "വിഭാഗം അനുസരിച്ച് ഫിൽട്ടർ ചെയ്യുക", @@ -19,4 +21,4 @@ "loading": "ലോഡ് ചെയ്യുന്നു...", "invalid_asset_id_msg": "ശ്ശോ! നിങ്ങൾ നൽകിയ അസറ്റ് ഐഡി സാധുതയുള്ളതായി കാണുന്നില്ല.", "asset_not_found_msg": "ശ്ശോ! നിങ്ങൾ അന്വേഷിക്കുന്ന അസറ്റ് നിലവിലില്ല. അസറ്റ് ഐഡി പരിശോധിക്കുക." -} +} \ No newline at end of file diff --git a/src/Locale/ml/SortOptions.json b/src/Locale/ml/SortOptions.json deleted file mode 100644 index 97eb76e6160..00000000000 --- a/src/Locale/ml/SortOptions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "-created_date": "ആദ്യം സൃഷ്ടിച്ച ഏറ്റവും പുതിയ തീയതി", - "created_date": "ഏറ്റവും പഴയ സൃഷ്ടിച്ച തീയതി ആദ്യം", - "-category_severity": "ഏറ്റവും ഉയർന്ന തീവ്രത വിഭാഗം ആദ്യം", - "category_severity": "ഏറ്റവും കുറഞ്ഞ തീവ്രത വിഭാഗം ആദ്യം", - "-modified_date": "ഏറ്റവും പുതിയ അപ്ഡേറ്റ് തീയതി ആദ്യം", - "modified_date": "ഏറ്റവും പഴയ പുതുക്കിയ തീയതി ആദ്യം", - "facility__name,last_consultation__current_bed__bed__name": "ബെഡ് നമ്പർ 1-N", - "facility__name,-last_consultation__current_bed__bed__name": "കിടക്ക നമ്പർ N-1", - "-review_time": "ഏറ്റവും പുതിയ അവലോകന തീയതി ആദ്യം", - "review_time": "ഏറ്റവും പഴയ അവലോകന തീയതി ആദ്യം", - "taken_at": "ആദ്യം എടുത്ത ഏറ്റവും പഴയ തീയതി", - "-taken_at": "ഏറ്റവും പുതിയ തീയതി ആദ്യം", - "name": "രോഗിയുടെ പേര് AZ", - "-name": "രോഗിയുടെ പേര് ZA", - "bed__name": "ബെഡ് നമ്പർ 1-N", - "-bed__name": "കിടക്ക നമ്പർ N-1" -} diff --git a/src/Locale/ta/Common.json b/src/Locale/ta/Common.json index abf6427ce9c..770772c883a 100644 --- a/src/Locale/ta/Common.json +++ b/src/Locale/ta/Common.json @@ -203,5 +203,23 @@ "deleted_successfully": "{{name}} வெற்றிகரமாக நீக்கப்பட்டது", "delete_item": "{{name}}ஐ நீக்கவும்", "unsupported_browser": "ஆதரிக்கப்படாத உலாவி", - "unsupported_browser_description": "உங்கள் உலாவி ({{name}} பதிப்பு {{version}}) ஆதரிக்கப்படவில்லை. உங்கள் உலாவியை சமீபத்திய பதிப்பிற்கு புதுப்பிக்கவும் அல்லது சிறந்த அனுபவத்திற்காக ஆதரிக்கப்படும் உலாவிக்கு மாறவும்." + "unsupported_browser_description": "உங்கள் உலாவி ({{name}} பதிப்பு {{version}}) ஆதரிக்கப்படவில்லை. உங்கள் உலாவியை சமீபத்திய பதிப்பிற்கு புதுப்பிக்கவும் அல்லது சிறந்த அனுபவத்திற்காக ஆதரிக்கப்படும் உலாவிக்கு மாறவும்.", + "SORT_OPTIONS__-created_date": "புதிதாக உருவாக்கப்பட்ட தேதி முதலில்", + "SORT_OPTIONS__created_date": "பழைய உருவாக்கப்பட்ட தேதி முதலில்", + "SORT_OPTIONS__-category_severity": "அதிக தீவிரத்தன்மை பிரிவு முதலில்", + "SORT_OPTIONS__category_severity": "குறைந்த தீவிரத்தன்மை வகை முதலில்", + "SORT_OPTIONS__-modified_date": "முதலில் புதுப்பிக்கப்பட்ட தேதி", + "SORT_OPTIONS__modified_date": "பழைய புதுப்பிக்கப்பட்ட தேதி முதலில்", + "SORT_OPTIONS__facility__name,last_consultation__current_bed__bed__name": "படுக்கை எண் 1-N", + "SORT_OPTIONS__facility__name,-last_consultation__current_bed__bed__name": "படுக்கை எண். N-1", + "SORT_OPTIONS__-review_time": "சமீபத்திய மதிப்பாய்வு தேதி முதலில்", + "SORT_OPTIONS__review_time": "பழைய மதிப்பாய்வு தேதி முதலில்", + "SORT_OPTIONS__taken_at": "முதலில் எடுக்கப்பட்ட பழைய தேதி", + "SORT_OPTIONS__-taken_at": "சமீபத்தில் எடுக்கப்பட்ட தேதி முதலில்", + "SORT_OPTIONS__name": "நோயாளியின் பெயர் AZ", + "SORT_OPTIONS__-name": "நோயாளியின் பெயர் ZA", + "SORT_OPTIONS__bed__name": "படுக்கை எண் 1-N", + "SORT_OPTIONS__-bed__name": "படுக்கை எண். N-1", + "middleware_hostname": "மிடில்வேர் ஹோஸ்ட்பெயர்", + "local_ipaddress": "உள்ளூர் ஐபி முகவரி" } \ No newline at end of file diff --git a/src/Locale/ta/Facility.json b/src/Locale/ta/Facility.json index e1c5462f587..f8c6b976296 100644 --- a/src/Locale/ta/Facility.json +++ b/src/Locale/ta/Facility.json @@ -100,7 +100,6 @@ "duplicate_patient_record_rejection": "நான் உருவாக்க விரும்பும் சந்தேக நபர் / நோயாளி பட்டியலில் இல்லை என்பதை உறுதிப்படுத்துகிறேன்.", "duplicate_patient_record_birth_unknown": "நோயாளியின் பிறந்த ஆண்டு குறித்து உங்களுக்குத் தெரியாவிட்டால், உங்கள் மாவட்ட பராமரிப்பு ஒருங்கிணைப்பாளர், இடமாற்றம் செய்யும் வசதி அல்லது நோயாளியைத் தொடர்பு கொள்ளவும்.", "patient_transfer_birth_match_note": "குறிப்பு: பரிமாற்றக் கோரிக்கையைச் செயல்படுத்த, பிறந்த ஆண்டு நோயாளியுடன் பொருந்த வேண்டும்.", - "cover_image_updated_note": "புதுப்பிக்கப்பட்ட அட்டைப் படத்தைப் பார்க்க சிறிது நேரம் ஆகலாம்", "available_features": "கிடைக்கும் அம்சங்கள்", "update_facility": "மேம்படுத்தல் வசதி", "configure_facility": "வசதியை உள்ளமைக்கவும்", diff --git a/src/Locale/ta/Notifications.json b/src/Locale/ta/Notifications.json index a372a092286..4bf38d60b93 100644 --- a/src/Locale/ta/Notifications.json +++ b/src/Locale/ta/Notifications.json @@ -4,6 +4,8 @@ "mark_as_unread": "படிக்காதது எனக் குறி", "subscribe": "குழுசேர்", "subscribe_on_this_device": "இந்தச் சாதனத்தில் குழுசேரவும்", + "notification_permission_denied": "அறிவிப்பு அனுமதி நிராகரிக்கப்பட்டது", + "notification_permission_granted": "அறிவிப்பு அனுமதி அளிக்கப்பட்டது", "show_unread_notifications": "படிக்காததைக் காட்டு", "show_all_notifications": "அனைத்தையும் காட்டு", "filter_by_category": "வகையின்படி வடிகட்டவும்", @@ -19,4 +21,4 @@ "loading": "ஏற்றுகிறது...", "invalid_asset_id_msg": "அச்சச்சோ! நீங்கள் உள்ளிட்ட சொத்து ஐடி சரியானதாகத் தெரியவில்லை.", "asset_not_found_msg": "அச்சச்சோ! நீங்கள் தேடும் சொத்து இல்லை. சொத்து ஐடியைச் சரிபார்க்கவும்." -} +} \ No newline at end of file diff --git a/src/Locale/ta/SortOptions.json b/src/Locale/ta/SortOptions.json deleted file mode 100644 index a2f85b6df49..00000000000 --- a/src/Locale/ta/SortOptions.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "-created_date": "புதிதாக உருவாக்கப்பட்ட தேதி முதலில்", - "created_date": "பழைய உருவாக்கப்பட்ட தேதி முதலில்", - "-category_severity": "அதிக தீவிரத்தன்மை பிரிவு முதலில்", - "category_severity": "குறைந்த தீவிரத்தன்மை வகை முதலில்", - "-modified_date": "முதலில் புதுப்பிக்கப்பட்ட தேதி", - "modified_date": "பழைய புதுப்பிக்கப்பட்ட தேதி முதலில்", - "facility__name,last_consultation__current_bed__bed__name": "படுக்கை எண் 1-N", - "facility__name,-last_consultation__current_bed__bed__name": "படுக்கை எண். N-1", - "-review_time": "சமீபத்திய மதிப்பாய்வு தேதி முதலில்", - "review_time": "பழைய மதிப்பாய்வு தேதி முதலில்", - "taken_at": "முதலில் எடுக்கப்பட்ட பழைய தேதி", - "-taken_at": "சமீபத்தில் எடுக்கப்பட்ட தேதி முதலில்", - "name": "நோயாளியின் பெயர் AZ", - "-name": "நோயாளியின் பெயர் ZA", - "bed__name": "படுக்கை எண் 1-N", - "-bed__name": "படுக்கை எண். N-1" -} diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 05a6cf18dc4..47d3530d065 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -1,4 +1,3 @@ -import { HCXClaimModel, HCXPolicyModel } from "../Components/HCX/models"; import { fireRequest } from "./fireRequest"; // asset bed @@ -25,27 +24,6 @@ export const deleteAssetBed = (asset_id: string) => }, ); -// Download Actions -export const downloadFacility = () => { - return fireRequest("downloadFacility"); -}; - -export const downloadFacilityCapacity = () => { - return fireRequest("downloadFacilityCapacity"); -}; - -export const downloadFacilityDoctors = () => { - return fireRequest("downloadFacilityDoctors"); -}; - -export const downloadFacilityTriage = () => { - return fireRequest("downloadFacilityTriage"); -}; - -//Patient -export const getAllPatient = (params: object, altKey: string) => { - return fireRequest("patientList", [], params, null, altKey); -}; export const getPatient = (pathParam: object) => { return fireRequest("getPatient", [], {}, pathParam); }; @@ -55,11 +33,6 @@ export const getDistrictByName = (params: object) => { return fireRequest("getDistrictByName", [], params, null); }; -// Sample Test -export const downloadSampleTests = (params: object) => { - return fireRequest("getTestSampleList", [], { ...params, csv: 1 }); -}; - // Consultation export const getConsultation = (id: string) => { return fireRequest("getConsultation", [], {}, { id: id }); @@ -69,108 +42,5 @@ export const dischargePatient = (params: object, pathParams: object) => { return fireRequest("dischargePatient", [], params, pathParams); }; -//Shift -export const listShiftRequests = (params: object, key: string) => { - return fireRequest("listShiftRequests", [], params, null, key); -}; - -export const downloadShiftRequests = (params: object) => { - return fireRequest("downloadShiftRequests", [], params); -}; - -// External Results -export const externalResultList = (params: object, altKey: string) => { - return fireRequest("externalResultList", [], params, null, altKey); -}; - -// Resource -export const downloadResourceRequests = (params: object) => { - return fireRequest("downloadResourceRequests", [], params); -}; - -export const listAssets = (params: object) => - fireRequest("listAssets", [], params); export const operateAsset = (id: string, params: object) => fireRequest("operateAsset", [], params, { external_id: id }); - -export const listPMJYPackages = (query?: string) => - fireRequest("listPMJYPackages", [], { query }); - -// HCX Actions -export const HCXActions = { - checkEligibility: (policy: string) => { - return fireRequest("hcxCheckEligibility", [], { policy }); - }, - - payors: { - list(query: string) { - return fireRequest("hcxListPayors", [], { query }); - }, - }, - - policies: { - list(params: object) { - return fireRequest("listHCXPolicies", [], params); - }, - create(obj: HCXPolicyModel) { - return fireRequest("createHCXPolicy", [], obj); - }, - read(id: string) { - return fireRequest("getHCXPolicy", [], {}, { external_id: id }); - }, - update(id: string, obj: HCXPolicyModel) { - return fireRequest("updateHCXPolicy", [], obj, { external_id: id }); - }, - partialUpdate(id: string, obj: Partial) { - return fireRequest("partialUpdateHCXPolicy", [], obj, { - external_id: id, - }); - }, - delete(id: string) { - return fireRequest("deleteHCXPolicy", [], {}, { external_id: id }); - }, - }, - - claims: { - list(params: object) { - return fireRequest("listHCXClaims", [], params); - }, - create(obj: object) { - return fireRequest("createHCXClaim", [], obj); - }, - read(id: string) { - return fireRequest("getHCXClaim", [], {}, { external_id: id }); - }, - update(id: string, obj: HCXClaimModel) { - return fireRequest("updateHCXClaim", [], obj, { external_id: id }); - }, - partialUpdate(id: string, obj: Partial) { - return fireRequest("partialUpdateHCXClaim", [], obj, { - external_id: id, - }); - }, - delete(id: string) { - return fireRequest("deleteHCXClaim", [], {}, { external_id: id }); - }, - }, - - preauths: { - list(consultation: string) { - return fireRequest( - "listHCXClaims", - [], - { - consultation, - ordering: "-modified_date", - use: "preauthorization", - }, - {}, - `listPreAuths-${consultation}`, - ); - }, - }, - - makeClaim(claim: string) { - return fireRequest("hcxMakeClaim", [], { claim }); - }, -}; diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 228b24c7360..a0c3271386d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -3,25 +3,6 @@ import { CreateConsentTBody, } from "../Components/ABDM/types/consent"; import { HealthInformationModel } from "../Components/ABDM/types/health-information"; -import { - IAadhaarOtp, - IAadhaarOtpTBody, - ICheckAndGenerateMobileOtp, - IConfirmMobileOtp, - IcreateHealthFacilityTBody, - ICreateHealthIdRequest, - ICreateHealthIdResponse, - IGenerateMobileOtpTBody, - IgetAbhaCardTBody, - IHealthFacility, - IHealthId, - IinitiateAbdmAuthenticationTBody, - ILinkABHANumber, - ILinkViaQRBody, - IpartialUpdateHealthFacilityTBody, - ISearchByHealthIdTBody, - IVerifyAadhaarOtpTBody, -} from "../Components/ABDM/models"; import { AssetBedBody, AssetBedModel, @@ -33,15 +14,6 @@ import { AvailabilityRecord, PatientAssetBed, } from "../Components/Assets/AssetTypes"; -import { - IDeleteBedCapacity, - IDeleteExternalResult, - IExternalResult, - IExternalResultCsv, - ILocalBodies, - ILocalBodyByDistrict, - IPartialUpdateExternalResult, -} from "../Components/ExternalResult/models"; import { BedModel, CapacityModal, @@ -52,40 +24,88 @@ import { DailyRoundsRes, DistrictModel, DoctorModal, - DupPatientModel, FacilityModel, FacilityRequest, + FacilitySpokeModel, + FacilitySpokeRequest, IFacilityNotificationRequest, IFacilityNotificationResponse, + IUserFacilityRequest, InventoryItemsModel, InventoryLogResponse, InventorySummaryResponse, - IUserFacilityRequest, LocalBodyModel, LocationModel, MinimumQuantityItemResponse, - PatientConsentModel, PatientNotesEditModel, PatientNotesModel, PatientStatsModel, - PatientTransferRequest, PatientTransferResponse, StateModel, WardModel, } from "../Components/Facility/models"; +import { + DailyRoundsModel, + PatientModel, + SampleReportModel, + SampleTestModel, +} from "../Components/Patient/models"; +import { + IAadhaarOtp, + IAadhaarOtpTBody, + ICheckAndGenerateMobileOtp, + IConfirmMobileOtp, + ICreateHealthIdRequest, + ICreateHealthIdResponse, + IGenerateMobileOtpTBody, + IHealthFacility, + IHealthId, + ILinkABHANumber, + ILinkViaQRBody, + ISearchByHealthIdTBody, + IVerifyAadhaarOtpTBody, + IcreateHealthFacilityTBody, + IgetAbhaCardTBody, + IinitiateAbdmAuthenticationTBody, + IpartialUpdateHealthFacilityTBody, +} from "../Components/ABDM/models"; +import { IComment, IResource } from "../Components/Resource/models"; +import { + IDeleteBedCapacity, + IDeleteExternalResult, + IExternalResult, + IExternalResultCsv, + ILocalBodies, + ILocalBodyByDistrict, + IPartialUpdateExternalResult, +} from "../Components/ExternalResult/models"; +import { + InvestigationGroup, + InvestigationType, +} from "../Components/Facility/Investigations"; +import { + DupPatientModel, + PatientConsentModel, + PatientTransferRequest, +} from "../Components/Facility/models"; import { MedibaseMedicine, Prescription } from "../Components/Medicine/models"; import { NotificationData, PNconfigData, } from "../Components/Notifications/models"; +import { + HCXClaimModel, + HCXCommunicationModel, + HCXPolicyModel, +} from "../Components/HCX/models"; +import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; +import { IShift } from "../Components/Shifting/models"; +import { Investigation } from "../Components/Facility/Investigations/Reports/types"; +import { PaginatedResponse } from "../Utils/request/types"; import { CreateFileRequest, CreateFileResponse, - DailyRoundsModel, FileUploadModel, - PatientModel, - SampleReportModel, - SampleTestModel, } from "../Components/Patient/models"; import { SkillModel, @@ -94,24 +114,15 @@ import { UserAssignedModel, UserModel, } from "../Components/Users/models"; -import { PaginatedResponse } from "../Utils/request/types"; - -import { ICD11DiagnosisModel } from "../Components/Diagnosis/types"; import { EventGeneric, type Type, } from "../Components/Facility/ConsultationDetails/Events/types"; -import { - InvestigationGroup, - InvestigationType, -} from "../Components/Facility/Investigations"; import { InvestigationSessionType } from "../Components/Facility/Investigations/investigationsTab"; -import { Investigation } from "../Components/Facility/Investigations/Reports/types"; -import { HCXPolicyModel } from "../Components/HCX/models"; -import { IComment, IResource } from "../Components/Resource/models"; -import { IShift } from "../Components/Shifting/models"; import { AbhaNumberModel } from "../Components/ABDM/types/abha"; import { ScribeModel } from "../Components/Scribe/Scribe"; +import { InsurerOptionModel } from "../Components/HCX/InsurerAutocomplete"; +import { PMJAYPackageItem } from "../Components/Common/PMJAYProcedurePackageAutocomplete"; /** * A fake function that returns an empty object casted to type T @@ -357,7 +368,7 @@ const routes = { getPermittedFacility: { path: "/api/v1/facility/{id}/", method: "GET", - TRes: Type(), + TRes: Type(), }, getAnyFacility: { @@ -380,6 +391,38 @@ const routes = { TBody: Type>(), }, + getFacilitySpokes: { + path: "/api/v1/facility/{id}/spokes/", + method: "GET", + TRes: Type>(), + }, + + updateFacilitySpokes: { + path: "/api/v1/facility/{id}/spokes/{spoke_id}/", + method: "PATCH", + TRes: Type(), + TBody: Type(), + }, + + getFacilitySpoke: { + path: "/api/v1/facility/{id}/spokes/{spoke_id}/", + method: "GET", + TRes: Type(), + }, + + createFacilitySpoke: { + path: "/api/v1/facility/{id}/spokes/", + method: "POST", + TRes: Type(), + TBody: Type>(), + }, + + deleteFacilitySpoke: { + path: "/api/v1/facility/{id}/spokes/{spoke_id}/", + method: "DELETE", + TRes: Type>(), + }, + deleteFacilityCoverImage: { path: "/api/v1/facility/{id}/cover_image/", method: "DELETE", @@ -534,24 +577,29 @@ const routes = { downloadFacility: { path: "/api/v1/facility/?csv", method: "GET", + TRes: Type(), }, downloadFacilityCapacity: { path: "/api/v1/facility/?csv&capacity", method: "GET", + TRes: Type(), }, downloadFacilityDoctors: { path: "/api/v1/facility/?csv&doctors", method: "GET", + TRes: Type(), }, downloadFacilityTriage: { path: "/api/v1/facility/?csv&triage", method: "GET", + TRes: Type(), }, downloadPatients: { path: "/api/v1/patient/?csv", method: "GET", + TRes: Type(), }, getConsultationList: { path: "/api/v1/consultation/", @@ -765,7 +813,10 @@ const routes = { method: "POST", TRes: Type(), TBody: Type< - Pick & { consultation?: string } + Pick & { + consultation?: string; + reply_to?: string; + } >(), }, updatePatientNote: { @@ -1074,6 +1125,7 @@ const routes = { downloadShiftRequests: { path: "/api/v1/shift/", method: "GET", + TRes: Type(), }, getShiftComments: { path: "/api/v1/shift/{id}/comment/", @@ -1245,6 +1297,7 @@ const routes = { downloadResourceRequests: { path: "/api/v1/resource/", method: "GET", + TRes: Type(), }, getResourceComments: { path: "/api/v1/resource/{id}/comment/", @@ -1579,89 +1632,166 @@ const routes = { }, // HCX Endpoints + hcx: { + policies: { + list: { + path: "/api/hcx/policy/", + method: "GET", + TRes: Type>(), + }, + + create: { + path: "/api/hcx/policy/", + method: "POST", + TRes: Type(), + }, + + get: { + path: "/api/hcx/policy/{external_id}/", + method: "GET", + }, + + update: { + path: "/api/hcx/policy/{external_id}/", + method: "PUT", + TRes: Type(), + }, + + partialUpdate: { + path: "/api/hcx/policy/{external_id}/", + method: "PATCH", + }, + + delete: { + path: "/api/hcx/policy/{external_id}/", + method: "DELETE", + TRes: Type>(), + }, + + listPayors: { + path: "/api/hcx/payors/", + method: "GET", + TRes: Type(), + }, + + checkEligibility: { + path: "/api/hcx/check_eligibility/", + method: "POST", + TBody: Type<{ policy: string }>(), + TRes: Type(), + }, + }, - listPMJYPackages: { - path: "/api/v1/hcx/pmjy_packages/", - method: "GET", - }, - - hcxListPayors: { - path: "/api/v1/hcx/payors/", - method: "GET", - }, - - hcxCheckEligibility: { - path: "/api/v1/hcx/check_eligibility/", - method: "POST", - TRes: Type(), - }, - - listHCXPolicies: { - path: "/api/v1/hcx/policy/", - method: "GET", - TRes: Type>(), - }, - - createHCXPolicy: { - path: "/api/v1/hcx/policy/", - method: "POST", - TRes: Type(), - }, - - getHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "GET", - }, - - updateHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "PUT", - TRes: Type(), - }, - - partialUpdateHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "PATCH", - }, - - deleteHCXPolicy: { - path: "/api/v1/hcx/policy/{external_id}/", - method: "DELETE", - }, - - listHCXClaims: { - path: "/api/v1/hcx/claim/", - method: "GET", - }, - - createHCXClaim: { - path: "/api/v1/hcx/claim/", - method: "POST", - }, - - getHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "GET", - }, - - updateHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "PUT", - }, - - partialUpdateHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "PATCH", - }, - - deleteHCXClaim: { - path: "/api/v1/hcx/claim/{external_id}/", - method: "DELETE", - }, + claims: { + list: { + path: "/api/hcx/claim/", + method: "GET", + TRes: Type>(), + }, + + create: { + path: "/api/hcx/claim/", + method: "POST", + TBody: Type<{ + policy: string; + items: { + id: string; + price: number; + category?: string; + name: string; + }[]; + consultation: string; + use: "preauthorization" | "claim"; + }>(), + TRes: Type(), + }, + + get: { + path: "/api/hcx/claim/{external_id}/", + method: "GET", + }, + + update: { + path: "/api/hcx/claim/{external_id}/", + method: "PUT", + }, + + partialUpdate: { + path: "/api/hcx/claim/{external_id}/", + method: "PATCH", + }, + + delete: { + path: "/api/hcx/claim/{external_id}/", + method: "DELETE", + }, + + listPMJYPackages: { + path: "/api/hcx/pmjy_packages/", + method: "GET", + TRes: Type(), + }, + + makeClaim: { + path: "/api/hcx/make_claim/", + method: "POST", + TBody: Type<{ claim: string }>(), + TRes: Type(), + }, + }, - hcxMakeClaim: { - path: "/api/v1/hcx/make_claim/", - method: "POST", + communications: { + list: { + path: "/api/hcx/communication/", + method: "GET", + TRes: Type>(), + }, + + create: { + path: "/api/hcx/communication/", + method: "POST", + TRes: Type(), + TBody: Type<{ + claim: string; + content: { + type: string; + data: string; + }[]; + }>(), + }, + + get: { + path: "/api/hcx/communication/{external_id}/", + method: "GET", + TRes: Type(), + }, + + update: { + path: "/api/hcx/communication/{external_id}/", + method: "PUT", + TRes: Type(), + }, + + partialUpdate: { + path: "/api/hcx/communication/{external_id}/", + method: "PATCH", + TRes: Type(), + }, + + delete: { + path: "/api/hcx/communication/{external_id}/", + method: "DELETE", + }, + + send: { + path: "/api/hcx/send_communication/", + method: "POST", + TRes: Type(), + TBody: Type<{ + communication: string; + }>(), + }, + }, }, } as const; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index 60e0f9411d5..f5ca25f45b2 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -63,7 +63,7 @@ export default function AppRouter() { let routes = Routes; if (careConfig.hcx.enabled) { - routes = { ...routes, ...HCXRoutes }; + routes = { ...HCXRoutes, ...routes }; } if ( diff --git a/src/Routers/routes/HCXRoutes.tsx b/src/Routers/routes/HCXRoutes.tsx index 8a36e033c15..80378b24621 100644 --- a/src/Routers/routes/HCXRoutes.tsx +++ b/src/Routers/routes/HCXRoutes.tsx @@ -1,6 +1,10 @@ -import ConsultationClaims from "../../Components/Facility/ConsultationClaims"; +import ConsultationClaims, { + IConsultationClaimsProps, +} from "../../Components/Facility/ConsultationClaims"; export default { "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": - (pathParams: any) => , + (pathParams: IConsultationClaimsProps) => ( + + ), }; diff --git a/src/Routers/routes/ResourceRoutes.tsx b/src/Routers/routes/ResourceRoutes.tsx index 8408ab4d79d..6636d0d83ea 100644 --- a/src/Routers/routes/ResourceRoutes.tsx +++ b/src/Routers/routes/ResourceRoutes.tsx @@ -1,5 +1,3 @@ -import { DndProvider } from "react-dnd"; -import { HTML5Backend } from "react-dnd-html5-backend"; import ResourceDetails from "../../Components/Resource/ResourceDetails"; import { ResourceDetailsUpdate } from "../../Components/Resource/ResourceDetailsUpdate"; import ListView from "../../Components/Resource/ListView"; @@ -12,11 +10,7 @@ const getDefaultView = () => export default { "/resource": () => , - "/resource/board": () => ( - - - - ), + "/resource/board": () => , "/resource/list": () => , "/resource/:id": ({ id }: DetailRoute) => , "/resource/:id/update": ({ id }: DetailRoute) => ( diff --git a/src/Routers/routes/ShiftingRoutes.tsx b/src/Routers/routes/ShiftingRoutes.tsx index 9b20b4a1a0b..f2e12de4f25 100644 --- a/src/Routers/routes/ShiftingRoutes.tsx +++ b/src/Routers/routes/ShiftingRoutes.tsx @@ -1,5 +1,3 @@ -import { DndProvider } from "react-dnd"; -import { HTML5Backend } from "react-dnd-html5-backend"; import { ShiftCreate } from "../../Components/Patient/ShiftCreate"; import ShiftDetails from "../../Components/Shifting/ShiftDetails"; import { ShiftDetailsUpdate } from "../../Components/Shifting/ShiftDetailsUpdate"; @@ -12,11 +10,7 @@ const getDefaultView = () => export default { "/shifting": () => , - "/shifting/board": () => ( - - - - ), + "/shifting/board": () => , "/shifting/list": () => , "/shifting/:id": ({ id }: any) => , "/shifting/:id/update": ({ id }: any) => , diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index 188a2f7a61b..87de7991a6e 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -28,6 +28,7 @@ const notify = (text, type) => { sticker: false, }, stack: notifyStack, + delay: 3000, }); notification.refs.elem.addEventListener("click", () => { notification.close(); diff --git a/src/Utils/featureFlags.tsx b/src/Utils/featureFlags.tsx new file mode 100644 index 00000000000..739d49e821a --- /dev/null +++ b/src/Utils/featureFlags.tsx @@ -0,0 +1,78 @@ +import { createContext, useContext, useState, useEffect } from "react"; +import useQuery from "./request/useQuery"; +import routes from "../Redux/api"; +import useAuthUser from "../Common/hooks/useAuthUser"; +import { FacilityModel } from "../Components/Facility/models"; + +export type FeatureFlag = "SCRIBE_ENABLED"; // "HCX_ENABLED" | "ABDM_ENABLED" | + +export interface FeatureFlagsResponse { + user_flags: FeatureFlag[]; + facility_flags: { + facility: string; + features: FeatureFlag[]; + }[]; +} + +const defaultFlags: FeatureFlag[] = []; + +const FeatureFlagsContext = createContext({ + user_flags: defaultFlags, + facility_flags: [], +}); + +export const FeatureFlagsProvider = (props: { children: React.ReactNode }) => { + const [featureFlags, setFeatureFlags] = useState({ + user_flags: defaultFlags, + facility_flags: [], + }); + + const user = useAuthUser(); + + useEffect(() => { + if (user.user_flags) { + setFeatureFlags((ff) => ({ + ...ff, + user_flags: [...defaultFlags, ...(user.user_flags || [])], + })); + } + }, [user]); + + return ( + + {props.children} + + ); +}; + +export const useFeatureFlags = (facility?: FacilityModel | string) => { + const [facilityObject, setFacilityObject] = useState< + FacilityModel | undefined + >(typeof facility === "string" ? undefined : facility); + + const context = useContext(FeatureFlagsContext); + if (context === undefined) { + throw new Error( + "useFeatureFlags must be used within a FeatureFlagsProvider", + ); + } + + const facilityQuery = useQuery(routes.getPermittedFacility, { + pathParams: { + id: typeof facility === "string" ? facility : "", + }, + prefetch: false, + silent: true, + onResponse: (res) => { + setFacilityObject(res.data); + }, + }); + + const facilityFlags = facilityObject?.facility_flags || []; + + useEffect(() => { + facilityQuery.refetch(); + }, [facility]); + + return [...context.user_flags, ...facilityFlags]; +}; diff --git a/src/Utils/request/uploadFile.ts b/src/Utils/request/uploadFile.ts index 506acf72e57..c527878e283 100644 --- a/src/Utils/request/uploadFile.ts +++ b/src/Utils/request/uploadFile.ts @@ -1,5 +1,6 @@ import { Dispatch, SetStateAction } from "react"; import { handleUploadPercentage } from "./utils"; +import * as Notification from "../../Utils/Notifications.js"; const uploadFile = ( url: string, @@ -19,6 +20,16 @@ const uploadFile = ( xhr.onload = () => { onLoad(xhr); + if (400 <= xhr.status && xhr.status <= 499) { + const error = JSON.parse(xhr.responseText); + if (typeof error === "object" && !Array.isArray(error)) { + Object.values(error).forEach((msg) => { + Notification.Error({ msg: msg || "Something went wrong!" }); + }); + } else { + Notification.Error({ msg: error || "Something went wrong!" }); + } + } }; if (setUploadPercent != null) { @@ -28,6 +39,9 @@ const uploadFile = ( } xhr.onerror = () => { + Notification.Error({ + msg: "Network Failure. Please check your internet connectivity.", + }); onError(); }; xhr.send(file); diff --git a/src/Utils/useFileUpload.tsx b/src/Utils/useFileUpload.tsx index fdd69c45765..a0888643414 100644 --- a/src/Utils/useFileUpload.tsx +++ b/src/Utils/useFileUpload.tsx @@ -2,6 +2,7 @@ import { ChangeEvent, DetailedHTMLProps, InputHTMLAttributes, + useEffect, useState, } from "react"; import { @@ -20,9 +21,12 @@ import AudioCaptureDialog from "../Components/Files/AudioCaptureDialog"; import { t } from "i18next"; export type FileUploadOptions = { + multiple?: boolean; type: string; category?: FileCategory; onUpload?: (file: FileUploadModel) => void; + // if allowed, will fallback to the name of the file if a seperate filename is not defined. + allowNameFallback?: boolean; } & ( | { allowedExtensions?: string[]; @@ -41,15 +45,19 @@ export interface FileInputProps export type FileUploadReturn = { progress: null | number; error: null | string; + validateFiles: () => boolean; handleCameraCapture: () => void; handleAudioCapture: () => void; handleFileUpload: (associating_id: string) => Promise; Dialogues: JSX.Element; Input: (_: FileInputProps) => JSX.Element; - fileName: string; - file: File | null; - setFileName: (name: string) => void; - clearFile: () => void; + fileNames: string[]; + files: File[]; + setFileName: (names: string, index?: number) => void; + setFileNames: (names: string[]) => void; + removeFile: (index: number) => void; + clearFiles: () => void; + uploading: boolean; }; // Array of image extensions @@ -67,73 +75,82 @@ const ExtImage: string[] = [ export default function useFileUpload( options: FileUploadOptions, ): FileUploadReturn { - const { type, onUpload, category = "UNSPECIFIED" } = options; + const { + type, + onUpload, + category = "UNSPECIFIED", + multiple, + allowNameFallback = true, + } = options; - const [uploadFileName, setUploadFileName] = useState(""); + const [uploadFileNames, setUploadFileNames] = useState([]); const [error, setError] = useState(null); const [progress, setProgress] = useState(null); const [cameraModalOpen, setCameraModalOpen] = useState(false); const [audioModalOpen, setAudioModalOpen] = useState(false); + const [uploading, setUploading] = useState(false); - const [file, setFile] = useState(null); + const [files, setFiles] = useState([]); const onFileChange = (e: ChangeEvent): any => { if (!e.target.files?.length) { return; } - const f = e.target.files[0]; - const fileName = f.name; - setFile(e.target.files[0]); + const selectedFiles = Array.from(e.target.files); + setFiles((prev) => [...prev, ...selectedFiles]); - // This is commented out to prompt users to input valid file names. See https://github.com/ohcnetwork/care_fe/issues/7942#issuecomment-2324391329 - //setUploadFileName( - // uploadFileName || - // fileName.substring(0, fileName.lastIndexOf(".")) || - // fileName, - //); - - const ext: string = fileName.split(".")[1]; - - if (ExtImage.includes(ext)) { - const options = { - initialQuality: 0.6, - alwaysKeepResolution: true, - }; - imageCompression(f, options).then((compressedFile: File) => { - setFile(compressedFile); - }); - return; - } - setFile(f); + selectedFiles.forEach((file) => { + const ext: string = file.name.split(".")[1]; + if (ExtImage.includes(ext)) { + const options = { + initialQuality: 0.6, + alwaysKeepResolution: true, + }; + imageCompression(file, options).then((compressedFile: File) => { + setFiles((prev) => + prev.map((f) => (f.name === file.name ? compressedFile : f)), + ); + }); + } + }); }; + useEffect(() => { + const blanks = Array(files.length).fill(""); + setUploadFileNames((names) => [...names, ...blanks].slice(0, files.length)); + }, [files]); + const validateFileUpload = () => { - const filenameLength = uploadFileName.trim().length; - const f = file; - if (f === undefined || f === null) { + if (files.length === 0) { setError(t("file_error__choose_file")); return false; } - if (filenameLength === 0) { - setError(t("file_error__file_name")); - return false; - } - if (f.size > 10e7) { - setError(t("file_error__file_size")); - return false; - } - const extension = f.name.split(".").pop(); - if ( - "allowedExtensions" in options && - !options.allowedExtensions?.includes(extension || "") - ) { - setError( - t("file_error__file_type", { - extension, - allowedExtensions: options.allowedExtensions?.join(", "), - }), - ); - return false; + + for (const file of files) { + const filenameLength = file.name.trim().length; + if (filenameLength === 0) { + setError(t("file_error__file_name")); + return false; + } + if (file.size > 10e7) { + setError(t("file_error__file_size")); + return false; + } + const extension = file.name.split(".").pop(); + if ( + "allowedExtensions" in options && + !options.allowedExtensions + ?.map((extension) => extension.replace(".", "")) + ?.includes(extension || "") + ) { + setError( + t("file_error__file_type", { + extension, + allowedExtensions: options.allowedExtensions?.join(", "), + }), + ); + return false; + } } return true; }; @@ -151,23 +168,20 @@ export default function useFileUpload( }); }; - const uploadfile = async (data: CreateFileResponse) => { + const uploadfile = async (data: CreateFileResponse, file: File) => { const url = data.signed_url; const internal_name = data.internal_name; - const f = file; - if (!f) return; - const newFile = new File([f], `${internal_name}`); + const newFile = new File([file], `${internal_name}`); + return new Promise((resolve, reject) => { uploadFile( url, newFile, "PUT", - { "Content-Type": file?.type }, + { "Content-Type": file.type }, (xhr: XMLHttpRequest) => { if (xhr.status >= 200 && xhr.status < 300) { setProgress(null); - setFile(null); - setUploadFileName(""); Notification.Success({ msg: t("file_uploaded"), }); @@ -196,27 +210,40 @@ export default function useFileUpload( const handleUpload = async (associating_id: string) => { if (!validateFileUpload()) return; - const f = file; - const filename = uploadFileName === "" && f ? f.name : uploadFileName; - const name = f?.name; setProgress(0); - const { data } = await request(routes.createUpload, { - body: { - original_name: name ?? "", - file_type: type, - name: filename, - associating_id, - file_category: category, - mime_type: f?.type ?? "", - }, - }); + for (const [index, file] of files.entries()) { + const filename = + allowNameFallback && uploadFileNames[index] === "" && file + ? file.name + : uploadFileNames[index]; + if (!filename) { + setError(t("file_error__single_file_name")); + return; + } + setUploading(true); - if (data) { - await uploadfile(data); - await markUploadComplete(data, associating_id); + const { data } = await request(routes.createUpload, { + body: { + original_name: file.name ?? "", + file_type: type, + name: filename, + associating_id, + file_category: category, + mime_type: file.type ?? "", + }, + }); + + if (data) { + await uploadfile(data, file); + await markUploadComplete(data, associating_id); + } } + + setUploading(false); + setFiles([]); + setUploadFileNames([]); }; const Dialogues = ( @@ -224,17 +251,15 @@ export default function useFileUpload( setCameraModalOpen(false)} - onCapture={(f) => { - setFile(f); - setUploadFileName(uploadFileName || ""); + onCapture={(file) => { + setFiles((prev) => [...prev, file]); }} /> setAudioModalOpen(false)} - onCapture={(f) => { - setFile(f); - setUploadFileName(uploadFileName || ""); + onCapture={(file) => { + setFiles((prev) => [...prev, file]); }} autoRecord /> @@ -245,9 +270,10 @@ export default function useFileUpload( "." + e).join(",") @@ -262,18 +288,28 @@ export default function useFileUpload( return { progress, error, + validateFiles: validateFileUpload, handleCameraCapture: () => setCameraModalOpen(true), handleAudioCapture: () => setAudioModalOpen(true), handleFileUpload: handleUpload, Dialogues, Input, - fileName: uploadFileName, - file: file, - setFileName: setUploadFileName, - clearFile: () => { - setFile(null); - setError(null); - setUploadFileName(""); + fileNames: uploadFileNames, + files: files, + setFileNames: setUploadFileNames, + setFileName: (name: string, index = 0) => { + setUploadFileNames((prev) => + prev.map((n, i) => (i === index ? name : n)), + ); + }, + removeFile: (index = 0) => { + setFiles((prev) => prev.filter((_, i) => i !== index)); + setUploadFileNames((prev) => prev.filter((_, i) => i !== index)); + }, + clearFiles: () => { + setFiles([]); + setUploadFileNames([]); }, + uploading, }; } From c5314259eb953d759deef86117eac01d45f8c3ee Mon Sep 17 00:00:00 2001 From: Shivank Kacker Date: Wed, 9 Oct 2024 12:19:04 +0530 Subject: [PATCH 2/2] fix export file name --- src/Components/Shifting/BoardView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Components/Shifting/BoardView.tsx b/src/Components/Shifting/BoardView.tsx index 838c0a6df7d..f83e0a4974e 100644 --- a/src/Components/Shifting/BoardView.tsx +++ b/src/Components/Shifting/BoardView.tsx @@ -147,7 +147,7 @@ export default function BoardView() { }); return data ?? null; }} - filenamePrefix={`shift_requests_${board}`} + filenamePrefix={`shift_requests_${board.label || board.text}`} /> ),