diff --git a/cypress/e2e/assets_spec/asset_homepage.cy.ts b/cypress/e2e/assets_spec/asset_homepage.cy.ts index 8bc24c7370f..61f7d8a52c0 100644 --- a/cypress/e2e/assets_spec/asset_homepage.cy.ts +++ b/cypress/e2e/assets_spec/asset_homepage.cy.ts @@ -73,7 +73,7 @@ describe("Asset Tab", () => { assetFilters.assertAssetTypeText("INTERNAL"); assetFilters.assertAssetClassText("ONVIF"); assetFilters.assertStatusText("ACTIVE"); - assetFilters.assertLocationText("Camera Locations"); + assetFilters.assertLocationText("Camera Loc"); assetFilters.clickadvancefilter(); assetFilters.clearFilters(); }); @@ -92,7 +92,6 @@ describe("Asset Tab", () => { assetPage.importAssetFile(); assetPage.selectImportLocation("Camera Locations"); assetPage.clickImportAsset(); - assetPage.verifySuccessNotification("Assets imported successfully"); }); it("verify imported asset", () => { diff --git a/cypress/e2e/assets_spec/assets_manage.cy.ts b/cypress/e2e/assets_spec/assets_manage.cy.ts index 34c554b374e..8b440f68761 100644 --- a/cypress/e2e/assets_spec/assets_manage.cy.ts +++ b/cypress/e2e/assets_spec/assets_manage.cy.ts @@ -5,6 +5,12 @@ import { AssetSearchPage } from "../../pageobject/Asset/AssetSearch"; import FacilityPage from "../../pageobject/Facility/FacilityCreation"; import { AssetFilters } from "../../pageobject/Asset/AssetFilters"; +function addDaysToDate(numberOfDays: number) { + const inputDate = new Date(); + inputDate.setDate(inputDate.getDate() + numberOfDays); + return inputDate.toISOString().split("T")[0]; +} + describe("Asset", () => { const assetPage = new AssetPage(); const loginPage = new LoginPage(); @@ -26,6 +32,33 @@ describe("Asset", () => { cy.awaitUrl("/assets"); }); + it("Verify Asset Warranty Expiry Label", () => { + assetSearchPage.typeSearchKeyword(assetname); + assetSearchPage.pressEnter(); + assetSearchPage.verifyBadgeContent(assetname); + assetSearchPage.clickAssetByName(assetname); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(100)); // greater than 3 months + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel(""); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(80)); // less than 3 months + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel("3 months"); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(20)); // less than 1 month + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel("1 month"); + assetPage.clickupdatedetailbutton(); + assetPage.scrollintoWarrantyDetails(); + assetPage.enterWarrantyExpiryDate(addDaysToDate(100)); // check for greater than 3 months again to verify the label is removed + assetPage.clickassetupdatebutton(); + assetPage.verifyWarrantyExpiryLabel(""); + }); + it("Create & Edit a service history and verify reflection", () => { assetSearchPage.typeSearchKeyword(assetname); assetSearchPage.pressEnter(); diff --git a/cypress/fixtures/sampleAsset.xlsx b/cypress/fixtures/sampleAsset.xlsx index f8e234ce447..49421f62bac 100644 Binary files a/cypress/fixtures/sampleAsset.xlsx and b/cypress/fixtures/sampleAsset.xlsx differ diff --git a/cypress/pageobject/Asset/AssetCreation.ts b/cypress/pageobject/Asset/AssetCreation.ts index 45356a1a4e7..93bbc87c9ab 100644 --- a/cypress/pageobject/Asset/AssetCreation.ts +++ b/cypress/pageobject/Asset/AssetCreation.ts @@ -150,7 +150,7 @@ export class AssetPage { configureVitalAsset(hostName: string, localIp: string) { cy.get("[data-testid=asset-configure-button]").click(); - cy.get("#middlewareHostname").type(hostName); + cy.get("#middleware_hostname").type(hostName); cy.get("#localipAddress").type(localIp); } @@ -285,16 +285,39 @@ export class AssetPage { cy.get("#notes").scrollIntoView(); } - enterAssetNotes(text) { + enterAssetNotes(text: string) { cy.get("#notes").click().clear(); cy.get("#notes").click().type(text); } - enterAssetservicedate(text) { + enterAssetservicedate(text: string) { cy.get("input[name='last_serviced_on']").click(); cy.get("#date-input").click().type(text); } + scrollintoWarrantyDetails() { + cy.get("#warranty-details").scrollIntoView(); + } + + enterWarrantyExpiryDate(text: string) { + cy.get("#WarrantyAMCExpiry").click(); + cy.get("#WarrantyAMCExpiry").click().type(text); + } + + verifyWarrantyExpiryLabel(duration: string) { + if (duration === "") { + cy.get("#warranty-amc-expired-red").should("not.exist"); + cy.get("#warranty-amc-expiring-soon-orange").should("not.exist"); + cy.get("#warranty-amc-expiring-soon-yellow").should("not.exist"); + } else if (duration === "expired") { + cy.get("#warranty-amc-expired-red").should("be.visible"); + } else if (duration === "1 month") { + cy.get("#warranty-amc-expiring-soon-orange").should("be.visible"); + } else if (duration === "3 months") { + cy.get("#warranty-amc-expiring-soon-yellow").should("be.visible"); + } + } + clickassetupdatebutton() { cy.get("#submit").click(); } diff --git a/cypress/pageobject/Patient/PatientConsultation.ts b/cypress/pageobject/Patient/PatientConsultation.ts index 6937559d548..2eb6550cb8b 100644 --- a/cypress/pageobject/Patient/PatientConsultation.ts +++ b/cypress/pageobject/Patient/PatientConsultation.ts @@ -49,7 +49,7 @@ export class PatientConsultationPage { .click() .type("1A"); cy.get("#icd11_diagnoses_object [role='option']") - .contains("1A03 Intestinal infections due to Escherichia coli") + .contains("1A00 Cholera") .scrollIntoView() .click(); cy.get("label[for='icd11_diagnoses_object']").click(); @@ -57,7 +57,7 @@ export class PatientConsultationPage { cy.get("#icd11_principal_diagnosis [role='combobox']").click().type("1A"); cy.get("#icd11_principal_diagnosis [role='option']") - .contains("1A03 Intestinal infections due to Escherichia coli") + .contains("1A00 Cholera") .click(); cy.get("#consultation_notes").click().type(consulationNotes); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 7f5484564fc..2dd8c477233 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -81,7 +81,7 @@ Cypress.Commands.add( Cypress.Commands.add("verifyNotification", (text) => { cy.get(".pnotify-container").should("exist").contains(text); - return cy.get(".pnotify-container").click({ force: true }); + return cy.get(".pnotify-container").contains(text).click({ force: true }); }); Cypress.on("uncaught:exception", () => { diff --git a/package-lock.json b/package-lock.json index 4871fbe374b..a2f8d202145 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,7 +32,7 @@ "events": "^3.3.0", "i18next": "^23.2.7", "i18next-browser-languagedetector": "^7.1.0", - "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "postcss-loader": "^7.3.3", "qrcode.react": "^3.1.0", "raviger": "^4.1.2", @@ -46,7 +46,7 @@ "react-google-recaptcha": "^3.1.0", "react-i18next": "^13.0.1", "react-markdown": "^8.0.7", - "react-player": "^2.12.0", + "react-player": "^2.13.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", "react-transition-group": "^4.4.5", @@ -57,7 +57,7 @@ "rehype-raw": "^6.1.1", "rescript-webapi": "^0.8.0", "use-keyboard-shortcut": "^1.1.6", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "devDependencies": { "@storybook/addon-essentials": "^7.0.26", @@ -73,7 +73,7 @@ "@types/cypress": "^1.1.3", "@types/echarts": "^4.9.18", "@types/google.maps": "^3.53.4", - "@types/lodash": "^4.14.195", + "@types/lodash-es": "^4.17.9", "@types/lodash.get": "^4.4.7", "@types/node": "^20.4.0", "@types/prop-types": "*", @@ -5483,6 +5483,15 @@ "integrity": "sha512-22y3o88f4a94mKljsZcanlNWPzO0uBsBdzLAngf2tp533LzZcQzb6+eZPJ+vCTt+bqF2XnvT9gejTLsAcJAJyQ==", "dev": true }, + "node_modules/@types/lodash-es": { + "version": "4.17.9", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.9.tgz", + "integrity": "sha512-ZTcmhiI3NNU7dEvWLZJkzG6ao49zOIjEgIE0RgV7wbPxU0f2xT3VSAHw2gmst8swH6V0YkLRGp4qPlX/6I90MQ==", + "dev": true, + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.get": { "version": "4.4.7", "resolved": "https://registry.npmjs.org/@types/lodash.get/-/lodash.get-4.4.7.tgz", @@ -12454,6 +12463,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, "node_modules/lodash.castarray": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", @@ -13720,6 +13734,8 @@ }, "node_modules/npm/node_modules/@colors/colors": { "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -13730,6 +13746,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui": { "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, "inBundle": true, "license": "ISC", @@ -13747,6 +13765,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "inBundle": true, "license": "MIT", @@ -13759,12 +13779,16 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "inBundle": true, "license": "MIT", @@ -13782,6 +13806,8 @@ }, "node_modules/npm/node_modules/@isaacs/cliui/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14034,6 +14060,8 @@ }, "node_modules/npm/node_modules/@pkgjs/parseargs": { "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14118,6 +14146,8 @@ }, "node_modules/npm/node_modules/agent-base": { "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14144,6 +14174,8 @@ }, "node_modules/npm/node_modules/aggregate-error": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14157,6 +14189,8 @@ }, "node_modules/npm/node_modules/ansi-regex": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14166,6 +14200,8 @@ }, "node_modules/npm/node_modules/ansi-styles": { "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14181,6 +14217,8 @@ }, "node_modules/npm/node_modules/aproba": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14206,12 +14244,16 @@ }, "node_modules/npm/node_modules/balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/base64-js": { "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -14256,6 +14298,8 @@ }, "node_modules/npm/node_modules/brace-expansion": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14333,6 +14377,8 @@ }, "node_modules/npm/node_modules/chownr": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "dev": true, "inBundle": true, "license": "ISC", @@ -14369,6 +14415,8 @@ }, "node_modules/npm/node_modules/clean-stack": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14406,6 +14454,8 @@ }, "node_modules/npm/node_modules/clone": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14424,6 +14474,8 @@ }, "node_modules/npm/node_modules/color-convert": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14436,6 +14488,8 @@ }, "node_modules/npm/node_modules/color-name": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true, "inBundle": true, "license": "MIT" @@ -14470,18 +14524,24 @@ }, "node_modules/npm/node_modules/concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/console-control-strings": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/cross-spawn": { "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "inBundle": true, "license": "MIT", @@ -14513,6 +14573,8 @@ }, "node_modules/npm/node_modules/cssesc": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14525,6 +14587,8 @@ }, "node_modules/npm/node_modules/debug": { "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -14542,12 +14606,16 @@ }, "node_modules/npm/node_modules/debug/node_modules/ms": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/defaults": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, "inBundle": true, "license": "MIT", @@ -14560,6 +14628,8 @@ }, "node_modules/npm/node_modules/delegates": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", "dev": true, "inBundle": true, "license": "MIT" @@ -14584,12 +14654,16 @@ }, "node_modules/npm/node_modules/eastasianwidth": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/emoji-regex": { "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, "inBundle": true, "license": "MIT" @@ -14630,6 +14704,8 @@ }, "node_modules/npm/node_modules/events": { "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -14654,6 +14730,8 @@ }, "node_modules/npm/node_modules/foreground-child": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", "dev": true, "inBundle": true, "license": "ISC", @@ -14682,12 +14760,16 @@ }, "node_modules/npm/node_modules/fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/function-bind": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true, "inBundle": true, "license": "MIT" @@ -14741,6 +14823,8 @@ }, "node_modules/npm/node_modules/has": { "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "dev": true, "inBundle": true, "license": "MIT", @@ -14753,6 +14837,8 @@ }, "node_modules/npm/node_modules/has-unicode": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14791,6 +14877,8 @@ }, "node_modules/npm/node_modules/https-proxy-agent": { "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14858,6 +14946,8 @@ }, "node_modules/npm/node_modules/imurmurhash": { "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "inBundle": true, "license": "MIT", @@ -14867,6 +14957,8 @@ }, "node_modules/npm/node_modules/indent-string": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14876,6 +14968,8 @@ }, "node_modules/npm/node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "inBundle": true, "license": "ISC", @@ -14886,6 +14980,8 @@ }, "node_modules/npm/node_modules/inherits": { "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -14919,6 +15015,8 @@ }, "node_modules/npm/node_modules/ip": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", "dev": true, "inBundle": true, "license": "MIT" @@ -14958,6 +15056,8 @@ }, "node_modules/npm/node_modules/is-fullwidth-code-point": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "inBundle": true, "license": "MIT", @@ -14973,6 +15073,8 @@ }, "node_modules/npm/node_modules/isexe": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, "inBundle": true, "license": "ISC" @@ -15239,6 +15341,8 @@ }, "node_modules/npm/node_modules/minimatch": { "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", "dev": true, "inBundle": true, "license": "ISC", @@ -15277,6 +15381,8 @@ }, "node_modules/npm/node_modules/minipass-collect/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15318,6 +15424,8 @@ }, "node_modules/npm/node_modules/minipass-flush/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15340,6 +15448,8 @@ }, "node_modules/npm/node_modules/minipass-json-stream/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15364,6 +15474,8 @@ }, "node_modules/npm/node_modules/minipass-pipeline/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15388,6 +15500,8 @@ }, "node_modules/npm/node_modules/minipass-sized/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15400,6 +15514,8 @@ }, "node_modules/npm/node_modules/minizlib": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15413,6 +15529,8 @@ }, "node_modules/npm/node_modules/minizlib/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15425,6 +15543,8 @@ }, "node_modules/npm/node_modules/mkdirp": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, "inBundle": true, "license": "MIT", @@ -15505,6 +15625,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "inBundle": true, "license": "MIT", @@ -15534,6 +15656,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -15554,6 +15678,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "inBundle": true, "license": "ISC", @@ -15596,6 +15722,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/readable-stream": { "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "inBundle": true, "license": "MIT", @@ -15610,6 +15738,8 @@ }, "node_modules/npm/node_modules/node-gyp/node_modules/signal-exit": { "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true, "inBundle": true, "license": "ISC" @@ -15800,6 +15930,8 @@ }, "node_modules/npm/node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "inBundle": true, "license": "ISC", @@ -15809,6 +15941,8 @@ }, "node_modules/npm/node_modules/p-map": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -15870,6 +16004,8 @@ }, "node_modules/npm/node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, "inBundle": true, "license": "MIT", @@ -15879,6 +16015,8 @@ }, "node_modules/npm/node_modules/path-key": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -15935,6 +16073,8 @@ }, "node_modules/npm/node_modules/process": { "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16074,6 +16214,8 @@ }, "node_modules/npm/node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "inBundle": true, "license": "ISC", @@ -16089,6 +16231,8 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16099,6 +16243,8 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/glob": { "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -16119,6 +16265,8 @@ }, "node_modules/npm/node_modules/rimraf/node_modules/minimatch": { "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "inBundle": true, "license": "ISC", @@ -16131,6 +16279,8 @@ }, "node_modules/npm/node_modules/safe-buffer": { "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -16158,6 +16308,8 @@ }, "node_modules/npm/node_modules/semver": { "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "dev": true, "inBundle": true, "license": "ISC", @@ -16173,6 +16325,8 @@ }, "node_modules/npm/node_modules/semver/node_modules/lru-cache": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, "inBundle": true, "license": "ISC", @@ -16185,12 +16339,16 @@ }, "node_modules/npm/node_modules/set-blocking": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true, "inBundle": true, "license": "ISC" }, "node_modules/npm/node_modules/shebang-command": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16203,6 +16361,8 @@ }, "node_modules/npm/node_modules/shebang-regex": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16212,6 +16372,8 @@ }, "node_modules/npm/node_modules/signal-exit": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", "dev": true, "inBundle": true, "license": "ISC", @@ -16279,6 +16441,8 @@ }, "node_modules/npm/node_modules/spdx-correct": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", "dev": true, "inBundle": true, "license": "Apache-2.0", @@ -16289,12 +16453,16 @@ }, "node_modules/npm/node_modules/spdx-exceptions": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", "dev": true, "inBundle": true, "license": "CC-BY-3.0" }, "node_modules/npm/node_modules/spdx-expression-parse": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "dev": true, "inBundle": true, "license": "MIT", @@ -16305,6 +16473,8 @@ }, "node_modules/npm/node_modules/spdx-license-ids": { "version": "3.0.13", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz", + "integrity": "sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==", "dev": true, "inBundle": true, "license": "CC0-1.0" @@ -16323,6 +16493,8 @@ }, "node_modules/npm/node_modules/string_decoder": { "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16332,6 +16504,8 @@ }, "node_modules/npm/node_modules/string-width": { "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "inBundle": true, "license": "MIT", @@ -16347,6 +16521,8 @@ "node_modules/npm/node_modules/string-width-cjs": { "name": "string-width", "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "inBundle": true, "license": "MIT", @@ -16361,6 +16537,8 @@ }, "node_modules/npm/node_modules/strip-ansi": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16374,6 +16552,8 @@ "node_modules/npm/node_modules/strip-ansi-cjs": { "name": "strip-ansi", "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "inBundle": true, "license": "MIT", @@ -16417,6 +16597,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass": { "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", "dev": true, "inBundle": true, "license": "ISC", @@ -16429,6 +16611,8 @@ }, "node_modules/npm/node_modules/tar/node_modules/fs-minipass/node_modules/minipass": { "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", "dev": true, "inBundle": true, "license": "ISC", @@ -16608,6 +16792,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-regex": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16620,6 +16806,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/ansi-styles": { "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, "inBundle": true, "license": "MIT", @@ -16632,12 +16820,16 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/emoji-regex": { "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true, "inBundle": true, "license": "MIT" }, "node_modules/npm/node_modules/wrap-ansi/node_modules/string-width": { "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, "inBundle": true, "license": "MIT", @@ -16655,6 +16847,8 @@ }, "node_modules/npm/node_modules/wrap-ansi/node_modules/strip-ansi": { "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", "dev": true, "inBundle": true, "license": "MIT", @@ -18189,9 +18383,9 @@ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" }, "node_modules/react-player": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.12.0.tgz", - "integrity": "sha512-rymLRz/2GJJD+Wc01S7S+i9pGMFYnNmQibR2gVE3KmHJCBNN8BhPAlOPTGZtn1uKpJ6p4RPLlzPQ1OLreXd8gw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/react-player/-/react-player-2.13.0.tgz", + "integrity": "sha512-gkY7ZdbVFztlKFFhCPcnDrFQm+L399b8fhWsKatZ+b2wpKJwfUHBXQFMRxqYQGT0ic1/wQ7D7EZEWy7ZBqk2pw==", "dependencies": { "deepmerge": "^4.0.0", "load-script": "^1.0.0", @@ -20903,9 +21097,13 @@ } }, "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/package.json b/package.json index 246734dc3d4..ed884f49881 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "events": "^3.3.0", "i18next": "^23.2.7", "i18next-browser-languagedetector": "^7.1.0", - "lodash": "^4.17.21", + "lodash-es": "^4.17.21", "postcss-loader": "^7.3.3", "qrcode.react": "^3.1.0", "raviger": "^4.1.2", @@ -86,7 +86,7 @@ "react-google-recaptcha": "^3.1.0", "react-i18next": "^13.0.1", "react-markdown": "^8.0.7", - "react-player": "^2.12.0", + "react-player": "^2.13.0", "react-qr-reader": "^2.2.1", "react-redux": "^8.1.1", "react-transition-group": "^4.4.5", @@ -97,7 +97,7 @@ "rehype-raw": "^6.1.1", "rescript-webapi": "^0.8.0", "use-keyboard-shortcut": "^1.1.6", - "uuid": "^9.0.0" + "uuid": "^9.0.1" }, "devDependencies": { "@storybook/addon-essentials": "^7.0.26", @@ -113,7 +113,7 @@ "@types/cypress": "^1.1.3", "@types/echarts": "^4.9.18", "@types/google.maps": "^3.53.4", - "@types/lodash": "^4.14.195", + "@types/lodash-es": "^4.17.9", "@types/lodash.get": "^4.4.7", "@types/node": "^20.4.0", "@types/prop-types": "*", diff --git a/public/config.json b/public/config.json index 8bb0dab3b57..444362cfb20 100644 --- a/public/config.json +++ b/public/config.json @@ -5,12 +5,12 @@ "site_url": "care.coronasafe.in", "analytics_server_url": "https://plausible.10bedicu.in", "header_logo": { - "light":"https://cdn.coronasafe.network/header_logo.png", - "dark":"https://cdn.coronasafe.network/header_logo.png" + "light": "https://cdn.coronasafe.network/header_logo.png", + "dark": "https://cdn.coronasafe.network/header_logo.png" }, "main_logo": { - "light":"https://cdn.coronasafe.network/light-logo.svg", - "dark":"https://cdn.coronasafe.network/black-logo.svg" + "light": "https://cdn.coronasafe.network/light-logo.svg", + "dark": "https://cdn.coronasafe.network/black-logo.svg" }, "gmaps_api_key": "AIzaSyDsBAc3y7deI5ZO3NtK5GuzKwtUzQNJNUk", "gov_data_api_key": "579b464db66ec23bdd000001cdd3946e44ce4aad7209ff7b23ac571b", diff --git a/src/CAREUI/display/Chip.tsx b/src/CAREUI/display/Chip.tsx index abc21711551..7bcbd6078e4 100644 --- a/src/CAREUI/display/Chip.tsx +++ b/src/CAREUI/display/Chip.tsx @@ -11,6 +11,7 @@ interface Props { text: string; tooltip?: string; className?: string; + id?: string; } export default function Chip({ @@ -21,6 +22,7 @@ export default function Chip({ }: Props) { return ( { +const RecordMeta = ({ time, user, prefix, className, inlineUser }: Props) => { const isOnline = user && isUserOnline(user); let child = ( @@ -25,14 +26,15 @@ const RecordMeta = ({ time, user, prefix, className }: Props) => { {relativeTime(time)} {formatDateTime(time)} - {user && ( - <> + {user && !inlineUser && ( + + by {user.first_name} {user.last_name} {isOnline && ( -
+
)} - + )}
@@ -43,7 +45,13 @@ const RecordMeta = ({ time, user, prefix, className }: Props) => {
{prefix} {child} + {user && inlineUser && by} {user && } + {user && inlineUser && ( + + {user.first_name} {user.last_name} + + )}
); } diff --git a/src/CAREUI/misc/PaginatedList.tsx b/src/CAREUI/misc/PaginatedList.tsx index 28ee17650ad..3b699177d9e 100644 --- a/src/CAREUI/misc/PaginatedList.tsx +++ b/src/CAREUI/misc/PaginatedList.tsx @@ -30,7 +30,7 @@ function useContextualized() { return ctx as PaginatedListContext; } -interface Props extends QueryOptions { +interface Props extends QueryOptions> { route: QueryRoute>; perPage?: number; children: (ctx: PaginatedListContext) => JSX.Element | JSX.Element[]; diff --git a/src/Common/constants.tsx b/src/Common/constants.tsx index f64a9fa27ae..0b08b4b9a42 100644 --- a/src/Common/constants.tsx +++ b/src/Common/constants.tsx @@ -499,6 +499,7 @@ export const TELEMEDICINE_ACTIONS = [ { id: 60, text: "COMPLETE", desc: "Complete" }, { id: 70, text: "REVIEW", desc: "Review" }, { id: 80, text: "NOT_REACHABLE", desc: "Not Reachable" }, + { id: 90, text: "DISCHARGE_RECOMMENDED", desc: "Discharge Recommended" }, ]; export const FRONTLINE_WORKER = [ @@ -896,7 +897,7 @@ export const XLSXAssetImportSchema = { Class: { prop: "asset_class", type: String, - oneOf: ["HL7MONITOR", "ONVIF"], + oneOf: ["HL7MONITOR", "ONVIF", "VENTILATOR", ""], }, Description: { prop: "description", type: String }, "Working Status": { @@ -908,7 +909,7 @@ export const XLSXAssetImportSchema = { } else if (status === "NOT WORKING") { return false; } else { - throw new Error("Invalid Working Status"); + throw new Error("Invalid Working Status: " + status); } }, required: true, @@ -917,6 +918,7 @@ export const XLSXAssetImportSchema = { prop: "not_working_reason", type: String, }, + "Serial Number": { prop: "serial_number", type: String }, "QR Code ID": { prop: "qr_code_id", type: String }, Manufacturer: { prop: "manufacturer", type: String }, "Vendor Name": { prop: "vendor_name", type: String }, @@ -925,10 +927,11 @@ export const XLSXAssetImportSchema = { prop: "support_email", type: String, parse: (email: string) => { + if (!email) return null; const isValid = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(email); if (!isValid) { - throw new Error("Invalid Support Email"); + throw new Error("Invalid Support Email: " + email); } return email; @@ -938,23 +941,38 @@ export const XLSXAssetImportSchema = { prop: "support_phone", type: String, parse: (phone: number | string) => { - phone = "+91" + String(phone); - if (!PhoneNumberValidator(["support"])(phone) === undefined) { - throw new Error("Invalid Support Phone Number"); + phone = String(phone); + if (phone.length === 10 && !phone.startsWith("1800")) { + phone = "+91" + phone; + } + if (phone.startsWith("91") && phone.length === 12) { + phone = "+" + phone; + } + if (phone.startsWith("+911800")) { + phone = "1800" + phone.slice(6); + } + if ( + PhoneNumberValidator(["mobile", "landline", "support"])(phone) !== + undefined + ) { + throw new Error("Invalid Support Phone Number: " + phone); } return phone ? phone : undefined; }, required: true, }, - "Warrenty End Date": { + "Warranty End Date": { prop: "warranty_amc_end_of_validity", type: String, parse: (date: string) => { - const parsed = new Date(date); + if (!date) return null; + const parts = date.split("-"); + const reformattedDateStr = `${parts[2]}-${parts[1]}-${parts[0]}`; + const parsed = new Date(reformattedDateStr); if (String(parsed) === "Invalid Date") { - throw new Error("Invalid Warrenty End Date"); + throw new Error("Invalid Warranty End Date:" + date); } return dateQueryString(parsed); @@ -964,10 +982,13 @@ export const XLSXAssetImportSchema = { prop: "last_serviced_on", type: String, parse: (date: string) => { - const parsed = new Date(date); + if (!date) return null; + const parts = date.split("-"); + const reformattedDateStr = `${parts[2]}-${parts[1]}-${parts[0]}`; + const parsed = new Date(reformattedDateStr); if (String(parsed) === "Invalid Date") { - throw new Error("Invalid Last Service Date"); + throw new Error("Invalid Last Service Date:" + date); } return dateQueryString(parsed); @@ -981,13 +1002,14 @@ export const XLSXAssetImportSchema = { prop: "local_ip_address", type: String, parse: (ip: string) => { + if (!ip) return null; const isValid = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/.test( ip ); if (!isValid) { - throw new Error("Invalid Config IP Address"); + throw new Error("Invalid Config IP Address: " + ip); } return ip; diff --git a/src/Common/hooks/useAsyncOptions.ts b/src/Common/hooks/useAsyncOptions.ts index f0a1c895430..2f3f68d5c3a 100644 --- a/src/Common/hooks/useAsyncOptions.ts +++ b/src/Common/hooks/useAsyncOptions.ts @@ -1,4 +1,4 @@ -import { debounce } from "lodash"; +import { debounce } from "lodash-es"; import { useMemo, useState } from "react"; import { useDispatch } from "react-redux"; diff --git a/src/Common/hooks/useConfig.ts b/src/Common/hooks/useConfig.ts index 7b956afec07..15e456ea571 100644 --- a/src/Common/hooks/useConfig.ts +++ b/src/Common/hooks/useConfig.ts @@ -68,6 +68,7 @@ export interface IConfig { * Env to toggle peacetime and wartime shifting */ wartime_shifting: boolean; + jwt_token_refresh_interval?: number; } const useConfig = () => { diff --git a/src/Common/hooks/useMSEplayer.ts b/src/Common/hooks/useMSEplayer.ts index b14f94a6a43..fcbf216ed6a 100644 --- a/src/Common/hooks/useMSEplayer.ts +++ b/src/Common/hooks/useMSEplayer.ts @@ -208,6 +208,9 @@ export const useMSEMediaPlayer = ({ readPacket(event.data); } }; + ws.onerror = function (event) { + onError && onError(event); + }; }, false ); diff --git a/src/Common/hooks/useSlug.ts b/src/Common/hooks/useSlug.ts new file mode 100644 index 00000000000..69d8f591c84 --- /dev/null +++ b/src/Common/hooks/useSlug.ts @@ -0,0 +1,45 @@ +import { usePath } from "raviger"; + +/** + * Returns the slug from the current path. + * @param prefix The prefix of the slug. + * @returns The slug. + * @example + * // Current path: /consultation/94b9a + * const consultation = useSlug("consultation"); // consultation = "94b9a" + */ +export default function useSlug(prefix: string) { + const path = usePath() ?? ""; + return findSlug(path.split("/"), prefix); +} + +/** + * Returns the slugs from the current path. + * @param prefix The prefixes of the slug. + * @returns The slugs + * @example + * // Current path: /facility/5b0a/consultation/94b9a + * const [facility, consultation] = useSlug("facility", "consultation"); + * // facility = "5b0a" + * // consultation = "94b9a" + */ +export const useSlugs = (...prefix: string[]) => { + const path = usePath() ?? ""; + return prefix.map((p) => findSlug(path.split("/"), p)); +}; + +const findSlug = (segments: string[], prefix: string) => { + const index = segments.findIndex((segment) => segment === prefix); + if (index === -1) { + throw new Error( + `Prefix "${prefix}" not found in path "${segments.join("/")}"` + ); + } + + const slug = segments[index + 1]; + if (!slug) { + throw new Error(`Slug not found in path "${segments.join("/")}"`); + } + + return slug; +}; diff --git a/src/Components/ABDM/ABHAProfileModal.tsx b/src/Components/ABDM/ABHAProfileModal.tsx index 0d6b7d9b90b..cc16fd45b8d 100644 --- a/src/Components/ABDM/ABHAProfileModal.tsx +++ b/src/Components/ABDM/ABHAProfileModal.tsx @@ -5,9 +5,9 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import DialogModal from "../Common/Dialog"; import QRCode from "qrcode.react"; import { formatDateTime } from "../../Utils/utils"; -import { getAbhaCard } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { useRef } from "react"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; interface IProps { patientId?: string; @@ -18,21 +18,24 @@ interface IProps { const ABHAProfileModal = ({ patientId, show, onClose, abha }: IProps) => { const printRef = useRef(null); - const dispatch = useDispatch(); const downloadAbhaCard = async (type: "pdf" | "png") => { if (!patientId) return; - const response = await dispatch(getAbhaCard(patientId, type)); + const { res, data } = await request(routes.abha.getAbhaCard, { + body: { + patient: patientId, + type: type, + }, + }); - if (response.status === 200 && response.data) { + if (res?.status === 200 && data) { if (type === "png") { const downloadLink = document.createElement("a"); - downloadLink.href = - "data:application/octet-stream;base64," + response.data; + downloadLink.href = "data:application/octet-stream;base64," + data; downloadLink.download = "abha.png"; downloadLink.click(); } else { - const htmlPopup = ``; + const htmlPopup = ``; const printWindow = window.open("", "PDF"); printWindow?.document.write(htmlPopup); diff --git a/src/Components/ABDM/ConfigureHealthFacility.tsx b/src/Components/ABDM/ConfigureHealthFacility.tsx index 025a3f0e862..a2b8f254bcb 100644 --- a/src/Components/ABDM/ConfigureHealthFacility.tsx +++ b/src/Components/ABDM/ConfigureHealthFacility.tsx @@ -1,12 +1,12 @@ -import { lazy, useCallback, useEffect, useReducer, useState } from "react"; -import { useDispatch } from "react-redux"; - -import { healthFacilityActions } from "../../Redux/actions"; +import { lazy, useEffect, useReducer, useState } from "react"; import * as Notification from "../../Utils/Notifications.js"; import { navigate } from "raviger"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import TextFormField from "../Form/FormFields/TextFormField"; import { classNames } from "../../Utils/utils"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import request from "../../Utils/request/request"; const Loading = lazy(() => import("../Common/Loading")); const initForm = { @@ -40,30 +40,28 @@ const FormReducer = (state = initialState, action: any) => { export const ConfigureHealthFacility = (props: any) => { const [state, dispatch] = useReducer(FormReducer, initialState); const { facilityId } = props; - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); - const fetchData = useCallback(async () => { - if (facilityId) { - setIsLoading(true); - const res = await dispatchAction(healthFacilityActions.read(facilityId)); - - if (res?.status === 200 && res?.data) { - const formData = { - ...state.form, - hf_id: res.data.hf_id, - health_facility: res.data, - }; - dispatch({ type: "set_form", form: formData }); - } + const { + data: health_facility, + loading, + refetch, + } = useQuery(routes.abha.getHealthFacility, { + pathParams: { facility__external_id: facilityId }, + }); - setIsLoading(false); - } - }, [dispatchAction, facilityId]); + useEffect(() => { + const formData = { + ...state.form, + hf_id: health_facility?.hf_id, + health_facility: health_facility, + }; + dispatch({ type: "set_form", form: formData }); + }, [health_facility]); useEffect(() => { - fetchData(); - }, [dispatch, fetchData]); + refetch(); + }, [dispatch, refetch]); const handleSubmit = async (e: any) => { e.preventDefault(); @@ -78,27 +76,43 @@ export const ConfigureHealthFacility = (props: any) => { return; } - let res = null; + let response = null; + let responseData = null; if (state.form.health_facility) { - res = await dispatchAction( - healthFacilityActions.partialUpdate(facilityId, { - hf_id: state.form.hf_id, - }) + const { res, data } = await request( + routes.abha.partialUpdateHealthFacility, + { + pathParams: { + facility__external_id: facilityId, + }, + body: { + hf_id: state.form.hf_id, + }, + } ); + response = res; + responseData = data; } else if (state.form.hf_id === state.form.health_facility?.hf_id) { - res = await dispatchAction( - healthFacilityActions.registerService(facilityId) + const { res, data } = await request( + routes.abha.registerHealthFacilityAsService, + { + pathParams: { + facility__external_id: facilityId, + }, + } ); + response = res; + responseData = data; - if (res?.status === 200 && res?.data) { - if (res.data?.registered) { + if (response?.status === 200 && responseData) { + if (responseData?.registered) { dispatch({ type: "set_form", form: { ...state.form, health_facility: { ...state.form?.health_facility, - registered: res.data.registered, + registered: responseData.registered, }, }, }); @@ -112,24 +126,26 @@ export const ConfigureHealthFacility = (props: any) => { }); return; } else { - res = await dispatchAction( - healthFacilityActions.create({ + const { res, data } = await request(routes.abha.createHealthFacility, { + body: { facility: facilityId, hf_id: state.form.hf_id, - }) - ); + }, + }); + response = res; + responseData = data; } setIsLoading(false); - if (res && res.data) { + if (response && responseData) { Notification.Success({ msg: "Health Facility config updated successfully", }); navigate(`/facility/${facilityId}`); } else { - if (res?.data) + if (responseData) Notification.Error({ - msg: "Something went wrong: " + (res.data.detail || ""), + msg: "Something went wrong: " + (responseData.detail || ""), }); } setIsLoading(false); @@ -142,7 +158,7 @@ export const ConfigureHealthFacility = (props: any) => { }); }; - if (isLoading) { + if (loading || isLoading) { return ; } diff --git a/src/Components/ABDM/LinkABHANumberModal.tsx b/src/Components/ABDM/LinkABHANumberModal.tsx index 30b1ff4afa3..367c9815db1 100644 --- a/src/Components/ABDM/LinkABHANumberModal.tsx +++ b/src/Components/ABDM/LinkABHANumberModal.tsx @@ -1,19 +1,6 @@ import * as Notify from "../../Utils/Notifications"; import Dropdown, { DropdownItem } from "../Common/components/Menu"; -import { - checkAndGenerateMobileOtp, - confirmWithAadhaarOtp, - confirmWithMobileOtp, - createHealthId, - generateAadhaarOtp, - initiateAbdmAuthentication, - linkViaQR, - resentAadhaarOtp, - searchByHealthId, - verifyAadhaarOtp, - verifyMobileOtp, -} from "../../Redux/actions"; import { useEffect, useState } from "react"; import ButtonV2 from "../Common/components/ButtonV2"; @@ -24,7 +11,8 @@ import OtpFormField from "../Form/FormFields/OtpFormField"; import QRScanner from "../Common/QRScanner"; import TextFormField from "../Form/FormFields/TextFormField"; import { classNames } from "../../Utils/utils"; -import { useDispatch } from "react-redux"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; export const validateRule = ( condition: boolean, @@ -144,8 +132,6 @@ const ScanABHAQRSection = ({ onSuccess, closeModal, }: ScanABHAQRSectionProps) => { - const dispatch = useDispatch(); - const [qrValue, setQrValue] = useState(""); const [authMethods, setAuthMethods] = useState([]); const [selectedAuthMethod, setSelectedAuthMethod] = useState(""); @@ -187,23 +173,23 @@ const ScanABHAQRSection = ({ try { const abha = JSON.parse(value); - const res = await dispatch(linkViaQR(abha, patientId)); + const { res, data } = await request(routes.abha.linkViaQR, { + body: { ...abha, patientId }, + }); if (res?.status === 200 || res?.status === 202) { Notify.Success({ msg: "Request sent successfully" }); onSuccess?.({ - ...res.data, + ...data, abha_profile: { - ...res.data?.abha_profile, - healthIdNumber: res.data?.abha_profile?.abha_number, - healthId: res.data?.abha_profile?.health_id, + ...data?.abha_profile, + healthIdNumber: data?.abha_profile?.abha_number, + healthId: data?.abha_profile?.health_id, mobile: abha?.mobile, monthOfBirth: - res.data?.abha_profile?.date_of_birth?.split("-")[1], - dayOfBirth: - res.data?.abha_profile?.date_of_birth?.split("-")[2], - yearOfBirth: - res.data?.abha_profile?.date_of_birth?.split("-")[0], + data?.abha_profile?.date_of_birth?.split("-")[1], + dayOfBirth: data?.abha_profile?.date_of_birth?.split("-")[2], + yearOfBirth: data?.abha_profile?.date_of_birth?.split("-")[0], }, }); } else { @@ -256,29 +242,55 @@ const ScanABHAQRSection = ({ disabled={otp.length !== 6} onClick={async () => { let response = null; + let Rdata = null; + let Rerror = null; switch (selectedAuthMethod) { case "MOBILE_OTP": - response = await dispatch( - confirmWithMobileOtp(txnId, otp, patientId) - ); + { + const { res, data, error } = await request( + routes.abha.confirmWithMobileOtp, + { + body: { + otp: otp, + txnId: txnId, + patientId: patientId, + }, + } + ); + response = res; + Rdata = data; + Rerror = error; + } break; case "AADHAAR_OTP": - response = await dispatch( - confirmWithAadhaarOtp(txnId, otp, patientId) - ); + { + const { res, data, error } = await request( + routes.abha.confirmWithAadhaarOtp, + { + body: { + otp: otp, + txnId: txnId, + patientId: patientId, + }, + } + ); + response = res; + Rdata = data; + Rerror = error; + } break; } - if (response.status === 200) { - onSuccess?.(response.data); + if (response?.status === 200) { + onSuccess?.(Rdata); Notify.Success({ msg: "ABHA Number linked successfully", }); } else { Notify.Error({ - msg: response?.message ?? "Something went wrong!", + msg: Rerror ?? "Something went wrong!", }); } }} @@ -290,13 +302,14 @@ const ScanABHAQRSection = ({ {authMethods.map((method) => ( { - const response = await dispatch( - initiateAbdmAuthentication(method, qrValue) + const { res, data } = await request( + routes.abha.initiateAbdmAuthentication, + { body: { authMethod: method, healthid: qrValue } } ); - if (response.status === 200 && response?.data?.txnId) { + if (res?.status === 200 && data?.txnId) { setSelectedAuthMethod(method); - setTxnId(response.data.txnId); + setTxnId(data.txnId); } }} > @@ -308,11 +321,18 @@ const ScanABHAQRSection = ({ { - const response = await dispatch(searchByHealthId(qrValue)); - - if (response.status === 200 && response?.data?.authMethods) { + const { res, data } = await request( + routes.abha.searchByHealthId, + { + body: { + healthId: qrValue, + }, + } + ); + + if (res?.status === 200 && data?.authMethods) { setAuthMethods( - response.data.authMethods?.filter?.((method: string) => + data.authMethods?.filter?.((method: string) => supportedAuthMethods.find( (supported) => supported === method ) @@ -339,8 +359,6 @@ const VerifyAadhaarSection = ({ onVerified, onSignin, }: VerifyAadhaarSectionProps) => { - const dispatch = useDispatch(); - const [aadhaarNumber, setAadhaarNumber] = useState(""); const [aadhaarNumberError, setAadhaarNumberError] = useState(); @@ -382,17 +400,22 @@ const VerifyAadhaarSection = ({ if (!validateAadhaar()) return; setIsSendingOtp(true); - const res = await dispatch(generateAadhaarOtp(aadhaarNumber)); + + const { res, data } = await request(routes.abha.generateAadhaarOtp, { + body: { + aadhaar: aadhaarNumber, + }, + }); setIsSendingOtp(false); - if (res.status === 200 && res.data) { - const { txnId } = res.data; + if (res?.status === 200 && data) { + const { txnId } = data; setTxnId(txnId); Notify.Success({ msg: "OTP has been sent to the mobile number registered with the Aadhar number.", }); } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } }; @@ -400,16 +423,20 @@ const VerifyAadhaarSection = ({ if (!validateAadhaar() || !txnId) return; setIsSendingOtp(true); - const res = await dispatch(resentAadhaarOtp(txnId)); + const { res, data } = await request(routes.abha.generateAadhaarOtp, { + body: { + txnId: txnId, + }, + }); setIsSendingOtp(false); - if (res.status === 200 && res.data.txnId) { - setTxnId(res.data.txnId); + if (res?.status === 200 && data?.txnId) { + setTxnId(data.txnId); Notify.Success({ msg: "OTP has been resent to the mobile number registered with the Aadhar number.", }); } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } }; @@ -430,11 +457,16 @@ const VerifyAadhaarSection = ({ if (!validateOtp() || !txnId) return; setIsVerifyingOtp(true); - const res = await dispatch(verifyAadhaarOtp(txnId, otp)); + const { res, data } = await request(routes.abha.verifyAadhaarOtp, { + body: { + otp: otp, + txnId: txnId, + }, + }); setIsVerifyingOtp(false); - if (res.status === 200 && res.data.txnId) { - setTxnId(res.data.txnId); + if (res?.status === 200 && data?.txnId) { + setTxnId(data.txnId); Notify.Success({ msg: "OTP verified" }); setIsVerified(true); } else { @@ -563,8 +595,6 @@ const VerifyMobileSection = ({ onVerified, patientMobile, }: VerifyMobileSectionProps) => { - const dispatch = useDispatch(); - const [mobile, setMobile] = useState(() => patientMobile || ""); const [mobileError, setMobileError] = useState(); @@ -602,11 +632,16 @@ const VerifyMobileSection = ({ setOtpDispatched(false); setIsSendingOtp(true); - const res = await dispatch(checkAndGenerateMobileOtp(txnId, mobile)); + const { res, data } = await request(routes.abha.checkAndGenerateMobileOtp, { + body: { + mobile: mobile, + txnId: txnId, + }, + }); setIsSendingOtp(false); - if (res.status === 200 && res.data) { - const { txnId, mobileLinked } = res.data; + if (res?.status === 200 && data) { + const { txnId, mobileLinked } = data; setTxnId(txnId); if (mobileLinked) { @@ -621,7 +656,7 @@ const VerifyMobileSection = ({ }); } } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } }; @@ -642,11 +677,16 @@ const VerifyMobileSection = ({ if (!validateOtp()) return; setIsVerifyingOtp(true); - const res = await dispatch(verifyMobileOtp(txnId, otp)); + const { res, data } = await request(routes.abha.verifyMobileOtp, { + body: { + txnId: txnId, + otp: otp, + }, + }); setIsVerifyingOtp(false); - if (res.status === 200 && res.data.txnId) { - setTxnId(res.data.txnId); + if (res?.status === 200 && data?.txnId) { + setTxnId(data.txnId); Notify.Success({ msg: "OTP verified" }); setIsVerified(true); } else { @@ -718,21 +758,24 @@ const CreateHealthIDSection = ({ onCreateSuccess, patientId, }: CreateHealthIDSectionProps) => { - const dispatch = useDispatch(); const [healthId, setHealthId] = useState(""); const [isCreating, setIsCreating] = useState(false); const [isHealthIdInputInFocus, setIsHealthIdInputInFocus] = useState(false); const handleCreateHealthId = async () => { setIsCreating(true); - const res = await dispatch( - createHealthId({ txnId: transactionId, patientId, healthId }) - ); - if (res.status === 200) { + const { res, data } = await request(routes.abha.createHealthId, { + body: { + healthId: healthId, + txnId: transactionId, + patientId: patientId, + }, + }); + if (res?.status === 200) { Notify.Success({ msg: "Abha Address created" }); - onCreateSuccess(res.data); + onCreateSuccess(data); } else { - Notify.Error({ msg: JSON.stringify(res.data) }); + Notify.Error({ msg: JSON.stringify(data) }); } setIsCreating(false); }; diff --git a/src/Components/ABDM/LinkCareContextModal.tsx b/src/Components/ABDM/LinkCareContextModal.tsx index fc0b488954d..36749ff3d23 100644 --- a/src/Components/ABDM/LinkCareContextModal.tsx +++ b/src/Components/ABDM/LinkCareContextModal.tsx @@ -5,9 +5,9 @@ import DateFormField from "../Form/FormFields/DateFormField"; import DialogModal from "../Common/Dialog"; import { PatientModel } from "../Patient/models"; import TextFormField from "../Form/FormFields/TextFormField"; -import { linkCareContext } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { useState } from "react"; +import routes from "../../Redux/api.js"; +import request from "../../Utils/request/request.js"; interface IProps { consultationId: string; @@ -25,8 +25,6 @@ const LinkCareContextModal = ({ const [acceptedDisclaimer, setAcceptedDisclaimer] = useState(false); const [isLinkingCareContext, setIsLinkingCareContext] = useState(false); - const dispatch = useDispatch(); - return ( { setIsLinkingCareContext(true); - const res = await dispatch( - linkCareContext(consultationId, { + const { res } = await request(routes.abha.linkCareContext, { + body: { + consultation: consultationId, name: patient?.abha_number_object?.name, gender: patient?.abha_number_object?.gender, dob: patient?.abha_number_object?.date_of_birth, - }) - ); - - if (res.status === 202) { + }, + reattempts: 0, + }); + if (res?.status === 202) { Notification.Success({ msg: "Care Context sucessfully linked!", }); diff --git a/src/Components/ABDM/models.ts b/src/Components/ABDM/models.ts index b01579fba88..f992cc9df4f 100644 --- a/src/Components/ABDM/models.ts +++ b/src/Components/ABDM/models.ts @@ -17,3 +17,84 @@ export interface ICreateHealthIdResponse { healthId?: string; healthIdNumber: string; } + +export interface IHealthFacility { + id: string; + registered: boolean; + external_id: string; + created_date: string; + modified_date: string; + hf_id: string; + facility: string; + detail?: string; +} + +export interface ILinkABHANumber { + abha_profile: { + abha_number: string; + health_id: string; + date_of_birth: string; + }; +} + +export interface IConfirmMobileOtp { + otp: string; + txnId: string; + patientId?: string; + message?: string; +} + +export interface IHealthId { + authMethods?: string[]; +} + +export interface IAadhaarOtp { + txnId: string; +} + +export interface ICheckAndGenerateMobileOtp { + mobileLinked: boolean; + txnId: string; +} + +export interface IAadhaarOtpTBody { + aadhaar?: string; + txnId?: string; +} + +export interface IVerifyAadhaarOtpTBody { + consultation?: string; + name?: string; + gender?: "M" | "F" | "O"; + dob?: string; + otp?: string; + txnId?: string; +} + +export interface IGenerateMobileOtpTBody { + mobile: string; + txnId: string; +} + +export interface ISearchByHealthIdTBody { + healthId: string; +} + +export interface IinitiateAbdmAuthenticationTBody { + authMethod: string; + healthid: string; +} + +export interface IgetAbhaCardTBody { + patient: string; + type: "pdf" | "png"; +} + +export interface IcreateHealthFacilityTBody { + facility: string; + hf_id: string; +} + +export interface IpartialUpdateHealthFacilityTBody { + hf_id: string; +} diff --git a/src/Components/Assets/AssetFilter.tsx b/src/Components/Assets/AssetFilter.tsx index 4fc4ff6d7f2..8b250088d1a 100644 --- a/src/Components/Assets/AssetFilter.tsx +++ b/src/Components/Assets/AssetFilter.tsx @@ -1,19 +1,19 @@ import { useState, useEffect, useCallback } from "react"; -import { useAbortableEffect, statusType } from "../../Common/utils"; import { navigate, useQueryParams } from "raviger"; import { FacilitySelect } from "../Common/FacilitySelect"; import { FacilityModel } from "../Facility/models"; -import { useDispatch } from "react-redux"; -import { - getFacilityAssetLocation, - getPermittedFacility, -} from "../../Redux/actions"; -import * as Notification from "../../Utils/Notifications.js"; import { LocationSelect } from "../Common/LocationSelect"; import { AssetClass, AssetLocationObject } from "./AssetTypes"; import { FieldLabel } from "../Form/FormFields/FormField"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import FiltersSlideover from "../../CAREUI/interactive/FiltersSlideover"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import DateRangeFormField from "../Form/FormFields/DateRangeFormField"; +import dayjs from "dayjs"; +import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import { DateRange } from "../Common/DateRangeInputV2"; +import { dateQueryString } from "../../Utils/utils"; const initialLocation = { id: "", @@ -25,9 +25,11 @@ const initialLocation = { }, }; +const getDate = (value: any) => + value && dayjs(value).isValid() && dayjs(value).toDate(); + function AssetFilter(props: any) { const { filter, onChange, closeFilter } = props; - const dispatch: any = useDispatch(); const [facility, setFacility] = useState({ name: "" }); const [location, setLocation] = useState(initialLocation); @@ -38,12 +40,39 @@ function AssetFilter(props: any) { const [asset_class, setAssetClass] = useState( filter.asset_class || "" ); - const [facilityId, setFacilityId] = useState(filter.facility); + const [facilityId, setFacilityId] = useState(filter.facility); const [locationId, setLocationId] = useState(filter.location); + const [warrantyExpiry, setWarrantyExpiry] = useState({ + before: filter.warranty_amc_end_of_validity_before || null, + after: filter.warranty_amc_end_of_validity_after || null, + }); const [qParams, _] = useQueryParams(); + useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setFacility(data); + } + }, + prefetch: !!facilityId, + }); + + useQuery(routes.getFacilityAssetLocation, { + pathParams: { + facilityId: String(facilityId), + locationId: String(locationId), + }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setLocation(data); + } + }, + prefetch: !!(facilityId && locationId), + }); + useEffect(() => { - setFacilityId(facility?.id ? facility?.id : ""); + setFacilityId(facility?.id ? `${facility?.id}` : ""); setLocationId( facility?.id === qParams.facility ? qParams.location ?? "" : "" ); @@ -63,48 +92,6 @@ function AssetFilter(props: any) { else navigate("/assets"); }, [qParams]); - const fetchFacility = useCallback( - async (status: statusType) => { - if (facilityId) { - const facilityData: any = await dispatch( - getPermittedFacility(facilityId) - ); - if (!status.aborted) { - setFacility(facilityData?.data); - } - } - }, - [filter.facility] - ); - - const fetchLocation = useCallback( - async (status: statusType) => { - if (locationId && facilityId) { - const [locationData]: any = await Promise.all([ - dispatch( - getFacilityAssetLocation(String(facilityId), String(locationId)) - ), - ]); - if (!status.aborted && locationData !== undefined) { - if (!locationData.data) - Notification.Error({ - msg: "Something went wrong..!", - }); - else { - setLocation(locationData.data); - } - } - } else { - setLocation(initialLocation); - } - }, - [filter.location] - ); - - useAbortableEffect((status: statusType) => { - filter.facility && fetchFacility(status); - filter.location && fetchLocation(status); - }, []); const applyFilter = () => { const data = { facility: facilityId, @@ -112,6 +99,10 @@ function AssetFilter(props: any) { asset_class: asset_class ?? "", status: asset_status ?? "", location: locationId, + warranty_amc_end_of_validity_before: dateQueryString( + warrantyExpiry.before + ), + warranty_amc_end_of_validity_after: dateQueryString(warrantyExpiry.after), }; onChange(data); }; @@ -124,6 +115,13 @@ function AssetFilter(props: any) { setLocationId(selectedId); }; + const handleDateRangeChange = (event: FieldChangeEvent) => { + const state = { ...warrantyExpiry }; + state.after = event.value.start?.toString(); + state.before = event.value.end?.toString(); + setWarrantyExpiry(state); + }; + return ( setAssetClass(value)} /> + + ); } diff --git a/src/Components/Assets/AssetImportModal.tsx b/src/Components/Assets/AssetImportModal.tsx index 548df2901ec..f8ab7cbc65f 100644 --- a/src/Components/Assets/AssetImportModal.tsx +++ b/src/Components/Assets/AssetImportModal.tsx @@ -5,10 +5,7 @@ import { FacilityModel } from "../Facility/models"; import { AssetData } from "./AssetTypes"; import * as Notification from "../../Utils/Notifications.js"; import { Cancel, Submit } from "../Common/components/ButtonV2"; -import { listFacilityAssetLocation } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; import { Link } from "raviger"; -import SelectMenuV2 from "../Form/SelectMenuV2"; import readXlsxFile from "read-excel-file"; import { LocalStorageKeys, @@ -17,6 +14,9 @@ import { import { parseCsvFile } from "../../Utils/utils"; import useConfig from "../../Common/hooks/useConfig"; import DialogModal from "../Common/Dialog"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; interface Props { open: boolean; @@ -25,30 +25,31 @@ interface Props { } const AssetImportModal = ({ open, onClose, facility }: Props) => { - const [isImporting, setIsUploading] = useState(false); + const [isImporting, setIsImporting] = useState(false); const [selectedFile, setSelectedFile] = useState(); const [preview, setPreview] = useState<(AssetData & { notes?: string; last_serviced_on?: string })[]>(); const [location, setLocation] = useState(""); + const [errors, setErrors] = useState({ + location: "", + }); const [locations, setLocations] = useState([]); - const dispatchAction: any = useDispatch(); const { sample_format_asset_import } = useConfig(); + const [locationsLoading, setLocationsLoading] = useState(false); const closeModal = () => { setPreview(undefined); setSelectedFile(undefined); onClose && onClose(); }; - - useEffect(() => { - dispatchAction( - listFacilityAssetLocation({}, { facility_external_id: facility.id }) - ).then(({ data }: any) => { - if (data.count > 0) { + useQuery(routes.listFacilityAssetLocation, { + pathParams: { facility_external_id: `${facility.id}` }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { setLocations(data.results); } - }); - }, []); + }, + }); useEffect(() => { const readFile = async () => { @@ -85,11 +86,11 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { } } } - } catch (e) { + } catch (e: any) { setPreview(undefined); console.log(e); Notification.Error({ - msg: "Invalid file", + msg: "Invalid file: " + e.message, }); setSelectedFile(undefined); } @@ -110,10 +111,19 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { closeModal(); return; } + if (!location) { + setErrors({ + ...errors, + location: "Please select a location", + }); + return; + } + setIsImporting(true); let error = false; + Notification.Success({ msg: "Importing assets..." }); for (const asset of preview || []) { - const asset_data = JSON.stringify({ + const asset_data: any = { name: asset.name, asset_type: asset.asset_type, asset_class: asset.asset_class, @@ -129,11 +139,15 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { qr_code_id: asset.qr_code_id, manufacturer: asset.manufacturer, meta: { ...asset.meta }, - warranty_amc_end_of_validity: asset.warranty_amc_end_of_validity, - last_serviced_on: asset.last_serviced_on, note: asset.notes, - cancelToken: { promise: {} }, - }); + }; + + if (asset.last_serviced_on) + asset_data["last_serviced_on"] = asset.last_serviced_on; + + if (asset.warranty_amc_end_of_validity) + asset_data["warranty_amc_end_of_validity"] = + asset.warranty_amc_end_of_validity; const response = await fetch("/api/v1/asset/", { method: "POST", @@ -142,7 +156,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { Authorization: "Bearer " + localStorage.getItem(LocalStorageKeys.accessToken), }, - body: asset_data, + body: JSON.stringify(asset_data), }); const data = await response.json(); if (response.status !== 201) { @@ -152,16 +166,22 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { }); error = true; } else { - if (preview) setPreview(preview.filter((a) => a.id !== asset.id)); + setPreview((preview) => { + return preview?.slice(1); + }); } } if (!error) { Notification.Success({ msg: "Assets imported successfully" }); await sleep(1000); - setIsUploading(false); - closeModal(); + setIsImporting(false); window.location.reload(); - } else Notification.Error({ msg: "Error importing some assets" }); + } else { + Notification.Error({ msg: "Error importing some assets" }); + await sleep(1000); + setIsImporting(false); + closeModal(); + } }; const dragProps = useDragAndDrop(); @@ -189,7 +209,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { fixedWidth={false} > {facility.name} - {locations.length === 0 ? ( + {!locationsLoading && locations.length === 0 ? ( <>

@@ -214,31 +234,28 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { {preview && preview?.length > 0 ? (

- {preview.length} assets will be imported + {preview.length} assets {isImporting ? "are being" : "will be"}{" "} + imported

-
@@ -340,6 +357,7 @@ const AssetImportModal = ({ open, onClose, facility }: Props) => { {isImporting ? ( diff --git a/src/Components/Assets/AssetManage.tsx b/src/Components/Assets/AssetManage.tsx index 84325d704aa..23098bff22f 100644 --- a/src/Components/Assets/AssetManage.tsx +++ b/src/Components/Assets/AssetManage.tsx @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect, ReactElement, lazy } from "react"; +import { useState, useEffect, ReactElement, lazy } from "react"; import { AssetClass, assetClassProps, @@ -6,14 +6,6 @@ import { AssetService, AssetTransaction, } from "./AssetTypes"; -import { statusType, useAbortableEffect } from "../../Common/utils"; -import { useDispatch } from "react-redux"; -import { - deleteAsset, - getAsset, - listAssetService, - listAssetTransaction, -} from "../../Redux/actions"; import Pagination from "../Common/Pagination"; import { navigate } from "raviger"; import QRCode from "qrcode.react"; @@ -34,7 +26,11 @@ import useAuthUser from "../../Common/hooks/useAuthUser"; import dayjs from "dayjs"; import RelativeDateUserMention from "../Common/RelativeDateUserMention"; import { AssetServiceEditModal } from "./AssetServiceEditModal"; +import { warrantyAmcValidityChip } from "./AssetsList"; import Page from "../Common/components/Page"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; interface AssetManageProps { assetId: string; @@ -50,80 +46,60 @@ const checkAuthority = (type: string, cutoff: string) => { const AssetManage = (props: AssetManageProps) => { const { t } = useTranslation(); const { assetId, facilityId } = props; - const [asset, setAsset] = useState(); const [isPrintMode, setIsPrintMode] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [offset, setOffset] = useState(0); const [totalCount, setTotalCount] = useState(0); - const [transactions, setTransactions] = useState([]); const [transactionDetails, setTransactionDetails] = useState< ReactElement | ReactElement[] >(); - const [services, setServices] = useState([]); const [servicesDetails, setServiceDetails] = useState< ReactElement | ReactElement[] >(); - const [isLoading, setIsLoading] = useState(false); - const dispatch = useDispatch(); const limit = 14; const authUser = useAuthUser(); const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [serviceEditData, setServiceEditData] = useState< AssetService & { open: boolean; viewOnly?: boolean } >(); + const [transactionFilter, setTransactionFilter] = useState({}); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const assetData = await dispatch(getAsset(assetId)); - if (!status.aborted) { - setIsLoading(false); - if (assetData?.data) { - setAsset(assetData.data); - - const transactionFilter = assetData.qr_code_id - ? { qr_code_id: assetData.qr_code_id } - : { external_id: assetId }; - - const [transactionsData, servicesData] = await Promise.all([ - dispatch( - listAssetTransaction({ - ...transactionFilter, - limit, - offset, - }) - ), - dispatch(listAssetService({}, assetId)), - ]); - - if (transactionsData?.data) { - setTransactions(transactionsData.data.results); - setTotalCount(transactionsData.data.count); - } else { - Notification.Error({ - msg: "Error fetching transactions", - }); - } - if (servicesData?.data) { - setServices(servicesData.data.results); - } else { - Notification.Error({ - msg: "Error fetching service logs", - }); - } - } else { - navigate("/not-found"); - } + const { data: asset, loading } = useQuery(routes.getAsset, { + pathParams: { + external_id: assetId, + }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setTransactionFilter( + data.qr_code_id + ? { qr_code_id: data.qr_code_id } + : { external_id: assetId } + ); } }, - [dispatch, assetId, offset] - ); + }); - useAbortableEffect( - (status: statusType) => { - fetchData(status); + const { data: transactions } = useQuery(routes.listAssetTransaction, { + prefetch: !!asset, + query: { + ...transactionFilter, + limit, + offset, }, - [dispatch, fetchData] + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setTotalCount(data.count); + } + }, + }); + + const { data: services, refetch: serviceRefetch } = useQuery( + routes.listAssetService, + { + pathParams: { + asset_external_id: assetId, + }, + } ); const handlePagination = (page: number, limit: number) => { @@ -157,25 +133,25 @@ const AssetManage = (props: AssetManageProps) => { const populateTableRows = (txns: AssetTransaction[]) => { if (txns.length > 0) { setTransactionDetails( - transactions.map((transaction: AssetTransaction) => ( + transactions?.results.map((transaction: AssetTransaction) => ( {transaction.from_location.name} - + {transaction.to_location.name} - + {transaction.performed_by.first_name}{" "} {transaction.performed_by.last_name} - + {formatDateTime(transaction.modified_date)} @@ -200,9 +176,9 @@ const AssetManage = (props: AssetManageProps) => { const populateServiceTableRows = (txns: AssetService[]) => { if (txns.length > 0) { setServiceDetails( - services.map((service: AssetService) => ( + services?.results.map((service: AssetService) => ( - + {dayjs(service.serviced_on).format("DD MMM YYYY")} @@ -229,7 +205,7 @@ const AssetManage = (props: AssetManageProps) => { )} - + { }; useEffect(() => { - populateTableRows(transactions); + if (transactions) populateTableRows(transactions.results); }, [transactions]); useEffect(() => { - populateServiceTableRows(services); + if (services) populateServiceTableRows(services?.results); }, [services]); - if (isLoading) return ; + if (loading) return ; if (isPrintMode) return ; const assetClassProp = @@ -323,13 +299,17 @@ const AssetManage = (props: AssetManageProps) => { const handleDelete = async () => { if (asset) { - const response = await dispatch(deleteAsset(asset.id)); - if (response && response.status === 204) { - Notification.Success({ - msg: "Asset deleted successfully", - }); - navigate("/assets"); - } + await request(routes.deleteAsset, { + pathParams: { + external_id: asset.id, + }, + onResponse: () => { + Notification.Success({ + msg: "Asset deleted successfully", + }); + navigate("/assets"); + }, + }); } }; @@ -408,6 +388,9 @@ const AssetManage = (props: AssetManageProps) => { startIcon="l-times" /> )} + {warrantyAmcValidityChip( + asset?.warranty_amc_end_of_validity as string + )}

@@ -525,7 +508,7 @@ const AssetManage = (props: AssetManageProps) => { - - @@ -558,13 +541,13 @@ const AssetManage = (props: AssetManageProps) => { - - - @@ -591,7 +574,7 @@ const AssetManage = (props: AssetManageProps) => { handleClose={() => setServiceEditData({ ...serviceEditData, open: false }) } - handleUpdate={() => fetchData({ aborted: false })} + handleUpdate={() => serviceRefetch()} show={serviceEditData.open} viewOnly={serviceEditData.viewOnly} /> diff --git a/src/Components/Assets/AssetServiceEditModal.tsx b/src/Components/Assets/AssetServiceEditModal.tsx index 66d44d11907..3964dfe0770 100644 --- a/src/Components/Assets/AssetServiceEditModal.tsx +++ b/src/Components/Assets/AssetServiceEditModal.tsx @@ -1,16 +1,16 @@ import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { updateAssetService } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; import ButtonV2, { Cancel, Submit } from "../Common/components/ButtonV2"; import DialogModal from "../Common/Dialog"; import { AssetData, AssetService, AssetServiceEdit } from "./AssetTypes"; import dayjs from "dayjs"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; -import DateInputV2 from "../Common/DateInputV2"; -import { FieldLabel } from "../Form/FormFields/FormField"; import { formatDate, formatDateTime } from "../../Utils/utils"; import CareIcon from "../../CAREUI/icons/CareIcon"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import DateFormField from "../Form/FormFields/DateFormField"; +import { t } from "i18next"; export const AssetServiceEditModal = (props: { asset?: AssetData; @@ -24,23 +24,25 @@ export const AssetServiceEditModal = (props: { serviced_on: props.service_record?.serviced_on, note: props.service_record?.note, }); - const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [editRecord, setEditRecord] = useState(); const handleSubmit = async (e: any) => { e.preventDefault(); setIsLoading(true); - const data = { + const body = { serviced_on: form.serviced_on, note: form.note, }; - - const res = await dispatchAction( - updateAssetService(props.asset?.id ?? "", props.service_record.id, data) - ); + const { data } = await request(routes.updateAssetService, { + pathParams: { + asset_external_id: props.asset?.id ?? "", + external_id: props.service_record.id, + }, + body: body, + }); setIsLoading(false); - if (res?.data) { + if (data) { Notification.Success({ msg: "Asset service record updated successfully", }); @@ -61,12 +63,12 @@ export const AssetServiceEditModal = (props: {

- Update record for asset + {t("update_record_for_asset")} {props.asset?.name}

@@ -110,13 +112,17 @@ export const AssetServiceEditModal = (props: {
-

Edited On

+

+ {t("edited_on")} +

{formatDateTime(editRecord.edited_on)}

-

Edited By

+

+ {t("edited_by")} +

{editRecord.edited_by.username}

@@ -125,7 +131,7 @@ export const AssetServiceEditModal = (props: {

- Serviced On + {t("serviced_on")}

-

Notes

+

+ {t("notes")} +

{editRecord.note || "-"}

@@ -151,7 +159,7 @@ export const AssetServiceEditModal = (props: { editRecord ? setEditRecord(undefined) : props.handleClose(); }} > - {editRecord ? "Back" : "Close"} + {editRecord ? t("back") : t("close")}
@@ -163,12 +171,12 @@ export const AssetServiceEditModal = (props: {

- Update record for asset + {t("update_record_for_asset")} {props.asset?.name}

@@ -178,19 +186,31 @@ export const AssetServiceEditModal = (props: { className="col-span-6 sm:col-span-3" data-testid="asset-last-serviced-on-input" > - Serviced On - { - setForm({ - ...form, - serviced_on: dayjs(date).format("YYYY-MM-DD"), - }); + if ( + dayjs(date.value).format("YYYY-MM-DD") > + new Date( + props.service_record.created_date + ).toLocaleDateString("en-ca") + ) { + Notification.Error({ + msg: `Service date can't be after ${formatDate( + props.service_record.created_date + )} (Creation date)`, + }); + } else { + setForm({ + ...form, + serviced_on: dayjs(date.value).format("YYYY-MM-DD"), + }); + } }} - max={new Date(props.service_record.created_date)} />
@@ -198,8 +218,8 @@ export const AssetServiceEditModal = (props: { { setForm({ ...form, note: e.value }); @@ -210,7 +230,7 @@ export const AssetServiceEditModal = (props: {
diff --git a/src/Components/Assets/AssetType/HL7Monitor.tsx b/src/Components/Assets/AssetType/HL7Monitor.tsx index 55f4d0c258e..b4fefbc90b2 100644 --- a/src/Components/Assets/AssetType/HL7Monitor.tsx +++ b/src/Components/Assets/AssetType/HL7Monitor.tsx @@ -1,10 +1,5 @@ import { SyntheticEvent, useEffect, useState } from "react"; import { AssetData } from "../AssetTypes"; -import { useDispatch } from "react-redux"; -import { - partialUpdateAsset, - getPermittedFacility, -} from "../../../Redux/actions"; import * as Notification from "../../../Utils/Notifications.js"; import MonitorConfigure from "../configure/MonitorConfigure"; import Loading from "../../Common/Loading"; @@ -16,6 +11,9 @@ import TextFormField from "../../Form/FormFields/TextFormField"; import HL7PatientVitalsMonitor from "../../VitalsMonitor/HL7PatientVitalsMonitor"; import VentilatorPatientVitalsMonitor from "../../VitalsMonitor/VentilatorPatientVitalsMonitor"; import useAuthUser from "../../../Common/hooks/useAuthUser"; +import request from "../../../Utils/request/request"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; interface HL7MonitorProps { assetId: string; @@ -33,19 +31,14 @@ const HL7Monitor = (props: HL7MonitorProps) => { const [localipAddress, setLocalIPAddress] = useState(""); const [ipadrdress_error, setIpAddress_error] = useState(""); const authUser = useAuthUser(); - const dispatch = useDispatch(); - - useEffect(() => { - const fetchFacility = async () => { - const res = await dispatch(getPermittedFacility(facilityId)); - - if (res.status === 200 && res.data) { - setFacilityMiddlewareHostname(res.data.middleware_address); + const { data: facility, loading } = useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data && data.middleware_address) { + setFacilityMiddlewareHostname(data.middleware_address); } - }; - - if (facilityId) fetchFacility(); - }, [dispatch, facilityId]); + }, + }); useEffect(() => { setAssetType(asset?.asset_class); @@ -65,26 +58,28 @@ const HL7Monitor = (props: HL7MonitorProps) => { local_ip_address: localipAddress, }, }; - const res: any = await Promise.resolve( - dispatch(partialUpdateAsset(assetId, data)) - ); + const { res } = await request(routes.partialUpdateAsset, { + pathParams: { external_id: assetId }, + body: data, + }); if (res?.status === 200) { Notification.Success({ msg: "Asset Configured Successfully", }); } else { Notification.Error({ - msg: "Something went wrong..!", + msg: "Something went wrong!", }); } } else { - setIpAddress_error("Please Enter a Valid IP address !!"); + setIpAddress_error("IP address is invalid"); } }; - const middleware = middlewareHostname || facilityMiddlewareHostname; + const fallbackMiddleware = + asset?.location_object?.middleware_address || facilityMiddlewareHostname; - if (isLoading) return ; + if (isLoading || loading || !facility) return ; return (
@@ -93,11 +88,29 @@ const HL7Monitor = (props: HL7MonitorProps) => {

Connection

-
+
+

Middleware Hostname

+ {!middlewareHostname && ( +
+ + + Middleware hostname sourced from{" "} + {asset?.location_object?.middleware_address + ? "asset location" + : "asset facility"} + +
+ )} +
+ } + placeholder={fallbackMiddleware} value={middlewareHostname} onChange={(e) => setMiddlewareHostname(e.value)} errorClassName="hidden" @@ -127,12 +140,16 @@ const HL7Monitor = (props: HL7MonitorProps) => { {assetType === "HL7MONITOR" && ( )} {assetType === "VENTILATOR" && ( )}
diff --git a/src/Components/Assets/AssetType/ONVIFCamera.tsx b/src/Components/Assets/AssetType/ONVIFCamera.tsx index 4720c876010..eb6ebb9abfd 100644 --- a/src/Components/Assets/AssetType/ONVIFCamera.tsx +++ b/src/Components/Assets/AssetType/ONVIFCamera.tsx @@ -1,11 +1,5 @@ import { useEffect, useState } from "react"; import { AssetData } from "../AssetTypes"; -import { useDispatch } from "react-redux"; -import { - partialUpdateAsset, - createAssetBed, - getPermittedFacility, -} from "../../../Redux/actions"; import * as Notification from "../../../Utils/Notifications.js"; import { BedModel } from "../../Facility/models"; import axios from "axios"; @@ -18,6 +12,13 @@ import { Submit } from "../../Common/components/ButtonV2"; import { SyntheticEvent } from "react"; import useAuthUser from "../../../Common/hooks/useAuthUser"; +import request from "../../../Utils/request/request"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; + +import CareIcon from "../../../CAREUI/icons/CareIcon"; + + interface Props { assetId: string; facilityId: string; @@ -43,19 +44,15 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const [refreshPresetsHash, setRefreshPresetsHash] = useState( Number(new Date()) ); - const dispatch = useDispatch(); + const { data: facility, loading } = useQuery(routes.getPermittedFacility, { + pathParams: { id: facilityId }, + }); const authUser = useAuthUser(); useEffect(() => { - const fetchFacility = async () => { - const res = await dispatch(getPermittedFacility(facilityId)); - - if (res.status === 200 && res.data) { - setFacilityMiddlewareHostname(res.data.middleware_address); - } - }; - - if (facilityId) fetchFacility(); - }, [dispatch, facilityId]); + if (facility?.middleware_address) { + setFacilityMiddlewareHostname(facility.middleware_address); + } + }, [facility, facilityId]); useEffect(() => { if (asset) { @@ -83,18 +80,19 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { camera_access_key: `${username}:${password}:${streamUuid}`, }, }; - const res: any = await Promise.resolve( - dispatch(partialUpdateAsset(assetId, data)) - ); + const { res } = await request(routes.partialUpdateAsset, { + pathParams: { external_id: assetId }, + body: data, + }); if (res?.status === 200) { Notification.Success({ msg: "Asset Configured Successfully" }); onUpdated?.(); } else { - Notification.Error({ msg: "Something went wrong..!" }); + Notification.Error({ msg: "Something went wrong!" }); } setLoadingSetConfiguration(false); } else { - setIpAddress_error("Please Enter a Valid Camera address !!"); + setIpAddress_error("IP address is invalid"); } }; @@ -110,15 +108,14 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { const presetData = await axios.get( `https://${facilityMiddlewareHostname}/status?hostname=${config.hostname}&port=${config.port}&username=${config.username}&password=${config.password}` ); - const res: any = await Promise.resolve( - dispatch( - createAssetBed( - { meta: { ...data, ...presetData.data } }, - assetId, - bed?.id as string - ) - ) - ); + + const { res } = await request(routes.createAssetBed, { + body: { + meta: { ...data, ...presetData.data }, + asset: assetId, + bed: bed?.id as string, + }, + }); if (res?.status === 201) { Notification.Success({ msg: "Preset Added Successfully", @@ -138,8 +135,11 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => { } setLoadingAddPreset(false); }; + + if (isLoading || loading || !facility) return ; - if (isLoading) return ; + const fallbackMiddleware = + asset?.location_object?.middleware_address || facilityMiddlewareHostname; return (
@@ -148,8 +148,26 @@ const ONVIFCamera = ({ assetId, facilityId, asset, onUpdated }: Props) => {
+

Middleware Hostname

+ {!middlewareHostname && ( +
+ + + Middleware hostname sourced from{" "} + {asset?.location_object?.middleware_address + ? "asset location" + : "asset facility"} + +
+ )} +
+ } + placeholder={fallbackMiddleware} value={middlewareHostname} onChange={({ value }) => setMiddlewareHostname(value)} /> diff --git a/src/Components/Assets/AssetTypes.tsx b/src/Components/Assets/AssetTypes.tsx index 52c7e45003c..8b96b6beeb7 100644 --- a/src/Components/Assets/AssetTypes.tsx +++ b/src/Components/Assets/AssetTypes.tsx @@ -91,6 +91,8 @@ export interface AssetData { }; } +export type AssetUpdate = Partial; + export interface AssetsResponse { count: number; next?: string; @@ -137,8 +139,12 @@ export interface AssetBedModel { created_date: string; modified_date: string; meta: Record; + asset?: string; + bed?: string; } +export type AssetBedBody = Partial; + export interface AssetServiceEdit { id: string; asset_service: AssetService; @@ -166,3 +172,9 @@ export interface PatientAssetBed { patient?: PatientModel; meta?: Record; } + +export interface AssetServiceUpdate { + external_id: string; + serviced_on: string; + note: string; +} diff --git a/src/Components/Assets/AssetsList.tsx b/src/Components/Assets/AssetsList.tsx index d91468166ac..4bcb2698a9a 100644 --- a/src/Components/Assets/AssetsList.tsx +++ b/src/Components/Assets/AssetsList.tsx @@ -1,15 +1,8 @@ -import { useDispatch } from "react-redux"; import QrReader from "react-qr-reader"; -import { statusType, useAbortableEffect } from "../../Common/utils"; import * as Notification from "../../Utils/Notifications.js"; -import { - getAnyFacility, - listAssets, - getFacilityAssetLocation, - getAsset, -} from "../../Redux/actions"; +import { listAssets } from "../../Redux/actions"; import { assetClassProps, AssetData } from "./AssetTypes"; -import { useState, useCallback, useEffect, lazy } from "react"; +import { useState, useEffect, lazy } from "react"; import { Link, navigate } from "raviger"; import AssetFilter from "./AssetFilter"; import { parseQueryParams } from "../../Utils/primitives"; @@ -28,6 +21,9 @@ import AssetImportModal from "./AssetImportModal"; import Page from "../Common/components/Page"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import { useTranslation } from "react-i18next"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; const Loading = lazy(() => import("../Common/Loading")); @@ -52,60 +48,45 @@ const AssetsList = () => { const [status, setStatus] = useState(); const [facilityName, setFacilityName] = useState(); const [asset_class, setAssetClass] = useState(); - const [locationName, setLocationName] = useState(); const [importAssetModalOpen, setImportAssetModalOpen] = useState(false); - const dispatch: any = useDispatch(); const assetsExist = assets.length > 0 && Object.keys(assets[0]).length > 0; const [showFacilityDialog, setShowFacilityDialog] = useState(false); const [selectedFacility, setSelectedFacility] = useState({ name: "", }); + const params = { + limit: resultsPerPage, + page: qParams.page, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + search_text: qParams.search || "", + facility: qParams.facility || "", + asset_type: qParams.asset_type || "", + asset_class: qParams.asset_class || "", + location: qParams.facility ? qParams.location || "" : "", + status: qParams.status || "", + }; - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const params = { - limit: resultsPerPage, - page: qParams.page, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - search_text: qParams.search || "", - facility: qParams.facility || "", - asset_type: qParams.asset_type || "", - asset_class: qParams.asset_class || "", - location: qParams.facility ? qParams.location || "" : "", - status: qParams.status || "", - }; - const { data } = await dispatch(listAssets(params)); - if (!status.aborted) { - setIsLoading(false); - if (!data) - Notification.Error({ - msg: "Something went wrong..!", - }); - else { - setAssets(data.results); - setTotalCount(data.count); - if (qParams.facility) { - const fetchFacility = await dispatch( - getAnyFacility(qParams.facility) - ); - setSelectedFacility(fetchFacility.data as FacilityModel); - } - } + useQuery(routes.listAssets, { + query: params, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setAssets(data.results); + setTotalCount(data.count); } }, - [ - resultsPerPage, - qParams.page, - qParams.search, - qParams.facility, - qParams.asset_type, - qParams.asset_class, - qParams.location, - qParams.status, - dispatch, - ] - ); + }); + + useQuery(routes.getAnyFacility, { + pathParams: { id: qParams.facility }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data) { + setFacility(data); + setSelectedFacility(data); + setFacilityName(data.name); + } + }, + prefetch: !!qParams.facility, + }); useEffect(() => { setAssetType(qParams.asset_type); @@ -119,56 +100,13 @@ const AssetsList = () => { setAssetClass(qParams.asset_class); }, [qParams.asset_class]); - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [dispatch, fetchData] - ); - useEffect(() => { - async function fetchFacilityName() { - if (!qParams.facility) return setFacilityName(""); - const res = await dispatch(getAnyFacility(qParams.facility, "facility")); - setFacilityName(res?.data?.name); - } - fetchFacilityName(); - }, [dispatch, qParams.facility]); - - const fetchFacility = useCallback( - async (status: statusType) => { - if (!qParams.facility) return setFacility(undefined); - setIsLoading(true); - const res = await dispatch(getAnyFacility(qParams.facility)); - if (!status.aborted) { - setFacility(res?.data); - setIsLoading(false); - } - }, - [dispatch, qParams.facility] - ); - const fetchLocationName = useCallback( - async (status: statusType) => { - if (!qParams.location || !qParams.facility) - return setLocationName(undefined); - setIsLoading(true); - const res = await dispatch( - getFacilityAssetLocation(qParams.facility, qParams.location) - ); - if (!status.aborted) { - setLocationName(res?.data?.name); - setIsLoading(false); - } - }, - [dispatch, qParams.facility, qParams.location] - ); - - useAbortableEffect( - (status: statusType) => { - fetchFacility(status); - fetchLocationName(status); + const { data: location } = useQuery(routes.getFacilityAssetLocation, { + pathParams: { + facility_external_id: String(qParams.facility), + external_id: String(qParams.location), }, - [fetchFacility, fetchLocationName] - ); + prefetch: !!(qParams.facility && qParams.location), + }); const getAssetIdFromQR = async (assetUrl: string) => { try { @@ -178,8 +116,10 @@ const AssetsList = () => { // QR Maybe searchParams "asset" or "assetQR" const assetId = params.asset || params.assetQR; if (assetId) { - const { data } = await dispatch(listAssets({ qr_code_id: assetId })); - return data.results[0].id; + const { data } = await request(routes.listAssets, { + query: { qr_code_id: assetId }, + }); + return data?.results[0].id; } } catch (err) { console.log(err); @@ -187,11 +127,13 @@ const AssetsList = () => { }; const checkValidAssetId = async (assetId: string) => { - const assetData = await dispatch(getAsset(assetId)); + const { data: assetData } = await request(routes.getAsset, { + pathParams: { id: assetId }, + }); try { - if (assetData.data) { + if (assetData) { navigate( - `/facility/${assetData.data.location_object.facility.id}/assets/${assetId}` + `/facility/${assetData.location_object.facility.id}/assets/${assetId}` ); } } catch (err) { @@ -288,6 +230,7 @@ const AssetsList = () => { ) : ( )} + {warrantyAmcValidityChip(asset.warranty_amc_end_of_validity)}
@@ -427,7 +370,17 @@ const AssetsList = () => { value("Asset Type", "asset_type", asset_type ?? ""), value("Asset Class", "asset_class", asset_class ?? ""), value("Status", "status", status?.replace(/_/g, " ") ?? ""), - value("Location", "location", locationName ?? ""), + value("Location", "location", location?.name ?? ""), + value( + "Warranty AMC End Of Validity Before", + "warranty_amc_end_of_validity_before", + qParams.warranty_amc_end_of_validity_before ?? "" + ), + value( + "Warranty AMC End Of Validity After", + "warranty_amc_end_of_validity_after", + qParams.warranty_amc_end_of_validity_after ?? "" + ), ]} />
@@ -484,4 +437,47 @@ const AssetsList = () => { ); }; +export const warrantyAmcValidityChip = ( + warranty_amc_end_of_validity: string +) => { + if (warranty_amc_end_of_validity === "" || !warranty_amc_end_of_validity) + return; + const today = new Date(); + const warrantyAmcEndDate = new Date(warranty_amc_end_of_validity); + + const days = Math.ceil( + Math.abs(Number(warrantyAmcEndDate) - Number(today)) / (1000 * 60 * 60 * 24) + ); + + if (warrantyAmcEndDate < today) { + return ( + + ); + } else if (days <= 30) { + return ( + + ); + } else if (days <= 90) { + return ( + + ); + } +}; + export default AssetsList; diff --git a/src/Components/Assets/configure/MonitorConfigure.tsx b/src/Components/Assets/configure/MonitorConfigure.tsx index 1d469335ce2..d1b24b24959 100644 --- a/src/Components/Assets/configure/MonitorConfigure.tsx +++ b/src/Components/Assets/configure/MonitorConfigure.tsx @@ -1,62 +1,50 @@ -import { Dispatch, useEffect, useState } from "react"; +import { useState } from "react"; import { BedSelect } from "../../Common/BedSelect"; import { BedModel } from "../../Facility/models"; import { AssetData } from "../AssetTypes"; -import { - createAssetBed, - listAssetBeds, - partialUpdateAssetBed, -} from "../../../Redux/actions"; import * as Notification from "../../../Utils/Notifications.js"; -import { useDispatch } from "react-redux"; import { Submit } from "../../Common/components/ButtonV2"; import { FieldLabel } from "../../Form/FormFields/FormField"; +import request from "../../../Utils/request/request"; +import routes from "../../../Redux/api"; +import useQuery from "../../../Utils/request/useQuery"; -const saveLink = (assetId: string, bedId: string, dispatch: Dispatch) => { - dispatch(createAssetBed({}, assetId, bedId)); +const saveLink = async (assetId: string, bedId: string) => { + await request(routes.createAssetBed, { + body: { + asset: assetId, + bed: bedId, + }, + }); Notification.Success({ msg: "AssetBed Link created successfully" }); }; -const update_Link = ( +const update_Link = async ( assetbedId: string, assetId: string, - bed: BedModel, - assetBed: any, - dispatch: Dispatch + bed: BedModel ) => { - dispatch( - partialUpdateAssetBed( - { - asset: assetId, - bed: bed.id, - }, - assetbedId - ) - ); + await request(routes.partialUpdateAssetBed, { + pathParams: { external_id: assetbedId }, + body: { + asset: assetId, + bed: bed.id ?? "", + }, + }); Notification.Success({ msg: "AssetBed Link updated successfully" }); }; export default function MonitorConfigure({ asset }: { asset: AssetData }) { const [bed, setBed] = useState({}); const [updateLink, setUpdateLink] = useState(false); - const [assetBed, setAssetBed] = useState(); - const dispatch: any = useDispatch(); - - const getAssetBeds = async (id: string) => { - const assetBeds = await dispatch(listAssetBeds({ asset: id })); - if (assetBeds.data?.results?.length > 0) { - setUpdateLink(true); - setAssetBed(assetBeds.data.results[0]); - setBed(assetBeds.data.results[0].bed_object); - } else { - setUpdateLink(false); - } - }; - - useEffect(() => { - if (asset.id) { - getAssetBeds(asset.id); - } - }, [asset]); + const { data: assetBed } = useQuery(routes.listAssetBeds, { + query: { asset: asset.id }, + onResponse: ({ res, data }) => { + if (res?.status === 200 && data && data.results.length > 0) { + setBed(data.results[0].bed_object); + setUpdateLink(true); + } + }, + }); return ( diff --git a/src/Components/Auth/Login.tsx b/src/Components/Auth/Login.tsx index cb7ea7f08f8..140a0013fd9 100644 --- a/src/Components/Auth/Login.tsx +++ b/src/Components/Auth/Login.tsx @@ -1,10 +1,9 @@ import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; -import { postForgotPassword, postLogin } from "../../Redux/actions"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; import { useTranslation } from "react-i18next"; import ReCaptcha from "react-google-recaptcha"; import * as Notification from "../../Utils/Notifications.js"; -import { get } from "lodash"; import LegendInput from "../../CAREUI/interactive/LegendInput"; import LanguageSelectorLogin from "../Common/LanguageSelectorLogin"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -25,7 +24,6 @@ export const Login = (props: { forgot?: boolean }) => { custom_logo_alt, custom_description, } = useConfig(); - const dispatch: any = useDispatch(); const initForm: any = { username: "", password: "", @@ -90,37 +88,35 @@ export const Login = (props: { forgot?: boolean }) => { }; }, []); - const handleSubmit = (e: any) => { + const handleSubmit = async (e: any) => { e.preventDefault(); const valid = validateData(); if (valid) { // replaces button with spinner setLoading(true); - dispatch(postLogin(valid)).then((resp: any) => { - const res = get(resp, "data", null); - const statusCode = get(resp, "status", ""); - if (res && statusCode === 429) { - setCaptcha(true); - // captcha displayed set back to login button - setLoading(false); - } else if (res && statusCode === 200) { - localStorage.setItem(LocalStorageKeys.accessToken, res.access); - localStorage.setItem(LocalStorageKeys.refreshToken, res.refresh); - - if ( - window.location.pathname === "/" || - window.location.pathname === "/login" - ) { - window.location.href = "/facility"; - } else { - window.location.href = window.location.pathname.toString(); - } + const { res, data } = await request(routes.login, { + body: { ...valid }, + }); + if (res && res.status === 429) { + setCaptcha(true); + // captcha displayed set back to login button + setLoading(false); + } else if (res && res.status === 200 && data) { + localStorage.setItem(LocalStorageKeys.accessToken, data.access); + localStorage.setItem(LocalStorageKeys.refreshToken, data.refresh); + if ( + window.location.pathname === "/" || + window.location.pathname === "/login" + ) { + window.location.href = "/facility"; } else { - // error from server set back to login button - setLoading(false); + window.location.href = window.location.pathname.toString(); } - }); + } else { + // error from server set back to login button + setLoading(false); + } } }; @@ -146,26 +142,22 @@ export const Login = (props: { forgot?: boolean }) => { return form; }; - const handleForgetSubmit = (e: any) => { + const handleForgetSubmit = async (e: any) => { e.preventDefault(); const valid = validateForgetData(); if (valid) { setLoading(true); - dispatch(postForgotPassword(valid)).then((resp: any) => { - setLoading(false); - const res = resp && resp.data; - if (res && res.status === "OK") { - Notification.Success({ - msg: t("password_sent"), - }); - } else if (res && res.data) { - setErrors(res.data); - } else { - Notification.Error({ - msg: t("something_wrong"), - }); - } + const { res, error } = await request(routes.forgotPassword, { + body: { ...valid }, }); + setLoading(false); + if (res?.ok) { + Notification.Success({ + msg: t("password_sent"), + }); + } else if (res && error) { + setErrors(error); + } } }; diff --git a/src/Components/Auth/ResetPassword.tsx b/src/Components/Auth/ResetPassword.tsx index 08dd1da44a0..47d120e1a97 100644 --- a/src/Components/Auth/ResetPassword.tsx +++ b/src/Components/Auth/ResetPassword.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import request from "../../Utils/request/request.js"; import * as Notification from "../../Utils/Notifications.js"; -import { postResetPassword, checkResetToken } from "../../Redux/actions"; import { navigate } from "raviger"; import { useTranslation } from "react-i18next"; import { LocalStorageKeys } from "../../Common/constants"; @@ -9,9 +8,9 @@ import { Cancel, Submit } from "../Common/components/ButtonV2"; import TextFormField from "../Form/FormFields/TextFormField"; import { validateRule } from "../Users/UserAdd"; import { validatePassword } from "../../Common/validation.js"; +import routes from "../../Redux/api.js"; export const ResetPassword = (props: any) => { - const dispatch: any = useDispatch(); const initForm: any = { password: "", confirm: "", @@ -65,36 +64,37 @@ export const ResetPassword = (props: any) => { return form; }; - const handleSubmit = (e: any) => { + const handleSubmit = async (e: any) => { e.preventDefault(); const valid = validateData(); if (valid) { valid.token = props.token; - dispatch(postResetPassword(valid)).then((resp: any) => { - const res = resp && resp.data; - if (res && res.status === "OK") { - localStorage.removeItem(LocalStorageKeys.accessToken); - Notification.Success({ - msg: t("password_reset_success"), - }); - navigate("/login"); - } else if (res && res.data) { - setErrors(res.data); - } else { - Notification.Error({ - msg: t("password_reset_failure"), - }); - } + const { res, error } = await request(routes.resetPassword, { + body: { ...valid }, }); + if (res?.ok) { + localStorage.removeItem(LocalStorageKeys.accessToken); + Notification.Success({ + msg: t("password_reset_success"), + }); + navigate("/login"); + } else if (res && error) { + setErrors(error); + } } }; useEffect(() => { - if (props.token) { - dispatch(checkResetToken({ token: props.token })).then((resp: any) => { - const res = resp && resp.data; - if (!res || res.status !== "OK") navigate("/invalid-reset"); + const checkResetToken = async () => { + const { res } = await request(routes.checkResetToken, { + body: { token: props.token }, }); + if (!res || !res.ok) { + navigate("/invalid-reset"); + } + }; + if (props.token) { + checkResetToken(); } else { navigate("/invalid-reset"); } diff --git a/src/Components/Common/DateInputV2.tsx b/src/Components/Common/DateInputV2.tsx index bcebd4e0055..95652e68638 100644 --- a/src/Components/Common/DateInputV2.tsx +++ b/src/Components/Common/DateInputV2.tsx @@ -1,14 +1,4 @@ -import { MutableRefObject, useEffect, useRef, useState } from "react"; -import { - addMonths, - addYears, - format, - getDay, - getDaysInMonth, - isEqual, - subMonths, - subYears, -} from "date-fns"; +import { MutableRefObject, useEffect, useState } from "react"; import CareIcon from "../../CAREUI/icons/CareIcon"; import { Popover } from "@headlessui/react"; @@ -60,19 +50,24 @@ const DateInputV2: React.FC = ({ const [displayValue, setDisplayValue] = useState( value ? dayjs(value).format("DDMMYYYY") : "" ); - const popover = useRef(null); const decrement = () => { switch (type) { case "date": - setDatePickerHeaderDate((prev) => subMonths(prev, 1)); + setDatePickerHeaderDate((prev) => + dayjs(prev).subtract(1, "month").toDate() + ); break; case "month": - setDatePickerHeaderDate((prev) => subYears(prev, 1)); + setDatePickerHeaderDate((prev) => + dayjs(prev).subtract(1, "year").toDate() + ); break; case "year": - setDatePickerHeaderDate((prev) => subYears(prev, 1)); - setYear((prev) => subYears(prev, 10)); + setDatePickerHeaderDate((prev) => + dayjs(prev).subtract(1, "year").toDate() + ); + setYear((prev) => dayjs(prev).subtract(10, "year").toDate()); break; } }; @@ -80,24 +75,24 @@ const DateInputV2: React.FC = ({ const increment = () => { switch (type) { case "date": - setDatePickerHeaderDate((prev) => addMonths(prev, 1)); + setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "month").toDate()); break; case "month": - setDatePickerHeaderDate((prev) => addYears(prev, 1)); + setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); break; case "year": - setDatePickerHeaderDate((prev) => addYears(prev, 1)); - setYear((prev) => addYears(prev, 10)); + setDatePickerHeaderDate((prev) => dayjs(prev).add(1, "year").toDate()); + setYear((prev) => dayjs(prev).add(10, "year").toDate()); break; } }; const isSelectedDate = (date: number) => { - if (value) - return isEqual( - new Date(value.getFullYear(), value.getMonth(), date), - value - ); + if (value) { + return dayjs( + new Date(value.getFullYear(), value.getMonth(), date) + ).isSame(dayjs(value)); + } }; type CloseFunction = ( @@ -114,12 +109,15 @@ const DateInputV2: React.FC = ({ ) ); close(); + setIsOpen?.(false); }; const getDayCount = (date: Date) => { - const daysInMonth = getDaysInMonth(date); + const daysInMonth = dayjs(date).daysInMonth(); - const dayOfWeek = getDay(new Date(date.getFullYear(), date.getMonth(), 1)); + const dayOfWeek = dayjs( + new Date(date.getFullYear(), date.getMonth(), 1) + ).day(); const blankDaysArray = []; for (let i = 1; i <= dayOfWeek; i++) { blankDaysArray.push(i); @@ -213,13 +211,7 @@ const DateInputV2: React.FC = ({ {({ open, close }) => (
- { - setIsOpen?.(!isOpen); - }} - > + = ({ {(open || isOpen) && ( { - setIsOpen?.(false); - }} - ref={popover} static className={classNames( "cui-dropdown-base absolute mt-0.5 w-72 divide-y-0 p-4", @@ -252,10 +240,6 @@ const DateInputV2: React.FC = ({ { - popover.current?.focus(); - e.preventDefault(); - }} className="cui-input-base bg-gray-50" value={ displayValue.replace( @@ -296,7 +280,7 @@ const DateInputV2: React.FC = ({ onClick={showMonthPicker} className="cursor-pointer rounded px-3 py-1 text-center font-medium text-black hover:bg-gray-300" > - {format(datePickerHeaderDate, "MMMM")} + {dayjs(datePickerHeaderDate).format("MMMM")}
)}
= ({

{type == "year" ? year.getFullYear() - : format(datePickerHeaderDate, "yyyy")} + : dayjs(datePickerHeaderDate).format("YYYY")}

@@ -385,14 +369,9 @@ const DateInputV2: React.FC = ({ )} onClick={setMonthValue(i)} > - {format( - new Date( - datePickerHeaderDate.getFullYear(), - i, - 1 - ), - "MMM" - )} + {dayjs( + new Date(datePickerHeaderDate.getFullYear(), i, 1) + ).format("MMM")}
))}
diff --git a/src/Components/Common/Export.tsx b/src/Components/Common/Export.tsx index 3595b3c84a0..f991c476aac 100644 --- a/src/Components/Common/Export.tsx +++ b/src/Components/Common/Export.tsx @@ -44,7 +44,7 @@ export const ExportMenu = ({ } + icon={} className="tooltip border-primary-500 bg-white text-primary-500 hover:bg-primary-100 enabled:border" > {exportItems.map((item) => ( diff --git a/src/Components/Common/GLocationPicker.tsx b/src/Components/Common/GLocationPicker.tsx index 5356d28fa2a..fc121b8519f 100644 --- a/src/Components/Common/GLocationPicker.tsx +++ b/src/Components/Common/GLocationPicker.tsx @@ -7,14 +7,6 @@ import CareIcon from "../../CAREUI/icons/CareIcon"; import useConfig from "../../Common/hooks/useConfig"; import { Popover } from "@headlessui/react"; -const render = (status: Status) => { - if (status === "LOADING") { - return ; - } - - return

{status}

; -}; - interface GLocationPickerProps { lat: number; lng: number; @@ -67,22 +59,37 @@ const GLocationPicker = ({ setCenter(m?.getCenter()?.toJSON()); }; + const render = (status: Status) => { + switch (status) { + case Status.LOADING: + return ; + case Status.SUCCESS: + return ( + + {location && } + + ); + default: + return

{status}

; + } + }; + return (
- - - {location && } - - +
); }; @@ -149,7 +156,9 @@ const Map: React.FC = ({ places.length > 0 && places[0].geometry?.location ) { - handleOnChange(places[0].geometry.location); + const selectedLocation = places[0].geometry.location; + handleOnChange(selectedLocation); + map?.setCenter(selectedLocation); } }); } diff --git a/src/Components/Common/Uptime.tsx b/src/Components/Common/Uptime.tsx index ce60e7b7f7e..6f6966b9053 100644 --- a/src/Components/Common/Uptime.tsx +++ b/src/Components/Common/Uptime.tsx @@ -4,7 +4,7 @@ import { listAssetAvailability } from "../../Redux/actions"; import { useDispatch } from "react-redux"; import * as Notification from "../../Utils/Notifications.js"; import { AssetStatus, AssetUptimeRecord } from "../Assets/AssetTypes"; -import { reverse } from "lodash"; +import { reverse } from "lodash-es"; import { classNames } from "../../Utils/utils"; import dayjs from "../../Utils/dayjs"; diff --git a/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res b/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res index 35d137e0414..dfe0e5cc3dc 100644 --- a/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res +++ b/src/Components/CriticalCareRecording/NeurologicalMonitoring/CriticalCare__NeurologicalMonitoringEditor.res @@ -68,12 +68,12 @@ let reducer = (state, action) => { | SetConsciousnessLevel(consciousnessLevel) => { ...state, dirty: true, - consciousnessLevel: consciousnessLevel, + consciousnessLevel, } | SetConsciousnessLevelDetails(consciousnessLevelDetails) => { ...state, dirty: true, - consciousnessLevelDetails: consciousnessLevelDetails, + consciousnessLevelDetails, } | SetLeftPupilSize(leftPupilSize) => { ...state, @@ -83,17 +83,17 @@ let reducer = (state, action) => { | SetLeftPupilSizeDetails(leftPupilSizeDetails) => { ...state, dirty: true, - leftPupilSizeDetails: leftPupilSizeDetails, + leftPupilSizeDetails, } | SetLeftPupilLightReaction(leftPupilLightReaction) => { ...state, dirty: true, - leftPupilLightReaction: leftPupilLightReaction, + leftPupilLightReaction, } | SetLeftPupilLightReactionDetails(leftPupilLightReactionDetails) => { ...state, dirty: true, - leftPupilLightReactionDetails: leftPupilLightReactionDetails, + leftPupilLightReactionDetails, } | SetRightPupilSize(rightPupilSize) => { ...state, @@ -103,17 +103,17 @@ let reducer = (state, action) => { | SetRightPupilSizeDetails(rightPupilSizeDetails) => { ...state, dirty: true, - rightPupilSizeDetails: rightPupilSizeDetails, + rightPupilSizeDetails, } | SetRightPupilLightReaction(rightPupilLightReaction) => { ...state, dirty: true, - rightPupilLightReaction: rightPupilLightReaction, + rightPupilLightReaction, } | SetRightPupilLightReactionDetails(rightPupilLightReactionDetails) => { ...state, dirty: true, - rightPupilLightReactionDetails: rightPupilLightReactionDetails, + rightPupilLightReactionDetails, } | SetGlasgowEyeOpen(glasgowEyeOpen) => {...state, glasgowEyeOpen: Some(glasgowEyeOpen)} | SetGlasgowVerbalResponse(glasgowVerbalResponse) => { @@ -129,22 +129,22 @@ let reducer = (state, action) => { | SetLimbResponseUpperExtremityRight(limbResponseUpperExtremityRight) => { ...state, dirty: true, - limbResponseUpperExtremityRight: limbResponseUpperExtremityRight, + limbResponseUpperExtremityRight, } | SetLimbResponseUpperExtremityLeft(limbResponseUpperExtremityLeft) => { ...state, dirty: true, - limbResponseUpperExtremityLeft: limbResponseUpperExtremityLeft, + limbResponseUpperExtremityLeft, } | SetLimbResponseLowerExtremityRight(limbResponseLowerExtremityRight) => { ...state, dirty: true, - limbResponseLowerExtremityRight: limbResponseLowerExtremityRight, + limbResponseLowerExtremityRight, } | SetLimbResponseLowerExtremityLeft(limbResponseLowerExtremityLeft) => { ...state, dirty: true, - limbResponseLowerExtremityLeft: limbResponseLowerExtremityLeft, + limbResponseLowerExtremityLeft, } | SetSaving => {...state, saving: true} @@ -265,7 +265,7 @@ let renderConsciousnessLevel = ( ) => {
{str("Level Of Consciousness")}
-
+
{Js.Array.map(consciousnessLevel => { onInputChange(consciousnessLevel)} /> - }, [Alert, Drowsy, Stuporous, Comatose, CannotBeAssessed])->React.array} + }, [ + Unresponsive, + Alert, + RespondsToPain, + RespondsToVoice, + AgitatedOrConfused, + OnsetOfAgitationAndConfusion, + ])->React.array}
- {ReactUtils.nullUnless( - onInputDetailsChange(event)} - />, - input === NeurologicalMonitoring.CannotBeAssessed, - )}
} diff --git a/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res b/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res index f476fd36c46..7dd295ecb51 100644 --- a/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res +++ b/src/Components/CriticalCareRecording/NeurologicalMonitoring/DailyRound__NeurologicalMonitoring.res @@ -16,10 +16,6 @@ let make = ( NeurologicalMonitoring.consciousnessLevel(neurologicalMonitoring), ), )} - {renderOptionalDescription( - "Consciousness Level Reaction Description", - NeurologicalMonitoring.consciousnessLevelDetails(neurologicalMonitoring), - )}
{title("Left Pupil")} diff --git a/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res b/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res index f42ca5c140d..0fae710ce36 100644 --- a/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res +++ b/src/Components/CriticalCareRecording/types/CriticalCare__NeurologicalMonitoring.res @@ -1,4 +1,11 @@ -type consciousnessLevel = Alert | Drowsy | Stuporous | Comatose | CannotBeAssessed | Unknown +type consciousnessLevel = + | Unresponsive + | RespondsToPain + | RespondsToVoice + | Alert + | AgitatedOrConfused + | OnsetOfAgitationAndConfusion + | Unknown type lightReaction = Brisk | Sluggish | Fixed | CannotBeAssessed | Unknown type limpResponse = Strong | Moderate | Weak | Flexion | Extension | NONE_ | Unknown @@ -68,11 +75,12 @@ let make = ( let makeConsciousnessLevel = consciousnessLevel => { switch consciousnessLevel { + | "UNRESPONSIVE" => Unresponsive + | "RESPONDS_TO_PAIN" => RespondsToPain + | "RESPONDS_TO_VOICE" => RespondsToVoice | "ALERT" => Alert - | "DROWSY" => Drowsy - | "STUPOROUS" => Stuporous - | "COMATOSE" => Comatose - | "CANNOT_BE_ASSESSED" => CannotBeAssessed + | "AGITATED_OR_CONFUSED" => AgitatedOrConfused + | "ONSET_OF_AGITATION_AND_CONFUSION" => OnsetOfAgitationAndConfusion | "UNKNOWN" | _ => Unknown @@ -81,11 +89,12 @@ let makeConsciousnessLevel = consciousnessLevel => { let encodeConConsciousnessLevel = consciousnessLevel => { switch consciousnessLevel { + | Unresponsive => "UNRESPONSIVE" + | RespondsToPain => "RESPONDS_TO_PAIN" + | RespondsToVoice => "RESPONDS_TO_VOICE" | Alert => "ALERT" - | Drowsy => "DROWSY" - | Stuporous => "STUPOROUS" - | Comatose => "COMATOSE" - | CannotBeAssessed => "CANNOT_BE_ASSESSED" + | AgitatedOrConfused => "AGITATED_OR_CONFUSED" + | OnsetOfAgitationAndConfusion => "ONSET_OF_AGITATION_AND_CONFUSION" | Unknown => "UNKNOWN" } } @@ -150,11 +159,12 @@ let lightReactionToString = lightReaction => { let consciousnessLevelToString = consciousnessLevel => { switch consciousnessLevel { + | Unresponsive => "Unresponsive" + | RespondsToPain => "Responds to Pain" + | RespondsToVoice => "Responds to Voice" | Alert => "Alert" - | Drowsy => "Drowsy" - | Stuporous => "Stuporous" - | Comatose => "Comatose" - | CannotBeAssessed => "Cannot be assessed" + | AgitatedOrConfused => "Agitated or Confused" + | OnsetOfAgitationAndConfusion => "Onset of Agitation and Confusion" | Unknown => "Unknown" } } diff --git a/src/Components/ExternalResult/ExternalResultUpload.tsx b/src/Components/ExternalResult/ExternalResultUpload.tsx index 5f39cb213a4..a3b6dd2fbf9 100644 --- a/src/Components/ExternalResult/ExternalResultUpload.tsx +++ b/src/Components/ExternalResult/ExternalResultUpload.tsx @@ -1,22 +1,21 @@ -import _ from "lodash"; import { navigate } from "raviger"; import { lazy, useState } from "react"; import CSVReader from "react-csv-reader"; -import { useDispatch } from "react-redux"; import useConfig from "../../Common/hooks/useConfig"; -import { externalResultUploadCsv } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications.js"; const PageTitle = lazy(() => import("../Common/PageTitle")); import { useTranslation } from "react-i18next"; import { Cancel, Submit } from "../Common/components/ButtonV2"; import useAppHistory from "../../Common/hooks/useAppHistory"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import { IExternalResult } from "./models"; export default function ExternalResultUpload() { const { sample_format_external_result_import } = useConfig(); - const dispatch: any = useDispatch(); // for disabling save button once clicked const [loading, setLoading] = useState(false); - const [csvData, setCsvData] = useState(new Array()); + const [csvData, setCsvData] = useState(new Array()); const [errors, setErrors] = useState([]); const handleForce = (data: any) => { setCsvData(data); @@ -32,26 +31,35 @@ export default function ExternalResultUpload() { header.toLowerCase().replace(/\W/g, "_"), }; - const handleSubmit = (e: any) => { + const handleSubmit = async (e: any) => { e.preventDefault(); setLoading(true); const valid = true; - if (csvData.length !== 0) { - const data = { - sample_tests: csvData, - }; + if (csvData.length !== 0) { if (valid) { setErrors([]); - dispatch(externalResultUploadCsv(data)).then((resp: any) => { - if (resp && resp.status === 202) { + + try { + const { res, data } = await request(routes.externalResultUploadCsv, { + body: { + sample_tests: csvData, + }, + }); + + if (res && res.status === 202) { setLoading(false); navigate("/external_results"); } else { - setErrors(resp.data.map((err: any) => Object.entries(err))); + if (data) { + setErrors(data.map((err: any) => Object.entries(err))); + } setLoading(false); } - }); + } catch (error) { + console.error("An error occurred:", error); + setLoading(false); + } } else { setLoading(false); } @@ -117,13 +125,13 @@ export default function ExternalResultUpload() {
{index + 1}
{data.name}
-
+
{errors && errors.length !== 0 ? errors.map((error: any) => { return (
- {_.startCase(_.camelCase(error[0][0]))} -{" "} - {error[0][1]} + {error[0][0].toLowerCase()} -{" "} + {error[0][1].toLowerCase()}
); }) diff --git a/src/Components/ExternalResult/ListFilter.tsx b/src/Components/ExternalResult/ListFilter.tsx index 91b75e535d2..75358196ffd 100644 --- a/src/Components/ExternalResult/ListFilter.tsx +++ b/src/Components/ExternalResult/ListFilter.tsx @@ -1,6 +1,4 @@ -import { useEffect, useState } from "react"; -import { getAllLocalBodyByDistrict } from "../../Redux/actions"; -import { useDispatch } from "react-redux"; +import { useState } from "react"; import useMergeState from "../../Common/hooks/useMergeState"; import { navigate } from "raviger"; import { useTranslation } from "react-i18next"; @@ -11,6 +9,9 @@ import DateRangeFormField from "../Form/FormFields/DateRangeFormField"; import dayjs from "dayjs"; import { dateQueryString } from "../../Utils/utils"; import useAuthUser from "../../Common/hooks/useAuthUser"; +import useQuery from "../../Utils/request/useQuery"; +import routes from "../../Redux/api"; +import Loading from "../Common/Loading"; const clearFilterState = { created_date_before: "", @@ -29,10 +30,8 @@ export default function ListFilter(props: any) { const { filter, onChange, closeFilter, dataList } = props; const [wardList, setWardList] = useState([]); const [lsgList, setLsgList] = useState([]); - const [wards, setWards] = useState([]); const [selectedLsgs, setSelectedLsgs] = useState([]); - const dispatch: any = useDispatch(); const authUser = useAuthUser(); const [filterState, setFilterState] = useMergeState({ created_date_before: filter.created_date_before || null, @@ -45,6 +44,57 @@ export default function ListFilter(props: any) { }); const { t } = useTranslation(); + const { loading } = useQuery(routes.getAllLocalBodyByDistrict, { + pathParams: { id: String(authUser.district) }, + onResponse: ({ res, data }) => { + if (res && data) { + let allWards: any[] = []; + let allLsgs: any[] = []; + if (res && data) { + data.forEach((local: any) => { + allLsgs = [...allLsgs, { id: local.id, name: local.name }]; + if (local.wards) { + local.wards.forEach((ward: any) => { + allWards = [ + ...allWards, + { + id: ward.id, + name: ward.number + ": " + ward.name, + panchayath: local.name, + number: ward.number, + local_body_id: local.id, + }, + ]; + }); + } + }); + } + + sortByName(allWards); + sortByName(allLsgs); + setWardList(allWards || []); + setLsgList(allLsgs || []); + const filteredWard = filter?.wards?.split(",").map(Number); + const selectedWards: any = + filteredWard && allWards + ? allWards.filter(({ id }: { id: number }) => { + return filteredWard.includes(id); + }) + : []; + setWards(selectedWards); + + const filteredLsgs = filter?.local_bodies?.split(",").map(Number); + const selectedLsgs: any = + filteredLsgs && allLsgs + ? allLsgs.filter(({ id }: { id: number }) => { + return filteredLsgs.includes(id); + }) + : []; + setSelectedLsgs(selectedLsgs); + } + }, + }); + const handleDateRangeChange = ( startDateId: string, endDateId: string, @@ -118,59 +168,10 @@ export default function ListFilter(props: any) { }); }; - useEffect(() => { - async function getWardList() { - const id = authUser.district; - const res = await dispatch(getAllLocalBodyByDistrict({ id })); - let allWards: any[] = []; - let allLsgs: any[] = []; - res?.data?.forEach((local: any) => { - allLsgs = [...allLsgs, { id: local.id, name: local.name }]; - if (local.wards) { - local.wards.forEach((ward: any) => { - allWards = [ - ...allWards, - { - id: ward.id, - name: ward.number + ": " + ward.name, - panchayath: local.name, - number: ward.number, - local_body_id: local.id, - }, - ]; - }); - } - }); - sortByName(allWards); - sortByName(allLsgs); - setWardList(allWards || []); - setLsgList(allLsgs || []); - const filteredWard = filter?.wards?.split(",").map(Number); - const selectedWards: any = - filteredWard && allWards - ? allWards.filter(({ id }: { id: number }) => { - return filteredWard.includes(id); - }) - : []; - setWards(selectedWards); - - const filteredLsgs = filter?.local_bodies?.split(",").map(Number); - const selectedLsgs: any = - filteredLsgs && allLsgs - ? allLsgs.filter(({ id }: { id: number }) => { - return filteredLsgs.includes(id); - }) - : []; - setSelectedLsgs(selectedLsgs); - } - getWardList(); - }, []); - const filterWards = () => { const selectedLsgIds: any = selectedLsgs.map((e) => { return e.id; }); - const selectedwards: any = selectedLsgIds.length === 0 ? wardList @@ -183,13 +184,15 @@ export default function ListFilter(props: any) { const handleChange = (event: any) => { const { name, value } = event.target; - const filterData: any = { ...filterState }; filterData[name] = value; - setFilterState(filterData); }; + if (loading) { + ; + } + return ( import("../Common/Loading")); export default function ResultItem(props: any) { - const dispatch: any = useDispatch(); - const initialData: any = {}; - const [data, setData] = useState(initialData); - const [isLoading, setIsLoading] = useState(true); const [showDeleteAlert, setShowDeleteAlert] = useState(false); const { t } = useTranslation(); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatch(externalResult({ id: props.id })); - if (!status.aborted) { - if (res && res.data) { - setData(res.data); - } - setIsLoading(false); - } - }, - [props.id, dispatch] - ); + const { data: resultItemData, loading } = useQuery(routes.externalResult, { + pathParams: { id: props.id }, + }); const handleDelete = async () => { - const res = await dispatch(deleteExternalResult(props.id)); - if (res?.status === 204) { - Notification.Success({ - msg: t("record_has_been_deleted_successfully"), + if (showDeleteAlert) { + const { res, data } = await request(routes.deleteExternalResult, { + pathParams: { id: props.id }, }); - } else { - Notification.Error({ - msg: - t("error_while_deleting_record") + ": " + (res?.data?.detail || ""), - }); - } - setShowDeleteAlert(false); - navigate("/external_results"); + if (res?.status === 204) { + Notification.Success({ + msg: t("record_has_been_deleted_successfully"), + }); + } else { + Notification.Error({ + msg: t("error_while_deleting_record") + ": " + (data?.detail || ""), + }); + } + setShowDeleteAlert(false); + navigate("/external_results"); + } }; - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] - ); - - if (isLoading) { + if (loading || !resultItemData) { return ; } @@ -69,14 +50,18 @@ export default function ResultItem(props: any) { variant="danger" action={t("delete")} show={showDeleteAlert} - onConfirm={() => handleDelete()} + onConfirm={() => { + handleDelete(); + }} onClose={() => setShowDeleteAlert(false)} />
diff --git a/src/Components/ExternalResult/ResultList.tsx b/src/Components/ExternalResult/ResultList.tsx index 74fbf8430b0..d148b26e3f5 100644 --- a/src/Components/ExternalResult/ResultList.tsx +++ b/src/Components/ExternalResult/ResultList.tsx @@ -1,12 +1,10 @@ import ButtonV2 from "../Common/components/ButtonV2"; import { navigate } from "raviger"; -import { lazy, useEffect, useState } from "react"; -import { useDispatch } from "react-redux"; +import { lazy, useState } from "react"; import { externalResultList } from "../../Redux/actions"; import ListFilter from "./ListFilter"; import FacilitiesSelectDialogue from "./FacilitiesSelectDialogue"; import { FacilityModel } from "../Facility/models"; -import { parsePhoneNumber } from "../../Utils/utils"; import SearchInput from "../Form/SearchInput"; import useFilters from "../../Common/hooks/useFilters"; import CareIcon from "../../CAREUI/icons/CareIcon"; @@ -15,14 +13,12 @@ import PhoneNumberFormField from "../Form/FormFields/PhoneNumberFormField"; import CountBlock from "../../CAREUI/display/Count"; import { AdvancedFilterButton } from "../../CAREUI/interactive/FiltersSlideover"; import Page from "../Common/components/Page"; - +import routes from "../../Redux/api"; +import useQuery from "../../Utils/request/useQuery"; +import { parsePhoneNumber } from "../../Utils/utils"; const Loading = lazy(() => import("../Common/Loading")); export default function ResultList() { - const dispatch: any = useDispatch(); - const [data, setData] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [totalCount, setTotalCount] = useState(0); const { qParams, updateQuery, @@ -57,61 +53,31 @@ export default function ResultList() { setPhoneNumberError("Enter a valid number"); }; + const params = { + page: qParams.page || 1, + name: qParams.name || "", + mobile_number: qParams.mobile_number + ? parsePhoneNumber(qParams.mobile_number) ?? "" + : "", + wards: qParams.wards || undefined, + local_bodies: qParams.local_bodies || undefined, + created_date_before: qParams.created_date_before || undefined, + created_date_after: qParams.created_date_after || undefined, + result_date_before: qParams.result_date_before || undefined, + result_date_after: qParams.result_date_after || undefined, + sample_collection_date_after: + qParams.sample_collection_date_after || undefined, + sample_collection_date_before: + qParams.sample_collection_date_before || undefined, + offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, + srf_id: qParams.srf_id || undefined, + }; - let manageResults: any = null; - useEffect(() => { - setIsLoading(true); - const params = { - page: qParams.page || 1, - name: qParams.name || "", - mobile_number: qParams.mobile_number - ? parsePhoneNumber(qParams.mobile_number) ?? "" - : "", - wards: qParams.wards || undefined, - local_bodies: qParams.local_bodies || undefined, - created_date_before: qParams.created_date_before || undefined, - created_date_after: qParams.created_date_after || undefined, - result_date_before: qParams.result_date_before || undefined, - result_date_after: qParams.result_date_after || undefined, - sample_collection_date_after: - qParams.sample_collection_date_after || undefined, - sample_collection_date_before: - qParams.sample_collection_date_before || undefined, - offset: (qParams.page ? qParams.page - 1 : 0) * resultsPerPage, - srf_id: qParams.srf_id || undefined, - }; - - dispatch(externalResultList(params, "externalResultList")) - .then((res: any) => { - if (res && res.data) { - setData(res.data.results); - setTotalCount(res.data.count); - setIsLoading(false); - } - }) - .catch(() => { - setIsLoading(false); - }); + const { data, loading } = useQuery(routes.externalResultList, { + query: params, + }); - if (!params.mobile_number) { - setPhoneNum("+91"); - } - }, [ - dispatch, - qParams.name, - qParams.page, - qParams.mobile_number, - qParams.wards, - qParams.created_date_before, - qParams.created_date_after, - qParams.result_date_before, - qParams.result_date_after, - qParams.sample_collection_date_after, - qParams.sample_collection_date_before, - qParams.local_bodies, - qParams.srf_id, - dataList, - ]); + let manageResults: any = null; const removeLSGFilter = (paramKey: any, id: any) => { const updatedLsgList = dataList.lsgList.filter((x: any) => x.id !== id); @@ -158,8 +124,8 @@ export default function ResultList() { }; let resultList: any[] = []; - if (data && data.length) { - resultList = data.map((result: any) => { + if (data?.results.length) { + resultList = data.results.map((result: any) => { const resultUrl = `/external_results/${result.id}`; return (
@@ -173,7 +139,7 @@ export default function ResultList() { className="group inline-flex space-x-2 text-sm leading-5" >

- {result.name} - {result.age} {result.age_in} + {`${result.name}`} - {result.age} {result.age_in}

@@ -214,7 +180,7 @@ export default function ResultList() { }); } - if (isLoading || !data) { + if (loading) { manageResults = ( ); - } else if (data && data.length) { + } else if (data?.results.length) { manageResults = <>{resultList}; - } else if (data && data.length === 0) { + } else if (data?.results.length === 0) { manageResults = (
+ Serviced on @@ -537,7 +520,7 @@ const AssetManage = (props: AssetManageProps) => { Last Updated + Edit
Moved from + Moved to + Moved By + Moved On
@@ -222,9 +188,9 @@ export default function ResultList() {
@@ -286,8 +252,8 @@ export default function ResultList() {
@@ -358,7 +324,7 @@ export default function ResultList() {
- + import("../Common/Loading")); @@ -63,79 +57,70 @@ export default function UpdateResult(props: any) { const { id } = props; const { goBack } = useAppHistory(); - const dispatchAction: any = useDispatch(); const [state, dispatch] = useReducer(FormReducer, initialState); - const [isLoading, setIsLoading] = useState(false); + const [isLoading, setIsLoading] = useState(true); const [isLocalbodyLoading, setIsLocalbodyLoading] = useState(false); const [isWardLoading, setIsWardLoading] = useState(false); const [localBody, setLocalBody] = useState(initialLocalbodies); const [ward, setWard] = useState(initialLocalbodies); - const fetchData = useCallback( - async (status: statusType) => { - setIsLoading(true); - const res = await dispatchAction(externalResult({ id: id })); - if (!status.aborted) { - if (res && res.data) { - const form = { ...state.form }; - form["name"] = res.data.name; - form["age"] = res.data.age; - form["age_in"] = res.data.age_in; - form["srf_id"] = res.data.srf_id; - form["address"] = res.data.address; - form["district"] = res.data.district_object.name; - form["local_body"] = String(res.data.local_body); - form["ward"] = String(res.data.ward); - form["patient_created"] = String(res.data.patient_created); + const { loading } = useQuery(routes.externalResult, { + pathParams: { id }, + onResponse: async ({ res, data }) => { + if (res && data) { + const form = { ...state.form }; + form["name"] = data.name; + form["age"] = data.age; + form["age_in"] = data.age_in; + form["srf_id"] = data.srf_id; + form["address"] = data.address; + form["district"] = data.district_object.name; + form["local_body"] = String(data.local_body); + form["ward"] = String(data.ward); + form["patient_created"] = String(data.patient_created); - dispatch({ type: "set_form", form }); + dispatch({ type: "set_form", form }); - Promise.all([ - fetchLocalBody(res.data.district), - fetchWards(res.data.local_body), - ]); - } + Promise.all([ + fetchLocalBody(data.district), + fetchWards(data.local_body), + ]); setIsLoading(false); } }, - [props.id, dispatchAction] - ); + }); - const fetchLocalBody = useCallback( - async (id: string) => { - if (Number(id) > 0) { - setIsLocalbodyLoading(true); - const localBodyList = await dispatchAction( - getLocalbodyByDistrict({ id }) - ); + const fetchLocalBody = async (id: number) => { + if (Number(id) > 0) { + setIsLocalbodyLoading(true); + const { res, data } = await request(routes.getLocalbodyByDistrict, { + pathParams: { id: String(id) }, + }); + if (res && data) { setIsLocalbodyLoading(false); - setLocalBody([...initialLocalbodies, ...localBodyList.data]); - } else { - setLocalBody(initialLocalbodies); + setLocalBody([...initialLocalbodies, ...data]); } - }, - [dispatchAction] - ); + } else { + setLocalBody(initialLocalbodies); + } + }; const fetchWards = useCallback( - async (id: string) => { + async (id: number) => { if (Number(id) > 0) { setIsWardLoading(true); - const wardList = await dispatchAction(getWardByLocalBody({ id })); + const { res, data } = await request(routes.getWardByLocalBody, { + pathParams: { id: String(id) }, + }); + if (res && data) { + setWard([...initialWard, ...data.results]); + } setIsWardLoading(false); - setWard([...initialWard, ...wardList.data.results]); } else { setWard(initialLocalbodies); } }, - [dispatchAction] - ); - - useAbortableEffect( - (status: statusType) => { - fetchData(status); - }, - [fetchData] + [props.id] ); const validateForm = () => { @@ -195,15 +180,20 @@ export default function UpdateResult(props: any) { const validForm = validateForm(); if (validForm) { setIsLoading(true); - const data = { + const rdata = { address: state.form.address ? state.form.address : undefined, local_body: state.form.local_body ? state.form.local_body : undefined, ward: state.form.ward, patient_created: state.form.patient_created === "true", }; - const res = await dispatchAction(partialUpdateExternalResult(id, data)); + + const { res, data } = await request(routes.partialUpdateExternalResult, { + pathParams: { id }, + body: rdata, + }); + setIsLoading(false); - if (res && res.data) { + if (res && data) { dispatch({ type: "set_form", form: initForm }); Notification.Success({ msg: "External Result updated successfully", @@ -213,7 +203,7 @@ export default function UpdateResult(props: any) { } }; - if (isLoading) { + if (isLoading || loading) { return ; } @@ -262,10 +252,7 @@ export default function UpdateResult(props: any) { options={localBody} optionLabel={(localBody) => localBody.name} optionValue={(localBody) => localBody.id} - onChange={(e) => [ - handleChange(e), - fetchWards(String(e.value)), - ]} + onChange={(e) => [handleChange(e), fetchWards(e.value)]} error={state.errors.local_body} /> )} diff --git a/src/Components/ExternalResult/models.ts b/src/Components/ExternalResult/models.ts new file mode 100644 index 00000000000..8ccaba04d05 --- /dev/null +++ b/src/Components/ExternalResult/models.ts @@ -0,0 +1,72 @@ +export interface IExternalResultUploadCsv { + sample_tests: any[]; +} + +export interface IExternalResult { + id: number; + name: string; + age: number; + age_in: string; + test_type: string; + result: string; + result_date: string; + patient_created: boolean; + gender: string; + source: string; + is_repeat: boolean; + mobile_number: string; + patient_status: string; + sample_type: string; + sample_collection_date: string; + patient_category: string; + srf_id: string; + district_object: { + id: number; + name: string; + state: number; + }; + district: number; + ward: number; + local_body: number; + address: string; + ward_object: { + id: number; + number: number; + name: string; + }; + local_body_object: { + id: number; + name: string; + }; +} + +export interface ILocalBodies { + id: number; + name: string; + state: number; + number: number; + body_type: number; + localbody_code: string; + district: number; +} + +export interface IDeleteExternalResult { + detail: string; +} + +export interface IPartialUpdateExternalResult { + address: string; + ward: number; + local_body: number; + patient_created: boolean; +} + +export interface ILocalBodyByDistrict { + id: number; + name: string; + state: number; +} + +export interface IExternalResultCsv { + sample_tests: Partial[]; +} diff --git a/src/Components/Facility/AddLocationForm.tsx b/src/Components/Facility/AddLocationForm.tsx index 92fed759647..445c9849b2d 100644 --- a/src/Components/Facility/AddLocationForm.tsx +++ b/src/Components/Facility/AddLocationForm.tsx @@ -28,6 +28,7 @@ export const AddLocationForm = (props: LocationFormProps) => { const dispatchAction: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [name, setName] = useState(""); + const [middlewareAddress, setMiddlewareAddress] = useState(""); const [description, setDescription] = useState(""); const [facilityName, setFacilityName] = useState(""); const [locationName, setLocationName] = useState(""); @@ -41,6 +42,7 @@ export const AddLocationForm = (props: LocationFormProps) => { }>({ name: "", description: "", + middlewareAddress: "", }); const headerText = !locationId ? "Add Location" : "Update Location"; const buttonText = !locationId ? "Add Location" : "Update Location"; @@ -83,6 +85,7 @@ export const AddLocationForm = (props: LocationFormProps) => { .filter((s: UserAssignedModel) => s.user_type === "Staff") .map((s: UserAssignedModel) => s.id) || [] ); + setMiddlewareAddress(res?.data?.middleware_address || ""); } setIsLoading(false); } @@ -94,6 +97,7 @@ export const AddLocationForm = (props: LocationFormProps) => { const error = { name: "", description: "", + middlewareAddress: "", }; if (name.trim().length === 0) { @@ -101,6 +105,16 @@ export const AddLocationForm = (props: LocationFormProps) => { formValid = false; } + if ( + middlewareAddress && + middlewareAddress.match( + /^(?!https?:\/\/)[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)*\.[a-zA-Z]{2,}$/ + ) === null + ) { + error.middlewareAddress = "Invalid Middleware Address"; + formValid = false; + } + setErrors(error); return formValid; }; @@ -116,6 +130,7 @@ export const AddLocationForm = (props: LocationFormProps) => { name, description, duty_staff: [...doctors, ...staff], + middleware_address: middlewareAddress, }; const res = await dispatchAction( @@ -226,6 +241,14 @@ export const AddLocationForm = (props: LocationFormProps) => { `${option.first_name} ${option.last_name}` } optionValue={(option: any) => option.id} +
+ setMiddlewareAddress(e.value)} + error={errors.middlewareAddress} />
diff --git a/src/Components/Facility/AssetCreate.tsx b/src/Components/Facility/AssetCreate.tsx index 156d738857a..84fc09188d0 100644 --- a/src/Components/Facility/AssetCreate.tsx +++ b/src/Components/Facility/AssetCreate.tsx @@ -37,7 +37,8 @@ import useVisibility from "../../Utils/useVisibility"; import { validateEmailAddress } from "../../Common/validation"; import { dateQueryString, parsePhoneNumber } from "../../Utils/utils.js"; import dayjs from "../../Utils/dayjs"; -import DateInputV2 from "../Common/DateInputV2.js"; +import DateFormField from "../Form/FormFields/DateFormField.js"; +import { t } from "i18next"; const Loading = lazy(() => import("../Common/Loading")); @@ -305,7 +306,7 @@ const AssetCreate = (props: AssetProps) => { setLocation(""); setAssetType(assetTypeInitial); setAssetClass(assetClassInitial); - setIsWorking(""); + setIsWorking(undefined); setNotWorkingReason(""); setSerialNumber(""); setVendorName(""); @@ -404,7 +405,7 @@ const AssetCreate = (props: AssetProps) => { if (locations.length === 0) { return ( {

- You need at least a location to create an assest. + {t("you_need_at_least_a_location_to_create_an_assest")}

@@ -440,7 +441,8 @@ const AssetCreate = (props: AssetProps) => { onClick={() => setIsScannerActive(false)} className="btn btn-default mb-2" > - Close Scanner + + {t("close_scanner")} { } style={{ width: "100%" }} /> -

Scan Asset QR!

+

+ {t("scan_asset_qr")} +

); @@ -479,7 +483,7 @@ const AssetCreate = (props: AssetProps) => { return (
{ > setName(value)} @@ -544,7 +548,7 @@ const AssetCreate = (props: AssetProps) => { {/* Location */} - Asset Location + {t("asset_location")}
{ data-testid="asset-type-input" > { { > setDescription(value)} error={state.errors.description} @@ -664,7 +668,7 @@ const AssetCreate = (props: AssetProps) => { className="col-span-6" required name="is_working" - label="Working Status" + label={t("working_status")} options={["true", "false"]} optionLabel={(option) => { return ( @@ -692,8 +696,8 @@ const AssetCreate = (props: AssetProps) => { > setNotWorkingReason(e.value)} error={state.errors.not_working_reason} @@ -717,7 +721,7 @@ const AssetCreate = (props: AssetProps) => { id="qr_code_id" name="qr_code_id" placeholder="" - label="Asset QR ID" + label={t("asset_qr_id")} value={qrCodeId} onChange={(e) => setQrCodeId(e.value)} error={state.errors.qr_code_id} @@ -743,9 +747,9 @@ const AssetCreate = (props: AssetProps) => { setManufacturer(e.value)} error={state.errors.manufacturer} /> @@ -760,7 +764,7 @@ const AssetCreate = (props: AssetProps) => { { const value = dayjs(event.value); @@ -788,8 +792,8 @@ const AssetCreate = (props: AssetProps) => { setSupportName(e.value)} error={state.errors.support_name} @@ -804,7 +808,7 @@ const AssetCreate = (props: AssetProps) => { > setSupportPhone(e.value)} @@ -822,8 +826,8 @@ const AssetCreate = (props: AssetProps) => { setSupportEmail(e.value)} error={state.errors.support_email} @@ -841,9 +845,9 @@ const AssetCreate = (props: AssetProps) => { setVendorName(e.value)} error={state.errors.vendor_name} /> @@ -858,7 +862,7 @@ const AssetCreate = (props: AssetProps) => { setSerialNumber(e.value)} error={state.errors.serial_number} @@ -874,25 +878,26 @@ const AssetCreate = (props: AssetProps) => { ref={fieldRef["last_serviced_on"]} data-testid="asset-last-serviced-on-input" > - Last Serviced On - { if ( - dayjs(date).format("YYYY-MM-DD") > + dayjs(date.value).format("YYYY-MM-DD") > new Date().toLocaleDateString("en-ca") ) { Notification.Error({ msg: "Last Serviced date can't be in future", }); } else { - setLastServicedOn(dayjs(date).format("YYYY-MM-DD")); + setLastServicedOn( + dayjs(date.value).format("YYYY-MM-DD") + ); } }} - max={new Date()} /> { > setNotes(e.value)} error={state.errors.notes} @@ -928,13 +935,13 @@ const AssetCreate = (props: AssetProps) => { /> handleSubmit(e, false)} - label={assetId ? "Update" : "Create Asset"} + label={assetId ? t("update") : t("create_asset")} /> {!assetId && ( handleSubmit(e, true)} - label="Create & Add More" + label={t("create_add_more")} /> )}
diff --git a/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx b/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx index 19810102833..0643765339f 100644 --- a/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx +++ b/src/Components/Facility/ConsultationDetails/ConsultationMedicinesTab.tsx @@ -1,9 +1,12 @@ import { ConsultationTabProps } from "./index"; import PrescriptionAdministrationsTable from "../../Medicine/PrescriptionAdministrationsTable"; +import PageTitle from "../../Common/PageHeadTitle"; export const ConsultationMedicinesTab = (props: ConsultationTabProps) => { return (
+ {/* eslint-disable-next-line i18next/no-literal-string */} + import("../../Common/PageTitle")); -export const ConsultationNeutritionTab = (props: ConsultationTabProps) => { +export const ConsultationNutritionTab = (props: ConsultationTabProps) => { return (
diff --git a/src/Components/Facility/ConsultationDetails/index.tsx b/src/Components/Facility/ConsultationDetails/index.tsx index 403f1752704..3d3271b16d5 100644 --- a/src/Components/Facility/ConsultationDetails/index.tsx +++ b/src/Components/Facility/ConsultationDetails/index.tsx @@ -41,6 +41,7 @@ import { ConsultationVentilatorTab } from "./ConsultationVentilatorTab"; import { ConsultationPressureSoreTab } from "./ConsultationPressureSoreTab"; import { ConsultationDialysisTab } from "./ConsultationDialysisTab"; import { ConsultationNeurologicalMonitoringTab } from "./ConsultationNeurologicalMonitoringTab"; +import { ConsultationNutritionTab } from "./ConsultationNutritionTab"; const Loading = lazy(() => import("../../Common/Loading")); const PageTitle = lazy(() => import("../../Common/PageTitle")); @@ -65,7 +66,7 @@ const TABS = { NURSING: ConsultationNursingTab, NEUROLOGICAL_MONITORING: ConsultationNeurologicalMonitoringTab, VENTILATOR: ConsultationVentilatorTab, - NUTRITION: ConsultationNursingTab, + NUTRITION: ConsultationNutritionTab, PRESSURE_SORE: ConsultationPressureSoreTab, DIALYSIS: ConsultationDialysisTab, }; diff --git a/src/Components/Facility/Consultations/Feed.tsx b/src/Components/Facility/Consultations/Feed.tsx index 75ab72d168c..31691c736f4 100644 --- a/src/Components/Facility/Consultations/Feed.tsx +++ b/src/Components/Facility/Consultations/Feed.tsx @@ -32,6 +32,7 @@ import useKeyboardShortcut from "use-keyboard-shortcut"; import useFullscreen from "../../../Common/hooks/useFullscreen.js"; import { triggerGoal } from "../../../Integrations/Plausible.js"; import useAuthUser from "../../../Common/hooks/useAuthUser.js"; +import Spinner from "../../Common/Spinner.js"; interface IFeedProps { facilityId: string; @@ -57,6 +58,7 @@ export const Feed: React.FC = ({ consultationId, facilityId }) => { const [cameraState, setCameraState] = useState(null); const [isFullscreen, setFullscreen] = useFullscreen(); const [videoStartTime, setVideoStartTime] = useState(null); + const [statusReported, setStatusReported] = useState(false); const authUser = useAuthUser(); useEffect(() => { @@ -232,13 +234,32 @@ export const Feed: React.FC = ({ consultationId, facilityId }) => { useEffect(() => { let tId: any; if (streamStatus !== StreamStatus.Playing) { - setStreamStatus(StreamStatus.Loading); + if (streamStatus !== StreamStatus.Offline) { + setStreamStatus(StreamStatus.Loading); + } tId = setTimeout(() => { startStream({ onSuccess: () => setStreamStatus(StreamStatus.Playing), - onError: () => setStreamStatus(StreamStatus.Offline), + onError: () => { + setStreamStatus(StreamStatus.Offline); + if (!statusReported) { + triggerGoal("Camera Feed Viewed", { + consultationId, + userId: authUser.id, + result: "error", + }); + setStatusReported(true); + } + }, }); }, 100); + } else if (!statusReported) { + triggerGoal("Camera Feed Viewed", { + consultationId, + userId: authUser.id, + result: "success", + }); + setStatusReported(true); } return () => { @@ -505,8 +526,9 @@ export const Feed: React.FC = ({ consultationId, facilityId }) => { STATUS: OFFLINE

Feed is currently not live.

-

- Click refresh button to try again. +

Trying to connect...

+

+

)} diff --git a/src/Components/Facility/Consultations/LiveFeed.tsx b/src/Components/Facility/Consultations/LiveFeed.tsx index c6ba749b471..aba473e958d 100644 --- a/src/Components/Facility/Consultations/LiveFeed.tsx +++ b/src/Components/Facility/Consultations/LiveFeed.tsx @@ -36,6 +36,7 @@ const LiveFeed = (props: any) => { const [streamStatus, setStreamStatus] = useState( StreamStatus.Offline ); + const [videoStartTime, setVideoStartTime] = useState(null); const [bed, setBed] = useState({}); const [preset, setNewPreset] = useState(""); const [loading, setLoading] = useState(); @@ -100,6 +101,16 @@ const LiveFeed = (props: any) => { }, }); + const calculateVideoLiveDelay = () => { + const video = liveFeedPlayerRef.current as HTMLVideoElement; + if (!video || !videoStartTime) return 0; + + const timeDifference = + (new Date().getTime() - videoStartTime.getTime()) / 1000; + + return timeDifference - video.currentTime; + }; + const getBedPresets = async (id: any) => { const bedAssets = await dispatch( listAssetBeds({ @@ -223,6 +234,7 @@ const LiveFeed = (props: any) => { }, reset: () => { setStreamStatus(StreamStatus.Loading); + setVideoStartTime(null); startStream({ onSuccess: () => setStreamStatus(StreamStatus.Playing), onError: () => setStreamStatus(StreamStatus.Offline), @@ -344,8 +356,25 @@ const LiveFeed = (props: any) => { playsInline className="z-10 h-full w-full" ref={liveFeedPlayerRef} + onPlay={() => { + setVideoStartTime(() => new Date()); + }} + onWaiting={() => { + const delay = calculateVideoLiveDelay(); + if (delay > 5) { + setStreamStatus(StreamStatus.Loading); + } + }} > + {streamStatus === StreamStatus.Playing && + calculateVideoLiveDelay() > 3 && ( +
+ + Slow Network Detected +
+ )} + {loading && (
diff --git a/src/Components/Facility/Consultations/NeurologicalTables.tsx b/src/Components/Facility/Consultations/NeurologicalTables.tsx index 89e9d598604..36fae175480 100644 --- a/src/Components/Facility/Consultations/NeurologicalTables.tsx +++ b/src/Components/Facility/Consultations/NeurologicalTables.tsx @@ -28,10 +28,7 @@ const DataTable = (props: any) => { Right
-
+
{data.map((x: any, i: any) => { return (
{ const LOC_OPTIONS = [ { id: 0, value: "Unknown" }, { id: 5, value: "Alert" }, - { id: 10, value: "Drowsy" }, - { id: 15, value: "Stuporous" }, - { id: 20, value: "Comatose" }, - { id: 25, value: "Cannot Be Assessed" }, + { id: 10, value: "Responds to Voice" }, + { id: 15, value: "Responds to Pain" }, + { id: 20, value: "Unresponsive" }, + { id: 25, value: "Agitated or Confused" }, + { id: 30, value: "Onset of Agitation and Confusion" }, ]; const REACTION_OPTIONS = [ @@ -297,16 +295,13 @@ export const NeurologicalTable = (props: any) => {
Level Of Consciousness
-
+
{locData.map((x: any, i: any) => (
-
+
{x.date}
@@ -376,10 +371,7 @@ export const NeurologicalTable = (props: any) => { Total
-
+
{glasgowData.map((x: any, i: any) => { return (
{ const { @@ -197,7 +225,8 @@ export const LinePlot = (props: any) => { } return ( - { @@ -81,5 +108,5 @@ export const StackedLinePlot = (props: any) => { }, series: series, }; - return ; + return ; }; diff --git a/src/Components/Facility/UpdateFacilityMiddleware.tsx b/src/Components/Facility/FacilityConfigure.tsx similarity index 98% rename from src/Components/Facility/UpdateFacilityMiddleware.tsx rename to src/Components/Facility/FacilityConfigure.tsx index 211d8cf458a..fd5754b441d 100644 --- a/src/Components/Facility/UpdateFacilityMiddleware.tsx +++ b/src/Components/Facility/FacilityConfigure.tsx @@ -46,7 +46,7 @@ const FormReducer = (state = initialState, action: any) => { } }; -export const UpdateFacilityMiddleware = (props: any) => { +export const FacilityConfigure = (props: any) => { const [state, dispatch] = useReducer(FormReducer, initialState); const { facilityId } = props; const dispatchAction: any = useDispatch(); @@ -155,6 +155,7 @@ export const UpdateFacilityMiddleware = (props: any) => { handleChange(e)} error={state.errors?.middleware_address} diff --git a/src/Components/Facility/FacilityHome.tsx b/src/Components/Facility/FacilityHome.tsx index 15317b1b56c..68990e64416 100644 --- a/src/Components/Facility/FacilityHome.tsx +++ b/src/Components/Facility/FacilityHome.tsx @@ -323,7 +323,7 @@ export const FacilityHome = (props: any) => { StaffUserTypeIndex; const editCoverImageTooltip = hasPermissionToEditCoverImage && ( -
+
{`${hasCoverImage ? "Edit" : "Upload"}`}
@@ -541,9 +541,7 @@ export const FacilityHome = (props: any) => { - navigate(`/facility/${facilityId}/middleware/update`) - } + onClick={() => navigate(`/facility/${facilityId}/configure`)} authorizeFor={NonReadOnlyUsers} icon={} > diff --git a/src/Components/Facility/Investigations/InvestigationTable.tsx b/src/Components/Facility/Investigations/InvestigationTable.tsx index 8149e18496b..54e7e50bdc7 100644 --- a/src/Components/Facility/Investigations/InvestigationTable.tsx +++ b/src/Components/Facility/Investigations/InvestigationTable.tsx @@ -2,7 +2,6 @@ import ButtonV2 from "../../Common/components/ButtonV2"; import CareIcon from "../../../CAREUI/icons/CareIcon"; import { SelectFormField } from "../../Form/FormFields/SelectFormField"; import TextFormField from "../../Form/FormFields/TextFormField"; -import _ from "lodash"; import { classNames } from "../../../Utils/utils"; import { useState } from "react"; diff --git a/src/Components/Facility/Investigations/Reports/index.tsx b/src/Components/Facility/Investigations/Reports/index.tsx index ab5380c62ca..2db928386f1 100644 --- a/src/Components/Facility/Investigations/Reports/index.tsx +++ b/src/Components/Facility/Investigations/Reports/index.tsx @@ -17,7 +17,7 @@ import { InvestigationResponse } from "./types"; import Loading from "../../../Common/Loading"; import Page from "../../../Common/components/Page"; import ReportTable from "./ReportTable"; -import _ from "lodash"; +import { chain } from "lodash-es"; import { useDispatch } from "react-redux"; import { useRef } from "react"; @@ -175,7 +175,7 @@ const InvestigationReports = ({ id }: any) => { }) ); - const investigationList = _.chain(data) + const investigationList = chain(data) .compact() .flatten() .map((i) => ({ diff --git a/src/Components/Facility/Investigations/Reports/utils.tsx b/src/Components/Facility/Investigations/Reports/utils.tsx index 485a3e1d5f4..46b95800339 100644 --- a/src/Components/Facility/Investigations/Reports/utils.tsx +++ b/src/Components/Facility/Investigations/Reports/utils.tsx @@ -1,20 +1,20 @@ -import _ from "lodash"; +import { memoize, chain, findIndex } from "lodash-es"; import { InvestigationResponse } from "./types"; -export const transformData = _.memoize((data: InvestigationResponse) => { - const sessions = _.chain(data) +export const transformData = memoize((data: InvestigationResponse) => { + const sessions = chain(data) .map((value) => value.session_object) .uniqBy("session_external_id") .orderBy("session_created_date", "desc") .value(); - const groupByInvestigation = _.chain(data) + const groupByInvestigation = chain(data) .groupBy("investigation_object.external_id") .values() .value(); const reqData = groupByInvestigation.map((value) => { const sessionValues = Array.from({ length: sessions.length }); value.forEach((val) => { - const sessionIndex = _.findIndex(sessions, [ + const sessionIndex = findIndex(sessions, [ "session_external_id", val.session_object.session_external_id, ]); @@ -55,7 +55,7 @@ export const transformData = _.memoize((data: InvestigationResponse) => { return { sessions, data: reqData }; }); -export const getColorIndex = _.memoize( +export const getColorIndex = memoize( ({ max, min, value }: { min?: number; max?: number; value?: number }) => { if (!max && min && value) { // 1 => yellow color diff --git a/src/Components/Facility/Investigations/ShowInvestigation.tsx b/src/Components/Facility/Investigations/ShowInvestigation.tsx index 4c2826f6eea..4b8d1d065a7 100644 --- a/src/Components/Facility/Investigations/ShowInvestigation.tsx +++ b/src/Components/Facility/Investigations/ShowInvestigation.tsx @@ -9,7 +9,7 @@ import { import PageTitle from "../../Common/PageTitle"; import InvestigationTable from "./InvestigationTable"; -import _ from "lodash"; +import { set, chain } from "lodash-es"; import { navigate } from "raviger"; import * as Notification from "../../../Utils/Notifications.js"; @@ -110,7 +110,7 @@ export default function ShowInvestigation(props: any) { const handleValueChange = (value: any, name: string) => { const changedFields = { ...state.changedFields }; - _.set(changedFields, name, value); + set(changedFields, name, value); dispatch({ type: "set_changed_fields", changedFields }); }; @@ -147,7 +147,7 @@ export default function ShowInvestigation(props: any) { }; const handleUpdateCancel = useCallback(() => { - const changedValues = _.chain(state.initialValues) + const changedValues = chain(state.initialValues) .map((val: any, _key: string) => ({ id: val?.id, initialValue: val?.notes || val?.value || null, diff --git a/src/Components/Facility/Investigations/Table.tsx b/src/Components/Facility/Investigations/Table.tsx index 4d6ce4c2340..4bd7e841543 100644 --- a/src/Components/Facility/Investigations/Table.tsx +++ b/src/Components/Facility/Investigations/Table.tsx @@ -1,7 +1,7 @@ import { FieldChangeEvent } from "../../Form/FormFields/Utils"; import { SelectFormField } from "../../Form/FormFields/SelectFormField"; import TextFormField from "../../Form/FormFields/TextFormField"; -import _ from "lodash"; +import { set } from "lodash-es"; import { useState } from "react"; const TestRow = ({ data, value, onChange, i }: any) => { @@ -59,7 +59,7 @@ export const TestTable = ({ title, data, state, dispatch }: any) => { const handleValueChange = (value: any, name: string) => { const form = { ...state }; - _.set(form, name, value); + set(form, name, value); dispatch({ type: "set_form", form }); }; diff --git a/src/Components/Facility/LegacyFacilityCNS.tsx b/src/Components/Facility/LegacyFacilityCNS.tsx index b3a933b9a9f..5b0005c9daf 100644 --- a/src/Components/Facility/LegacyFacilityCNS.tsx +++ b/src/Components/Facility/LegacyFacilityCNS.tsx @@ -16,7 +16,7 @@ import Pagination from "../Common/Pagination"; import { PatientModel } from "../Patient/models"; import { FacilityModel } from "./models"; import AutocompleteFormField from "../Form/FormFields/Autocomplete"; -import { uniqBy } from "lodash"; +import { uniqBy } from "lodash-es"; import DialogModal from "../Common/Dialog"; import { LegacyMonitorCard } from "./LegacyMonitorCard"; diff --git a/src/Components/Facility/LocationManagement.tsx b/src/Components/Facility/LocationManagement.tsx index 8c3bbff3227..4da95834403 100644 --- a/src/Components/Facility/LocationManagement.tsx +++ b/src/Components/Facility/LocationManagement.tsx @@ -70,6 +70,7 @@ const Location = ({ description, id, duty_staff_objects, + middleware_address, }: LocationModel) => { const doctors = duty_staff_objects?.filter((u) => u.user_type === "Doctor") || []; @@ -81,6 +82,7 @@ const Location = ({

{name}

{description}

+

{middleware_address}

{doctors.map((doctor) => (

@@ -99,7 +101,6 @@ const Location = ({

-
= { className?: string; @@ -51,6 +52,10 @@ const Form = ({ if (Object.keys(errors).length) { dispatch({ type: "set_errors", errors }); + + if (errors.$all) { + Notification.Error({ msg: errors.$all }); + } return; } } diff --git a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx index 31ac781e018..02aa03fdf71 100644 --- a/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx +++ b/src/Components/Form/FormFields/NumericWithUnitsFormField.tsx @@ -34,6 +34,7 @@ export default function NumericWithUnitsFormField(props: Props) { max={props.max} autoComplete={props.autoComplete} required={field.required} + value={numValue} onChange={(e) => field.handleChange(e.target.value + " " + unitValue)} />
diff --git a/src/Components/Form/Utils.ts b/src/Components/Form/Utils.ts index 2ec5d4b60e5..0592e81a06c 100644 --- a/src/Components/Form/Utils.ts +++ b/src/Components/Form/Utils.ts @@ -1,7 +1,9 @@ import { FieldError } from "./FieldValidators"; export type FormDetails = { [key: string]: any }; -export type FormErrors = Partial>; +export type FormErrors = Partial< + Record +>; export type FormState = { form: T; errors: FormErrors }; export type FormAction = | { type: "set_form"; form: T } diff --git a/src/Components/Medicine/CreatePrescriptionForm.tsx b/src/Components/Medicine/CreatePrescriptionForm.tsx index fb7fec5f431..2a58c632d20 100644 --- a/src/Components/Medicine/CreatePrescriptionForm.tsx +++ b/src/Components/Medicine/CreatePrescriptionForm.tsx @@ -1,4 +1,4 @@ -import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import { RequiredFieldValidator } from "../Form/FieldValidators"; import Form from "../Form/Form"; import { SelectFormField } from "../Form/FormFields/SelectFormField"; import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; @@ -11,6 +11,7 @@ import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormFi import { useTranslation } from "react-i18next"; import MedibaseAutocompleteFormField from "./MedibaseAutocompleteFormField"; import dayjs from "../../Utils/dayjs"; +import { PrescriptionFormValidator } from "./validators"; export default function CreatePrescriptionForm(props: { prescription: Prescription; @@ -40,16 +41,7 @@ export default function CreatePrescriptionForm(props: { } }} noPadding - validate={(form) => { - const errors: Partial> = {}; - errors.medicine_object = RequiredFieldValidator()(form.medicine_object); - errors.dosage = RequiredFieldValidator()(form.dosage); - if (form.is_prn) - errors.indicator = RequiredFieldValidator()(form.indicator); - if (!form.is_prn) - errors.frequency = RequiredFieldValidator()(form.frequency); - return errors; - }} + validate={PrescriptionFormValidator()} className="max-w-3xl" > {(field) => ( diff --git a/src/Components/Medicine/EditPrescriptionForm.tsx b/src/Components/Medicine/EditPrescriptionForm.tsx new file mode 100644 index 00000000000..d5261f70c7e --- /dev/null +++ b/src/Components/Medicine/EditPrescriptionForm.tsx @@ -0,0 +1,158 @@ +import { useState } from "react"; +import Form from "../Form/Form"; +import { Prescription } from "./models"; +import request from "../../Utils/request/request"; +import routes from "../../Redux/api"; +import * as Notification from "../../Utils/Notifications"; +import useSlug from "../../Common/hooks/useSlug"; +import { RequiredFieldValidator } from "../Form/FieldValidators"; +import { useTranslation } from "react-i18next"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import NumericWithUnitsFormField from "../Form/FormFields/NumericWithUnitsFormField"; +import { + PRESCRIPTION_FREQUENCIES, + PRESCRIPTION_ROUTES, +} from "./CreatePrescriptionForm"; +import TextFormField from "../Form/FormFields/TextFormField"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import { EditPrescriptionFormValidator } from "./validators"; + +interface Props { + initial: Prescription; + onDone: (created: boolean) => void; +} + +const handleSubmit = async ( + consultation_external_id: string, + oldObj: Prescription, + { discontinued_reason, ...newObj }: Prescription +) => { + const discontinue = await request(routes.discontinuePrescription, { + pathParams: { consultation_external_id, external_id: oldObj.id }, + body: { + discontinued_reason: discontinued_reason + ? `Edit: ${discontinued_reason}` + : "Edited", + }, + }); + + if (discontinue.res?.status !== 200) { + Notification.Error({ + msg: "Failed to discontinue previous prescription", + }); + return; + } + + const { res } = await request(routes.createPrescription, { + pathParams: { consultation_external_id }, + body: { + ...newObj, + // Forcing the medicine to be the same as the old one + medicine: oldObj.medicine_object?.id, + medicine_old: oldObj.medicine_old, + }, + }); + + return res?.status === 201; +}; + +export default function EditPrescriptionForm(props: Props) { + const consultation = useSlug("consultation"); + const [isLoading, setIsLoading] = useState(false); + const { t } = useTranslation(); + + return ( + + disabled={isLoading} + defaults={props.initial} + onCancel={() => props.onDone(false)} + onSubmit={async (obj) => { + setIsLoading(true); + const success = await handleSubmit(consultation, props.initial, obj); + setIsLoading(false); + + if (success) { + props.onDone(true); + } + }} + noPadding + validate={EditPrescriptionFormValidator(props.initial)} + > + {(field) => ( + <> + + +
+ t("PRESCRIPTION_ROUTE_" + key)} + optionValue={(key) => key} + /> + +
+ + {props.initial.is_prn ? ( + <> + + + `${hours} hrs.`} + optionValue={(hours) => hours} + position="above" + /> + + ) : ( +
+ + t("PRESCRIPTION_FREQUENCY_" + key.toUpperCase()) + } + optionValue={([key]) => key} + /> + +
+ )} + + + + )} + + ); +} diff --git a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx index 81282126d7c..c80b66f44c6 100644 --- a/src/Components/Medicine/PrescriptionAdministrationsTable.tsx +++ b/src/Components/Medicine/PrescriptionAdministrationsTable.tsx @@ -20,6 +20,7 @@ import { formatTime, } from "../../Utils/utils"; import useRangePagination from "../../Common/hooks/useRangePagination"; +import EditPrescriptionForm from "./EditPrescriptionForm"; interface DateRange { start: Date; @@ -47,6 +48,8 @@ export default function PrescriptionAdministrationsTable({ const { t } = useTranslation(); const [state, setState] = useState(); + const [showDiscontinued, setShowDiscontinued] = useState(false); + const [discontinuedCount, setDiscontinuedCount] = useState(); const pagination = useRangePagination({ bounds: state?.administrationsTimeBounds ?? { start: new Date(), @@ -64,8 +67,13 @@ export default function PrescriptionAdministrationsTable({ ); const refetch = useCallback(async () => { + const filters = { + is_prn: prn, + prescription_type: "REGULAR", + }; + const res = await dispatch( - list({ is_prn: prn, prescription_type: "REGULAR" }) + list(showDiscontinued ? filters : { ...filters, discontinued: false }) ); setState({ @@ -74,7 +82,14 @@ export default function PrescriptionAdministrationsTable({ ), administrationsTimeBounds: getAdministrationBounds(res.data.results), }); - }, [consultation_id, dispatch]); + + if (showDiscontinued === false) { + const discontinuedRes = await dispatch( + list({ ...filters, discontinued: true, limit: 0 }) + ); + setDiscontinuedCount(discontinuedRes.data.count); + } + }, [consultation_id, showDiscontinued, dispatch]); useEffect(() => { refetch(); @@ -141,17 +156,22 @@ export default function PrescriptionAdministrationsTable({ } /> -
- +
+
- - -
{t("medicine")} -

Dosage &

-

- {!state?.prescriptions[0]?.is_prn ? "Frequency" : "Indicator"} -

+
+
+ {t("medicine")} + +

Dosage &

+

+ {!state?.prescriptions[0]?.is_prn + ? "Frequency" + : "Indicator"} +

+
+
@@ -164,6 +184,8 @@ export default function PrescriptionAdministrationsTable({ variant="secondary" disabled={!pagination.hasPrevious} onClick={pagination.previous} + tooltip="Previous 24 hours" + tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" > @@ -205,6 +227,8 @@ export default function PrescriptionAdministrationsTable({ variant="secondary" disabled={!pagination.hasNext} onClick={pagination.next} + tooltip="Next 24 hours" + tooltipClassName="tooltip-bottom -translate-x-1/2 text-xs" > @@ -227,6 +251,23 @@ export default function PrescriptionAdministrationsTable({
+ {showDiscontinued === false && !!discontinuedCount && ( + setShowDiscontinued(true)} + > + + + + Show {discontinuedCount} other discontinued + prescription(s) + + + + )} + {state?.prescriptions.length === 0 && (
@@ -254,6 +295,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { const { t } = useTranslation(); // const [showActions, setShowActions] = useState(false); const [showDetails, setShowDetails] = useState(false); + const [showEdit, setShowEdit] = useState(false); const [showAdminister, setShowAdminister] = useState(false); const [showDiscontinue, setShowDiscontinue] = useState(false); const [administrations, setAdministrations] = @@ -285,8 +327,7 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { return ( {showDiscontinue && ( @@ -342,6 +383,21 @@ const PrescriptionRow = ({ prescription, ...props }: PrescriptionRowProps) => { {t("discontinue")} + { + setShowDetails(false); + setShowEdit(true); + }} + > + + {t("edit")} + {
)} + {showEdit && ( + setShowEdit(false)} + show={showEdit} + title={`${t("edit")} ${t( + prescription.is_prn ? "prn_prescription" : "prescription_medication" + )}: ${ + prescription.medicine_object?.name ?? prescription.medicine_old + }`} + description={ +
+ + {t("edit_caution_note")} +
+ } + className="w-full max-w-3xl lg:min-w-[600px]" + > + { + setShowEdit(false); + if (success) { + props.refetch(); + } + }} + /> +
+ )} setShowDetails(true)} > -
- +
+ + {prescription.medicine_object?.name ?? prescription.medicine_old} + + + {prescription.discontinued && ( + + {t("discontinued")} + )} - > - {prescription.medicine_object?.name ?? prescription.medicine_old} - - {prescription.discontinued && ( - - {t("discontinued")} - - )} + {prescription.route && ( + + {t(prescription.route)} + + )} +
- {prescription.route && ( - - {t(prescription.route)} - - )} +
+

{prescription.dosage}

+

+ {!prescription.is_prn + ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) + : prescription.indicator} +

+
- -

{prescription.dosage}

-

- {!prescription.is_prn - ? t("PRESCRIPTION_FREQUENCY_" + prescription.frequency) - : prescription.indicator} -

- - {/* Administration Cells */} {props.intervals.map(({ start, end }, index) => ( diff --git a/src/Components/Medicine/PrescriptionDetailCard.tsx b/src/Components/Medicine/PrescriptionDetailCard.tsx index 6da4fa7ae8d..bf27aa34068 100644 --- a/src/Components/Medicine/PrescriptionDetailCard.tsx +++ b/src/Components/Medicine/PrescriptionDetailCard.tsx @@ -5,6 +5,7 @@ import ReadMore from "../Common/components/Readmore"; import ButtonV2 from "../Common/components/ButtonV2"; import { PrescriptionActions } from "../../Redux/actions"; import { useTranslation } from "react-i18next"; +import RecordMeta from "../../CAREUI/display/RecordMeta"; export default function PrescriptionDetailCard({ prescription, @@ -29,7 +30,7 @@ export default function PrescriptionDetailCard({ prescription.discontinued && "bg-gray-200 opacity-80" )} > -
+
@@ -83,7 +84,7 @@ export default function PrescriptionDetailCard({
-
+
{prescription.medicine_object?.name ?? prescription.medicine_old} @@ -146,6 +147,23 @@ export default function PrescriptionDetailCard({ )}
+ +
+ + Prescribed + + + {prescription.discontinued && ( + + and was discontinued + + + )} +
{props.children} diff --git a/src/Components/Medicine/models.ts b/src/Components/Medicine/models.ts index 62aea46b6d2..0c49d199b21 100644 --- a/src/Components/Medicine/models.ts +++ b/src/Components/Medicine/models.ts @@ -1,7 +1,7 @@ import { PerformedByModel } from "../HCX/misc"; interface BasePrescription { - readonly id?: string; + readonly id: string; medicine?: string; medicine_object?: MedibaseMedicine; medicine_old?: string; diff --git a/src/Components/Medicine/validators.ts b/src/Components/Medicine/validators.ts new file mode 100644 index 00000000000..40261646d05 --- /dev/null +++ b/src/Components/Medicine/validators.ts @@ -0,0 +1,49 @@ +import { FieldError, RequiredFieldValidator } from "../Form/FieldValidators"; +import { FormErrors } from "../Form/Utils"; +import { Prescription } from "./models"; + +export const PrescriptionFormValidator = () => { + return (form: Prescription): FormErrors => { + const errors: Partial> = {}; + errors.medicine_object = RequiredFieldValidator()(form.medicine_object); + errors.dosage = RequiredFieldValidator()(form.dosage); + if (form.is_prn) + errors.indicator = RequiredFieldValidator()(form.indicator); + if (!form.is_prn) + errors.frequency = RequiredFieldValidator()(form.frequency); + return errors; + }; +}; + +export const EditPrescriptionFormValidator = (old: Prescription) => { + return (form: Prescription): FormErrors => { + const errors = PrescriptionFormValidator()(form); + + if (comparePrescriptions(old, form)) { + errors.$all = "No changes made"; + } + + return errors; + }; +}; + +const PRESCRIPTION_COMPARE_FIELDS: (keyof Prescription)[] = [ + "medicine", + "days", + "discontinued", + "dosage", + "frequency", + "indicator", + "is_prn", + "max_dosage", + "min_hours_between_doses", + "prescription_type", + "route", +]; + +export const comparePrescriptions = (a: Prescription, b: Prescription) => { + return ( + PRESCRIPTION_COMPARE_FIELDS.every((field) => a[field] === b[field]) && + a.medicine_object?.id === b.medicine_object?.id + ); +}; diff --git a/src/Components/Notifications/NotificationsList.tsx b/src/Components/Notifications/NotificationsList.tsx index f6afa6cccd8..5f124516a14 100644 --- a/src/Components/Notifications/NotificationsList.tsx +++ b/src/Components/Notifications/NotificationsList.tsx @@ -165,6 +165,7 @@ export default function NotificationsList({ const [isMarkingAllAsRead, setIsMarkingAllAsRead] = useState(false); const [isSubscribed, setIsSubscribed] = useState(""); const [isSubscribing, setIsSubscribing] = useState(false); + const [showUnread, setShowUnread] = useState(false); const { t } = useTranslation(); useEffect(() => { @@ -351,33 +352,39 @@ export default function NotificationsList({ } else if (data?.length) { manageResults = ( <> - {data.map((result: any) => ( - - ))} + {data + .filter((notification: any) => + showUnread ? notification.read_at === null : true + ) + .map((result: any) => ( + + ))} {isLoading && (
)} - {totalCount > RESULT_LIMIT && offset < totalCount - RESULT_LIMIT && ( -
- setOffset((prev) => prev + RESULT_LIMIT)} - > - {isLoading ? t("loading") : t("load_more")} - -
- )} + {!showUnread && + totalCount > RESULT_LIMIT && + offset < totalCount - RESULT_LIMIT && ( +
+ setOffset((prev) => prev + RESULT_LIMIT)} + > + {isLoading ? t("loading") : t("load_more")} + +
+ )} ); } else if (data && data.length === 0) { @@ -448,6 +455,21 @@ export default function NotificationsList({ /> {t("mark_all_as_read")} + setShowUnread(!showUnread)} + > + + + + {showUnread + ? t("show_all_notifications") + : t("show_unread_notifications")} + +
{ - console.log("ID:", external_id.id); - const res = await dispatch(getNotificationData({ id: external_id.id })); + const res = await dispatch(getNotificationData({ id })); const data = res.data.caused_objects; switch (res.data.event) { case "PATIENT_CREATED": diff --git a/src/Components/Patient/DailyRounds.tsx b/src/Components/Patient/DailyRounds.tsx index a84ad4a3e8b..ff749aed29e 100644 --- a/src/Components/Patient/DailyRounds.tsx +++ b/src/Components/Patient/DailyRounds.tsx @@ -1,40 +1,40 @@ import { navigate } from "raviger"; -import { useCallback, useState, useEffect, lazy } from "react"; +import dayjs from "dayjs"; +import { lazy, useCallback, useEffect, useState } from "react"; import { useDispatch } from "react-redux"; import { - TELEMEDICINE_ACTIONS, + PATIENT_CATEGORIES, REVIEW_AT_CHOICES, RHYTHM_CHOICES, - PATIENT_CATEGORIES, + TELEMEDICINE_ACTIONS, } from "../../Common/constants"; +import useAppHistory from "../../Common/hooks/useAppHistory"; import { statusType, useAbortableEffect } from "../../Common/utils"; import { createDailyReport, getConsultationDailyRoundsDetails, getDailyReport, - updateDailyReport, getPatient, + updateDailyReport, } from "../../Redux/actions"; +import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; import * as Notification from "../../Utils/Notifications"; import { formatDateTime } from "../../Utils/utils"; -import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; -import { Cancel, Submit } from "../Common/components/ButtonV2"; -import useAppHistory from "../../Common/hooks/useAppHistory"; -import { DraftSection, useAutoSaveReducer } from "../../Utils/AutoSave"; -import Page from "../Common/components/Page"; -import { FieldChangeEvent } from "../Form/FormFields/Utils"; -import TextFormField from "../Form/FormFields/TextFormField"; -import { SelectFormField } from "../Form/FormFields/SelectFormField"; -import PatientCategorySelect from "./PatientCategorySelect"; -import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; -import { SymptomsSelect } from "../Common/SymptomsSelect"; -import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; import BloodPressureFormField, { meanArterialPressure, } from "../Common/BloodPressureFormField"; +import { SymptomsSelect } from "../Common/SymptomsSelect"; import TemperatureFormField from "../Common/TemperatureFormField"; -import dayjs from "dayjs"; +import { Cancel, Submit } from "../Common/components/ButtonV2"; +import Page from "../Common/components/Page"; +import CheckBoxFormField from "../Form/FormFields/CheckBoxFormField"; +import RangeAutocompleteFormField from "../Form/FormFields/RangeAutocompleteFormField"; +import { SelectFormField } from "../Form/FormFields/SelectFormField"; +import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; +import TextFormField from "../Form/FormFields/TextFormField"; +import { FieldChangeEvent } from "../Form/FormFields/Utils"; +import PatientCategorySelect from "./PatientCategorySelect"; const Loading = lazy(() => import("../Common/Loading")); const initForm: any = { @@ -44,8 +44,7 @@ const initForm: any = { other_details: "", patient_category: "", current_health: 0, - recommend_discharge: false, - action: null, + actions: null, review_interval: 0, admitted_to: "", taken_at: null, @@ -274,7 +273,6 @@ export const DailyRounds = (props: any) => { physical_examination_info: state.form.physical_examination_info, other_details: state.form.other_details, consultation: consultationId, - recommend_discharge: JSON.parse(state.form.recommend_discharge), action: prevAction, review_interval: Number(prevReviewInterval), }; @@ -516,12 +514,6 @@ export const DailyRounds = (props: any) => { }} /> - - {state.form.rounds_type === "NORMAL" && ( <>

Vitals

diff --git a/src/Components/Patient/FileUpload.tsx b/src/Components/Patient/FileUpload.tsx index fe0881e6994..2b0b068b0ef 100644 --- a/src/Components/Patient/FileUpload.tsx +++ b/src/Components/Patient/FileUpload.tsx @@ -100,7 +100,7 @@ interface FileUploadProps { hideBack: boolean; audio?: boolean; unspecified: boolean; - sampleId?: number; + sampleId?: string; claimId?: string; } diff --git a/src/Components/Patient/ManagePatients.tsx b/src/Components/Patient/ManagePatients.tsx index 6e83eb9bc91..aa3a837647d 100644 --- a/src/Components/Patient/ManagePatients.tsx +++ b/src/Components/Patient/ManagePatients.tsx @@ -815,7 +815,7 @@ export const PatientManager = () => { }} className="mr-5 w-full lg:w-fit" > - + Export ) : ( diff --git a/src/Components/Patient/PatientInfoCard.tsx b/src/Components/Patient/PatientInfoCard.tsx index 701b76b5e3a..2d297642abb 100644 --- a/src/Components/Patient/PatientInfoCard.tsx +++ b/src/Components/Patient/PatientInfoCard.tsx @@ -5,23 +5,24 @@ import { DISCHARGE_REASONS, PATIENT_CATEGORIES, RESPIRATORY_SUPPORT, + TELEMEDICINE_ACTIONS, } from "../../Common/constants"; import { ConsultationModel, PatientCategory } from "../Facility/models"; -import ABHAProfileModal from "../ABDM/ABHAProfileModal"; -import Beds from "../Facility/Consultations/Beds"; -import ButtonV2 from "../Common/components/ButtonV2"; -import CareIcon from "../../CAREUI/icons/CareIcon"; -import DialogModal from "../Common/Dialog"; import { Link } from "raviger"; +import { useState } from "react"; +import CareIcon from "../../CAREUI/icons/CareIcon"; +import useConfig from "../../Common/hooks/useConfig"; +import { getDimensionOrDash } from "../../Common/utils"; +import dayjs from "../../Utils/dayjs"; +import { formatDate, formatDateTime } from "../../Utils/utils.js"; +import ABHAProfileModal from "../ABDM/ABHAProfileModal"; import LinkABHANumberModal from "../ABDM/LinkABHANumberModal"; import LinkCareContextModal from "../ABDM/LinkCareContextModal"; +import DialogModal from "../Common/Dialog"; +import ButtonV2 from "../Common/components/ButtonV2"; +import Beds from "../Facility/Consultations/Beds"; import { PatientModel } from "./models"; -import { getDimensionOrDash } from "../../Common/utils"; -import useConfig from "../../Common/hooks/useConfig"; -import { useState } from "react"; -import { formatAge, formatDate, formatDateTime } from "../../Utils/utils.js"; -import dayjs from "../../Utils/dayjs"; export default function PatientInfoCard(props: { patient: PatientModel; @@ -170,29 +171,44 @@ export default function PatientInfoCard(props: { Discharged from CARE

)} -

- {formatAge(patient.age, patient.date_of_birth, true)} - • - {patient.gender} - {consultation?.suggestion === "DC" && ( - <> - • - - - Domiciliary Care - - - )} - {consultation?.is_readmission && ( - <> - • - - - Readmitted - - - )} -

+
+
+ {patient.action && patient.action != 10 && ( +
+
+ + {" "} + { + TELEMEDICINE_ACTIONS.find( + (i) => i.id === patient.action + )?.desc + } + +
+
+ )} +
+
+ Age: {patient.age} years +
+
+
+
+ Gender: {patient.gender} +
+
+ {consultation?.suggestion === "DC" && ( +
+
+
+ Domiciliary Care + +
+
+
+ )} +
+
{[ ["Blood Group", patient.blood_group, patient.blood_group], diff --git a/src/Components/Patient/PatientRegister.tsx b/src/Components/Patient/PatientRegister.tsx index c515e7584c1..4fc6fef7fbe 100644 --- a/src/Components/Patient/PatientRegister.tsx +++ b/src/Components/Patient/PatientRegister.tsx @@ -61,7 +61,7 @@ import TextAreaFormField from "../Form/FormFields/TextAreaFormField"; import TextFormField from "../Form/FormFields/TextFormField"; import TransferPatientDialog from "../Facility/TransferPatientDialog"; import countryList from "../../Common/static/countries.json"; -import { debounce } from "lodash"; +import { debounce } from "lodash-es"; import useAppHistory from "../../Common/hooks/useAppHistory"; import useConfig from "../../Common/hooks/useConfig"; diff --git a/src/Components/Patient/SampleDetails.tsx b/src/Components/Patient/SampleDetails.tsx index 7814109978b..672f3905792 100644 --- a/src/Components/Patient/SampleDetails.tsx +++ b/src/Components/Patient/SampleDetails.tsx @@ -7,20 +7,16 @@ import ButtonV2 from "../Common/components/ButtonV2"; import Card from "../../CAREUI/display/Card"; import { FileUpload } from "./FileUpload"; import Page from "../Common/components/Page"; -import _ from "lodash"; import { formatAge, formatDateTime } from "../../Utils/utils"; import { getTestSample } from "../../Redux/actions"; import { navigate } from "raviger"; import { useDispatch } from "react-redux"; +import { DetailRoute } from "../../Routers/types"; const Loading = lazy(() => import("../Common/Loading")); -interface SampleDetailsProps { - id: number; -} - -export const SampleDetails = ({ id }: SampleDetailsProps) => { +export const SampleDetails = ({ id }: DetailRoute) => { const dispatch: any = useDispatch(); const [isLoading, setIsLoading] = useState(false); const [sampleDetails, setSampleDetails] = useState({}); @@ -261,12 +257,16 @@ export const SampleDetails = ({ id }: SampleDetailsProps) => {
- Status: {" "} - {_.startCase(_.camelCase(flow.status))} + + Status:{" "} + {" "} + {flow.status}
- Label:{" "} - {_.capitalize(flow.notes)} + + Label: + {" "} + {flow.notes?.toLowerCase()}
Created On :{" "} @@ -304,12 +304,16 @@ export const SampleDetails = ({ id }: SampleDetailsProps) => {
- Status: - {_.startCase(_.camelCase(sampleDetails.status))} + + Status:{" "} + + {sampleDetails.status}
- Result: - {_.startCase(_.camelCase(sampleDetails.result))} + + Result:{" "} + + {sampleDetails.result}
Patient: @@ -342,11 +346,11 @@ export const SampleDetails = ({ id }: SampleDetailsProps) => {
)} {sampleDetails.doctor_name && ( -
+
Doctor's Name:{" "} - {_.startCase(_.camelCase(sampleDetails.doctor_name))} + {sampleDetails.doctor_name}
)} {sampleDetails.diagnosis && ( @@ -426,10 +430,10 @@ export const SampleDetails = ({ id }: SampleDetailsProps) => { )} {sampleDetails.sample_type && (
- + Sample Type:{" "} - {_.startCase(_.camelCase(sampleDetails.sample_type))} + {sampleDetails.sample_type}
)}
diff --git a/src/Components/Patient/SampleTestCard.tsx b/src/Components/Patient/SampleTestCard.tsx index 847be365181..0c299f0a1b5 100644 --- a/src/Components/Patient/SampleTestCard.tsx +++ b/src/Components/Patient/SampleTestCard.tsx @@ -6,7 +6,6 @@ import { SAMPLE_TEST_STATUS } from "../../Common/constants"; import { patchSample } from "../../Redux/actions"; import * as Notification from "../../Utils/Notifications"; import UpdateStatusDialog from "./UpdateStatusDialog"; -import _ from "lodash"; import { formatDateTime } from "../../Utils/utils"; import ButtonV2 from "../Common/components/ButtonV2"; import { NonReadOnlyUsers } from "../../Utils/AuthorizeFor"; @@ -91,8 +90,8 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Status{" "}
-
- {_.startCase(_.camelCase(itemData.status))} +
+ {itemData.status?.toLowerCase()}
@@ -101,10 +100,11 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Sample Type{" "}
-
- {itemData.sample_type !== "OTHER TYPE" +
+ {(itemData.sample_type !== "OTHER TYPE" ? itemData.sample_type - : itemData.sample_type_other} + : itemData.sample_type_other + )?.toLowerCase()}
@@ -125,8 +125,8 @@ export const SampleTestCard = (props: SampleDetailsProps) => {
Result{" "}
-
- {_.startCase(_.camelCase(itemData.result))} +
+ {itemData.result?.toLowerCase()}
diff --git a/src/Components/Patient/models.tsx b/src/Components/Patient/models.tsx index 5c9e24d548b..341e13e3c80 100644 --- a/src/Components/Patient/models.tsx +++ b/src/Components/Patient/models.tsx @@ -46,6 +46,7 @@ export interface AbhaObject { export interface PatientModel { test_id?: string; id?: string; + action?: number; name?: string; age?: number; allow_transfer?: boolean; diff --git a/src/Components/Resource/ListView.tsx b/src/Components/Resource/ListView.tsx index b368c9bf3ea..24b4e263039 100644 --- a/src/Components/Resource/ListView.tsx +++ b/src/Components/Resource/ListView.tsx @@ -31,7 +31,7 @@ export default function ListView() { const { t } = useTranslation(); const onBoardViewBtnClick = () => - navigate("/resource/board-view", { query: qParams }); + navigate("/resource/board", { query: qParams }); const appliedFilters = formatFilter(qParams); const refreshList = () => { diff --git a/src/Components/Resource/ResourceBoardView.tsx b/src/Components/Resource/ResourceBoardView.tsx index de3058f9406..d3b2b202649 100644 --- a/src/Components/Resource/ResourceBoardView.tsx +++ b/src/Components/Resource/ResourceBoardView.tsx @@ -32,7 +32,7 @@ export default function BoardView() { const { t } = useTranslation(); const onListViewBtnClick = () => { - navigate("/resource/list-view", { query: qParams }); + navigate("/resource/list", { query: qParams }); localStorage.setItem("defaultResourceView", "list"); }; diff --git a/src/Components/Resource/ResourceDetails.tsx b/src/Components/Resource/ResourceDetails.tsx index 591f6b48bdb..67adc7edd82 100644 --- a/src/Components/Resource/ResourceDetails.tsx +++ b/src/Components/Resource/ResourceDetails.tsx @@ -231,7 +231,7 @@ export default function ResourceDetails(props: { id: string }) { {isPrintMode ? (
diff --git a/src/Components/Shifting/BoardView.tsx b/src/Components/Shifting/BoardView.tsx index 0eddfddf745..85815770acf 100644 --- a/src/Components/Shifting/BoardView.tsx +++ b/src/Components/Shifting/BoardView.tsx @@ -95,9 +95,7 @@ export default function BoardView() {
- navigate("/shifting/list-view", { query: qParams }) - } + onClick={() => navigate("/shifting/list", { query: qParams })} > {t("list_view")} diff --git a/src/Components/Shifting/ListView.tsx b/src/Components/Shifting/ListView.tsx index 6d55122ea11..f3fb14a4c0a 100644 --- a/src/Components/Shifting/ListView.tsx +++ b/src/Components/Shifting/ListView.tsx @@ -312,9 +312,7 @@ export default function ListView() {
- navigate("/shifting/board-view", { query: qParams }) - } + onClick={() => navigate("/shifting/board", { query: qParams })} > {t("board_view")} diff --git a/src/Components/Shifting/ShiftDetails.tsx b/src/Components/Shifting/ShiftDetails.tsx index 03e09702aac..7bffe429960 100644 --- a/src/Components/Shifting/ShiftDetails.tsx +++ b/src/Components/Shifting/ShiftDetails.tsx @@ -557,7 +557,7 @@ export default function ShiftDetails(props: { id: string }) { ) : ( { + return `${dayjs().diff(dayjs(timestamp), "minute")}m ago`; +}; + +const isWithinMinutes = (timestamp: string, minutes: number) => { + return dayjs().diff(dayjs(timestamp), "minute") < minutes; +}; export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { const { connect, waveformCanvas, data, isOnline } = useHL7VitalsMonitor( @@ -30,6 +39,10 @@ export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { connect(props.socketUrl); }, [props.socketUrl]); + const bpWithinMaxPersistence = !!( + (data.bp?.["date-time"] && isWithinMinutes(data.bp?.["date-time"], 30)) // Max blood pressure persistence is 30 minutes + ); + return (
{props.patientAssetBed && ( @@ -97,24 +110,37 @@ export default function HL7PatientVitalsMonitor(props: IVitalsComponentProps) { {/* Blood Pressure */}
-
+
NIBP - {data.bp?.systolic.unit ?? "--"} + + {bpWithinMaxPersistence ? data.bp?.systolic.unit ?? "--" : "--"} + + + {data.bp?.["date-time"] && minutesAgo(data.bp?.["date-time"])} +
Sys / Dia
- {data.bp?.systolic.value ?? "--"} + + {bpWithinMaxPersistence + ? data.bp?.systolic.value ?? "--" + : "--"} + / - {data.bp?.diastolic.value ?? "--"} + + {bpWithinMaxPersistence + ? data.bp?.diastolic.value ?? "--" + : "--"} +
Mean - {data.bp?.map.value ?? "--"} + {bpWithinMaxPersistence ? data.bp?.map.value ?? "--" : "--"}
diff --git a/src/Components/VitalsMonitor/types.ts b/src/Components/VitalsMonitor/types.ts index 066b7a7cc78..60979a6f9b0 100644 --- a/src/Components/VitalsMonitor/types.ts +++ b/src/Components/VitalsMonitor/types.ts @@ -8,7 +8,7 @@ export interface VitalsDataBase { "patient-name": string; } -export interface VitalsValueBase { +export interface VitalsValueBase extends VitalsDataBase { value: number; unit: string; interpretation: string; diff --git a/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts b/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts index 8b74a2d05d2..ed16cc2edfd 100644 --- a/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts +++ b/src/Components/VitalsMonitor/useHL7VitalsMonitor.ts @@ -8,11 +8,12 @@ import useCanvas from "../../Common/hooks/useCanvas"; import { ChannelOptions, IVitalsComponentProps, + VitalsDataBase, VitalsValueBase as VitalsValue, } from "./types"; import { getChannel, getVitalsCanvasSizeAndDuration } from "./utils"; -interface VitalsBPValue { +interface VitalsBPValue extends VitalsDataBase { systolic: VitalsValue; diastolic: VitalsValue; map: VitalsValue; diff --git a/src/Locale/en/Asset.json b/src/Locale/en/Asset.json index cf13de5cd47..f24549ee0b6 100644 --- a/src/Locale/en/Asset.json +++ b/src/Locale/en/Asset.json @@ -1,3 +1,15 @@ { - "create_asset": "Create Asset" + "create_asset": "Create Asset", + "edit_history": "Edit History", + "update_record_for_asset": "Update record for asset", + "edited_on": "Edited On", + "edited_by": "Edited By", + "serviced_on": "Serviced On", + "notes": "Notes", + "back": "Back", + "close": "Close", + "update_asset_service_record": "Update Asset Service Record", + "eg_details_on_functionality_service_etc": "Eg. Details on functionality, service, etc.", + "updating": "Updating", + "update": "Update" } diff --git a/src/Locale/en/Facility.json b/src/Locale/en/Facility.json index aa44d86dda5..5e69f8108af 100644 --- a/src/Locale/en/Facility.json +++ b/src/Locale/en/Facility.json @@ -20,5 +20,37 @@ "type_b_cylinders": "B Type Cylinders", "type_c_cylinders": "C Type Cylinders", "type_d_cylinders": "D Type Cylinders", - "select_local_body": "Select Local Body" + "select_local_body": "Select Local Body", + "update_asset": "Update Asset", + "create_new_asset": "Create New Asset", + "you_need_at_least_a_location_to_create_an_assest": "You need at least a location to create an assest.", + "add_location": "Add Location", + "close_scanner": "Close Scanner", + "scan_asset_qr": "Scan Asset QR!", + "update": "Update", + "create": "Create", + "asset_name": "Asset Name", + "asset_location": "Asset Location", + "asset_type": "Asset Type", + "asset_class": "Asset Class", + "description": "Description", + "details_about_the_equipment": "Details about the equipment", + "working_status": "Working Status", + "why_the_asset_is_not_working": "Why the asset is not working?", + "describe_why_the_asset_is_not_working": "Describe why the asset is not working", + "asset_qr_id": "Asset QR ID", + "manufacturer": "Manufacturer", + "eg_xyz": "Eg. XYZ", + "eg_abc": "Eg. ABC", + "warranty_amc_expiry": "Warranty / AMC Expiry", + "customer_support_name": "Customer Support Name", + "customer_support_number": "Customer support number", + "customer_support_email": "Customer Support Email", + "eg_mail_example_com": "Eg. mail@example.com", + "vendor_name": "Vendor Name", + "serial_number": "Serial Number", + "last_serviced_on": "Last Serviced On", + "notes": "Notes", + "create_asset": "Create Asset", + "create_add_more": "Create & Add More" } diff --git a/src/Locale/en/Medicine.json b/src/Locale/en/Medicine.json index 8cc80234b74..d32015e7618 100644 --- a/src/Locale/en/Medicine.json +++ b/src/Locale/en/Medicine.json @@ -33,7 +33,9 @@ "last_administered": "Last administered", "modification_caution_note": "No modifications possible once added", "discontinue_caution_note": "Are you sure you want to discontinue this prescription?", + "edit_caution_note": "A new prescription will be added to the consultation with the edited details and the current prescription will be discontinued.", "reason_for_discontinuation": "Reason for discontinuation", + "reason_for_edit": "Reason for edit", "PRESCRIPTION_ROUTE_ORAL": "Oral", "PRESCRIPTION_ROUTE_IV": "IV", "PRESCRIPTION_ROUTE_IM": "IM", @@ -47,4 +49,4 @@ "PRESCRIPTION_FREQUENCY_Q4H": "4th hourly", "PRESCRIPTION_FREQUENCY_QOD": "Alternate day", "PRESCRIPTION_FREQUENCY_QWK": "Once a week" -} +} \ No newline at end of file diff --git a/src/Locale/en/Notifications.json b/src/Locale/en/Notifications.json index a4770af5618..8c6255ca850 100644 --- a/src/Locale/en/Notifications.json +++ b/src/Locale/en/Notifications.json @@ -3,6 +3,8 @@ "mark_as_read": "Mark as Read", "subscribe": "Subscribe", "subscribe_on_this_device": "Subscribe on this device", + "show_unread_notifications": "Show Unread", + "show_all_notifications": "Show All", "filter_by_category": "Filter by category", "mark_all_as_read": "Mark all as Read", "reload": "Reload", diff --git a/src/Providers/AuthUserProvider.tsx b/src/Providers/AuthUserProvider.tsx index 64027a17215..d515149341e 100644 --- a/src/Providers/AuthUserProvider.tsx +++ b/src/Providers/AuthUserProvider.tsx @@ -5,6 +5,7 @@ import routes from "../Redux/api"; import useQuery from "../Utils/request/useQuery"; import { LocalStorageKeys } from "../Common/constants"; import request from "../Utils/request/request"; +import useConfig from "../Common/hooks/useConfig"; interface Props { children: React.ReactNode; @@ -12,6 +13,7 @@ interface Props { } export default function AuthUserProvider({ children, unauthorized }: Props) { + const { jwt_token_refresh_interval } = useConfig(); const { res, data, loading } = useQuery(routes.currentUser, { refetchOnWindowFocus: false, prefetch: true, @@ -24,8 +26,11 @@ export default function AuthUserProvider({ children, unauthorized }: Props) { } updateRefreshToken(true); - setInterval(() => updateRefreshToken(), 5 * 60 * 1000); // TODO: move this interval to config.json - }, [data]); + setInterval( + () => updateRefreshToken(), + jwt_token_refresh_interval ?? 5 * 60 * 1000 + ); + }, [data, jwt_token_refresh_interval]); if (loading || !res) { return ; diff --git a/src/Redux/actions.tsx b/src/Redux/actions.tsx index 6e0d91fc59d..1d2d6f4f7b6 100644 --- a/src/Redux/actions.tsx +++ b/src/Redux/actions.tsx @@ -6,8 +6,6 @@ import { } from "../Components/Medicine/models"; import { fireRequest, fireRequestForFiles } from "./fireRequest"; -import { ICreateHealthIdRequest } from "../Components/ABDM/models"; - export const getConfig = () => { return fireRequestForFiles("config"); }; @@ -501,7 +499,7 @@ export const sampleReport = (id: string, sampleId: string) => { export const getTestList = (params: object) => { return fireRequest("getTestSampleList", [], params); }; -export const getTestSample = (id: number) => { +export const getTestSample = (id: string) => { return fireRequest("getTestSample", [id], {}); }; export const patchSample = (params: object, pathParam: object) => { @@ -668,13 +666,6 @@ export const externalResultList = (params: object, altKey: string) => { export const externalResult = (pathParam: object) => { return fireRequest("externalResult", [], {}, pathParam); }; -export const externalResultUploadCsv = (params: object) => { - return fireRequest("externalResultUploadCsv", [], params); -}; - -export const deleteExternalResult = (id: string) => { - return fireRequest("deleteExternalResult", [id], {}); -}; export const updateExternalResult = (id: number, params: object) => { return fireRequest("updateExternalResult", [], params, { id }); @@ -841,96 +832,23 @@ export const listAssets = (params: object) => fireRequest("listAssets", [], params); export const createAsset = (params: object) => fireRequest("createAsset", [], params); -export const getAssetUserLocation = (params: object) => - fireRequest("getAssetUserLocation", [], params); -export const createAssetUserLocation = (params: object) => - fireRequest("createAssetUserLocation", [], params); export const getAsset = (id: string) => fireRequest("getAsset", [], {}, { external_id: id }); export const deleteAsset = (id: string) => fireRequest("deleteAsset", [], {}, { external_id: id }); export const updateAsset = (id: string, params: object) => fireRequest("updateAsset", [], params, { external_id: id }); -export const partialUpdateAsset = (id: string, params: object) => - fireRequest("partialUpdateAsset", [], params, { external_id: id }); export const operateAsset = (id: string, params: object) => fireRequest("operateAsset", [], params, { external_id: id }); -export const listAssetTransaction = (params: object) => - fireRequest("listAssetTransaction", [], params); -export const getAssetTransaction = (id: string) => - fireRequest("getAssetTransaction", [], {}, { id }); - -export const listAssetService = (params: object, asset_external_id: string) => - fireRequest("listAssetService", [], params, { asset_external_id }); -export const getAssetService = ( - params: object, - asset_external_id: string, - external_id: string -) => - fireRequest("getAssetService", [], params, { - asset_external_id, - external_id, - }); -export const updateAssetService = ( - asset_external_id: string, - external_id: string, - params: object -) => - fireRequest("updateAssetService", [], params, { - asset_external_id, - external_id, - }); - // ABDM related -export const generateAadhaarOtp = (aadhaar: string) => - fireRequest("generateAadhaarOtp", [], { aadhaar }); export const resentAadhaarOtp = (txnId: string) => fireRequest("resendAadhaarOtp", [], { txnId }); -export const verifyAadhaarOtp = (txnId: string, otp: string) => - fireRequest("verifyAadhaarOtp", [], { txnId, otp }); - export const generateMobileOtp = (txnId: string, mobile: string) => fireRequest("generateMobileOtp", [], { txnId, mobile }); -export const checkAndGenerateMobileOtp = (txnId: string, mobile: string) => - fireRequest("checkAndGenerateMobileOtp", [], { txnId, mobile }); - -export const verifyMobileOtp = (txnId: string, otp: string) => - fireRequest("verifyMobileOtp", [], { txnId, otp }); - -export const createHealthId = (data: ICreateHealthIdRequest) => - fireRequest("createHealthId", [], data); - -export const searchByHealthId = (healthId: string) => - fireRequest("searchByHealthId", [], { healthId }); - -export const initiateAbdmAuthentication = ( - authMethod: string, - healthid: string -) => fireRequest("initiateAbdmAuthentication", [], { authMethod, healthid }); - -export const confirmWithAadhaarOtp = ( - txnId: string, - otp: string, - patientId?: string -) => fireRequest("confirmWithAadhaarOtp", [], { txnId, otp, patientId }); - -export const confirmWithMobileOtp = ( - txnId: string, - otp: string, - patientId?: string -) => fireRequest("confirmWithMobileOtp", [], { txnId, otp, patientId }); - -export const linkViaQR = (abha_details: any, patientId?: string) => { - return fireRequest("linkViaQR", [], { - ...abha_details, - patientId, - }); -}; - export const linkCareContext = ( consultationId: string, data: { name?: string; gender?: "M" | "F" | "O"; dob?: string } @@ -941,53 +859,16 @@ export const linkCareContext = ( }); }; -export const getAbhaCard = (patient: string, type: "pdf" | "png") => { - return fireRequest("getAbhaCard", [], { - patient, - type, - }); -}; - export const healthFacilityActions = { list: (params: object) => { return fireRequest("listHealthFacilities", [], params); }, - create: (data: object) => { - return fireRequest("createHealthFacility", [], data); - }, - - read: (id: string) => { - return fireRequest( - "getHealthFacility", - [], - {}, - { facility_id: id }, - undefined, - true - ); - }, - update: (id: string, data: object) => { return fireRequest("updateHealthFacility", [], data, { facility_id: id, }); }, - - partialUpdate: (id: string, data: object) => { - return fireRequest("partialUpdateHealthFacility", [], data, { - facility_id: id, - }); - }, - - registerService: (id: string) => { - return fireRequest( - "registerHealthFacilityAsService", - [], - {}, - { facility_id: id } - ); - }, }; export const listAssetAvailability = (params: object) => @@ -1003,7 +884,7 @@ export const PrescriptionActions = (consultation_external_id: string) => { const pathParams = { consultation_external_id }; return { - list: (query?: Partial) => { + list: (query?: Record) => { let altKey; if (query?.is_prn !== undefined) { altKey = query?.is_prn diff --git a/src/Redux/api.tsx b/src/Redux/api.tsx index 1aa06c7e1bc..7e1ef24c058 100644 --- a/src/Redux/api.tsx +++ b/src/Redux/api.tsx @@ -1,6 +1,43 @@ import { IConfig } from "../Common/hooks/useConfig"; +import { + IAadhaarOtp, + IAadhaarOtpTBody, + ICheckAndGenerateMobileOtp, + IConfirmMobileOtp, + ICreateHealthIdRequest, + ICreateHealthIdResponse, + IGenerateMobileOtpTBody, + IHealthFacility, + IHealthId, + ILinkABHANumber, + ISearchByHealthIdTBody, + IVerifyAadhaarOtpTBody, + IcreateHealthFacilityTBody, + IgetAbhaCardTBody, + IinitiateAbdmAuthenticationTBody, + IpartialUpdateHealthFacilityTBody, +} from "../Components/ABDM/models"; import { AssetData } from "../Components/Assets/AssetTypes"; -import { LocationModel } from "../Components/Facility/models"; +import { + AssetBedBody, + AssetBedModel, + AssetData, + AssetLocationObject, + AssetService, + AssetServiceUpdate, + AssetTransaction, + AssetUpdate, +} from "../Components/Assets/AssetTypes"; +import { FacilityModel, LocationModel, WardModel } from "../Components/Facility/models"; +import { + IDeleteExternalResult, + IExternalResult, + IExternalResultCsv, + ILocalBodies, + ILocalBodyByDistrict, + IPartialUpdateExternalResult, +} from "../Components/ExternalResult/models"; +import { Prescription } from "../Components/Medicine/models"; import { UserModel } from "../Components/Users/models"; import { PaginatedResponse } from "../Utils/request/types"; @@ -17,6 +54,11 @@ interface JwtTokenObtainPair { refresh: string; } +interface LoginInput { + username: string; + password: string; +} + const routes = { config: { path: import.meta.env.REACT_APP_CONFIG ?? "/config.json", @@ -30,6 +72,8 @@ const routes = { path: "/api/v1/auth/login/", method: "POST", noAuth: true, + TRes: Type(), + TBody: Type(), }, token_refresh: { @@ -47,16 +91,25 @@ const routes = { checkResetToken: { path: "/api/v1/password_reset/check/", method: "POST", + noAuth: true, + TRes: Type>(), + TBody: Type<{ token: string }>(), }, resetPassword: { path: "/api/v1/password_reset/confirm/", method: "POST", + noAuth: true, + TRes: Type>(), + TBody: Type<{ password: string; confirm: string }>(), }, forgotPassword: { path: "/api/v1/password_reset/", method: "POST", + noAuth: true, + TRes: Type>(), + TBody: Type<{ username: string }>(), }, updatePassword: { @@ -172,10 +225,14 @@ const routes = { getPermittedFacility: { path: "/api/v1/facility/{id}/", + method: "GET", + TRes: Type(), }, getAnyFacility: { path: "/api/v1/getallfacilities/{id}/", + method: "GET", + TRes: Type(), }, updateFacility: { @@ -209,6 +266,7 @@ const routes = { getFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", method: "GET", + TRes: Type(), }, updateFacilityAssetLocation: { path: "/api/v1/facility/{facility_external_id}/asset_location/{external_id}/", @@ -223,10 +281,13 @@ const routes = { listAssetBeds: { path: "/api/v1/assetbed/", method: "GET", + TRes: Type>(), }, createAssetBed: { path: "/api/v1/assetbed/", method: "POST", + TRes: Type(), + TBody: Type(), }, getAssetBed: { path: "/api/v1/assetbed/{external_id}/", @@ -239,6 +300,8 @@ const routes = { partialUpdateAssetBed: { path: "/api/v1/assetbed/{external_id}/", method: "PATCH", + TRes: Type(), + TBody: Type(), }, deleteAssetBed: { path: "/api/v1/assetbed/{external_id}/", @@ -484,18 +547,25 @@ const routes = { // External Results externalResultList: { path: "/api/v1/external_result/", + method: "GET", + TRes: Type>(), }, externalResult: { path: "/api/v1/external_result/{id}/", + method: "GET", + TRes: Type(), }, externalResultUploadCsv: { path: "/api/v1/external_result/bulk_upsert/", method: "POST", + TBody: Type(), + TRes: Type(), }, deleteExternalResult: { - path: "/api/v1/external_result", + path: "/api/v1/external_result/{id}/", method: "DELETE", + TRes: Type(), }, updateExternalResult: { @@ -506,6 +576,8 @@ const routes = { partialUpdateExternalResult: { path: "/api/v1/external_result/{id}/", method: "PATCH", + TRes: Type(), + TBody: Type(), }, // States @@ -530,9 +602,13 @@ const routes = { }, getAllLocalBodyByDistrict: { path: "/api/v1/district/{id}/get_all_local_body/", + method: "GET", + TRes: Type(), }, getLocalbodyByDistrict: { path: "/api/v1/district/{id}/local_bodies/", + method: "GET", + TRes: Type(), }, // Local Body @@ -555,6 +631,8 @@ const routes = { }, getWardByLocalBody: { path: "/api/v1/ward/?local_body={id}", + method: "GET", + TRes: Type>(), }, // Sample Test @@ -790,6 +868,7 @@ const routes = { listAssets: { path: "/api/v1/asset", method: "GET", + TRes: Type>(), }, createAsset: { path: "/api/v1/asset/", @@ -811,6 +890,7 @@ const routes = { deleteAsset: { path: "/api/v1/asset/{external_id}/", method: "DELETE", + TRes: Type(), }, updateAsset: { path: "/api/v1/asset/{external_id}/", @@ -819,6 +899,8 @@ const routes = { partialUpdateAsset: { path: "/api/v1/asset/{external_id}/", method: "PATCH", + TRes: Type(), + TBody: Type(), }, // Asset transaction endpoints @@ -826,6 +908,7 @@ const routes = { listAssetTransaction: { path: "/api/v1/asset_transaction/", method: "GET", + TRes: Type>(), }, getAssetTransaction: { path: "/api/v1/asset_transaction/{id}", @@ -837,6 +920,7 @@ const routes = { listAssetService: { path: "/api/v1/asset/{asset_external_id}/service_records/", method: "GET", + TRes: Type>(), }, getAssetService: { path: "/api/v1/asset/{asset_external_id}/service_records/{external_id}", @@ -845,110 +929,151 @@ const routes = { updateAssetService: { path: "/api/v1/asset/{asset_external_id}/service_records/{external_id}", method: "PUT", - }, - - // ABDM HealthID endpoints - generateAadhaarOtp: { - path: "/api/v1/abdm/healthid/generate_aadhaar_otp/", - method: "POST", - }, - - resendAadhaarOtp: { - path: "/api/v1/abdm/healthid/resend_aadhaar_otp/", - method: "POST", - }, - - verifyAadhaarOtp: { - path: "/api/v1/abdm/healthid/verify_aadhaar_otp/", - method: "POST", - }, - - generateMobileOtp: { - path: "/api/v1/abdm/healthid/generate_mobile_otp/", - method: "POST", - }, - - checkAndGenerateMobileOtp: { - path: "/api/v1/abdm/healthid/check_and_generate_mobile_otp/", - method: "POST", - }, - - // TODO: resend mobile otp - verifyMobileOtp: { - path: "/api/v1/abdm/healthid/verify_mobile_otp/", - method: "POST", - }, - - createHealthId: { - path: "/api/v1/abdm/healthid/create_health_id/", - method: "POST", - }, - - searchByHealthId: { - path: "/api/v1/abdm/healthid/search_by_health_id/", - method: "POST", - }, - - initiateAbdmAuthentication: { - path: "/api/v1/abdm/healthid/auth_init/", - method: "POST", - }, - - confirmWithAadhaarOtp: { - path: "/api/v1/abdm/healthid/confirm_with_aadhaar_otp/", - method: "POST", - }, - - confirmWithMobileOtp: { - path: "/api/v1/abdm/healthid/confirm_with_mobile_otp/", - method: "POST", - }, - - linkViaQR: { - path: "/api/v1/abdm/healthid/link_via_qr/", - method: "POST", - }, - - linkCareContext: { - path: "/api/v1/abdm/healthid/add_care_context/", - method: "POST", - }, - - getAbhaCard: { - path: "/api/v1/abdm/healthid/get_abha_card/", - method: "POST", - }, - - // ABDM Health Facility - - listHealthFacility: { - path: "/api/v1/abdm/health_facility/", - method: "GET", - }, - - createHealthFacility: { - path: "/api/v1/abdm/health_facility/", - method: "POST", - }, - - getHealthFacility: { - path: "/api/v1/abdm/health_facility/{facility_id}/", - method: "GET", - }, - - updateHealthFacility: { - path: "/api/v1/abdm/health_facility/{facility_id}/", - method: "PUT", - }, - - partialUpdateHealthFacility: { - path: "/api/v1/abdm/health_facility/{facility_id}/", - method: "PATCH", - }, - - registerHealthFacilityAsService: { - path: "/api/v1/abdm/health_facility/{facility_id}/register_service/", - method: "POST", + TRes: Type(), + TBody: Type(), + }, + + abha: { + // ABDM HealthID endpoints + generateAadhaarOtp: { + path: "/api/v1/abdm/healthid/generate_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + resendAadhaarOtp: { + path: "/api/v1/abdm/healthid/resend_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + verifyAadhaarOtp: { + path: "/api/v1/abdm/healthid/verify_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + generateMobileOtp: { + path: "/api/v1/abdm/healthid/generate_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + checkAndGenerateMobileOtp: { + path: "/api/v1/abdm/healthid/check_and_generate_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + // TODO: resend mobile otp + verifyMobileOtp: { + path: "/api/v1/abdm/healthid/verify_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + createHealthId: { + path: "/api/v1/abdm/healthid/create_health_id/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + searchByHealthId: { + path: "/api/v1/abdm/healthid/search_by_health_id/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + initiateAbdmAuthentication: { + path: "/api/v1/abdm/healthid/auth_init/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + confirmWithAadhaarOtp: { + path: "/api/v1/abdm/healthid/confirm_with_aadhaar_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + confirmWithMobileOtp: { + path: "/api/v1/abdm/healthid/confirm_with_mobile_otp/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + linkViaQR: { + path: "/api/v1/abdm/healthid/link_via_qr/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + linkCareContext: { + path: "/api/v1/abdm/healthid/add_care_context/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + getAbhaCard: { + path: "/api/v1/abdm/healthid/get_abha_card/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + // ABDM Health Facility + + listHealthFacility: { + path: "/api/v1/abdm/health_facility/", + method: "GET", + }, + + createHealthFacility: { + path: "/api/v1/abdm/health_facility/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, + + getHealthFacility: { + path: "/api/v1/abdm/health_facility/{facility_id}/", + method: "GET", + TRes: Type(), + }, + + updateHealthFacility: { + path: "/api/v1/abdm/health_facility/{facility_id}/", + method: "PUT", + TRes: Type(), + TBody: Type(), + }, + + partialUpdateHealthFacility: { + path: "/api/v1/abdm/health_facility/{facility_id}/", + method: "PATCH", + TRes: Type(), + TBody: Type(), + }, + + registerHealthFacilityAsService: { + path: "/api/v1/abdm/health_facility/{facility_id}/register_service/", + method: "POST", + TRes: Type(), + TBody: Type(), + }, }, // Asset Availability endpoints @@ -972,6 +1097,8 @@ const routes = { createPrescription: { path: "/api/v1/consultation/{consultation_external_id}/prescriptions/", method: "POST", + TBody: Type(), + TRes: Type(), }, listAdministrations: { @@ -997,6 +1124,8 @@ const routes = { discontinuePrescription: { path: "/api/v1/consultation/{consultation_external_id}/prescriptions/{external_id}/discontinue/", method: "POST", + TBody: Type<{ discontinued_reason: string }>(), + TRes: Type>(), }, // HCX Endpoints diff --git a/src/Redux/fireRequest.tsx b/src/Redux/fireRequest.tsx index 9859ea540b6..bf083e23a92 100644 --- a/src/Redux/fireRequest.tsx +++ b/src/Redux/fireRequest.tsx @@ -1,6 +1,6 @@ import * as Notification from "../Utils/Notifications.js"; -import { isEmpty, omitBy } from "lodash"; +import { isEmpty, omitBy } from "lodash-es"; import { LocalStorageKeys } from "../Common/constants"; import api from "./api"; diff --git a/src/Routers/AppRouter.tsx b/src/Routers/AppRouter.tsx index f1449f13bc2..0f6108b00e3 100644 --- a/src/Routers/AppRouter.tsx +++ b/src/Routers/AppRouter.tsx @@ -1,64 +1,9 @@ import { useRedirect, useRoutes, usePath, Redirect } from "raviger"; import { useState, useEffect } from "react"; -import { ConsultationDetails } from "../Components/Facility/ConsultationDetails"; -import TreatmentSummary from "../Components/Facility/TreatmentSummary"; -import { ConsultationForm } from "../Components/Facility/ConsultationForm"; -import { FacilityCreate } from "../Components/Facility/FacilityCreate"; -import { FacilityHome } from "../Components/Facility/FacilityHome"; -import { HospitalList } from "../Components/Facility/HospitalList"; -import { TriageForm } from "../Components/Facility/TriageForm"; -import { DailyRounds } from "../Components/Patient/DailyRounds"; -import { PatientManager } from "../Components/Patient/ManagePatients"; -import PatientNotes from "../Components/Patient/PatientNotes"; -import { PatientHome } from "../Components/Patient/PatientHome"; -import { PatientRegister } from "../Components/Patient/PatientRegister"; -import { SampleDetails } from "../Components/Patient/SampleDetails"; -import SampleReport from "../Components/Patient/SamplePreview"; -import { SampleTest } from "../Components/Patient/SampleTest"; -import SampleViewAdmin from "../Components/Patient/SampleViewAdmin"; -import ManageUsers from "../Components/Users/ManageUsers"; -import { UserAdd } from "../Components/Users/UserAdd"; -import InventoryList from "../Components/Facility/InventoryList"; -import InventoryLog from "../Components/Facility/InventoryLog"; -import { AddInventoryForm } from "../Components/Facility/AddInventoryForm"; -import { SetInventoryForm } from "../Components/Facility/SetInventoryForm"; -import MinQuantityList from "../Components/Facility/MinQuantityList"; -import { ShiftCreate } from "../Components/Patient/ShiftCreate"; -import UserProfile from "../Components/Users/UserProfile"; -import ShiftBoardView from "../Components/Shifting/BoardView"; -import ShiftListView from "../Components/Shifting/ListView"; -import ShiftDetails from "../Components/Shifting/ShiftDetails"; -import { ShiftDetailsUpdate } from "../Components/Shifting/ShiftDetailsUpdate"; -import ResourceCreate from "../Components/Resource/ResourceCreate"; -import ResourceBoardView from "../Components/Resource/ResourceBoardView"; -import ResourceListView from "../Components/Resource/ListView"; -import ResourceDetails from "../Components/Resource/ResourceDetails"; -import { ResourceDetailsUpdate } from "../Components/Resource/ResourceDetailsUpdate"; -import ResultList from "../Components/ExternalResult/ResultList"; -import ResultItem from "../Components/ExternalResult/ResultItem"; -import ExternalResultUpload from "../Components/ExternalResult/ExternalResultUpload"; -import ResultUpdate from "../Components/ExternalResult/ResultUpdate"; -import { FileUpload } from "../Components/Patient/FileUpload"; -import Investigation from "../Components/Facility/Investigations"; -import ShowInvestigation from "../Components/Facility/Investigations/ShowInvestigation"; -import InvestigationReports from "../Components/Facility/Investigations/Reports"; -import AssetCreate from "../Components/Facility/AssetCreate"; -import DeathReport from "../Components/DeathReport/DeathReport"; -import { make as CriticalCareRecording } from "../Components/CriticalCareRecording/CriticalCareRecording.bs"; + import ShowPushNotification from "../Components/Notifications/ShowPushNotification"; import { NoticeBoard } from "../Components/Notifications/NoticeBoard"; -import { AddLocationForm } from "../Components/Facility/AddLocationForm"; -import { AddBedForm } from "../Components/Facility/AddBedForm"; -import LocationManagement from "../Components/Facility/LocationManagement"; -import { BedManagement } from "../Components/Facility/BedManagement"; -import AssetsList from "../Components/Assets/AssetsList"; -import AssetManage from "../Components/Assets/AssetManage"; -import AssetConfigure from "../Components/Assets/AssetConfigure"; -import { DailyRoundListDetails } from "../Components/Patient/DailyRoundListDetails"; import Error404 from "../Components/ErrorPages/404"; -import { DndProvider } from "react-dnd"; -import { HTML5Backend } from "react-dnd-html5-backend"; -import FacilityUsers from "../Components/Facility/FacilityUsers"; import { DesktopSidebar, MobileSidebar, @@ -66,349 +11,55 @@ import { SidebarShrinkContext, } from "../Components/Common/Sidebar/Sidebar"; import { BLACKLISTED_PATHS, LocalStorageKeys } from "../Common/constants"; -import { UpdateFacilityMiddleware } from "../Components/Facility/UpdateFacilityMiddleware"; import useConfig from "../Common/hooks/useConfig"; -import ConsultationClaims from "../Components/Facility/ConsultationClaims"; import { handleSignOut } from "../Utils/utils"; import SessionExpired from "../Components/ErrorPages/SessionExpired"; -import ManagePrescriptions from "../Components/Medicine/ManagePrescriptions"; -import CentralNursingStation from "../Components/Facility/CentralNursingStation"; -export default function AppRouter() { - const { main_logo, enable_hcx } = useConfig(); +import UserRoutes from "./routes/UserRoutes"; +import PatientRoutes from "./routes/PatientRoutes"; +import SampleRoutes from "./routes/SampleRoutes"; +import FacilityRoutes from "./routes/FacilityRoutes"; +import ConsultationRoutes from "./routes/ConsultationRoutes"; +import HCXRoutes from "./routes/HCXRoutes"; +import ShiftingRoutes from "./routes/ShiftingRoutes"; +import AssetRoutes from "./routes/AssetRoutes"; +import ResourceRoutes from "./routes/ResourceRoutes"; +import ExternalResultRoutes from "./routes/ExternalResultRoutes"; +import { DetailRoute } from "./types"; + +const Routes = { + "/": () => , - const routes = { - "/": () => , - "/users": () => , - "/users/add": () => , - "/user/profile": () => , - "/patients": () => , - "/patient/:id": ({ id }: any) => , - "/patient/:id/investigation_reports": ({ id }: any) => ( - - ), - "/sample": () => , - "/sample/:id": ({ id }: any) => , - "/patient/:patientId/test_sample/:sampleId/icmr_sample": ({ - patientId, - sampleId, - }: any) => , - "/facility": () => , - "/facility/create": () => , - "/facility/:facilityId/update": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/middleware/update": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/users": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/resource/new": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/triage": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/patient": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/patient/:id": ({ facilityId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:id/update": ({ facilityId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/sample-test": ({ - facilityId, - patientId, - }: any) => , - "/facility/:facilityId/patient/:patientId/sample/:id": ({ id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/notes": ({ - facilityId, - patientId, - }: any) => , - "/facility/:facilityId/patient/:patientId/files": ({ - facilityId, - patientId, - }: any) => ( - - ), - "/facility/:facilityId/triage/:id": ({ facilityId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation": ({ - facilityId, - patientId, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/update": ({ - facilityId, - patientId, - id, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/files/": ({ - facilityId, - patientId, - id, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions": - (path: any) => , - "/facility/:facilityId/patient/:patientId/consultation/:id/investigation": - ({ facilityId, patientId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/investigation/:sessionId": - ({ facilityId, patientId, id, sessionId }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:id/daily-rounds": ({ - facilityId, - patientId, - id, - }: any) => ( - - ), - ...(enable_hcx - ? { - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": - (pathParams: any) => , - } - : {}), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id/update": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), + ...AssetRoutes, + ...ConsultationRoutes, + ...ExternalResultRoutes, + ...FacilityRoutes, + ...PatientRoutes, + ...ResourceRoutes, + ...SampleRoutes, + ...ShiftingRoutes, + ...UserRoutes, - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id/update": - ({ facilityId, patientId, consultationId, id }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/shift/new": ({ - facilityId, - patientId, - }: any) => , - "/facility/:facilityId/inventory": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location/:locationId/beds": ({ - facilityId, - locationId, - }: any) => ( - - ), - "/facility/:facilityId/inventory/add": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location/add": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/location/:locationId/update": ({ - facilityId, - locationId, - }: any) => ( - - ), - "/facility/:facilityId/location/:locationId/beds/add": ({ - facilityId, - locationId, - }: any) => , - "/facility/:facilityId/location/:locationId/beds/:bedId/update": ({ - facilityId, - locationId, - bedId, - }: any) => ( - - ), - "/facility/:facilityId/inventory/min_quantity/set": ({ - facilityId, - }: any) => , - "/facility/:facilityId/inventory/min_quantity/list": ({ - facilityId, - }: any) => , - "/facility/:facilityId/inventory/min_quantity": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/inventory/:inventoryId": ({ - facilityId, - inventoryId, - }: any) => ( - - ), - "/facility/:facilityId/assets/new": ({ facilityId }: any) => ( - - ), - "/facility/:facilityId/assets/:assetId/update": ({ - facilityId, - assetId, - }: any) => , - "/assets": () => , - "/facility/:facilityId/assets/:assetId": ({ assetId, facilityId }: any) => ( - - ), - "/facility/:facilityId/assets/:assetId/configure": ({ - assetId, - facilityId, - }: any) => , - "/facility/:facilityId/cns": ({ facilityId }: any) => ( - - ), + "/notifications/:id": ({ id }: DetailRoute) => ( + + ), + "/notice_board": () => , + + "/session-expired": () => , + "/not-found": () => , +}; + +export default function AppRouter() { + const { main_logo, enable_hcx } = useConfig(); - "/shifting": () => - localStorage.getItem("defaultShiftView") === "list" ? ( - - ) : ( - - - - ), - "/shifting/board-view": () => ( - - - - ), - "/shifting/list-view": () => , - "/shifting/:id": ({ id }: any) => , - "/shifting/:id/update": ({ id }: any) => , - "/resource": () => - localStorage.getItem("defaultResourceView") === "list" ? ( - - ) : ( - - - - ), + let routes = Routes; - "/resource/board-view": () => ( - - - - ), - "/resource/list-view": () => , - "/resource/:id": ({ id }: any) => , - "/resource/:id/update": ({ id }: any) => , - "/external_results": () => , - "/external_results/upload": () => , - "/external_results/:id": ({ id }: any) => , - "/external_results/:id/update": ({ id }: any) => , - "/death_report/:id": ({ id }: any) => , - "/notifications/:id": (id: any) => ( - - ), - "/notice_board": () => , - "/facility/:facilityId/patient/:patientId/consultation/:consultationId": ({ - facilityId, - patientId, - consultationId, - }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/treatment-summary": - ({ facilityId, patientId, consultationId }: any) => ( - - ), - "/facility/:facilityId/patient/:patientId/consultation/:consultationId/:tab": - ({ facilityId, patientId, consultationId, tab }: any) => ( - - ), - "/session-expired": () => , - "/not-found": () => , - }; + if (enable_hcx) { + routes = { ...routes, ...HCXRoutes }; + } - useRedirect("/", "/facility"); useRedirect("/user", "/users"); - const pages = useRoutes(routes as any) || ; + const pages = useRoutes(routes) || ; const path = usePath(); const [sidebarOpen, setSidebarOpen] = useState(false); diff --git a/src/Routers/routes/AssetRoutes.tsx b/src/Routers/routes/AssetRoutes.tsx new file mode 100644 index 00000000000..d3bd96ca437 --- /dev/null +++ b/src/Routers/routes/AssetRoutes.tsx @@ -0,0 +1,21 @@ +import AssetConfigure from "../../Components/Assets/AssetConfigure"; +import AssetManage from "../../Components/Assets/AssetManage"; +import AssetsList from "../../Components/Assets/AssetsList"; +import AssetCreate from "../../Components/Facility/AssetCreate"; + +export default { + "/assets": () => , + + "/facility/:facilityId/assets/new": (params: any) => ( + + ), + "/facility/:facilityId/assets/:assetId/update": (params: any) => ( + + ), + "/facility/:facilityId/assets/:assetId": (params: any) => ( + + ), + "/facility/:facilityId/assets/:assetId/configure": (params: any) => ( + + ), +}; diff --git a/src/Routers/routes/ConsultationRoutes.tsx b/src/Routers/routes/ConsultationRoutes.tsx new file mode 100644 index 00000000000..4f1d6e7d75d --- /dev/null +++ b/src/Routers/routes/ConsultationRoutes.tsx @@ -0,0 +1,141 @@ +import { ConsultationForm } from "../../Components/Facility/ConsultationForm"; +import Investigation from "../../Components/Facility/Investigations"; +import ShowInvestigation from "../../Components/Facility/Investigations/ShowInvestigation"; +import ManagePrescriptions from "../../Components/Medicine/ManagePrescriptions"; +import { DailyRoundListDetails } from "../../Components/Patient/DailyRoundListDetails"; +import { DailyRounds } from "../../Components/Patient/DailyRounds"; +import { FileUpload } from "../../Components/Patient/FileUpload"; +import { make as CriticalCareRecording } from "../../Components/CriticalCareRecording/CriticalCareRecording.bs"; +import { ConsultationDetails } from "../../Components/Facility/ConsultationDetails"; +import TreatmentSummary from "../../Components/Facility/TreatmentSummary"; + +export default { + "/facility/:facilityId/patient/:patientId/consultation": ({ + facilityId, + patientId, + }: any) => , + "/facility/:facilityId/patient/:patientId/consultation/:id/update": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/files/": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/prescriptions": + (path: any) => , + "/facility/:facilityId/patient/:patientId/consultation/:id/investigation": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/investigation/:sessionId": + ({ facilityId, patientId, id, sessionId }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:id/daily-rounds": ({ + facilityId, + patientId, + id, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id/update": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily-rounds/:id": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/daily_rounds/:id/update": + ({ facilityId, patientId, consultationId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId": ({ + facilityId, + patientId, + consultationId, + }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/treatment-summary": + ({ facilityId, patientId, consultationId }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/:tab": + ({ facilityId, patientId, consultationId, tab }: any) => ( + + ), +}; diff --git a/src/Routers/routes/ExternalResultRoutes.tsx b/src/Routers/routes/ExternalResultRoutes.tsx new file mode 100644 index 00000000000..af4bf090d78 --- /dev/null +++ b/src/Routers/routes/ExternalResultRoutes.tsx @@ -0,0 +1,14 @@ +import ExternalResultUpload from "../../Components/ExternalResult/ExternalResultUpload"; +import ResultItem from "../../Components/ExternalResult/ResultItem"; +import ResultList from "../../Components/ExternalResult/ResultList"; +import ResultUpdate from "../../Components/ExternalResult/ResultUpdate"; +import { DetailRoute } from "../types"; + +export default { + "/external_results": () => , + "/external_results/upload": () => , + "/external_results/:id": ({ id }: DetailRoute) => , + "/external_results/:id/update": ({ id }: DetailRoute) => ( + + ), +}; diff --git a/src/Routers/routes/FacilityInventoryRoutes.tsx b/src/Routers/routes/FacilityInventoryRoutes.tsx new file mode 100644 index 00000000000..17e93b2bc60 --- /dev/null +++ b/src/Routers/routes/FacilityInventoryRoutes.tsx @@ -0,0 +1,24 @@ +import { Redirect } from "raviger"; +import InventoryList from "../../Components/Facility/InventoryList"; +import InventoryLog from "../../Components/Facility/InventoryLog"; +import MinQuantityList from "../../Components/Facility/MinQuantityList"; +import { SetInventoryForm } from "../../Components/Facility/SetInventoryForm"; + +export default { + "/facility/:facilityId/inventory": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/inventory/min_quantity/set": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/inventory/min_quantity/list": ({ + facilityId, + }: any) => , + "/facility/:facilityId/inventory/min_quantity": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/inventory/:inventoryId": ({ + facilityId, + inventoryId, + }: any) => , +}; diff --git a/src/Routers/routes/FacilityLocationRoutes.tsx b/src/Routers/routes/FacilityLocationRoutes.tsx new file mode 100644 index 00000000000..c43673b60f5 --- /dev/null +++ b/src/Routers/routes/FacilityLocationRoutes.tsx @@ -0,0 +1,38 @@ +import { AddBedForm } from "../../Components/Facility/AddBedForm"; +import { AddInventoryForm } from "../../Components/Facility/AddInventoryForm"; +import { AddLocationForm } from "../../Components/Facility/AddLocationForm"; +import { BedManagement } from "../../Components/Facility/BedManagement"; +import LocationManagement from "../../Components/Facility/LocationManagement"; + +export default { + "/facility/:facilityId/location": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/location/:locationId/beds": ({ + facilityId, + locationId, + }: any) => , + "/facility/:facilityId/inventory/add": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/location/add": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/location/:locationId/update": ({ + facilityId, + locationId, + }: any) => ( + + ), + "/facility/:facilityId/location/:locationId/beds/add": ({ + facilityId, + locationId, + }: any) => , + "/facility/:facilityId/location/:locationId/beds/:bedId/update": ({ + facilityId, + locationId, + bedId, + }: any) => ( + + ), +}; diff --git a/src/Routers/routes/FacilityRoutes.tsx b/src/Routers/routes/FacilityRoutes.tsx new file mode 100644 index 00000000000..77247df9189 --- /dev/null +++ b/src/Routers/routes/FacilityRoutes.tsx @@ -0,0 +1,45 @@ +import { FacilityConfigure } from "../../Components/Facility/FacilityConfigure"; +import { FacilityCreate } from "../../Components/Facility/FacilityCreate"; +import { FacilityHome } from "../../Components/Facility/FacilityHome"; +import FacilityUsers from "../../Components/Facility/FacilityUsers"; +import { HospitalList } from "../../Components/Facility/HospitalList"; +import { TriageForm } from "../../Components/Facility/TriageForm"; +import ResourceCreate from "../../Components/Resource/ResourceCreate"; +import CentralNursingStation from "../../Components/Facility/CentralNursingStation"; +import FacilityLocationRoutes from "./FacilityLocationRoutes"; +import FacilityInventoryRoutes from "./FacilityInventoryRoutes"; + +export default { + "/facility": () => , + "/facility/create": () => , + "/facility/:facilityId/update": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/configure": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/cns": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId": ({ facilityId }: any) => ( + + ), + + "/facility/:facilityId/users": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/resource/new": ({ facilityId }: any) => ( + + ), + + // Triage related routes + "/facility/:facilityId/triage": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/triage/:id": ({ facilityId, id }: any) => ( + + ), + + ...FacilityLocationRoutes, + ...FacilityInventoryRoutes, +}; diff --git a/src/Routers/routes/HCXRoutes.tsx b/src/Routers/routes/HCXRoutes.tsx new file mode 100644 index 00000000000..8a36e033c15 --- /dev/null +++ b/src/Routers/routes/HCXRoutes.tsx @@ -0,0 +1,6 @@ +import ConsultationClaims from "../../Components/Facility/ConsultationClaims"; + +export default { + "/facility/:facilityId/patient/:patientId/consultation/:consultationId/claims": + (pathParams: any) => , +}; diff --git a/src/Routers/routes/PatientRoutes.tsx b/src/Routers/routes/PatientRoutes.tsx new file mode 100644 index 00000000000..ae594d767ec --- /dev/null +++ b/src/Routers/routes/PatientRoutes.tsx @@ -0,0 +1,46 @@ +import InvestigationReports from "../../Components/Facility/Investigations/Reports"; +import { FileUpload } from "../../Components/Patient/FileUpload"; +import { PatientManager } from "../../Components/Patient/ManagePatients"; +import { PatientHome } from "../../Components/Patient/PatientHome"; +import PatientNotes from "../../Components/Patient/PatientNotes"; +import { PatientRegister } from "../../Components/Patient/PatientRegister"; +import { DetailRoute } from "../types"; +import DeathReport from "../../Components/DeathReport/DeathReport"; + +export default { + "/patients": () => , + "/patient/:id": ({ id }: DetailRoute) => , + "/patient/:id/investigation_reports": ({ id }: DetailRoute) => ( + + ), + + // Facility Scoped Routes + "/facility/:facilityId/patient": ({ facilityId }: any) => ( + + ), + "/facility/:facilityId/patient/:id": ({ facilityId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:id/update": ({ facilityId, id }: any) => ( + + ), + "/facility/:facilityId/patient/:patientId/notes": ({ + facilityId, + patientId, + }: any) => , + "/facility/:facilityId/patient/:patientId/files": ({ + facilityId, + patientId, + }: any) => ( + + ), + "/death_report/:id": ({ id }: any) => , +}; diff --git a/src/Routers/routes/ResourceRoutes.tsx b/src/Routers/routes/ResourceRoutes.tsx new file mode 100644 index 00000000000..8408ab4d79d --- /dev/null +++ b/src/Routers/routes/ResourceRoutes.tsx @@ -0,0 +1,25 @@ +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"; +import BoardView from "../../Components/Resource/ResourceBoardView"; +import { Redirect } from "raviger"; +import { DetailRoute } from "../types"; + +const getDefaultView = () => + localStorage.getItem("defaultResourceView") === "list" ? "list" : "board"; + +export default { + "/resource": () => , + "/resource/board": () => ( + + + + ), + "/resource/list": () => , + "/resource/:id": ({ id }: DetailRoute) => , + "/resource/:id/update": ({ id }: DetailRoute) => ( + + ), +}; diff --git a/src/Routers/routes/SampleRoutes.tsx b/src/Routers/routes/SampleRoutes.tsx new file mode 100644 index 00000000000..290a34fd4eb --- /dev/null +++ b/src/Routers/routes/SampleRoutes.tsx @@ -0,0 +1,25 @@ +import { SampleDetails } from "../../Components/Patient/SampleDetails"; +import SampleReport from "../../Components/Patient/SamplePreview"; +import { SampleTest } from "../../Components/Patient/SampleTest"; +import SampleViewAdmin from "../../Components/Patient/SampleViewAdmin"; +import { DetailRoute, RouteParams } from "../types"; + +export default { + "/sample": () => , + "/sample/:id": ({ id }: DetailRoute) => , + "/patient/:patientId/test_sample/:sampleId/icmr_sample": ({ + patientId, + sampleId, + }: RouteParams<"patientId" | "sampleId">) => ( + + ), + "/facility/:facilityId/patient/:patientId/sample-test": ({ + facilityId, + patientId, + }: RouteParams<"facilityId" | "patientId">) => ( + + ), + "/facility/:facilityId/patient/:patientId/sample/:id": ({ + id, + }: DetailRoute) => , +}; diff --git a/src/Routers/routes/ShiftingRoutes.tsx b/src/Routers/routes/ShiftingRoutes.tsx new file mode 100644 index 00000000000..9b20b4a1a0b --- /dev/null +++ b/src/Routers/routes/ShiftingRoutes.tsx @@ -0,0 +1,27 @@ +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"; +import ListView from "../../Components/Shifting/ListView"; +import BoardView from "../../Components/Shifting/BoardView"; +import { Redirect } from "raviger"; + +const getDefaultView = () => + localStorage.getItem("defaultShiftView") === "list" ? "list" : "board"; + +export default { + "/shifting": () => , + "/shifting/board": () => ( + + + + ), + "/shifting/list": () => , + "/shifting/:id": ({ id }: any) => , + "/shifting/:id/update": ({ id }: any) => , + "/facility/:facilityId/patient/:patientId/shift/new": ({ + facilityId, + patientId, + }: any) => , +}; diff --git a/src/Routers/routes/UserRoutes.tsx b/src/Routers/routes/UserRoutes.tsx new file mode 100644 index 00000000000..56877ca4c78 --- /dev/null +++ b/src/Routers/routes/UserRoutes.tsx @@ -0,0 +1,9 @@ +import ManageUsers from "../../Components/Users/ManageUsers"; +import { UserAdd } from "../../Components/Users/UserAdd"; +import UserProfile from "../../Components/Users/UserProfile"; + +export default { + "/users": () => , + "/users/add": () => , + "/user/profile": () => , +}; diff --git a/src/Routers/types.ts b/src/Routers/types.ts new file mode 100644 index 00000000000..dc7138b8df7 --- /dev/null +++ b/src/Routers/types.ts @@ -0,0 +1,5 @@ +export type RouteParams = Record; + +export interface DetailRoute { + id: string; +} diff --git a/src/Utils/Notifications.js b/src/Utils/Notifications.js index b4393049531..298622c35ca 100644 --- a/src/Utils/Notifications.js +++ b/src/Utils/Notifications.js @@ -1,6 +1,6 @@ import { alert, Stack, defaultModules } from "@pnotify/core"; import * as PNotifyMobile from "@pnotify/mobile"; -import _ from "lodash"; +import { startCase, camelCase } from "lodash-es"; defaultModules.set(PNotifyMobile, {}); @@ -43,7 +43,7 @@ const notifyError = (error) => { errorMsg = error.detail; } else { for (let [key, value] of Object.entries(error)) { - let keyName = _.startCase(_.camelCase(key)); + let keyName = startCase(camelCase(key)); if (Array.isArray(value)) { const uniques = [...new Set(value)]; errorMsg += `${keyName} - ${uniques.splice(0, 5).join(", ")}`; diff --git a/src/Utils/request/request.ts b/src/Utils/request/request.ts index 2d735734aae..7cfb647d4b7 100644 --- a/src/Utils/request/request.ts +++ b/src/Utils/request/request.ts @@ -38,7 +38,8 @@ export default async function request( try { const res = await fetch(url, options); - const data: TData = await res.json(); + + const data = await getResponseBody(res); result = { res, @@ -61,3 +62,21 @@ export default async function request( ); return result; } + +async function getResponseBody(res: Response): Promise { + if (!(res.headers.get("content-length") !== "0")) { + return null as TData; + } + + const isJson = res.headers.get("content-type")?.includes("application/json"); + + if (!isJson) { + return (await res.text()) as TData; + } + + try { + return await res.json(); + } catch { + return (await res.text()) as TData; + } +}