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/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/e2e/users_spec/UsersCreation.cy.ts b/cypress/e2e/users_spec/UsersCreation.cy.ts index e24fdeca9e2..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", 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/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/package-lock.json b/package-lock.json index 051c12bd2b4..5665fe00f1a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@headlessui/react": "^2.1.2", "@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 +23,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", @@ -40,7 +40,7 @@ "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", @@ -1869,9 +1869,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" }, @@ -3765,178 +3765,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" @@ -3957,48 +3957,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" @@ -7273,12 +7273,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": { @@ -14344,11 +14344,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": { @@ -19217,9 +19217,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" } diff --git a/package.json b/package.json index 62cdc8a0fb9..e0b415a9523 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "@headlessui/react": "^2.1.2", "@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 +58,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", @@ -75,7 +75,7 @@ "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", 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/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/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/AssetType/ONVIFCamera.tsx b/src/Components/Assets/AssetType/ONVIFCamera.tsx index e49fcad549d..f79894d089f 100644 --- a/src/Components/Assets/AssetType/ONVIFCamera.tsx +++ b/src/Components/Assets/AssetType/ONVIFCamera.tsx @@ -16,7 +16,9 @@ import routes from "../../../Redux/api"; import useQuery from "../../../Utils/request/useQuery"; import CareIcon from "../../../CAREUI/icons/CareIcon"; -import useOperateCamera from "../../CameraFeed/useOperateCamera"; +import useOperateCamera, { + PTZPayload, +} from "../../CameraFeed/useOperateCamera"; interface Props { assetId: string; @@ -95,18 +97,20 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const addPreset = async (e: SyntheticEvent) => { e.preventDefault(); - const data = { + const meta = { bed_id: bed.id, preset_name: newPreset, }; try { setLoadingAddPreset(true); - const { data: presetData } = await operate({ type: "get_status" }); + const { data } = await operate({ type: "get_status" }); + const { position } = (data as { result: { position: PTZPayload } }) + .result; const { res } = await request(routes.createAssetBed, { body: { - meta: { ...data, ...presetData }, + meta: { ...meta, position }, asset: assetId, bed: bed?.id as string, }, 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 a52214f5946..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; @@ -185,6 +186,10 @@ export default function CameraFeed(props: Props) { {props.asset.name} + {!isIOS && (
) => { - const bp = field.value ?? {}; + const bp = { + systolic: field.value?.systolic, + diastolic: field.value?.diastolic, + }; bp[event.name as keyof BloodPressure] = event.value; field.handleChange(Object.values(bp).filter(Boolean).length ? bp : null); }; 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/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/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/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/CoverImageEditModal.tsx b/src/Components/Facility/CoverImageEditModal.tsx index 8e6204abb6f..d65a1d0ebfd 100644 --- a/src/Components/Facility/CoverImageEditModal.tsx +++ b/src/Components/Facility/CoverImageEditModal.tsx @@ -158,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(); @@ -191,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 5957b3e812f..5573fe883d0 100644 --- a/src/Components/Facility/DischargeModal.tsx +++ b/src/Components/Facility/DischargeModal.tsx @@ -27,8 +27,10 @@ import useQuery from "../../Utils/request/useQuery"; import { useTranslation } from "react-i18next"; import useConfirmedAction from "../../Common/hooks/useConfirmedAction"; import ConfirmDialog from "../Common/ConfirmDialog"; -import careConfig from "@careConfig"; import routes from "../../Redux/api"; +import { EditDiagnosesBuilder } from "../Diagnosis/ConsultationDiagnosisBuilder/ConsultationDiagnosisBuilder"; +import Loading from "../Common/Loading"; +import careConfig from "@careConfig"; interface PreDischargeFormInterface { new_discharge_reason: number | null; @@ -123,6 +125,11 @@ 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; @@ -208,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 && ( <> @@ -387,6 +389,27 @@ 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 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" > { const { t } = useTranslation(); const [openDeleteDialog, setOpenDeleteDialog] = useState(false); const [editCoverImage, setEditCoverImage] = useState(false); - const [coverImageEdited, setCoverImageEdited] = useState(false); const authUser = useAuthUser(); const { @@ -131,20 +130,11 @@ export const FacilityHome = ({ facilityId }: Props) => { ); const CoverImage = () => ( - <> - {facilityData?.name} - {coverImageEdited && ( -
- - {t("cover_image_updated_note")} - -
- )} - + {facilityData?.name} ); return ( @@ -169,10 +159,7 @@ export const FacilityHome = ({ facilityId }: Props) => { /> { - facilityFetch(); - setCoverImageEdited(true); - }} + onSave={() => facilityFetch()} onClose={() => setEditCoverImage(false)} onDelete={() => facilityFetch()} facility={facilityData ?? ({} as FacilityModel)} 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/models.tsx b/src/Components/Facility/models.tsx index 97d81674658..56c70f2ac94 100644 --- a/src/Components/Facility/models.tsx +++ b/src/Components/Facility/models.tsx @@ -7,6 +7,7 @@ import { PATIENT_NOTES_THREADS, UserRole, } from "../../Common/constants"; +import { FeatureFlag } from "../../Utils/featureFlags"; import { ConsultationDiagnosis, CreateDiagnosis } from "../Diagnosis/types"; import { AssignedToObjectModel, @@ -80,6 +81,7 @@ export interface FacilityModel { local_body?: number; ward?: number; pincode?: string; + facility_flags?: FeatureFlag[]; latitude?: string; longitude?: string; kasp_empanelled?: boolean; diff --git a/src/Components/LogUpdate/Sections/Vitals.tsx b/src/Components/LogUpdate/Sections/Vitals.tsx index 2d533d529e4..9253d2e8697 100644 --- a/src/Components/LogUpdate/Sections/Vitals.tsx +++ b/src/Components/LogUpdate/Sections/Vitals.tsx @@ -137,7 +137,7 @@ const BPAttributeEditor = ({ name={attribute} label={t(attribute)} onChange={(event) => { - const bp = log.bp ?? {}; + const bp = { systolic: log.bp?.systolic, diastolic: log.bp?.diastolic }; bp[event.name as keyof BloodPressure] = event.value; onChange({ bp: Object.values(bp).filter(Boolean).length ? bp : undefined, 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/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 7a5b0d28a38..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 @@ -923,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/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/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 index 4b32c72eda4..aa0b031ba0e 100644 --- a/src/Components/Resource/ResourceBoard.tsx +++ b/src/Components/Resource/ResourceBoard.tsx @@ -1,5 +1,4 @@ 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"; @@ -241,12 +240,18 @@ export default function ResourceBoard({

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

diff --git a/src/Components/Resource/ResourceBoardView.tsx b/src/Components/Resource/ResourceBoardView.tsx index bff0282f91a..345cd11c381 100644 --- a/src/Components/Resource/ResourceBoardView.tsx +++ b/src/Components/Resource/ResourceBoardView.tsx @@ -3,7 +3,6 @@ import { 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"; @@ -15,6 +14,8 @@ 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"; const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); @@ -50,9 +51,15 @@ export default function BoardView() { className="mx-3 md:mx-5" 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/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..f303df72d99 100644 --- a/src/Components/Shifting/BoardView.tsx +++ b/src/Components/Shifting/BoardView.tsx @@ -8,7 +8,6 @@ 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"; @@ -21,6 +20,8 @@ import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover" import CareIcon from "../../CAREUI/icons/CareIcon"; import Tabs from "../Common/components/Tabs"; import careConfig from "@careConfig"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; const Loading = lazy(() => import("../Common/Loading")); const PageTitle = lazy(() => import("../Common/PageTitle")); @@ -135,9 +136,12 @@ export default function BoardView() { 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/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 index d7d182ec3e0..6f2eb3b77a3 100644 --- a/src/Components/Shifting/ShiftingBoard.tsx +++ b/src/Components/Shifting/ShiftingBoard.tsx @@ -7,7 +7,6 @@ import { useState, } from "react"; import { classNames, formatDateTime, formatName } 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"; @@ -346,12 +345,15 @@ export default function ShiftingBoard({

{title || board}{" "} - downloadShiftRequests({ - ...formatFilter({ ...filterProp, status: board }), - csv: 1, - }) - } + action={async () => { + const { data } = await request(routes.downloadShiftRequests, { + query: { + ...formatFilter({ ...filterProp, status: board }), + csv: true, + }, + }); + return data ?? null; + }} filenamePrefix={`shift_requests_${board}`} />

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 9ee1e722ee1..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, }; @@ -372,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"); @@ -553,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" @@ -650,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 7f0b9f51802..cdecac19f62 100644 --- a/src/Locale/en/Common.json +++ b/src/Locale/en/Common.json @@ -177,7 +177,7 @@ "treating_doctor": "Treating Doctor", "hubs": "Hub Facilities", "spokes": "Spoke Facilities", - "add_spoke" : "Add Spoke Facility", + "add_spoke": "Add Spoke Facility", "ration_card__NO_CARD": "Non-card holder", "ration_card__BPL": "BPL", "ration_card__APL": "APL", @@ -230,5 +230,8 @@ "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" -} \ No newline at end of file + "SORT_OPTIONS__-bed__name": "Bed No. N-1", + "middleware_hostname": "Middleware Hostname", + "local_ipaddress": "Local IP Address", + "qualification": "Qualification" +} 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 4e835bec675..fe23a64f668 100644 --- a/src/Locale/en/Facility.json +++ b/src/Locale/en/Facility.json @@ -100,7 +100,6 @@ "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", 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/hi/Common.json b/src/Locale/hi/Common.json index 4cd3bbe3e36..daa8781a47f 100644 --- a/src/Locale/hi/Common.json +++ b/src/Locale/hi/Common.json @@ -219,5 +219,7 @@ "SORT_OPTIONS__name": "मरीज़ का नाम AZ", "SORT_OPTIONS__-name": "मरीज का नाम ZA", "SORT_OPTIONS__bed__name": "बिस्तर नं. 1-एन", - "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/kn/Common.json b/src/Locale/kn/Common.json index 6eeaa71ea48..02b00db4891 100644 --- a/src/Locale/kn/Common.json +++ b/src/Locale/kn/Common.json @@ -219,5 +219,7 @@ "SORT_OPTIONS__name": "ರೋಗಿಯ ಹೆಸರು AZ", "SORT_OPTIONS__-name": "ರೋಗಿಯ ಹೆಸರು ZA", "SORT_OPTIONS__bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ 1-N", - "SORT_OPTIONS__-bed__name": "ಹಾಸಿಗೆ ಸಂಖ್ಯೆ N-1" + "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/ml/Common.json b/src/Locale/ml/Common.json index ab1e0db6feb..0c861324515 100644 --- a/src/Locale/ml/Common.json +++ b/src/Locale/ml/Common.json @@ -219,5 +219,7 @@ "SORT_OPTIONS__name": "രോഗിയുടെ പേര് AZ", "SORT_OPTIONS__-name": "രോഗിയുടെ പേര് ZA", "SORT_OPTIONS__bed__name": "ബെഡ് നമ്പർ 1-N", - "SORT_OPTIONS__-bed__name": "കിടക്ക നമ്പർ N-1" + "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/ta/Common.json b/src/Locale/ta/Common.json index 2a9bc4458b5..770772c883a 100644 --- a/src/Locale/ta/Common.json +++ b/src/Locale/ta/Common.json @@ -219,5 +219,7 @@ "SORT_OPTIONS__name": "நோயாளியின் பெயர் AZ", "SORT_OPTIONS__-name": "நோயாளியின் பெயர் ZA", "SORT_OPTIONS__bed__name": "படுக்கை எண் 1-N", - "SORT_OPTIONS__-bed__name": "படுக்கை எண். N-1" + "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/Redux/actions.tsx b/src/Redux/actions.tsx index 00e96e48eef..47d3530d065 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -24,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); }; @@ -54,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 }); @@ -68,26 +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 }); diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 0bedb12dca5..a0c3271386d 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -577,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/", @@ -1120,6 +1125,7 @@ const routes = { downloadShiftRequests: { path: "/api/v1/shift/", method: "GET", + TRes: Type(), }, getShiftComments: { path: "/api/v1/shift/{id}/comment/", @@ -1291,6 +1297,7 @@ const routes = { downloadResourceRequests: { path: "/api/v1/resource/", method: "GET", + TRes: Type(), }, getResourceComments: { path: "/api/v1/resource/{id}/comment/", 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]; +};